From 219e9369198b4db169aad97871cba76c135720e2 Mon Sep 17 00:00:00 2001 From: kndiaye Date: Thu, 6 Mar 2014 17:21:15 +0100 Subject: [PATCH] Initial commit --- .gitignore | 2 + 3rdparty/dualcursor.m | 846 ++ 3rdparty/findjobj.m | 2793 ++++ 3rdparty/jewelersLoupe.m | 122 + 3rdparty/m2googlevis.m | 105 + 3rdparty/magnify.m | 139 + @int32/sparse.m | 11 + FindConstInMFiles.m | 58 + MRslice.m | 214 + Plot2dHist.m | 37 + Tr2event.m | 35 + WhatsThisImage.m | 74 + WilcoxonSRPVs.m | 104 + absolutepath.m | 156 + affinetransform.m | 72 + allhandles.m | 47 + allthesame.m | 60 + allunique.m | 31 + aprime.m | 150 + argouts.m | 13 + art/rectpix.m | 18 + art/rotbar.m | 43 + art/test.jpg | Bin 0 -> 99700 bytes avevar.m | 17 + bardata.m | 22 + barerrorbar.m | 107 + barstem.m | 84 + barweb.m | 170 + baseline_correction.m | 33 + beamformerkernel.m | 25 + between.m | 41 + bibliography/hindex.m | 176 + bibliography/pmid2bib.m | 16 + bit.m | 12 + bitget2.m | 26 + blackguis.m | 15 + bootstrap.m | 47 + brainstorm/bst_lighting.m | 81 + brainstorm/bst_struct2fv.m | 35 + brainstorm/bst_surface2fv.m | 44 + brainstorm/bst_trimSurface.m | 41 + brainstorm/carto_cortex.m | 153 + brainstorm/carto_flatcap.m | 209 + brainstorm/carto_flatdisc.m | 346 + brainstorm/cortex_menu.m | 473 + brainstorm/ctf_readClassFile.m | 0 brainstorm/ds2brainstorm.m | 1258 ++ brainstorm/getChannelLocs.m | 4 + brainstorm/getPositionChannel.m | 4 + brainstorm/meeg_menu.m | 21 + brainstorm/meegui_menu.m | 39 + brainstorm/mybrainstorm_toolbar.m | 134 + brainstorm/scout_button.m | 147 + brainstorm/scout_menu.m | 125 + brainstorm/scout_swell.m | 30 + brainstorm/scouts_menu.m | 26 + brainstorm/vertconn2adjacency.m | 61 + brainstorm/view_MNEresults.m | 336 + brainstorm/viewpoint_menu.m | 199 + brainstorm3.m | 26 + brainstorm3/ExtThreshold.zip | Bin 0 -> 83824 bytes brainstorm3/bst_batch.m | 1276 ++ brainstorm3/bst_rmanova.m | 5 + brainstorm3/bst_stat_struct.mat | Bin 0 -> 8736 bytes brainstorm3/guess_factorial_design.m | 15 + brainstorm3/gui_stat_common.m.mine | 315 + brainstorm3/import_anova.m | 438 + brainstorm3/knd_rmanova_files.m | 180 + brainstorm3/om_load_full.m | 6 + brainstorm3/private/gui_setEnabledControls.m | 62 + brainstorm3/private/gui_stat_common.m | 311 + brainstorm3/sTargetStudy_struct.mat | Bin 0 -> 1336 bytes brainstorm3/script_anova.m | 52 + brainstorm3/script_import_Fmap.m | 135 + brainstorm3/script_import_anova.m | 136 + brainstorm3/script_import_anova_New1.m | 132 + brainstorm3/script_import_anova_Old1.m | 118 + brainstorm3/svn_diff.csh | 22 + brainstorm3/svn_diff.csh~ | 11 + brainstorm3/toolbox/core/bst_getContext.m | 2006 +++ brainstorm3/toolbox/core/bst_safeCall.m | 46 + brainstorm3/toolbox/db/db_getDataTemplate.m | 538 + brainstorm3/toolbox/gui/gui_figure3DViz.m | 2183 +++ brainstorm3/toolbox/gui/panel_scouts.m | 2678 ++++ brainstorm3/toolbox/gui/panel_stat.m | 303 + brainstorm3/toolbox/gui/panel_statRun.m | 1555 ++ brainstorm3/toolbox/gui/panel_statRun.m.orig | 1547 ++ brainstorm3/toolbox/gui/panel_surface.m | 1840 +++ brainstorm3/toolbox/io/import_data.m | 545 + brainstorm3/toolbox/misc/mri_drawCuts.m | 230 + brainstorm3/toolbox/stat/bst_batch.m | 1258 ++ brainstorm3/tvalue_paired.mexglx | Bin 0 -> 7442 bytes brainvisa/writemesh.m | 80 + brodmann/MNITemplate_brodmann.m | 195 + brodmann/MNI_brodmann.m | 80 + brodmann/MNI_brodmann_karimovitch.m | 170 + brodmann/MNI_karimovitch.m | 42 + brodmann/TTareas2TTgyri.m | 4 + brodmann/TTbasta.mat | Bin 0 -> 1992496 bytes brodmann/TTdaemon.mat | Bin 0 -> 3091840 bytes brodmann/TalairachDaemonDatabase.m | 132 + brodmann/atlascomparator.m | 136 + brodmann/atlasviewer.m | 97 + brodmann/ba_color.m | 102 + brodmann/brodmann.m | 122 + brodmann/correct_flip_tess.m | 47 + brodmann/img2voxels.m | 17 + brodmann/labelbrainstorm.m | 191 + brodmann/labelize.m | 52 + brodmann/labelviewer.m | 80 + brodmann/makeTTbasta.m | 53 + brodmann/make_aal_colormap.m | 148 + brodmann/mergeBA.m | 32 + brodmann/mergeBA.txt | 24 + brodmann/merge_cluster.m | 15 + brodmann/montreal_brodmann.m | 162 + bugs/emptiness.m | 163 + cartool/J_segmentation.m | 297 + cartool/converts.m | 16 + cartool/ctf2cartool.m | 40 + cartool/event2mrkfile.m | 29 + cartool/pierre/computeavgref.m | 23 + cartool/pierre/computedataagainstref.m | 34 + cartool/pierre/computedissandsc.m | 33 + cartool/pierre/computegfp.m | 24 + cartool/pierre/openels.m | 38 + cartool/pierre/openeph.m | 59 + cartool/pierre/openfreq.m | 53 + cartool/pierre/opensef.m | 39 + cartool/pierre/saveeph.m | 36 + cartool/pierre/savesef.m | 61 + cartool/read_eeg.m | 47 + cartool/read_eph.m | 7 + cartool/read_is.m | 28 + cartool/read_mrk.m | 33 + cartool/read_ris.m | 28 + cartool/read_sef.m | 55 + cartool/read_spi.m | 4 + cartool/read_xyz.m | 6 + cartool/write_sef.m | 78 + catstructs.m | 118 + ccaxis.m | 46 + cdf.m | 77 + cell2str.m | 68 + cellcat.m | 19 + cellfun2.m | 31 + circle3.m | 19 + circpermarray.m | 29 + clickableLegend.m | 56 + closest.m | 15 + cluster_threshold.m | 68 + clustering.m | 117 + clusters2faces.m | 29 + clusters2patches.m | 54 + cnrs_section27.m | 101 + cogent/Textquestions.m | 135 + cogent/discrethopper.m | 146 + cogent/questionnaire.m | 170 + cogent/semislider.m | 166 + cogent/slider.m | 115 + coldhot.m | 26 + colon2.m | 44 + colorbar_slider.m | 97 + colormap_threshold.m | 85 + colorname2rgb.m | 64 + columnlegend.m | 132 + compute_forward.m | 2660 ++++ compute_inverse.m | 862 ++ concatenatepatch.m | 35 + continuity.m | 41 + corrcoef2.m | 147 + cov2.m | 28 + curvature_cortex.m | 134 + data/COLORS.mat | Bin 0 -> 488 bytes data/PSC.mat | Bin 0 -> 416 bytes data/SUJETS.mat | Bin 0 -> 208 bytes data/nicehead-CTF.tri | 3514 +++++ data/nicehead.mat | Bin 0 -> 43712 bytes data/repromn.m | 53 + data/test2.wrl | 30 + dec2basen.m | 33 + decours.m | 256 + deg2rad.m | 10 + depfuntoolbox.m | 62 + detrend2.m | 65 + dgrouping.m | 67 + dirr.m | 279 + drag.m | 289 + dumpmemmex.dll | Bin 0 -> 36864 bytes dunnsidak.m | 34 + e2pie.m | 183 + edist.c | 68 + edist.dll | Bin 0 -> 18432 bytes edist.dll.old | Bin 0 -> 18432 bytes edist.mexglx | Bin 0 -> 6117 bytes edist.mexw32 | Bin 0 -> 6656 bytes edist.pdb | Bin 0 -> 274432 bytes eeglab/chanlocs2bstchannel.m | 18 + eeglab/eeglab2brainstorm.m | 0 eeglab/icadefs.m | 97 + eeglab/plugins/LENA/eegplugin_lena.m | 33 + .../LENA/lenaformat_files/image001.gif | Bin 0 -> 3798 bytes eeglab/plugins/LENA/pop_readLENA.m | 175 + eeglab/plugins/LENA/readLENA.m | 143 + eeglab/plugins/bstorm/WS_FTP.LOG | 3 + eeglab/plugins/bstorm/bstchannel2chanlocs.m | 12 + .../plugins/bstorm/bstormchannels2chanlocs.m | 7 + .../bstorm/bstormchannels2chanlocs_gui.m | 47 + eeglab/plugins/bstorm/eegplugin_bstorm.m | 28 + eeglab/plugins/bstorm/pop_readbstorm.m | 147 + eeglab/plugins/bstorm/readbstorm.m | 85 + eeglab/plugins/bstorm/v1/pop_readbstorm.m | 156 + eeglab/plugins/bstorm/v1/readbstorm.m | 106 + .../bstorm1.01/bstormchannels2chanlocs.m | 67 + eeglab/plugins/bstorm1.01/eegplugin_bstorm.m | 69 + eeglab/plugins/bstorm1.01/pop_readbstorm.m | 161 + eeglab/plugins/bstorm1.01/readbstorm.m | 107 + eeglab/plugins/bstorm1.01/v1/pop_readbstorm.m | 156 + eeglab/plugins/bstorm1.01/v1/readbstorm.m | 106 + eeglab/plugins/ctf/ds2brainstorm.m | 977 ++ eeglab/plugins/ctf/importctf.m | 114 + eeglab/plugins/ctf/pop_importctf.m | 157 + ellipse.m | 149 + elproj.m | 105 + entropy.m | 32 + entropy2.m | 68 + epilepsie/dataplot_cb.m | 6685 +++++++++ epilepsie/mergedata.m | 41 + epilepsie/mergeresults.m | 47 + epilepsie/scriptStudySubject.m | 50 + epilepsie/splitdata.m | 20 + epilepsie/view_cortex.m | 54 + epilepsie/view_minnorm_cb.m | 451 + epilepsie/view_topography_cb.m | 849 ++ extrema.m | 146 + extrema2.m | 174 + fakebar.m | 43 + fftfilter.m | 183 + field2struct.m | 39 + fieldtriptopo.m | 162 + fig2pdf.m | 68 + figure_call.m | 62 + filefinder.m | 62 + fileparts2.m | 74 + fillarray.m | 117 + findTessellationHandles.m | 92 + findclosest2.m | 60 + findinstruct.m | 57 + findn.m | 33 + finitefun.m | 37 + forcecell.m | 10 + fread2.m | 86 + fullfiles.m | 41 + geod_cluster.m | 218 + geod_cluster2.m | 218 + getfield2.m | 117 + gfp.m | 6 + globaldissimilarity.m | 29 + gower.m | 23 + grayhot.m | 9 + grayish.m | 37 + greyish.m | 75 + groupfun.m | 85 + groupfun.m.mine | 78 + groupfun.m.r565 | 74 + groupfun.m.r684 | 75 + hash.m | 79 + headplot2.m | 797 + hindex.m | 176 + hist2d.m | 67 + histfun.m | 69 + histk.m | 69 + histo2D.m | 31 + hline.m | 58 + horizalign.m | 30 + hostname.m | 4 + hot2.m | 32 + huemap.m | 52 + icatb/icatb_check_path.m | 92 + icatb/icatb_displayGUI.m | 1690 +++ icatb/icatb_drawTimecourses.m | 924 ++ icatb/icatb_loadSPM_new.m | 467 + idxmember.m | 28 + iff.m | 42 + image_spm.m | 59 + imagepval.m | 10 + imax.m | 28 + img_labels.m | 18 + img_slice.m | 175 + imgfilt.m | 143 + ind2sub2.m | 49 + interpolate_cortex1.m | 107 + isfield2.m | 42 + ismember2.m | 13 + isort.m | 4 + isscalar2.m | 13 + issfield.m | 12 + javauigetfiles/MatlabR13FileFilter.class | Bin 0 -> 1870 bytes javauigetfiles/MatlabR13FileFilter.java | 58 + javauigetfiles/uiGetFiles.m | 88 + karim.m | 28 + labelbads.m | 33 + labelize2.m | 57 + labels2parcels.m | 46 + labnic/Labnic_1st_analysis.m | 203 + labnic/Labnic_1st_contrast_spm5.m | 107 + labnic/Labnic_1stlevel_spm8.m | 205 + labnic/Labnic_Anova_2groups_spm8.m | 187 + labnic/Labnic_Anova_spm8.m | 97 + labnic/Labnic_PPI_spm8.m | 753 + labnic/Labnic_rfx_ones_spm8.m | 196 + labnic/Labnic_rfx_twos_spm5.m | 205 + labnic/extract_clubetas.m | 292 + labnic/nic_spm_explorer.m | 80 + labnic/nifti2img.m | 860 ++ labnic/ps2pdf.m | 385 + labnic/read_eprimetxt.m | 252 + labnic/references_batches_spm8 [WikiNIC].html | 6432 ++++++++ .../button-css.png | Bin 0 -> 299 bytes .../button-donate.gif | Bin 0 -> 187 bytes .../button-dw.png | Bin 0 -> 427 bytes .../button-php.gif | Bin 0 -> 207 bytes .../button-rss.png | Bin 0 -> 280 bytes .../button-xhtml.png | Bin 0 -> 321 bytes .../cc-by-nc-sa.png | Bin 0 -> 686 bytes .../css.css | 1 + .../css_002.css | 1 + .../css_003.css | 1 + .../indexer.gif | Bin 0 -> 42 bytes .../js.js | 1 + labnic/references_software [WikiNIC].html | 2423 +++ .../button-css.png | Bin 0 -> 299 bytes .../button-donate.gif | Bin 0 -> 187 bytes .../button-dw.png | Bin 0 -> 427 bytes .../button-php.gif | Bin 0 -> 207 bytes .../button-rss.png | Bin 0 -> 280 bytes .../button-xhtml.png | Bin 0 -> 321 bytes .../cc-by-nc-sa.png | Bin 0 -> 686 bytes .../css.css | 1 + .../css_002.css | 1 + .../css_003.css | 1 + .../icon_wink.gif | Bin 0 -> 170 bytes .../indexer.gif | Bin 0 -> 42 bytes .../js.js | 1 + labnic/spm5_preprocessing_labnic.m | 279 + labnic/spm8_preprocessing_labnic.m | 274 + labnic/swr_import_DICOM.m | 229 + labnic_spm_defaults.m | 126 + lacie.m | 26 + lena/@datahandler/compute.m | 9 + lena/@datahandler/convert.m | 93 + lena/@datahandler/data.m | 17 + lena/@datahandler/datahandler.m | 86 + lena/@datahandler/datahandler_classdef.m | 62 + lena/@datahandler/file.m | 0 lena/@datahandler/get.m | 13 + lena/@datahandler/sensornames.m | 22 + lena/@datahandler/set.m | 9 + lena/@lena/lena.m | 48 + lena/create_events.m | 16 + lena/ctf_folder.m | 106 + lena/ctf_read_classfile.m | 254 + lena/ctf_read_markerfile.m | 188 + lena/extractBINblocksRevised4SensorsBis.m | 427 + lena/lena2brainstorm.m | 48 + lena/ptx2events.m | 27 + lena/readLENA.m | 150 + lena/readLENA.m~ | 147 + lena/readLENAHead.m | 568 + lena/read_ClassFile.m | 53 + lena/read_events.m | 32 + lena/read_lena.m | 475 + lena/read_lena.m~ | 474 + lena/read_lena_classes.m | 37 + lena/read_lena_events.m | 79 + lena/read_ptx.m | 45 + lena/read_trc.m | 119 + lena/readds.m | 33 + lena/xml_read.m | 514 + linearfit.m | 67 + ll.m | 88 + loadallmat.m | 22 + loadfield.m | 25 + localmax.m | 84 + localmin.m | 8 + locmax.m | 81 + lowerfields.m | 23 + lowpass.m | 147 + lowpassfilter.m | 156 + lpass.m | 61 + match.m | 14 + matlabupdate.m | 35 + matlabupdate/2000-11-02/strtrim.m | 44 + matlabupdate/2004-10-13/isscalar.m | 12 + matlabupdate/2004-10-13/isvector.m | 4 + matlabupdate/2006-08-03/cast.m | 13 + matlabupdate/2006-08-03/edist.dll | Bin 0 -> 18432 bytes matlabupdate/overloaded/str2num.m | 85 + max2.m | 40 + maxabs.m | 28 + maxnan.m | 36 + mcorr.m | 74 + meannan.m | 42 + mediansplit.m | 107 + mergestructs.m | 59 + merror.deprecated.m | 16 + mesh_smooth_gaussian.m | 44 + mex_issue.m | 33 + mexpopts.sh | 372 + mfiletemplate.m | 56 + minf2struct.m | 64 + misc/biblio/pubmed1.m | 30 + misc/biblio/pubmed2.m | 45 + misc/biblio/repairbib.m | 42 + misc/cdf.m | 49 + misc/compute_ins.m | 0 misc/docinfo.m | 25 + misc/filelabel.m | 34 + misc/gmail/gmailclient.m | 4 + misc/hex_to_uint64.m | 56 + misc/hypercube.m | 91 + misc/ins_c.m | 34 + misc/isi/isi.bk.htm | 4928 +++++++ misc/isi/isi.htm | 4928 +++++++ misc/isi/isi.m | 27 + misc/isi/isi.mat | Bin 0 -> 10464 bytes misc/isi/jcr_recs.txt | 96 + misc/labnic_expe_forms_from_gmail.m | 0 misc/physique1.m | 90 + misc/recallfigmfile.m | 6 + misc/safe_exit.m | 26 + misc/worldhappiness.dat | 244 + misc/worldhappiness.m | 0 mkdirr.m | 28 + mne.m | Bin 0 -> 59 bytes montecarlo.m | 36 + montyhall.m | 15 + mouseclick.dll | Bin 0 -> 24576 bytes mri_slice.m | 119 + mseq.m | 422 + myColors.m | 4 + mycaxis.m | 26 + mycolormap.m | 21 + mymatlabpath.m | 3 + mypath.m | 118 + mypsychtoolbox/ConfidenceDisc.m | 273 + mypsychtoolbox/ConfidenceScale2_mod.m | 181 + mypsychtoolbox/ConfidenceScale3.m | 237 + mypsychtoolbox/DrawText.m | 189 + mypsychtoolbox/FlipWindow.m | 47 + mypsychtoolbox/KbNameCell.m | 7 + mypsychtoolbox/OpenDisplay.m | 266 + mypsychtoolbox/RectAlign.m | 127 + mypsychtoolbox/RectPosition.m | 4 + mypsychtoolbox/WaitAnyPress.m | 102 + mypsychtoolbox/WaitAnyRelease.m | 71 + mypsychtoolbox/WaitKeyPress.m | 48 + mypsychtoolbox/WaitMouseClick.m | 47 + mypsychtoolox.m | 42 + myspm.m | 117 + myspm_image.m | 466 + myspm_orthviews.m | 1527 ++ mystats/T_pair.m | 129 + mystats/anova-a.m | 102 + mystats/anova.m | 626 + mystats/kurtosis.m | 19 + mystats/myanova.1.m | 679 + mystats/myanova.2.m | 516 + mystats/myanova.6.m | 737 + mystats/myanova.m | 737 + mystats/myanova.old.2.m | 186 + mystats/myanova.old.m | 186 + mystats/myanova1.m | 36 + mystats/myanova2.m | 89 + mystats/myanova3.m | 301 + mystats/myanova4.m | 57 + mystats/myanova5.m | 516 + mystats/myanovaeffects.m | 27 + mystats/myanovan.m | 372 + mystats/permfriedman.m | 148 + mystats/permtest.m | 604 + mystats/permtest2.m | 129 + mystats/permtest2b.m | 129 + mystats/skewness.m | 23 + mystats/trimmean.m | 3 + mystats/wyrm.m | 50 + mytriplot.m | 324 + myview_surface.m | 47 + nappe.m | 28 + nd2array.m | 71 + ndcat.m | 140 + ndiags.m | 29 + ndmultiply.m | 50 + nearest.c | 84 + nearest.dll | Bin 0 -> 12288 bytes nearest.m | 17 + nearest.o | Bin 0 -> 5000 bytes nestingmatrix.m | 27 + newdeal.m | 28 + noaccents.m | 63 + nonempty.m | 25 + nonnans.m | 45 + nonunique.m | 47 + normalize.m | 198 + normalize.m~ | 198 + normalsOutFaces.m | 35 + normalsOutVertices.m | 40 + normalsfaces.m | 35 + normalspatch.m | 11 + normalsvertices.m | 11 + normalz.m | 7 + norminv.m | 28 + normminmax.m | 20 + nth.m | 16 + numrep.m | 45 + octave/fminunc.m | 426 + oddsratio.m | 22 + openpdf.m | 2 + overlap.m | 44 + package.m | 178 + paren_parse.m | 2 + paren_parser.m | 28 + pascualmarqui.m | 41 + patch_swell.m | 55 + patchify.m | 34 + patchplot.m | 66 + pca.m | 118 + permarray.m | 4 + permprep.m | 31 + permute2.m | 31 + perso/predictors.m | 18 + perso/prerdictors.m | 12 + piecemeal.m | 42 + plot2d.m | 21 + plot3d.m | 18 + plot4.m | 51 + plotSDV.m | 10 + plotdata.m | 308 + ploterr.m | 82 + ploterrw.m | 103 + ploterrw1.m | 86 + plotlabel.m | 60 + plotlinearfit.m | 67 + plotnd.m | 472 + plotpatch.m | 25 + plottopo2.m | 931 ++ positivepart.m | 17 + preprocessing/artifacts.m | 136 + preprocessing/explorer.m | 255 + print_pdf.m | 252 + print_pdf.mat | Bin 0 -> 275 bytes printmatrix.m | 27 + prob_clusters.m | 110 + pseudo/IOPort/IOPort.m | 5 + pval2patch.m | 48 + pval2patch_old.m | 41 + quantile.m | 72 + r/MatlabServer.m | 225 + r/anovaR.m | 16 + r/permutations.demo.R | 19 + r/test1.m | 3 + raiseobj.m | 64 + raisobj.m | 8 + randpick.m | 42 + randtiles.m | 10 + ranksort.m | 7 + rdir.m | 153 + read_eprimetxt.m | 237 + read_table.m | 56 + readmeshes.m | 18 + readtri.m | 16 + reducepatch2.m | 21 + regcheck.m | 74 + relativepath.m | 102 + reldiff.m | 52 + renormalize.m | 44 + respond.m | 23 + retrieve.m | 29 + rgb2luminance.m | 38 + rgb2yuv.m | 13 + rivercross.m | 53 + rmmean.m | 25 + roc.m | 58 + rootmeansquare.m | 45 + rotate3daxes.m | 424 + rotatelr.m | 30 + rotateticklabel.m | 82 + rotateud.m | 30 + rotation.m | 33 + rownorm.m | 25 + savefields.m | 54 + saveppt.m | 677 + scalpface.m | 63 + scrollaxes.m | 421 + sdt.m | 57 + select3d.m | 381 + selectscout.m | 17 + setAllText.m | 29 + setall.m | 26 + setdiff.m | 199 + setdiff2.m | 199 + setfield2.m | 14 + setfocus.m | 75 + setmycolormap.m | 19 + sf_estrrep.m | 9 + sfieldnames.m | 22 + sgetfield.m | 72 + showpercent.m | 138 + sigproc/__zp2ssg2__.m | 68 + sigproc/bilinear.m | 103 + sigproc/cheby1.m | 99 + sigproc/complement.m | 61 + sigproc/create_set.m | 56 + sigproc/decimate.m | 82 + sigproc/filtfilt.m | 72 + sigproc/firls.m | 116 + sigproc/hann.m | 48 + sigproc/hanning.m | 48 + sigproc/sftrans.m | 185 + sigproc/zp2ssg2.m | 68 + sigproc/zp2tf.m | 74 + simsort.m | 49 + slidingwindow.m | 209 + sources_menu.m | 60 + spatfreq/exp8full.jpg | Bin 0 -> 29102 bytes spatfreq/exp8hf.jpg | Bin 0 -> 28813 bytes spatfreq/exp8lf.jpg | Bin 0 -> 17276 bytes spatfreq/exp8mf.jpg | Bin 0 -> 21077 bytes spatfreq/fft.jpg | Bin 0 -> 93017 bytes spatfreq/fft2.bmp | Bin 0 -> 17206 bytes spatfreq/filtimg1.m | 137 + spatfreq/lena_bw.bmp | Bin 0 -> 66614 bytes spatfreq/lena_bw.gif | Bin 0 -> 69732 bytes spatfreq/logo_sm.gif | Bin 0 -> 4707 bytes sphericity.m | 87 + spm/SPMstruct.pdf | Bin 0 -> 28515 bytes spm/image_spm.m | 59 + spm/myspm_defaults.m | 126 + spm/myspm_get_data_xyz.m | 128 + spm/myspm_hierarchy.m | 0 spm/myspm_struct_info.m | 172 + spm/myspm_updatepath.m | 89 + spm/nii_for_spm2/@file_array/Contents.m | 51 + spm/nii_for_spm2/@file_array/cat.m | 40 + spm/nii_for_spm2/@file_array/cpermute.m | 9 + spm/nii_for_spm2/@file_array/ctranspose.m | 9 + spm/nii_for_spm2/@file_array/disp.m | 34 + spm/nii_for_spm2/@file_array/display.m | 14 + spm/nii_for_spm2/@file_array/double.m | 12 + spm/nii_for_spm2/@file_array/end.m | 17 + spm/nii_for_spm2/@file_array/fieldnames.m | 16 + spm/nii_for_spm2/@file_array/file_array.m | 39 + spm/nii_for_spm2/@file_array/horzcat.m | 11 + spm/nii_for_spm2/@file_array/length.m | 11 + spm/nii_for_spm2/@file_array/loadobj.m | 14 + spm/nii_for_spm2/@file_array/mystruct.m | 16 + spm/nii_for_spm2/@file_array/ndims.m | 12 + spm/nii_for_spm2/@file_array/numel.m | 14 + spm/nii_for_spm2/@file_array/numeric.m | 14 + spm/nii_for_spm2/@file_array/permute.m | 10 + .../@file_array/private/datatypes.m | 56 + spm/nii_for_spm2/@file_array/private/dim.m | 37 + spm/nii_for_spm2/@file_array/private/dtype.m | 86 + spm/nii_for_spm2/@file_array/private/fname.m | 35 + .../@file_array/private/mystruct.m | 16 + spm/nii_for_spm2/@file_array/private/offset.m | 35 + .../@file_array/private/resize_scales.m | 24 + .../@file_array/private/scl_inter.m | 36 + .../@file_array/private/scl_slope.m | 35 + spm/nii_for_spm2/@file_array/reshape.m | 21 + spm/nii_for_spm2/@file_array/size.m | 43 + spm/nii_for_spm2/@file_array/subsasgn.m | 146 + spm/nii_for_spm2/@file_array/subsref.m | 124 + spm/nii_for_spm2/@file_array/transpose.m | 10 + spm/nii_for_spm2/@file_array/vertcat.m | 11 + spm/nii_for_spm2/@nifti/Contents.m | 82 + spm/nii_for_spm2/@nifti/create.m | 74 + spm/nii_for_spm2/@nifti/disp.m | 23 + spm/nii_for_spm2/@nifti/display.m | 14 + spm/nii_for_spm2/@nifti/fieldnames.m | 26 + spm/nii_for_spm2/@nifti/nifti.m | 91 + spm/nii_for_spm2/@nifti/private/M2Q.m | 33 + spm/nii_for_spm2/@nifti/private/Q2M.m | 31 + .../@nifti/private/decode_qform0.m | 61 + spm/nii_for_spm2/@nifti/private/empty_hdr.m | 15 + .../@nifti/private/encode_qform0.m | 45 + spm/nii_for_spm2/@nifti/private/findindict.m | 37 + spm/nii_for_spm2/@nifti/private/getdict.m | 153 + spm/nii_for_spm2/@nifti/private/mayo2nifti1.m | 67 + spm/nii_for_spm2/@nifti/private/mayostruc.m | 80 + spm/nii_for_spm2/@nifti/private/nifti1.h | 1222 ++ spm/nii_for_spm2/@nifti/private/nifti_stats.c | 11277 ++++++++++++++ spm/nii_for_spm2/@nifti/private/nifti_stats.m | 41 + .../@nifti/private/nifti_stats_mex.c | 124 + spm/nii_for_spm2/@nifti/private/niftistruc.m | 81 + spm/nii_for_spm2/@nifti/private/read_extras.m | 27 + spm/nii_for_spm2/@nifti/private/read_hdr.m | 85 + .../@nifti/private/read_hdr_raw.m | 93 + .../@nifti/private/write_extras.m | 36 + .../@nifti/private/write_hdr_raw.m | 77 + spm/nii_for_spm2/@nifti/structn.m | 20 + spm/nii_for_spm2/@nifti/subsasgn.m | 400 + spm/nii_for_spm2/@nifti/subsref.m | 241 + spm/nii_for_spm2/spm_conv_vol.mexa64 | Bin 0 -> 265719 bytes spm/nii_for_spm2/spm_conv_vol.mexglx | Bin 0 -> 222367 bytes spm/nii_for_spm2/spm_conv_vol.mexmac | Bin 0 -> 300876 bytes spm/nii_for_spm2/spm_conv_vol.mexmaci | Bin 0 -> 258416 bytes spm/nii_for_spm2/spm_conv_vol.mexs64 | Bin 0 -> 270568 bytes spm/nii_for_spm2/spm_conv_vol.mexsol | Bin 0 -> 245780 bytes spm/nii_for_spm2/spm_conv_vol.mexw32 | Bin 0 -> 221696 bytes spm/nii_for_spm2/spm_conv_vol.mexw64 | Bin 0 -> 219136 bytes spm/nii_for_spm2/spm_ecat2nifti.m | 406 + .../spm_eeg_marry_scalp_head_quick.m | 16 + spm/nii_for_spm2/spm_fileparts.m | 24 + spm/nii_for_spm2/spm_mnc2nifti.m | 213 + spm/nii_for_spm2/spm_read_hdr.m | 141 + spm/nii_for_spm2/spm_read_vols.m | 59 + spm/nii_for_spm2/spm_render_vol.mexa64 | Bin 0 -> 260719 bytes spm/nii_for_spm2/spm_render_vol.mexglx | Bin 0 -> 217471 bytes spm/nii_for_spm2/spm_render_vol.mexmac | Bin 0 -> 296712 bytes spm/nii_for_spm2/spm_render_vol.mexmaci | Bin 0 -> 258484 bytes spm/nii_for_spm2/spm_render_vol.mexs64 | Bin 0 -> 262592 bytes spm/nii_for_spm2/spm_render_vol.mexsol | Bin 0 -> 241768 bytes spm/nii_for_spm2/spm_render_vol.mexw32 | Bin 0 -> 219648 bytes spm/nii_for_spm2/spm_render_vol.mexw64 | Bin 0 -> 217088 bytes spm/nii_for_spm2/spm_resels_vol.mexa64 | Bin 0 -> 256876 bytes spm/nii_for_spm2/spm_resels_vol.mexglx | Bin 0 -> 214180 bytes spm/nii_for_spm2/spm_resels_vol.mexmac | Bin 0 -> 296616 bytes spm/nii_for_spm2/spm_resels_vol.mexmaci | Bin 0 -> 254256 bytes spm/nii_for_spm2/spm_resels_vol.mexs64 | Bin 0 -> 258624 bytes spm/nii_for_spm2/spm_resels_vol.mexsol | Bin 0 -> 237884 bytes spm/nii_for_spm2/spm_resels_vol.mexw32 | Bin 0 -> 218112 bytes spm/nii_for_spm2/spm_resels_vol.mexw64 | Bin 0 -> 214016 bytes spm/nii_for_spm2/spm_sample_vol.mexa64 | Bin 0 -> 255008 bytes spm/nii_for_spm2/spm_sample_vol.mexglx | Bin 0 -> 211220 bytes spm/nii_for_spm2/spm_sample_vol.mexmac | Bin 0 -> 296508 bytes spm/nii_for_spm2/spm_sample_vol.mexmaci | Bin 0 -> 254208 bytes spm/nii_for_spm2/spm_sample_vol.mexs64 | Bin 0 -> 257904 bytes spm/nii_for_spm2/spm_sample_vol.mexsol | Bin 0 -> 237300 bytes spm/nii_for_spm2/spm_sample_vol.mexw32 | Bin 0 -> 217600 bytes spm/nii_for_spm2/spm_sample_vol.mexw64 | Bin 0 -> 213504 bytes spm/nii_for_spm2/spm_slice_vol.mexa64 | Bin 0 -> 255007 bytes spm/nii_for_spm2/spm_slice_vol.mexglx | Bin 0 -> 210819 bytes spm/nii_for_spm2/spm_slice_vol.mexmac | Bin 0 -> 296508 bytes spm/nii_for_spm2/spm_slice_vol.mexmaci | Bin 0 -> 254208 bytes spm/nii_for_spm2/spm_slice_vol.mexs64 | Bin 0 -> 257672 bytes spm/nii_for_spm2/spm_slice_vol.mexsol | Bin 0 -> 237040 bytes spm/nii_for_spm2/spm_slice_vol.mexw32 | Bin 0 -> 217088 bytes spm/nii_for_spm2/spm_slice_vol.mexw64 | Bin 0 -> 212992 bytes spm/nii_for_spm2/spm_vol.m | 179 + spm/nii_for_spm2/spm_vol_check.m | 72 + spm/nii_for_spm2/spm_vol_nifti.m | 39 + spm/spm2/myspm_get_data_xyz.m | 90 + spm/spm2/spm_P_RF.m | 188 + spm/spm2/spm_create_vol.m | 363 + spm/spm2/spm_dcm_display.m | 266 + spm/spm2/spm_defaults.m | 126 + spm/spm2/spm_figure.m | 875 ++ spm/spm2/spm_fmri_spm_ui.m | 478 + spm/spm2/spm_get.m | 1842 +++ spm/spm2/spm_imcalc_ui.m | 172 + spm/spm2/spm_list_fullfiles.m | 33 + spm/spm2/spm_mask2.m | 132 + spm/spm2/spm_mat2hdr.m | 67 + spm/spm2/spm_mat_into_hdr.m | 262 + spm/spm2/spm_mip.m | 77 + spm/spm2/spm_mip_ui.m | 711 + spm/spm2/spm_move_spm.m | 42 + spm/spm2/spm_peristimulus_sampling.m | 51 + spm/spm2/spm_resss.m | 125 + spm/spm2/spm_results_ui.m | 1053 ++ spm/spm2/spm_sections.m | 36 + spm/spm2/spm_spm.m | 948 ++ spm/spm2/spm_vol.m | 152 + spm/spm5/spm_2008Oct17.ps | Bin 0 -> 13404 bytes spm/spm5/spm_config_realign_log.m | 225 + spm/spm5/spm_dicom_convert.m | 1180 ++ spm/spm5/spm_jobman.m | 2934 ++++ spm/spm5/spm_log2html.m | 154 + spm/spm5/spm_vol.m | 185 + spm/spm_check_realign.m | 109 + spm/spm_log2html/spm_jobman.m | 2934 ++++ spm/spm_log2html/spm_log2html.m | 154 + spm/spm_mm2vox.m | 52 + spm/spm_mysections.m | 58 + spm/spm_render2.m | 278 + spm/spm_sample_vol.m | 0 spm/spm_sections_colin27.m | 30 + spm/spm_update.m | 69 + spm2.m | 3 + spm5.m | 3 + spm_defaults.m | 126 + spm_mm2vox.m | 52 + spm_render2.m | 278 + spm_sections_colin27.m | 30 + sprintf2.m | 86 + sprintfcell.m | 42 + squeeze2.m | 24 + ssetfield.m | 74 + sstruct.m | 16 + ssvep/flicker1.m | 50 + staircase/CreateStaircase.m | 37 + staircase/GetStaircaseResults.m | 45 + staircase/GetStaircaseThreshold.m | 23 + staircase/GetStaircaseVariable.m | 7 + staircase/RefreshStaircase.m | 37 + staircase/SetStaircaseResponse.m | 10 + staircase/ShowStaircaseResults.m | 20 + staircase/UpdateStaircase.m | 20 + staircase/Weibull.m | 13 + staircase/old/CreateStaircase.m | 39 + staircase/old/GetStaircaseResults.m | 44 + staircase/old/GetStaircaseVariable.m | 7 + staircase/old/ShowStaircaseResults.m | 20 + staircase/old/UpdateStaircase.m | 63 + staircase/testStaircase.m | 76 + start_brainstorm3.m | 11 + start_eeglab.m | 4 + start_spm.m | 3 + startup.m | 120 + stat/alpha_significance.m | 18 + stat/alpha_threshold.m | 11 + stat/anova_examples.m | 471 + stat/anovaeffects.m | 16 + stat/anovalevels.m | 69 + stat/anovand.m | 1533 ++ stat/binocdf.m | 121 + stat/chi2_cdf.m | 32 + stat/cohen_d.m | 55 + stat/coincidence_matrix.m | 99 + stat/compare_anovas.m | 3 + stat/cronbach.m | 46 + stat/distributionPlot/countEntries.m | 110 + stat/distributionPlot/distributionPlot.m | 270 + stat/distributionPlot/histogram.m | 207 + stat/distributionfit.m | 188 + stat/dunnsidak.m | 34 + stat/effectsizes_to_errorrates.m | 47 + stat/f_cdf.m | 375 + stat/friedman_posthoc.m | 106 + stat/friedman_test.m | 110 + stat/ftest.m | 33 + stat/goutte/avevar.m | 17 + stat/goutte/bootstrap.m | 47 + stat/goutte/budget.txt | 24 + stat/goutte/ftest.m | 33 + stat/goutte/jackknife.m | 46 + stat/goutte/kstest.m | 46 + stat/goutte/pca.m | 118 + stat/goutte/pttest.m | 41 + stat/goutte/qks.m | 58 + stat/goutte/test.m | 35 + stat/goutte/ttest.m | 30 + stat/goutte/uttest.m | 30 + stat/hedge_g.m | 32 + stat/hochberg.m | 46 + stat/holm.m | 47 + stat/krippendorff_alpha.m | 61 + stat/kstest.m | 46 + stat/kstest2.m | 47 + stat/levenetest.m | 153 + stat/mvrnorm.m | 96 + stat/mwwtest.m | 129 + stat/myanova.m | 700 + stat/myanova3.m | 301 + stat/myanovaeffects.m | 27 + stat/mypermtest.m | 266 + stat/myplannedcomp.m | 8 + stat/myposthoc.m | 8 + stat/myrmanova.m.bad | Bin 0 -> 19990 bytes stat/myttest.m | 220 + stat/octave_anovan.m | 366 + stat/p_rep.m | 54 + stat/permftest.m | 404 + stat/permktest.m | 308 + stat/permtest/permtest.m | 401 + stat/permtest/permtest_cluster.m | 119 + stat/permttest.m | 320 + stat/permttest_cluster.m | 246 + stat/permttest_norm.m | 83 + stat/permttest_norm2.m | 123 + stat/permttest_run.m | 0 stat/phicoef.m | 13 + stat/posthoc.m | 26 + stat/powerStudent.m | 102 + stat/power_ttest.m | 39 + stat/pseudotvalue.m | 23 + stat/pttest.m | 41 + stat/pvalue.m | 97 + stat/qtukey.m | 139 + stat/rau.m | 48 + stat/rep_anovan.m | 52 + stat/rm_anova2.m | 136 + stat/shapirowilktest.m | 260 + stat/spearmann.m | 23 + stat/ssbar.m | 78 + stat/stat_bar.m | 52 + stat/stat_examples.m | 67 + stat/t_cdf.m | 106 + stat/t_test.m | 78 + stat/test.m | 35 + stat/test_anova.m | 26 + stat/test_perm.m | 7 + stat/testdata.m | 17 + stat/ttest.m | 30 + stat/ttest_bonf.m | 123 + stat/tukeyhsd.m | 185 + stat/tvalue.bak.dll | Bin 0 -> 12288 bytes stat/tvalue.c | 92 + stat/tvalue.dll | Bin 0 -> 6656 bytes stat/tvalue.m | 12 + stat/tvalue2.dll | Bin 0 -> 12288 bytes stat/u_test.m | 133 + stat/uttest.m | 30 + stat/winsor.m | 77 + stderr.m | 40 + stderr_within.m | 36 + stderrw.m | 45 + stdnan.m | 82 + stdw.m | 60 + strcell.m | 22 + strfindobj.m | 25 + strongconn.m | 73 + struct2cell2.m | 12 + struct2fv.m | 18 + struct2list.m | 81 + struct2minf.m | 63 + struct2tab.m | 155 + structcomparator.m | 56 + structdiff.m | 139 + structmatch.m | 143 + subarray.m | 150 + subdir.m | 26 + subjectimage2spmvol.m | 49 + subpatch.m | 10 + subsasgn2.m | 96 + sumnan.m | 45 + system1020.m | 5 + tabread.m | 61 + template.m | 62 + tess2patch.m | 23 + tex_clusters.m | 110 + textalign.m | 38 + threshold.m | 19 + tiedrank.m | 26 + tiedrank2.m | 26 + tilefigs.m | 98 + tiles.m | 44 + tilesperm.m | 54 + timebar.m | 215 + timeit.m | 1139 ++ togglestate.m | 51 + trim.m | 59 + trimsurface.m | 102 + trimsurface_gui.m | 229 + twxticklabel.m | 75 + uigetpathfile.m | 10 + usbpath.m | 10 + vectvec.m | 4 + vertvec.m | 7 + vidal.m | 77 + view3d.m | 48 + viewer3d.m | 48 + vline.m | 66 + wheelzoom.m | 110 + whichLoop.m | 60 + whichRank.m | 63 + windfind.m | 64 + wwhich.m | 78 + x2cell.m | 4 + x2char.m | 58 + xjview.m | 8669 +++++++++++ xjview8p4.m | 12218 ++++++++++++++++ xjview_labnic.m | 8677 +++++++++++ xjview_markus.m | 8547 +++++++++++ xjview_v4.m | 8669 +++++++++++ xline.m | 39 + xml2struct.m | 47 + xticklabel_rotate.m | 212 + xyz2channel.m | 5 + xyz2channels.m | 5 + xyzlabels.m | 15 + yline.m | 39 + yprime.dll | Bin 0 -> 6144 bytes yuv2rgb.m | 13 + zoom_axes.m | 141 + zscore.m | 41 + ztransform.m | 30 + 988 files changed, 213489 insertions(+) create mode 100644 .gitignore create mode 100644 3rdparty/dualcursor.m create mode 100644 3rdparty/findjobj.m create mode 100644 3rdparty/jewelersLoupe.m create mode 100644 3rdparty/m2googlevis.m create mode 100644 3rdparty/magnify.m create mode 100644 @int32/sparse.m create mode 100644 FindConstInMFiles.m create mode 100644 MRslice.m create mode 100644 Plot2dHist.m create mode 100644 Tr2event.m create mode 100644 WhatsThisImage.m create mode 100644 WilcoxonSRPVs.m create mode 100644 absolutepath.m create mode 100644 affinetransform.m create mode 100644 allhandles.m create mode 100644 allthesame.m create mode 100644 allunique.m create mode 100644 aprime.m create mode 100644 argouts.m create mode 100644 art/rectpix.m create mode 100644 art/rotbar.m create mode 100644 art/test.jpg create mode 100644 avevar.m create mode 100644 bardata.m create mode 100644 barerrorbar.m create mode 100644 barstem.m create mode 100644 barweb.m create mode 100644 baseline_correction.m create mode 100644 beamformerkernel.m create mode 100644 between.m create mode 100644 bibliography/hindex.m create mode 100644 bibliography/pmid2bib.m create mode 100644 bit.m create mode 100644 bitget2.m create mode 100644 blackguis.m create mode 100644 bootstrap.m create mode 100644 brainstorm/bst_lighting.m create mode 100755 brainstorm/bst_struct2fv.m create mode 100755 brainstorm/bst_surface2fv.m create mode 100644 brainstorm/bst_trimSurface.m create mode 100644 brainstorm/carto_cortex.m create mode 100644 brainstorm/carto_flatcap.m create mode 100644 brainstorm/carto_flatdisc.m create mode 100644 brainstorm/cortex_menu.m create mode 100644 brainstorm/ctf_readClassFile.m create mode 100644 brainstorm/ds2brainstorm.m create mode 100755 brainstorm/getChannelLocs.m create mode 100755 brainstorm/getPositionChannel.m create mode 100644 brainstorm/meeg_menu.m create mode 100644 brainstorm/meegui_menu.m create mode 100644 brainstorm/mybrainstorm_toolbar.m create mode 100644 brainstorm/scout_button.m create mode 100644 brainstorm/scout_menu.m create mode 100644 brainstorm/scout_swell.m create mode 100644 brainstorm/scouts_menu.m create mode 100644 brainstorm/vertconn2adjacency.m create mode 100644 brainstorm/view_MNEresults.m create mode 100644 brainstorm/viewpoint_menu.m create mode 100644 brainstorm3.m create mode 100644 brainstorm3/ExtThreshold.zip create mode 100644 brainstorm3/bst_batch.m create mode 100644 brainstorm3/bst_rmanova.m create mode 100644 brainstorm3/bst_stat_struct.mat create mode 100644 brainstorm3/guess_factorial_design.m create mode 100755 brainstorm3/gui_stat_common.m.mine create mode 100644 brainstorm3/import_anova.m create mode 100644 brainstorm3/knd_rmanova_files.m create mode 100644 brainstorm3/om_load_full.m create mode 100755 brainstorm3/private/gui_setEnabledControls.m create mode 100755 brainstorm3/private/gui_stat_common.m create mode 100644 brainstorm3/sTargetStudy_struct.mat create mode 100644 brainstorm3/script_anova.m create mode 100644 brainstorm3/script_import_Fmap.m create mode 100644 brainstorm3/script_import_anova.m create mode 100644 brainstorm3/script_import_anova_New1.m create mode 100644 brainstorm3/script_import_anova_Old1.m create mode 100755 brainstorm3/svn_diff.csh create mode 100755 brainstorm3/svn_diff.csh~ create mode 100644 brainstorm3/toolbox/core/bst_getContext.m create mode 100644 brainstorm3/toolbox/core/bst_safeCall.m create mode 100644 brainstorm3/toolbox/db/db_getDataTemplate.m create mode 100644 brainstorm3/toolbox/gui/gui_figure3DViz.m create mode 100644 brainstorm3/toolbox/gui/panel_scouts.m create mode 100644 brainstorm3/toolbox/gui/panel_stat.m create mode 100644 brainstorm3/toolbox/gui/panel_statRun.m create mode 100644 brainstorm3/toolbox/gui/panel_statRun.m.orig create mode 100644 brainstorm3/toolbox/gui/panel_surface.m create mode 100644 brainstorm3/toolbox/io/import_data.m create mode 100644 brainstorm3/toolbox/misc/mri_drawCuts.m create mode 100644 brainstorm3/toolbox/stat/bst_batch.m create mode 100755 brainstorm3/tvalue_paired.mexglx create mode 100644 brainvisa/writemesh.m create mode 100644 brodmann/MNITemplate_brodmann.m create mode 100644 brodmann/MNI_brodmann.m create mode 100644 brodmann/MNI_brodmann_karimovitch.m create mode 100644 brodmann/MNI_karimovitch.m create mode 100644 brodmann/TTareas2TTgyri.m create mode 100644 brodmann/TTbasta.mat create mode 100644 brodmann/TTdaemon.mat create mode 100644 brodmann/TalairachDaemonDatabase.m create mode 100644 brodmann/atlascomparator.m create mode 100644 brodmann/atlasviewer.m create mode 100644 brodmann/ba_color.m create mode 100644 brodmann/brodmann.m create mode 100644 brodmann/correct_flip_tess.m create mode 100644 brodmann/img2voxels.m create mode 100644 brodmann/labelbrainstorm.m create mode 100644 brodmann/labelize.m create mode 100644 brodmann/labelviewer.m create mode 100644 brodmann/makeTTbasta.m create mode 100644 brodmann/make_aal_colormap.m create mode 100644 brodmann/mergeBA.m create mode 100644 brodmann/mergeBA.txt create mode 100644 brodmann/merge_cluster.m create mode 100644 brodmann/montreal_brodmann.m create mode 100644 bugs/emptiness.m create mode 100644 cartool/J_segmentation.m create mode 100644 cartool/converts.m create mode 100644 cartool/ctf2cartool.m create mode 100644 cartool/event2mrkfile.m create mode 100644 cartool/pierre/computeavgref.m create mode 100644 cartool/pierre/computedataagainstref.m create mode 100644 cartool/pierre/computedissandsc.m create mode 100644 cartool/pierre/computegfp.m create mode 100644 cartool/pierre/openels.m create mode 100644 cartool/pierre/openeph.m create mode 100644 cartool/pierre/openfreq.m create mode 100644 cartool/pierre/opensef.m create mode 100644 cartool/pierre/saveeph.m create mode 100644 cartool/pierre/savesef.m create mode 100644 cartool/read_eeg.m create mode 100644 cartool/read_eph.m create mode 100644 cartool/read_is.m create mode 100644 cartool/read_mrk.m create mode 100644 cartool/read_ris.m create mode 100644 cartool/read_sef.m create mode 100644 cartool/read_spi.m create mode 100644 cartool/read_xyz.m create mode 100644 cartool/write_sef.m create mode 100644 catstructs.m create mode 100644 ccaxis.m create mode 100644 cdf.m create mode 100644 cell2str.m create mode 100644 cellcat.m create mode 100644 cellfun2.m create mode 100644 circle3.m create mode 100644 circpermarray.m create mode 100644 clickableLegend.m create mode 100644 closest.m create mode 100644 cluster_threshold.m create mode 100644 clustering.m create mode 100644 clusters2faces.m create mode 100644 clusters2patches.m create mode 100644 cnrs_section27.m create mode 100644 cogent/Textquestions.m create mode 100644 cogent/discrethopper.m create mode 100644 cogent/questionnaire.m create mode 100644 cogent/semislider.m create mode 100644 cogent/slider.m create mode 100644 coldhot.m create mode 100644 colon2.m create mode 100644 colorbar_slider.m create mode 100644 colormap_threshold.m create mode 100644 colorname2rgb.m create mode 100755 columnlegend.m create mode 100644 compute_forward.m create mode 100644 compute_inverse.m create mode 100644 concatenatepatch.m create mode 100644 continuity.m create mode 100644 corrcoef2.m create mode 100644 cov2.m create mode 100644 curvature_cortex.m create mode 100644 data/COLORS.mat create mode 100644 data/PSC.mat create mode 100644 data/SUJETS.mat create mode 100644 data/nicehead-CTF.tri create mode 100644 data/nicehead.mat create mode 100644 data/repromn.m create mode 100644 data/test2.wrl create mode 100644 dec2basen.m create mode 100644 decours.m create mode 100644 deg2rad.m create mode 100644 depfuntoolbox.m create mode 100644 detrend2.m create mode 100644 dgrouping.m create mode 100644 dirr.m create mode 100644 drag.m create mode 100644 dumpmemmex.dll create mode 100644 dunnsidak.m create mode 100644 e2pie.m create mode 100644 edist.c create mode 100644 edist.dll create mode 100644 edist.dll.old create mode 100644 edist.mexglx create mode 100644 edist.mexw32 create mode 100644 edist.pdb create mode 100644 eeglab/chanlocs2bstchannel.m create mode 100644 eeglab/eeglab2brainstorm.m create mode 100644 eeglab/icadefs.m create mode 100644 eeglab/plugins/LENA/eegplugin_lena.m create mode 100644 eeglab/plugins/LENA/lenaformat_files/image001.gif create mode 100644 eeglab/plugins/LENA/pop_readLENA.m create mode 100644 eeglab/plugins/LENA/readLENA.m create mode 100644 eeglab/plugins/bstorm/WS_FTP.LOG create mode 100644 eeglab/plugins/bstorm/bstchannel2chanlocs.m create mode 100644 eeglab/plugins/bstorm/bstormchannels2chanlocs.m create mode 100644 eeglab/plugins/bstorm/bstormchannels2chanlocs_gui.m create mode 100644 eeglab/plugins/bstorm/eegplugin_bstorm.m create mode 100644 eeglab/plugins/bstorm/pop_readbstorm.m create mode 100644 eeglab/plugins/bstorm/readbstorm.m create mode 100644 eeglab/plugins/bstorm/v1/pop_readbstorm.m create mode 100644 eeglab/plugins/bstorm/v1/readbstorm.m create mode 100644 eeglab/plugins/bstorm1.01/bstormchannels2chanlocs.m create mode 100644 eeglab/plugins/bstorm1.01/eegplugin_bstorm.m create mode 100644 eeglab/plugins/bstorm1.01/pop_readbstorm.m create mode 100644 eeglab/plugins/bstorm1.01/readbstorm.m create mode 100644 eeglab/plugins/bstorm1.01/v1/pop_readbstorm.m create mode 100644 eeglab/plugins/bstorm1.01/v1/readbstorm.m create mode 100644 eeglab/plugins/ctf/ds2brainstorm.m create mode 100644 eeglab/plugins/ctf/importctf.m create mode 100644 eeglab/plugins/ctf/pop_importctf.m create mode 100644 ellipse.m create mode 100644 elproj.m create mode 100644 entropy.m create mode 100644 entropy2.m create mode 100644 epilepsie/dataplot_cb.m create mode 100644 epilepsie/mergedata.m create mode 100644 epilepsie/mergeresults.m create mode 100644 epilepsie/scriptStudySubject.m create mode 100644 epilepsie/splitdata.m create mode 100644 epilepsie/view_cortex.m create mode 100644 epilepsie/view_minnorm_cb.m create mode 100644 epilepsie/view_topography_cb.m create mode 100644 extrema.m create mode 100644 extrema2.m create mode 100644 fakebar.m create mode 100644 fftfilter.m create mode 100644 field2struct.m create mode 100644 fieldtriptopo.m create mode 100644 fig2pdf.m create mode 100644 figure_call.m create mode 100644 filefinder.m create mode 100644 fileparts2.m create mode 100644 fillarray.m create mode 100644 findTessellationHandles.m create mode 100644 findclosest2.m create mode 100644 findinstruct.m create mode 100644 findn.m create mode 100644 finitefun.m create mode 100644 forcecell.m create mode 100644 fread2.m create mode 100644 fullfiles.m create mode 100644 geod_cluster.m create mode 100644 geod_cluster2.m create mode 100644 getfield2.m create mode 100644 gfp.m create mode 100644 globaldissimilarity.m create mode 100644 gower.m create mode 100644 grayhot.m create mode 100644 grayish.m create mode 100644 greyish.m create mode 100644 groupfun.m create mode 100644 groupfun.m.mine create mode 100644 groupfun.m.r565 create mode 100644 groupfun.m.r684 create mode 100644 hash.m create mode 100644 headplot2.m create mode 100644 hindex.m create mode 100644 hist2d.m create mode 100644 histfun.m create mode 100644 histk.m create mode 100644 histo2D.m create mode 100644 hline.m create mode 100644 horizalign.m create mode 100644 hostname.m create mode 100644 hot2.m create mode 100644 huemap.m create mode 100644 icatb/icatb_check_path.m create mode 100644 icatb/icatb_displayGUI.m create mode 100644 icatb/icatb_drawTimecourses.m create mode 100644 icatb/icatb_loadSPM_new.m create mode 100644 idxmember.m create mode 100644 iff.m create mode 100644 image_spm.m create mode 100644 imagepval.m create mode 100644 imax.m create mode 100644 img_labels.m create mode 100644 img_slice.m create mode 100644 imgfilt.m create mode 100644 ind2sub2.m create mode 100644 interpolate_cortex1.m create mode 100644 isfield2.m create mode 100644 ismember2.m create mode 100644 isort.m create mode 100644 isscalar2.m create mode 100644 issfield.m create mode 100644 javauigetfiles/MatlabR13FileFilter.class create mode 100644 javauigetfiles/MatlabR13FileFilter.java create mode 100644 javauigetfiles/uiGetFiles.m create mode 100644 karim.m create mode 100644 labelbads.m create mode 100644 labelize2.m create mode 100644 labels2parcels.m create mode 100644 labnic/Labnic_1st_analysis.m create mode 100644 labnic/Labnic_1st_contrast_spm5.m create mode 100644 labnic/Labnic_1stlevel_spm8.m create mode 100644 labnic/Labnic_Anova_2groups_spm8.m create mode 100644 labnic/Labnic_Anova_spm8.m create mode 100644 labnic/Labnic_PPI_spm8.m create mode 100644 labnic/Labnic_rfx_ones_spm8.m create mode 100644 labnic/Labnic_rfx_twos_spm5.m create mode 100644 labnic/extract_clubetas.m create mode 100644 labnic/nic_spm_explorer.m create mode 100644 labnic/nifti2img.m create mode 100644 labnic/ps2pdf.m create mode 100644 labnic/read_eprimetxt.m create mode 100644 labnic/references_batches_spm8 [WikiNIC].html create mode 100644 labnic/references_batches_spm8 [WikiNIC]_fichiers/button-css.png create mode 100644 labnic/references_batches_spm8 [WikiNIC]_fichiers/button-donate.gif create mode 100644 labnic/references_batches_spm8 [WikiNIC]_fichiers/button-dw.png create mode 100644 labnic/references_batches_spm8 [WikiNIC]_fichiers/button-php.gif create mode 100644 labnic/references_batches_spm8 [WikiNIC]_fichiers/button-rss.png create mode 100644 labnic/references_batches_spm8 [WikiNIC]_fichiers/button-xhtml.png create mode 100644 labnic/references_batches_spm8 [WikiNIC]_fichiers/cc-by-nc-sa.png create mode 100644 labnic/references_batches_spm8 [WikiNIC]_fichiers/css.css create mode 100644 labnic/references_batches_spm8 [WikiNIC]_fichiers/css_002.css create mode 100644 labnic/references_batches_spm8 [WikiNIC]_fichiers/css_003.css create mode 100644 labnic/references_batches_spm8 [WikiNIC]_fichiers/indexer.gif create mode 100644 labnic/references_batches_spm8 [WikiNIC]_fichiers/js.js create mode 100644 labnic/references_software [WikiNIC].html create mode 100644 labnic/references_software [WikiNIC]_fichiers/button-css.png create mode 100644 labnic/references_software [WikiNIC]_fichiers/button-donate.gif create mode 100644 labnic/references_software [WikiNIC]_fichiers/button-dw.png create mode 100644 labnic/references_software [WikiNIC]_fichiers/button-php.gif create mode 100644 labnic/references_software [WikiNIC]_fichiers/button-rss.png create mode 100644 labnic/references_software [WikiNIC]_fichiers/button-xhtml.png create mode 100644 labnic/references_software [WikiNIC]_fichiers/cc-by-nc-sa.png create mode 100644 labnic/references_software [WikiNIC]_fichiers/css.css create mode 100644 labnic/references_software [WikiNIC]_fichiers/css_002.css create mode 100644 labnic/references_software [WikiNIC]_fichiers/css_003.css create mode 100644 labnic/references_software [WikiNIC]_fichiers/icon_wink.gif create mode 100644 labnic/references_software [WikiNIC]_fichiers/indexer.gif create mode 100644 labnic/references_software [WikiNIC]_fichiers/js.js create mode 100644 labnic/spm5_preprocessing_labnic.m create mode 100644 labnic/spm8_preprocessing_labnic.m create mode 100644 labnic/swr_import_DICOM.m create mode 100644 labnic_spm_defaults.m create mode 100644 lacie.m create mode 100644 lena/@datahandler/compute.m create mode 100644 lena/@datahandler/convert.m create mode 100644 lena/@datahandler/data.m create mode 100644 lena/@datahandler/datahandler.m create mode 100644 lena/@datahandler/datahandler_classdef.m create mode 100644 lena/@datahandler/file.m create mode 100644 lena/@datahandler/get.m create mode 100644 lena/@datahandler/sensornames.m create mode 100644 lena/@datahandler/set.m create mode 100644 lena/@lena/lena.m create mode 100644 lena/create_events.m create mode 100644 lena/ctf_folder.m create mode 100644 lena/ctf_read_classfile.m create mode 100644 lena/ctf_read_markerfile.m create mode 100644 lena/extractBINblocksRevised4SensorsBis.m create mode 100644 lena/lena2brainstorm.m create mode 100644 lena/ptx2events.m create mode 100644 lena/readLENA.m create mode 100644 lena/readLENA.m~ create mode 100644 lena/readLENAHead.m create mode 100644 lena/read_ClassFile.m create mode 100755 lena/read_events.m create mode 100755 lena/read_lena.m create mode 100644 lena/read_lena.m~ create mode 100644 lena/read_lena_classes.m create mode 100644 lena/read_lena_events.m create mode 100644 lena/read_ptx.m create mode 100644 lena/read_trc.m create mode 100644 lena/readds.m create mode 100644 lena/xml_read.m create mode 100644 linearfit.m create mode 100644 ll.m create mode 100644 loadallmat.m create mode 100644 loadfield.m create mode 100644 localmax.m create mode 100644 localmin.m create mode 100644 locmax.m create mode 100644 lowerfields.m create mode 100644 lowpass.m create mode 100644 lowpassfilter.m create mode 100644 lpass.m create mode 100644 match.m create mode 100644 matlabupdate.m create mode 100644 matlabupdate/2000-11-02/strtrim.m create mode 100644 matlabupdate/2004-10-13/isscalar.m create mode 100644 matlabupdate/2004-10-13/isvector.m create mode 100644 matlabupdate/2006-08-03/cast.m create mode 100644 matlabupdate/2006-08-03/edist.dll create mode 100644 matlabupdate/overloaded/str2num.m create mode 100644 max2.m create mode 100644 maxabs.m create mode 100644 maxnan.m create mode 100644 mcorr.m create mode 100644 meannan.m create mode 100644 mediansplit.m create mode 100644 mergestructs.m create mode 100644 merror.deprecated.m create mode 100644 mesh_smooth_gaussian.m create mode 100644 mex_issue.m create mode 100644 mexpopts.sh create mode 100644 mfiletemplate.m create mode 100644 minf2struct.m create mode 100644 misc/biblio/pubmed1.m create mode 100644 misc/biblio/pubmed2.m create mode 100644 misc/biblio/repairbib.m create mode 100644 misc/cdf.m create mode 100644 misc/compute_ins.m create mode 100644 misc/docinfo.m create mode 100644 misc/filelabel.m create mode 100644 misc/gmail/gmailclient.m create mode 100644 misc/hex_to_uint64.m create mode 100644 misc/hypercube.m create mode 100644 misc/ins_c.m create mode 100644 misc/isi/isi.bk.htm create mode 100644 misc/isi/isi.htm create mode 100644 misc/isi/isi.m create mode 100644 misc/isi/isi.mat create mode 100644 misc/isi/jcr_recs.txt create mode 100644 misc/labnic_expe_forms_from_gmail.m create mode 100644 misc/physique1.m create mode 100644 misc/recallfigmfile.m create mode 100644 misc/safe_exit.m create mode 100644 misc/worldhappiness.dat create mode 100644 misc/worldhappiness.m create mode 100644 mkdirr.m create mode 100644 mne.m create mode 100644 montecarlo.m create mode 100644 montyhall.m create mode 100644 mouseclick.dll create mode 100644 mri_slice.m create mode 100644 mseq.m create mode 100644 myColors.m create mode 100644 mycaxis.m create mode 100644 mycolormap.m create mode 100644 mymatlabpath.m create mode 100644 mypath.m create mode 100644 mypsychtoolbox/ConfidenceDisc.m create mode 100644 mypsychtoolbox/ConfidenceScale2_mod.m create mode 100644 mypsychtoolbox/ConfidenceScale3.m create mode 100644 mypsychtoolbox/DrawText.m create mode 100644 mypsychtoolbox/FlipWindow.m create mode 100644 mypsychtoolbox/KbNameCell.m create mode 100644 mypsychtoolbox/OpenDisplay.m create mode 100644 mypsychtoolbox/RectAlign.m create mode 100644 mypsychtoolbox/RectPosition.m create mode 100644 mypsychtoolbox/WaitAnyPress.m create mode 100644 mypsychtoolbox/WaitAnyRelease.m create mode 100644 mypsychtoolbox/WaitKeyPress.m create mode 100644 mypsychtoolbox/WaitMouseClick.m create mode 100644 mypsychtoolox.m create mode 100644 myspm.m create mode 100644 myspm_image.m create mode 100644 myspm_orthviews.m create mode 100644 mystats/T_pair.m create mode 100644 mystats/anova-a.m create mode 100644 mystats/anova.m create mode 100644 mystats/kurtosis.m create mode 100644 mystats/myanova.1.m create mode 100644 mystats/myanova.2.m create mode 100644 mystats/myanova.6.m create mode 100644 mystats/myanova.m create mode 100644 mystats/myanova.old.2.m create mode 100644 mystats/myanova.old.m create mode 100644 mystats/myanova1.m create mode 100644 mystats/myanova2.m create mode 100644 mystats/myanova3.m create mode 100644 mystats/myanova4.m create mode 100644 mystats/myanova5.m create mode 100644 mystats/myanovaeffects.m create mode 100644 mystats/myanovan.m create mode 100644 mystats/permfriedman.m create mode 100644 mystats/permtest.m create mode 100644 mystats/permtest2.m create mode 100644 mystats/permtest2b.m create mode 100644 mystats/skewness.m create mode 100644 mystats/trimmean.m create mode 100644 mystats/wyrm.m create mode 100644 mytriplot.m create mode 100644 myview_surface.m create mode 100644 nappe.m create mode 100644 nd2array.m create mode 100644 ndcat.m create mode 100644 ndiags.m create mode 100644 ndmultiply.m create mode 100644 nearest.c create mode 100644 nearest.dll create mode 100644 nearest.m create mode 100644 nearest.o create mode 100644 nestingmatrix.m create mode 100644 newdeal.m create mode 100644 noaccents.m create mode 100644 nonempty.m create mode 100644 nonnans.m create mode 100644 nonunique.m create mode 100644 normalize.m create mode 100644 normalize.m~ create mode 100644 normalsOutFaces.m create mode 100644 normalsOutVertices.m create mode 100644 normalsfaces.m create mode 100644 normalspatch.m create mode 100644 normalsvertices.m create mode 100644 normalz.m create mode 100644 norminv.m create mode 100644 normminmax.m create mode 100644 nth.m create mode 100644 numrep.m create mode 100644 octave/fminunc.m create mode 100644 oddsratio.m create mode 100644 openpdf.m create mode 100644 overlap.m create mode 100644 package.m create mode 100644 paren_parse.m create mode 100644 paren_parser.m create mode 100644 pascualmarqui.m create mode 100644 patch_swell.m create mode 100644 patchify.m create mode 100644 patchplot.m create mode 100644 pca.m create mode 100644 permarray.m create mode 100644 permprep.m create mode 100644 permute2.m create mode 100644 perso/predictors.m create mode 100644 perso/prerdictors.m create mode 100644 piecemeal.m create mode 100644 plot2d.m create mode 100644 plot3d.m create mode 100644 plot4.m create mode 100644 plotSDV.m create mode 100644 plotdata.m create mode 100644 ploterr.m create mode 100644 ploterrw.m create mode 100644 ploterrw1.m create mode 100644 plotlabel.m create mode 100644 plotlinearfit.m create mode 100644 plotnd.m create mode 100644 plotpatch.m create mode 100644 plottopo2.m create mode 100644 positivepart.m create mode 100644 preprocessing/artifacts.m create mode 100644 preprocessing/explorer.m create mode 100644 print_pdf.m create mode 100644 print_pdf.mat create mode 100644 printmatrix.m create mode 100644 prob_clusters.m create mode 100644 pseudo/IOPort/IOPort.m create mode 100644 pval2patch.m create mode 100644 pval2patch_old.m create mode 100644 quantile.m create mode 100644 r/MatlabServer.m create mode 100644 r/anovaR.m create mode 100644 r/permutations.demo.R create mode 100644 r/test1.m create mode 100644 raiseobj.m create mode 100644 raisobj.m create mode 100644 randpick.m create mode 100644 randtiles.m create mode 100644 ranksort.m create mode 100644 rdir.m create mode 100644 read_eprimetxt.m create mode 100644 read_table.m create mode 100644 readmeshes.m create mode 100644 readtri.m create mode 100644 reducepatch2.m create mode 100644 regcheck.m create mode 100644 relativepath.m create mode 100644 reldiff.m create mode 100644 renormalize.m create mode 100644 respond.m create mode 100644 retrieve.m create mode 100644 rgb2luminance.m create mode 100644 rgb2yuv.m create mode 100644 rivercross.m create mode 100644 rmmean.m create mode 100644 roc.m create mode 100644 rootmeansquare.m create mode 100644 rotate3daxes.m create mode 100644 rotatelr.m create mode 100644 rotateticklabel.m create mode 100644 rotateud.m create mode 100644 rotation.m create mode 100644 rownorm.m create mode 100644 savefields.m create mode 100644 saveppt.m create mode 100644 scalpface.m create mode 100644 scrollaxes.m create mode 100644 sdt.m create mode 100644 select3d.m create mode 100644 selectscout.m create mode 100644 setAllText.m create mode 100644 setall.m create mode 100644 setdiff.m create mode 100644 setdiff2.m create mode 100644 setfield2.m create mode 100644 setfocus.m create mode 100644 setmycolormap.m create mode 100644 sf_estrrep.m create mode 100644 sfieldnames.m create mode 100644 sgetfield.m create mode 100644 showpercent.m create mode 100644 sigproc/__zp2ssg2__.m create mode 100644 sigproc/bilinear.m create mode 100644 sigproc/cheby1.m create mode 100644 sigproc/complement.m create mode 100644 sigproc/create_set.m create mode 100644 sigproc/decimate.m create mode 100644 sigproc/filtfilt.m create mode 100644 sigproc/firls.m create mode 100644 sigproc/hann.m create mode 100644 sigproc/hanning.m create mode 100644 sigproc/sftrans.m create mode 100644 sigproc/zp2ssg2.m create mode 100644 sigproc/zp2tf.m create mode 100644 simsort.m create mode 100644 slidingwindow.m create mode 100644 sources_menu.m create mode 100644 spatfreq/exp8full.jpg create mode 100644 spatfreq/exp8hf.jpg create mode 100644 spatfreq/exp8lf.jpg create mode 100644 spatfreq/exp8mf.jpg create mode 100644 spatfreq/fft.jpg create mode 100644 spatfreq/fft2.bmp create mode 100644 spatfreq/filtimg1.m create mode 100644 spatfreq/lena_bw.bmp create mode 100644 spatfreq/lena_bw.gif create mode 100644 spatfreq/logo_sm.gif create mode 100644 sphericity.m create mode 100644 spm/SPMstruct.pdf create mode 100644 spm/image_spm.m create mode 100644 spm/myspm_defaults.m create mode 100644 spm/myspm_get_data_xyz.m create mode 100644 spm/myspm_hierarchy.m create mode 100644 spm/myspm_struct_info.m create mode 100644 spm/myspm_updatepath.m create mode 100644 spm/nii_for_spm2/@file_array/Contents.m create mode 100755 spm/nii_for_spm2/@file_array/cat.m create mode 100755 spm/nii_for_spm2/@file_array/cpermute.m create mode 100755 spm/nii_for_spm2/@file_array/ctranspose.m create mode 100755 spm/nii_for_spm2/@file_array/disp.m create mode 100755 spm/nii_for_spm2/@file_array/display.m create mode 100755 spm/nii_for_spm2/@file_array/double.m create mode 100755 spm/nii_for_spm2/@file_array/end.m create mode 100755 spm/nii_for_spm2/@file_array/fieldnames.m create mode 100755 spm/nii_for_spm2/@file_array/file_array.m create mode 100755 spm/nii_for_spm2/@file_array/horzcat.m create mode 100755 spm/nii_for_spm2/@file_array/length.m create mode 100644 spm/nii_for_spm2/@file_array/loadobj.m create mode 100755 spm/nii_for_spm2/@file_array/mystruct.m create mode 100755 spm/nii_for_spm2/@file_array/ndims.m create mode 100755 spm/nii_for_spm2/@file_array/numel.m create mode 100755 spm/nii_for_spm2/@file_array/numeric.m create mode 100755 spm/nii_for_spm2/@file_array/permute.m create mode 100755 spm/nii_for_spm2/@file_array/private/datatypes.m create mode 100755 spm/nii_for_spm2/@file_array/private/dim.m create mode 100755 spm/nii_for_spm2/@file_array/private/dtype.m create mode 100755 spm/nii_for_spm2/@file_array/private/fname.m create mode 100755 spm/nii_for_spm2/@file_array/private/mystruct.m create mode 100755 spm/nii_for_spm2/@file_array/private/offset.m create mode 100755 spm/nii_for_spm2/@file_array/private/resize_scales.m create mode 100755 spm/nii_for_spm2/@file_array/private/scl_inter.m create mode 100755 spm/nii_for_spm2/@file_array/private/scl_slope.m create mode 100755 spm/nii_for_spm2/@file_array/reshape.m create mode 100644 spm/nii_for_spm2/@file_array/size.m create mode 100644 spm/nii_for_spm2/@file_array/subsasgn.m create mode 100755 spm/nii_for_spm2/@file_array/subsref.m create mode 100755 spm/nii_for_spm2/@file_array/transpose.m create mode 100755 spm/nii_for_spm2/@file_array/vertcat.m create mode 100755 spm/nii_for_spm2/@nifti/Contents.m create mode 100755 spm/nii_for_spm2/@nifti/create.m create mode 100755 spm/nii_for_spm2/@nifti/disp.m create mode 100755 spm/nii_for_spm2/@nifti/display.m create mode 100755 spm/nii_for_spm2/@nifti/fieldnames.m create mode 100755 spm/nii_for_spm2/@nifti/nifti.m create mode 100755 spm/nii_for_spm2/@nifti/private/M2Q.m create mode 100755 spm/nii_for_spm2/@nifti/private/Q2M.m create mode 100755 spm/nii_for_spm2/@nifti/private/decode_qform0.m create mode 100755 spm/nii_for_spm2/@nifti/private/empty_hdr.m create mode 100755 spm/nii_for_spm2/@nifti/private/encode_qform0.m create mode 100755 spm/nii_for_spm2/@nifti/private/findindict.m create mode 100755 spm/nii_for_spm2/@nifti/private/getdict.m create mode 100755 spm/nii_for_spm2/@nifti/private/mayo2nifti1.m create mode 100755 spm/nii_for_spm2/@nifti/private/mayostruc.m create mode 100755 spm/nii_for_spm2/@nifti/private/nifti1.h create mode 100755 spm/nii_for_spm2/@nifti/private/nifti_stats.c create mode 100755 spm/nii_for_spm2/@nifti/private/nifti_stats.m create mode 100755 spm/nii_for_spm2/@nifti/private/nifti_stats_mex.c create mode 100755 spm/nii_for_spm2/@nifti/private/niftistruc.m create mode 100755 spm/nii_for_spm2/@nifti/private/read_extras.m create mode 100644 spm/nii_for_spm2/@nifti/private/read_hdr.m create mode 100644 spm/nii_for_spm2/@nifti/private/read_hdr_raw.m create mode 100755 spm/nii_for_spm2/@nifti/private/write_extras.m create mode 100644 spm/nii_for_spm2/@nifti/private/write_hdr_raw.m create mode 100755 spm/nii_for_spm2/@nifti/structn.m create mode 100755 spm/nii_for_spm2/@nifti/subsasgn.m create mode 100755 spm/nii_for_spm2/@nifti/subsref.m create mode 100644 spm/nii_for_spm2/spm_conv_vol.mexa64 create mode 100755 spm/nii_for_spm2/spm_conv_vol.mexglx create mode 100755 spm/nii_for_spm2/spm_conv_vol.mexmac create mode 100644 spm/nii_for_spm2/spm_conv_vol.mexmaci create mode 100644 spm/nii_for_spm2/spm_conv_vol.mexs64 create mode 100755 spm/nii_for_spm2/spm_conv_vol.mexsol create mode 100755 spm/nii_for_spm2/spm_conv_vol.mexw32 create mode 100644 spm/nii_for_spm2/spm_conv_vol.mexw64 create mode 100755 spm/nii_for_spm2/spm_ecat2nifti.m create mode 100755 spm/nii_for_spm2/spm_eeg_marry_scalp_head_quick.m create mode 100755 spm/nii_for_spm2/spm_fileparts.m create mode 100644 spm/nii_for_spm2/spm_mnc2nifti.m create mode 100755 spm/nii_for_spm2/spm_read_hdr.m create mode 100644 spm/nii_for_spm2/spm_read_vols.m create mode 100644 spm/nii_for_spm2/spm_render_vol.mexa64 create mode 100755 spm/nii_for_spm2/spm_render_vol.mexglx create mode 100755 spm/nii_for_spm2/spm_render_vol.mexmac create mode 100644 spm/nii_for_spm2/spm_render_vol.mexmaci create mode 100644 spm/nii_for_spm2/spm_render_vol.mexs64 create mode 100755 spm/nii_for_spm2/spm_render_vol.mexsol create mode 100755 spm/nii_for_spm2/spm_render_vol.mexw32 create mode 100644 spm/nii_for_spm2/spm_render_vol.mexw64 create mode 100644 spm/nii_for_spm2/spm_resels_vol.mexa64 create mode 100755 spm/nii_for_spm2/spm_resels_vol.mexglx create mode 100755 spm/nii_for_spm2/spm_resels_vol.mexmac create mode 100644 spm/nii_for_spm2/spm_resels_vol.mexmaci create mode 100644 spm/nii_for_spm2/spm_resels_vol.mexs64 create mode 100755 spm/nii_for_spm2/spm_resels_vol.mexsol create mode 100755 spm/nii_for_spm2/spm_resels_vol.mexw32 create mode 100644 spm/nii_for_spm2/spm_resels_vol.mexw64 create mode 100644 spm/nii_for_spm2/spm_sample_vol.mexa64 create mode 100755 spm/nii_for_spm2/spm_sample_vol.mexglx create mode 100755 spm/nii_for_spm2/spm_sample_vol.mexmac create mode 100644 spm/nii_for_spm2/spm_sample_vol.mexmaci create mode 100644 spm/nii_for_spm2/spm_sample_vol.mexs64 create mode 100755 spm/nii_for_spm2/spm_sample_vol.mexsol create mode 100755 spm/nii_for_spm2/spm_sample_vol.mexw32 create mode 100644 spm/nii_for_spm2/spm_sample_vol.mexw64 create mode 100644 spm/nii_for_spm2/spm_slice_vol.mexa64 create mode 100755 spm/nii_for_spm2/spm_slice_vol.mexglx create mode 100755 spm/nii_for_spm2/spm_slice_vol.mexmac create mode 100644 spm/nii_for_spm2/spm_slice_vol.mexmaci create mode 100644 spm/nii_for_spm2/spm_slice_vol.mexs64 create mode 100755 spm/nii_for_spm2/spm_slice_vol.mexsol create mode 100755 spm/nii_for_spm2/spm_slice_vol.mexw32 create mode 100644 spm/nii_for_spm2/spm_slice_vol.mexw64 create mode 100644 spm/nii_for_spm2/spm_vol.m create mode 100755 spm/nii_for_spm2/spm_vol_check.m create mode 100755 spm/nii_for_spm2/spm_vol_nifti.m create mode 100644 spm/spm2/myspm_get_data_xyz.m create mode 100644 spm/spm2/spm_P_RF.m create mode 100644 spm/spm2/spm_create_vol.m create mode 100644 spm/spm2/spm_dcm_display.m create mode 100644 spm/spm2/spm_defaults.m create mode 100644 spm/spm2/spm_figure.m create mode 100644 spm/spm2/spm_fmri_spm_ui.m create mode 100644 spm/spm2/spm_get.m create mode 100644 spm/spm2/spm_imcalc_ui.m create mode 100644 spm/spm2/spm_list_fullfiles.m create mode 100644 spm/spm2/spm_mask2.m create mode 100644 spm/spm2/spm_mat2hdr.m create mode 100644 spm/spm2/spm_mat_into_hdr.m create mode 100644 spm/spm2/spm_mip.m create mode 100644 spm/spm2/spm_mip_ui.m create mode 100644 spm/spm2/spm_move_spm.m create mode 100644 spm/spm2/spm_peristimulus_sampling.m create mode 100644 spm/spm2/spm_resss.m create mode 100644 spm/spm2/spm_results_ui.m create mode 100644 spm/spm2/spm_sections.m create mode 100644 spm/spm2/spm_spm.m create mode 100644 spm/spm2/spm_vol.m create mode 100644 spm/spm5/spm_2008Oct17.ps create mode 100644 spm/spm5/spm_config_realign_log.m create mode 100644 spm/spm5/spm_dicom_convert.m create mode 100644 spm/spm5/spm_jobman.m create mode 100644 spm/spm5/spm_log2html.m create mode 100644 spm/spm5/spm_vol.m create mode 100644 spm/spm_check_realign.m create mode 100644 spm/spm_log2html/spm_jobman.m create mode 100644 spm/spm_log2html/spm_log2html.m create mode 100644 spm/spm_mm2vox.m create mode 100644 spm/spm_mysections.m create mode 100644 spm/spm_render2.m create mode 100644 spm/spm_sample_vol.m create mode 100644 spm/spm_sections_colin27.m create mode 100644 spm/spm_update.m create mode 100644 spm2.m create mode 100644 spm5.m create mode 100644 spm_defaults.m create mode 100644 spm_mm2vox.m create mode 100644 spm_render2.m create mode 100644 spm_sections_colin27.m create mode 100644 sprintf2.m create mode 100644 sprintfcell.m create mode 100644 squeeze2.m create mode 100644 ssetfield.m create mode 100644 sstruct.m create mode 100644 ssvep/flicker1.m create mode 100755 staircase/CreateStaircase.m create mode 100755 staircase/GetStaircaseResults.m create mode 100755 staircase/GetStaircaseThreshold.m create mode 100755 staircase/GetStaircaseVariable.m create mode 100755 staircase/RefreshStaircase.m create mode 100755 staircase/SetStaircaseResponse.m create mode 100644 staircase/ShowStaircaseResults.m create mode 100755 staircase/UpdateStaircase.m create mode 100644 staircase/Weibull.m create mode 100644 staircase/old/CreateStaircase.m create mode 100644 staircase/old/GetStaircaseResults.m create mode 100644 staircase/old/GetStaircaseVariable.m create mode 100644 staircase/old/ShowStaircaseResults.m create mode 100644 staircase/old/UpdateStaircase.m create mode 100644 staircase/testStaircase.m create mode 100644 start_brainstorm3.m create mode 100644 start_eeglab.m create mode 100644 start_spm.m create mode 100644 startup.m create mode 100644 stat/alpha_significance.m create mode 100644 stat/alpha_threshold.m create mode 100644 stat/anova_examples.m create mode 100644 stat/anovaeffects.m create mode 100644 stat/anovalevels.m create mode 100644 stat/anovand.m create mode 100644 stat/binocdf.m create mode 100644 stat/chi2_cdf.m create mode 100644 stat/cohen_d.m create mode 100644 stat/coincidence_matrix.m create mode 100644 stat/compare_anovas.m create mode 100644 stat/cronbach.m create mode 100644 stat/distributionPlot/countEntries.m create mode 100644 stat/distributionPlot/distributionPlot.m create mode 100644 stat/distributionPlot/histogram.m create mode 100644 stat/distributionfit.m create mode 100644 stat/dunnsidak.m create mode 100644 stat/effectsizes_to_errorrates.m create mode 100644 stat/f_cdf.m create mode 100644 stat/friedman_posthoc.m create mode 100644 stat/friedman_test.m create mode 100644 stat/ftest.m create mode 100644 stat/goutte/avevar.m create mode 100644 stat/goutte/bootstrap.m create mode 100644 stat/goutte/budget.txt create mode 100644 stat/goutte/ftest.m create mode 100644 stat/goutte/jackknife.m create mode 100644 stat/goutte/kstest.m create mode 100644 stat/goutte/pca.m create mode 100644 stat/goutte/pttest.m create mode 100644 stat/goutte/qks.m create mode 100644 stat/goutte/test.m create mode 100644 stat/goutte/ttest.m create mode 100644 stat/goutte/uttest.m create mode 100644 stat/hedge_g.m create mode 100644 stat/hochberg.m create mode 100644 stat/holm.m create mode 100644 stat/krippendorff_alpha.m create mode 100644 stat/kstest.m create mode 100644 stat/kstest2.m create mode 100644 stat/levenetest.m create mode 100644 stat/mvrnorm.m create mode 100644 stat/mwwtest.m create mode 100644 stat/myanova.m create mode 100644 stat/myanova3.m create mode 100644 stat/myanovaeffects.m create mode 100644 stat/mypermtest.m create mode 100644 stat/myplannedcomp.m create mode 100644 stat/myposthoc.m create mode 100644 stat/myrmanova.m.bad create mode 100644 stat/myttest.m create mode 100644 stat/octave_anovan.m create mode 100644 stat/p_rep.m create mode 100644 stat/permftest.m create mode 100644 stat/permktest.m create mode 100644 stat/permtest/permtest.m create mode 100644 stat/permtest/permtest_cluster.m create mode 100644 stat/permttest.m create mode 100644 stat/permttest_cluster.m create mode 100644 stat/permttest_norm.m create mode 100644 stat/permttest_norm2.m create mode 100644 stat/permttest_run.m create mode 100644 stat/phicoef.m create mode 100644 stat/posthoc.m create mode 100755 stat/powerStudent.m create mode 100644 stat/power_ttest.m create mode 100644 stat/pseudotvalue.m create mode 100644 stat/pttest.m create mode 100644 stat/pvalue.m create mode 100644 stat/qtukey.m create mode 100644 stat/rau.m create mode 100644 stat/rep_anovan.m create mode 100644 stat/rm_anova2.m create mode 100644 stat/shapirowilktest.m create mode 100644 stat/spearmann.m create mode 100644 stat/ssbar.m create mode 100644 stat/stat_bar.m create mode 100644 stat/stat_examples.m create mode 100644 stat/t_cdf.m create mode 100644 stat/t_test.m create mode 100644 stat/test.m create mode 100644 stat/test_anova.m create mode 100644 stat/test_perm.m create mode 100644 stat/testdata.m create mode 100644 stat/ttest.m create mode 100644 stat/ttest_bonf.m create mode 100644 stat/tukeyhsd.m create mode 100644 stat/tvalue.bak.dll create mode 100644 stat/tvalue.c create mode 100644 stat/tvalue.dll create mode 100644 stat/tvalue.m create mode 100644 stat/tvalue2.dll create mode 100644 stat/u_test.m create mode 100644 stat/uttest.m create mode 100644 stat/winsor.m create mode 100644 stderr.m create mode 100644 stderr_within.m create mode 100644 stderrw.m create mode 100644 stdnan.m create mode 100644 stdw.m create mode 100644 strcell.m create mode 100644 strfindobj.m create mode 100644 strongconn.m create mode 100644 struct2cell2.m create mode 100644 struct2fv.m create mode 100644 struct2list.m create mode 100644 struct2minf.m create mode 100644 struct2tab.m create mode 100644 structcomparator.m create mode 100644 structdiff.m create mode 100644 structmatch.m create mode 100644 subarray.m create mode 100644 subdir.m create mode 100644 subjectimage2spmvol.m create mode 100644 subpatch.m create mode 100644 subsasgn2.m create mode 100644 sumnan.m create mode 100644 system1020.m create mode 100644 tabread.m create mode 100644 template.m create mode 100644 tess2patch.m create mode 100644 tex_clusters.m create mode 100644 textalign.m create mode 100644 threshold.m create mode 100644 tiedrank.m create mode 100644 tiedrank2.m create mode 100644 tilefigs.m create mode 100644 tiles.m create mode 100644 tilesperm.m create mode 100644 timebar.m create mode 100644 timeit.m create mode 100644 togglestate.m create mode 100644 trim.m create mode 100644 trimsurface.m create mode 100644 trimsurface_gui.m create mode 100644 twxticklabel.m create mode 100644 uigetpathfile.m create mode 100644 usbpath.m create mode 100644 vectvec.m create mode 100644 vertvec.m create mode 100644 vidal.m create mode 100644 view3d.m create mode 100644 viewer3d.m create mode 100644 vline.m create mode 100644 wheelzoom.m create mode 100644 whichLoop.m create mode 100644 whichRank.m create mode 100644 windfind.m create mode 100644 wwhich.m create mode 100644 x2cell.m create mode 100644 x2char.m create mode 100644 xjview.m create mode 100644 xjview8p4.m create mode 100644 xjview_labnic.m create mode 100644 xjview_markus.m create mode 100644 xjview_v4.m create mode 100644 xline.m create mode 100644 xml2struct.m create mode 100644 xticklabel_rotate.m create mode 100644 xyz2channel.m create mode 100644 xyz2channels.m create mode 100644 xyzlabels.m create mode 100644 yline.m create mode 100644 yprime.dll create mode 100644 yuv2rgb.m create mode 100644 zoom_axes.m create mode 100644 zscore.m create mode 100644 ztransform.m diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..241bda6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.svn +.DS_Store \ No newline at end of file diff --git a/3rdparty/dualcursor.m b/3rdparty/dualcursor.m new file mode 100644 index 0000000..cfcf989 --- /dev/null +++ b/3rdparty/dualcursor.m @@ -0,0 +1,846 @@ +function val = dualcursor(state,deltalabelpos,marker_color,datalabelformatfcnh,axh) +%dualcursor Add dual vertical cursors to a plot +% +%Syntax +% dualcursor +% dualcursor('state'); +% dualcursor([X1 X2]); +% dualcursor('state',deltalabelpos); +% dualcursor([X1 X2],deltalabelpos); +% dualcursor('update', ...) %Updates existing cursors when data has changed +% dualcursor(..., ..., marker_color_spec); +% dualcursor(..., ..., ..., fcnhandle); +% dualcursor(..., ..., ..., ..., axh); +% val = dualcursor %return values of the 2 cursors on the current axis +% val = dualcursor(h) %return values of the 2 cursors on the axis or line w/handle h +% +%Description +% Easy Mode: +% dualcursor on %Turns on data cursors +% dualcursor off %Turns off data cursors +% dualcursor %Toggles the state of data cursors +% val = dualcursor %Return the coordinates of the 2 cursors +% val = [x1 y1 x2 y2] +% +% Interaction: +% Click on the cursors to drag them around. +% Click on the cursor label to reposition. +% If multiple lines are plotted, click on a line to make it active. +% Right click on a cursor to: +% - Export the selected region to the workspace +% (exports as a structure named cursors) +% - Export the selected region to a new figure +% - Export cursor data to workspace +% (exports as a variable named cursordata = [x1 y1 x2 y2]) +% - Remove the cursors from the plot +% Click on the delta calculation labels to reposition them. +% +% Advanced Options: +% 1. Specify initial x-coordinates for the cursors +% dualcursor([x1 x2]); %Adds cursors at x1, x2 +% +% 2. Specify the location of the text label for displaying x2-x1, y2-y1 results +% dualcursor([],deltalabelpos); %Specifies the location to display DeltaX +% %and DeltaY calculations +% deltalabelpos = [DeltaX_x, DeltaX_y; DeltaY_x, DeltaY_y] in normalized axis units +% default = [.65 -.08;.9 -.08] puts them just below the lower right-hand corner. +% If this is too hard, you can also reposition the labels with your mouse! +% +% 3. Specifying the color and marker for the cursors +% dualcursor([],[],marker_color_spec); +% %Turns on data cursor, using specified marker and color +% %marker_color_spec is a one or two element string, specifying color and/or marker +% %style. >>help plot for valid marker and color specifiers +% ex 'go' - red circles +% 's' - squares, with default color (red) +% +% 4. Update the existing cursors when the data in the plot has changed. +% dualcursor('update'); +% This mode is useful when using the cursors on a live data stream. +% +% This simply updates the appropriate values to reflect the latest data. Here's +% how to do this: +% - When you initialize the graphics before taking data, make your initial call +% to dualcursor (dualcursor on, for instance) +% - In the code that updates the graphics display with new data, call +% dualcursor update +% +% 5. Specify custom formatting for the data label showing individual cursor values +% This is implemented with a function handle (@). You provide a handle to a +% specifically formatted function: +% dualcursor(..., ..., ..., fcnhandle) +% %Specifies the text and formatting for the data label +% %fcnhandle is a handle to the function that defines the formatting. This function +% %must have the following argument syntax: +% function textstring = mytextstring(xv,yv); +% Input: +% xv (scalar) The x cursor value +% yv (scalar) The y cursor value +% Output: +% textstring (string) The formatted text string +% {'string'} Cell array of strings for multi-line display +% +% Example +% Create the following function. save as frequencystring.m +% function textstring = frequencystring(xv,yv); +% textstring = {['Amp: ' num2str(yv,'%2g') ' dB'] +% ['f: ' num2str(xv,'%2g') ' Hz'];}; +% %Use this code when you are ready to call dualcursor +% fcnhandle = @frequencystring; %Create handle to your function +% dualcursor(..., ..., ..., fcnhandle) +% +% 6. Add cursors to a specific axis (not necessarily current axis). +% dualcursor(..., ..., ..., ..., axh); +% Adds the cursors to the axis with handle axh +% This mode is useful when axis handles might be hidden +% +% +% Example +% %Set up some interesting data +% load handel +% Ns = 2^12; +% Y = fft(y,Ns); +% Y = 2/Ns*abs(Y(1:Ns/2)); +% df = Fs/Ns; +% f = (0:1:Ns/2-1)*df; +% %Plot it +% figure; +% plot(f,100*[Y sqrt(Y)]) +% title('My cursor example'); +% xlabel('Frequency (Hz)'); +% ylabel('Amplitude'); +% axis([0 1200 0 inf]); +% +% %Turn on cursors +% dualcursor +% +% %Turn off cursors +% dualcursor off +% +% %Place cursors at x=300 and x=400, place DeltaX/DeltaY display in the upper left +% % hand corner, and use a green square for the cursor. +% dualcursor([300 400],[.05 1.05; .25 1.05],'gs'); +% +% %Move the cursors around. Try moving the data label, too +% +% %Now, wasn't that fun? Finally, get the current cursor positions +% val = dualcursor; +% %val = [x1 y1 x2 y2] +% +% +% NOTE: HandleVisibility of axis must be set to 'on' or 'callback' +% +% see also: DATALABEL, LINELABEL + +% This function is provided as an example only. It has not been +% tested, and therefore, it is not officially supported by The +% MathWorks, Inc. + +% Written by Scott Hirsch +% shirsch@mathworks.com +% Copyright (c) by The MathWorks, Inc. 1985-2002 +% This is a (major) modfication of datalabel, available at MATLAB Central + +%Parse input arguments +%If output argument, return the cursor values + +%See if the user specified a formatting function for the datalabel + + +if nargin<4 || isempty(datalabelformatfcnh) %Nope. Use the default one included here. + datalabelformatfcnh = @local_maketextstring; +end; + +if nargout + if nargin==0 %Use current axis + h = gca; + else + h = state; + if strcmp(get(h,'Type'),'line'); + h = get(h,'Parent'); + end; + end; + cursors = findobj(h,'Tag','Cursor'); + if length(cursors)==2 %Should be empty (no cursors), or length=2 + for ii=1:2 + cn = getappdata(cursors(ii),'CursorNumber'); + ind = (cn-1)*2+1:cn*2; %Index into val + val(ind) = getappdata(cursors(ii),'Coordinates'); + end; + else + val = []; + warning('I could not find any cursors'); + end; + return +elseif nargin<5 || isempty(axh) %Did the user specify a handle? + axh = gca; +end; + + +%If no input arguments, switch state (turn off/on) +if (nargin==0 & nargout==0) | isempty(state) %Switch state. Check current state + dots = findobj(axh,'Type','line','Tag','Cursor'); %See if there are any cursors + if isempty(dots) %None found. Turn cursors on + state = 'on'; + else + state = 'off'; + end; +end; + +%Check if the first argument is numeric. The user is specifying +% the initial x-coordinates of the markers +if nargin>=1 & isnumeric(state) %First input is x coordinates of markers + x_init = state(:); + + %error check + if length(x_init)~=2 + error('First input must be 2 element vector of x coordinates'); + end; + state = 'on'; %Turn on data cursors. +else %Default position = 1/3, 2/3 x axis limits + % xl = xlim; %X Limits. this is the letter L, not the number 1 + if strcmp(get(axh,'Type'),'figure') || strcmp(get(axh,'Type'),'uipanel') || strcmp(get(axh,'Type'),'root'); %user clicked on the axis itself; do nothing + return + end; + + + xl = xlim(axh); + lim = localObjbounds(axh); % Problem using objbounds with hggroup objects, so I have a simple version of my own + lim = lim(1:2); %x values only + xl(isinf(xl)) = lim(isinf(xl)); + width = diff(xl); %Axis width + x_init = xl(1)+[1/3 2/3]*width; + +end; + +% Get handle to figure just once +hFig = ancestor(axh,'figure'); + + +switch state + case 'on' + %Initialization + % Set the WindowButtonDownFcn + % Add the cursors. + + %If there are already some data cursors on this plot, delete them! + dualcursor('off',[],[],[],axh); + + %Parse user inputs + + %Check for user input of position for delta labels + if nargin<2 | isempty(deltalabelpos) + deltalabelpos = [.65 -.08;.9 -.08]; %Use defaults + %[x1 y1; x2 y2] + end; + + + %Marker and color specification + if nargin >= 3 , + %Parse marker string. User might specify color, marker, or both + colors = 'bgrcmyk'; + markers = '+o*.xsdv^>=6.5 + set(th1,'BackgroundColor','y'); + set(th2,'BackgroundColor','y'); + end; + + yl = ylim(axh); + lim = localObjbounds(axh); + lim = lim(3:4); %y values only + yl(isinf(yl)) = lim(isinf(yl)); + + %Add the cursors + ph1 = line([xv1 xv1 xv1],[yl(1) yv1 yl(2)], ... + 'Color',color, ... + 'Marker',marker, ... + 'Tag','Cursor', ... + 'UserData',[lineh th1], ... + 'LineStyle','-', ... + 'Parent',axh); + ph2 = line([xv2 xv2 xv2],[yl(1) yv2 yl(2)], ... + 'Color',color, ... + 'Marker',marker, ... + 'Tag','Cursor', ... + 'UserData',[lineh th2], ... + 'LineStyle','-', ... + 'Parent',axh); + + %Add context menu to the cursors +% cmenu = uicontextmenu('Parent',get(axh,'Parent')); + cmenu = uicontextmenu('Parent',hFig); + set([ph1 ph2],'UIContextMenu',cmenu); + + % Define the context menu items + item1 = uimenu(cmenu, 'Label', 'Export region to workspace', ... + 'Callback', 'dualcursor(''exportws'',[],[],[],get(gco,''Parent''))'); + item2 = uimenu(cmenu, 'Label', 'Export region to new figure', ... + 'Callback', 'dualcursor(''exportfig'',[],[],[],get(gco,''Parent''))'); + item3 = uimenu(cmenu, 'Label', 'Export cursor data to workspace', ... + 'Callback', 'dualcursor(''exportcursor'',[],[],[],get(gco,''Parent''));'); + item4 = uimenu(cmenu, 'Label', 'Turn cursors off', ... + 'Callback', 'dualcursor(''off'',[],[],[],get(gco,''Parent''))', ... + 'Separator','on'); + + + setappdata(th1,'Coordinates',[xv1 yv1]); + setappdata(ph1,'Coordinates',[xv1 yv1]); + setappdata(th2,'Coordinates',[xv2 yv2]); + setappdata(ph2,'Coordinates',[xv2 yv2]); + setappdata(th1,'CursorNumber',1); + setappdata(ph1,'CursorNumber',1); + setappdata(th2,'CursorNumber',2); + setappdata(ph2,'CursorNumber',2); + setappdata(th1,'FormatFcnH',datalabelformatfcnh); + setappdata(th2,'FormatFcnH',datalabelformatfcnh); + setappdata(th1,'Offset',[0 0]); %Offset for the text label from the data value + setappdata(th2,'Offset',[0 0]); %Offset for the text label from the data value + + % mh = uicontextmenu('Tag','DeleteObject', ... + % 'Callback','ud = get(gco,''UserData'');delete([gco ud(2)]);'); + % set([th1 th2],'UIContextMenu',mh); + + set(th1,'UserData',[lineh ph1]); %Store handle to line + set(th2,'UserData',[lineh ph2]); %Store handle to line + + + %Calculate Difference + dx = xv2 - xv1; + dy = yv2 - yv1; + + %Add display for cursor deltas + deltah(1) = text(deltalabelpos(1,1),deltalabelpos(1,2),['({x}_{2}-{x}_{1}) = ' num2str(dx,'%+.2f')], ... + 'Units','Normalized', ... + 'HorizontalAlignment','left', ... + 'Tag','CursorDeltaText', ... + 'FontSize',8, ... + 'Interpreter','tex', ... + 'Parent',axh); %dx + deltah(2) = text(deltalabelpos(2,1),deltalabelpos(2,2),['{y}_{2}-{y}_{1} = ' num2str(dy,'%+.2f')], ... + 'Units','Normalized', ... + 'HorizontalAlignment','left', ... + 'Tag','CursorDeltaText', ... + 'FontSize',8, ... + 'Interpreter','tex', ... + 'Parent',axh); %dy + + %Set Application Data. + setappdata(axh,'Delta_Handle',deltah); + setappdata(axh,'Marker',marker); + setappdata(axh,'Color',color); + + set(hFig,'WindowButtonDownFcn','dualcursor(''down'',[],[],[],get(gco,''Parent''))') + set(hFig,'DoubleBuffer','on'); %eliminate flicker + + case 'down' % Execute the WindowButtonDownFcn + htype = get(gco,'Type'); + tag = get(gco,'Tag'); + marker = getappdata(axh,'Marker'); + color = getappdata(axh,'Color'); + + %If it's a movable object (Cursor, CursorText label, or Cursor Delta Text + %label), make it movable. + if strcmp(tag,'CursorText') | strcmp(tag,'Cursor') | strcmp(tag,'CursorDeltaText') + set(gco,'EraseMode','xor') + set(gcf,'WindowButtonMotionFcn','dualcursor(''move'',[],[],[],get(gco,''Parent''))', ... + 'WindowButtonUpFcn','dualcursor(''up'',[],[],[],get(gco,''Parent''))'); + end; + + if strcmp(tag,'Cursor') %If clicked on a cursor + %Label the cursor we are moving. Add a text label just above the plot + CursorNumber = getappdata(gco,'CursorNumber'); + cnstr = num2str(CursorNumber); + + xl = xlim(axh); + lim = localObjbounds(axh); + lim = lim(1:2); %x values only + xl(isinf(xl)) = lim(isinf(xl)); + % yl = lim(3:4); + + + xv = getappdata(gco,'Coordinates'); + xv = xv(1); + + %Convert to normalized + xn = (xv - xl(1))/(xl(2) - xl(1)); + yn = 1.05; + + CNh = text(xn,yn,cnstr, ... + 'Units','Normalized', ... + 'HorizontalAlignment','center', ... + 'Parent',axh); %dx + + %For R13 or higher (MATLAB 6.5), use a background color on the text string + v=ver('MATLAB'); + v=str2num(v.Version); + if v>=6.5 + set(CNh,'BackgroundColor','y'); + end; + setappdata(gco,'CNh',CNh); + + end + + case 'move' % Execute the WindowButtonMotionFcn + htype = get(gco,'Type'); + tag = get(gco,'Tag'); + if ~isempty(gco) + + %Special (simple) case - just repositioning the delta labels + if strcmp(tag,'CursorDeltaText') %The cursor delta labels. + cp = get(axh,'CurrentPoint'); + pt = cp(1,[1 2]); + + %Put into normalized units + ax = axis; + lim = localObjbounds(axh); + ax(isinf(ax)) = lim(isinf(ax)); + + pt(1) = (pt(1) - ax(1))/(ax(2)-ax(1)); + pt(2) = (pt(2) - ax(3))/(ax(4)-ax(3)); + + set(gco,'Position', [pt 0]) + drawnow + return + end; + + %Is this the cursor or the text + if strcmp(tag,'CursorText') %The text + th = gco; + handles = get(gco,'UserData'); + ph = handles(2); + slide = 0; %Don't slide along line; just reposition text + else %The marker + ph = gco; + handles = get(gco,'UserData'); + th = handles(2); + slide = 1; %Slide along line to next data point + end; + + offset = getappdata(th,'Offset'); %Offset from data value + + cp = get(axh,'CurrentPoint'); + pt = cp(1,[1 2]); + + %Constrain to Line + lh = getappdata(get(th,'Parent'),'SelectedLine'); + % lh = handles(1); %Line + + x = cp(1,1); %first xy values + y = cp(1,2); %first xy values + + if slide %Move to new data value + xl = get(lh,'XData'); + yl = get(lh,'YData'); + + + %Get nearest value + [xv,yv]=local_nearest(x,xl,yl); + + + %If we are moving a cursor, must move the cursor number label, too + if strcmp(tag,'Cursor') + %Move the Cursor Number label, too + CNh = getappdata(gco,'CNh'); + pos = get(CNh,'Position'); + xlm = xlim(axh); + lim = localObjbounds(axh); + lim = lim(1:2); %x values only + xlm(isinf(xlm)) = lim(isinf(xlm)); + xn = (xv - xlm(1))/(xlm(2)-xlm(1)); + set(CNh,'Position',[xn pos(2:3)]) + end; + + yl = ylim(axh); + lim = localObjbounds(get(lh,'Parent')); + lim = lim(3:4); %y values only + yl(isinf(yl)) = lim(isinf(yl)); + datalabelformatfcnh = getappdata(th,'FormatFcnH'); + + textstring = feval(datalabelformatfcnh,xv,yv); + set(th,'Position', [xv yv 0] + [offset 0],'String',textstring) + set(ph,'XData',[xv xv xv],'YData',[yl(1) yv yl(2)]); + + setappdata(ph,'Coordinates',[xv yv]); + setappdata(th,'Coordinates',[xv yv]); + + %Update delta calculation + cursors = findobj(axh,'Tag','Cursor'); + cn1 = getappdata(cursors(1),'CursorNumber'); + if cn1==2 %Switch order + temp = cursors(1); + cursors(1) = cursors(2); + cursors(2) = temp; + end; + + + deltah = getappdata(axh,'Delta_Handle'); %Handle to cursors + + %Positions of two dualcursors + xy1 = getappdata(cursors(1),'Coordinates'); + xy2 = getappdata(cursors(2),'Coordinates'); + + + %Calculate Difference + dx = xy2(1) - xy1(1); + dy = xy2(2) - xy1(2); + + set(deltah(1),'String',['({x}_{2}-{x}_{1}) = ' num2str(dx,'%+.2f')]); + set(deltah(2),'String',['({y}_{2}-{y}_{1}) = ' num2str(dy,'%+.2f')]); + + else %Just move text around. + set(th,'Position', [x y 0]) + end; + drawnow + end; + + case 'up' % Execute the WindowButtonUpFcn + htype = get(gco,'Type'); + tag = get(gco,'Tag'); + if strcmp(tag,'CursorText') | strcmp(tag,'Cursor') | strcmp(tag,'CursorDeltaText'); + set(gco,'EraseMode','Normal') + set(gcf,'WindowButtonMotionFcn','') + + if strcmp(tag,'CursorText') %If the text label, record it's relative position + cp = get(axh,'CurrentPoint'); + pt = cp(1,[1 2]); + + coords = getappdata(gco,'Coordinates'); + offset(1) = pt(1) - coords(1); + offset = pt - coords; + setappdata(gco,'Offset',offset); + end; + + + if strcmp(tag,'Cursor') %Delete the temporary cursor number label + CNh = getappdata(gco,'CNh'); + delete(CNh); + end; + + end; + + case 'selectline' % User selected a new line to be active + %Make the selected line bold + lh = getappdata(axh,'SelectedLine'); + lw = get(lh,'LineWidth'); + set(lh,'LineWidth',5*lw); + drawnow + + %Update the cursors + dualcursor('update',deltalabelpos,marker_color,datalabelformatfcnh,axh); + + %Put the line back the way you found it! + set(lh,'LineWidth',lw); + + case 'update' %Update the cursor value + %Find the position of the existing cursors + + cursors = findobj(axh,'Tag','Cursor'); + cd1 = getappdata(cursors(1),'Coordinates'); + cd2 = getappdata(cursors(2),'Coordinates'); + cn1 = getappdata(cursors(1),'CursorNumber'); + cn2 = getappdata(cursors(2),'CursorNumber'); + clear x + x(cn1) = cd1(1); %x value of cursor number cn1 + x(cn2) = cd2(1); %x value of cursor number cn2 + + lh = getappdata(axh,'SelectedLine'); + + handles = get(cursors(cn1),'UserData'); + th1 = handles(2); + handles = get(cursors(cn2),'UserData'); + th2 = handles(2); + + offset1 = getappdata(th1,'Offset'); %Offset from data value + offset2 = getappdata(th2,'Offset'); %Offset from data value + + + ylm = ylim(axh); + lim = localObjbounds(get(lh,'Parent')); + lim = lim(3:4); %y values only + ylm(isinf(ylm)) = lim(isinf(ylm)); + + + xl = get(lh,'XData'); + yl = get(lh,'YData'); + + + %Get nearest value + [xv1,yv1]=local_nearest(x(1),xl,yl); + [xv2,yv2]=local_nearest(x(2),xl,yl); + + datalabelformatfcnh = getappdata(th1,'FormatFcnH'); + + % textstring1 = {['x=' num2str(xv1)];['y=' num2str(yv1)]}; + % textstring2 = {['x=' num2str(xv2)];['y=' num2str(yv2)]}; + textstring1 = feval(datalabelformatfcnh,xv1,yv1); + textstring2 = feval(datalabelformatfcnh,xv2,yv2); + set(th1,'Position', [xv1 yv1 0] + [offset1 0],'String',textstring1) + set(th2,'Position', [xv2 yv2 0] + [offset2 0],'String',textstring2) + set(cursors(cn1),'XData',[xv1 xv1 xv1],'YData',[ylm(1) yv1 ylm(2)]); + set(cursors(cn2),'XData',[xv2 xv2 xv2],'YData',[ylm(1) yv2 ylm(2)]); + %Update delta calculation + deltah = getappdata(axh,'Delta_Handle'); %Handle to delta calculation + + % %Positions of two dualcursors + xy1 = getappdata(cursors(1),'Coordinates'); + xy2 = getappdata(cursors(2),'Coordinates'); + + %Calculate Difference + dx = xv2 - xv1; + dy = yv2 - yv1; + + set(deltah(1),'String',['{x}_{2}-{x}_{1} ' num2str(dx,'%+.2f')]); + set(deltah(2),'String',['{y}_{2}-{y}_{1} ' num2str(dy,'%+.2f')]); + + case 'exportws' %Export selected region to workspace + [xd,yd] = local_extractregion(axh); + + %If there's only one line, don't make the user hassle with cell arrays + if length(xd)==1 + xd = xd{1}; + yd = yd{1}; + end; + + cursors.xd = xd; + cursors.yd = yd; + + assignin('base','cursors',cursors); + disp('Variable: cursors created in workspace'); + + case 'exportfig' %Export selected region to a new figure + % [xd,yd,proplist,props] = local_extractregion; + [xd,yd,hgS] = local_extractregion(axh); + + %Create new plot + fh = figure; + ax = axes; + struct2handle(hgS,ax); + + %Get labels from axes, too + %Clunky, but it seems to work + %First, create empty title, xlabel, ylabel + newhandles(1) = title(''); + newhandles(2) = xlabel(''); + newhandles(3) = ylabel(''); + + %Get strings from original plot + props = {'Title','xlabel','ylabel'}; + vals = get(axh,props); + handles = [vals{:}]; + str = get(handles,'String'); + + %Set the new strings to match + set(newhandles,{'String'},str) + + case 'exportcursor' + % Get cursor coordinates + cursordata = dualcursor(axh); + + % Push the data into the base workspace. + assignin('base','cursordata',cursordata); + disp('Variable: cursordata created in workspace'); + + case 'off' % Unset the WindowButton...Fcns + % set(get(axh,'Parent'),'WindowButtonDownFcn','','WindowButtonUpFcn','') + set(hFig,'WindowButtonDownFcn','','WindowButtonUpFcn','') + + h1 = findobj(axh,'Tag','CursorText'); %All text + h2 = findobj(axh,'Tag','Cursor'); %The cursors + h3 = findobj(axh,'Tag','CursorDeltaText'); %The cursors + + lineh = local_findlines(axh); + set(lineh,'ButtonDownFcn',''); + + % erasemode = getappdata(axh,'OriginalEraseMode'); + % if isempty(erasemode), erasemode = 'normal'; end; %handles first time + % set(local_findlines(axh),'EraseMode',erasemode); + + delete([h1;h2;h3]); + +end %switch/case on action +end + +% function hFig = figHandle(axh) +% % Get the handle to the parent figure +% hFig = ancestor(axh,'figure'); +% end + +function [xv,yv]=local_nearest(x,xl,yl) +%Inputs: +% x Selected x value +% xl Line Data (x) + +%Find nearest value of [xl] to (x) +%Special Case: Line has a single non-singleton value +if sum(isfinite(xl))==1 + fin = find(isfinite(xl)); + xv = xl(fin); + yv = yl(fin); +else + %Normalize axes + xlmin = min(xl); + xlmax = max(xl); + xln = (xl - xlmin)./(xlmax - xlmin); + xn = (x - xlmin)./(xlmax - xlmin); + + + %Find nearest x value only. + c = abs(xln - xn); + + [junk,ind] = min(c); + + %Nearest value on the line + xv = xl(ind); + yv = yl(ind); +end; +end + +function textstring = local_maketextstring(xv,yv) +textstring = {['x = ' num2str(xv,'%2g')]; + ['y = ' num2str(yv,'%2.2g')]}; +end + +function [xd,yd,hgS] = local_extractregion(axh) +val = dualcursor(axh); + +%Find all lines +lineh = local_findlines(axh); + +Nl = length(lineh); + +%Get all line properties, so we can reproduce the appearance +hgS = handle2struct(lineh); %Get properties + +xd = get(lineh,'XData'); +yd = get(lineh,'YData'); + +%Figure out the index into these lines that val corresponds to +x1ind = zeros(Nl,1); +x2ind = zeros(Nl,1); + +%Special handling for single line case +if Nl==1 + xd= {xd}; + yd = {yd}; +end; + +for ii = 1:Nl + x1ind(ii) = max(find(xd{ii}<=val(1))); + x2ind(ii) = max(find(xd{ii}<=val(3))); + + %Keep data from this region only + xd{ii} = xd{ii}(x1ind(ii):x2ind(ii)); + yd{ii} = yd{ii}(x1ind(ii):x2ind(ii)); + + %Update handle structure, to make plotting really easy + hgS(ii).properties.XData = xd{ii}; + hgS(ii).properties.YData = yd{ii}; + +end; +end + +function lineh = local_findlines(axh); +lineh = findobj(axh,'Type','line'); %Find a line to add cursor to +dots = findobj(axh,'Type','line','Tag','Cursor'); %Ignore existing cursors +lineh = setdiff(lineh,dots); + +%Ignore lines with only one or two values - these are probably annotations of some +%sort +xdtemp = get(lineh,'XData'); +linehtemp = lineh; +lineh=[]; +if ~iscell(xdtemp) %If there's only one line, force data into a cell array + xdtemp = {xdtemp}; +end; + +for ii=1:length(xdtemp); + if length(xdtemp{ii})>2 + lineh = [lineh; linehtemp(ii)]; + end; +end; +end + +function lim = localObjbounds(axh); +% Get x limits of all data in axes axh +kids = get(axh,'Children'); +xmin = Inf; xmax = -Inf; +ymin = Inf; ymax = -Inf; +for ii=1:length(kids) + try % Pass through if can't get data. hopefully we hit at least one + xd = get(kids(ii),'XData'); + xmin = min([xmin min(xd(:))]); + xmax = max([xmax max(xd(:))]); + + yd = get(kids(ii),'YData'); + ymin = min([ymin min(yd(:))]); + ymax = max([ymax max(yd(:))]); + end +end +% Nuclear option, in case things went really bad +xmin(xmin==Inf) = 0; xmax(xmax==-Inf) = 1; +ymin(ymin==Inf) = 0; ymax(ymax==-Inf) = 1; + +lim = [xmin xmax ymin ymax]; +end diff --git a/3rdparty/findjobj.m b/3rdparty/findjobj.m new file mode 100644 index 0000000..324940f --- /dev/null +++ b/3rdparty/findjobj.m @@ -0,0 +1,2793 @@ +function [handles,levels,parentIdx,listing] = findjobj(container,varargin) +%findjobj Find java objects contained within a specified java container or Matlab GUI handle +% +% Syntax: +% [handles, levels, parentIds, listing] = findjobj(container, 'PropName',PropValue(s), ...) +% +% Input parameters: +% container - optional handle to java container uipanel or figure. If unsupplied then current figure will be used +% 'PropName',PropValue - optional list of property pairs (case insensitive). PropName may also be named -PropName +% 'position' - filter results based on those elements that contain the specified X,Y position or a java element +% Note: specify a Matlab position (X,Y = pixels from bottom left corner), not a java one +% 'size' - filter results based on those elements that have the specified W,H (in pixels) +% 'class' - filter results based on those elements that contain the substring (or java class) PropValue +% Note1: filtering is case insensitive and relies on regexp, so you can pass wildcards etc. +% Note2: '-class' is an undocumented findobj PropName, but only works on Matlab (not java) classes +% 'property' - filter results based on those elements that possess the specified case-insensitive property string +% Note1: passing a property value is possible if the argument following 'property' is a cell in the +% format of {'propName','propValue'}. Example: FINDJOBJ(...,'property',{'Text','click me'}) +% Note2: partial property names (e.g. 'Tex') are accepted, as long as they're not ambiguous +% 'depth' - filter results based on specified depth. 0=top-level, Inf=all levels (default=Inf) +% 'flat' - same as specifying: 'depth',0 +% 'not' - negates the following filter: 'not','class','c' returns all elements EXCEPT those with class 'c' +% 'persist' - persist figure components information, allowing much faster results for subsequent invocations +% 'print' - display all java elements in a hierarchical list, indented appropriately +% Note1: optional PropValue of element index or handle to java container +% Note2: normally this option would be placed last, after all filtering is complete. Placing this +% option before some filters enables debug print-outs of interim filtering results. +% Note3: output is to the Matlab command window unless the 'listing' (4th) output arg is requested +% 'list' - same as 'print' +% 'nomenu' - skip menu processing, for "lean" list of handles & much faster processing; +% This option is the default for HG containers but not for figure, Java or no container +% 'debug' - list found component positions in the Command Window +% +% Output parameters: +% handles - list of handles to java elements +% levels - list of corresponding hierarchy level of the java elements (top=0) +% parentIds - list of indexes (in unfiltered handles) of the parent container of the corresponding java element +% listing - results of 'print'/'list' options (empty if these options were not specified) +% +% Note: If no output parameter is specified, then an interactive window will be displayed with a +% ^^^^ tree view of all container components, their properties and callbacks. +% +% Examples: +% findjobj; % display list of all javaelements of currrent figure in an interactive GUI +% handles = findjobj; % get list of all java elements of current figure (inc. menus, toolbars etc.) +% findjobj('print'); % list all java elements in current figure +% findjobj('print',6); % list all java elements in current figure, contained within its 6th element +% handles = findjobj(hButton); % hButton is a matlab button +% handles = findjobj(gcf,'position',getpixelposition(hButton,1)); % same as above but also return hButton's panel +% handles = findjobj(hButton,'persist'); % same as above, persist info for future reuse +% handles = findjobj('class','pushbutton'); % get all pushbuttons in current figure +% handles = findjobj('class','pushbutton','position',123,456); % get all pushbuttons at the specified position +% handles = findjobj(gcf,'class','pushbutton','size',23,15); % get all pushbuttons with the specified size +% handles = findjobj('property','Text','not','class','button'); % get all non-button elements with 'text' property +% handles = findjobj('-property',{'Text','click me'}); % get all elements with 'text' property = 'click me' +% +% Sample usage: +% hButton = uicontrol('string','click me'); +% jButton = findjobj(hButton,'nomenu'); +% % or: jButton = findjobj('property',{'Text','click me'}); +% jButton.setFlyOverAppearance(1); +% jButton.setCursor(java.awt.Cursor.getPredefinedCursor(java.awt.Cursor.HAND_CURSOR)); +% set(jButton,'FocusGainedCallback',@myMatlabFunction); % some 30 callback points available... +% jButton.get; % list all changeable properties... +% +% hEditbox = uicontrol('style',edit'); +% jEditbox = findjobj(hEditbox,'nomenu'); +% jEditbox.setCaretColor(java.awt.Color.red); +% jEditbox.KeyTypedCallback = @myCallbackFunc; % many more callbacks where this came from... +% jEdit.requestFocus; +% +% Known issues/limitations: +% - Cannot currently process multiple container objects - just one at a time +% - Initial processing is a bit slow when the figure is laden with many UI components (so better use 'persist') +% - Passing a simple container Matlab handle is currently filtered by its position+size: should find a better way to do this +% - Matlab uipanels are not implemented as simple java panels, and so they can't be found using this utility +% - Labels have a write-only text property in java, so they can't be found using the 'property',{'Text','string'} notation +% +% Warning: +% This code heavily relies on undocumented and unsupported Matlab functionality. +% It works on Matlab 7+, but use at your own risk! +% +% Bugs and suggestions: +% Please send to Yair Altman (altmany at gmail dot com) +% +% Change log: +% 2009-10-27: Fixed auto-collapse of invisible container nodes; added dynamic tree tooltips & context-menu; minor fix to version-check display +% 2009-09-30: Fix for Matlab 7.0 as suggested by Oliver W; minor GUI fix (classname font) +% 2009-08-07: Fixed edge-case of missing JIDE tables +% 2009-05-24: Added support for future Matlab versions that will not support JavaFrame +% 2009-05-15: Added sanity checks for axes items +% 2009-04-28: Added 'debug' input arg; increased size tolerance 1px => 2px +% 2009-04-23: Fixed location of popupmenus (always 20px high despite what's reported by Matlab...); fixed uiinspect processing issues; added blog link; narrower action buttons +% 2009-04-09: Automatic 'nomenu' for uicontrol inputs; significant performance improvement +% 2009-03-31: Fixed position of some Java components; fixed properties tooltip; fixed node visibility indication +% 2009-02-26: Indicated components visibility (& auto-collapse non-visible containers); auto-highlight selected component; fixes in node icons, figure title & tree refresh; improved error handling; display FindJObj version update description if available +% 2009-02-24: Fixed update check; added dedicated labels icon +% 2009-02-18: Fixed compatibility with old Matlab versions +% 2009-02-08: Callbacks table fixes; use uiinspect if available; fix update check according to new FEX website +% 2008-12-17: R2008b compatibility +% 2008-09-10: Fixed minor bug as per Johnny Smith +% 2007-11-14: Fixed edge case problem with class properties tooltip; used existing object icon if available; added checkbox option to hide standard callbacks +% 2007-08-15: Fixed object naming relative property priorities; added sanity check for illegal container arg; enabled desktop (0) container; cleaned up warnings about special class objects +% 2007-08-03: Fixed minor tagging problems with a few Java sub-classes; displayed UIClassID if text/name/tag is unavailable +% 2007-06-15: Fixed problems finding HG components found by J. Wagberg +% 2007-05-22: Added 'nomenu' option for improved performance; fixed 'export handles' bug; fixed handle-finding/display bugs; "cleaner" error handling +% 2007-04-23: HTMLized classname tooltip; returned top-level figure Frame handle for figure container; fixed callbacks table; auto-checked newer version; fixed Matlab 7.2 compatibility issue; added HG objects tree +% 2007-04-19: Fixed edge case of missing figure; displayed tree hierarchy in interactive GUI if no output args; workaround for figure sub-menus invisible unless clicked +% 2007-04-04: Improved performance; returned full listing results in 4th output arg; enabled partial property names & property values; automatically filtered out container panels if children also returned; fixed finding sub-menu items +% 2007-03-20: First version posted on the MathWorks file exchange: http://www.mathworks.com/matlabcentral/fileexchange/loadFile.do?objectId=14317 +% +% See also: +% java, handle, findobj, findall, javaGetHandles, uiinspect (on the File Exchange) + +% License to use and modify this code is granted freely to all interested, as long as the original author is +% referenced and attributed as such. The original author maintains the right to be solely associated with this work. + +% Programmed and Copyright by Yair M. Altman: altmany(at)gmail.com +% $Revision: 1.23 $ $Date: 2009/10/27 21:07:32 $ + + % Ensure Java AWT is enabled + error(javachk('awt')); + + persistent pContainer pHandles pLevels pParentIdx pPositions + + try + % Initialize + handles = handle([]); + levels = []; + parentIdx = []; + positions = []; % Java positions start at the top-left corner + %sizes = []; + listing = ''; + hg_levels = []; + hg_handles = handle([]); % HG handles are double + hg_parentIdx = []; + nomenu = false; + menuBarFoundFlag = false; + + % Default container is the current figure's root panel + if nargin + if isempty(container) % empty container - bail out + return; + elseif ischar(container) % container skipped - this is part of the args list... + varargin = {container, varargin{:}}; + origContainer = getCurrentFigure; + [container,contentSize] = getRootPanel(origContainer); + elseif isequal(container,0) % root + origContainer = handle(container); + container = com.mathworks.mde.desk.MLDesktop.getInstance.getMainFrame; + contentSize = [container.getWidth, container.getHeight]; + elseif ishghandle(container) % && ~isa(container,'java.awt.Container') + container = container(1); % another current limitation... + hFig = ancestor(container,'figure'); + origContainer = handle(container); + if isa(origContainer,'uimenu') + % getpixelposition doesn't work for menus... - damn! + varargin = {'class','MenuPeer', 'property',{'Label',strrep(get(container,'Label'),'&','')}, varargin{:}}; + elseif ~isa(origContainer, 'figure') && ~isempty(hFig) + % See limitations section above: should find a better way to directly refer to the element's java container + try + % Note: 'PixelBounds' is undocumented and unsupported, but much faster than getpixelposition! + % ^^^^ unfortunately, its Y position is inaccurate in some cases - damn! + %size = get(container,'PixelBounds'); + pos = fix(getpixelposition(container,1)); + %varargin = {'position',pos(1:2), 'size',pos(3:4), 'not','class','java.awt.Panel', varargin{:}}; + catch + try + figName = get(hFig,'name'); + if strcmpi(get(hFig,'number'),'on') + figName = regexprep(['Figure ' num2str(hFig) ': ' figName],': $',''); + end + mde = com.mathworks.mde.desk.MLDesktop.getInstance; + jFig = mde.getClient(figName); + if isempty(jFig), error('dummy'); end + catch + warning('off','MATLAB:HandleGraphics:ObsoletedProperty:JavaFrame'); % R2008b compatibility + jFig = get(get(hFig,'JavaFrame'),'FigurePanelContainer'); + end + pos = []; + try + pxsize = get(container,'PixelBounds'); + pos = [pxsize(1)+5, jFig.getHeight - (pxsize(4)-5)]; + catch + % never mind... + end + end + if size(pos,2) == 2 + pos(:,3:4) = 0; + end + if ~isempty(pos) + if isa(handle(container),'uicontrol') && strcmp(get(container,'style'),'popupmenu') + % popupmenus (combo-box dropdowns) are always 20px high + pos(2) = pos(2) + pos(4) - 20; + pos(4) = 20; + end + %varargin = {'position',pos(1:2), 'size',size(3:4)-size(1:2)-10, 'not','class','java.awt.Panel', varargin{:}}; + varargin = {'position',pos(1:2)+[0,pos(4)], 'size',pos(3:4), 'not','class','java.awt.Panel', 'nomenu', varargin{:}}; + end + elseif isempty(hFig) + hFig = handle(container); + end + [container,contentSize] = getRootPanel(hFig); + elseif isjava(container) + % Maybe a java container obj (will crash otherwise) + origContainer = container; + contentSize = [container.getWidth, container.getHeight]; + else + error('YMA:findjobj:IllegalContainer','Input arg does not appear to be a valid GUI object'); + end + else + % Default container = current figure + origContainer = getCurrentFigure; + [container,contentSize] = getRootPanel(origContainer); + end + + % Check persistency + if isequal(pContainer,container) + % persistency requested and the same container is reused, so reuse the hierarchy information + [handles,levels,parentIdx,positions] = deal(pHandles, pLevels, pParentIdx, pPositions); + else + % Pre-allocate space of complex data containers for improved performance + handles = repmat(handles,1,1000); + positions = zeros(1000,2); + + % Check whether to skip menu processing + nomenu = paramSupplied(varargin,'nomenu'); + + % Traverse the container hierarchy and extract the elements within + traverseContainer(container,0,1); + + % Remove unnecessary pre-allocated elements + dataLen = length(levels); + handles (dataLen+1:end) = []; + positions(dataLen+1:end,:) = []; + end + + % Process persistency check before any filtering is done + if paramSupplied(varargin,'persist') + [pContainer, pHandles, pLevels, pParentIdx, pPositions] = deal(container,handles,levels,parentIdx,positions); + end + + % Save data for possible future use in presentObjectTree() below + allHandles = handles; + allLevels = levels; + allParents = parentIdx; + selectedIdx = 1:length(handles); + %[positions(:,1)-container.getX, container.getHeight - positions(:,2)] + + % Debug-list all found compponents and their positions + if paramSupplied(varargin,'debug') + for handleIdx = 1 : length(allHandles) + pos = sprintf('%d,%d %dx%d',[positions(handleIdx,:) getXY(handles(handleIdx))]); + disp([repmat(' ',1,levels(handleIdx)) '[' pos(1:end-1) '] ' char(toString(handles(handleIdx)))]); + end + end + + % Process optional args + % Note: positions is NOT returned since it's based on java coord system (origin = top-left): will confuse Matlab users + processArgs(varargin{:}); %#ok + + % De-cell and trim listing, if only one element was found (no use for indented listing in this case) + if iscell(listing) && length(listing)==1 + listing = strtrim(listing{1}); + end + + % If no output args and no listing requested, present the FINDJOBJ interactive GUI + if nargout == 0 && isempty(listing) + presentObjectTree(); + + % Display the listing, if this was specifically requested yet no relevant output arg was specified + elseif nargout < 4 && ~isempty(listing) + if ~iscell(listing) + disp(listing); + else + for listingIdx = 1 : length(listing) + disp(listing{listingIdx}); + end + end + end + + return; %debug point + + catch + % 'Cleaner' error handling - strip the stack info etc. + err = lasterror; + err.message = regexprep(err.message,'Error using ==> [^\n]+\n',''); + if isempty(findstr(mfilename,err.message)) + % Indicate error origin, if not already stated within the error message + err.message = [mfilename ': ' err.message]; + end + rethrow(err); + end + + + %% Check existence of a (case-insensitive) optional parameter in the params list + function [flag,idx] = paramSupplied(paramsList,paramName) + %idx = find(~cellfun('isempty',regexpi(paramsList(cellfun(@ischar,paramsList)),['^-?' paramName]))); + idx = find(~cellfun('isempty',regexpi(paramsList(cellfun('isclass',paramsList,'char')),['^-?' paramName]))); % 30/9/2009 fix for ML 7.0 suggested by Oliver W + flag = any(idx); + end + + %% Get current figure (even if its 'HandleVisibility' property is 'off') + function curFig = getCurrentFigure + oldShowHidden = get(0,'ShowHiddenHandles'); + set(0,'ShowHiddenHandles','on'); % minor fix per Johnny Smith + curFig = gcf; + set(0,'ShowHiddenHandles',oldShowHidden); + end + + %% Get Java reference to top-level (root) panel - actually, a reference to the java figure + function [jRootPane,contentSize] = getRootPanel(hFig) + try + contentSize = [0,0]; % initialize + jRootPane = hFig; + figName = get(hFig,'name'); + if strcmpi(get(hFig,'number'),'on') + figName = regexprep(['Figure ' num2str(hFig) ': ' figName],': $',''); + end + mde = com.mathworks.mde.desk.MLDesktop.getInstance; + jFigPanel = mde.getClient(figName); + jRootPane = jFigPanel; + jRootPane = jFigPanel.getRootPane; + catch + try + jFrame = get(hFig,'JavaFrame'); + jFigPanel = get(jFrame,'FigurePanelContainer'); + jRootPane = jFigPanel; + jRootPane = jFigPanel.getComponent(0).getRootPane; + catch + % Never mind + end + end + try + % If invalid RootPane, retry up to N times + tries = 10; + while isempty(jRootPane) && tries>0 % might happen if figure is still undergoing rendering... + drawnow; pause(0.001); + tries = tries - 1; + jRootPane = jFigPanel.getComponent(0).getRootPane; + end + + % If still invalid, use FigurePanelContainer which is good enough in 99% of cases... (menu/tool bars won't be accessible, though) + if isempty(jRootPane) + jRootPane = jFigPanel; + end + contentSize = [jRootPane.getWidth, jRootPane.getHeight]; + + % Try to get the ancestor FigureFrame + jRootPane = jRootPane.getTopLevelAncestor; + catch + % Never mind - FigurePanelContainer is good enough in 99% of cases... (menu/tool bars won't be accessible, though) + end + end + + %% Traverse the container hierarchy and extract the elements within + function traverseContainer(jcontainer,level,parent) + persistent figureComponentFound menuRootFound + + % Record the data for this node + %disp([repmat(' ',1,level) '<= ' char(jcontainer.toString)]) + thisIdx = length(levels) + 1; + levels(thisIdx) = level; + parentIdx(thisIdx) = parent; + handles(thisIdx) = handle(jcontainer,'callbackproperties'); + try + positions(thisIdx,:) = getXY(jcontainer); + %sizes(thisIdx,:) = [jcontainer.getWidth, jcontainer.getHeight]; + catch + positions(thisIdx,:) = [0,0]; + %sizes(thisIdx,:) = [0,0]; + end + if level>0 + positions(thisIdx,:) = positions(thisIdx,:) + positions(parent,:); + if ~figureComponentFound && ... + strcmp(jcontainer.getName,'fComponentContainer') && ... + isa(jcontainer,'com.mathworks.hg.peer.FigureComponentContainer') % there are 2 FigureComponentContainers - only process one... + + % restart coordinate system, to exclude menu & toolbar areas + positions(thisIdx,:) = positions(thisIdx,:) - [jcontainer.getRootPane.getX, jcontainer.getRootPane.getY]; + figureComponentFound = true; + end + elseif level==1 + positions(thisIdx,:) = positions(thisIdx,:) + positions(parent,:); + else + % level 0 - initialize flags used later + figureComponentFound = false; + menuRootFound = false; + end + parentId = length(parentIdx); + + % Traverse Menu items, unless the 'nomenu' option was requested + if ~nomenu + try + for child = 1 : getNumMenuComponents(jcontainer) + traverseContainer(jcontainer.getMenuComponent(child-1),level+1,parentId); + end + catch + % Probably not a Menu container, but maybe a top-level JMenu, so discard duplicates + %if isa(handles(end).java,'javax.swing.JMenuBar') + if ~menuRootFound && strcmp(handles(end).java.class,'javax.swing.JMenuBar') %faster... + if removeDuplicateNode(thisIdx) + menuRootFound = true; + return; + end + end + end + end + + % Now recursively process all this node's children (if any), except menu items if so requested + %if isa(jcontainer,'java.awt.Container') + try % try-catch is faster than checking isa(jcontainer,'java.awt.Container')... + %if jcontainer.getComponentCount, jcontainer.getComponents, end + if ~nomenu || menuBarFoundFlag || isempty(strfind(jcontainer.class,'FigureMenuBar')) + lastChildComponent = java.lang.Object; + child = 0; + while (child < jcontainer.getComponentCount) + childComponent = jcontainer.getComponent(child); + % Looping over menus sometimes causes jcontainer to get mixed up (probably a JITC bug), so identify & fix + if isequal(childComponent,lastChildComponent) + child = child + 1; + childComponent = jcontainer.getComponent(child); + end + lastChildComponent = childComponent; + %disp([repmat(' ',1,level) '=> ' num2str(child) ': ' char(childComponent.class)]) + traverseContainer(childComponent,level+1,parentId); + child = child + 1; + end + else + menuBarFoundFlag = true; % use this flag to skip further testing for FigureMenuBar + end + catch + % do nothing - probably not a container + %dispError + end + + % ...and yet another type of child traversal... + try + for child = 1 : jcontainer.java.getChildCount + traverseContainer(jcontainer.java.getChildAt(child-1),level+1,parentId); + end + catch + % do nothing - probably not a container + %dispError + end + + % TODO: Add axis (plot) component handles + end + + %% Get the XY location of a Java component + function xy = getXY(jcontainer) + % Note: getX/getY are better than get(..,'X') (mem leaks), + % ^^^^ but sometimes they fail and revert to getx.m ... + % Note2: try awtinvoke() catch is faster than checking ismethod()... + % Note3: using AWTUtilities.invokeAndWait() directly is even faster than awtinvoke()... + try %if ismethod(jcontainer,'getX') + %positions(thisIdx,:) = [jcontainer.getX, jcontainer.getY]; + cls = getClass(jcontainer); + location = com.mathworks.jmi.AWTUtilities.invokeAndWait(jcontainer,getMethod(cls,'getLocation',[]),[]); + x = location.getX; + y = location.getY; + catch %else + try + x = com.mathworks.jmi.AWTUtilities.invokeAndWait(jcontainer,getMethod(cls,'getX',[]),[]); + y = com.mathworks.jmi.AWTUtilities.invokeAndWait(jcontainer,getMethod(cls,'getY',[]),[]); + catch + try + x = awtinvoke(jcontainer,'getX()'); + y = awtinvoke(jcontainer,'getY()'); + catch + x = get(jcontainer,'X'); + y = get(jcontainer,'Y'); + end + end + end + %positions(thisIdx,:) = [x, y]; + xy = [x,y]; + end + + %% Get the number of menu sub-elements + function numMenuComponents = getNumMenuComponents(jcontainer) + + % The following line will raise an Exception for anything except menus + numMenuComponents = jcontainer.getMenuComponentCount; + + % No error so far, so this must be a menu container... + % Note: Menu subitems are not visible until the top-level (root) menu gets initial focus... + % Try several alternatives, until we get a non-empty menu (or not...) + % TODO: Improve performance - this takes WAY too long... + if jcontainer.isTopLevelMenu && (numMenuComponents==0) + jcontainer.requestFocus; + numMenuComponents = jcontainer.getMenuComponentCount; + if (numMenuComponents == 0) + drawnow; pause(0.001); + numMenuComponents = jcontainer.getMenuComponentCount; + if (numMenuComponents == 0) + jcontainer.setSelected(true); + numMenuComponents = jcontainer.getMenuComponentCount; + if (numMenuComponents == 0) + drawnow; pause(0.001); + numMenuComponents = jcontainer.getMenuComponentCount; + if (numMenuComponents == 0) + jcontainer.doClick; % needed in order to populate the sub-menu components + numMenuComponents = jcontainer.getMenuComponentCount; + if (numMenuComponents == 0) + drawnow; %pause(0.001); + numMenuComponents = jcontainer.getMenuComponentCount; + jcontainer.doClick; % close menu by re-clicking... + if (numMenuComponents == 0) + drawnow; %pause(0.001); + numMenuComponents = jcontainer.getMenuComponentCount; + end + else + % ok - found sub-items + % Note: no need to close menu since this will be done when focus moves to the FindJObj window + %jcontainer.doClick; % close menu by re-clicking... + end + end + end + jcontainer.setSelected(false); % de-select the menu + end + end + end + end + + %% Remove a specific tree node's data + function nodeRemovedFlag = removeDuplicateNode(thisIdx) + nodeRemovedFlag = false; + for idx = 1 : thisIdx-1 + if isequal(handles(idx),handles(thisIdx)) + levels(thisIdx) = []; + parentIdx(thisIdx) = []; + handles(thisIdx) = []; + positions(thisIdx,:) = []; + %sizes(thisIdx,:) = []; + nodeRemovedFlag = true; + return; + end + end + end + + %% Process optional args + function processArgs(varargin) + + % Initialize + invertFlag = false; + listing = ''; + + % Loop over all optional args + while ~isempty(varargin) && ~isempty(handles) + + % Process the arg (and all its params) + foundIdx = 1 : length(handles); + if iscell(varargin{1}), varargin{1} = varargin{1}{1}; end + if ~isempty(varargin{1}) && varargin{1}(1)=='-' + varargin{1}(1) = []; + end + switch lower(varargin{1}) + case 'not' + invertFlag = true; + case 'position' + [varargin,foundIdx] = processPositionArgs(varargin{:}); + if invertFlag, foundIdx = ~foundIdx; invertFlag = false; end + case 'size' + [varargin,foundIdx] = processSizeArgs(varargin{:}); + if invertFlag, foundIdx = ~foundIdx; invertFlag = false; end + case 'class' + [varargin,foundIdx] = processClassArgs(varargin{:}); + if invertFlag, foundIdx = ~foundIdx; invertFlag = false; end + case 'property' + [varargin,foundIdx] = processPropertyArgs(varargin{:}); + if invertFlag, foundIdx = ~foundIdx; invertFlag = false; end + case 'depth' + [varargin,foundIdx] = processDepthArgs(varargin{:}); + if invertFlag, foundIdx = ~foundIdx; invertFlag = false; end + case 'flat' + varargin = {'depth',0, varargin{min(2:end):end}}; + [varargin,foundIdx] = processDepthArgs(varargin{:}); + if invertFlag, foundIdx = ~foundIdx; invertFlag = false; end + case {'print','list'} + [varargin,listing] = processPrintArgs(varargin{:}); + case {'persist','nomenu','debug'} + % ignore - already handled in main function above + otherwise + error('YMA:findjobj:IllegalOption',['Option ' num2str(varargin{1}) ' is not a valid option. Type ''help ' mfilename ''' for the full options list.']); + end + + % If only parent-child pairs found + foundIdx = find(foundIdx); + if ~isempty(foundIdx) && isequal(parentIdx(foundIdx(2:2:end)),foundIdx(1:2:end)) + % Return just the children (the parent panels are uninteresting) + foundIdx(1:2:end) = []; + end + + % Filter the results + selectedIdx = selectedIdx(foundIdx); + handles = handles(foundIdx); + levels = levels(foundIdx); + parentIdx = parentIdx(foundIdx); + positions = positions(foundIdx,:); + + % Remove this arg and proceed to the next one + varargin(1) = []; + + end % Loop over all args + end + + %% Process 'print' option + function [varargin,listing] = processPrintArgs(varargin) + if length(varargin)<2 || ischar(varargin{2}) + % No second arg given, so use the first available element + listingContainer = handles(1); %#ok - used in evalc below + else + % Get the element to print from the specified second arg + if isnumeric(varargin{2}) && (varargin{2} == fix(varargin{2})) % isinteger doesn't work on doubles... + if (varargin{2} > 0) && (varargin{2} <= length(handles)) + listingContainer = handles(varargin{2}); %#ok - used in evalc below + elseif varargin{2} > 0 + error('YMA:findjobj:IllegalPrintFilter','Print filter index %g > number of available elements (%d)',varargin{2},length(handles)); + else + error('YMA:findjobj:IllegalPrintFilter','Print filter must be a java handle or positive numeric index into handles'); + end + elseif ismethod(varargin{2},'list') + listingContainer = varargin{2}; %#ok - used in evalc below + else + error('YMA:findjobj:IllegalPrintFilter','Print filter must be a java handle or numeric index into handles'); + end + varargin(2) = []; + end + + % use evalc() to capture output into a Matlab variable + %listing = evalc('listingContainer.list'); + + % Better solution: loop over all handles and process them one by one + listing = cell(length(handles),1); + for componentIdx = 1 : length(handles) + listing{componentIdx} = [repmat(' ',1,levels(componentIdx)) char(handles(componentIdx).toString)]; + end + end + + %% Process 'position' option + function [varargin,foundIdx] = processPositionArgs(varargin) + if length(varargin)>1 + positionFilter = varargin{2}; + %if (isjava(positionFilter) || iscom(positionFilter)) && ismethod(positionFilter,'getLocation') + try % try-catch is faster... + % Java/COM object passed - get its position + positionFilter = positionFilter.getLocation; + filterXY = [positionFilter.getX, positionFilter.getY]; + catch + if ~isscalar(positionFilter) + % position vector passed + if (length(positionFilter)>=2) && isnumeric(positionFilter) + % Remember that java coordinates start at top-left corner, Matlab coords start at bottom left... + %positionFilter = java.awt.Point(positionFilter(1), container.getHeight - positionFilter(2)); + filterXY = [container.getX + positionFilter(1), container.getY + contentSize(2) - positionFilter(2)]; + + % Check for full Matlab position vector (x,y,w,h) + %if (length(positionFilter)==4) + % varargin{end+1} = 'size'; + % varargin{end+1} = fix(positionFilter(3:4)); + %end + else + error('YMA:findjobj:IllegalPositionFilter','Position filter must be a java UI component, or X,Y pair'); + end + elseif length(varargin)>2 + % x,y passed as separate arg values + if isnumeric(positionFilter) && isnumeric(varargin{3}) + % Remember that java coordinates start at top-left corner, Matlab coords start at bottom left... + %positionFilter = java.awt.Point(positionFilter, container.getHeight - varargin{3}); + filterXY = [container.getX + positionFilter, container.getY + contentSize(2) - varargin{3}]; + varargin(3) = []; + else + error('YMA:findjobj:IllegalPositionFilter','Position filter must be a java UI component, or X,Y pair'); + end + else + error('YMA:findjobj:IllegalPositionFilter','Position filter must be a java UI component, or X,Y pair'); + end + end + + % Compute the required element positions in order to be eligible for a more detailed examination + % Note: based on the following constraints: 0 <= abs(elementX-filterX) + abs(elementY+elementH-filterY) < 7 + baseDeltas = [positions(:,1)-filterXY(1), positions(:,2)-filterXY(2)]; % faster than repmat()... + %baseHeight = - baseDeltas(:,2);% -abs(baseDeltas(:,1)); + %minHeight = baseHeight - 7; + %maxHeight = baseHeight + 7; + %foundIdx = ~arrayfun(@(b)(invoke(b,'contains',positionFilter)),handles); % ARGH! - disallowed by Matlab! + %foundIdx = repmat(false,1,length(handles)); + %foundIdx(length(handles)) = false; % faster than repmat()... + foundIdx = (abs(baseDeltas(:,1)) < 7) & (abs(baseDeltas(:,2)) < 7); % & (minHeight >= 0); + %fi = find(foundIdx); + %for componentIdx = 1 : length(fi) + %foundIdx(componentIdx) = handles(componentIdx).getBounds.contains(positionFilter); + + % Search for a point no farther than 7 pixels away (prevents rounding errors) + %foundIdx(componentIdx) = handles(componentIdx).getLocationOnScreen.distanceSq(positionFilter) < 50; % fails for invisible components... + + %p = java.awt.Point(positions(componentIdx,1), positions(componentIdx,2) + handles(componentIdx).getHeight); + %foundIdx(componentIdx) = p.distanceSq(positionFilter) < 50; + + %foundIdx(componentIdx) = sum(([baseDeltas(componentIdx,1),baseDeltas(componentIdx,2)+handles(componentIdx).getHeight]).^2) < 50; + + % Following is the fastest method found to date: only eligible elements are checked in detailed + % elementHeight = handles(fi(componentIdx)).getHeight; + % foundIdx(fi(componentIdx)) = elementHeight > minHeight(fi(componentIdx)) && ... + % elementHeight < maxHeight(fi(componentIdx)); + %disp([componentIdx,elementHeight,minHeight(fi(componentIdx)),maxHeight(fi(componentIdx)),foundIdx(fi(componentIdx))]) + %end + + varargin(2) = []; + else + foundIdx = []; + end + end + + %% Process 'size' option + function [varargin,foundIdx] = processSizeArgs(varargin) + if length(varargin)>1 + sizeFilter = lower(varargin{2}); + %if (isjava(sizeFilter) || iscom(sizeFilter)) && ismethod(sizeFilter,'getSize') + try % try-catch is faster... + % Java/COM object passed - get its size + sizeFilter = sizeFilter.getSize; + filterWidth = sizeFilter.getWidth; + filterHeight = sizeFilter.getHeight; + catch + if ~isscalar(sizeFilter) + % size vector passed + if (length(sizeFilter)>=2) && isnumeric(sizeFilter) + %sizeFilter = java.awt.Dimension(sizeFilter(1),sizeFilter(2)); + filterWidth = sizeFilter(1); + filterHeight = sizeFilter(2); + else + error('YMA:findjobj:IllegalSizeFilter','Size filter must be a java UI component, or W,H pair'); + end + elseif length(varargin)>2 + % w,h passed as separate arg values + if isnumeric(sizeFilter) && isnumeric(varargin{3}) + %sizeFilter = java.awt.Dimension(sizeFilter,varargin{3}); + filterWidth = sizeFilter; + filterHeight = varargin{3}; + varargin(3) = []; + else + error('YMA:findjobj:IllegalSizeFilter','Size filter must be a java UI component, or W,H pair'); + end + else + error('YMA:findjobj:IllegalSizeFilter','Size filter must be a java UI component, or W,H pair'); + end + end + %foundIdx = ~arrayfun(@(b)(invoke(b,'contains',sizeFilter)),handles); % ARGH! - disallowed by Matlab! + foundIdx(length(handles)) = false; % faster than repmat()... + for componentIdx = 1 : length(handles) + %foundIdx(componentIdx) = handles(componentIdx).getSize.equals(sizeFilter); + % Allow a 2-pixel tollerance to account for non-integer pixel sizes + foundIdx(componentIdx) = abs(handles(componentIdx).getWidth - filterWidth) <= 2 && ... % faster than getSize.equals() + abs(handles(componentIdx).getHeight - filterHeight) <= 2; + end + varargin(2) = []; + else + foundIdx = []; + end + end + + %% Process 'class' option + function [varargin,foundIdx] = processClassArgs(varargin) + if length(varargin)>1 + classFilter = varargin{2}; + %if ismethod(classFilter,'getClass') + try % try-catch is faster... + classFilter = char(classFilter.getClass); + catch + if ~ischar(classFilter) + error('YMA:findjobj:IllegalClassFilter','Class filter must be a java object, class or string'); + end + end + + % Now convert all java classes to java.lang.Strings and compare to the requested filter string + try + foundIdx(length(handles)) = false; % faster than repmat()... + jClassFilter = java.lang.String(classFilter).toLowerCase; + for componentIdx = 1 : length(handles) + % Note: JVM 1.5's String.contains() appears slightly slower and is available only since Matlab 7.2 + foundIdx(componentIdx) = handles(componentIdx).getClass.toString.toLowerCase.indexOf(jClassFilter) >= 0; + end + catch + % Simple processing: slower since it does extra processing within opaque.char() + for componentIdx = 1 : length(handles) + % Note: using @toChar is faster but returns java String, not a Matlab char + foundIdx(componentIdx) = ~isempty(regexpi(char(handles(componentIdx).getClass),classFilter)); + end + end + + varargin(2) = []; + else + foundIdx = []; + end + end + + %% Process 'property' option + function [varargin,foundIdx] = processPropertyArgs(varargin) + if length(varargin)>1 + propertyName = varargin{2}; + if iscell(propertyName) + if length(propertyName) == 2 + propertyVal = propertyName{2}; + propertyName = propertyName{1}; + elseif length(propertyName) == 1 + propertyName = propertyName{1}; + else + error('YMA:findjobj:IllegalPropertyFilter','Property filter must be a string (case insensitive name of property) or cell array {propName,propValue}'); + end + end + if ~ischar(propertyName) + error('YMA:findjobj:IllegalPropertyFilter','Property filter must be a string (case insensitive name of property) or cell array {propName,propValue}'); + end + propertyName = lower(propertyName); + %foundIdx = arrayfun(@(h)isprop(h,propertyName),handles); % ARGH! - disallowed by Matlab! + foundIdx(length(handles)) = false; % faster than repmat()... + + % Split processing depending on whether a specific property value was requested (ugly but faster...) + if exist('propertyVal','var') + for componentIdx = 1 : length(handles) + try + % Find out whether this element has the specified property + % Note: findprop() and its return value schema.prop are undocumented and unsupported! + prop = findprop(handles(componentIdx),propertyName); % faster than isprop() & enables partial property names + + % If found, compare it to the actual element's property value + foundIdx(componentIdx) = ~isempty(prop) && isequal(get(handles(componentIdx),prop.Name),propertyVal); + catch + % Some Java classes have a write-only property (like LabelPeer with 'Text'), so we end up here + % In these cases, simply assume that the property value doesn't match and continue + foundIdx(componentIdx) = false; + end + end + else + for componentIdx = 1 : length(handles) + try + % Find out whether this element has the specified property + % Note: findprop() and its return value schema.prop are undocumented and unsupported! + foundIdx(componentIdx) = ~isempty(findprop(handles(componentIdx),propertyName)); + catch + foundIdx(componentIdx) = false; + end + end + end + varargin(2) = []; + else + foundIdx = []; + end + end + + %% Process 'depth' option + function [varargin,foundIdx] = processDepthArgs(varargin) + if length(varargin)>1 + level = varargin{2}; + if ~isnumeric(level) + error('YMA:findjobj:IllegalDepthFilter','Depth filter must be a number (=maximal element depth)'); + end + foundIdx = (levels <= level); + varargin(2) = []; + else + foundIdx = []; + end + end + + %% Convert property data into a string + function data = charizeData(data) + if isa(data,'com.mathworks.hg.types.HGCallback') + data = get(data,'Callback'); + end + if ~ischar(data) + newData = strtrim(evalc('disp(data)')); + try + newData = regexprep(newData,' +',' '); + newData = regexprep(newData,'Columns \d+ through \d+\s',''); + newData = regexprep(newData,'Column \d+\s',''); + catch + %never mind... + end + if iscell(data) + newData = ['{ ' newData ' }']; + elseif isempty(data) + newData = ''; + elseif isnumeric(data) || islogical(data) || any(ishandle(data)) || numel(data) > 1 %&& ~isscalar(data) + newData = ['[' newData ']']; + end + data = newData; + elseif ~isempty(data) + data = ['''' data '''']; + end + end % charizeData + + %% Get callbacks table data + function [cbData, cbHeaders, cbTableEnabled] = getCbsData(obj, stripStdCbsFlag) + classHdl = classhandle(handle(obj)); + cbNames = get(classHdl.Events,'Name'); + if ~isempty(cbNames) && ~iscom(obj) %only java-based please... + cbNames = strcat(cbNames,'Callback'); + end + propNames = get(classHdl.Properties,'Name'); + propCbIdx = []; + if ~isempty(propNames) + propCbIdx = find(~cellfun(@isempty,regexp(propNames,'(Fcn|Callback)$'))); + cbNames = unique([cbNames; propNames(propCbIdx)]); %#ok logical is faster but less debuggable... + end + if ~isempty(cbNames) + if stripStdCbsFlag + cbNames = stripStdCbs(cbNames); + end + if iscell(cbNames) + cbNames = sort(cbNames); + end + hgHandleFlag = 0; try hgHandleFlag = ishghandle(obj); catch, end %#ok + try + obj = handle(obj,'CallbackProperties'); + catch + hgHandleFlag = 1; + end + if hgHandleFlag + % HG handles don't allow CallbackProperties - search only for *Fcn + cbNames = propNames(propCbIdx); + end + if iscom(obj) + cbs = obj.eventlisteners; + if ~isempty(cbs) + cbNamesRegistered = cbs(:,1); + cbData = setdiff(cbNames,cbNamesRegistered); + %cbData = charizeData(cbData); + if size(cbData,2) > size(cbData(1)) + cbData = cbData'; + end + cbData = [cbData, cellstr(repmat(' ',length(cbData),1))]; + cbData = [cbData; cbs]; + [sortedNames, sortedIdx] = sort(cbData(:,1)); + sortedCbs = cellfun(@charizeData,cbData(sortedIdx,2),'un',0); + cbData = [sortedNames, sortedCbs]; + else + cbData = [cbNames, cellstr(repmat(' ',length(cbNames),1))]; + end + elseif iscell(cbNames) + cbNames = sort(cbNames); + %cbData = [cbNames, get(obj,cbNames)']; + cbData = cbNames; + for idx = 1 : length(cbNames) + try + cbData{idx,2} = charizeData(get(obj,cbNames{idx})); + catch + cbData{idx,2} = '(callback value inaccessible)'; + end + end + else % only one event callback + %cbData = {cbNames, get(obj,cbNames)'}; + %cbData{1,2} = charizeData(cbData{1,2}); + try + cbData = {cbNames, charizeData(get(obj,cbNames))}; + catch + cbData = {cbNames, '(callback value inaccessible)'}; + end + end + cbHeaders = {'Callback name','Callback value'}; + cbTableEnabled = true; + else + cbData = {'(no callbacks)'}; + cbHeaders = {'Callback name'}; + cbTableEnabled = false; + end + end % getCbsData + + %% Get relative (0.0-1.0) divider location + function divLocation = getRalativeDivlocation(jDiv) + divLocation = jDiv.getDividerLocation; + if divLocation > 1 % i.e. [pixels] + visibleRect = jDiv.getVisibleRect; + if jDiv.getOrientation == 0 % vertical + start = visibleRect.getY; + extent = visibleRect.getHeight - start; + else + start = visibleRect.getX; + extent = visibleRect.getWidth - start; + end + divLocation = (divLocation - start) / extent; + end + end % getRalativeDivlocation + + %% Try to set a treenode icon based on a container's icon + function setTreeNodeIcon(treenode,container) + try + iconImage = []; + iconImage = container.getIcon; + if ~isempty(findprop(handle(iconImage),'Image')) % get(iconImage,'Image') is easier but leaks memory... + iconImage = iconImage.getImage; + else + a=b; %#ok cause an error + end + catch + try + iconImage = container.getIconImage; + catch + try + if ~isempty(iconImage) + ge = java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment; + gd = ge.getDefaultScreenDevice; + gc = gd.getDefaultConfiguration; + image = gc.createCompatibleImage(iconImage.getIconWidth, iconImage.getIconHeight); % a BufferedImage object + g = image.createGraphics; + iconImage.paintIcon([], g, 0, 0); + g.dispose; + iconImage = image; + end + catch + % never mind... + end + end + end + if ~isempty(iconImage) + iconImage = setIconSize(iconImage); + treenode.setIcon(iconImage); + end + end % setTreeNodeIcon + + %% Present the object hierarchy tree + function presentObjectTree() + import java.awt.* + import javax.swing.* + hTreeFig = findall(0,'tag','findjobjFig'); + iconpath = [matlabroot, '/toolbox/matlab/icons/']; + cbHideStd = 0; % Initial state of the cbHideStdCbs checkbox + if isempty(hTreeFig) + % Prepare the figure + hTreeFig = figure('tag','findjobjFig','menuBar','none','toolBar','none','Name','FindJObj','NumberTitle','off','handleVisibility','off','IntegerHandle','off'); + figIcon = ImageIcon([iconpath 'tool_legend.gif']); + drawnow; + try + mde = com.mathworks.mde.desk.MLDesktop.getInstance; + jTreeFig = mde.getClient('FindJObj').getTopLevelAncestor; + jTreeFig.setIcon(figIcon); + catch + jTreeFig = get(hTreeFig,'JavaFrame'); + jTreeFig.setFigureIcon(figIcon); + end + vsplitPaneLocation = 0.8; + hsplitPaneLocation = 0.5; + else + % Remember cbHideStdCbs checkbox & dividers state for later + userdata = get(hTreeFig, 'userdata'); + try cbHideStd = userdata.cbHideStdCbs.isSelected; catch, end %#ok + vsplitPaneLocation = getRalativeDivlocation(userdata.vsplitPane); + hsplitPaneLocation = getRalativeDivlocation(userdata.hsplitPane); + + % Clear the figure and redraw + clf(hTreeFig); + figure(hTreeFig); % bring to front + end + + % Traverse all HG children, if root container was a HG handle + if ishghandle(origContainer) %&& ~isequal(origContainer,container) + traverseHGContainer(origContainer,0,0); + end + + % Prepare the tree pane + warning('off','MATLAB:uitreenode:MigratingFunction'); % R2008b compatibility + tree_h = com.mathworks.hg.peer.UITreePeer; + hasChildren = sum(allParents==1) > 1; + icon = [iconpath 'upfolder.gif']; + [rootName, rootTitle] = getNodeName(container); + try + root = uitreenode('v0', handle(container), rootName, icon, ~hasChildren); + catch % old matlab version don't have the 'v0' option + root = uitreenode(handle(container), rootName, icon, ~hasChildren); + end + setTreeNodeIcon(root,container); % constructor must accept a char icon unfortunately, so need to do this afterwards... + if ~isempty(rootTitle) + set(hTreeFig, 'Name',['FindJObj - ' char(rootTitle)]); + end + nodedata.idx = 1; + nodedata.obj = container; + set(root,'userdata',nodedata); + root.setUserObject(container); + setappdata(root,'childHandle',container); + tree_h.setRoot(root); + treePane = tree_h.getScrollPane; + treePane.setMinimumSize(Dimension(50,50)); + jTreeObj = treePane.getViewport.getComponent(0); + jTreeObj.setShowsRootHandles(true) + jTreeObj.getSelectionModel.setSelectionMode(javax.swing.tree.TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION); + %jTreeObj.setVisible(0); + %jTreeObj.getCellRenderer.setLeafIcon([]); + %jTreeObj.getCellRenderer.setOpenIcon(figIcon); + %jTreeObj.getCellRenderer.setClosedIcon([]); + treePanel = JPanel(BorderLayout); + treePanel.add(treePane, BorderLayout.CENTER); + progressBar = JProgressBar(0); + progressBar.setMaximum(length(allHandles) + length(hg_handles)); % = # of all nodes + treePanel.add(progressBar, BorderLayout.SOUTH); + + % Prepare the image pane +%disable for now, until we get it working... +%{ + try + hFig = ancestor(origContainer,'figure'); + [cdata, cm] = getframe(hFig); %#ok cm unused + tempfname = [tempname '.png']; + imwrite(cdata,tempfname); % don't know how to pass directly to BufferedImage, so use disk... + jImg = javax.imageio.ImageIO.read(java.io.File(tempfname)); + try delete(tempfname); catch end + imgPanel = JPanel(); + leftPanel = JSplitPane(JSplitPane.VERTICAL_SPLIT, treePanel, imgPanel); + leftPanel.setOneTouchExpandable(true); + leftPanel.setContinuousLayout(true); + leftPanel.setResizeWeight(0.8); + catch + leftPanel = treePanel; + end +%} + leftPanel = treePanel; + + % Prepare the inspector pane + classNameLabel = JLabel([' ' char(container.class)]); + classNameLabel.setForeground(Color.blue); + updateNodeTooltip(container, classNameLabel); + inspectorPanel = JPanel(BorderLayout); + inspectorPanel.add(classNameLabel, BorderLayout.NORTH); + % TODO: Maybe uncomment the following when we add the HG tree - in the meantime it's unused (java properties are un-groupable) + %objReg = com.mathworks.services.ObjectRegistry.getLayoutRegistry; + %toolBar = awtinvoke('com.mathworks.mlwidgets.inspector.PropertyView$ToolBarStyle','valueOf(Ljava.lang.String;)','GROUPTOOLBAR'); + %inspectorPane = com.mathworks.mlwidgets.inspector.PropertyView(objReg, toolBar); + inspectorPane = com.mathworks.mlwidgets.inspector.PropertyView; + identifiers = disableDbstopError; %#ok "dbstop if error" causes inspect.m to croak due to a bug - so workaround + inspectorPane.setObject(container); + inspectorPane.setAutoUpdate(true); + % TODO: Add property listeners + % TODO: Display additional props + inspectorTable = inspectorPane; + while ~isa(inspectorTable,'javax.swing.JTable') + inspectorTable = inspectorTable.getComponent(0); + end + toolTipText = 'hover mouse over the red classname above to see the full list of properties'; + inspectorTable.setToolTipText(toolTipText); + jideTableUtils = []; + try + % Try JIDE features - see http://www.jidesoft.com/products/JIDE_Grids_Developer_Guide.pdf + com.mathworks.mwswing.MJUtilities.initJIDE; + jideTableUtils = eval('com.jidesoft.grid.TableUtils;'); % prevent JIDE alert by run-time (not load-time) evaluation + jideTableUtils.autoResizeAllColumns(inspectorTable); + inspectorTable.setRowAutoResizes(true); + inspectorTable.getModel.setShowExpert(1); + catch + % JIDE is probably unavailable - never mind... + end + inspectorPanel.add(inspectorPane, BorderLayout.CENTER); + % TODO: Add data update listeners + + % Prepare the callbacks pane + callbacksPanel = JPanel(BorderLayout); + classHdl = classhandle(handle(container)); + eventNames = get(classHdl.Events,'Name'); + if ~isempty(eventNames) + cbNames = sort(strcat(eventNames,'Callback')); + cbData = [cbNames, get(container,cbNames)']; + cbTableEnabled = true; + else + cbData = {'(no callbacks)',''}; + cbTableEnabled = false; + end + cbHeaders = {'Callback name','Callback value'}; + try + % Use JideTable if available on this system + %callbacksTableModel = javax.swing.table.DefaultTableModel(cbData,cbHeaders); %#ok + %callbacksTable = eval('com.jidesoft.grid.PropertyTable(callbacksTableModel);'); % prevent JIDE alert by run-time (not load-time) evaluation + callbacksTable = eval('com.jidesoft.grid.TreeTable(cbData,cbHeaders);'); % prevent JIDE alert by run-time (not load-time) evaluation + callbacksTable.setRowAutoResizes(true); + callbacksTable.setColumnAutoResizable(true); + callbacksTable.setColumnResizable(true); + jideTableUtils.autoResizeAllColumns(callbacksTable); + callbacksTable.setTableHeader([]); % hide the column headers since now we can resize columns with the gridline + callbacksLabel = JLabel(' Callbacks:'); % The column headers are replaced with a header label + %callbacksPanel.add(callbacksLabel, BorderLayout.NORTH); + + % Add checkbox to show/hide standard callbacks + callbacksTopPanel = JPanel; + callbacksTopPanel.setLayout(BoxLayout(callbacksTopPanel, BoxLayout.LINE_AXIS)); + callbacksTopPanel.add(callbacksLabel); + callbacksTopPanel.add(Box.createHorizontalGlue); + jcb = JCheckBox('Hide standard callbacks', cbHideStd); + set(jcb, 'ActionPerformedCallback',@cbHideStdCbs_Callback, 'userdata',callbacksTable, 'tooltip','Hide standard Swing callbacks - only component-specific callbacks will be displayed'); + callbacksTopPanel.add(jcb); + callbacksPanel.add(callbacksTopPanel, BorderLayout.NORTH); + catch + % Otherwise, use a standard Swing JTable (keep the headers to enable resizing) + callbacksTable = JTable(cbData,cbHeaders); + end + cbToolTipText = 'Callbacks may be ''strings'', @funcHandle or {@funcHandle,arg1,...}'; + callbacksTable.setToolTipText(cbToolTipText); + callbacksTable.setGridColor(inspectorTable.getGridColor); + cbNameTextField = JTextField; + cbNameTextField.setEditable(false); % ensure that the callback names are not modified... + cbNameCellEditor = DefaultCellEditor(cbNameTextField); + cbNameCellEditor.setClickCountToStart(intmax); % i.e, never enter edit mode... + callbacksTable.getColumnModel.getColumn(0).setCellEditor(cbNameCellEditor); + if ~cbTableEnabled + callbacksTable.getColumnModel.getColumn(1).setCellEditor(cbNameCellEditor); + end + set(callbacksTable.getModel, 'TableChangedCallback',@tbCallbacksChanged, 'UserData',container); + cbScrollPane = JScrollPane(callbacksTable); + cbScrollPane.setVerticalScrollBarPolicy(cbScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED); + callbacksPanel.add(cbScrollPane, BorderLayout.CENTER); + callbacksPanel.setToolTipText(cbToolTipText); + + % Prepare the top-bottom JSplitPanes + vsplitPane = JSplitPane(JSplitPane.VERTICAL_SPLIT, inspectorPanel, callbacksPanel); + vsplitPane.setOneTouchExpandable(true); + vsplitPane.setContinuousLayout(true); + vsplitPane.setResizeWeight(0.8); + + % Prepare the left-right JSplitPane + hsplitPane = JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftPanel, vsplitPane); + hsplitPane.setOneTouchExpandable(true); + hsplitPane.setContinuousLayout(true); + hsplitPane.setResizeWeight(0.6); + pos = getpixelposition(hTreeFig); + + % Prepare the bottom pane with all buttons + lowerPanel = JPanel(FlowLayout); + blogUrlLabel = 'Undocumented
Matlab.com
'; + jWebsite = createJButton(blogUrlLabel, @btWebsite_Callback, 'Visit the UndocumentedMatlab.com blog'); + jWebsite.setContentAreaFilled(0); + lowerPanel.add(jWebsite); + lowerPanel.add(createJButton('Refresh
tree', {@btRefresh_Callback, origContainer, hTreeFig}, 'Rescan the component tree, from the root down')); + lowerPanel.add(createJButton('Export to
workspace', {@btExport_Callback, jTreeObj, classNameLabel}, 'Export the selected component handles to workspace variable findjobj_hdls')); + lowerPanel.add(createJButton('Request
focus', {@btFocus_Callback, jTreeObj, root}, 'Set the focus on the first selected component')); + lowerPanel.add(createJButton('Inspect
object', {@btInspect_Callback, jTreeObj, root}, 'View the signature of all methods supported by the first selected component')); + lowerPanel.add(createJButton('Check for
updates', {@btCheckFex_Callback}, 'Check the MathWorks FileExchange for the latest version of FindJObj')); + + % Display everything on-screen + globalPanel = JPanel(BorderLayout); + globalPanel.add(hsplitPane, BorderLayout.CENTER); + globalPanel.add(lowerPanel, BorderLayout.SOUTH); + [obj, hcontainer] = javacomponent(globalPanel, [0,0,pos(3:4)], hTreeFig); + set(hcontainer,'units','normalized'); + drawnow; + hsplitPane.setDividerLocation(hsplitPaneLocation); % this only works after the JSplitPane is displayed... + vsplitPane.setDividerLocation(vsplitPaneLocation); % this only works after the JSplitPane is displayed... + %restoreDbstopError(identifiers); + + % Refresh & resize the screenshot thumbnail +%disable for now, until we get it working... +%{ + try + hAx = axes('Parent',hTreeFig, 'units','pixels', 'position',[10,10,250,150], 'visible','off'); + axis(hAx,'image'); + image(cdata,'Parent',hAx); + axis(hAx,'off'); + set(hAx,'UserData',cdata); + set(imgPanel, 'ComponentResizedCallback',{@resizeImg, hAx}, 'UserData',lowerPanel); + imgPanel.getGraphics.drawImage(jImg, 0, 0, []); + catch + % Never mind... + end +%} + % If all handles were selected (i.e., none were filtered) then only select the first + if (length(selectedIdx) == length(allHandles)) && ~isempty(selectedIdx) + selectedIdx = 1; + end + + % Store handles for callback use + userdata.handles = allHandles; + userdata.levels = allLevels; + userdata.parents = allParents; + userdata.hg_handles = hg_handles; + userdata.hg_levels = hg_levels; + userdata.hg_parents = hg_parentIdx; + userdata.initialIdx = selectedIdx; + userdata.userSelected = false; % Indicates the user has modified the initial selections + userdata.inInit = true; + userdata.jTree = jTreeObj; + userdata.jTreePeer = tree_h; + userdata.vsplitPane = vsplitPane; + userdata.hsplitPane = hsplitPane; + userdata.classNameLabel = classNameLabel; + userdata.inspectorPane = inspectorPane; + userdata.callbacksTable = callbacksTable; + userdata.jideTableUtils = jideTableUtils; + try + userdata.cbHideStdCbs = jcb; + catch + userdata.cbHideStdCbs = []; + end + + set(tree_h, 'userdata',userdata); + set(hTreeFig, 'userdata',userdata); + set(callbacksTable,'userdata',userdata); + + % Select the root node if requested + % Note: we must do so here since all other nodes except the root are processed by expandNode + if any(selectedIdx==1) + tree_h.setSelectedNode(root); + end + + % Set the initial cbHideStdCbs state + try + if jcb.isSelected + drawnow; + evd.getSource.isSelected = jcb.isSelected; + cbHideStdCbs_Callback(jcb,evd); + end + catch + % never mind... + end + + % Set the callback functions + set(tree_h, 'NodeExpandedCallback', {@nodeExpanded, tree_h}); + set(tree_h, 'NodeSelectedCallback', {@nodeSelected, tree_h}); + + % Set the tree mouse-click callback + % Note: default actions (expand/collapse) will still be performed? + % Note: MousePressedCallback is better than MouseClickedCallback + % since it fires immediately when mouse button is pressed, + % without waiting for its release, as MouseClickedCallback does + handleTree = tree_h.getScrollPane; + jTreeObj = handleTree.getViewport.getComponent(0); + set(jTreeObj, 'MousePressedCallback', {@treeMousePressedCallback,tree_h}); % context (right-click) menu + set(jTreeObj, 'MouseMovedCallback', @treeMouseMovedCallback); % mouse hover tooltips + + % Pre-expand all rows + expandNode(progressBar, jTreeObj, tree_h, root, 0); + %jTreeObj.setVisible(1); + + % Hide the progressbar now that we've finished expanding all rows + try + hsplitPane.getLeftComponent.setTopComponent(treePane); + catch + % Probably not a vSplitPane on the left... + hsplitPane.setLeftComponent(treePane); + end + hsplitPane.setDividerLocation(hsplitPaneLocation); % need to do it again... + + % Update userdata + userdata.inInit = false; + set(tree_h, 'userdata',userdata); + set(hTreeFig,'userdata',userdata); + + % Set keyboard focus on the tree + jTreeObj.requestFocus; + drawnow; + + % Check for a newer version + checkVersion(); + + % Reset the last error + lasterr(''); + end + + %% Rresize image pane + function resizeImg(varargin) %#ok - unused (TODO: waiting for img placement fix...) + try + hPanel = varargin{1}; + hAx = varargin{3}; + lowerPanel = get(hPanel,'UserData'); + newJPos = cell2mat(get(hPanel,{'X','Y','Width','Height'})); + newMPos = [1,get(lowerPanel,'Height'),newJPos(3:4)]; + set(hAx, 'units','pixels', 'position',newMPos, 'Visible','on'); + uistack(hAx,'top'); % no good... + set(hPanel,'Opaque','off'); % also no good... + catch + % Never mind... + dispError + end + return; + end + + %% "dbstop if error" causes inspect.m to croak due to a bug - so workaround by temporarily disabling this dbstop + function identifiers = disableDbstopError + dbStat = dbstatus; + idx = find(strcmp({dbStat.cond},'error')); + identifiers = [dbStat(idx).identifier]; + if ~isempty(idx) + dbclear if error; + msgbox('''dbstop if error'' had to be disabled due to a Matlab bug that would have caused Matlab to crash.', 'FindJObj', 'warn'); + end + end + + %% Restore any previous "dbstop if error" + function restoreDbstopError(identifiers) %#ok + for itemIdx = 1 : length(identifiers) + eval(['dbstop if error ' identifiers{itemIdx}]); + end + end + + %% Recursively expand all nodes (except toolbar/menubar) in startup + function expandNode(progressBar, tree, tree_h, parentNode, parentRow) + try + if nargin < 5 + parentPath = javax.swing.tree.TreePath(parentNode.getPath); + parentRow = tree.getRowForPath(parentPath); + end + tree.expandRow(parentRow); + progressBar.setValue(progressBar.getValue+1); + numChildren = parentNode.getChildCount; + if (numChildren == 0) + pause(0.0002); % as short as possible... + drawnow; + end + nodesToUnExpand = {'FigureMenuBar','MLMenuBar','MJToolBar','Box','uimenu','uitoolbar','ScrollBar'}; + numChildren = parentNode.getChildCount; + for childIdx = 0 : numChildren-1 + childNode = parentNode.getChildAt(childIdx); + + % Expand child node if not leaf & not toolbar/menubar + if childNode.isLeafNode + progressBar.setValue(progressBar.getValue+1); + + % Pre-select the node based upon the user's FINDJOBJ filters + try + nodedata = get(childNode, 'userdata'); + userdata = get(tree_h, 'userdata'); + if ~ishghandle(nodedata.obj) && ~userdata.userSelected && any(userdata.initialIdx == nodedata.idx) + pause(0.0002); % as short as possible... + drawnow; + if isempty(tree_h.getSelectedNodes) + tree_h.setSelectedNode(childNode); + else + newSelectedNodes = [tree_h.getSelectedNodes, childNode]; + tree_h.setSelectedNodes(newSelectedNodes); + end + end + catch + % never mind... + dispError + end + + else + % Expand all non-leaves + expandNode(progressBar, tree, tree_h, childNode); + + % Re-collapse toolbar/menubar etc., and also invisible containers + % Note: if we simply did nothing, progressbar would not have been updated... + try + childHandle = getappdata(childNode,'childHandle'); %=childNode.getUserObject + visible = childHandle.isVisible; + catch + visible = 1; + end + visible = visible && isempty(findstr(get(childNode,'Name'),'color="gray"')); + %if any(strcmp(childNode.getName,nodesToUnExpand)) + %name = char(childNode.getName); + if any(cellfun(@(s)~isempty(strmatch(s,char(childNode.getName))),nodesToUnExpand)) || ~visible + childPath = javax.swing.tree.TreePath(childNode.getPath); + childRow = tree.getRowForPath(childPath); + tree.collapseRow(childRow); + end + end + end + catch + % never mind... + dispError + end + end + + %% Create utility buttons + function hButton = createJButton(nameStr, handler, toolTipText) + try + jButton = javax.swing.JButton(['
' nameStr]); + jButton.setCursor(java.awt.Cursor.getPredefinedCursor(java.awt.Cursor.HAND_CURSOR)); + jButton.setToolTipText(toolTipText); + minSize = jButton.getMinimumSize; + jButton.setMinimumSize(java.awt.Dimension(minSize.getWidth,35)); + hButton = handle(jButton,'CallbackProperties'); + set(hButton,'ActionPerformedCallback',handler); + catch + % Never mind... + end + end + + %% Flash a component off/on for the specified duration + % note: starts with 'on'; if numTimes is odd then ends with 'on', otherwise with 'off' + function flashComponent(jComps,delaySecs,numTimes) + persistent redBorder redBorderPanels + try + % Handle callback data from right-click (context-menu) + if iscell(numTimes) + [jComps,delaySecs,numTimes] = deal(numTimes{:}); + end + + if isempty(redBorder) % reuse if possible + redBorder = javax.swing.border.LineBorder(java.awt.Color.red,2,0); + end + for compIdx = 1 : length(jComps) + try + oldBorder{compIdx} = jComps(compIdx).getBorder; %#ok grow + catch + oldBorder{compIdx} = []; %#ok grow + end + isSettable(compIdx) = ismethod(jComps(compIdx),'setBorder'); %#ok grow + if isSettable(compIdx) + try + % some components prevent border modification: + oldBorderFlag = jComps(compIdx).isBorderPainted; + if ~oldBorderFlag + jComps(compIdx).setBorderPainted(1); + isSettable(compIdx) = jComps(compIdx).isBorderPainted; %#ok grow + jComps(compIdx).setBorderPainted(oldBorderFlag); + end + catch + % do nothing... + end + end + if compIdx > length(redBorderPanels) + redBorderPanels{compIdx} = javax.swing.JPanel; + redBorderPanels{compIdx}.setBorder(redBorder); + redBorderPanels{compIdx}.setOpaque(0); % transparent interior, red border + end + try + redBorderPanels{compIdx}.setBounds(jComps(compIdx).getBounds); + catch + % never mind - might be an HG handle + end + end + for idx = 1 : 2*numTimes + if idx>1, pause(delaySecs); end % don't pause at start + visible = mod(idx,2); + for compIdx = 1 : length(jComps) + try + jComp = jComps(compIdx); + + % Prevent Matlab crash (java buffer overflow...) + if jComp.isa('com.mathworks.mwswing.desk.DTSplitPane') || ... + jComp.isa('com.mathworks.mwswing.MJSplitPane') + continue; + end + + % HG handles are highlighted by setting their 'Selected' property + if isa(jComp,'uimenu') + if visible + oldColor = get(jComp,'ForegroundColor'); + setappdata(jComp,'findjobj_oldColor',oldColor); + set(jComp,'ForegroundColor','red'); + else + oldColor = getappdata(jComp,'findjobj_oldColor'); + set(jComp,'ForegroundColor',oldColor); + rmappdata(jComp,'ForegroundColor'); + end + + elseif ishghandle(jComp) + if visible + set(jComp,'Selected','on'); + else + set(jComp,'Selected','off'); + end + + else %if isjava(jComp) + + jParent = jComps(compIdx).getParent; + + % Most Java components allow modifying their borders + if isSettable(compIdx) + if visible + jComp.setBorder(redBorder); + try jComp.setBorderPainted(1); catch, end %#ok + else %if ~isempty(oldBorder{compIdx}) + jComp.setBorder(oldBorder{compIdx}); + end + jComp.repaint; + + % The other Java components are highlighted by a transparent red-border + % panel that is placed on top of them in their parent's space + elseif ~isempty(jParent) + if visible + jParent.add(redBorderPanels{compIdx}); + jParent.setComponentZOrder(redBorderPanels{compIdx},0); + else + jParent.remove(redBorderPanels{compIdx}); + end + jParent.repaint + end + end + catch + % never mind - try the next component (if any) + end + end + drawnow; + end + catch + % never mind... + dispError; + end + return; % debug point + end % flashComponent + + %% Select tree node + function nodeSelected(src, evd, tree) %#ok + try + if iscell(tree) + [src,node] = deal(tree{:}); + else + node = evd.getCurrentNode; + end + %nodeHandle = node.getUserObject; + nodedata = get(node,'userdata'); + nodeHandle = nodedata.obj; + userdata = get(src,'userdata'); + if ~isempty(nodeHandle) && ~isempty(userdata) + numSelections = userdata.jTree.getSelectionCount; + selectionPaths = userdata.jTree.getSelectionPaths; + if (numSelections == 1) + % Indicate that the user has modified the initial selection (except if this was an initial auto-selected node) + if ~userdata.inInit + userdata.userSelected = true; + set(src,'userdata',userdata); + end + + % Update the fully-qualified class name label + numInitialIdx = length(userdata.initialIdx); + thisHandle = nodeHandle; + try + if ~ishghandle(thisHandle) + thisHandle = java(nodeHandle); + end + catch + % never mind... + end + if ~userdata.inInit || (numInitialIdx == 1) + userdata.classNameLabel.setText([' ' char(thisHandle.class)]); + else + userdata.classNameLabel.setText([' ' num2str(numInitialIdx) 'x handles (some handles hidden by unexpanded tree nodes)']); + end + if ishghandle(thisHandle) + userdata.classNameLabel.setText(userdata.classNameLabel.getText.concat(' (HG handle)')); + end + userdata.inspectorPane.dispose; % remove props listeners - doesn't work... + updateNodeTooltip(nodeHandle, userdata.classNameLabel); + + % Update the data properties inspector pane + % Note: we can't simply use the evd nodeHandle, because this node might have been DE-selected with only one other node left selected... + %nodeHandle = selectionPaths(1).getLastPathComponent.getUserObject; + nodedata = get(selectionPaths(1).getLastPathComponent,'userdata'); + nodeHandle = nodedata.obj; + %identifiers = disableDbstopError; % "dbstop if error" causes inspect.m to croak due to a bug - so workaround + userdata.inspectorPane.setObject(thisHandle); + + % Update the callbacks table + try + stripStdCbsFlag = getappdata(userdata.callbacksTable,'hideStdCbs'); + [cbData, cbHeaders, cbTableEnabled] = getCbsData(nodeHandle, stripStdCbsFlag); %#ok cbTableEnabled unused + callbacksTableModel = javax.swing.table.DefaultTableModel(cbData,cbHeaders); + set(callbacksTableModel, 'TableChangedCallback',@tbCallbacksChanged, 'UserData',nodeHandle); + userdata.callbacksTable.setModel(callbacksTableModel) + userdata.callbacksTable.setRowAutoResizes(true); + userdata.jideTableUtils.autoResizeAllColumns(userdata.callbacksTable); + catch + % never mind... + dispError + end + pause(0.005); + drawnow; + %restoreDbstopError(identifiers); + + % Highlight the selected object (if visible) + flashComponent(nodeHandle,0.2,3); + + elseif (numSelections > 1) % Multiple selections + + % Get the list of all selected nodes + jArray = javaArray('java.lang.Object', numSelections); + toolTipStr = ''; + sameClassFlag = true; + for idx = 1 : numSelections + %jArray(idx) = selectionPaths(idx).getLastPathComponent.getUserObject; + nodedata = get(selectionPaths(idx).getLastPathComponent,'userdata'); + try + jArray(idx) = java(nodedata.obj); + catch + jArray(idx) = nodedata.obj; + end + toolTipStr = [toolTipStr ' ' jArray(idx).class ' ']; %#ok grow + if (idx < numSelections), toolTipStr = [toolTipStr '
']; end %#ok grow + if (idx > 1) && sameClassFlag && ~isequal(jArray(idx).getClass,jArray(1).getClass) + sameClassFlag = false; + end + end + toolTipStr = [toolTipStr '']; + + % Update the fully-qualified class name label + if sameClassFlag + classNameStr = jArray(1).class; + else + classNameStr = 'handle'; + end + if all(ishghandle(jArray)) + if strcmp(classNameStr,'handle') + classNameStr = 'HG handles'; + else + classNameStr = [classNameStr ' (HG handles)']; + end + end + classNameStr = [' ' num2str(numSelections) 'x ' classNameStr]; + userdata.classNameLabel.setText(classNameStr); + userdata.classNameLabel.setToolTipText(toolTipStr); + + % Update the data properties inspector pane + %identifiers = disableDbstopError; % "dbstop if error" causes inspect.m to croak due to a bug - so workaround + userdata.inspectorPane.getRegistry.setSelected(jArray, true); + + % Update the callbacks table + try + % Get intersecting callback names & values + stripStdCbsFlag = getappdata(userdata.callbacksTable,'hideStdCbs'); + [cbData, cbHeaders, cbTableEnabled] = getCbsData(jArray(1), stripStdCbsFlag); %#ok cbHeaders & cbTableEnabled unused + if ~isempty(cbData) + cbNames = cbData(:,1); + for idx = 2 : length(jArray) + [cbData2, cbHeaders2] = getCbsData(jArray(idx), stripStdCbsFlag); %#ok cbHeaders2 unused + if ~isempty(cbData2) + newCbNames = cbData2(:,1); + [cbNames, cbIdx, cb2Idx] = intersect(cbNames,newCbNames); %#ok cb2Idx unused + cbData = cbData(cbIdx,:); + for cbIdx = 1 : length(cbNames) + newIdx = find(strcmp(cbNames{cbIdx},newCbNames)); + if ~isequal(cbData2{newIdx,2}, cbData{cbIdx,2}) + cbData{cbIdx,2} = ''; + end + end + else + cbData = cbData([],:); %=empty cell array + end + if isempty(cbData) + break; + end + end + end + cbHeaders = {'Callback name','Callback value'}; + callbacksTableModel = javax.swing.table.DefaultTableModel(cbData,cbHeaders); + set(callbacksTableModel, 'TableChangedCallback',@tbCallbacksChanged, 'UserData',jArray); + userdata.callbacksTable.setModel(callbacksTableModel) + userdata.callbacksTable.setRowAutoResizes(true); + userdata.jideTableUtils.autoResizeAllColumns(userdata.callbacksTable); + catch + % never mind... + dispError + end + + pause(0.005); + drawnow; + %restoreDbstopError(identifiers); + + % Highlight the selected objects (if visible) + flashComponent(jArray,0.2,3); + end + + % TODO: Auto-highlight selected object (?) + %nodeHandle.requestFocus; + end + catch + dispError + end + end + + %% IFF utility function for annonymous cellfun funcs + function result = iff(test,trueVal,falseVal) %#ok + try + if test + result = trueVal; + else + result = falseVal; + end + catch + result = false; + end + end + + %% Get an HTML representation of the object's properties + function dataFieldsStr = getPropsHtml(nodeHandle, dataFields) + try + % Get a text representation of the fieldnames & values + dataFieldsStr = ''; % just in case the following croaks... + if isempty(dataFields) + return; + end + dataFieldsStr = evalc('disp(dataFields)'); + if dataFieldsStr(end)==char(10), dataFieldsStr=dataFieldsStr(1:end-1); end + + % Strip out callbacks + dataFieldsStr = regexprep(dataFieldsStr,'^\s*\w*Callback(Data)?:[^\n]*$','','lineanchors'); + dataFieldsStr = regexprep(dataFieldsStr,'\n\n','\n'); + + % HTMLize tooltip data + % First, set the fields' font based on its read-write status + nodeHandle = handle(nodeHandle); % ensure this is a Matlab handle, not a java object + fieldNames = fieldnames(dataFields); + undefinedStr = ''; + for fieldIdx = 1 : length(fieldNames) + thisFieldName = fieldNames{fieldIdx}; + accessFlags = get(findprop(nodeHandle,thisFieldName),'AccessFlags'); + if isfield(accessFlags,'PublicSet') && strcmp(accessFlags.PublicSet,'on') + % Bolden read/write fields + thisFieldFormat = ['' thisFieldName ':$2']; + elseif ~isfield(accessFlags,'PublicSet') + % Undefined - probably a Matlab-defined field of com.mathworks.hg.peer.FigureFrameProxy... + thisFieldFormat = ['' thisFieldName ':$2']; + undefinedStr = ', undefined'; + else % PublicSet=='off' + % Gray-out & italicize any read-only fields + thisFieldFormat = ['' thisFieldName ':$2']; + end + dataFieldsStr = regexprep(dataFieldsStr, ['([\s\n])' thisFieldName ':([^\n]*)'], ['$1' thisFieldFormat]); + end + catch + % never mind... - probably an ambiguous property name + %dispError + end + + % Method 1: simple
list + %dataFieldsStr = strrep(dataFieldsStr,char(10),' 
  '); + + % Method 2: 2x2-column + dataFieldsStr = regexprep(dataFieldsStr, '^\s*([^:]+:)([^\n]*)\n^\s*([^:]+:)([^\n]*)$', '', 'lineanchors'); + dataFieldsStr = regexprep(dataFieldsStr, '^[^<]\s*([^:]+:)([^\n]*)$', '', 'lineanchors'); + dataFieldsStr = ['(modifiable' undefinedStr ' & read-only fields)

  

 $1 $2    $3 $4 
 $1 $2  
' dataFieldsStr '
']; + end + + %% Update tooltip string with a node's data + function updateNodeTooltip(nodeHandle, uiObject) + try + toolTipStr = nodeHandle.class; + dataFieldsStr = ''; + + % Add HG annotation if relevant + if ishghandle(nodeHandle) + hgStr = ' HG Handle'; + else + hgStr = ''; + end + + % Note: don't bulk-get because (1) not all properties are returned & (2) some properties cause a Java exception + % Note2: the classhandle approach does not enable access to user-defined schema.props + ch = classhandle(handle(nodeHandle)); + dataFields = []; + [sortedNames, sortedIdx] = sort(get(ch.Properties,'Name')); + for idx = 1 : length(sortedIdx) + sp = ch.Properties(sortedIdx(idx)); + % TODO: some fields (see EOL comment below) generate a Java Exception from: com.mathworks.mlwidgets.inspector.PropertyRootNode$PropertyListener$1$1.run + if strcmp(sp.AccessFlags.PublicGet,'on') % && ~any(strcmp(sp.Name,{'FixedColors','ListboxTop','Extent'})) + try + dataFields.(sp.Name) = get(nodeHandle, sp.Name); + catch + dataFields.(sp.Name) = 'Error!'; + end + else + dataFields.(sp.Name) = '(no public getter method)'; + end + end + dataFieldsStr = getPropsHtml(nodeHandle, dataFields); + catch + % Probably a non-HG java object + try + % Note: the bulk-get approach enables access to user-defined schema-props, but not to some original classhandle Properties... + dataFields = get(nodeHandle); + dataFieldsStr = getPropsHtml(nodeHandle, dataFields); + catch + % Probably a missing property getter implementation + try + % Inform the user - bail out on error + err = lasterror; + dataFieldsStr = ['

' strrep(err.message, char(10), '
')]; + catch + % forget it... + end + end + end + + % Set the object tooltip + if ~isempty(dataFieldsStr) + toolTipStr = [' ' char(toolTipStr) '' hgStr ': ' dataFieldsStr '']; + end + uiObject.setToolTipText(toolTipStr); + end + + %% Expand tree node + function nodeExpanded(src, evd, tree) %#ok + % tree = handle(src); + % evdsrc = evd.getSource; + evdnode = evd.getCurrentNode; + + if ~tree.isLoaded(evdnode) + + % Get the list of children TreeNodes + childnodes = getChildrenNodes(tree, evdnode); + + % Add the HG sub-tree (unless already included in the first tree) + childHandle = getappdata(evdnode.handle,'childHandle'); %=evdnode.getUserObject + if evdnode.isRoot && ~isempty(hg_handles) && ~isequal(hg_handles(1).java, childHandle) + childnodes = [childnodes, getChildrenNodes(tree, evdnode, true)]; + end + + % If we have a single child handle, wrap it within a javaArray for tree.add() to "swallow" + if (length(childnodes) == 1) + chnodes = childnodes; + childnodes = javaArray('com.mathworks.hg.peer.UITreeNode', 1); + childnodes(1) = java(chnodes); + end + + % Add child nodes to the current node + tree.add(evdnode, childnodes); + tree.setLoaded(evdnode, true); + end + end + + %% Get an icon image no larger than 16x16 pixels + function iconImage = setIconSize(iconImage) + try + iconWidth = iconImage.getWidth; + iconHeight = iconImage.getHeight; + if iconWidth > 16 + newHeight = fix(iconHeight * 16 / iconWidth); + iconImage = iconImage.getScaledInstance(16,newHeight,iconImage.SCALE_SMOOTH); + elseif iconHeight > 16 + newWidth = fix(iconWidth * 16 / iconHeight); + iconImage = iconImage.getScaledInstance(newWidth,16,iconImage.SCALE_SMOOTH); + end + catch + % never mind... - return original icon + end + end % setIconSize + + %% Get list of children nodes + function nodes = getChildrenNodes(tree, parentNode, isRootHGNode) + try + iconpath = [matlabroot, '/toolbox/matlab/icons/']; + nodes = handle([]); + userdata = get(tree,'userData'); + hdls = userdata.handles; + nodedata = get(parentNode,'userdata'); + if nargin < 3 + %isJavaNode = ~ishghandle(parentNode.getUserObject); + isJavaNode = ~ishghandle(nodedata.obj); + isRootHGNode = false; + else + isJavaNode = ~isRootHGNode; + end + + % Search for this parent node in the list of all nodes + parents = userdata.parents; + nodeIdx = nodedata.idx; + + if isJavaNode && isempty(nodeIdx) % Failback, in case userdata doesn't work for some reason... + for hIdx = 1 : length(hdls) + %if isequal(handle(parentNode.getUserObject), hdls(hIdx)) + if isequal(handle(nodedata.obj), hdls(hIdx)) + nodeIdx = hIdx; + break; + end + end + end + if ~isJavaNode + if isRootHGNode % =root HG node + thisChildHandle = userdata.hg_handles(1); + childName = getNodeName(thisChildHandle); + hasGrandChildren = any(parents==1); + icon = []; + if hasGrandChildren && length(hg_handles)>1 + childName = childName.concat(' - HG root container'); + icon = [iconpath 'figureicon.gif']; + end + try + nodes = uitreenode('v0', thisChildHandle, childName, icon, ~hasGrandChildren); + catch % old matlab version don't have the 'v0' option + try + nodes = uitreenode(thisChildHandle, childName, icon, ~hasGrandChildren); + catch + % probably an invalid handle - ignore... + end + end + + % Add the handler to the node's internal data + % Note: could also use 'userdata', but setUserObject() is recommended for TreeNodes + % Note2: however, setUserObject() sets a java *BeenAdapter object for HG handles instead of the required original class, so use setappdata + %nodes.setUserObject(thisChildHandle); + setappdata(nodes,'childHandle',thisChildHandle); + nodedata.idx = 1; + nodedata.obj = thisChildHandle; + set(nodes,'userdata',nodedata); + return; + else % non-root HG node + parents = userdata.hg_parents; + hdls = userdata.hg_handles; + end % if isRootHGNode + end % if ~isJavaNode + + % If this node was found, get the list of its children + if ~isempty(nodeIdx) + %childIdx = setdiff(find(parents==nodeIdx),nodeIdx); + childIdx = find(parents==nodeIdx); + childIdx(childIdx==nodeIdx) = []; % faster... + numChildren = length(childIdx); + for cIdx = 1 : numChildren + thisChildIdx = childIdx(cIdx); + thisChildHandle = hdls(thisChildIdx); + childName = getNodeName(thisChildHandle); + try + visible = thisChildHandle.Visible; + if visible + try visible = thisChildHandle.Width > 0; catch, end %#ok + end + if ~visible + childName = ['' char(childName) '']; %#ok grow + end + catch + % never mind... + end + hasGrandChildren = any(parents==thisChildIdx); + try + isaLabel = isa(thisChildHandle.java,'javax.swing.JLabel'); + catch + isaLabel = 0; + end + if hasGrandChildren && ~any(strcmp(thisChildHandle.class,{'axes'})) + icon = [iconpath 'foldericon.gif']; + elseif isaLabel + icon = [iconpath 'tool_text.gif']; + else + icon = []; + end + try + nodes(cIdx) = uitreenode('v0', thisChildHandle, childName, icon, ~hasGrandChildren); + catch % old matlab version don't have the 'v0' option + try + nodes(cIdx) = uitreenode(thisChildHandle, childName, icon, ~hasGrandChildren); + catch + % probably an invalid handle - ignore... + end + end + + % Use existing object icon, if available + try + setTreeNodeIcon(nodes(cIdx),thisChildHandle); + catch + % probably an invalid handle - ignore... + end + + % Pre-select the node based upon the user's FINDJOBJ filters + try + if isJavaNode && ~userdata.userSelected && any(userdata.initialIdx == thisChildIdx) + pause(0.0002); % as short as possible... + drawnow; + if isempty(tree.getSelectedNodes) + tree.setSelectedNode(nodes(cIdx)); + else + newSelectedNodes = [tree.getSelectedNodes, nodes(cIdx).java]; + tree.setSelectedNodes(newSelectedNodes); + end + end + catch + % never mind... + end + + % Add the handler to the node's internal data + % Note: could also use 'userdata', but setUserObject() is recommended for TreeNodes + % Note2: however, setUserObject() sets a java *BeenAdapter object for HG handles instead of the required original class, so use setappdata + % Note3: the following will error if invalid handle - ignore + try + if isJavaNode + thisChildHandle = thisChildHandle.java; + end + %nodes(cIdx).setUserObject(thisChildHandle); + setappdata(nodes(cIdx),'childHandle',thisChildHandle); + nodedata.idx = thisChildIdx; + nodedata.obj = thisChildHandle; + set(nodes(cIdx),'userdata',nodedata); + catch + % never mind (probably an invalid handle) - leave unchanged (like a leaf) + end + end + end + catch + % Never mind - leave unchanged (like a leaf) + %error('YMA:findjobj:UnknownNodeType', 'Error expanding component tree node'); + dispError + end + end + + %% Get a node's name + function [nodeName, nodeTitle] = getNodeName(hndl,charsLimit) + try + % Initialize (just in case one of the succeding lines croaks) + nodeName = ''; + if ~ismethod(hndl,'getClass') + try + nodeName = hndl.class; + catch + nodeName = hndl.type; % last-ditch try... + end + else + nodeName = hndl.getClass.getSimpleName; + end + + % Strip away the package name, leaving only the regular classname + if ~isempty(nodeName) && ischar(nodeName) + nodeName = java.lang.String(nodeName); + nodeName = nodeName.substring(nodeName.lastIndexOf('.')+1); + end + if (nodeName.length == 0) + % fix case of anonymous internal classes, that do not have SimpleNames + try + nodeName = hndl.getClass.getName; + nodeName = nodeName.substring(nodeName.lastIndexOf('.')+1); + catch + % never mind - leave unchanged... + end + end + + % Get any unique identifying string (if available in one of several fields) + labelsToCheck = {'label','title','text','string','displayname','toolTipText','TooltipString','actionCommand','name','Tag','style'}; %,'UIClassID'}; + nodeTitle = ''; + strField = ''; %#ok - used for debugging + while ((~isa(nodeTitle,'java.lang.String') && ~ischar(nodeTitle)) || isempty(nodeTitle)) && ~isempty(labelsToCheck) + try + nodeTitle = get(hndl,labelsToCheck{1}); + strField = labelsToCheck{1}; %#ok - used for debugging + catch + % never mind - probably missing prop, so skip to next one + end + labelsToCheck(1) = []; + end + if length(nodeTitle) ~= numel(nodeTitle) + % Multi-line - convert to a long single line + nodeTitle = nodeTitle'; + nodeTitle = nodeTitle(:)'; + end + if nargin<2, charsLimit = 25; end + extraStr = regexprep(nodeTitle,{sprintf('(.{%d,%d}).*',charsLimit,min(charsLimit,length(nodeTitle)-1)),' +'},{'$1...',' '},'once'); + if ~isempty(extraStr) + if ischar(extraStr) + nodeName = nodeName.concat(' (''').concat(extraStr).concat(''')'); + else + nodeName = nodeName.concat(' (').concat(num2str(extraStr)).concat(')'); + end + %nodeName = nodeName.concat(strField); + end + catch + % Never mind - use whatever we have so far + %dispError + end + end + + %% Strip standard Swing callbacks from a list of events + function evNames = stripStdCbs(evNames) + try + stdEvents = {'AncestorAdded', 'AncestorMoved', 'AncestorRemoved', 'AncestorResized', ... + 'CaretPositionChanged', 'ComponentAdded', 'ComponentRemoved', ... + 'ComponentHidden', 'ComponentMoved', 'ComponentResized', 'ComponentShown', ... + 'PropertyChange', 'FocusGained', 'FocusLost', ... + 'HierarchyChanged', 'InputMethodTextChanged', ... + 'KeyPressed', 'KeyReleased', 'KeyTyped', ... + 'MouseClicked', 'MouseDragged', 'MouseEntered', 'MouseExited', ... + 'MouseMoved', 'MousePressed', 'MouseReleased', 'MouseWheelMoved', ... + 'VetoableChange'}; + stdEvents = [stdEvents, strcat(stdEvents,'Callback'), strcat(stdEvents,'Fcn')]; + evNames = setdiff(evNames,stdEvents)'; + catch + % Never mind... + dispError + end + end + + %% Callback function for checkbox + function cbHideStdCbs_Callback(src, evd, varargin) %#ok + try + % Store the current checkbox value for later use + callbacksTable = get(src,'userdata'); + if evd.getSource.isSelected + setappdata(callbacksTable,'hideStdCbs',1); + else + setappdata(callbacksTable,'hideStdCbs',[]); + end + + % Rescan the current node + userdata = get(callbacksTable,'userdata'); + ed.getCurrentNode = userdata.jTree.getSelectionModel.getSelectionPath.getLastPathComponent; + nodeSelected(userdata.jTreePeer,ed,[]); + catch + % Never mind... + dispError + end + end + + %% Callback function for button + function btWebsite_Callback(src, evd, varargin) %#ok + try + web('http://UndocumentedMatlab.com/'); + catch + % Never mind... + dispError + end + end + + %% Callback function for button + function btRefresh_Callback(src, evd, varargin) %#ok + try + % Set cursor shape to hourglass until we're done + hTreeFig = varargin{2}; + set(hTreeFig,'Pointer','watch'); + drawnow; + object = varargin{1}; + + % Re-invoke this utility to re-scan the container for all children + findjobj(object); + catch + % Never mind... + end + + % Restore default cursor shape + set(hTreeFig,'Pointer','arrow'); + end + + %% Callback function for button + function btExport_Callback(src, evd, varargin) %#ok + try + % Get the list of all selected nodes + if length(varargin) > 1 + jTree = varargin{1}; + numSelections = jTree.getSelectionCount; + selectionPaths = jTree.getSelectionPaths; + hdls = handle([]); + for idx = 1 : numSelections + %hdls(idx) = handle(selectionPaths(idx).getLastPathComponent.getUserObject); + nodedata = get(selectionPaths(idx).getLastPathComponent,'userdata'); + hdls(idx) = handle(nodedata.obj,'CallbackProperties'); + end + + % Assign the handles in the base workspace & inform user + assignin('base','findjobj_hdls',hdls); + classNameLabel = varargin{2}; + msg = ['Exported ' char(classNameLabel.getText.trim) ' to base workspace variable findjobj_hdls']; + else + % Right-click (context-menu) callback + data = varargin{1}; + obj = data{1}; + varName = data{2}; + if isempty(varName) + varName = inputdlg('Enter workspace variable name','FindJObj'); + if isempty(varName), return; end % bail out on + varName = varName{1}; + if isempty(varName) || ~ischar(varName), return; end % bail out on empty/null + varName = genvarname(varName); + end + assignin('base',varName,handle(obj,'CallbackProperties')); + msg = ['Exported object to base workspace variable ' varName]; + end + msgbox(msg,'FindJObj','help'); + catch + % Never mind... + dispError + end + end + + %% Callback function for button + function btFocus_Callback(src, evd, varargin) %#ok + try + % Request focus for the specified object + object = getTopSelectedObject(varargin{:}); + object.requestFocus; + catch + try + object = object.java.getPeer.requestFocus; + object.requestFocus; + catch + % Never mind... + %dispError + end + end + end + + %% Callback function for button + function btInspect_Callback(src, evd, varargin) %#ok + try + % Inspect the specified object + if length(varargin) == 1 + object = varargin{1}; + else + object = getTopSelectedObject(varargin{:}); + end + if isempty(which('uiinspect')) + + % If the user has not indicated NOT to be informed about UIInspect + if ~ispref('FindJObj','dontCheckUIInspect') + + % Ask the user whether to download UIINSPECT (YES, no, no & don't ask again) + answer = questdlg({'The object inspector requires UIINSPECT from the MathWorks File Exchange. UIINSPECT was created by Yair Altman, like this FindJObj utility.','','Download & install UIINSPECT?'},'UIInspect','Yes','No','No & never ask again','Yes'); + switch answer + case 'Yes' % => Yes: download & install + try + % Download UIINSPECT + baseUrl = 'http://www.mathworks.com/matlabcentral/fileexchange/17935'; + fileUrl = [baseUrl '?controller=file_infos&download=true']; + file = urlread(fileUrl); + file = regexprep(file,[char(13),char(10)],'\n'); %convert to OS-dependent EOL + + % Install... + newPath = fullfile(fileparts(which(mfilename)),'uiinspect.m'); + fid = fopen(newPath,'wt'); + fprintf(fid,'%s',file); + fclose(fid); + catch + % Error downloading: inform the user + msgbox(['Error in downloading: ' lasterr], 'UIInspect', 'warn'); + web(baseUrl); + end + + % ...and now run it... + %pause(0.1); + drawnow; + dummy = which('uiinspect'); %#ok used only to load into memory + uiinspect(object); + return; + + case 'No & never ask again' % => No & don't ask again + setpref('FindJObj','dontCheckUIInspect',1); + + otherwise + % forget it... + end + end + drawnow; + + % No UIINSPECT available - run the good-ol' METHODSVIEW()... + methodsview(object); + else + uiinspect(object); + end + catch + try + if isjava(object) + methodsview(object) + else + methodsview(object.java); + end + catch + % Never mind... + dispError + end + end + end + + %% Callback function for button + function btCheckFex_Callback(src, evd, varargin) %#ok + try + % Check the FileExchange for the latest version + web('http://www.mathworks.com/matlabcentral/fileexchange/loadFile.do?objectId=14317'); + catch + % Never mind... + dispError + end + end + + %% Check for existence of a newer version + function checkVersion() + try + % If the user has not indicated NOT to be informed + if ~ispref('FindJObj','dontCheckNewerVersion') + + % Get the latest version date from the File Exchange webpage + baseUrl = 'http://www.mathworks.com/matlabcentral/fileexchange/'; + webUrl = [baseUrl '14317']; % 'loadFile.do?objectId=14317']; + webPage = urlread(webUrl); + modIdx = strfind(webPage,'>Updates<'); + if ~isempty(modIdx) + webPage = webPage(modIdx:end); + % Note: regexp hangs if substr not found, so use strfind instead... + %latestWebVersion = regexprep(webPage,'.*?>(20[\d-]+).*','$1'); + dateIdx = strfind(webPage,'class="date">'); + if ~isempty(dateIdx) + latestDate = webPage(dateIdx(end)+13 : dateIdx(end)+23); + try + startIdx = dateIdx(end)+27; + descStartIdx = startIdx + strfind(webPage(startIdx:startIdx+999),''); + descEndIdx = startIdx + strfind(webPage(startIdx:startIdx+999),''); + descStr = webPage(descStartIdx(1)+3 : descEndIdx(1)-2); + descStr = regexprep(descStr,'',''); + catch + descStr = ''; + end + + % Get this file's latest date + thisFileName = which(mfilename); %#ok + try + thisFileData = dir(thisFileName); + try + thisFileDatenum = thisFileData.datenum; + catch % old ML versions... + thisFileDatenum = datenum(thisFileData.date); + end + catch + thisFileText = evalc('type(thisFileName)'); + thisFileLatestDate = regexprep(thisFileText,'.*Change log:[\s%]+([\d-]+).*','$1'); + thisFileDatenum = datenum(thisFileLatestDate,'yyyy-mm-dd'); + end + + % If there's a newer version on the File Exchange webpage (allow 2 days grace period) + if (thisFileDatenum < datenum(latestDate,'dd mmm yyyy')-2) + + % Ask the user whether to download the newer version (YES, no, no & don't ask again) + msg = {['A newer version (' latestDate ') of FindJObj is available on the MathWorks File Exchange:'], '', ... + ['\color{blue}' descStr '\color{black}'], '', ... + 'Download & install the new version?'}; + createStruct.Interpreter = 'tex'; + createStruct.Default = 'Yes'; + answer = questdlg(msg,'FindJObj','Yes','No','No & never ask again',createStruct); + switch answer + case 'Yes' % => Yes: download & install newer file + try + %fileUrl = [baseUrl '/download.do?objectId=14317&fn=findjobj&fe=.m']; + fileUrl = [baseUrl '/14317?controller=file_infos&download=true']; + file = urlread(fileUrl); + file = regexprep(file,[char(13),char(10)],'\n'); %convert to OS-dependent EOL + fid = fopen(thisFileName,'wt'); + fprintf(fid,'%s',file); + fclose(fid); + catch + % Error downloading: inform the user + msgbox(['Error in downloading: ' lasterr], 'FindJObj', 'warn'); + web(webUrl); + end + case 'No & never ask again' % => No & don't ask again + setpref('FindJObj','dontCheckNewerVersion',1); + otherwise + % forget it... + end + end + end + else + % Maybe webpage not fully loaded or changed format - bail out... + end + end + catch + % Never mind... + end + end + + %% Get the first selected object (might not be the top one - depends on selection order) + function object = getTopSelectedObject(jTree, root) + try + object = []; + numSelections = jTree.getSelectionCount; + if numSelections > 0 + % Get the first object specified + %object = jTree.getSelectionPath.getLastPathComponent.getUserObject; + nodedata = get(jTree.getSelectionPath.getLastPathComponent,'userdata'); + else + % Get the root object (container) + %object = root.getUserObject; + nodedata = get(root,'userdata'); + end + object = nodedata.obj; + catch + % Never mind... + dispError + end + end + + %% Update component callback upon callbacksTable data change + function tbCallbacksChanged(src, evd) + try + % exit if invalid handle or already in Callback + if ~ishandle(src) || ~isempty(getappdata(src,'inCallback')) % || length(dbstack)>1 %exit also if not called from user action + return; + end + setappdata(src,'inCallback',1); % used to prevent endless recursion + + % Update the object's callback with the modified value + modifiedColIdx = evd.getColumn; + modifiedRowIdx = evd.getFirstRow; + if modifiedColIdx==1 && modifiedRowIdx>=0 %sanity check - should always be true + table = evd.getSource; + object = get(src,'userdata'); + cbName = strtrim(table.getValueAt(modifiedRowIdx,0)); + try + cbValue = strtrim(char(table.getValueAt(modifiedRowIdx,1))); + if ~isempty(cbValue) && ismember(cbValue(1),'{[@''') + cbValue = eval(cbValue); + end + if (~ischar(cbValue) && ~isa(cbValue, 'function_handle') && (iscom(object(1)) || iscell(cbValue))) + revertCbTableModification(table, modifiedRowIdx, modifiedColIdx, cbName, object, ''); + else + for objIdx = 1 : length(object) + if ~iscom(object(objIdx)) + set(object(objIdx), cbName, cbValue); + else + cbs = object(objIdx).eventlisteners; + if ~isempty(cbs) + cbs = cbs(strcmpi(cbs(:,1),cbName),:); + object(objIdx).unregisterevent(cbs); + end + if ~isempty(cbValue) + object(objIdx).registerevent({cbName, cbValue}); + end + end + end + end + catch + revertCbTableModification(table, modifiedRowIdx, modifiedColIdx, cbName, object, lasterr) + end + end + catch + % never mind... + end + setappdata(src,'inCallback',[]); % used to prevent endless recursion + end + + %% Revert Callback table modification + function revertCbTableModification(table, modifiedRowIdx, modifiedColIdx, cbName, object, errMsg) %#ok + try + % Display a notification MsgBox + msg = 'Callbacks must be a ''string'', or a @function handle'; + if ~iscom(object(1)), msg = [msg ' or a {@func,args...} construct']; end + if ~isempty(errMsg), msg = {errMsg, '', msg}; end + msgbox(msg, ['Error setting ' cbName ' callback'], 'warn'); + + % Revert to the current value + curValue = ''; + try + if ~iscom(object(1)) + curValue = charizeData(get(object(1),cbName)); + else + cbs = object(1).eventlisteners; + if ~isempty(cbs) + cbs = cbs(strcmpi(cbs(:,1),cbName),:); + curValue = charizeData(cbs(1,2)); + end + end + catch + % never mind... - clear the current value + end + table.setValueAt(curValue, modifiedRowIdx, modifiedColIdx); + catch + % never mind... + end + end % revertCbTableModification + + %% Traverse an HG container hierarchy and extract the HG elements within + function traverseHGContainer(hcontainer,level,parent) + try + % Record the data for this node + thisIdx = length(hg_levels) + 1; + hg_levels(thisIdx) = level; + hg_parentIdx(thisIdx) = parent; + hg_handles(thisIdx) = handle(hcontainer); + parentId = length(hg_parentIdx); + + % Now recursively process all this node's children (if any) + %if ishghandle(hcontainer) + try % try-catch is faster than checking ishghandle(hcontainer)... + allChildren = allchild(handle(hcontainer)); + for childIdx = 1 : length(allChildren) + traverseHGContainer(allChildren(childIdx),level+1,parentId); + end + catch + % do nothing - probably not a container + %dispError + end + + % TODO: Add axis (plot) component handles + catch + % forget it... + end + end + + %% Debuggable "quiet" error-handling + function dispError + err = lasterror; + msg = err.message; + for idx = 1 : length(err.stack) + filename = err.stack(idx).file; + if ~isempty(regexpi(filename,mfilename)) + funcname = err.stack(idx).name; + line = num2str(err.stack(idx).line); + msg = [msg ' at ' funcname ' line #' line '']; %#ok grow + break; + end + end + disp(msg); + return; % debug point + end + + %% ML 7.0 - compatible ischar() function + function flag = ischar(data) + try + flag = builtin('ischar',data); + catch + flag = isa(data,'char'); + end + end + + %% Set up the uitree context (right-click) menu + function jmenu = setTreeContextMenu(obj,node,tree_h) + % Prepare the context menu (note the use of HTML labels) + import javax.swing.* + titleStr = getNodeTitleStr(obj,node); + titleStr = regexprep(titleStr,'


.*',''); + menuItem0 = JMenuItem(titleStr); + set(menuItem0,'Enabled','off','Armed','off'); + menuItem1 = JMenuItem('Export handle to findjobj_hdls'); + menuItem2 = JMenuItem('Export handle to...'); + menuItem3 = JMenuItem('Request focus (bring to front)'); + menuItem4 = JMenuItem('Flash component borders'); + menuItem5 = JMenuItem('Display properties & callbacks'); + menuItem6 = JMenuItem('Inspect object'); + + % Set the menu items' callbacks + set(menuItem1,'ActionPerformedCallback',{@btExport_Callback,{obj,'findjobj_hdls'}}); + set(menuItem2,'ActionPerformedCallback',{@btExport_Callback,{obj,[]}}); + set(menuItem3,'ActionPerformedCallback',{@requestFocus,obj}); + set(menuItem4,'ActionPerformedCallback',{@flashComponent,{obj,0.2,3}}); + set(menuItem5,'ActionPerformedCallback',{@nodeSelected,{tree_h,node}}); + set(menuItem6,'ActionPerformedCallback',{@btInspect_Callback,obj}); + + % Add all menu items to the context menu (with internal separator) + jmenu = JPopupMenu; + jmenu.add(menuItem0); + jmenu.addSeparator; + handleValue=[]; try handleValue = double(obj); catch, end; %#ok + if ~isempty(handleValue) + % For valid HG handles only + menuItem0a = JMenuItem('Copy handle value to clipboard'); + set(menuItem0a,'ActionPerformedCallback',sprintf('clipboard(''copy'',%.99g)',handleValue)); + jmenu.add(menuItem0a); + end + jmenu.add(menuItem1); + jmenu.add(menuItem2); + jmenu.addSeparator; + jmenu.add(menuItem3); + jmenu.add(menuItem4); + jmenu.add(menuItem5); + jmenu.add(menuItem6); + end % setTreeContextMenu + + %% Set the mouse-press callback + function treeMousePressedCallback(hTree, eventData, tree_h) %#ok hTree is unused + if eventData.isMetaDown % right-click is like a Meta-button + % Get the clicked node + clickX = eventData.getX; + clickY = eventData.getY; + jtree = eventData.getSource; + treePath = jtree.getPathForLocation(clickX, clickY); + try + % Modify the context menu based on the clicked node + node = treePath.getLastPathComponent; + userdata = get(node,'userdata'); + obj = userdata.obj; + jmenu = setTreeContextMenu(obj,node,tree_h); + + % TODO: remember to call jmenu.remove(item) in item callback + % or use the timer hack shown here to remove the item: + % timerFcn = {@menuRemoveItem,jmenu,item}; + % start(timer('TimerFcn',timerFcn,'StartDelay',0.2)); + + % Display the (possibly-modified) context menu + jmenu.show(jtree, clickX, clickY); + jmenu.repaint; + + % This is for debugging: + userdata.tree = jtree; + setappdata(gcf,'findjobj_hgtree',userdata) + catch + % clicked location is NOT on top of any node + % Note: can also be tested by isempty(treePath) + end + end + end % treeMousePressedCallback + + %% Remove the extra context menu item after display + function menuRemoveItem(hObj,eventData,jmenu,item) %#ok unused + jmenu.remove(item); + end % menuRemoveItem + + %% Get the title for the tooltip and context (right-click) menu + function nodeTitleStr = getNodeTitleStr(obj,node) + try + % Display the full classname and object name in the tooltip + %nodeName = char(node.getName); + %nodeName = strrep(nodeName, '',''); + %nodeName = strrep(nodeName, '',''); + nodeName = char(getNodeName(obj,99)); + [objClass,objName] = strtok(nodeName); + objName = objName(3:end-1); % strip leading ( and trailing ) + if isempty(objName), objName = '(none found)'; end + nodeName = char(node.getName); + objClass = char(obj.getClass.getName); + nodeTitleStr = sprintf('Class name: %s
Text/title: %s',objClass,objName); + + % If the component is invisible, state this in the tooltip + if ~isempty(strfind(nodeName,'color="gray"')) + nodeTitleStr = [nodeTitleStr '
*** Invisible ***']; + end + nodeTitleStr = [nodeTitleStr '
Right-click for context-menu']; + catch + % Possible not a Java object - try treating as an HG handle + try + handleValueStr = sprintf('#: %.99g',double(obj)); + try + type = ''; + type = get(obj,'type'); + type(1) = upper(type(1)); + catch + if ~ishandle(obj) + type = ['Invalid ' char(node.getName) '']; + handleValueStr = '!!!
Perhaps this handle was deleted after this UIInspect tree was
already drawn. Try to refresh by selecting any valid node handle'; + end + end + nodeTitleStr = sprintf('%s handle %s',type,handleValueStr); + try + % If the component is invisible, state this in the tooltip + if strcmp(get(obj,'Visible'),'off') + nodeTitleStr = [nodeTitleStr '
Invisible']; + end + catch + % never mind... + end + catch + % never mind... - ignore + end + end + end % getNodeTitleStr + + %% Handle tree mouse movement callback - used to set the tooltip & context-menu + function treeMouseMovedCallback(hTree, eventData) + try + x = eventData.getX; + y = eventData.getY; + jtree = eventData.getSource; + treePath = jtree.getPathForLocation(x, y); + try + % Set the tooltip string based on the hovered node + node = treePath.getLastPathComponent; + userdata = get(node,'userdata'); + obj = userdata.obj; + tooltipStr = getNodeTitleStr(obj,node); + set(hTree,'ToolTipText',tooltipStr) + catch + % clicked location is NOT on top of any node + % Note: can also be tested by isempty(treePath) + end + catch + dispError; + end + return; % denug breakpoint + end % treeMouseMovedCallback + + %% Request focus for a specific object handle + function requestFocus(hTree, eventData, obj) %#ok hTree & eventData are unused + % Ensure the object handle is valid + if isjava(obj) + obj.requestFocus; + return; + elseif ~ishandle(obj) + msgbox('The selected object does not appear to be a valid handle as defined by the ishandle() function. Perhaps this object was deleted after this hierarchy tree was already drawn. Refresh this tree by selecting a valid node handle and then retry.','FindJObj','warn'); + beep; + return; + end + + try + foundFlag = 0; + while ~foundFlag + if isempty(obj), return; end % sanity check + type = get(obj,'type'); + obj = double(obj); + foundFlag = any(strcmp(type,{'figure','axes','uicontrol'})); + if ~foundFlag + obj = get(obj,'Parent'); + end + end + feval(type,obj); + catch + % never mind... + dispError; + end + end % requestFocus + + +end % FINDJOBJ + + +%% TODO TODO TODO +%{ +- Enh: Improve performance - esp. expandNode() (performance solved in non-nteractive mode) +- Enh: Add property listeners - same problem in MathWork's inspect.m +- Enh: Display additional properties - same problem in MathWork's inspect.m +- Enh: Add axis (plot, Graphics) component handles +- Enh: Group callbacks according to the first word (up to 2nd cap letter) +- Enh: Add figure thumbnail image below the java tree (& indicate corresponding jObject when selected) +- Enh: scroll initially-selected node into view (problem because treenode has no pixel location) +- Fix: java exceptions when getting some fields (com.mathworks.mlwidgets.inspector.PropertyRootNode$PropertyListener$1$1.run) +- Fix: use EDT if available (especially in flashComponent) +%} \ No newline at end of file diff --git a/3rdparty/jewelersLoupe.m b/3rdparty/jewelersLoupe.m new file mode 100644 index 0000000..dc222c2 --- /dev/null +++ b/3rdparty/jewelersLoupe.m @@ -0,0 +1,122 @@ +function jewelersLoupe(hFig) +%JEWELERSLOUPE modifies a figure to enable a Jeweler's Loupe tool. +% JEWELERSLOUPE(FIG) will modify the figure with handle FIG to allow a +% jeweler's loupe tool, which magnifies the axes by a factor of 4, to +% appear whenever a user clicks on an axes. Dragging will cause the loupe +% to follow the cursor. The tool assumes that any axes clicked will be a +% linear 2-D axes. Performance will degrade as the amount of data in the +% axes increates. If no figure is given, the loupe will appear in the +% current figure. +% +% Example: +% plot(magic(3)); +% jewelersLoupe(gcf); +% +% See also PAN, ZOOM. + +% Copyright 2008 The MathWorks, Inc. + +% If a figure has not been passed in, +if nargin < 1 + hFig = gcf; +end + +% Make sure we have a valid figure: +if ~ishandle(hFig) || ~strcmpi(get(hFig,'Type'),'figure') + error('JEWELERSLOUPE:INVALIDFIGURE',... + 'The first input argument must be a valid figure.'); +end + +% Set up the callback for the loupe: +set(hFig,'WindowButtonDownFcn',@localCreateLoupe); + +%------------------------------------------------------------------------% +function localCreateLoupe(hFig,evd) %#ok +% The loupe will appear for the current axes. +hAx = get(hFig,'CurrentAxes'); + +% If the axes is empty, we won't do anything. +if isempty(hAx) + return; +end + +% The loupe is implemented in terms of a separate axes: +hLoupe = axes('Parent',hFig,'Tag','loupe','HandleVisibility','off',... + 'XTickLabel','','YTickLabel','','XTick',[],'YTick',[],'Box','on'); +% Set up the initial loupe position: +localUpdateLoupePosition(hFig,hLoupe); + +% Copy the contents of the axes into the loupe: +copyobj(get(hAx,'Children'),hLoupe); + +% We will zoom by a factor of 4 into the loupe based on the current point +% in the axes. +localDoZoom(hLoupe,hAx); + +% Set callbacks for motion and button up: +set(hFig,'WindowButtonMotionFcn',{@localMoveLoupe,hAx,hLoupe}); +set(hFig,'WindowButtonUpFcn',{@localCleanUp,hLoupe}); + +%------------------------------------------------------------------------% +function localMoveLoupe(hFig,evd,hAx,hLoupe) %#ok +% Move the loupe based on the current point: +localUpdateLoupePosition(hFig,hLoupe); + +% Perform the zoom operation on the axes: +localDoZoom(hLoupe,hAx); + +%------------------------------------------------------------------------% +function localCleanUp(hFig,evd,hLoupe) %#ok +% Clean up after the loupe has finished execution: + +% Remove the callbacks: +set(hFig,'WindowButtonMotionFcn',''); +set(hFig,'WindowButtonUpFcn',''); + +% Delete the loupe: +delete(hLoupe); + +%------------------------------------------------------------------------% +function localUpdateLoupePosition(hFig,hLoupe) +% Moves the loupe based on the current point in the figure: + +% The loupe should be centered on the current location and take up 25% of +% the figure space. +oldUnits = get(hFig,'Units'); +set(hFig,'Units','Normalized'); +mousePoint = get(hFig,'CurrentPoint'); +set(hFig,'Units',oldUnits); +loupePosition = [(mousePoint-.125) .25 .25]; +set(hLoupe,'Position',loupePosition); + +%------------------------------------------------------------------------% +function localDoZoom(hLoupe,hAx) +% First, calculate the center based on the current point in the axes: +newCenter = get(hAx,'CurrentPoint'); +% We are assuming a 2-D axes, so we will take the first two coordinates. +newCenter = newCenter(1,1:2); + +% Zoom in by a factor of 4 around the given center point. +center_x = newCenter(1); +center_y = newCenter(2); + +% Start by zooming the X-limits: +origXLim = get(hAx,'XLim'); +xmin = origXLim(1); +xmax = origXLim(2); +dx = diff(origXLim); +newdx = dx * (1/4); +newdx = min(newdx,xmax-xmin); +newXLim = [center_x-newdx/2 center_x+newdx/2]; + +% Next, zoom the Y-limits: +origYLim = get(hAx,'YLim'); +ymin = origYLim(1); +ymax = origYLim(2); +dy = diff(origYLim); +newdy = dy * (1/4); +newdy = min(newdy,ymax-ymin); +newYLim = [center_y-newdy/2 center_y+newdy/2]; + +% Update the limits: +set(hLoupe,'XLim',newXLim,'YLim',newYLim); diff --git a/3rdparty/m2googlevis.m b/3rdparty/m2googlevis.m new file mode 100644 index 0000000..211794a --- /dev/null +++ b/3rdparty/m2googlevis.m @@ -0,0 +1,105 @@ +function htmlStr = m2googlevis(varargin) +%M2GOOGLEVIS creates html code for Google's visualization API +% +% htmlstr = m2googlevis('style','parm1',data1,...,'parmn',datan [,opts]) +% +% Inputs: +% ------- +% 'style' is a string from the following list which will choose the Google +% Visualization API package to use +% 'lineChart' +% 'scatterChart' +% +% 'parm' is a string giving the name of the variable to be plotted +% +% data is a vector (either a cell array of strings or a double) giving the +% corresponding data to be plotted +% +% opts is an optional structure with the following fields: +% legendLocn = location of the legend +% ('bottom','left','top','right','none') +% title = The title of the plot +% +% Outputs: +% -------- +% htmlStr is a a character string which can be saved directly to an HTML +% file for web display + +% title - m2googlevis.m author - Adam Leadbetter (alead@bodc.ac.uk) +% version - 0.2 date - 2008Dec18 +% +% Revisions +% --------- +% 0.2 2008Dec22 Adam Leadbetter - Added scatterChart +% + a = ['''' repmat('%s ', 1, 5) '''']; +% +% Check the invocation +% TODO: Check that all the inputs are of the same length +% + if(isstruct(varargin{end})) + opts = varargin{end}; + varargin(end) = []; + else + opts.legendLocn = 'right'; + opts.title = ''; + end + htmlStr = ['']; + htmlStr = [htmlStr '
']; \ No newline at end of file diff --git a/3rdparty/magnify.m b/3rdparty/magnify.m new file mode 100644 index 0000000..e9350fd --- /dev/null +++ b/3rdparty/magnify.m @@ -0,0 +1,139 @@ +function magnify(f1) +% +%magnify(f1) +% +% Figure creates a magnification box when under the mouse +% position when a button is pressed. Press '+'/'-' while +% button pressed to increase/decrease magnification. Press +% '>'/'<' while button pressed to increase/decrease box size. +% Hold 'Ctrl' while clicking to leave magnification on figure. +% +% Example: +% plot(1:100,randn(1,100),(1:300)/3,rand(1,300)), grid on, +% magnify; + +% Rick Hindman - 7/29/04 + +if (nargin == 0), f1 = gcf; end; +set(f1, ... + 'WindowButtonDownFcn', @ButtonDownCallback, ... + 'WindowButtonUpFcn', @ButtonUpCallback, ... + 'WindowButtonMotionFcn', @ButtonMotionCallback, ... + 'KeyPressFcn', @KeyPressCallback); +return; + +function ButtonDownCallback(src,eventdata) + f1 = src; + a1 = get(f1,'CurrentAxes'); + a2 = copyobj(a1,f1); + + set(f1, ... + 'UserData',[f1,a1,a2], ... + 'Pointer','fullcrosshair', ... + 'CurrentAxes',a2); + set(a2, ... + 'UserData',[2,0.2], ... %magnification, frame size + 'Color',get(a1,'Color'), ... + 'Box','on'); + xlabel(''); ylabel(''); zlabel(''); title(''); + set(get(a2,'Children'), ... + 'LineWidth', 2); + set(a1, ... + 'Color',get(a1,'Color')*0.95); + set(f1, ... + 'CurrentAxes',a1); + ButtonMotionCallback(src); +return; + +function ButtonUpCallback(src,eventdata) + H = get(src,'UserData'); + f1 = H(1); a1 = H(2); a2 = H(3); + set(a1, ... + 'Color',get(a2,'Color')); + set(f1, ... + 'UserData',[], ... + 'Pointer','arrow', ... + 'CurrentAxes',a1); + if ~strcmp(get(f1,'SelectionType'),'alt'), + delete(a2); + end; +return; + +function ButtonMotionCallback(src,eventdata) + H = get(src,'UserData'); + if ~isempty(H) + f1 = H(1); a1 = H(2); a2 = H(3); + a2_param = get(a2,'UserData'); + f_pos = get(f1,'Position'); + a1_pos = get(a1,'Position'); + + [f_cp, a1_cp] = pointer2d(f1,a1); + + set(a2,'Position',[(f_cp./f_pos(3:4)) 0 0]+a2_param(2)*a1_pos(3)*[-1 -1 2 2]); + a2_pos = get(a2,'Position'); + + set(a2,'XLim',a1_cp(1)+(1/a2_param(1))*(a2_pos(3)/a1_pos(3))*diff(get(a1,'XLim'))*[-0.5 0.5]); + set(a2,'YLim',a1_cp(2)+(1/a2_param(1))*(a2_pos(4)/a1_pos(4))*diff(get(a1,'YLim'))*[-0.5 0.5]); + end; +return; + +function KeyPressCallback(src,eventdata) + H = get(gcf,'UserData'); + if ~isempty(H) + f1 = H(1); a1 = H(2); a2 = H(3); + a2_param = get(a2,'UserData'); + if (strcmp(get(f1,'CurrentCharacter'),'+') | strcmp(get(f1,'CurrentCharacter'),'=')) + a2_param(1) = a2_param(1)*1.2; + elseif (strcmp(get(f1,'CurrentCharacter'),'-') | strcmp(get(f1,'CurrentCharacter'),'_')) + a2_param(1) = a2_param(1)/1.2; + elseif (strcmp(get(f1,'CurrentCharacter'),'<') | strcmp(get(f1,'CurrentCharacter'),',')) + a2_param(2) = a2_param(2)/1.2; + elseif (strcmp(get(f1,'CurrentCharacter'),'>') | strcmp(get(f1,'CurrentCharacter'),'.')) + a2_param(2) = a2_param(2)*1.2; + end; + set(a2,'UserData',a2_param); + ButtonMotionCallback(src); + end; +return; + + + +% Included for completeness (usually in own file) +function [fig_pointer_pos, axes_pointer_val] = pointer2d(fig_hndl,axes_hndl) +% +%pointer2d(fig_hndl,axes_hndl) +% +% Returns the coordinates of the pointer (in pixels) +% in the desired figure (fig_hndl) and the coordinates +% in the desired axis (axes coordinates) +% +% Example: +% figure(1), +% hold on, +% for i = 1:1000, +% [figp,axp]=pointer2d; +% plot(axp(1),axp(2),'.','EraseMode','none'); +% drawnow; +% end; +% hold off + +% Rick Hindman - 4/18/01 + +if (nargin == 0), fig_hndl = gcf; axes_hndl = gca; end; +if (nargin == 1), axes_hndl = get(fig_hndl,'CurrentAxes'); end; + +set(fig_hndl,'Units','pixels'); + +pointer_pos = get(0,'PointerLocation'); %pixels {0,0} lower left +fig_pos = get(fig_hndl,'Position'); %pixels {l,b,w,h} + +fig_pointer_pos = pointer_pos - fig_pos([1,2]); +set(fig_hndl,'CurrentPoint',fig_pointer_pos); + +if (isempty(axes_hndl)), + axes_pointer_val = []; +elseif (nargout == 2), + axes_pointer_line = get(axes_hndl,'CurrentPoint'); + axes_pointer_val = sum(axes_pointer_line)/2; +end; + diff --git a/@int32/sparse.m b/@int32/sparse.m new file mode 100644 index 0000000..e26bda1 --- /dev/null +++ b/@int32/sparse.m @@ -0,0 +1,11 @@ +function [varargout]=sparse(varargin) +switch nargin + case 1 + varargout={sparse(double(varargin{1}))}; + case 2 + varargout{1}=sparse(double(varargin{1}),double(varargin{2})); + case 3 + varargout{1}=sparse(double(varargin{1}),double(varargin{2}),double(varargin{3})); + case 5 + varargout{1}=sparse(double(varargin{1}),double(varargin{2}),double(varargin{3}),double(varargin{4}),double(varargin{5})); +end diff --git a/FindConstInMFiles.m b/FindConstInMFiles.m new file mode 100644 index 0000000..a8b2d20 --- /dev/null +++ b/FindConstInMFiles.m @@ -0,0 +1,58 @@ +function FindConstInMFiles +%FindConstInMFiles() finds the contant variables in .m files +% FindConstInMFiles() searches in all the .m files under a chosen directory and +% writes to an output file (results.txt) all the assignments of constant +% values to variables. The core of the programme is the regular expression. +% Everything else is just a recursive algorithm to search all the files. +% Regular expression: [a-zA-Z][\.\w+]*\s*=\s*[\.\d]+([eE][\+\-]?\d+)?\s*[;\n] +% [a-zA-Z] : variables have to start with a letter +% [\.\w+]* : to take into account structure variables +% *\s*=\s* : multiple spaces before and/or after the '=' sign +% [\.\d]+ : number with or without decimal separation +% ([eE][\+\-]?\d+)? : exponential symbol, +/- characters, number +% \s*[;\n] : white-spaces, end of statement, end of line + +clc; +clear; +DirPath = uigetdir(); % Get directory where to search +if ~DirPath, return, end +fid = fopen('results.txt','wt'); % open output file +if fid == -1 + disp('Error creating output file') + return +end +StartFind(DirPath,fid); %start search for files +fclose(fid); % close output file + +%% StartFind +function StartFind(DirPath,fid) +DirContent = dir(DirPath); % gets the content of the current directory +files = {DirContent(~[DirContent.isdir]').name}; % gets only the files +folders = {DirContent([DirContent.isdir]').name}; % gets only the directories +for i = 1:length(files) % for all the files + file = fullfile(DirPath,files{i}); % get the full file name + [pathstr, name, ext] = fileparts(files{i}); % get the extension + if ~strcmp(ext,'.m') % Test for .m extension and continue to next file if it's not + continue + end + FileContent = fileread(file); % read to a string the contents of the files + [str ind] = regexp(FileContent,'[a-zA-Z][\.\w+]*\s*=\s*[\.\d]+([eE][\+\-]?\d+)?\s*[;\n]','match','start'); % regular expression + if ~isempty(str) % if there are valid results... + fprintf(fid,'\n%s\n',file); % Print the file name + endofline = regexp(FileContent,'\n','start'); % Get all the indexes of all the lines + for j = 1:length(str) % for all valid results... + fprintf(fid,'Line %d: %s\n',find(ind(j)0)) +set(hp, 'FaceAlpha', 'interp') +axis image +if NewAxes + view(60,30) +end + +end + + +% Axial View -------------------------------- +if get(mriHandles.axial3D,'value') + + X = linspace(0,MRI.FOV(1),size(MRI.Cube,1)); + Y = linspace(0,MRI.FOV(2),size(MRI.Cube,2)); + Z = mriCoord(3)*ones(length(X), length(Y)); + hhh = surf(X,Y,Z); + set(hhh,'visible','off') + %set(hhh,'visible','on','Cdata',double(MRI.Cube(:,:,newIndices(3)))','edgecolor','none') + %hold on + x = get(hhh,'xdata'); + y = get(hhh,'ydata'); + X = repmat(x,length(y),1); + Y = repmat(y',1,length(x)); + x = X(:); y = Y(:); z = Z(:); + + [transf,scsCoord] = mri2scs(MRI,[x,y,z]'); + scsCoord = scsCoord/1000; + + X = (reshape(scsCoord(1,:),size(MRI.Cube,1),size(MRI.Cube,2))); + Y = (reshape(scsCoord(2,:),size(MRI.Cube,1),size(MRI.Cube,2))); + Z = (reshape(scsCoord(3,:),size(MRI.Cube,1),size(MRI.Cube,2))); + % figure, hold on + hhh = surf(X,Y,Z); + set(hhh,'visible','off') + + if isempty(mriImages.Axial) % Fisrt time 3D visualization of MR slices was called + figure(h3D), hold on + mriImages.Axial = surf(X,Y,Z); + %setappdata(h3D,'mriImages',mriImages) + else + mriImages = getappdata(h3D,'mriImages'); + end + + CData = double(MRI.Cube(:,:,newIndices(3)))'; + CData = CData/max(CData(:)); + CData(CData < (min(CData(:))+1*std(CData(:)))) = NaN; + set(mriImages.Axial,'Xdata',X,'Ydata',Y,'ZData',Z,'Cdata',CData,'edgecolor','none','facelighting','none') + +else + if ~isempty(mriImages.Axial) % Remove from visualization + delete(mriImages.Axial) + mriImages.Axial = []; + end +end + +% Sagittal View -------------------------------- +if get(mriHandles.sagittal3D,'value') + + Y = linspace(0,MRI.FOV(2),size(MRI.Cube,2)); + Z = linspace(0,MRI.FOV(3),size(MRI.Cube,3)); + X = mriCoord(1)*ones(length(Y), length(Z)); + + hhh = surf(Y,Z,X); + set(hhh,'visible','off') + %set(hhh,'visible','on','Cdata',double(MRI.Cube(:,:,newIndices(3)))','edgecolor','none') + %hold on + y = get(hhh,'xdata'); + z = get(hhh,'ydata'); + Y = repmat(y,length(z),1); + Z = repmat(z',1,length(y)); + x = X(:); y = Y(:); z = Z(:); + + [transf,scsCoord] = mri2scs(MRI,[x,y,z]'); + scsCoord = scsCoord/1000; + + X = (reshape(scsCoord(1,:),size(MRI.Cube,2),size(MRI.Cube,3))); + Y = (reshape(scsCoord(2,:),size(MRI.Cube,2),size(MRI.Cube,3))); + Z = (reshape(scsCoord(3,:),size(MRI.Cube,2),size(MRI.Cube,3))); + % figure, hold on + hhh = surf(X,Y,Z); + set(hhh,'visible','off') + + if isempty(mriImages.Sagittal) % Fisrt time 3D visualization of MR slices was called + figure(h3D), hold on + mriImages.Sagittal = surf(X,Y,Z); + %setappdata(h3D,'mriImages',mriImages) + else + mriImages = getappdata(h3D,'mriImages'); + end + CData = double(squeeze(MRI.Cube(newIndices(1),:,:)))'; + CData = CData/max(CData(:)); + CData(CData < (min(CData(:))+1*std(CData(:)))) = NaN; + set(mriImages.Sagittal,'Xdata',X,'Ydata',Y,'ZData',Z,'Cdata',CData,'edgecolor','none','facelighting','none') + +else + if ~isempty(mriImages.Sagittal) % Remove from visualization + delete(mriImages.Sagittal) + mriImages.Sagittal = []; + end +end + + +% Coronal View -------------------------------- +if get(mriHandles.coronal3D,'value') + + X = linspace(0,MRI.FOV(1),size(MRI.Cube,1)); + Z = linspace(0,MRI.FOV(3),size(MRI.Cube,3)); + Y = mriCoord(2)*ones(length(X), length(Z)); + + hhh = surf(X,Z,Y); + set(hhh,'visible','off') + %set(hhh,'visible','on','Cdata',double(MRI.Cube(:,:,newIndices(3)))','edgecolor','none') + %hold on + x = get(hhh,'xdata'); + z = get(hhh,'ydata'); + X = repmat(x,length(z),1); + Z = repmat(z',1,length(x)); + x = X(:); y = Y(:); z = Z(:); + + [transf,scsCoord] = mri2scs(MRI,[x,y,z]'); + scsCoord = scsCoord/1000; + + X = (reshape(scsCoord(1,:),size(MRI.Cube,2),size(MRI.Cube,3))); + Y = (reshape(scsCoord(2,:),size(MRI.Cube,2),size(MRI.Cube,3))); + Z = (reshape(scsCoord(3,:),size(MRI.Cube,2),size(MRI.Cube,3))); + % figure, hold on + hhh = surf(X,Y,Z); + set(hhh,'visible','off') + + if isempty(mriImages.Coronal) % Fisrt time 3D visualization of MR slices was called + figure(h3D), hold on + mriImages.Coronal = surf(X,Y,Z); + %setappdata(h3D,'mriImages',mriImages) + else + mriImages = getappdata(h3D,'mriImages'); + end + + CData = double(squeeze(MRI.Cube(:,newIndices(2),:)))'; + CData = CData/max(CData(:)); + CData(CData < (min(CData(:))+1*std(CData(:)))) = NaN; + set(mriImages.Coronal,'Xdata',X,'Ydata',Y,'ZData',Z,'Cdata',CData,'edgecolor','none','facelighting','none') + + +else + if ~isempty(mriImages.Coronal) % Remove from visualization + delete(mriImages.Coronal) + mriImages.Coronal = []; + end +end +set(h3D, 'colormap',bone(ngray)) diff --git a/Plot2dHist.m b/Plot2dHist.m new file mode 100644 index 0000000..f388cf9 --- /dev/null +++ b/Plot2dHist.m @@ -0,0 +1,37 @@ +%function Plot2dHist(mHist2d, vEdgeX, vEdgeY, strLabelX, strLabelY, strTitle) +%Shows 2d histogram in p-color map +%Example +% mYX = rand(10000,2); +% vXEdge = linspace(0,1,100); +% vYEdge = linspace(0,1,200); +% mHist2d = hist2d(mYX,vYEdge,vXEdge); +% +% Plot2dHist(mHist2d, vXEdge, vYEdge, 'X', 'Y', 'Example'); colorbar +function Plot2dHist(mHist2D, vEdgeX, vEdgeY, strLabelX, strLabelY, strTitle) + +nEdgeX = length(vEdgeX)-1; +nEdgeY = length(vEdgeY)-1; + +rMinX = min(vEdgeX); +rMaxX = max(vEdgeX); + +rMinY = min(vEdgeY); +rMaxY = max(vEdgeY); + +rDeltaX = (vEdgeX(2)-vEdgeX(1)); +rDeltaY = (vEdgeY(2)-vEdgeY(1)); + +vLabelX = (rMinX+0.5*rDeltaX):rDeltaX:(rMaxX-0.5*rDeltaX); +vLabelY = (rMinY+0.5*rDeltaY):rDeltaY:(rMaxY-0.5*rDeltaY); + +pcolor (vLabelX, vLabelY, mHist2D); +shading interp; colorbar; + +grid on; + +xlabel(strLabelX); +ylabel(strLabelY); + +axis([rMinX, rMaxX, rMinY, rMaxY]); + +title(strTitle) diff --git a/Tr2event.m b/Tr2event.m new file mode 100644 index 0000000..57c8e19 --- /dev/null +++ b/Tr2event.m @@ -0,0 +1,35 @@ +function [event]=Tr2event(Tr) + +disp('Calculating events...') +for i=1:Tr(1).nb + event(i).ref=Tr(1).a(i); + event(i).tstim=Tr(2).t(i); + event(i).tresp=0; + event(i).choice=0; + event(i).bad=1; %Eventually BAD, if not answered +end + +% Scanning responses +for i=1:Tr(10).nb + j=1+Tr(10).a(i); + event(j).tresp=Tr(10).t(i); + event(j).bad=0; % Not a bad one +end + + +% Which stim type was shown ? +for k=5:9 +for i=1:Tr(k).nb +j=1+Tr(k).a(i); +event(j).type=k; +event(j).length=k*.105-0.035; +end +end + +% Which answer was given ? +for k=3:4 +for i=1:Tr(k).nb +event(1+Tr(k).a(i)).choice=7-2*k; % No response = 0 ; ok = +1 ; no = -1 +end +end + diff --git a/WhatsThisImage.m b/WhatsThisImage.m new file mode 100644 index 0000000..9aa5be3 --- /dev/null +++ b/WhatsThisImage.m @@ -0,0 +1,74 @@ +function [html] = WhatsThisImage(filename,keywords,varargin) +%WHATSTHISIMAGE - Try to find a amtching image on the web +% [] = WhatsThisImage(filename) +% +% Example +% >> WhatsThisImage +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2009 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2009-03-02 Creation +% +% ----------------------------- Script History --------------------------------- + +filename = 'http://tbn2.google.com/images?q=tbn:PkiyfXuSo4dVIM:http://www.simpsonstrivia.com.ar/simpsons-photos/wallpapers/homer-simpson-wallpaper-brain-1024.jpg'; +keywords = 'brain' + +query = 'http://images.google.com/images?'; +params.q=keywords; +params.language = []; +params.tab = 'wi'; + +for f=fieldnames(params)' + if isfield(params, f{1}) && ~isempty(params.(f{1})) + query = [ query '&' f{1} '=' params.(f{1}) ]; + end +end +query +start=0 +for ipage = 1:2 + html = urlread(query); +end + +javaaddpath(fullfile(pwd,'googleapi','googleapi.jar')) + +%key = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'; +setpref('google','key',key); + + +% Setup. +googleSearch = com.google.soap.search.GoogleSearch; +key = getpref('google','key',''); +if isempty(key) + error(sprintf('%s\n%s', ... +'No key set. You need to get an access key from Google and set it like this:', ... +'setpref(''google'',''key'',''xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'');')); +end +googleSearch.setKey(key); +googleSearch.setQueryString(q); +googleSearch.setFilter(filter); + + + +function score = matching(im1,im2) +% Brutye foprce approach : pixel-based correlation in greyscale... +score = 0; +im1=normimage(im1); +im2=normimage(im2); +if ~isequal(size(im1),size(im2)) + return +end + + +sc = corrcoef(im1,im2) + +function x = normimage(im) +x = normalize(mean(double(im),3), 'unity', [1:2]); diff --git a/WilcoxonSRPVs.m b/WilcoxonSRPVs.m new file mode 100644 index 0000000..1476ca3 --- /dev/null +++ b/WilcoxonSRPVs.m @@ -0,0 +1,104 @@ +function [T,pvl,pvr,pvz,R,supp,cdf] = WilcoxonSRPVs(Z,Y); +% [T,pvl,pvr,pvzR,supp,cdf] = WilcoxonSRPVs(Z) +% computes for a data vector Z Wilcoxon's signed-rank +% test statistic +% T = sum_{i=1}^n sign(Z(i)) R(i) , +% where R(i) denotes the rank of |Z(i)| among the non-zero +% components of (|Z(1)|, |Z(2)|, |Z(3)|, ..., |Z(n)|). +% +% In case of Z(i) = 0 we set R(i) = 0, i.e. components equal +% to zero are ignored. +% +% In case of ties among the absolute values of Z's components, +% we use the usual convention of mean ranks. +% +% In case of two input arguments, say X and Y, the program +% WilcoxonSRPVs(X,Y) works with Z := X - Y. +% +% In addition we compute left-, right- and two-sided P-values +% pvl = Pr(T_o <= T) , +% pvr = Pr(T_o >= T) , +% pvz = 2 * min(pvl, pvr) . +% Here T_o = sum_{i=1}^n S(i)*R(i) with independent random signs +% S(1), S(2), ..., S(n), while R is viewed temporarily as a fixed +% vector. +% +% Additional output arguments are +% - supp : a vector containing all support points of the +% distribution function F(.) := Pr(T_o <= .), +% - cdf : a vector containing the numbers cdf(i) = F(supp(i)). +% +% Lutz Duembgen, March 26, 2003 + +if nargin == 2 + Z = Z - Y; +end + +R = abs(Z); +S = sign(Z); +JJ = find(R > 0); +R(JJ) = LocalRanks(R(JJ)); +T = sum(sign(Z).*R); + +N = length(JJ); +R2 = ceil(2*sort(R(JJ))); +h2 = N*(N+1); +h1 = ceil(h2/2); +% Now we work temporarily with the test statistic +% T2 = T + N(N+1)/2 +% = sum_{i=1}^N 1{Z(i) > 0} 2R(i) +% taking values in {0,1,...,N(N+1)}: +cdf = ones(1,h2+1); +m = 1; +for i=1:N + m_new = m+R2(i); + cdf(R2(i)+1:m_new) = (cdf(1:m) + cdf(R2(i)+1:m_new))/2; + cdf(1:R2(i)) = cdf(1:R2(i))/2; + m = m_new; +end + +if nargout >= 6 + supp = [0:h2] - h1; +end + +ind = h1 + T + 1; +pvl = cdf(ind); +if ind >= 2 + pvr = 1 - cdf(ind-1); +else + pvr = 1; +end +pvz = 2*min(pvl,pvr); + +if nargout==0 + fprintf(1,'Test statistic : %5.1f\n', T) + fprintf(1,'(standardized : %6.3f)\n', T/norm(R)) + fprintf(1,'Left-sided P-value : %6.4f\n', pvl) + fprintf(1,'Right-sided P-Value : %6.4f\n', pvr) + fprintf(1,'Two-sided P-Value : %6.4f\n', pvz) +end +return + + +function rv = LocalRanks(x); +% rv = LocalRanks(x) +% computes the vector rv of ranks of x with the usual +% convention of averaging ranks in case of ties. +% +% Lutz Duembgen, 23.02.1999 + +n = length(x); +rv = zeros(size(x)); + +[hv,ar] = sort(x); + +a = 1; +for b=2:n + if hv(b) > hv(a) + hv(a:b-1) = (a+b-1)/2; + a = b; + end; +end; +hv(a:n) = (a+n)/2; +rv(ar) = hv; +return diff --git a/absolutepath.m b/absolutepath.m new file mode 100644 index 0000000..1a07179 --- /dev/null +++ b/absolutepath.m @@ -0,0 +1,156 @@ +function [a] = absolutepath(r,p,c) +%ABSOLUTEPATH() - Returns the absolute path +% +% a = absolutepath(r,p) returns a char array a giving absolute path of r +% (relatively to path p, if not given relatively to current path ) +% +% a = absolutepath(r,p,c) indicates the computer type to work in +% c can be 'PCWIN'|'GLNX86' (see: computer). Default is to use current +% environment. +% If [c] == 1, absolutepath will try to move in the resulting path [a] so +% as to clean of any relative (e.g., '..') and symbolic links in [a] +if nargin < 3 + c = computer; +end +if nargin < 2 + p = cd; +end +a=p; +if nargin<1 + return +end + +if ispc + % Windows is not case-sensitive! + p=lower(p); + r=lower(r); +end +if isabsolute(r,c) + % if nargin < 2 + warning('Input path is already an absolute path: %s', r) + % else + % error('Cannot set r relatively to path p for it is already an absolute path: %s', r) + % end + a = r; +else + if isequal(c, 'PCWIN') || (isequal(c,1) && ispc) + filesep = '\'; + if length(r)>1 && isequal(r(1), filesep) + % In windows '\bla' is ambiguous about the drive + if isequal(p(2), ':') + p=p(1:2); + else isequal(p(1:2), [filesep filesep]) + p=[ filesep filesep strtok(p, filesep)]; + end + end + else + a=fullfile(p,r); +end + +if isequal(c,1) + lcd=cd; + cd(a); + a=cd; + cd(lcd); +end + +return + +function tf = isabsolute(p,c) +% Test if path p is an absolute path in the current environment +tf = true; +if isequal(c,1) + c = computer; +end +switch c + case 'GLNX86' + filesep = '/'; + if ~isempty(p) && ( isequal(p(1), filesep) || isequal(p(1), '~')) + % In Unix /bla is absolute as well as '~/bla' + % (whereas in Windows \bla doesn't specify the drive) + return; + end + case 'PCWIN' + filesep = '\'; + if length(p)>1 && ... + ( isequal(p(1:2),[ filesep filesep ]) || ... + isequal(p(1:2),[ ':' filesep ]) ) + % C:\bla\... or \\network\... schemes in windows + return; + end +end +tf=false; +return + + +if ispc + if length(p)>0 && isequal(p(1), filesep) + p = strtok(cd, filesep); + end +end + + + +if ispc + if length(p)>1 && isequal(p(2), ':') + + elseif length(p)>1 && isequal(p(2), filesep) + % network location + + end + +end + +% Predefine return string: +abs_path = ''; + +% Make sure strings end by a filesep character: +if length(act_path) == 0 | ~isequal(act_path(end),filesep) + act_path = [act_path filesep]; +end +if length(rel_path) == 0 | ~isequal(rel_path(end),filesep) + rel_path = [rel_path filesep]; +end + +% Convert to all lowercase: +[act_path] = fileparts( lower(act_path) ); +[rel_path] = fileparts( lower(rel_path) ); + +% Create a cell-array containing the directory levels: +act_path_cell = pathparts(act_path); +rel_path_cell = pathparts(rel_path); +abs_path_cell = act_path_cell; + +% Combine both paths level by level: +while length(rel_path_cell) > 0 + if isequal( rel_path_cell{1} , '.' ) + rel_path_cell( 1) = []; + elseif isequal( rel_path_cell{1} , '..' ) + abs_path_cell(end) = []; + rel_path_cell( 1) = []; + else + abs_path_cell{end+1} = rel_path_cell{1}; + rel_path_cell(1) = []; + end +end + +% Put cell array into string: +for i = 1 : length(abs_path_cell) + abs_path = [abs_path abs_path_cell{i} filesep]; +end + +return + +% ------------------------------------------------- + +function path_cell = pathparts(path_str) + +path_str = [filesep path_str filesep]; +path_cell = {}; + +sep_pos = findstr( path_str, filesep ); +for i = 1 : length(sep_pos)-1 + path_cell{i} = path_str( sep_pos(i)+1 : sep_pos(i+1)-1 ); +end + +return \ No newline at end of file diff --git a/affinetransform.m b/affinetransform.m new file mode 100644 index 0000000..3a47d81 --- /dev/null +++ b/affinetransform.m @@ -0,0 +1,72 @@ +function [varargout]=affinetransform(x,varargin) +% affinetransform - Apply affine transformation to 3D points +% [y,m,m2]=affinetransform(x,m) +% [y,m,m2]=affinetransform(x,r,t) +% [x,m,m2]=affinetransform(y,-1,m) or affinetransform(y,-1,r,t) +% [m,m2]=affinetransform([],r,t) +% x and y are the Nx3 coordinates +% r is the TRANSPOSED rotation matrix (see infra) or the rotation vector: [ux uy uz angle] +% t is the transposed (i.e. horizontal) translation vector +% m is the rotation/translation matrix: m = [r ; t] = [R' ; T'] +% m2 is the inverse transformation: m2 = [r'; -t*r'] +% the [-1] flag will apply the inverse transfo m2 (see infra) +% +% The classical formula for vertical vectors: +% Y = R * X + T +% is simplified into the following: +% y = [x 1]*m = [x*r]+t = [X'*R]+T' +% with: +% y and x are N-by-3 matrices (i.e. horizontal vectors) +% r = R' +% t = T' +% m = [r ; t] = [R T]' +% +% The [-1] flag apply the inverse transformation so that: +% X = R^-1 * ( Y - T) +% i.e: +% x = r' * [y' - t'] + +ninput=nargin-1; +if isequal(varargin{1}, -1) + InverseTransfo = 1; + varargin(1)=[]; + ninput=ninput-1; +else + InverseTransfo = 0; +end + +switch ninput + case 1 + m=varargin{1}; + r=m(1:3,:); + t=m(4,:); + case 2 + if isequal(size(varargin{1}), [1 4]) + u=varargin{1}; + cosa = cos(u(4)); + sina = sin(u(4)); + vera = 1 - cosa; + x = u(1); y = u(2); z = u(3); + r = [cosa+x^2*vera , x*y*vera-z*sina , x*z*vera+y*sina; ... + x*y*vera+z*sina , cosa+y^2*vera , y*z*vera-x*sina; ... + x*z*vera-y*sina , y*z*vera+x*sina , cosa+z^2*vera]'; + else + r=varargin{1}; + end + t = varargin{2}; + + otherwise + error('Invalid number of inputs') +end +m=[r ; t]; +m2 = [r'; -t*r']; +if isempty(x) + varargout={m,m2}; + return +end +if InverseTransfo + y=[x ones(size(x,1),1)]*m2; +else + y=[x ones(size(x,1),1)]*m; +end +varargout={y,m,m2}; \ No newline at end of file diff --git a/allhandles.m b/allhandles.m new file mode 100644 index 0000000..11f4f80 --- /dev/null +++ b/allhandles.m @@ -0,0 +1,47 @@ +function [H,exclude] = allhandles(A,varargin) +%ALLHANDLES - Handles of existing objects only +% [H] = allhandles(A) +% Returns in vector H only handles to existing objects from A +% +% [H,exclude] = allhandles(A, 'Property', Value, ...) +% Retrieves only those handles whose Property/ies match the Value(s) + +% Example +% >> allhandles(0:10) +% >> allhandles(findobj(0), 'type', 'line') +% +% See also: ishandle, nonzeros + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2009 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2009-10-22 Creation +% +% ----------------------------- Script History --------------------------------- + +% to do: varargin : only handles matching some criterion. +% eg. (tag, 'something') or ('visible', 'on') + +H=A(ishandle(A)); +exclude = []; +if nargin>1 + exclude=zeros(size(H)); + for i=1:numel(H) + for j=1:2:(nargin-1) + try + if ~isequal(get(H(i),varargin{j}), varargin{j+1}) + exclude(i) = j; + break + end + catch + exclude(i)=-1; + end + end + end + H=H(~exclude); +end \ No newline at end of file diff --git a/allthesame.m b/allthesame.m new file mode 100644 index 0000000..60843ac --- /dev/null +++ b/allthesame.m @@ -0,0 +1,60 @@ +function tf = allthesame(A,dim) +%ALLTHESAME - True if all elements are identical +% [TF] = allthesame(A) +% [TF] = allthesame(A,dim) +% Works only on numerical/char array (not cells) +% +% Example +% >> allthesame +% +% See also: unique, nonunique + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2009 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2009-04-10 Creation +% +% ----------------------------- Script History --------------------------------- + +s=size(A); +if nargin<2 + dim=[]; +end +if isempty(dim) + if sum(s)==max(s) + [ign,dim]=max(s); + else + dim=1; + end +end +if dim>1 + p=[dim setdiff(1:ndims(A),dim)]; + A=permute(A,p); + s=[s(p) 1]; +end +if iscell(A) + ncol=prod(s(2:end)); + tf = logical(zeros(s(1)-1,ncol)); + for j=1:ncol + for i=2:s(1) + tf(i-1,j)=isequal(A(1,j),A(i,j)); + if ~tf(i-1,j) + % don't explore the remaining lines for this column + break; + end + end + end +else + tf = A(2:end,:)==repmat(A(1,:),s(1)-1,1); +end +tf=all(tf,1); +tf=reshape(tf, [1 s(2:end)]); +if dim>1 + tf=ipermute(tf,p); +end +return diff --git a/allunique.m b/allunique.m new file mode 100644 index 0000000..0fc9a85 --- /dev/null +++ b/allunique.m @@ -0,0 +1,31 @@ +function [T,I,U] = allunique(X) +%ALLUNIQUE - Tests if all elements are unique (no duplicate) +% [T,I,U] = allunique(X) +% +% Inputs: +% X: matrix/vector +% Outputs: +% T: is 1 if there is no duplicated element in X +% I: indices of the non-unique elements +% U: list of unique elements: U =unique(X) +% Example +% >> allunique +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2008 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2008-07-09 Creation +% +% ----------------------------- Script History --------------------------------- +U=unique(X(:)); +T=numel(U) == numel(X); +if nargout>0 + I = find(~ismember(U,X(:))); +end \ No newline at end of file diff --git a/aprime.m b/aprime.m new file mode 100644 index 0000000..f7b2282 --- /dev/null +++ b/aprime.m @@ -0,0 +1,150 @@ +function [A,U,Z] = aprime(X1,X2,dim) +%APRIME - Compute A' (A-prime), i.e. non-parametric under ROC curve +% [] = aprime(X1,X2[,dim]) +% Default dim = 1 +% Example +% >> aprime +% +% See also: +% Reference: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2009 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2009-04-09 Creation +% +% ----------------------------- Script History --------------------------------- +if nargin<3 + dim = 1; +end +s=[ size(X1) ; size(X2) ]; +n1=s(1,dim); +n2=s(2,dim); +ranks = tiedrank(cat(dim,X1,X2),dim); +n1n2 = n1.*n2; +A = (sum(ranks(1:n1,:),dim)-n1*(n1+1)/2)/n1n2; +if nargout > 2 + U =A*n1n2; + Z = (U-n1*(n1+n2+1)/2)/sqrt(n1n2*(n1+n2+1)/12); +end +return + +function [r, tieadj] = tiedrank(x, tieflag, bidirectional) +%TIEDRANK Compute the ranks of a sample, adjusting for ties. +% [R, TIEADJ] = TIEDRANK(X) computes the ranks of the values in the +% vector X. If any X values are tied, TIEDRANK computes their average +% rank. The return value TIEADJ is an adjustment for ties required by +% the nonparametric tests SIGNRANK and RANKSUM, and for the computation +% of Spearman's rank correlation. +% +% [R, TIEADJ] = TIEDRANK(X,1) computes the ranks of the values in the +% vector X. TIEADJ is a vector of three adjustments for ties required +% in the computation of Kendall's tau. TIEDRANK(X,0) is the same as +% TIEDRANK(X). +% +% [R, TIEADJ] = TIEDRANK(X,0,1) computes the ranks from each end, so +% that the smallest and largest values get rank 1, the next smallest +% and largest get rank 2, etc. These ranks are used for the +% Ansari-Bradley test. +% +% See also ANSARIBRADLEY, CORR, PARTIALCORR, RANKSUM, SIGNRANK. + +% Copyright 1993-2005 The MathWorks, Inc. +% $Revision: 1.5.2.6 $ $Date: 2005/07/29 11:42:04 $ + +if nargin < 2 + tieflag = false; +end +if nargin < 3 + bidirectional = 0; +end + +if isvector(x) + [r,tieadj] = tr(x,tieflag,bidirectional); +else + if isa(x,'single') + outclass = 'single'; + else + outclass = 'double'; + end + + % Operate on each column vector of the input (possibly > 2 dimensional) + sz = size(x); + ncols = sz(2:end); % for 2x3x4, ncols will be [3 4] + r = zeros(sz,outclass); + if tieflag + tieadj = zeros([3,ncols],outclass); + else + tieadj = zeros([1,ncols],outclass); + end + for j=1:prod(ncols) + [r(:,j),tieadj(:,j)] = tr(x(:,j),tieflag,bidirectional); + end +end + +% -------------------------------- +function [r,tieadj] = tr(x,tieflag,bidirectional) +%TR Local tiedrank function to compute results for one column + +% Sort, then leave the NaNs (which are sorted to the end) alone +[sx, rowidx] = sort(x(:)); +numNaNs = sum(isnan(x)); +xLen = numel(x) - numNaNs; + +if bidirectional + % Use ranks counting from both low and high ends + if mod(xLen,2)==0 + ranks = [(1:xLen/2), (xLen/2:-1:1), NaN(1,numNaNs)]'; + else + ranks = [(1:(xLen+1)/2), ((xLen-1)/2:-1:1), NaN(1,numNaNs)]'; + end +else + % Use ranks counting from low end + ranks = [1:xLen NaN(1,numNaNs)]'; +end + +if tieflag + tieadj = [0; 0; 0]; +else + tieadj = 0; +end +if isa(x,'single') + ranks = single(ranks); + tieadj = single(tieadj); +end + +% Adjust for ties. Avoid using diff(sx) here in case there are infs. +ties = (sx(1:xLen-1) == sx(2:xLen)); +tieloc = [find(ties); xLen+2]; +maxTies = numel(tieloc); + +tiecount = 1; +while (tiecount < maxTies) + tiestart = tieloc(tiecount); + ntied = 2; + while(tieloc(tiecount+1) == tieloc(tiecount)+1) + tiecount = tiecount+1; + ntied = ntied+1; + end + + if tieflag + n2minusn = ntied*(ntied-1); + tieadj = tieadj + [n2minusn/2; n2minusn*(ntied-2); n2minusn*(2*ntied+5)]; + else + tieadj = tieadj + ntied*(ntied-1)*(ntied+1)/2; + end + + % Compute mean of tied ranks + ranks(tiestart:tiestart+ntied-1) = ... + sum(ranks(tiestart:tiestart+ntied-1)) / ntied; + tiecount = tiecount + 1; +end + +% Broadcast the ranks back out, including NaN where required. +r(rowidx) = ranks; +r = reshape(r,size(x)); diff --git a/argouts.m b/argouts.m new file mode 100644 index 0000000..a6d2e30 --- /dev/null +++ b/argouts.m @@ -0,0 +1,13 @@ +function [X] = argouts(f,N) +% argouts - retrieves N outputs from an evaluated expression f into a cell array +% [X]=argouts(f,N) +% +% Useful if outputs of f depends on the number of argout, e.g. +% E.g., [x{1},x{2}] = find(rand(2)>.5) +% is not the same as: x = {find(rand(2)>.5)} +% but (tada!) can be written: x = argouts('find(rand(2)>.5)',2) + +ev=sprintf('X{%d},', 1:N); +ev(end)=[]; +ev=[ '[' ev '] = ' f ';']; +eval(ev); diff --git a/art/rectpix.m b/art/rectpix.m new file mode 100644 index 0000000..392ed1d --- /dev/null +++ b/art/rectpix.m @@ -0,0 +1,18 @@ +function [z]=rectpix(fg, bgz, angle) +angle=-angle; +bgz = bgz-1; +[x,y]=meshgrid(-bgz(1)/2:bgz(1)/2 , -bgz(2)/2:bgz(2)/2); +ca=cos(angle); +sa=sin(angle); + +ca(abs(ca).1 + imshow(hh([1:sq(1)]+(ih-1)*sq(1),[1:sq(2)]+(iv-1)*sq(2))) + title(num2str(sh(ih,iv))) + pause + end + end +end + + +figure(2); +colormap(gray) +subplot(1,2,1); imagesc(sh); axis image; axis off +subplot(1,2,2); imagesc(hh); axis image; axis off +imwrite(hh, 'test.jpg') \ No newline at end of file diff --git a/art/test.jpg b/art/test.jpg new file mode 100644 index 0000000000000000000000000000000000000000..460e4be9c051017b1ca825ef884d0d66de3161b9 GIT binary patch literal 99700 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

lUh3Tz)ACM|3mL=_g~GuzC#&c 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 MAz)O^a0SH|00z@ROaK4? literal 0 HcmV?d00001 diff --git a/brainstorm3/bst_batch.m b/brainstorm3/bst_batch.m new file mode 100644 index 0000000..4702a8d --- /dev/null +++ b/brainstorm3/bst_batch.m @@ -0,0 +1,1276 @@ +function bst_batch(OPTIONS) +% BST_BATCH: Apply a process defined with panel_statRun, to a list a file. +% +% USAGE: bst_batch('CreatePanel', OPTIONS) +% +% INPUTS: (OPTIONS structure) +% - Conditions : Structure describing the files to process (from panel_stat and panel_process) +% - sProcess : Process to apply (defined in panel_statRun/GetProcessesList) +% - isData : 1 if files to process are recordings, 0 if they are cortical sources +% - Comment : Default comment for output files +% - isAbsoluteValues : For sources, apply "abs" before applying the process +% - nbPermutation : Number of permutations to perform +% - isOverwriteFiles : For filters, if 1 the input files will be replaced +% - OutputType : {'database', 'file', 'matlab'} +% - isCluster : If 1, use the clusters/scouts (only for data extraction functions) +% - isClusterAverage : If 1, merge the input clusters/scouts +% - sClusters : Clusters/scouts to apply (if isCluster = 1) +% - ClustersOptions : Structure that describes the way to compute the clusters/scouts values +% - Time : Time bounds to compute values +% - Baseline : Baseline bounds +% - iTime : Indices of the time samples to process +% - iBaseline : Indices of the baseline + +% @============================================================================= +% This software is part of The Brainstorm Toolbox +% http://neuroimage.usc.edu/brainstorm +% +% Copyright (c)2009 Brainstorm by the University of Southern California +% This software is distributed under the terms of the GNU General Public License +% as published by the Free Software Foundation. Further details on the GPL +% license can be found at http://www.gnu.org/copyleft/gpl.html. +% +% FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE +% UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY +% WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF +% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY +% LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE. +% +% For more information type "brainstorm licence" at command prompt. +% =============================================================================@ +% +% Authors: Francois Tadel, 2009 +% ----------------------------- Script History --------------------------------- +% FT 17-Jul-2009 Creation +% ------------------------------------------------------------------------------ + +% Get samples lists +SamplesA = OPTIONS.Conditions.SamplesA; +SamplesB = OPTIONS.Conditions.SamplesB; + +% ===== APPLY PROCESS ===== +sProcess = OPTIONS.sProcess; +% Switch according to category +switch (sProcess.Category) + case 'Filter' + iStudyToRedraw = BatchFilter(SamplesA, OPTIONS); + isNewConditions = 0; + case 'Extract' + iStudyToRedraw = BatchExtract(SamplesA, OPTIONS); + isNewConditions = 0; + case 'Filter2' + iStudyToRedraw = BatchFilter2(SamplesA, SamplesB, OPTIONS); + isNewConditions = 0; + case 'Average' + % === Grand-average (by condition) === + % Call several time the average function (once per condition) + if strcmpi(sProcess.Name, 'GAVE') + % Initial values + iStudyToRedraw = []; + isNewConditions = 0; + % Process each condition independently + uniqueConditions = unique({SamplesA.Condition}); + for i = 1:length(uniqueConditions) + % Process the average of condition #i + iSampCond = find(strcmpi(uniqueConditions{i}, {SamplesA.Condition})); + [tmpStudyToRedraw, tmpNewConditions] = BatchAverage(SamplesA(iSampCond), OPTIONS); + % Save the results + iStudyToRedraw = [iStudyToRedraw tmpStudyToRedraw]; + isNewConditions = isNewConditions || tmpNewConditions; + end + + % === Average by subject === + % Call several time the average function (once per condition) + elseif strcmpi(sProcess.Name, 'SubjAvg') + % Initial values + iStudyToRedraw = []; + isNewConditions = 0; + % Process each subject independently + uniqueSubjects = unique({SamplesA.SubjectFile}); + for i = 1:length(uniqueSubjects) + % Process the average of subject #i + iSampSubj = find(strcmpi(uniqueSubjects{i}, {SamplesA.SubjectFile})); + [tmpStudyToRedraw, tmpNewConditions] = BatchAverage(SamplesA(iSampSubj), OPTIONS); + % Save the results + iStudyToRedraw = [iStudyToRedraw tmpStudyToRedraw]; + isNewConditions = isNewConditions || tmpNewConditions; + end + + % Else, just call function once to average everything + else + [iStudyToRedraw, isNewConditions] = BatchAverage(SamplesA, OPTIONS); + end + + case {'Spectral'} + [iStudyToRedraw, isNewConditions] = BatchSpectralDecomposition(SamplesA, OPTIONS); + + case {'Recurrence'} + [iStudyToRedraw, isNewConditions] = BatchRecurrenceMaps(SamplesA, OPTIONS); + + case {'TTest', 'PermTest'} + iStudyToRedraw = BatchStat(SamplesA, SamplesB, OPTIONS); + isNewConditions = 0; + + case {'Anova'} + iStudyToRedraw = BatchStat(SamplesA,[],OPTIONS); + isNewConditions = 0; +end + +% ===== UPDATE INTERFACE ===== +% OUTPUT TYPE: DATABASE ONLY +if strcmpi(OPTIONS.OutputType, 'database') && ~isempty(iStudyToRedraw) + iStudyToRedraw = unique(iStudyToRedraw); + % Unload all datasets + bst_dataSetsManager('UnloadAll', 'Forced'); + % Update results links in target study + db_updateLinkResults('Study', iStudyToRedraw); + % Update tree model + if isNewConditions + tree_updateModel(); + else + tree_updateNode('Study', iStudyToRedraw); + end + % Select target study as current node + tree_selectStudyNode( iStudyToRedraw(1) ); + % Save database + bst_saveDatabase(); +end + +% Hide waitbar +bst_progressBar('stop'); + + +end + + + +%% ====================================================================================== +% ===== BATCH FUNCTIONS ================================================================ +% ====================================================================================== + +%% ===== BATCH: FILTERS ===== +function iStudyToRedraw = BatchFilter(SamplesA, OPTIONS) + % === SOME VERIFICATIONS === + % OUTPUT: DATABASE ONLY + if ~strcmpi(OPTIONS.OutputType, 'database') + error('Filtering results can only be stored in database.'); + end + % Get process + sProcess = OPTIONS.sProcess; + nbSamplesA = length(SamplesA); + iStudyToRedraw = []; + % Prepare OPTIONS structure for process_apply function + filterOPTIONS = struct('fcnFilter', str2func(['process_' sProcess.Name]), ... + 'FileA', '', ... + 'iTime', OPTIONS.iTime, ... + 'iBaseline', OPTIONS.iBaseline, ... + 'FileTag', sProcess.Name, ... + 'Comment', OPTIONS.Comment, ... + 'isAbsoluteValues', OPTIONS.isAbsoluteValues, ... + 'blockDimension', sProcess.blockDimension, ... + 'isAvgRef', sProcess.isAvgRef); + + % === FILES LOOP === + bst_progressBar('start', ['Apply process: ' sProcess.Name], 'Initialization...', 0, nbSamplesA); + % Process all the files + for i = 1:nbSamplesA + % Progress bar + bst_progressBar('text', ['File: ' SamplesA(i).FileName]); + bst_progressBar('inc', 1); + % Output study: same as input file + iTargetStudy = SamplesA(i).iStudy; + sTargetStudy = bst_getContext('Study', iTargetStudy); + % Ignore computation for files that are already processed + if ~isempty(strfind(SamplesA(i).FileName, ['_' sProcess.Name])) + res = java_dialog('confirm', ['The following file has already been processed with this filter: ' 10 SamplesA(i).FileName '.' 10 10 'Apply this process again ?'], 'Processes'); + if ~res + continue + end + end + % Update this study + iStudyToRedraw(end + 1) = iTargetStudy; + + % === COMPUTE FILTER === + % Complete filter configuration + filterOPTIONS.FileA = SamplesA(i).FullFileName; + % Apply process + [sNewFile, filterOPTIONS] = process_apply(filterOPTIONS); + % If process cancelled + if isempty(sNewFile) + return + end + + % === UPDATE STUDY === + % Overwrite initial file + if OPTIONS.isOverwriteFiles + % Delete the intial file + io_deleteFile(SamplesA(i).FullFileName, 1); + end + % Add new file to database + if OPTIONS.isData + if OPTIONS.isOverwriteFiles + iItem = SamplesA(i).iItem; + else + iItem = length(sTargetStudy.Data) + 1; + end + % Add new descriptor it to study + sTargetStudy.Data(iItem) = sNewFile; + else + if OPTIONS.isOverwriteFiles + iResult = SamplesA(i).iItem; + else + iResult = length(sTargetStudy.Result) + 1; + end + % Add new descriptor it to study + sTargetStudy.Result(iResult) = sNewFile; + end + bst_setContext('Study', iTargetStudy, sTargetStudy); + end +end + + +%% ===== BATCH: EXTRACT ===== +function iStudyToRedraw = BatchExtract(SamplesA, OPTIONS) + % Get process + sProcess = OPTIONS.sProcess; + nbSamplesA = length(SamplesA); + iStudyToRedraw = []; + isOneOutputMat = ~strcmpi(OPTIONS.OutputType, 'database'); + initTimeVector = []; + ProtocolInfo = bst_getContext('ProtocolInfo'); + + % === FILES LOOP === + bst_progressBar('start', 'Data extraction', 'Initialization...', 0, nbSamplesA); + % Process all the files + for iSample = 1:nbSamplesA + % Progress bar + bst_progressBar('text', ['File: ' SamplesA(iSample).FileName]); + bst_progressBar('inc', 1); + % Output study: same as input file + iTargetStudy = SamplesA(iSample).iStudy; + sTargetStudy = bst_getContext('Study', iTargetStudy); + % Update this study + iStudyToRedraw(end + 1) = iTargetStudy; + + % === READ FILE === + if sProcess.isAvgRef + [matValues, matName, ChannelFlag, TimeVector] = bst_readMatrixInFile(SamplesA(iSample).FullFileName, []); + else + [matValues, matName, ChannelFlag, TimeVector] = bst_readMatrixInFile(SamplesA(iSample).FullFileName, [], 'NoAvgRef'); + end + + % === TIME === + % Check time vectors + if (iSample == 1) + initTimeVector = TimeVector; + elseif (length(initTimeVector) ~= length(TimeVector)) || ~all(abs(initTimeVector - TimeVector) < 1e-6) + filedesc = fullfile(fileparts(SamplesA(iSample).FileName), SamplesA(iSample).Comment); + bst_error(['Time definition should be the same for all the files.' 10 10 'Ignoring file: "' filedesc '"'], 'bst_batch', 0); + return + end + % Keep only required time indices + matValues = matValues(:, OPTIONS.iTime); + TimeVector = TimeVector(OPTIONS.iTime); + + % === PROCESSES === + % Absolute values + if OPTIONS.isAbsoluteValues + matValues = abs(matValues); + end + % Process to apply to this block of data + switch (sProcess. Name) + case 'timemean' + % Compute the average of all the time samples on the defined time window + matValues = mean(matValues, 2); + isTimeMean = 1; + case 'timevar' + % Compute the variance of all the time samples on the defined time window + matValues = var(matValues, 0, 2); + isTimeMean = 1; + case 'extract' + % Nothing to do + isTimeMean = 0; + end + % TimeMean: Keep only the time window from which was computed the result + if isTimeMean + OPTIONS.iTime = OPTIONS.iTime([1,end]); + TimeVector = TimeVector([1, end]); + end + + % === APPLY CLUSTER === + if OPTIONS.isCluster + % Process each cluster + nbClusters = length(OPTIONS.Clusters); + tmpMatValues = zeros(nbClusters, size(matValues, 2)); + DescCluster = cell(nbClusters, 1); + for i = 1:nbClusters + sCluster = OPTIONS.Clusters(i); + % === GET ROWS INDICES === + if OPTIONS.isData + % Get channel file + [sStudy, iStudy] = bst_getContext('DataFile', SamplesA(iSample).FileName); + sChannel = bst_getContext('ChannelForStudy', iStudy); + % Load channel file + ChannelFile = fullfile(ProtocolInfo.STUDIES, sChannel.FileName); + ChannelMat = load(ChannelFile, 'Channel'); + % CLUSTERS: Get cluster channels + iRows = panel_clusters('GetChannelsInCluster', sCluster, ChannelMat.Channel, ChannelFlag); + if isempty(iRows) + return; + end + else + % SCOUTS: Get the indices of the sources of this scout + iRows = sCluster.Vertices; + end + + % === COMPUTE CLUSTER VALUES === + % Scouts: absolute or relative values + isDifference = strcmpi(sProcess.Name, 'diffAB'); + % If computing a difference : FORCE RELATIVE VALUES + if ~OPTIONS.ClustersOptions.isAbsolute || isDifference + tmp = matValues(iRows, :); + else + tmp = abs(matValues(iRows, :)); + end + % Scouts computation operation : mean or max + switch (OPTIONS.ClustersOptions.function) + case 'Max' + tmpMatValues(i,:) = max(tmp, [], 1); + case 'Power' + tmpMatValues(i,:) = sum(tmp .^ 2, 1); + case 'Mean' + tmpMatValues(i,:) = mean(tmp, 1); + otherwise + bst_error(['Cannot extract automatically all values.' 10 ... + 'Please change scout options: "Scouts" panel > View > Time series options'], ... + 'bst_batch', 0); + return; + end + % Save the name of the scout + DescCluster{i} = sCluster.Label; + end + % Return results matrix averaged by scout + matValues = tmpMatValues; + else + DescCluster = {}; + end + + % ===== OUTPUT STRUCTURE ==== + % Create new output structure + if ~isOneOutputMat || (iSample == 1) + newFileMat = CreateDefaultStruct(sProcess, SamplesA(iSample).FullFileName, OPTIONS.isData, TimeVector, OPTIONS.iTime, ChannelFlag, isOneOutputMat, 1); + % Append comment + newFileMat.Comment = [newFileMat.Comment, ' ', OPTIONS.Comment]; + end + % If new file to be saved at each loop: copy matValues + if ~isOneOutputMat + if isTimeMean + newFileMat.(matName) = repmat(matValues, [1, 2]); + else + newFileMat.(matName) = matValues; + end + else + % Get new matrix + prevMat = newFileMat.(matName); + % Check the orientation in which the new data should be appended: + % - Time mean : dimension 2 (time) + % - Channels mean : dimension 1 (channels) + if isTimeMean + % Check the number of electrodes + if ~isempty(prevMat) && (size(prevMat,1) ~= size(matValues,1)) + error(['The data files in samples set A do not have the same number of channels.' 10 ... + 'It is impossible to store their averages in the same matrix.']); + end + % Concatenate the results matrices & add description fields + newFileMat.(matName) = cat(2, prevMat, matValues); + newFileMat.DescFileName = cat(2, newFileMat.DescFileName, repmat({SamplesA(iSample).FileName}, [1, size(matValues,2)])); + newFileMat.DescCluster = cat(2, newFileMat.DescCluster, DescCluster); + else + if ~isempty(prevMat) && (size(prevMat,2) ~= size(matValues,2)) + error(['The files in samples set A do not have the same number of time samples.' 10 ... + 'It is impossible to store their averages in the same matrix.']); + end + % Concatenate the results matrices & add description fields + newFileMat.(matName) = cat(1, prevMat, matValues); + newFileMat.DescFileName = cat(1, newFileMat.DescFileName, repmat({SamplesA(iSample).FileName}, [size(matValues,1), 1])); + newFileMat.DescCluster = cat(1, newFileMat.DescCluster, DescCluster); + end + % Add bad channels from current ChannelFlag + newFileMat.ChannelFlag(ChannelFlag == -1) = -1; + end + + % ===== SAVE FILE ===== + if ~isOneOutputMat + SaveNewFile(newFileMat, sProcess, OPTIONS.isData, iTargetStudy, sTargetStudy, SamplesA(iSample).FileName); + end + end + + % === EXPORT DATA === + if isOneOutputMat + % Display waitbar + bst_progressBar('start', 'Processes', 'Saving results...'); + % Save file + switch(OPTIONS.OutputType) + case 'file' + % Save in a user defined file + if OPTIONS.isData || OPTIONS.isCluster + export_data(newFileMat); + else + export_result(newFileMat); + end + case 'matlab' + % Export to Matlab workspace + export_matlab(newFileMat); + end + end +end + + +%% ===== BATCH: AVERAGE ===== +function [iStudyToRedraw, isNewConditions] = BatchAverage(SamplesA, OPTIONS) + % Get process + sProcess = OPTIONS.sProcess; + % Get output study + [sTargetStudy, iTargetStudy isNewConditions, Comment] = GetOutputStudy(sProcess,SamplesA,repmat(SamplesA, 0),OPTIONS); + % Update this study + iStudyToRedraw = iTargetStudy; + % If no valid output study can be found + if isempty(iTargetStudy) + return; + end + + % === PROCESS AVERAGE === + bst_progressBar('start', ['Apply process: ' sProcess.Name], 'Initialization...', 0, length(SamplesA)); + % If absolute values + if OPTIONS.isAbsoluteValues + strAbsolute = 'AbsoluteValues'; + else + strAbsolute = []; + end + Stat = bst_statFiles('mean', {SamplesA.FullFileName}, OPTIONS.iTime, 'PercentProgressBar', strAbsolute); + + % === SAVE RESULTS === + % Get default file structure + newFileMat = CreateDefaultStruct(sProcess, SamplesA(1).FullFileName, OPTIONS.isData, Stat.Time, OPTIONS.iTime, Stat.ChannelFlag, 0, 0); + % Add averaged values in it + newFileMat.(Stat.MatName) = Stat.mean; + % Comment + if isempty(OPTIONS.Comment) && isempty(Comment) + % Use default commet: "ProcessName: date" + c = clock; + strTime = sprintf('%02.0f%02.0f%02.0f_%02.0f%02.0f', c(1)-2000, c(2:5)); + newFileMat.Comment = [sProcess.Name ': ' strTime]; + elseif isempty(OPTIONS.Comment) + newFileMat.Comment = Comment; + else + newFileMat.Comment = OPTIONS.Comment; + end + % Save and register file + SaveNewFile(newFileMat, sProcess, OPTIONS.isData, iTargetStudy, sTargetStudy, SamplesA(1).FileName); +end + + +%% ===== BATCH: FILTER2 ===== +function [iStudyToRedraw, isNewConditions] = BatchFilter2(SamplesA, SamplesB, OPTIONS) + % Get process + sProcess = OPTIONS.sProcess; + nbSamplesA = length(SamplesA); + iStudyToRedraw = []; + + % === FILES LOOP === + bst_progressBar('start', 'Comparisons', 'Initialization...', 0, nbSamplesA); + % Process all the files + for iSample = 1:nbSamplesA + % Progress bar + bst_progressBar('text', ['File: ' SamplesA(iSample).FileName]); + bst_progressBar('inc', 1); + % Get output study + [sTargetStudy, iTargetStudy isNewConditions, Comment] = GetOutputStudy(sProcess,SamplesA(iSample),SamplesB(iSample),OPTIONS); + % If no valid output study can be found + if isempty(iTargetStudy) + return; + end + % Update this study + iStudyToRedraw(end + 1) = iTargetStudy; + + % === READ FILES === + if sProcess.isAvgRef + % Sample A + [matValuesA, matNameA, ChannelFlagA, TimeVectorA] = bst_readMatrixInFile(SamplesA(iSample).FullFileName, []); + % Sample B + [matValuesB, matNameB, ChannelFlagB, TimeVectorB] = bst_readMatrixInFile(SamplesB(iSample).FullFileName, []); + else + % Sample A + [matValuesA, matNameA, ChannelFlagA, TimeVectorA] = bst_readMatrixInFile(SamplesA(iSample).FullFileName, [], 'NoAvgRef'); + % Sample B + [matValuesB, matNameB, ChannelFlagB, TimeVectorB] = bst_readMatrixInFile(SamplesB(iSample).FullFileName, [], 'NoAvgRef'); + end + + % === TIME === + % Check time vectors + if (length(TimeVectorA) ~= length(TimeVectorB)) || ~all(abs(TimeVectorA - TimeVectorB) < 1e-6) + bst_error('Each couple of files (A,B) must have the same time definition.', 'bst_batch', 0); + return + elseif (length(ChannelFlagA) ~= length(ChannelFlagB)) + bst_error('Each couple of files (A,B) must have the same number of channels.', 'bst_batch', 0); + return + end + % Keep only required time indices + matValuesA = matValuesA(:, OPTIONS.iTime); + matValuesB = matValuesB(:, OPTIONS.iTime); + TimeVector = TimeVectorA(OPTIONS.iTime); + % Combine bad channels + ChannelFlag = ones(size(ChannelFlagA)); + ChannelFlag((ChannelFlagA == -1) | (ChannelFlagB == -1)) = -1; + + % === PROCESSES === + % Absolute values + if OPTIONS.isAbsoluteValues + matValuesA = abs(matValuesA); + matValuesB = abs(matValuesB); + end + % Apply process + switch (sProcess.Name) + case 'diffAB' + matValues = matValuesA - matValuesB; + case 'meanAB' + matValues = (matValuesA + matValuesB) / 2; + end + + % ===== SAVE FILE ==== + % Create default structure + newFileMat = CreateDefaultStruct(sProcess, SamplesA(iSample).FullFileName, OPTIONS.isData, TimeVector, OPTIONS.iTime, ChannelFlag, 0, 1); + % Add averaged values in it + newFileMat.(matNameA) = matValues; + % Comment + if isempty(OPTIONS.Comment) + if ~isempty(Comment) + newFileMat.Comment = Comment; + else + % Build comments based on the samples couple + localCond = struct('SamplesA',SamplesA(iSample), 'SamplesB',SamplesB(iSample)); + newFileMat.Comment = panel_statRun('FormatComment', sProcess, localCond, OPTIONS.Time); + end + else + newFileMat.Comment = OPTIONS.Comment; + end + % Save file + SaveNewFile(newFileMat, sProcess, OPTIONS.isData, iTargetStudy, sTargetStudy, SamplesA(iSample).FileName); + end +end + + +%% ===== BATCH: STAT ===== +function iStudyToRedraw = BatchStat(SamplesA, SamplesB, OPTIONS) + % Get process + sProcess = OPTIONS.sProcess; + testType = sProcess.Name; + isPermTest = strcmpi(sProcess.Category, 'PermTest'); + % For old Karim t-test + isOldSimpleTest = ~isempty(strfind(sProcess.Name, 'old')); + if isOldSimpleTest + testType = strrep(testType, 'old_', ''); + end + isAnova = strcmpi(sProcess.Category, 'Anova'); + + + % Display progress bar + bst_progressBar('start', 'Statistical test', 'Initialization...', 0, length(SamplesA)); + % Get output study + if isAnova + [sTargetStudy, iTargetStudy isNewConditions, Comment] = GetOutputStudy(sProcess, SamplesA, SamplesA([]), OPTIONS); + else + [sTargetStudy, iTargetStudy isNewConditions, Comment] = GetOutputStudy(sProcess, SamplesA, SamplesB, OPTIONS); + end + iStudyToRedraw = iTargetStudy; + % If no valid output study can be found + if isempty(iTargetStudy) + return; + end + + % === Load all the samples === + % Needed only for permutation t-test, and OLD KARIM T-TEST + if isPermTest || isOldSimpleTest + % Samples set A + [X1, ChannelFlagA, TimeVector] = buildDataArray({SamplesA.FullFileName}, OPTIONS.iTime); + % Samples set B + [X2, ChannelFlagB] = buildDataArray({SamplesB.FullFileName}, OPTIONS.iTime); + % If is absolute values + if OPTIONS.isAbsoluteValues + X1 = abs(X1); + X2 = abs(X2); + end + % Combine channel flag + ChannelFlag = ChannelFlagA; + ChannelFlag(ChannelFlagB == -1) = -1; + end + + if isAnova + %knd: to do + warning('manually changed anova factors!') + [pmap,Fmap,fx,ChannelFlag, TimeVector] = knd_rmanova_files(SamplesA,OPTIONS); + + % === SIMPLE TESTS (OLD) === + elseif ~isPermTest && isOldSimpleTest + % Remove the "old" tag + testType = strrep(testType, 'old_', ''); + % Set permutation dimension + dim_p = 1; + % Display waitbar + bst_progressBar('start', 'Processes', 'Computing t-test...'); + % Compute test + [pmap,tmap] = knd_ttest(X1, X2, dim_p, testType); + + % === SIMPLE TESTS === + elseif ~isPermTest + % Compute test + if OPTIONS.isAbsoluteValues + [pmap,tmap,ChannelFlag, TimeVector] = knd_ttest_files({SamplesA.FullFileName}, {SamplesB.FullFileName}, testType, OPTIONS.iTime, OPTIONS.iTime, 'AbsoluteValues'); + else + [pmap,tmap,ChannelFlag, TimeVector] = knd_ttest_files({SamplesA.FullFileName}, {SamplesB.FullFileName}, testType, OPTIONS.iTime, OPTIONS.iTime); + end + + % === PERMUTATIONS TESTS === + elseif isPermTest + tic + % Set permutation dimension + if sProcess.isPaired + dim_p = 1; + else + dim_p = -1; + end + + % Compute test + [pmap,tmap] = permtest(X1, X2, {testType}, dim_p, [], OPTIONS.nbPermutation); + % Display computation time + timeLength = toc(); + bst_message_window(sprintf('\nTook: %3.2f secs', timeLength)); + + + end + + % === BUILD NEW FILES === + % Display waitbar + bst_progressBar('start', 'Processes', 'Saving results...'); + + if isAnova + Factors.N = 3; + NS = numel(unique({SamplesA.SubjectName})) + [u,i,j] = unique([SamplesA.iItem]) + if numel(u)>1 + SelectedItem = listdlg('ListString', [strvcat({SamplesA(i).Condition}) repmat(' : ',length(u),1) strvcat({SamplesA(i).Comment})],'SelectionMode','single'); + if isempty(SelectedItem) + return + end + SamplesA = SamplesA(j == SelectedItem); + end + f = {SamplesA.Condition}; + f=reshape(f(1+[0:3]*NS),[2 2]) + fa=sprintf('%s+',f{1,:}); + fb=sprintf('%s+',f{2,:}); + Factors.Names{1}=sprintf('%s vs. %s',fa(1:end-1),fb(1:end-1)); + fa=sprintf('%s+',f{:,1}); + fb=sprintf('%s+',f{:,2}); + Factors.Names{2}=sprintf('%s vs. %s',fa(1:end-1),fb(1:end-1)); + + %Factors.Names = { 'FB' 'Conf' 'Subj'}; + Factors.NLevels = [2 2 NS] + Factors.Levels = { {'C' 'I'} {'H' 'L'} [] }; + Factors.X = []; + %must loop on maps + for i_fx=1:size(pmap,1) + % Get default structure + newFileMat = CreateDefaultStruct(sProcess, SamplesA(1).FullFileName, OPTIONS.isData, TimeVector, OPTIONS.iTime, ChannelFlag, 0, 0); + % Comment + newFileMat.Comment = OPTIONS.Comment ; + if numel(fx{i_fx}) == 1 + newFileMat.Comment = [newFileMat.Comment ' - Main effect of ']; + else + newFileMat.Comment = [newFileMat.Comment ' - Interaction of ']; + end + newFileMat.Comment = [newFileMat.Comment sprintf('%s * ',Factors.Names{fx{i_fx}})]; + newFileMat.Comment(end-2:end)=[]; + % Save P and F maps + sz=[size(pmap) 1]; + newFileMat.pmap = single(reshape(pmap(i_fx,:),sz(2:end))); + newFileMat.tmap = single(reshape(Fmap(i_fx,:),sz(2:end))); + % Save new file and register in database + SaveNewFile(newFileMat, sProcess, OPTIONS.isData, iTargetStudy, sTargetStudy, SamplesA(1).FileName); + end + else + % Get default structure + newFileMat = CreateDefaultStruct(sProcess, SamplesA(1).FullFileName, OPTIONS.isData, TimeVector, OPTIONS.iTime, ChannelFlag, 0, 0); + % Comment + if isempty(OPTIONS.Comment) + newFileMat.Comment = Comment; + else + newFileMat.Comment = OPTIONS.Comment; + end + % Save P and T maps + newFileMat.pmap = single(pmap); + newFileMat.tmap = single(tmap); + % Save new file and register in database + SaveNewFile(newFileMat, sProcess, OPTIONS.isData, iTargetStudy, sTargetStudy, SamplesA(1).FileName); + end +end + + +%% ===== BATCH: SPECTRAL ANALYSIS ====== +function [iStudyToRedraw, isNewConditions] = BatchSpectralDecomposition(SamplesA, OPTIONS) + % Frequency bands of interest + freqBands = ... + [1 3.9;... + 4,7.9;... + 8,12.9;... + 13,34.9;... + 35,54.9;... + 65,85;... + 200,250]; + NamesFreqBands = {... + 'delta_1_4Hz',... + 'theta_4_8Hz',... + 'alpha_8_13Hz',... + 'beta_13_35Hz',... + 'gamma_35_55Hz',... + 'gamma_65_85Hz',... + 'gamma_200_250Hz'}; + + % Get process + sProcess = OPTIONS.sProcess; + nbSamplesA = length(SamplesA); + iStudyToRedraw = []; + + % === FILES LOOP === + bst_progressBar('start', 'Comparisons', 'Initialization...', 0, nbSamplesA); + % Process all the files + for iSample = 1:nbSamplesA + % Progress bar + bst_progressBar('text', ['File: ' SamplesA(iSample).FileName]); + bst_progressBar('inc', 1); + % Get output study + [sTargetStudy, iTargetStudy isNewConditions, Comment] = GetOutputStudy(sProcess,SamplesA(iSample),repmat(SamplesA, 0),OPTIONS); + % If no valid output study can be found + if isempty(iTargetStudy) + return; + end + % Update this study + iStudyToRedraw(end + 1) = iTargetStudy; + + % === READ FILES === + % Resolve link + [FileName, DataFile] = resolveResultsLink(SamplesA(iSample).FullFileName); + % Load results file + kernelMat = load(FileName); +% dataMat = load(DataFile,'F', 'Time'); + dataMat = in_data_bst( DataFile, [], 'Time', 'F'); + + %iMEG = good_channel(kernelMat.Channel,kernelMat.ChannelFlag, 'MEG'); %CBB: need to expand to EEG as well + iMEG = kernelMat.OPTIONS.GoodChannel; + dataMat.F = dataMat.F(iMEG, :); + + if iSample == 1 + F_RMS = zeros(size(kernelMat.ImagingKernel,1),length(NamesFreqBands),nbSamplesA); + end + sRate = abs(1/(dataMat.Time(2)-dataMat.Time(1))); + nTime = size(dataMat.F,2); + NFFT = 2^nextpow2(nTime); % Next power of 2 from length of y + freqVec = sRate/2*linspace(0,1,NFFT/2); + + fftF = fft(dataMat.F,NFFT,2)/nTime; + %powfftF = 2*abs(fftF(:,1:end/2)); + clear dataMat; + for ifreq = 1:length(NamesFreqBands) + VecInd = findclosest(freqBands(ifreq,:),freqVec); + %F = bandpassFilter_all(dataMat.F,sRate,freqBands(ifreq,1),freqBands(ifreq,2)); + F_RMS(:,ifreq,iSample) = sum(2*abs(kernelMat.ImagingKernel * fftF(:, VecInd(1):VecInd(2))),2)... + /length(VecInd(1):VecInd(2)); + % ImageGridAmp = sqrt(sum(ImageGridAmp.^2,2)/size(ImageGridAmp,2)); % compute root mean square power in current frequency band + end + end + + clear dataMat fftF + % Compute statistics + %F_RMS(F_RMS<.05*max(F_RMS(:)))=0; + aveStat = mean(F_RMS,3); + stdStat = std(F_RMS,0,3); + clear F_RMS + for ifreq = 1:length(NamesFreqBands) + current_aveStat = aveStat(:,ifreq); + tmp_current_aveStat = current_aveStat; + tmp_current_aveStat(tmp_current_aveStat<.5*max(tmp_current_aveStat))=0; + current_stdStat = stdStat(:,ifreq); + tStat = (tmp_current_aveStat./current_stdStat)*sqrt(nbSamplesA-1); + %tStat(isnan(tStat))=0; + + % === SAVE RESULTS === + kernelMat.ImageGridAmp = repmat(current_aveStat,1,2); + kernelMat.ImagingKernel = []; + kernelMat.ImageGridTime = [1,2]; + kernelMat.Comment = sprintf('GAVE: %s',NamesFreqBands{ifreq}); + % Save and register file + SaveNewFile(kernelMat, sProcess, OPTIONS.isData, iTargetStudy, sTargetStudy, SamplesA(1).FileName); + + kernelMat.ImageGridAmp = repmat(current_stdStat,1,2); + kernelMat.ImagingKernel = []; + kernelMat.ImageGridTime = [1,2]; + kernelMat.Comment = sprintf('STD: %s',NamesFreqBands{ifreq}); + % Save and register file + SaveNewFile(kernelMat, sProcess, OPTIONS.isData, iTargetStudy, sTargetStudy, SamplesA(1).FileName); + + + kernelMat.ImageGridAmp = repmat(tStat,1,2); + kernelMat.ImagingKernel = []; + kernelMat.ImageGridTime = [1,2]; + kernelMat.Comment = sprintf('T: %s',NamesFreqBands{ifreq}); + % Save and register file + SaveNewFile(kernelMat, sProcess, OPTIONS.isData, iTargetStudy, sTargetStudy, SamplesA(1).FileName); + + end + +end + +%% ===== BATCH: RECURRENCE MAPS ====== +function [iStudyToRedraw, isNewConditions] = BatchRecurrenceMaps(SamplesA, OPTIONS) + % Frequency bands of interest + ampThresh = .75; %Binarization Threshold + timeWindow = 30; % time window within which binarization is processed + bandPass1 = 1; + bandPass2 = 250; + + % Get process + sProcess = OPTIONS.sProcess; + nbSamplesA = length(SamplesA); + iStudyToRedraw = []; + + % === FILES LOOP === + bst_progressBar('start', 'Comparisons', 'Initialization...', 0, nbSamplesA); + % Process all the files + for iSample = 1:nbSamplesA + % Progress bar + bst_progressBar('text', ['File: ' SamplesA(iSample).FileName]); + bst_progressBar('inc', 1); + % Get output study + [sTargetStudy, iTargetStudy isNewConditions, Comment] = GetOutputStudy(sProcess,SamplesA(iSample),repmat(SamplesA, 0),OPTIONS); + % If no valid output study can be found + if isempty(iTargetStudy) + return; + end + % Update this study + iStudyToRedraw(end + 1) = iTargetStudy; + + % === READ FILES === + % Resolve link + [FileName, DataFile] = resolveResultsLink(SamplesA(iSample).FullFileName); + % Load results file + kernelMat = load(FileName); +% dataMat = load(DataFile,'F', 'Time'); + dataMat = in_data_bst( DataFile, [], 'Time', 'F'); + iMEG = good_channel(kernelMat.Channel,kernelMat.ChannelFlag, 'MEG'); %CBB: need to expand to EEG as well + dataMat.F = dataMat.F(iMEG, :); + + sRate = abs(1/(dataMat.Time(2)-dataMat.Time(1))); + nTime = size(dataMat.F,2); + + VecInd = findclosest([-timeWindow,timeWindow]/1000,dataMat.Time); + timeVec = VecInd(1):VecInd(2); + dataMat.F = bandpassFilter_all(dataMat.F,sRate, bandPass1,bandPass2); + ImageGridAmp = abs(kernelMat.ImagingKernel * dataMat.F(:, VecInd(1):VecInd(2))); + + % Thresholding + if iSample == 1; + iMask = zeros(size(ImageGridAmp,1),1); + iTimeMask = zeros(size(ImageGridAmp,1),nbSamplesA); + end + %tmp = zeros(size(ImageGridAmp)); + %tmp(find(ImageGridAmp>ampThresh*max(ImageGridAmp(:)))) = 1; + + % time-relative max + % maxx = max(ImageGridAmp); + % maxx = repmat(maxx,size(ImageGridAmp,1),1); + % time-absolute max + maxx = max(ImageGridAmp(:)); + maxx = maxx*ones(size(ImageGridAmp)); + + iMaxtmp = ImageGridAmp > (ampThresh * maxx); + iTimeMask(:,iSample) = min(... + iMaxtmp .* repmat(dataMat.Time(timeVec),size(ImageGridAmp,1),1), ... + [],2); + iMaxtmp = sum(iMaxtmp,2); + + iMask(find(iMaxtmp)) = iMask(find(iMaxtmp)) + 1; + %[I,J] = find(ImageGridAmp>ampThresh*max(ImageGridAmp(:))); + %iMask(I) = iMask(I) + 1; + %iTimeMask(I,iSample) = dataMat.Time(timeVec(J)); + end + clear dataMat + iMask = 100*iMask/nbSamplesA; + iTimeMask = 1000*mean(iTimeMask,2).*(iMask>0); + + % === SAVE RESULTS === + kernelMat.ImageGridAmp = repmat(iMask,1,2); + kernelMat.ImagingKernel = []; + kernelMat.ImageGridTime = [1,2]; + kernelMat.Comment = sprintf('Recurrence Map'); + % Save and register file + SaveNewFile(kernelMat, sProcess, OPTIONS.isData, iTargetStudy, sTargetStudy, SamplesA(1).FileName); + + kernelMat.ImageGridAmp = repmat(iTimeMask,1,2); + kernelMat.ImagingKernel = []; + kernelMat.ImageGridTime = [1,2]; + kernelMat.Comment = sprintf('Recurrence Map (latencies)'); + % Save and register file + SaveNewFile(kernelMat, sProcess, OPTIONS.isData, iTargetStudy, sTargetStudy, SamplesA(1).FileName); + + +end + +%% ====================================================================================== +% ===== HELPERS ======================================================================== +% ====================================================================================== +%% ===== CREATE DEFAULT OUTPUT STRUCTURE ===== +function sFileMat = CreateDefaultStruct(sProcess, FileName, isData, TimeVector, iTime, ChannelFlag, isOneOutputMat, isSampleBySample) +% Default structures for stat +isStat = ismember(sProcess.Category, {'TTest', 'PermTest', 'Anova'}); +if isStat + sStat = struct(... + 'Type', '', ... + 'Comment', '', ... + 'Time', TimeVector, ... + 'ChannelFlag', ChannelFlag, ... + 'pmap', [], ... + 'tmap', [], ... + 'pThreshold', 0.05); + end + + % If recordings + if isData + % Load file + sFileMat = load(FileName); + % Stat + if isStat + sStat.Type = 'data'; + sStat.Comment = sFileMat.Comment; + sFileMat = sStat; + % Data + else + sFileMat.Time = TimeVector; + sFileMat.F = []; + end + % Else: sources + else + % Get protocol directories + ProtocolInfo = bst_getContext('ProtocolInfo'); + % Resolve link, if it is a link + [FileName, DataFile] = resolveResultsLink(FileName); + % Load file + sFileMat = load(FileName); + % Stat + if isStat + sStat.Type = 'results'; + sStat.Comment = sFileMat.Comment; + sFileMat = sStat; + else + % Time + sFileMat.Time = iTime; + sFileMat.ImageGridTime = TimeVector; + % Reset some fields + sFileMat.OPTIONS.Data = []; + sFileMat.Fsynth = []; + sFileMat.ImagingKernel = []; + sFileMat.ImageGridAmp = []; + if ~isSampleBySample + sFileMat.DataFile = []; + elseif ~isempty(DataFile) + sFileMat.DataFile = strrep(DataFile, ProtocolInfo.STUDIES, ''); + end + end + % Remove "Kernel" indications in the Comment field + sFileMat.Comment = strrep(sFileMat.Comment, '(Kernel)', ''); + sFileMat.Comment = strrep(sFileMat.Comment, 'Kernel', ''); + end + % Bad channels list + sFileMat.ChannelFlag = ChannelFlag; + + % Add some fields for exported results + if isOneOutputMat + sFileMat.DescFileName = {}; + sFileMat.DescCluster = {}; + sFileMat.DescProcessType= sProcess.Name; + end +end + + +%% ===== SAVE NEW FILE ===== +function sTargetStudy = SaveNewFile(newFileMat, sProcess, isData, iTargetStudy, sTargetStudy, InputFile) + % Get protocol directories + ProtocolInfo = bst_getContext('ProtocolInfo'); + % Get default output file + OutputDir = fullfile(ProtocolInfo.STUDIES, fileparts(sTargetStudy.FileName)); + FullFileName = GetDefaultFileName(sProcess, isData, OutputDir, InputFile); + % Save in database + save(FullFileName, '-struct', 'newFileMat'); + % Register in database + isStat = ismember(sProcess.Category, {'TTest', 'PermTest', 'Anova'}); + FileName = strrep(FullFileName, ProtocolInfo.STUDIES, ''); + sTargetStudy = RegisterNewFile(newFileMat, FileName, isData, isStat, iTargetStudy); +end + + +%% ===== GET DEFAULT FILE NAME ===== +function OutputFile = GetDefaultFileName(sProcess, isData, OutputDir, InputFile) + % Get date and time for filename + c = clock; + strTime = sprintf('_%02.0f%02.0f%02.0f_%02.0f%02.0f', c(1)-2000, c(2:5)); + + % Output file tag + if ismember(sProcess.Category, {'TTest', 'PermTest', 'Anova'}) + if isData + fileTag = 'pdata'; + else + fileTag = 'presults'; + end + else + if isData + fileTag = 'data'; + else + fileTag = 'results'; + end + end + + % Other tags present in input file + if ~isempty(strfind(InputFile, '_zscore')) + strInputTags = '_zscore'; + else + strInputTags = ''; + end + + % Default filename + defFileName = [fileTag '_' sProcess.Name strTime strInputTags '.mat']; + % File in the target study + OutputFile = fullfile(OutputDir, defFileName); + % Make filename unique + OutputFile = io_makeUniqueFilename(OutputFile); +end + + +%% ===== REGISTER NEW FILE ===== +function sStudy = RegisterNewFile(FileMat, FileName, isData, isStat, iStudy) + % Get study + sStudy = bst_getContext('Study', iStudy); + % Stat + if isStat + % Create new stat file descriptor + sNewStat = db_getDataTemplate('Stat'); + sNewStat.FileName = FileName; + sNewStat.Comment = FileMat.Comment; + sNewStat.Type = FileMat.Type; + sNewStat.pThreshold = FileMat.pThreshold; + % Add it to study + sStudy.Stat(end+1) = sNewStat; + % Data + elseif isData + % Create new data descriptor + sNewData = db_getDataTemplate('Data'); + sNewData.FileName = FileName; + sNewData.Comment = FileMat.Comment; + % Add it to study + sStudy.Data(end+1) = sNewData; + % Results + else + % Create new results descriptor + sNewResult = db_getDataTemplate('Results'); + sNewResult.FileName = FileName; + sNewResult.Comment = FileMat.Comment; + sNewResult.DataFile = FileMat.DataFile; + % Add it to study + sStudy.Result(end+1) = sNewResult; + end + % Update database + bst_setContext('Study', iStudy, sStudy); +end + + +% ===== GET OUTPUT STUDY ===== +function [sOutputStudy, iOutputStudy, isNewConditions, Comment] = GetOutputStudy(sProcess,SamplesA,SamplesB,OPTIONS) + % Get some properties of the filter + isMixedSubjectsAB = isempty(SamplesB) || ~isequal({SamplesA.SubjectFile}, {SamplesB.SubjectFile}); + isSampleBySample = ismember(sProcess.Category, {'Filter2', 'Filter', 'Extract'}); + isConditionByCondition = strcmpi(sProcess.Name, 'GAVE'); + isNewConditions = 0; + % Get the list of involved studies + uniqueStudiesInd = unique([SamplesA.iStudy, SamplesB.iStudy]); +% Comment = OPTIONS.Comment; + Comment = ''; + + % ===== CHECK SUBJECT UNICITY ===== + % Get full subjects list without repetitions + uniqueSubjectFiles = unique(cat(2, {SamplesA.SubjectFile}, {SamplesB.SubjectFile})); + % For stat on sources: all the results files must be computed on the same surface. + % => else, the number of sources might be different between different files + % => user should realign surfaces before going on + if (length(uniqueSubjectFiles) > 1) + isUniqueSubject = 0; + if ~isSampleBySample && ~OPTIONS.isData + % If there is more than one subjects involved: need to check that all + % the subjects use the default anatomy + for i = 1:length(uniqueSubjectFiles) + sSubject = bst_getContext('Subject', uniqueSubjectFiles{i}); + if ~sSubject.UseDefaultAnat + gui_hidePanel('panel_statRun'); + sOutputStudy = []; + iOutputStudy = []; + bst_error(['The sources files you selected use different anatomies.' 10 ... + 'In order to perform any computation on sources, all the files' 10 ... + 'should use the same anatomy.' 10 10 ... + 'You have to project all your results on a template anatomy.' 10 ... + 'Popup menu: "Project sources on default anatomy".'], 'Processes', 0); + return; + end + end + end + else + isUniqueSubject = 1; + end + + % ===== GET OUTPUT STUDY ===== + % SUBJECT AVERAGE + if strcmpi(sProcess.Name, 'SubjAvg') + % Get subject + [sSubject, iSubject] = bst_getContext('Subject', SamplesA(1).SubjectFile); + % Get "intra" study for this subject + [sOutputStudy, iOutputStudy] = bst_getContext('AnalysisIntraStudy', iSubject); + % Comment + Comment = ['SubjectAvg(' SamplesA(1).SubjectName ')']; + % UNIQUE STUDY (OR OVERWRITE) + elseif (length(uniqueStudiesInd) == 1) || OPTIONS.isOverwriteFiles + % Get this unique study + [sOutputStudy, iOutputStudy] = bst_getContext('Study', uniqueStudiesInd(1)); + % MULTIPLE STUDIES : SAMPLE BY SAMPLE + elseif isSampleBySample && ~isMixedSubjectsAB + % Target study is a new condition (name = test comment) + % Get subject path + newCondPath = fileparts(SamplesA(1).SubjectFile); + % Get new condition name + localCond = struct('SamplesA',SamplesA,'SamplesB',SamplesB); + newCondName = panel_statRun('FormatComment', sProcess, localCond, OPTIONS.Time); + newCondName = io_standardizeFileName(newCondName); + % Comment + Comment = sProcess.FileTag; + Comment = strrep(Comment, '#A#', localCond.SamplesA(1).Comment); + Comment = strrep(Comment, '#B#', localCond.SamplesB(1).Comment); + % Try to get an existing condition with this path + [sOutputStudy, iOutputStudy] = bst_getContext('StudyWithCondition', fullfile(newCondPath,newCondName)); + % If does not exist: Create a new condition + if isempty(sOutputStudy) + iOutputStudy = db_addCondition(newCondPath, newCondName, 'NoRefresh'); + sOutputStudy = bst_getContext('Study', iOutputStudy); + isNewConditions = 1; + end + % MULTIPLE STUDIES : CONDITION BY CONDITION + elseif isConditionByCondition + Comment = ['GAVE(' SamplesA(1).Condition ')']; + % Grand average: stored in 'analysis-inter' node + [sOutputStudy, iOutputStudy] = bst_getContext('AnalysisInterStudy'); + % MULTIPLE STUDIES : GLOBAL + else + % UNIQUE SUBJECT : 'analysis-intra' + if isUniqueSubject + % Subject file is the same for all the studies => find common subject + [sSubject, iSubject] = bst_getContext('Subject', SamplesA(1).SubjectFile); + % Get 'analysis-intra' node of this subject + [sOutputStudy, iOutputStudy] = bst_getContext('AnalysisIntraStudy', iSubject); + % MULTIPLE SUBJECTS : 'analysis-inter' + else + % Get 'analysis-inter' node in current protocol + [sOutputStudy, iOutputStudy] = bst_getContext('AnalysisInterStudy'); + end + end + % Error ? + if isempty(sOutputStudy) + error('Cannot find an analysis study to store the results.'); + end + + % ===== COMBINE CHANNEL FILES ===== + % If source and target studies are not the same + if ~isequal(uniqueStudiesInd, iOutputStudy) + % Destination study for new channel file + [tmp__, iChanStudyDest] = bst_getContext('ChannelForStudy', iOutputStudy); + % Source channel files studies + [tmp__, iChanStudySrc] = bst_getContext('ChannelForStudy', uniqueStudiesInd); + % If target study has no channel file: create a new one by combination of the others + isNewChannelFile = db_combineChannelFiles(unique(iChanStudySrc), iChanStudyDest, [], ~OPTIONS.isData); + % Reload target study if it changed (new channel file) + if isNewChannelFile + sOutputStudy = bst_getContext('Study', iOutputStudy); + tree_updateModel(); + end + end +end + + +%% ===== BUILD DATA ARRAY ===== +function [X, ChannelFlag, Time] = buildDataArray(sampleFiles, iTime) + % Read out the data from multiple files and store in larger array X + % Parameters: + % - sampleFiles : cell array of data file names + % - Time : time values of the samples to extract + % - isData : a flag; 0 means read from Results file, 1 from surface data file + + % Open progress bar + isNewProgressBar = ~bst_progressBar('isvisible'); + if isNewProgressBar + bst_progressBar('start', 'Loading samples...', 'Processes', 0, length(sampleFiles)); + end + ChannelFlag = []; + + % Process all the samples files + for k = 1:length(sampleFiles) + % Progress bar + bst_progressBar('inc', 1); + bst_progressBar('text', ['File: ' sampleFiles{k}]); + % Read matrix + [matValues, matName, tmpChannelFlag, TimeVector] = bst_readMatrixInFile(sampleFiles{k}, iTime); + % Get time for the specified indices + % Bug: Time = TimeVector(iTime); + Time = TimeVector; + % Add bad channels to list of bad channels + if isempty(ChannelFlag) + ChannelFlag = tmpChannelFlag; + else + ChannelFlag(tmpChannelFlag == -1) = -1; + end + % Initialize large array + if (k == 1) + X = zeros(length(sampleFiles), size(matValues,1), size(matValues,2)); + end + + % Store read values in full data array + try + X(k,:,:) = matValues; + catch + error('Please first check that all the files in your samples have the same of channels/sources. Consider registering the MEG/EEG data to the same sensor cap'); + end + end + + if isNewProgressBar + bst_progressBar('stop'); + end +end + +function [p,F,fx,ChannelFlag, TimeVector] = knd_rmanova_files(SamplesA,OPTIONS) +% knd_rmanova_files - repeated measure anova +%SamplesA = SamplesA([SamplesA.iItem]== 4) +%OPTIONS.iTime = 599:630 +[X1, ChannelFlag, TimeVector] = buildDataArray({SamplesA.FullFileName}, OPTIONS.iTime); +if OPTIONS.isAbsoluteValues + X1 = abs(X1); +end +%TimeVector=round(mean(TimeVector)); +% X1=mean(X1,3); +sX=size(X1); +% X= subj * C/I * H/L +X1=reshape(X1,[15 2 2 sX(2:end)]); +% X= C/I * H/L * Subj * vertices +X1=permute(X1,[2 3 1 4:ndims(X1)]); +[p,F,fx]=myrmanova(X1,2,1:2,[],0); +return \ No newline at end of file diff --git a/brainstorm3/bst_rmanova.m b/brainstorm3/bst_rmanova.m new file mode 100644 index 0000000..1c43171 --- /dev/null +++ b/brainstorm3/bst_rmanova.m @@ -0,0 +1,5 @@ +function [varargout] = fun(varargin) +% qsfqsdfsdfg,weQJg~Ah%7+4Ubrl7kdUa9h`5xH5J*^1ND%b@ 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 bU-C{XE;4ch>nE-k=Pw;L;P2YpaO?j7jW length(sStudy.Data)) + return + end + CurFile = sStudy.Data(iItems(i)).FileName; + else + if (iItems(i) > length(sStudy.Result)) + return + end + CurFile = sStudy.Result(iItems(i)).FileName; + end + % Compare with saved filename + if ~io_compareFileNames(CurFile, SamplesFiles{i}) + return + end + end + isOk = 1; +end + + +%% ===== TREE KEYBOARD CALLBACK ===== +function TreeKeyboard_Callback(hObj, ev) + % Switch between actions + switch (ev.getKeyCode()) + % DELETE/BACKSPACE : DELETE NODE CALLBACK + case {ev.VK_DELETE, ev.VK_BACK_SPACE} + DeleteSelectedNodes(ev.getSource()); + end +end + + +%% ===== GET SELECTED DATA TYPE ===== +function DataType = GetDataType(panelName) + % Get panel controls + ctrl = bst_getContext('PanelControls', panelName); + % Get DataType + if ctrl.jButtonRecordings.isSelected() + DataType = 'data'; + else + DataType = 'results'; + end +end + + +%% ===== DELETE SELECTED NODES ===== +function DeleteSelectedNodes(bstTree) + % Get selected nodes + treeModel = awtinvoke(bstTree, 'getModel()'); + treeSelPaths = awtinvoke(bstTree, 'getSelectionPaths()'); + % Delete each selected node + for iNode = 1:length(treeSelPaths) + awtinvoke(treeModel, 'removeNodeFromParent(Ljavax.swing.tree.MutableTreeNode;)', ... + treeSelPaths(iNode).getLastPathComponent()); + end + awtinvoke(bstTree, 'refresh()'); +end + + +%% ===== GET NODES IN TREE ===== +function bstNodes = GetTreeNodes(bstTree) + % Get root node + nodeRoot = bstTree.getModel.getRoot(); + if (nodeRoot.getChildCount() == 0) + bstNodes = []; + return + end + % Get all the children + bstNodes = javaArray('org.brainstorm.tree.BstNode', nodeRoot.getChildCount()); + for i = 1:nodeRoot.getChildCount() + bstNodes(i) = nodeRoot.getChildAt(i-1); + end +end + + +%% ===== UPDATE ITEM COUNTS ===== +function UpdateItemProperties(bstTree, listName, isForced) + global GlobalData; + % Parse inputs + if (nargin < 3) + isForced = 0; + end + + % === COUNT ITEMS DEPENDENCIES === + % Get nodes in target tree + bstNodes = GetTreeNodes(bstTree); + % Get options + if strcmpi(listName, 'SampleFilesA') + DataType = GetDataType('Processes'); + else + DataType = GetDataType('Statistics'); + end + iAllStudies = []; + iAllItems = []; + % For each node: update item count in comment + for iNode = 1:length(bstNodes) + % Get number of dependent items + [iDepStudies, iDepItems] = tree_getDependencies(bstNodes(iNode), DataType); + iAllStudies = [iAllStudies, iDepStudies]; + iAllItems = [iAllItems, iDepItems]; + % Ignore nodes that were already processed + nodeComment = char(bstNodes(iNode).getComment()); + if ~isempty(strfind(nodeComment, '[')) && ~isForced + continue; + end + % Remove previous items count + nodeComment = strRemoveParenthesis(nodeComment, '['); + % Add items count + nodeComment = sprintf('%s [%d]', nodeComment, length(iDepItems)); + % Update node comment + awtinvoke(bstNodes(iNode), 'setComment(Ljava.lang.String;)', nodeComment); + end + % Compute total number of items + totalNbItems = length(iAllItems); + % Save the sample filenames (to be able to check for database modifications later) + SampleFiles = bst_getContext('GetFileNames', iAllStudies, iAllItems, DataType); + GlobalData.Stat.(listName) = SampleFiles; + + % === UPDATE TREE DISPLAY (items and title) === + jParentPanel = bstTree.getParent.getParent.getParent(); + titledBorder = jParentPanel.getBorder(); + strTitle = strRemoveParenthesis(char(titledBorder.getTitle()), '['); + strTitle = sprintf('%s [%d]', strTitle, totalNbItems); + awtinvoke(titledBorder, 'setTitle(Ljava.lang.String;)', strTitle); + %jParentPanel.updateUI(); + awtinvoke(bstTree, 'updateUI()'); + awtinvoke(jParentPanel, 'updateUI()'); +end + + +%% ===== GET SAMPLES ===== +% USAGE: sSamples = GetSamples(jTree, DataType, FilterFileTag) : Return all the samples in JTree +% sSamples = GetSamples() : Return an empty samples structure +function sSamples = GetSamples(jTree, DataType, FilterFileTag) + % Get protocol directories + ProtocolInfo = bst_getContext('ProtocolInfo'); + % Samples structure + sSamples = repmat(struct('iStudy', 0, ... + 'iItem', 0, ... + 'FileName', '', ... + 'FullFileName', '', ... + 'Comment', '', ... + 'Condition', '', ... + 'SubjectFile', ''), 0); + % If no argment: return the empty structure + if (nargin == 0) || isempty(jTree) + return + end + + % === GET FILES IN TREE === + % Get tree nodes + bstNodes = gui_stat_common('GetTreeNodes', jTree); + % For each node: get dependencies + iStudies = []; + iItems = []; + for iNode = 1:length(bstNodes) + [iNewStudies, iNewItems] = tree_getDependencies(bstNodes(iNode), DataType, FilterFileTag); + iStudies = [iStudies, iNewStudies]; + iItems = [iItems, iNewItems]; + end + + % === GET SAMPLES DESCRIPTION === + for i = 1:length(iStudies) + % Get study + sStudy = bst_getContext('Study', iStudies(i)); + sSamples(i).iStudy = iStudies(i); + sSamples(i).iItem = iItems(i); + % Get subject + sSamples(i).SubjectFile = sStudy.BrainStormSubject; + sSubject = bst_getContext('Subject', sSamples(i).SubjectFile); + sSamples(i).SubjectName = sSubject.Name; + % Data or results + switch lower(DataType) + case 'data' + sSamples(i).FileName = sStudy.Data(iItems(i)).FileName; + sSamples(i).Comment = sStudy.Data(iItems(i)).Comment; + case 'results' + sSamples(i).FileName = sStudy.Result(iItems(i)).FileName; + sSamples(i).Comment = sStudy.Result(iItems(i)).Comment; + otherwise + error('???'); + end + % Full filename + sSamples(i).FullFileName = fullfile(ProtocolInfo.STUDIES, sSamples(i).FileName); + % Condition + if ~isempty(sStudy.Condition) + sSamples(i).Condition = sStudy.Condition{1}; + else + sSamples(i).Condition = sStudy.Name; + end + end +end + +%% ===== GET CONDITIONS ===== +% Return the current Conditions structure: +% |- DataType: {'data', 'results'} +% |- SamplesA: +% | |- iStudies: Array of study indices +% | |- iItems: Array of data or results indices +% |- SamplesB: (NULL if only one condition) +% |- iStudies: Array of study indices +% |- iItems: Array of data or results indices +% +% USAGE: Cond = GetConditions() +% Cond = GetConditions(FilterFileTag) +% +% INPUT: +% - FilterFileTag: Put a condition to the filenames to be selected +% 'normal' excludes file tags : 'zscore', 'timemean' +function Conditions = GetConditions(PanelName, FilterFileTag) + % Parse inputs + if (nargin < 2) + FilterFileTag = ''; + end + % Get data type + Conditions.DataType = GetDataType(PanelName); + % Get panel controls + ctrl = bst_getContext('PanelControls', PanelName); + + % What is the calling panel: stat or processes + switch (PanelName) + case 'Processes' + Conditions.NbSamplesSets = 1; + Conditions.SamplesA = GetSamples(ctrl.jTreeFiles, Conditions.DataType, FilterFileTag); + Conditions.SamplesB = GetSamples(); + case 'Statistics' + Conditions.NbSamplesSets = 2; + Conditions.SamplesA = GetSamples(ctrl.jTreeFilesA, Conditions.DataType, FilterFileTag); + Conditions.SamplesB = GetSamples(ctrl.jTreeFilesB, Conditions.DataType, FilterFileTag); + %Allow one-sample stats + if isempty(Conditions.SamplesB) + Conditions.NbSamplesSets = 1; + end + otherwise + error('???'); + end +end + + + diff --git a/brainstorm3/import_anova.m b/brainstorm3/import_anova.m new file mode 100644 index 0000000..266a838 --- /dev/null +++ b/brainstorm3/import_anova.m @@ -0,0 +1,438 @@ +function [sTargetStudy] = import_anova(Conditions, sProcess, OPTIONS, TimeVector, pmap, tmap) +% [] = import_anova(Conditions, sProcess, OPTIONS, TimeVector,pmap, fmap) + +SamplesA = Conditions.SamplesA; + +% % Get condition StimRightThumb +% [sStudy, iStudy] = bst_getContext('StudyWithCondition', 'Subject01/StimRightThumb'); +% +% +% %% ===== SELECT FILES ===== +% % Get the two kernels computed in tutorial #7: the regular and the shared one +% iRes1 = find(~cellfun(@(c)isempty(strfind(c, 'MN: kernel')), {sStudy.Result.Comment})); +% iRes2 = find(~cellfun(@(c)isempty(strfind(c, 'MN: shared')), {sStudy.Result.Comment}) & ... +% io_compareFileNames({sStudy.Result.DataFile}, sStudy.Data(1).FileName)); +% % Define the two sets of files, A and B +% FilesA = {sStudy.Result(iRes1(1)).FileName}; +% FilesB = {sStudy.Result(iRes2(1)).FileName}; +% + + + +% === BUILD NEW FILES === +% Display waitbar +% bst_progressBar('start', 'Processes', 'Saving results...'); + +% NB: Format de la structure des arguments : +% +% sProcess = +% Name: 'RM-Anova' +% Comment: 'Anova (under dev.)' +% Description: 'Repeated measures parametric Analysis of Variance' +% FileTag: 'Anova' +% Category: 'Anova' +% UseBaseline: 0 +% DefaultOverwrite: 0 +% isSourceAbsolute: 1 +% isPaired: 0 +% blockDimension: 0 +% isAvgRef: 1 +% +% SamplesA = +% 1x60 struct array with fields: +% iStudy +% iItem +% FileName +% FullFileName +% Comment +% Condition +% SubjectFile +% DataFile +% SubjectName +% +% SamplesA(1) +% ans = +% iStudy: 5 +% iItem: 1 +% FileName: 'S11\avg_HighCorrect\data_lena_CONFINUM.mat' +% FullFileName: 'I:\data\confinum\brainstorm\data\S11\avg_HighCorrect\data_lena_CONFINUM.mat' +% Comment: 'avg_HighCorrect' +% Condition: 'avg_HighCorrect' +% SubjectFile: 'S11/brainstormsubject.mat' +% DataFile: [] +% SubjectName: 'S11' +% SamplesA(2) +% ans = +% iStudy: 14 +% iItem: 1 +% FileName: 'S12\avg_HighCorrect\data_lena_CONFINUM.mat' +% FullFileName: 'I:\data\confinum\brainstorm\data\S12\avg_HighCorrect\data_lena_CONFINUM.mat' +% Comment: 'avg_HighCorrect' +% Condition: 'avg_HighCorrect' +% SubjectFile: 'S12/brainstormsubject.mat' +% DataFile: [] +% SubjectName: 'S12' +% SamplesA(15) +% ans = +% iStudy: 131 +% iItem: 1 +% FileName: 'S27\avg_HighCorrect\data_lena_CONFINUM.mat' +% FullFileName: 'I:\data\confinum\brainstorm\data\S27\avg_HighCorrect\data_lena_CONFINUM.mat' +% Comment: 'avg_HighCorrect' +% Condition: 'avg_HighCorrect' +% SubjectFile: 'S27/brainstormsubject.mat' +% DataFile: [] +% SubjectName: 'S27' +% SamplesA(16) +% ans = +% iStudy: 6 +% iItem: 1 +% FileName: 'S11\avg_HighIncorrect\data_lena_CONFINUM_05.mat' +% FullFileName: 'I:\data\confinum\brainstorm\data\S11\avg_HighIncorrect\data_lena_CONFINUM_05.mat' +% Comment: 'avg_HighIncorrect' +% Condition: 'avg_HighIncorrect' +% SubjectFile: 'S11/brainstormsubject.mat' +% DataFile: [] +% SubjectName: 'S11' +% +% +% OPTIONS = +% isData: 1 +% Comment: 'Anova' +% isAbsoluteValues: 0 +% sProcess: [1x1 struct] +% nbPermutation: 10000 +% Factors: [] +% isOverwriteFiles: 0 +% OutputType: 'database' +% ForceOutputCond: [] +% isCluster: 0 +% isClusterAverage: 0 +% sClusters: [] +% ClustersOptions: [] +% Time: [-0.2998 0.5996] +% Baseline: [-0.2998 3.3000e-004] +% iTime: [1x921 double] +% iBaseline: [1x308 double] +% Conditions: [1x1 struct] + + + +% load bst_stat_struct.mat + + +sProcess.Name = 'RM-ANOVA' + +[sTargetStudy, iTargetStudy isNewConditions, Comment] = GetOutputStudy(sProcess, SamplesA, SamplesA([]), OPTIONS); + +ChannelFlag = []; +% Get default structure +newFileMat = CreateDefaultStruct(sProcess, SamplesA(1).FileName, OPTIONS.isData, TimeVector, OPTIONS.iTime, ChannelFlag, 0, 0); + +% Comment +newFileMat.Comment = OPTIONS.Comment ; +% if 0% numel(fx{i_fx}) == 1 +% newFileMat.Comment = [newFileMat.Comment ' - Main effect of ']; +% else +% newFileMat.Comment = [newFileMat.Comment ' - Interaction of ']; +% end +%newFileMat.Comment = [newFileMat.Comment sprintf('%s * ',Factors.Names{fx{i_fx}})]; + +% Save P and F maps + +newFileMat.pmap = pmap; +newFileMat.tmap = tmap; +% Save new file and register in database +sTargetStudy = SaveNewFile(newFileMat, sProcess, OPTIONS.isData, iTargetStudy, sTargetStudy, SamplesA(1).FileName); +end + +%% ====================================================================================== +% ===== HELPERS ======================================================================== +% ====================================================================================== +%% ===== CREATE DEFAULT OUTPUT STRUCTURE ===== +function sFileMat = CreateDefaultStruct(sProcess, FileName, isData, TimeVector, iTime, ChannelFlag, isOneOutputMat, isSampleBySample) +% Default structures for stat +isStat = 1% ismember(sProcess.Category, {'TTest', 'PermTest', 'Anova'}); +if isStat + sStat = struct(... + 'Type', '', ... + 'Comment', '', ... + 'Time', TimeVector, ... + 'ChannelFlag', ChannelFlag, ... + 'pmap', [], ... + 'tmap', [], ... + 'pThreshold', 0.05); +end + +% If recordings +if isData + % Load file + sFileMat = load(FileName); + % Stat + if isStat + sStat.Type = 'data'; + sStat.Comment = sFileMat.Comment; + sFileMat = sStat; + % Data + else + sFileMat.Time = TimeVector; + sFileMat.F = []; + end + % Else: sources +else + % Get protocol directories + ProtocolInfo = bst_get('ProtocolInfo'); + % Resolve link, if it is a link + [FileName, DataFile] = resolveResultsLink(FileName); + % Load file + sFileMat = load(FileName); + % Stat + if isStat + sStat.Type = 'presults'; + sStat.Comment = sFileMat.Comment; + sFileMat = sStat; + else + % Time + sFileMat.Time = iTime; + sFileMat.ImageGridTime = TimeVector; + % Reset some fields + sFileMat.OPTIONS.Data = []; + sFileMat.Fsynth = []; + sFileMat.ImagingKernel = []; + sFileMat.ImageGridAmp = []; + if ~isSampleBySample + sFileMat.DataFile = []; + elseif ~isempty(DataFile) + sFileMat.DataFile = strrep(DataFile, ProtocolInfo.STUDIES, ''); + end + end + % Remove "Kernel" indications in the Comment field + sFileMat.Comment = strrep(sFileMat.Comment, '(Kernel)', ''); + sFileMat.Comment = strrep(sFileMat.Comment, 'Kernel', ''); +end +% Bad channels list +sFileMat.ChannelFlag = ChannelFlag; + +% Add some fields for exported results +if isOneOutputMat + sFileMat.DescFileName = {}; + sFileMat.DescCluster = {}; + sFileMat.DescProcessType= sProcess.Name; +end + +end + + +%% ===== SAVE NEW FILE ===== +function sTargetStudy = SaveNewFile(newFileMat, sProcess, isData, iTargetStudy, sTargetStudy, InputFile) +% Get protocol directories +ProtocolInfo = bst_get('ProtocolInfo'); +% Get default output file +OutputDir = fullfile(ProtocolInfo.STUDIES, fileparts(sTargetStudy.FileName)); +FullFileName = GetDefaultFileName(sProcess, isData, OutputDir, InputFile); +% Save in database +save(FullFileName, '-struct', 'newFileMat'); +% Register in database +isStat = 1 % ismember(sProcess.Category, {'TTest', 'PermTest', 'Anova'}); + +% why removing the path ?? +FileName = strrep(FullFileName, ProtocolInfo.STUDIES, ''); +sTargetStudy = RegisterNewFile(newFileMat, FileName, isData, isStat, iTargetStudy); + +end + + +% ===== GET OUTPUT STUDY ===== +function [sOutputStudy, iOutputStudy, isNewConditions, Comment] = GetOutputStudy(sProcess,SamplesA,SamplesB,OPTIONS) +% Get some properties of the filter +isMixedSubjectsAB = isempty(SamplesB) || ~isequal({SamplesA.SubjectFile}, {SamplesB.SubjectFile}); +isSampleBySample = ismember(sProcess.Category, {'Filter2', 'Filter', 'Extract'}); +isConditionByCondition = strcmpi(sProcess.Name, 'GAVE'); +isNewConditions = 0; +% Get the list of involved studies +uniqueStudiesInd = unique([SamplesA.iStudy, SamplesB.iStudy]); +% Comment = OPTIONS.Comment; +Comment = ''; + +% ===== CHECK SUBJECT UNICITY ===== +% Get full subjects list without repetitions +uniqueSubjectFiles = unique(cat(2, {SamplesA.SubjectFile}, {SamplesB.SubjectFile})); +% For stat on sources: all the results files must be computed on the same surface. +% => else, the number of sources might be different between different files +% => user should realign surfaces before going on +if (length(uniqueSubjectFiles) > 1) + isUniqueSubject = 0; + if ~isSampleBySample && ~OPTIONS.isData + % If there is more than one subjects involved: need to check that all + % the subjects use the default anatomy + for i = 1:length(uniqueSubjectFiles) + sSubject = bst_get('Subject', uniqueSubjectFiles{i}); + if ~sSubject.UseDefaultAnat + gui_hidePanel('panel_statRun'); + sOutputStudy = []; + iOutputStudy = []; + bst_error(['The sources files you selected use different anatomies.' 10 ... + 'In order to perform any computation on sources, all the files' 10 ... + 'should use the same anatomy.' 10 10 ... + 'You have to project all your results on a template anatomy.' 10 ... + 'Popup menu: "Project sources on default anatomy".'], 'Processes', 0); + return; + end + end + end +else + isUniqueSubject = 1; +end + +% ===== GET OUTPUT STUDY ===== +% SUBJECT AVERAGE +if strcmpi(sProcess.Name, 'SubjAvg') + % Get subject + [sSubject, iSubject] = bst_get('Subject', SamplesA(1).SubjectFile); + % Get "intra" study for this subject + [sOutputStudy, iOutputStudy] = bst_get('AnalysisIntraStudy', iSubject); + % Comment + Comment = ['SubjectAvg(' SamplesA(1).SubjectName ')']; + % UNIQUE STUDY (OR OVERWRITE) +elseif (length(uniqueStudiesInd) == 1) || OPTIONS.isOverwriteFiles + % Get this unique study + [sOutputStudy, iOutputStudy] = bst_get('Study', uniqueStudiesInd(1)); + % MULTIPLE STUDIES : SAMPLE BY SAMPLE +elseif isSampleBySample && ~isMixedSubjectsAB + % Target study is a new condition (name = test comment) + % Get subject path + newCondPath = fileparts(SamplesA(1).SubjectFile); + % Get new condition name + localCond = struct('SamplesA',SamplesA,'SamplesB',SamplesB); + newCondName = panel_statRun('FormatComment', sProcess, localCond, OPTIONS.Time); + newCondName = io_standardizeFileName(newCondName); + % Comment + Comment = sProcess.FileTag; + Comment = strrep(Comment, '#A#', localCond.SamplesA(1).Comment); + Comment = strrep(Comment, '#B#', localCond.SamplesB(1).Comment); + % Try to get an existing condition with this path + [sOutputStudy, iOutputStudy] = bst_get('StudyWithCondition', fullfile(newCondPath,newCondName)); + % If does not exist: Create a new condition + if isempty(sOutputStudy) + iOutputStudy = db_addCondition(newCondPath, newCondName, 'NoRefresh'); + sOutputStudy = bst_get('Study', iOutputStudy); + isNewConditions = 1; + end + % MULTIPLE STUDIES : CONDITION BY CONDITION +elseif isConditionByCondition + Comment = ['GAVE(' SamplesA(1).Condition ')']; + % Grand average: stored in 'analysis-inter' node + [sOutputStudy, iOutputStudy] = bst_get('AnalysisInterStudy'); + % MULTIPLE STUDIES : GLOBAL +else + % UNIQUE SUBJECT : 'analysis-intra' + if isUniqueSubject + % Subject file is the same for all the studies => find common subject + [sSubject, iSubject] = bst_get('Subject', SamplesA(1).SubjectFile); + % Get 'analysis-intra' node of this subject + [sOutputStudy, iOutputStudy] = bst_get('AnalysisIntraStudy', iSubject); + % MULTIPLE SUBJECTS : 'analysis-inter' + else + % Get 'analysis-inter' node in current protocol + [sOutputStudy, iOutputStudy] = bst_get('AnalysisInterStudy'); + end +end +% Error ? +if isempty(sOutputStudy) + error('Cannot find an analysis study to store the results.'); +end + +% ===== COMBINE CHANNEL FILES ===== +% If source and target studies are not the same +if ~isequal(uniqueStudiesInd, iOutputStudy) + % Destination study for new channel file + [tmp__, iChanStudyDest] = bst_get('ChannelForStudy', iOutputStudy); + % Source channel files studies + [tmp__, iChanStudySrc] = bst_get('ChannelForStudy', uniqueStudiesInd); + % If target study has no channel file: create a new one by combination of the others + isNewChannelFile = db_combineChannelFiles(unique(iChanStudySrc), iChanStudyDest, [], ~OPTIONS.isData); + % Reload target study if it changed (new channel file) + if isNewChannelFile + sOutputStudy = bst_get('Study', iOutputStudy); + tree_updateModel(); + end +end +end + + +%% ===== GET DEFAULT FILE NAME ===== +function OutputFile = GetDefaultFileName(sProcess, isData, OutputDir, InputFile) + % Get date and time for filename + c = clock; + strTime = sprintf('_%02.0f%02.0f%02.0f_%02.0f%02.0f', c(1)-2000, c(2:5)); + + % Output file tag + + if ismember(sProcess.Category, {'TTest', 'PermTest', 'ANOVA'}) + if isData + fileTag = 'pdata'; + else + fileTag = 'presults'; + end + else + if isData + fileTag = 'data'; + else + fileTag = 'results'; + end + end + + % Other tags present in input file + if ~isempty(strfind(InputFile, '_zscore')) + strInputTags = '_zscore'; + else + strInputTags = ''; + end + + % Default filename + defFileName = [fileTag '_' sProcess.Name strTime strInputTags '.mat']; + % File in the target study + OutputFile = fullfile(OutputDir, defFileName); + % Make filename unique + OutputFile = io_makeUniqueFilename(OutputFile); +end + + + + +%% ===== REGISTER NEW FILE ===== +function sStudy = RegisterNewFile(FileMat, FileName, isData, isStat, iStudy) + % Get study + sStudy = bst_get('Study', iStudy); + % Stat + if isStat + % Create new stat file descriptor + % sNewStat = db_getDataTemplate('Stat'); + sNewStat = db_template('stat'); + sNewStat.FileName = FileName; + sNewStat.Comment = FileMat.Comment; + sNewStat.Type = FileMat.Type; + sNewStat.pThreshold = FileMat.pThreshold; + % Add it to study + sStudy.Stat(end+1) = sNewStat; + % Data + elseif isData + % Create new data descriptor + sNewData = db_template('data'); + sNewData.FileName = FileName; + sNewData.Comment = FileMat.Comment; + % Add it to study + sStudy.Data(end+1) = sNewData; + % Results + else + % Create new results descriptor + sNewResult = db_template('results'); + sNewResult.FileName = FileName; + sNewResult.Comment = FileMat.Comment; + sNewResult.DataFile = FileMat.DataFile; + % Add it to study + sStudy.Result(end+1) = sNewResult; + end + % Update database + bst_set('Study', iStudy, sStudy); +end \ No newline at end of file diff --git a/brainstorm3/knd_rmanova_files.m b/brainstorm3/knd_rmanova_files.m new file mode 100644 index 0000000..a08596d --- /dev/null +++ b/brainstorm3/knd_rmanova_files.m @@ -0,0 +1,180 @@ +function [p,F,fx,ChannelFlag, TimeVector] = knd_rmanova_files(SamplesA,OPTIONS) +% knd_rmanova_files - repeated measure anova + +%SamplesA = SamplesA([SamplesA.iItem]== 4) + +%OPTIONS.iTime = 599:630 + +[X1, ChannelFlag, TimeVector] = buildDataArray({SamplesA.FullFileName}, OPTIONS.iTime); +if OPTIONS.isAbsoluteValues + X1 = abs(X1); +end +%TimeVector=round(mean(TimeVector)); +% X1=mean(X1,3); +sX=size(X1); +% X= subj * C/I * H/L +X1=reshape(X1,[15 2 2 sX(2:end)]); +% X= C/I * H/L * Subj * vertices +X1=permute(X1,[2 3 1 4:ndims(X1)]); +[p,F,fx]=myrmanova(X1,2,1:2,[],0); +return + + + +% %% Get conditions common to all subjects +% function [isCommon]=GetCommonConditions(Samples) +% [C,iC,jC] = unique({Conditions.Samples.Condition}'); +% [S,jS,jS] = unique({Samples.SubjectName}); +% isCommon(iC) = 0*jC; +% for i = 1:numel(C) +% if isempty(setdiff(1:NS,jS(jC==i))) +% isCommon(jC==i) = 1; +% end +% end +% end +% + +%% ===== BUILD DATA ARRAY ===== +function [X, ChannelFlag, Time] = buildDataArray(sampleFiles, iTime) +% Read out the data from multiple files and store in larger array X +% Parameters: +% - sampleFiles : cell array of data file names +% - Time : time values of the samples to extract +% - isData : a flag; 0 means read from Results file, 1 from surface data file + +% Open progress bar +isNewProgressBar = ~bst_progressBar('isvisible'); +if isNewProgressBar + bst_progressBar('start', 'Loading samples...', 'Processes', 0, length(sampleFiles)); +end +ChannelFlag = []; + +% Process all the samples files +for k = 1:length(sampleFiles) + % Progress bar + bst_progressBar('inc', 1); + bst_progressBar('text', ['File: ' sampleFiles{k}]); + % Read matrix + [matValues, matName, tmpChannelFlag, TimeVector] = bst_readMatrixInFile(sampleFiles{k}, iTime); + % Get time for the specified indices + % Bug: Time = TimeVector(iTime); + Time = TimeVector; + % Add bad channels to list of bad channels + if isempty(ChannelFlag) + ChannelFlag = tmpChannelFlag; + else + ChannelFlag(tmpChannelFlag == -1) = -1; + end + % Initialize large array + if (k == 1) + X = zeros(length(sampleFiles), size(matValues,1), size(matValues,2)); + end + + % Store read values in full data array + try + X(k,:,:) = matValues; + catch + error('Please first check that all the files in your samples have the same of channels/sources. Consider registering the MEG/EEG data to the same sensor cap'); + end +end + +if isNewProgressBar + bst_progressBar('stop'); +end + +return + + +%function [p, t, ChannelFlag, TimeA, TimeB] = knd_ttest_files(FilesListA, FilesListB, wtest, iTimeA, iTimeB, varargin) + +% KND_TTEST_FILES: Student's t-tests for EEG/MEG recordings or sources, across trials or subjects. +% +% USAGE: [p, t, ChannelFlag, TimeA, TimeB] = knd_ttest_files(FilesListA, FilesListB, wtest, iTimeA, iTimeB) +% [p, t, ChannelFlag, TimeA, TimeB] = knd_ttest_files(FilesListA, FilesListB, wtest) +% [p, t, ChannelFlag, TimeA, TimeB] = knd_ttest_files(FilesListA, FilesListB) +% [p, t, ChannelFlag, TimeA, TimeB] = knd_ttest_files(..., 'AbsoluteValues'); + + + +% +% DESCRIPTION: +% Gives the probability that Student's t calculated on sets A and B, sampled from two +% distributions is higher than observed, i.e. the "significance" level. +% This is used to test whether two samples have significantly different means. +% +% INPUT: +% - FilesListA : Cell-array of paths to brainstorm recordings or sources files. +% - FilesListB : Cell-array of paths to brainstorm recordings or sources files. +% - wtest : String that specifies the test to be used. Possible values: +% - 'ttest' : t-test for equal variances [default] +% - 'uttest' : t-test for unequal variances +% - 'pttest' : t-test for paired samples +% - iTimeA : Time indices used from samples A +% - iTimeB : Time indices used from samples B +% - AbsoluteValues : Use absolute values of samples +% +% OUTPUT: +% - P : Probability map +% The smaller P is, the more significant the difference between the means. +% E.g. if P = 0.05 or 0.01, it is very likely that the two sets are +% sampled from distributions with different means. +% - T : Value of Student's t +% - ChannelFlag : Array with one entry per channel: +% -1 means that the channel was marked as bad in at least one of the input files +% 1 means that the channel was marked as good in all the input files + +% @============================================================================= +% This software is part of The Brainstorm Toolbox +% http://neuroimage.usc.edu/brainstorm +% +% Copyright (c)2009 Brainstorm by the University of Southern California +% This software is distributed under the terms of the GNU General Public License +% as published by the Free Software Foundation. Further details on the GPL +% license can be found at http://www.gnu.org/copyleft/gpl.html. +% +% FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE +% UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY +% WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF +% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY +% LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE. +% +% For more information type "brainstorm licence" at command prompt. +% =============================================================================@ +% +% Authors: K. N'Diaye, 2005 (Adapted from C. Goutte's functions) +% ----------------------------- Script History --------------------------------- +% KND 20-Apr-2005 Creation +% FT 24-Jun-2008 Adaptation for brainstorm3 +% ------------------------------------------------------------------------------ + + + + + + +%% ===== PARSE INPUTS ===== +if (nargin < 5) + iTimeA = []; + iTimeB = []; +end +% OPTIONS +isAbsoluteValues = 0; +if (length(varargin) >= 1) + if any(strcmpi(varargin, 'AbsoluteValues')) + isAbsoluteValues = 1; + end +end +% Dimensions +n1 = length(FilesListA); +n2 = length(FilesListB); +% There must be at least two samples in each set +if (n1 <= 1) || (n2 <= 1) + error('There must be at least two samples in each set.'); +end +% If paired test: number of samples must be equal +if strcmpi(wtest, 'pttest') && (n1 ~= n2) + error('For a paired t-test, number of samples must be equal in the two datasets'); +end +if (length(iTimeA) ~= length(iTimeB)) + error('You must specify the same numer of time samples for both datasets.'); +end \ No newline at end of file diff --git a/brainstorm3/om_load_full.m b/brainstorm3/om_load_full.m new file mode 100644 index 0000000..94c82f7 --- /dev/null +++ b/brainstorm3/om_load_full.m @@ -0,0 +1,6 @@ +function data = om_load_full(filename) + file = fopen(filename,'r'); + dims = fread(file,2,'uint32','ieee-le'); + data = fread(file,prod(dims),'double','ieee-le'); + data = reshape(data,dims'); + fclose(file); diff --git a/brainstorm3/private/gui_setEnabledControls.m b/brainstorm3/private/gui_setEnabledControls.m new file mode 100755 index 0000000..a614faf --- /dev/null +++ b/brainstorm3/private/gui_setEnabledControls.m @@ -0,0 +1,62 @@ +function gui_setEnabledControls( controls, status, isRecursive ) +% GUI_SETENABLEDCONTROLS: Enable/disable control and control's children. +% +% USAGE: gui_setEnabledControls( controls, status, isRecursive); +% +% INPUT: +% - controls : array of java components handles +% - status : {0,1} - If 1, controls are enabled +% - isRecursive : enable/disable component's children recursively + +% @============================================================================= +% This software is part of The Brainstorm Toolbox +% http://neuroimage.usc.edu/brainstorm +% +% Copyright (c)2009 Brainstorm by the University of Southern California +% This software is distributed under the terms of the GNU General Public License +% as published by the Free Software Foundation. Further details on the GPL +% license can be found at http://www.gnu.org/copyleft/gpl.html. +% +% FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE +% UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY +% WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF +% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY +% LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE. +% +% For more information type "brainstorm licence" at command prompt. +% =============================================================================@ +% +% Authors: Francois Tadel, 2008 +% ----------------------------- Script History --------------------------------- +% FT 24-Jun-2008 Creation +% ------------------------------------------------------------------------------ + + +if (nargin < 3) + isRecursive = 1; +end +for iControl = 1:length(controls) + ctrl1 = controls(iControl); + % Enable/Disable component + ctrl1.setEnabled(status); + % And all its children components + if isRecursive && ~isa(ctrl1, 'javax.swing.JList') + for iChild = 1:ctrl1.getComponentCount() + ctrl2 = ctrl1.getComponent(iChild - 1); + % Enable/Disable component child + ctrl2.setEnabled(status); + % And all its sub-children components + for iChildChild = 1:ctrl2.getComponentCount() + ctrl3 = ctrl2.getComponent(iChildChild - 1); + ctrl3.setEnabled(status); + for iChildChildChild = 1:ctrl3.getComponentCount() + ctrl4 = ctrl3.getComponent(iChildChildChild - 1); + ctrl4.setEnabled(status); + end + end + end + end +end + + + diff --git a/brainstorm3/private/gui_stat_common.m b/brainstorm3/private/gui_stat_common.m new file mode 100755 index 0000000..57b4a85 --- /dev/null +++ b/brainstorm3/private/gui_stat_common.m @@ -0,0 +1,311 @@ +function varargout = gui_stat_common(varargin) +% GUI_STAT_COMMON: Support functions, common to all the stat GUI. + +% @============================================================================= +% This software is part of The Brainstorm Toolbox +% http://neuroimage.usc.edu/brainstorm +% +% Copyright (c)2009 Brainstorm by the University of Southern California +% This software is distributed under the terms of the GNU General Public License +% as published by the Free Software Foundation. Further details on the GPL +% license can be found at http://www.gnu.org/copyleft/gpl.html. +% +% FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE +% UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY +% WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF +% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY +% LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE. +% +% For more information type "brainstorm licence" at command prompt. +% =============================================================================@ +% +% Authors: Francois Tadel, 2009 +% ----------------------------- Script History --------------------------------- +% FT 10-Jul-2009 Creation +% ------------------------------------------------------------------------------ + +% No parameters +if (nargin == 0) +% Else : execute appropriate local function +elseif ischar(varargin{1}) + if (nargout) + [varargout{1:nargout}] = bst_safeCall(str2func(varargin{1}), varargin{2:end}); + else + bst_safeCall(str2func(varargin{1}), varargin{2:end}); + end +end +end + + +%% ===== CHECK CONDITIONS ===== +function isOk = CheckConditions(PanelName) + global GlobalData; + isOk = 0; + + % ===== GET SAMPLES ===== + % Get conditions + Conditions = gui_stat_common('GetConditions', PanelName); + if isempty(Conditions) + return + end + isData = strcmpi(Conditions.DataType, 'data'); + % Check samples number + switch lower(PanelName) + case 'statistics' + iStudies = [Conditions.SamplesA.iStudy, Conditions.SamplesB.iStudy]; + iItems = [Conditions.SamplesA.iItem, Conditions.SamplesB.iItem]; + SamplesFiles = cat(2, GlobalData.Stat.SampleFilesAA, GlobalData.Stat.SampleFilesAB); + case 'processes' + iStudies = [Conditions.SamplesA.iStudy]; + iItems = [Conditions.SamplesA.iItem]; + SamplesFiles = GlobalData.Stat.SampleFilesA; + end + + % ===== CHECK FOR MODIFICATIONS ===== + % Check number of elements + if (length(SamplesFiles) ~= length(iStudies)) + return + end + % Check all the samples + for i = 1:length(SamplesFiles) + % Get study + sStudy = bst_getContext('Study', iStudies(i)); + if isempty(sStudy) + return + end + % Get filenames + if isData + if (iItems(i) > length(sStudy.Data)) + return + end + CurFile = sStudy.Data(iItems(i)).FileName; + else + if (iItems(i) > length(sStudy.Result)) + return + end + CurFile = sStudy.Result(iItems(i)).FileName; + end + % Compare with saved filename + if ~io_compareFileNames(CurFile, SamplesFiles{i}) + return + end + end + isOk = 1; +end + + +%% ===== TREE KEYBOARD CALLBACK ===== +function TreeKeyboard_Callback(hObj, ev) + % Switch between actions + switch (ev.getKeyCode()) + % DELETE/BACKSPACE : DELETE NODE CALLBACK + case {ev.VK_DELETE, ev.VK_BACK_SPACE} + DeleteSelectedNodes(ev.getSource()); + end +end + + +%% ===== GET SELECTED DATA TYPE ===== +function DataType = GetDataType(panelName) + % Get panel controls + ctrl = bst_getContext('PanelControls', panelName); + % Get DataType + if ctrl.jButtonRecordings.isSelected() + DataType = 'data'; + else + DataType = 'results'; + end +end + + +%% ===== DELETE SELECTED NODES ===== +function DeleteSelectedNodes(bstTree) + % Get selected nodes + treeModel = awtinvoke(bstTree, 'getModel()'); + treeSelPaths = awtinvoke(bstTree, 'getSelectionPaths()'); + % Delete each selected node + for iNode = 1:length(treeSelPaths) + awtinvoke(treeModel, 'removeNodeFromParent(Ljavax.swing.tree.MutableTreeNode;)', ... + treeSelPaths(iNode).getLastPathComponent()); + end + awtinvoke(bstTree, 'refresh()'); +end + + +%% ===== GET NODES IN TREE ===== +function bstNodes = GetTreeNodes(bstTree) + % Get root node + nodeRoot = bstTree.getModel.getRoot(); + if (nodeRoot.getChildCount() == 0) + bstNodes = []; + return + end + % Get all the children + bstNodes = javaArray('org.brainstorm.tree.BstNode', nodeRoot.getChildCount()); + for i = 1:nodeRoot.getChildCount() + bstNodes(i) = nodeRoot.getChildAt(i-1); + end +end + + +%% ===== UPDATE ITEM COUNTS ===== +function UpdateItemProperties(bstTree, listName, isForced) + global GlobalData; + % Parse inputs + if (nargin < 3) + isForced = 0; + end + + % === COUNT ITEMS DEPENDENCIES === + % Get nodes in target tree + bstNodes = GetTreeNodes(bstTree); + % Get options + if strcmpi(listName, 'SampleFilesA') + DataType = GetDataType('Processes'); + else + DataType = GetDataType('Statistics'); + end + iAllStudies = []; + iAllItems = []; + % For each node: update item count in comment + for iNode = 1:length(bstNodes) + % Get number of dependent items + [iDepStudies, iDepItems] = tree_getDependencies(bstNodes(iNode), DataType); + iAllStudies = [iAllStudies, iDepStudies]; + iAllItems = [iAllItems, iDepItems]; + % Ignore nodes that were already processed + nodeComment = char(bstNodes(iNode).getComment()); + if ~isempty(strfind(nodeComment, '[')) && ~isForced + continue; + end + % Remove previous items count + nodeComment = strRemoveParenthesis(nodeComment, '['); + % Add items count + nodeComment = sprintf('%s [%d]', nodeComment, length(iDepItems)); + % Update node comment + awtinvoke(bstNodes(iNode), 'setComment(Ljava.lang.String;)', nodeComment); + end + % Compute total number of items + totalNbItems = length(iAllItems); + % Save the sample filenames (to be able to check for database modifications later) + SampleFiles = bst_getContext('GetFileNames', iAllStudies, iAllItems, DataType); + GlobalData.Stat.(listName) = SampleFiles; + + % === UPDATE TREE DISPLAY (items and title) === + jParentPanel = bstTree.getParent.getParent.getParent(); + titledBorder = jParentPanel.getBorder(); + strTitle = strRemoveParenthesis(char(titledBorder.getTitle()), '['); + strTitle = sprintf('%s [%d]', strTitle, totalNbItems); + awtinvoke(titledBorder, 'setTitle(Ljava.lang.String;)', strTitle); + %jParentPanel.updateUI(); + awtinvoke(bstTree, 'updateUI()'); + awtinvoke(jParentPanel, 'updateUI()'); +end + + +%% ===== GET SAMPLES ===== +% USAGE: sSamples = GetSamples(jTree, DataType, FilterFileTag) : Return all the samples in JTree +% sSamples = GetSamples() : Return an empty samples structure +function sSamples = GetSamples(jTree, DataType, FilterFileTag) + % Get protocol directories + ProtocolInfo = bst_getContext('ProtocolInfo'); + % Samples structure + sSamples = repmat(struct('iStudy', 0, ... + 'iItem', 0, ... + 'FileName', '', ... + 'FullFileName', '', ... + 'Comment', '', ... + 'Condition', '', ... + 'SubjectFile', ''), 0); + % If no argment: return the empty structure + if (nargin == 0) || isempty(jTree) + return + end + + % === GET FILES IN TREE === + % Get tree nodes + bstNodes = gui_stat_common('GetTreeNodes', jTree); + % For each node: get dependencies + iStudies = []; + iItems = []; + for iNode = 1:length(bstNodes) + [iNewStudies, iNewItems] = tree_getDependencies(bstNodes(iNode), DataType, FilterFileTag); + iStudies = [iStudies, iNewStudies]; + iItems = [iItems, iNewItems]; + end + + % === GET SAMPLES DESCRIPTION === + for i = 1:length(iStudies) + % Get study + sStudy = bst_getContext('Study', iStudies(i)); + sSamples(i).iStudy = iStudies(i); + sSamples(i).iItem = iItems(i); + % Get subject + sSamples(i).SubjectFile = sStudy.BrainStormSubject; + sSubject = bst_getContext('Subject', sSamples(i).SubjectFile); + sSamples(i).SubjectName = sSubject.Name; + % Data or results + switch lower(DataType) + case 'data' + sSamples(i).FileName = sStudy.Data(iItems(i)).FileName; + sSamples(i).Comment = sStudy.Data(iItems(i)).Comment; + case 'results' + sSamples(i).FileName = sStudy.Result(iItems(i)).FileName; + sSamples(i).Comment = sStudy.Result(iItems(i)).Comment; + otherwise + error('???'); + end + % Full filename + sSamples(i).FullFileName = fullfile(ProtocolInfo.STUDIES, sSamples(i).FileName); + % Condition + if ~isempty(sStudy.Condition) + sSamples(i).Condition = sStudy.Condition{1}; + else + sSamples(i).Condition = sStudy.Name; + end + end +end + +%% ===== GET CONDITIONS ===== +% Return the current Conditions structure: +% |- DataType: {'data', 'results'} +% |- SamplesA: +% | |- iStudies: Array of study indices +% | |- iItems: Array of data or results indices +% |- SamplesB: (NULL if only one condition) +% |- iStudies: Array of study indices +% |- iItems: Array of data or results indices +% +% USAGE: Cond = GetConditions() +% Cond = GetConditions(FilterFileTag) +% +% INPUT: +% - FilterFileTag: Put a condition to the filenames to be selected +% 'normal' excludes file tags : 'zscore', 'timemean' +function Conditions = GetConditions(PanelName, FilterFileTag) + % Parse inputs + if (nargin < 2) + FilterFileTag = ''; + end + % Get data type + Conditions.DataType = GetDataType(PanelName); + % Get panel controls + ctrl = bst_getContext('PanelControls', PanelName); + + % What is the calling panel: stat or processes + switch (PanelName) + case 'Processes' + Conditions.NbSamplesSets = 1; + Conditions.SamplesA = GetSamples(ctrl.jTreeFiles, Conditions.DataType, FilterFileTag); + Conditions.SamplesB = GetSamples(); + case 'Statistics' + Conditions.NbSamplesSets = 2; + Conditions.SamplesA = GetSamples(ctrl.jTreeFilesA, Conditions.DataType, FilterFileTag); + Conditions.SamplesB = GetSamples(ctrl.jTreeFilesB, Conditions.DataType, FilterFileTag); + otherwise + error('???'); + end +end + + + diff --git a/brainstorm3/sTargetStudy_struct.mat b/brainstorm3/sTargetStudy_struct.mat new file mode 100644 index 0000000000000000000000000000000000000000..64ec1016c0d0f8be72e981dc079f1c3a4afc4e9c GIT binary patch literal 1336 zcmb8ci$Bu|00401b=1i`N=^LMr*exihJ+8+qK|IE4pVJ3XC4_DHVqpo3bmC;VR49Z z2*X8KsXW4l6y-5x&b*Vy7{9r{;l5utv=uXD&1_peV$X(@vZxDDe_%Ok;ewRyI{R^nC3A2c?5qdHc7A87RvVgM>wr6xWmO}^RL~Cj-1@tOqr(l54*b~g zLAYyEZn9M_y65|T93T>Ssf4Ab@Pos;SIAvNw7JOHN3cQ+5Ut9xzSuR*kc>ihFz2@7 z3&ECW;HI+j9Io2*Js6@X$e3L$k+Am0JV)gGL{O&p)DrH-h?TfMzSG+1r>cl5b=^ty zI=R1OjjF!&7qzjSGJC)Ox78Q9R(hAUHx9v8l2aZ^uv%8R=pwC+jkLwDsr08ry_YF& zLs5dN`D%4BFUw*v(ywleueLHZM79yx^|W>i5A;N4Udy~EF}uRMHhWLG)L>rji=Qed zcsF=G^{5(nmU~p-xVvf4VK5#9TS^_Pa@1B*@+Aqp9tTVxzh6t5aU?^gY&EO;>-<_J z+n;CZtI8gS6pP)qr3vO=5)BKN<(JM*Zl8^YBAlDzi}Lu9;C5CUX#AS{wWokxAS38H zO7$xjoc$2l|0EO4D_O;j2uK4?5FVD=0t1iNm`0`4+DG!?1H!vqt9&KdAWI)Ru^grt zb$aIwt?sqFXkk8!%2aK4UWiut44A5aTdz(#+)`qH z06$(*T8so3aPjsZYWOLFhK6)$>r?VATx~R=D}v9C{CH2y1NF}lz_+@AuC zh?Pn^pM)|Pj9TfN$?*p@%j~*>WBgl}_4gS~-~)~7LxpcAH$R#1pqlxKtoOG?meV2a z`zTxBMh2{C2d{s;nSaM7xsv2r)xKo*@#kQ2AwP-Do-W|v(l_mY@8>^fb`n-9-r4(? pNJO6*OT=%zj;hBuiNU#|nh0DDhchQ(S^MN9+NJj;Mz^)9{09JoozegR literal 0 HcmV?d00001 diff --git a/brainstorm3/script_anova.m b/brainstorm3/script_anova.m new file mode 100644 index 0000000..807f8e5 --- /dev/null +++ b/brainstorm3/script_anova.m @@ -0,0 +1,52 @@ +sModel = struct('Name', '', ... + 'Comment', '', ... + 'Description', '', ... + 'FileTag', '', ... + 'Category', '', ... + 'UseBaseline', 0, ... + 'DefaultOverwrite', 0, ... + 'isSourceAbsolute', 1, ... % If value=2, absolute value for sources is FORCED + 'isPaired', 0, ... + 'blockDimension', 0, ... % Dimension in which the data matrix can be split (0=none, 1=channels, 2=time) + 'isAvgRef', 1); % Compute EEG average reference before processing + +sTest = sModel; +sTest.Name = 'RM-Anova'; +sTest.Comment = 'Anova (under dev.)'; +sTest.FileTag = 'Anova'; +sTest.Description = 'Repeated measures parametric Analysis of Variance'; +sTest.Category = 'Anova'; +sAnova(end + 1) = sTest; + +OPTIONS = struct(); + +isData = 0; + +% Is processing recordings or results +OPTIONS.isData = isData; + + +% Get new file comment +OPTIONS.Comment = deblank(strtrim(char(jTextComment.getText()))); +% Is absolute values (for sources only) +OPTIONS.isAbsoluteValues = 0 ; % ~isData && jCheckAbsoluteValues.isSelected(); +% Get selected process +[OPTIONS.sProcess, selectedTab] = GetSelectedProcess(); + +OPTIONS.Factors = []; + +OPTIONS.isOverwriteFiles = 0; + +OPTIONS.OutputType = 'database'; +OPTIONS.ForceOutputCond = []; +OPTIONS.sClusters = []; +OPTIONS.ClustersOptions = []; + +% Get time +[OPTIONS.Time, OPTIONS.Baseline, OPTIONS.iTime, OPTIONS.iBaseline] = GetTimeWindows(TimeUnit); + +% Get files to batch +OPTIONS.Conditions = Conditions; + +bst_batch(OPTIONS); +panel_stat('ResetPanel'); \ No newline at end of file diff --git a/brainstorm3/script_import_Fmap.m b/brainstorm3/script_import_Fmap.m new file mode 100644 index 0000000..9fdbe26 --- /dev/null +++ b/brainstorm3/script_import_Fmap.m @@ -0,0 +1,135 @@ +% import des r?sultats de l'anovabst de Lydia dans BrainSotmr +% inspir? de TUTORIAL_10_STAT: Script that follows Brainstorm online +% tutorial #10: "Statistics" +%Auteurs: Karim N'Diaye, CTB + +%Alancer avec BrainSorm ouvert, onglet Statistics actif et case "cources" +%coch?e +% Appel import_anova + +%%% constantes +CONDITIONS = {'all_gdavg', 'anova'} + +%CONDITIONS = {'UNIT_EURO' 'UNIT_DOLLAR' 'UNIT_FRANC' 'UNIT_MARK' ... +% 'CENT_EURO' 'CENT_DOLLAR' 'CENT_FRANC' 'CENT_MARK'... +% 'SCR_UNIT_EURO' 'SCR_UNIT_DOLLAR' 'SCR_UNIT_FRANC' 'SCR_UNIT_MARK' ... +% 'SCR_CENT_EURO' 'SCR_CENT_DOLLAR' 'SCR_CENT_FRANC' 'SCR_CENT_MARK'}; + +%Pour ANOVA 3WAY +SUBJECTS = {'ALL'}; + +%Pour ANOVA 4WAY +%SUBJECTS = {'Subject01' 'Subject02' 'Subject03' 'Subject04' 'Subject05' 'Subject06' ... +% 'Subject08' 'Subject09' 'Subject10' 'Subject11' 'Subject12' 'Subject13' ... +% 'Subject21' 'Subject22' 'Subject23' 'SubjectPilot'}; + +path2bst='/data/brainstorm/confinum/data' +%path2bst='/pclxserver/raid6/data/MONEY/BrainStormData/MONEY_4WAY/data/' +path2anovares='.' +StructName='StatSources'; +FilesToFind='MN: MEG(Constr)'; %'MN: MEG(Full,Constr) | zscore' +FileComment='Anova1'; %%% Attention: nom sous lequel le fichier apparaitra dans la base BST +liste_effects=[1 2 12]; +%liste_effects=[1 2 3 12 13 23 123]; +%liste_effects=[1 2 3 4 12 13 14 23 24 34 123 124 134 234 1234]; + +%% Creat File List +i= 1; +clear FilesA +for i_sub=1:length(SUBJECTS) + for i_cond = 1:length(CONDITIONS) + + % Get condition StimRightThumb + [sStudy, iStudy] = bst_get('StudyWithCondition', [ SUBJECTS{i_sub} '/' CONDITIONS{i_cond}]); + + %% ===== SELECT FILES ===== + % Get the two kernels computed in tutorial #7: the regular and the shared one + iRes1 = find(~cellfun(@(c)isempty(strfind(c, FilesToFind)), {sStudy.Result.Comment})) + + iRes1 = iRes1(1); + % Define the list of files A + FilesA(i) = {[path2bst sStudy.Result(iRes1(1)).FileName]}; + i=i+1; + end +end +%% Panel +% Remove previous nodes in the Statistics panel +panel_stat('ResetPanel'); +% Add files to the Statistics panel +nFilesA = gui_stat_common('SetFilesToProcess', 'StatA', FilesA); + +% Set types of files to process: data/results +isData = 0; +if isData + panel_stat('SetFileType', 'data'); +else + panel_stat('SetFileType', 'presults'); +end +% Conditions: structure that define the files in the Processes list +Conditions = gui_stat_common('GetConditions', 'Statistics'); +% Set types of files to process: data/results/timefreq +DataType = 'results'; +panel_processes('SetFileType', DataType); + +%% ===== GET PROCESS ===== +% Get processes list, and pick a process in it +[sProcessesList, sSimpleTests, sPermTests] = panel_statRun('GetProcessesList', 'All'); +% Find a process using its name +ProcessName = 'diffAB'; +iProc = find(strcmpi({sProcessesList.Name}, ProcessName)); +if isempty(iProc) + error('Process not found.'); +end +% Process to use +sProcess = sProcessesList(iProc); + +sProcess.Name='ANOVA' +sProcess.Description = ''; +sProcess.Category = 'ANOVA'; +sProcess.FileTag = '' + +%% ===== TIME ===== +% Use all the time samples +TimeVector = panel_statRun('GetFileTimeVector', Conditions.SamplesA(1).iStudy, Conditions.SamplesA(1).iItem, DataType); +Time = [TimeVector(1), TimeVector(end)]; +iTime = 1:length(TimeVector); + + +Baseline =[]; +iBaseline =[]; +%% ===== DEFINE OPTIONS ===== +OPTIONS.Conditions = Conditions; % Files to process +OPTIONS.sProcess = sProcess; % Process to apply +OPTIONS.isData = isData; % Process data or recordings +OPTIONS.Comment = 'myanova'; % Default comment for output files (might be overridden) +OPTIONS.OutputType = 'database'; % Where o store the results: {database, file, matlab} +OPTIONS.ForceOutputCond = []; % When you want to store the result in a specific condition (used only when OutputType='database')=> Ex. 'Subject01/@intra' +OPTIONS.isOverwriteFiles = 0; % Overwrite input files, only in the case of filters (one input file = one output file) +OPTIONS.isAbsoluteValues = 1; % Compute the absolute value of the data before applying the process (usually 1 for sources, 0 for recordings) +OPTIONS.Time = Time; % Time window to process [tStart, tStop] in seconds +OPTIONS.iTime = iTime; % Time window: Indices in time vector for the full file +OPTIONS.Baseline = Baseline; % Some processes requires a baseline definition (it is the case for the zscore) +OPTIONS.iBaseline = iBaseline; % => Baseline and iBaseline work exactly the same way as Time and iTime +% Other options we do not use here: +OPTIONS.nbPermutation = 0; % For permuation tests only +OPTIONS.isCluster = 0; % Extract only some clusters/scouts values +OPTIONS.isClusterAverage = 0; % If 1, group all the clusters/scouts; If 0, consider they are separate +OPTIONS.sClusters = []; % Array of scouts/clusters structures +OPTIONS.ClustersOptions = []; % Structure that defines how the clusters/scouts values are computed (fields: function, isAbsolute) +OPTIONS.Baseline = []; +OPTIONS.iBaseline = []; + + +n_effects=size(liste_effects,2); +for i_effects = 1:n_effects + % Call processing function + %OutputFiles = bst_batch(OPTIONS); + load (strcat(path2anovares, StructName, 'F', num2str(liste_effects(i_effects)),'.mat')) + load (strcat(path2anovares, StructName, 'p', num2str(liste_effects(i_effects)),'.mat')) + p = tempStructp.ImageGridAmp; + F = tempStructF.ImageGridAmp; + OPTIONS.Comment=strcat(FileComment,'_F',num2str(liste_effects(i_effects))) + [sTargetStudy] = import_anova(Conditions, sProcess, OPTIONS, TimeVector, p, F); +end + + diff --git a/brainstorm3/script_import_anova.m b/brainstorm3/script_import_anova.m new file mode 100644 index 0000000..b015b76 --- /dev/null +++ b/brainstorm3/script_import_anova.m @@ -0,0 +1,136 @@ +% import des r?sultats de l'anovabst de Lydia dans BrainSotmr +% inspir? de TUTORIAL_10_STAT: Script that follows Brainstorm online +% tutorial #10: "Statistics" +%Auteurs: Karim N'Diaye, CTB + +%Alancer avec BrainSorm ouvert, onglet Statistics actif et case "cources" +%coch?e +% Appel import_anova + +%%% constantes +CONDITIONS = {''} + +%CONDITIONS = {'UNIT_EURO' 'UNIT_DOLLAR' 'UNIT_FRANC' 'UNIT_MARK' ... +% 'CENT_EURO' 'CENT_DOLLAR' 'CENT_FRANC' 'CENT_MARK'... +% 'SCR_UNIT_EURO' 'SCR_UNIT_DOLLAR' 'SCR_UNIT_FRANC' 'SCR_UNIT_MARK' ... +% 'SCR_CENT_EURO' 'SCR_CENT_DOLLAR' 'SCR_CENT_FRANC' 'SCR_CENT_MARK'}; + +%Pour ANOVA 3WAY +SUBJECTS = {'sujet_01' 'sujet_02' 'sujet_03' 'sujet_04' 'sujet_05' 'sujet_06' ... + 'sujet_08' 'sujet_09' 'sujet_10' 'sujet_11' 'sujet_12' 'sujet_13' ... + 'sujet_21' 'sujet_22' 'sujet_23' 'sujet_pilote'}; + +%Pour ANOVA 4WAY +%SUBJECTS = {'Subject01' 'Subject02' 'Subject03' 'Subject04' 'Subject05' 'Subject06' ... +% 'Subject08' 'Subject09' 'Subject10' 'Subject11' 'Subject12' 'Subject13' ... +% 'Subject21' 'Subject22' 'Subject23' 'SubjectPilot'}; + +path2bst='/pclxserver/raid6/data/MONEY/BrainStormData/MONEY/data/' +%path2bst='/pclxserver/raid6/data/MONEY/BrainStormData/MONEY_4WAY/data/' +path2anovares='/pclxserver/raid6/data/MONEY/Analyze16suj/StatSources/RawAbsolute/' +StructName='Sources3way'; +FilesToFind='MN: MEG(Full,Constr)'; %'MN: MEG(Full,Constr) | zscore' +FileComment='AbsRaw_An3F'; %%% Attention: nom sous lequel le fichier apparaitra dans la base BST +%liste_effects=[1 2 12]; +liste_effects=[1 2 3 12 13 23 123]; +%liste_effects=[1 2 3 4 12 13 14 23 24 34 123 124 134 234 1234]; + +%% Creat File List +i= 1; +clear FilesA +for i_sub=1:length(SUBJECTS) + for i_cond = 1:length(CONDITIONS) + + % Get condition StimRightThumb + [sStudy, iStudy] = bst_get('StudyWithCondition', [ SUBJECTS{i_sub} '/' CONDITIONS{i_cond}]); + + %% ===== SELECT FILES ===== + % Get the two kernels computed in tutorial #7: the regular and the shared one + iRes1 = find(~cellfun(@(c)isempty(strfind(c, FilesToFind)), {sStudy.Result.Comment})); + + iRes1 = iRes1(1); + % Define the list of files A + FilesA(i) = {[path2bst sStudy.Result(iRes1(1)).FileName]}; + i=i+1; + end +end +% Remove previous nodes in the Statistics panel +panel_stat('ResetPanel'); +% Add files to the Statistics panel +nFilesA = gui_stat_common('SetFilesToProcess', 'StatA', FilesA); + +% Set types of files to process: data/results +isData = 0; +if isData + panel_stat('SetFileType', 'data'); +else + panel_stat('SetFileType', 'presults'); +end +% Conditions: structure that define the files in the Processes list +Conditions = gui_stat_common('GetConditions', 'Statistics'); +% Set types of files to process: data/results/timefreq +DataType = 'results'; +panel_processes('SetFileType', DataType); + +%% ===== GET PROCESS ===== +% Get processes list, and pick a process in it +[sProcessesList, sSimpleTests, sPermTests] = panel_statRun('GetProcessesList', 'All'); +% Find a process using its name +ProcessName = 'diffAB'; +iProc = find(strcmpi({sProcessesList.Name}, ProcessName)); +if isempty(iProc) + error('Process not found.'); +end +% Process to use +sProcess = sProcessesList(iProc); + +sProcess.Name='ANOVA' +sProcess.Description = ''; +sProcess.Category = 'ANOVA'; +sProcess.FileTag = '' + +%% ===== TIME ===== +% Use all the time samples +TimeVector = panel_statRun('GetFileTimeVector', Conditions.SamplesA(1).iStudy, Conditions.SamplesA(1).iItem, DataType); +Time = [TimeVector(1), TimeVector(end)]; +iTime = 1:length(TimeVector); + + +Baseline =[]; +iBaseline =[]; +%% ===== DEFINE OPTIONS ===== +OPTIONS.Conditions = Conditions; % Files to process +OPTIONS.sProcess = sProcess; % Process to apply +OPTIONS.isData = isData; % Process data or recordings +OPTIONS.Comment = 'anovalena'; % Default comment for output files (might be overridden) +OPTIONS.OutputType = 'database'; % Where o store the results: {database, file, matlab} +OPTIONS.ForceOutputCond = []; % When you want to store the result in a specific condition (used only when OutputType='database')=> Ex. 'Subject01/@intra' +OPTIONS.isOverwriteFiles = 0; % Overwrite input files, only in the case of filters (one input file = one output file) +OPTIONS.isAbsoluteValues = 1; % Compute the absolute value of the data before applying the process (usually 1 for sources, 0 for recordings) +OPTIONS.Time = Time; % Time window to process [tStart, tStop] in seconds +OPTIONS.iTime = iTime; % Time window: Indices in time vector for the full file +OPTIONS.Baseline = Baseline; % Some processes requires a baseline definition (it is the case for the zscore) +OPTIONS.iBaseline = iBaseline; % => Baseline and iBaseline work exactly the same way as Time and iTime +% Other options we do not use here: +OPTIONS.nbPermutation = 0; % For permuation tests only +OPTIONS.isCluster = 0; % Extract only some clusters/scouts values +OPTIONS.isClusterAverage = 0; % If 1, group all the clusters/scouts; If 0, consider they are separate +OPTIONS.sClusters = []; % Array of scouts/clusters structures +OPTIONS.ClustersOptions = []; % Structure that defines how the clusters/scouts values are computed (fields: function, isAbsolute) +OPTIONS.Baseline = []; +OPTIONS.iBaseline = []; + + +n_effects=size(liste_effects,2); +for i_effects = 1:n_effects + % Call processing function + %OutputFiles = bst_batch(OPTIONS); + load (strcat(path2anovares, StructName, 'F', num2str(liste_effects(i_effects)),'.mat')) + load (strcat(path2anovares, StructName, 'p', num2str(liste_effects(i_effects)),'.mat')) + p = tempStructp.ImageGridAmp; + F = tempStructF.ImageGridAmp; + OPTIONS.Comment=strcat(FileComment,'_F',num2str(liste_effects(i_effects))) + [sTargetStudy] = import_anova(Conditions, sProcess, OPTIONS, TimeVector, p, F); +end + + diff --git a/brainstorm3/script_import_anova_New1.m b/brainstorm3/script_import_anova_New1.m new file mode 100644 index 0000000..526c307 --- /dev/null +++ b/brainstorm3/script_import_anova_New1.m @@ -0,0 +1,132 @@ +% TUTORIAL_10_STAT: Script that follows Brainstorm online tutorial #10: "Statistics" +% +% USAGE: +% 1) Run first the previous tutorials (#2, #3, #5, #6, #7) +% 2) Run this script + +% DESCRIPTION: Compute the difference between the two sources files that were computed in tutorial #7. + +% @============================================================================= +% This software is part of The Brainstorm Toolbox +% http://neuroimage.usc.edu/brainstorm +% +% Copyright (c)2010 Brainstorm by the University of Southern California +% This software is distributed under the terms of the GNU General Public License +% as published by the Free Software Foundation. Further details on the GPL +% license can be found at http://www.gnu.org/copyleft/gpl.html. +% +% FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE +% UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY +% WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF +% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY +% LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE. +% +% For more information type "brainstorm licence" at command prompt. +% =============================================================================@ +% +% Author: Francois Tadel, 2010 + + +%% ===== START BRAINSTORM ===== +% Add brainstorm.m path to the path +addpath(fileparts(fileparts(fileparts(mfilename('fullpath'))))); +% If brainstorm is not running yet: Start brainstorm without the GUI +if ~brainstorm('status') + brainstorm nogui +end + +CONDITIONS = {'avg_HighCorrect' 'avg_HighIncorrect' 'avg_LowCorrect' 'avg_LowIncorrect' } +SUBJECTS = cellstr(reshape(sprintf('S%02d',[11 12 14:23 25:27]),3,[])'); +%% Creat File List +i= 1; +clear FilesA +for i_sub=1:length(SUBJECTS) + for i_cond = 1:4 + + % Get condition StimRightThumb + [sStudy, iStudy] = bst_getContext('StudyWithCondition', [ SUBJECTS{i_sub} '/' CONDITIONS{i_cond}]); + + %% ===== SELECT FILES ===== + % Get the two kernels computed in tutorial #7: the regular and the shared one + iRes1 = find(~cellfun(@(c)isempty(strfind(c, 'MN: EEG (scalp)(Kernel)')), {sStudy.Result.Comment})); + + iRes1 = iRes1(1); + % Define the list of files A + FilesA(i) = {['I:\data\confinum\brainstorm\data' sStudy.Result(iRes1(1)).FileName]}; + i=i+1; + end +end +% Remove previous nodes in the Statistics panel +panel_stat('ResetPanel'); +% Add files to the Statistics panel +nFilesA = gui_stat_common('SetFilesToProcess', 'StatA', FilesA); + + % Set types of files to process: data/results +isData = 0; +if isData + panel_stat('SetFileType', 'data'); +else + panel_stat('SetFileType', 'presults'); +end +% Conditions: structure that define the files in the Processes list +Conditions = gui_stat_common('GetConditions', 'Statistics'); + + +%% ===== GET PROCESS ===== +% Get processes list, and pick a process in it +[sProcessesList, sSimpleTests, sPermTests] = panel_statRun('GetProcessesList', 'All'); +% Find a process using its name +ProcessName = 'diffAB'; +iProc = find(strcmpi({sProcessesList.Name}, ProcessName)); +if isempty(iProc) + error('Process not found.'); +end +% Process to use +sProcess = sProcessesList(iProc); + +sProcess.Name='ANOVA' +sProcess.Description = ''; +sProcess.Category = 'ANOVA'; +sProcess.FileTag = '' + +%% ===== TIME ===== +% Use all the time samples +TimeVector = panel_statRun('GetFileTimeVector', Conditions.SamplesA(1).iStudy, Conditions.SamplesA(1).iItem, isData); +Time = [TimeVector(1), TimeVector(end)]; +iTime = 1:length(TimeVector); + + +Baseline =[] +iBaseline =[] +%% ===== DEFINE OPTIONS ===== +OPTIONS.Conditions = Conditions; % Files to process +OPTIONS.sProcess = sProcess; % Process to apply +OPTIONS.isData = isData; % Process data or recordings +OPTIONS.Comment = 'anovalena'; % Default comment for output files (might be overridden) +OPTIONS.OutputType = 'database'; % Where o store the results: {database, file, matlab} +OPTIONS.ForceOutputCond = []; % When you want to store the result in a specific condition (used only when OutputType='database')=> Ex. 'Subject01/@intra' +OPTIONS.isOverwriteFiles = 0; % Overwrite input files, only in the case of filters (one input file = one output file) +OPTIONS.isAbsoluteValues = 1; % Compute the absolute value of the data before applying the process (usually 1 for sources, 0 for recordings) +OPTIONS.Time = Time; % Time window to process [tStart, tStop] in seconds +OPTIONS.iTime = iTime; % Time window: Indices in time vector for the full file +OPTIONS.Baseline = Baseline; % Some processes requires a baseline definition (it is the case for the zscore) +OPTIONS.iBaseline = iBaseline; % => Baseline and iBaseline work exactly the same way as Time and iTime +% Other options we do not use here: +OPTIONS.nbPermutation = 0; % For permuation tests only +OPTIONS.isCluster = 0; % Extract only some clusters/scouts values +OPTIONS.isClusterAverage = 0; % If 1, group all the clusters/scouts; If 0, consider they are separate +OPTIONS.sClusters = []; % Array of scouts/clusters structures +OPTIONS.ClustersOptions = []; % Structure that defines how the clusters/scouts values are computed (fields: function, isAbsolute) +OPTIONS.Baseline = []; +OPTIONS.iBaseline = []; + +n_effects=1 +for i_effects = 1:n_effects +% Call processing function +%OutputFiles = bst_batch(OPTIONS); +p = rand(15028,921); +F = rand(15028,921); +[sTargetStudy] = import_anova(Conditions, sProcess, OPTIONS, TimeVector, p, F) +end + + diff --git a/brainstorm3/script_import_anova_Old1.m b/brainstorm3/script_import_anova_Old1.m new file mode 100644 index 0000000..558982f --- /dev/null +++ b/brainstorm3/script_import_anova_Old1.m @@ -0,0 +1,118 @@ +% import des r?sultats de l'anovabst de Lydia dans BrainSotmr +% inspir? de TUTORIAL_10_STAT: Script that follows Brainstorm online +% tutorial #10: "Statistics" +%Auteurs: Karim N'Diaye, CTB + +%Alancer avec BrainSorm ouvert, onglet Statistics actif et case "cources" +%coch?e +% Appel import_anova + +%%% constantes +CONDITIONS = {'ALL_EURO' 'ALL_DOLLAR' 'ALL_FRANC' 'ALL_MARK' 'ALL_SCR_EURO' 'ALL_SCR_DOLLAR' 'ALL_SCR_FRANC' 'ALL_SCR_MARK'} +SUBJECTS = {'sujet_01' 'sujet_02' 'sujet_03' 'sujet_04' 'sujet_05' 'sujet_06' ... + 'sujet_08' 'sujet_09' 'sujet_10' 'sujet_11' 'sujet_12' 'sujet_13' ... + 'sujet_21' 'sujet_22' 'sujet_23' 'sujet_pilote'}; +path2bst='/pclxserver/raid6/data/MONEY/BrainStormData/MONEY/data/' +path2anovares='/pclxserver/raid6/data/MONEY/Analyze16suj/StatSources/Raw/' +StructName='Sources3way'; +FilesToFind='MN: MEG(Full,Constr)' +liste_effects=[1 2 3 12 13 23 123]; +FileComment='Raw_An3F'; %%% Attention: nom sous lequel le fichier apparaitra dans la base BST + +%% Creat File List +i= 1; +clear FilesA +for i_sub=1:length(SUBJECTS) + for i_cond = 1:length(CONDITIONS) + + % Get condition StimRightThumb + [sStudy, iStudy] = bst_getContext('StudyWithCondition', [ SUBJECTS{i_sub} '/' CONDITIONS{i_cond}]); + + %% ===== SELECT FILES ===== + % Get the two kernels computed in tutorial #7: the regular and the shared one + iRes1 = find(~cellfun(@(c)isempty(strfind(c, FilesToFind)), {sStudy.Result.Comment})); + + iRes1 = iRes1(1); + % Define the list of files A + FilesA(i) = {[path2bst sStudy.Result(iRes1(1)).FileName]}; + i=i+1; + end +end +% Remove previous nodes in the Statistics panel +panel_stat('ResetPanel'); +% Add files to the Statistics panel +nFilesA = gui_stat_common('SetFilesToProcess', 'StatA', FilesA); + + % Set types of files to process: data/results +isData = 0; +if isData + panel_stat('SetFileType', 'data'); +else + panel_stat('SetFileType', 'presults'); +end +% Conditions: structure that define the files in the Processes list +Conditions = gui_stat_common('GetConditions', 'Statistics'); + + +%% ===== GET PROCESS ===== +% Get processes list, and pick a process in it +[sProcessesList, sSimpleTests, sPermTests] = panel_statRun('GetProcessesList', 'All'); +% Find a process using its name +ProcessName = 'diffAB'; +iProc = find(strcmpi({sProcessesList.Name}, ProcessName)); +if isempty(iProc) + error('Process not found.'); +end +% Process to use +sProcess = sProcessesList(iProc); + +sProcess.Name='ANOVA' +sProcess.Description = ''; +sProcess.Category = 'ANOVA'; +sProcess.FileTag = '' + +%% ===== TIME ===== +% Use all the time samples +TimeVector = panel_statRun('GetFileTimeVector', Conditions.SamplesA(1).iStudy, Conditions.SamplesA(1).iItem, isData); +Time = [TimeVector(1), TimeVector(end)]; +iTime = 1:length(TimeVector); + + +Baseline =[]; +iBaseline =[]; +%% ===== DEFINE OPTIONS ===== +OPTIONS.Conditions = Conditions; % Files to process +OPTIONS.sProcess = sProcess; % Process to apply +OPTIONS.isData = isData; % Process data or recordings +OPTIONS.Comment = 'anovalena'; % Default comment for output files (might be overridden) +OPTIONS.OutputType = 'database'; % Where o store the results: {database, file, matlab} +OPTIONS.ForceOutputCond = []; % When you want to store the result in a specific condition (used only when OutputType='database')=> Ex. 'Subject01/@intra' +OPTIONS.isOverwriteFiles = 0; % Overwrite input files, only in the case of filters (one input file = one output file) +OPTIONS.isAbsoluteValues = 1; % Compute the absolute value of the data before applying the process (usually 1 for sources, 0 for recordings) +OPTIONS.Time = Time; % Time window to process [tStart, tStop] in seconds +OPTIONS.iTime = iTime; % Time window: Indices in time vector for the full file +OPTIONS.Baseline = Baseline; % Some processes requires a baseline definition (it is the case for the zscore) +OPTIONS.iBaseline = iBaseline; % => Baseline and iBaseline work exactly the same way as Time and iTime +% Other options we do not use here: +OPTIONS.nbPermutation = 0; % For permuation tests only +OPTIONS.isCluster = 0; % Extract only some clusters/scouts values +OPTIONS.isClusterAverage = 0; % If 1, group all the clusters/scouts; If 0, consider they are separate +OPTIONS.sClusters = []; % Array of scouts/clusters structures +OPTIONS.ClustersOptions = []; % Structure that defines how the clusters/scouts values are computed (fields: function, isAbsolute) +OPTIONS.Baseline = []; +OPTIONS.iBaseline = []; + + +n_effects=size(liste_effects,2); +for i_effects = 1:n_effects +% Call processing function +%OutputFiles = bst_batch(OPTIONS); +load (strcat(path2anovares, StructName, 'F', num2str(liste_effects(i_effects)),'.mat')) +load (strcat(path2anovares, StructName, 'p', num2str(liste_effects(i_effects)),'.mat')) +p = tempStructp.ImageGridAmp; +F = tempStructF.ImageGridAmp; +OPTIONS.Comment=strcat(FileComment,'_F',num2str(liste_effects(i_effects))) +[sTargetStudy] = import_anova(Conditions, sProcess, OPTIONS, TimeVector, p, F); +end + + diff --git a/brainstorm3/svn_diff.csh b/brainstorm3/svn_diff.csh new file mode 100755 index 0000000..96c9d8c --- /dev/null +++ b/brainstorm3/svn_diff.csh @@ -0,0 +1,22 @@ +#!/bin/tcsh -xfv + +foreach mine ( *.m ) + set updated = ` find ../../../mtoolbox/brainstorm3 -name $mine:t ` + echo "Files: " + echo " :" + echo " :" + echo $updated ":" $mine + echo " :" + echo " :" + diff -dby --suppress-common-lines $updated $mine + echo " [c]ompare / [s]kip / [q]uit ?" + + set action=$< + if ($action == "q" ) then + exit(1); + endif + if ($action == "c" ) then + kdiff3 $updated $mine -m -o $mine + endif + echo " " +end diff --git a/brainstorm3/svn_diff.csh~ b/brainstorm3/svn_diff.csh~ new file mode 100755 index 0000000..aba5f08 --- /dev/null +++ b/brainstorm3/svn_diff.csh~ @@ -0,0 +1,11 @@ +#!/bin/tcsh -xfv + +foreach mine ( *.m ) + echo $mine:t + set updated = ` find ../../../mtoolbox/brainstorm3 -name $mine:t ` + echo "Comparing "$updated $mine + kdiff3 $updated $mine -m -o $mine + if ($? == 1 ) then + exit(1) + end +end diff --git a/brainstorm3/toolbox/core/bst_getContext.m b/brainstorm3/toolbox/core/bst_getContext.m new file mode 100644 index 0000000..ee7811a --- /dev/null +++ b/brainstorm3/toolbox/core/bst_getContext.m @@ -0,0 +1,2006 @@ +function [bstContext, bstIndex, bstSubIndex] = bst_getContext( varargin ) +% BST_GETCONTEXT: Get a Brainstorm structure. +% This function is used to abstract the way that these structures are stored. +% +% USAGE : +% ====== DIRECTORIES ================================================================== +% - bst_getContext('UserDir') : User home directory +% - bst_getContext('BrainStormHomeDir') : Application directory of brainstorm +% - bst_getContext('BrainStormUserDir') : User home directory for brainstorm (/.brainstorm/) +% - bst_getContext('BrainStormTmpDir') : User brainstorm temporary directory (Default: /.brainstorm/tmp/) +% - bst_getContext('BrainStormTmpDir', isForcedDefault) : User DEFAULT brainstorm temporary directory (/.brainstorm/tmp/) +% - bst_getContext('BrainStormMexDir') : User brainstorm temporary directory (/.brainstorm/mex/) +% - bst_getContext('BrainStormDbFile') : User brainstorm.mat file (/.brainstorm/brainstorm.mat) +% - bst_getContext('BrainStormDbDir') : User database directory (contains all the brainstorm protocols) +% - bst_getContext('DirDefaultSubject') : Directory name of the default subject +% - bst_getContext('DirDefaultStudy') : Directory name of the default study for each subject +% - bst_getContext('DirAnalysisInter') : Directory name of the inter-subject analysis study +% - bst_getContext('DirAnalysisIntra') : Directory name of the intra-subject analysis study (for each subject) +% - bst_getContext('NormalizedSubjectName') : Name of the subject with a normalized anatomy +% - bst_getContext('AnatomyDefaults') : Get the contents of directory bstDir/defaults/anatomy +% - bst_getContext('EEGDefaults') : Get the contents of directory bstDir/defaults/eeg +% - bst_getContext('LastUsedDirs') : Structure with all the last used directories (last used) +% +% ====== PROTOCOLS ==================================================================== +% - bst_getContext('ProtocolsListInfo') : List of protocols (definition) +% - bst_getContext('ProtocolsListSubjects') : List of protocols (subjects) +% - bst_getContext('ProtocolsListStudies') : List of protocols (studies) +% - bst_getContext('iProtocol') : Indice of current protocol +% - bst_getContext('ProtocolInfo') : Definition structure for current protocol +% - bst_getContext('ProtocolSubjects') : Subjects list for current protocol +% - bst_getContext('ProtocolStudies') : Studies list for current protocol +% +% ====== STUDIES ====================================================================== +% - bst_getContext('Study', StudyFileName) : Get one study in current protocol with its file name +% - bst_getContext('Study', iStudies) : Get one or more studies +% - bst_getContext('Study') : Get current study in current protocol +% - bst_getContext('StudyCount') : Get number of studies in the current protocol +% - bst_getContext('StudyWithSubject', SubjectFile) : Find studies associated with a given subject file (WITHOUT the system studies ('intra_subject', 'default_study')) +% - bst_getContext('StudyWithSubject', ..., 'intra_subject') : Find studies ... INCLUDING 'intra_subject' study +% - bst_getContext('StudyWithSubject', ..., 'default_study') : Find studies ... INCLUDING 'default_study' study +% - bst_getContext('StudyWithCondition', ConditionPath) : Find studies for a given condition path +% - bst_getContext('StudyWithSubjectAndCondition', SubjectFile, ConditionsList) +% - bst_getContext('ChannelStudiesWithSubject', iSubjects) : Get all the studies where there should be a channel file for a list of subjects +% - bst_getContext('AnalysisIntraStudy', iSubject) : Get the default analysis study for target subject +% - bst_getContext('AnalysisInterStudy') : Get the default analysis study for inter-subject analysis +% - bst_getContext('DefaultStudy', iSubject) : Get the default study for target subject (by subject indice) +% - bst_getContext('DefaultStudy') : Get the global default study (common to all subjects) +% - bst_getContext('DefaultStudy', BrainStormSubject) : Get the default study for target subject (by filename) +% - bst_getContext('ChannelFile', ChannelFile) : Find a channel file in current protocol +% - bst_getContext('ChannelFileForStudy', StudyFile/DataFile) : Find a channel file in current protocol +% - bst_getContext('ChannelForStudy', iStudies) : Return current Channel struct for target study +% - bst_getContext('HeadModelForStudy', iStudy) : Return current HeadModel struct for target study +% - bst_getContext('DataFile', DataFile) : Find a DataFile in current protocol +% - bst_getContext('DataForDataList', iStudy, DataListName) : Find all the DataFiles grouped by a data list +% - bst_getContext('DataForStudy', iStudy) : Find all the Data files that are dependent on the channel/headmodel of a given study +% - bst_getContext('DataForStudies', iStudies) +% - bst_getContext('DataForChannelFile', ChannelFile) : Find all the DataFiles that use the given ChannelFile +% - bst_getContext('ResultsFile', ResultsFile) : Find a ResultsFile in current protocol +% - bst_getContext('ResultsForDataFile', DataFile) : Find all results computed based on DataFile +% - bst_getContext('StatFile', StatFile) : Find a StatFile in current protocol +% - bst_getContext('StatForDataFile', DataFile, iStudies) +% - bst_getContext('StatForDataFile', DataFile) +% - bst_getContext('GetFileNames') +% - bst_getContext('BestFittingSphere', ChannelFile) : Get a spherical approximation to head shape in the headmodels +% +% ====== SUBJECTS ====================================================================== +% - bst_getContext('Subject', SubjectFileName, isRaw) : Find a subject in current protocol with its file name +% - bst_getContext('Subject', SubjectDir, isRaw) : Find a subject in current protocol with its directory +% - bst_getContext('Subject', iSubject) : Get a subject (normal or default if iSubject==0) +% - bst_getContext('Subject') : Get current subject in current protocol +% - bst_getContext('SubjectWithName', Name) : Find a subject in current protocol with its name +% - bst_getContext('SubjectCount') : Get number of studies in the current protocol +% - bst_getContext('ConditionsForSubject', SubjectFile) : Find all conditions for a given subject +% - bst_getContext('SurfaceFile', SurfaceFile) : Find a surface in current protocol +% - bst_getContext('SurfaceFileByType', iSubject, SurfaceType) : Find the default surface for subject #i +% - bst_getContext('SurfaceFileByType', SurfaceName, SurfaceType) : Find the default surface for subject that also has surface SurfaceName +% - bst_getContext('SurfaceFileByType', MriName, SurfaceType) : Find the default surface for subject that also has MRI MriName +% - bst_getContext('MriFile', MriFile) : Find a MRI in current protocol +% +% ====== GUI ================================================================= +% - bst_getContext('GUI') : Get GUI structure (handles of Java panels and windows) +% - bst_getContext('Layout') : Configuration of the main Brainstorm window +% - bst_getContext('LayoutManager') : Name of the function that re-arrange automatically the figures +% - bst_getContext('ProgressBar') : Handle to Brainstorm progress bar +% - bst_getContext('PanelContainer') : Display list of registered panel containers +% - bst_getContext('PanelContainer', ContainerName) : Get a panel container handle +% - bst_getContext('Panel') : Display list of registered panels +% - bst_getContext('Panel', PanelName) : Find a panel with its name +% - bst_getContext('PanelControls', PanelName) : Get the controls of a panel +% - bst_getContext('PanelElement', PanelName, ElementName) : Get an element (control, callback, ...) in a panel +% - bst_getContext('PanelElement', ElementName) : Search for an element (control, callback, ...) in all the panels +% +% ====== CONFIGURATION ================================================================= +% - bst_getContext('Version') : Brainstorm version +% - bst_getContext('ByteOrder') : {'l','b'} - Byte order used to read and save binary files +% - bst_getContext('DisplayGFP') : {0,1} - If 1, the GFP is displayed on all the time series figures +% - bst_getContext('ExpandTrialsLists') : {0,1} - If 1, the trials lists are automatically expanded in the tree +% - bst_getContext('DefaultFormats') : Default formats for importing/exporting data, channels, ... (last used) +% - bst_getContext('BEMOptions') : BEM options +% - bst_getContext('BFSProperties') : Conductivities and thicknesses for 3-shell spherical forward model +% - bst_getContext('ImportCTFOptions') : Importation options for CTF format +% - bst_getContext('ImportFIFOptions') : Importation options for FIF format +% - bst_getContext('ImportEegRawOptions') : Importation options for RAW EEG format +% - bst_getContext('BugReportOptions') : Bug reporter options +% - bst_getContext('InverseOptions') : Importation options for RAW EEG format +% - bst_getContext('DefaultSurfaceDisplay') : Default display options for surfaces (smooth, data threshold, curvature) +% - bst_getContext('MagneticExtrapOptions') : Structure with the options for magnetic field extrapolation +% - bst_getContext('UniformizeTimeSeriesScales') : {0,1} - If 1, the Y-axis of all the time series figures have the scale +% - bst_getContext('DisplayAverageReference') : {0,1} - If 1, the EEG recordings will be displayed in average reference +% - bst_getContext('ReloadDbAtStartup') : {0,1} - If 1, reload automatically database at each startup +% - bst_getContext('UseDoubleScreen') : {0,1} - If 1, if two available display, use both; else use only the first one +% - bst_getContext('UseSigProcToolbox') : Use Matlab's Signal Processing Toolbox when available +% +% SEE ALSO bst_setContext + +% @============================================================================= +% This software is part of The Brainstorm Toolbox +% http://neuroimage.usc.edu/brainstorm +% +% Copyright (c)2009 Brainstorm by the University of Southern California +% This software is distributed under the terms of the GNU General Public License +% as published by the Free Software Foundation. Further details on the GPL +% license can be found at http://www.gnu.org/copyleft/gpl.html. +% +% FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE +% UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY +% WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF +% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY +% LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE. +% +% For more information type "brainstorm licence" at command prompt. +% =============================================================================@ +% +% Authors: Francois Tadel, 2008 +% ----------------------------- Script History --------------------------------- +% FT 24-Jun-2008 Creation +% ------------------------------------------------------------------------------ + + +%% ==== PARSE INPUTS ==== +if ((nargin >= 1) && ischar(varargin{1})) + contextName = varargin{1}; +else + return +end +% Initialize returned variable +bstContext = []; +bstIndex = []; +bstSubIndex = []; + + +% Get required context structure +switch lower(contextName) +%% ==== BRAINSTORM CONFIGURATION ==== + case 'version' + if ispref('BrainStorm', 'Version') + bstContext = getpref('BrainStorm', 'Version'); + else + bstContext = []; + end + + case 'brainstormhomedir' + if ispref('BrainStorm', 'brainstormHomeDir') + bstContext = getpref('BrainStorm', 'brainstormHomeDir'); + else + bstContext = []; + end + + case 'userdir' + try + userDir = char(java.lang.System.getProperty('user.home')); + catch + userDir = ''; + end + if isempty(userDir) + userDir = bst_getContext('BrainstormHomeDir'); + end + bstContext = userDir; + + case 'brainstormuserdir' + bstUserDir = fullfile(bst_getContext('UserDir'), '.brainstorm'); + if ~isdir(bstUserDir) + res = mkdir(bstUserDir); + if ~res + error(['Cannot create Brainstorm user directory: "' bstUserDir '".']); + end + end + bstContext = bstUserDir; + + case 'brainstormtmpdir' + tmpDir = ''; + isForcedDefault = ((nargin >= 2) && varargin{2}); + % If temporary directory is set in the preferences + if ~isForcedDefault && ispref('BrainStorm', 'BrainstormTempDir') + tmpDir = getpref('BrainStorm', 'BrainstormTempDir'); + end + % Else: use directory userdir/tmp + if isempty(tmpDir) + tmpDir = fullfile(bst_getContext('BrainStormUserDir'), 'tmp'); + end + % Create directory if it does not exist yet + if ~isdir(tmpDir) + res = mkdir(tmpDir); + if ~res + error(['Cannot create Brainstorm temporary directory: "' tmpDir '".']); + end + end + bstContext = tmpDir; + + case 'brainstormmexdir' + mexDir = fullfile(bst_getContext('BrainStormUserDir'), 'mex'); + if ~isdir(mexDir) + res = mkdir(mexDir); + if ~res + error(['Cannot create Brainstorm mex-files directory: "' mexDir '".']); + end + end + bstContext = mexDir; + + case 'brainstormdbfile' + bstContext = fullfile(bst_getContext('BrainStormUserDir'), 'brainstorm.mat'); + + case 'brainstormdbdir' + BrainStormDbDir = getappdata(0, 'BrainStormDbDir'); + if ~isempty(BrainStormDbDir); + bstContext = BrainStormDbDir; + bstIndex = 1; + else + % Ask user where is located + bstContext = fullfile(bst_getContext('UserDir'), 'brainstorm_db'); + bstIndex = 0; + end + + +%% ==== PROTOCOLS LIST ==== + case 'protocolslistinfo' + bstContext = getappdata(0, 'ProtocolInfo'); + case 'protocolslistsubjects' + bstContext = getappdata(0, 'ProtocolSubjects'); + case 'protocolsliststudies' + bstContext = getappdata(0, 'ProtocolStudies'); + + +%% ==== PROTOCOL ==== + case 'iprotocol' + bstContext = getappdata(0, 'iProtocol'); + if isempty(bstContext) + bstContext = 0; + end + case {'protocolinfo', 'protocolsubjects', 'protocolstudies'} + % Get protocols list (if empty : return) + ProtocolsList = getappdata(0, contextName); + if isempty(ProtocolsList) + return + end; + % Get protocol index + bstIndex = getappdata(0, 'iProtocol'); + % Check index integrity + if ((bstIndex <= 0) || (bstIndex > length(ProtocolsList))), warning('Brainstorm:InvalidIndex', 'Invalid index'), return, end + % Get requested protocol structure + bstContext = ProtocolsList(bstIndex); + + +%% ==== STUDY ==== + % Usage: [sStudy, iStudy] = bst_getContext('Study', StudyFileName) + % [sStudy, iStudy] = bst_getContext('Study') + % [sStudy, iStudy] = bst_getContext('Study', iStudies) + case 'study' + % Get list of current protocol description + ProtocolInfo = bst_getContext('ProtocolInfo'); + % Get list of current protocol studies + ProtocolStudies = bst_getContext('ProtocolStudies'); + if isempty(ProtocolStudies) || isempty(ProtocolInfo) % || isempty(ProtocolInfo.iStudy) + return; + end + % ===== PARSE INPUTS ===== + if (nargin < 2) + % Call: bst_getContext('Study'); + iStudies = ProtocolInfo.iStudy; + StudyFileName = []; + elseif (isnumeric(varargin{2})) + iStudies = varargin{2}; + StudyFileName = []; + elseif (ischar(varargin{2})) + iStudies = []; + StudyFileName = strrep(varargin{2}, ProtocolInfo.STUDIES, ''); + end + % Indices + iAnalysisStudy = -2; % CANNOT USE -1 => DISABLES SEARCH FUNCTIONS + iDefaultStudy = -3; + % Indices > 0: normal studies indiced in ProtocolStudies.Study array + + % ===== GET STUDY BY INDEX ===== + % Call: bst_getContext('Study', iStudies); + if ~isempty(iStudies) + bstContext = repmat(db_getDataTemplate('Study'), 0); + % Get analysis study + iTargetAnalysis = find(iStudies == iAnalysisStudy); + if ~isempty(iTargetAnalysis) + bstContext(iTargetAnalysis) = repmat(ProtocolStudies.AnalysisStudy, size(iTargetAnalysis)); + bstIndex(iTargetAnalysis) = repmat(iAnalysisStudy, size(iTargetAnalysis)); + end + % Get default study + iTargetDefault = find(iStudies == iDefaultStudy); + if ~isempty(iTargetDefault) + try + bstContext(iTargetDefault) = repmat(ProtocolStudies.DefaultStudy, size(iTargetDefault)); + catch ME + if isempty(bstContext) + bstContext= repmat(ProtocolStudies.DefaultStudy, size(iTargetDefault)); + else + rethrow(ME) + end + end + bstIndex(iTargetDefault) = repmat(iDefaultStudy, size(iTargetDefault)); + end + % Get normal studies + iTargetNormal = find((iStudies >= 1) & (iStudies <= length(ProtocolStudies.Study))); + if ~isempty(iTargetNormal) + bstContext(iTargetNormal) = ProtocolStudies.Study(iStudies(iTargetNormal)); + bstIndex(iTargetNormal) = iStudies(iTargetNormal); + end + % Error + if isempty(bstContext) + %warning('Brainstorm:InvalidIndex', 'Invalid study indice.'); + bstContext = []; + ProtocolInfo.iStudy = []; + bst_setContext('ProtocolInfo', ProtocolInfo); + end + + % ===== GET STUDY BY FILENAME ===== + % Call: bst_getContext('Study', StudyFileName); + elseif ~isempty(StudyFileName) + % NORMAL STUDY + iStudy = find(io_compareFileNames({ProtocolStudies.Study.FileName}, StudyFileName), 1); + % If a study is found : return it + if ~isempty(iStudy) + bstContext = ProtocolStudies.Study(iStudy); + bstIndex = iStudy; + % DEFAULT STUDY + elseif ~isempty(ProtocolStudies.DefaultStudy) && io_compareFileNames({ProtocolStudies.DefaultStudy.FileName}, StudyFileName) + bstContext = ProtocolStudies.DefaultStudy; + bstIndex = iDefaultStudy; + % ANALYSIS STUDY + elseif ~isempty(ProtocolStudies.AnalysisStudy) && io_compareFileNames({ProtocolStudies.AnalysisStudy.FileName}, StudyFileName) + bstContext = ProtocolStudies.AnalysisStudy; + bstIndex = iAnalysisStudy; + end + else + return + end + + +%% ==== STUDY WITH SUBJECT FILE ==== + % Usage : [sStudies, iStudies] = bst_getContext('StudyWithSubject', SubjectFile) : WITHOUT the system studies ('intra_subject', 'default_study') + % [sStudies, iStudies] = bst_getContext(..., 'intra_subject', 'default_study') : WITH the system studies: 'intra_subject' | 'default_study' + case 'studywithsubject' + % Parse inputs + if (nargin < 2) || ~ischar(varargin{2}) + error('Invalid call to bst_getContext()'); + end + if (nargin > 2) + IntraStudies = any(strcmpi(varargin(3:end), 'intra_subject')); + DefaultStudies = any(strcmpi(varargin(3:end), 'default_study')); + else + IntraStudies = 0; + DefaultStudies = 0; + end + SubjectFile = {varargin{2}}; + + % Get list of current protocol description + ProtocolInfo = bst_getContext('ProtocolInfo'); + ProtocolStudies = bst_getContext('ProtocolStudies'); + if isempty(ProtocolStudies) || isempty(ProtocolInfo) + return; + end + + % Get default subject + sDefaultSubject = bst_getContext('Subject', 0); + % If SubjectFile is the default subject filename + if ~isempty(sDefaultSubject) && ~isempty(sDefaultSubject.FileName) && io_compareFileNames( SubjectFile{1}, sDefaultSubject.FileName) + % Get all the subjects files that use default anatomy + ProtocolSubjects = bst_getContext('ProtocolSubjects'); + iSubjectUseDefaultAnat = find([ProtocolSubjects.Subject.UseDefaultAnat]); + if isempty(iSubjectUseDefaultAnat) + return + end + SubjectFile = {ProtocolSubjects.Subject(iSubjectUseDefaultAnat).FileName}; + % Also updates inter-subject node + isInterSubject = 1; + else + isInterSubject = 0; + end + % Search all the current protocol's studies + iStudies = []; + for i=1:length(SubjectFile) + iStudies = [iStudies, find(io_compareFileNames({ProtocolStudies.Study.BrainStormSubject}, SubjectFile{i}))]; + end + % Return results + if ~isempty(iStudies) + % Remove "analysis_intra" and "default_study" studies from list + if ~IntraStudies + iStudies(strcmpi({ProtocolStudies.Study(iStudies).Name}, bst_getContext('DirAnalysisIntra'))) = []; + end + if ~DefaultStudies + iStudies(strcmpi({ProtocolStudies.Study(iStudies).Name}, bst_getContext('DirDefaultStudy'))) = []; + end + % Return studies + bstContext = ProtocolStudies.Study(iStudies); + bstIndex = iStudies; + else + bstContext = repmat(db_getDataTemplate('Study'), 0); + bstIndex = []; + end + % Add inter-subject node, if needed + if isInterSubject + [sInterStudy, iInterStudy] = bst_getContext('AnalysisInterStudy'); + bstContext = [bstContext, sInterStudy]; + bstIndex = [bstIndex, iInterStudy]; + end + + +%% ==== STUDY WITH CONDITION PATH ==== + % Usage : [sStudies, iStudies] = bst_getContext('StudyWithCondition', ConditionPath) + % Condition path can have two formats : + % - 'subjectName/condition1/subCondition1...' : target condition for the specified subject + % - '*/condition1/subCondition1...' : target condition for all the subjects + case 'studywithcondition' + % Parse inputs + if (nargin ~= 2) || ~ischar(varargin{2}) + error('Invalid call to bst_getContext()'); + end + ConditionPath = varargin{2}; + % Get list of current protocol description + ProtocolInfo = bst_getContext('ProtocolInfo'); + ProtocolStudies = bst_getContext('ProtocolStudies'); + if isempty(ProtocolStudies) || isempty(ProtocolInfo) + return; + end + + % Split Condition path string + Conditions = strSplit(ConditionPath); + if (length(Conditions) < 2) + % If only one element in condition path + % => consider as if there were a '*/' before (all subjects) + iStudies = 1:length(ProtocolStudies.Study); + else + % If first element is '*', search for condition in all the studies + if (Conditions{1}(1) == '*') + iStudies = 1:length(ProtocolStudies.Study); + % Else : search for condition only in studies that are linked to the subject specified in the ConditionPath + else + iStudies = find(cellfun(@(f)strcmpi(fileparts(f), Conditions{1}), {ProtocolStudies.Study.BrainStormSubject})); + end + Conditions = Conditions(2:end); + end + + % Search all the current protocol's studies + iStudies = iStudies(cellfun(@(c)isequal(Conditions, c), {ProtocolStudies.Study(iStudies).Condition})); + % Return results + if ~isempty(iStudies) + % Remove "analysis_intra" and "default_study" studies from list + iStudies(strcmpi({ProtocolStudies.Study(iStudies).Name}, bst_getContext('DirAnalysisIntra'))) = []; + iStudies(strcmpi({ProtocolStudies.Study(iStudies).Name}, bst_getContext('DirDefaultStudy'))) = []; + % Sort by subject + if (length(iStudies) > 1) + SubjNameList = cell(1,length(iStudies)); + % For each study, get subject name + for i = 1:length(iStudies) + sSubject = bst_getContext('Subject', ProtocolStudies.Study(iStudies(i)).BrainStormSubject); + SubjNameList{i} = sSubject.Name; + end + % Sort subjects names + [sortSubjList, iSort] = sort(SubjNameList); + % Apply same sorting to studies + iStudies = iStudies(iSort); + end + % Return studies + bstContext = ProtocolStudies.Study(iStudies); + bstIndex = iStudies; + else + bstContext = repmat(db_getDataTemplate('Study'), 0); + bstIndex = []; + end + + +%% ==== STUDY WITH CONDITION AND SUBJECT ==== + % Usage: [sNewStudy, iNewStudy, ConditionPath] = bst_getContext('StudyWithSubjectAndCondition', SubjectFile, ConditionList) + case 'studywithsubjectandcondition' + % Parse inputs + if (nargin ~= 3) || ~ischar(varargin{2}) || ~iscell(varargin{3}) + error('Invalid call to bst_getContext()'); + end + SubjectFile = varargin{2}; + ConditionList = varargin{3}; + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % === VERSION 1 : USE SUBJECTS DIR === + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % Get subject's base subdirectory + subjectsSubDir = fileparts(SubjectFile); + iFirstSlash = min([findstr(subjectsSubDir, '/'), findstr(subjectsSubDir, '\')]); + if ~isempty(iFirstSlash) + subjectsSubDir(iFirstSlash:end) = []; + end + % Get study with subject AND condition + ConditionPath = fullfile(subjectsSubDir, ConditionList{:}); + [sStudy, iStudy] = bst_getContext('StudyWithCondition', ConditionPath); + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % === VERSION 2 : USE STUDIES DIR === + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + if isempty(sStudy) + % Get a study associated to subject (to get the studies subdirectory) + [sStudy, iStudy] = bst_getContext('StudyWithSubject', SubjectFile); + if ~isempty(sStudy) + % Get subject's studies base subdirectory + studiesSubDir = fileparts(sStudy(1).FileName); + iFirstSlash = min([findstr(studiesSubDir, '/'), findstr(studiesSubDir, '\')]); + if ~isempty(iFirstSlash) + studiesSubDir(iFirstSlash:end) = []; + end + % Get study with subject AND condition + ConditionPath = fullfile(studiesSubDir, ConditionList{:}); + [sStudy, iStudy] = bst_getContext('StudyWithCondition', ConditionPath); + end + end + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + % Returned values + bstContext = sStudy; + bstIndex = iStudy; + if isempty(sStudy) + bstSubIndex = ''; + else + bstSubIndex = ConditionPath; + end + +%% ==== CHANNEL STUDIES WITH SUBJECT ==== + % Usage: iStudies = bst_getContext('ChannelStudiesWithSubject', iSubjects, 'NoIntra') + case 'channelstudieswithsubject' + % Parse inputs + if (nargin >= 2) && isnumeric(varargin{2}) + iSubjects = varargin{2}; + else + error('Invalid call to bst_getContext()'); + end + if (nargin == 3) && strcmpi(varargin{3}, 'NoIntra') + NoIntra = 1; + else + NoIntra = 0; + end + % Process all subjects + iStudies = []; + for i=1:length(iSubjects) + iSubject = iSubjects(i); + sSubject = bst_getContext('Subject', iSubject, 1); + % No subject: error + if isempty(sSubject) + continue + % If subject uses default channel file + elseif (sSubject.UseDefaultChannel ~= 0) + % Get default study for this subject + [tmp___, iStudiesNew] = bst_getContext('DefaultStudy', iSubject); + iStudies = [iStudies, iStudiesNew]; + % Else: get all the studies belonging to this subject + else + if NoIntra + [tmp___, iStudiesNew] = bst_getContext('StudyWithSubject', sSubject.FileName); + else + [tmp___, iStudiesNew] = bst_getContext('StudyWithSubject', sSubject.FileName, 'intra_subject'); + end + iStudies = [iStudies, iStudiesNew]; + end + end + bstContext = iStudies; + +%% ==== CHANNEL STUDY WITH STUDY ==== + % Usage: iStudies = bst_getContext('ChannelStudiesWithSubject', iSubjects, 'NoIntra') + case 'channelstudieswithsubject' + % Parse inputs + if (nargin >= 2) && isnumeric(varargin{2}) + iSubjects = varargin{2}; + else + error('Invalid call to bst_getContext()'); + end + if (nargin == 3) && strcmpi(varargin{3}, 'NoIntra') + NoIntra = 1; + else + NoIntra = 0; + end + % Process all subjects + iStudies = []; + for i=1:length(iSubjects) + iSubject = iSubjects(i); + sSubject = bst_getContext('Subject', iSubject, 1); + % No subject: error + if isempty(sSubject) + continue + % If subject uses default channel file + elseif (sSubject.UseDefaultChannel ~= 0) + % Get default study for this subject + [tmp___, iStudiesNew] = bst_getContext('DefaultStudy', iSubject); + iStudies = [iStudies, iStudiesNew]; + % Else: get all the studies belonging to this subject + else + if NoIntra + [tmp___, iStudiesNew] = bst_getContext('StudyWithSubject', sSubject.FileName); + else + [tmp___, iStudiesNew] = bst_getContext('StudyWithSubject', sSubject.FileName, 'intra_subject'); + end + iStudies = [iStudies, iStudiesNew]; + end + end + bstContext = iStudies; + + +%% ==== STUDIES COUNT ==== + % Usage: [nbStudies] = bst_getContext('StudyCount') + case 'studycount' + % Get list of current protocol studies + ProtocolStudies = bst_getContext('ProtocolStudies'); + bstContext = length(ProtocolStudies.Study); + +%% ==== SUBJECTS COUNT ==== + % Usage: [nbSubjects] = bst_getContext('SubjectCount') + case 'subjectcount' + % Get list of current protocol studies + ProtocolSubjects = bst_getContext('ProtocolSubjects'); + bstContext = length(ProtocolSubjects.Subject); + +%% ==== ANALYSIS STUDY (INTRA) ==== + % Usage: [sAnalStudy, iAnalStudy] = bst_getContext('AnalysisIntraStudy', iSubject) + case 'analysisintrastudy' + % Parse inputs + if (nargin == 2) && isnumeric(varargin{2}) + iSubject = varargin{2}; + else + error('Invalid call to bst_getContext()'); + end + % Get subject + sSubject = bst_getContext('Subject', iSubject, 1); + % Get studies related to subject + [sSubjStudies, iSubjStudies] = bst_getContext('StudyWithSubject', sSubject.FileName, 'intra_subject'); + % Look for the 'AnalysisIntra' study + iFound = find(cellfun(@(c)ismember(bst_getContext('DirAnalysisIntra'), c), {sSubjStudies.Condition})); + iAnalStudy = iSubjStudies(iFound); + sAnalStudy = sSubjStudies(iFound); + % If no study found: need to create one + if isempty(iAnalStudy) + % Build new study + iAnalStudy = db_addCondition(fileparts(sSubject.FileName), bst_getContext('DirAnalysisIntra')); + sAnalStudy = bst_getContext('Study', iAnalStudy); + end + bstContext = sAnalStudy; + bstIndex = iAnalStudy; + + +%% ==== ANALYSIS STUDY (INTER) ==== + % Usage: [sAnalStudyInter, iAnalStudyInter] = bst_getContext('AnalysisInterStudy') + case 'analysisinterstudy' + iAnalStudyInter = -2; + [bstContext, bstIndex] = bst_getContext('Study', iAnalStudyInter); + + +%% ==== DEFAULT STUDY ==== + % Usage: [sDefaulStudy, iDefaultStudy] = bst_getContext('DefaultStudy', iSubject) + % [sDefaulStudy, iDefaultStudy] = bst_getContext('DefaultStudy') : iSubject=0 + % [sDefaulStudy, iDefaultStudy] = bst_getContext('DefaultStudy', BrainStormSubject) + case 'defaultstudy' + % Parse inputs + if (nargin == 1) + iSubject = 0; + elseif (nargin == 2) && isnumeric(varargin{2}) + iSubject = varargin{2}; + elseif (nargin == 2) && ischar(varargin{2}) + BrainStormSubject = varargin{2}; + % Get subject attached to study + [sSubject, iSubject] = bst_getContext('Subject', BrainStormSubject, 1); + if isempty(sSubject) || ~sSubject.UseDefaultChannel + return; + end + else + error('Invalid call to bst_getContext()'); + end + % === DEFAULT SUBJECT === + % => Return global default study + if (iSubject == 0) + % Get protocol's studies + ProtocolStudies = bst_getContext('ProtocolStudies'); + % Return Global default study + bstContext = ProtocolStudies.DefaultStudy; + bstIndex = -3; + % === NORMAL SUBJECT === + else + % Get subject + sSubject = bst_getContext('Subject', iSubject, 1); + % === GLOBAL DEFAULT STUDY === + if sSubject.UseDefaultChannel == 2 + % Get protocol's studies + ProtocolStudies = bst_getContext('ProtocolStudies'); + % Return Global default study + bstContext = ProtocolStudies.DefaultStudy; + bstIndex = -3; + % === SUBJECT'S DEFAULT STUDY === + elseif sSubject.UseDefaultChannel == 1 + % Get studies related to subject + [sSubjStudies, iSubjStudies] = bst_getContext('StudyWithSubject', sSubject.FileName, 'default_study'); + % Look for the 'DefaultStudy' study + iFound = find(cellfun(@(c)ismember(bst_getContext('DirDefaultStudy'), c), {sSubjStudies.Condition})); + iDefaultStudy = iSubjStudies(iFound); + sDefaultStudy = sSubjStudies(iFound); + % If no study found: need to create one + if isempty(iDefaultStudy) + % Build new study + iDefaultStudy = db_addCondition(fileparts(sSubject.FileName), bst_getContext('DirDefaultStudy')); + sDefaultStudy = bst_getContext('Study', iDefaultStudy); + end + bstContext = sDefaultStudy; + bstIndex = iDefaultStudy; + end + end + + + +%% ==== SUBJECT ==== + % Usage : [sSubject, iSubject] = bst_getContext('Subject', iSubject, isRaw) + % [sSubject, iSubject] = bst_getContext('Subject', SubjectFileName, isRaw); + % [sSubject, iSubject] = bst_getContext('Subject'); + % If isRaw is set: force to return the real brainstormsubject description + % (ignoring wether it uses protocol's default anatomy or not) + case 'subject' + % Get list of current protocol subjects + ProtocolSubjects = bst_getContext('ProtocolSubjects'); + if isempty(ProtocolSubjects) + return + end + sSubject = []; + % ISRAW parameter + if (nargin < 3) + isRaw = 0; + else + isRaw = varargin{3}; + end + % Call: bst_getContext('subject', iSubject, isRaw); + if (nargin >= 2) && isnumeric(varargin{2}) + iSubject = varargin{2}; + if (iSubject > length(ProtocolSubjects.Subject)) + error('Invalid subject indice.'); + end + % If required subject is default subject (iSubject = 0) + if (iSubject == 0) + % Default subject available + if ~isempty(ProtocolSubjects.DefaultSubject) + sSubject = ProtocolSubjects.DefaultSubject; + % Default subject not available + else + return + end + % Normal subject + else + sSubject = ProtocolSubjects.Subject(iSubject); + end + + % Call: bst_getContext('subject', SubjectFileName, isRaw); + % Call: bst_getContext('subject', SubjectDir, isRaw); + elseif (nargin >= 2) && isempty(varargin{2}) + % If study name is empty: use DefaultSubject + SubjectFileName = ProtocolSubjects.DefaultSubject.FileName; + elseif (nargin >= 2) && (ischar(varargin{2})) + [fName, fBase, fExt] = fileparts(varargin{2}); + % Argument is a Matlab .mat filename + if strcmpi(fExt, '.mat') + SubjectFileName = varargin{2}; + % Else : assume argument is a directory + else + % Find subject file in this directory + ProtocolInfo = bst_getContext('ProtocolInfo'); + subjPath = strrep(varargin{2}, ProtocolInfo.SUBJECTS, ''); + subjFile = dir(fullfile(ProtocolInfo.SUBJECTS, subjPath, '*brainstormsubject*.*')); + if (length(subjFile) == 1) + SubjectFileName = fullfile(subjPath, subjFile.name); + else + return + end + end + + % Call: bst_getContext('subject'); => looking for current subject + elseif (nargin < 2) + % Get current subject filename in current study + sStudy = bst_getContext('Study'); + if isempty(sStudy) + return + end + SubjectFileName = sStudy.BrainStormSubject; + % If study's subject is not defined, get DefaultSubject + if isempty(SubjectFileName) && ~isempty(ProtocolSubjects.DefaultSubject) + SubjectFileName = ProtocolSubjects.DefaultSubject.FileName; + end + else + error('Invalid call to bst_getContext()'); + end + + % If Subject is defined by its filename + if isempty(sSubject) + % Look in Default Subject + if ~isempty(ProtocolSubjects.DefaultSubject) && io_compareFileNames(ProtocolSubjects.DefaultSubject.FileName, SubjectFileName) + sSubject = ProtocolSubjects.DefaultSubject; + iSubject = 0; + % If not found : find target subject file name in normal subjects + else + iSubject = find(io_compareFileNames({ProtocolSubjects.Subject.FileName}, SubjectFileName), 1); + sSubject = ProtocolSubjects.Subject(iSubject); + end + end + + % Return found subject + if ~isempty(iSubject) && ~isempty(sSubject) + % If subject uses default subject + if sSubject.UseDefaultAnat && ~isRaw && ~isempty(ProtocolSubjects.DefaultSubject) && ~isempty(ProtocolSubjects.DefaultSubject.FileName) + % Return default subject (WITH REAL SUBJECT'S NAME) + bstContext = ProtocolSubjects.DefaultSubject; + bstContext.Name = sSubject.Name; + bstContext.UseDefaultAnat = sSubject.UseDefaultAnat; + bstContext.UseDefaultChannel = sSubject.UseDefaultChannel; + bstIndex = iSubject; + % Else, return found subject + else + bstContext = sSubject; + bstIndex = iSubject; + end + end + +%% ==== SUBJECT WITH NAME ==== + % Usage : [sSubject, iSubject] = bst_getContext('Subject', SubjectName) + case 'subjectwithname' + % Parse inputs + if (nargin ~= 2) || ~ischar(varargin{2}) + error('Invalid call to bst_getContext()'); + end + SubjectName = varargin{2}; + % Get list of current protocol subjects + ProtocolSubjects = bst_getContext('ProtocolSubjects'); + if isempty(ProtocolSubjects) + return + end + % Search all subjects + iSubject = find(strcmpi({ProtocolSubjects.Subject.Name}, SubjectName), 1); + if ~isempty(iSubject) + bstContext = ProtocolSubjects.Subject(iSubject); + bstIndex = iSubject; + end + + +%% ==== SURFACE FILE ==== + % Usage : [sSubject, iSubject, iSurface] = bst_getContext('SurfaceFile', SurfaceFile) + case 'surfacefile' + % Get list of current protocol subjects + ProtocolSubjects = bst_getContext('ProtocolSubjects'); + ProtocolInfo = bst_getContext('ProtocolInfo'); + if isempty(ProtocolSubjects) + return + end; + + % Parse inputs + if (nargin == 2) + SurfaceFile = varargin{2}; + else + error('Invalid call to bst_getContext().'); + end + + % Remove SUBJECTS path from SurfaceFile + SurfaceFile = strrep(SurfaceFile, ProtocolInfo.SUBJECTS, ''); + % Look for surface file in DefaultSubject + if ~isempty(ProtocolSubjects.DefaultSubject) + % Find the first surface that matches the SurfaceFile + iSurface = find(io_compareFileNames(SurfaceFile, {ProtocolSubjects.DefaultSubject.Surface.FileName}), 1); + % If a surface was found in default subject : return it + if ~isempty(iSurface) + bstContext = ProtocolSubjects.DefaultSubject; + bstIndex = 0; + bstSubIndex = iSurface; + return + end + end + % Look for surface file in all the surfaces of all subjects + for iSubj = 1:length(ProtocolSubjects.Subject) + % Find the first surface that matches the SurfaceFile + iSurface = find(io_compareFileNames(SurfaceFile, {ProtocolSubjects.Subject(iSubj).Surface.FileName}), 1); + % If a surface was found in current subject : return it + if ~isempty(iSurface) + bstContext = ProtocolSubjects.Subject(iSubj); + bstIndex = iSubj; + bstSubIndex = iSurface; + return + end + end + + +%% ==== SURFACE FILE BY TYPE ==== + % Usage : [sSurface, iSurface] = bst_getContext('SurfaceFileByType', iSubject, SurfaceType) + % [sSurface, iSurface] = bst_getContext('SurfaceFileByType', SurfaceFile, SurfaceType) + % [sSurface, iSurface] = bst_getContext('SurfaceFileByType', MriFile, SurfaceType) + case 'surfacefilebytype' + % Get subject + if ischar(varargin{2}) + FileName = varargin{2}; + [fileFormat, fileTypes] = io_getFileType(FileName); + if ismember('tess', fileTypes) + [sSubject, iSubject] = bst_getContext('SurfaceFile', FileName); + else + [sSubject, iSubject] = bst_getContext('MriFile', FileName); + end + else + iSubject = varargin{2}; + sSubject = bst_getContext('Subject', iSubject); + end + SurfaceType = varargin{3}; + % Look for required surface type + field = ['i' SurfaceType]; + if ~isfield(sSubject, field) || isempty(sSubject.(field)) + return + end + bstContext = sSubject.Surface(sSubject.(field)); + bstIndex = sSubject.(field); + + +%% ==== MRI FILE ==== + % Usage : [sSubject, iSubject, iMri] = bst_getContext('MriFile', MriFile) + case 'mrifile' + % Get list of current protocol subjects + ProtocolSubjects = bst_getContext('ProtocolSubjects'); + ProtocolInfo = bst_getContext('ProtocolInfo'); + if isempty(ProtocolSubjects) + return + end; + + % Parse inputs + if (nargin == 2) + MriFile = varargin{2}; + else + error('Invalid call to bst_getContext().'); + end + + % Remove SUBJECTS path from MriFile + MriFile = strrep(MriFile, ProtocolInfo.SUBJECTS, ''); + % Look for MRI file in DefaultSubject + if ~isempty(ProtocolSubjects.DefaultSubject) + % Find the first MRI that matches the MriFile + iMri = find(io_compareFileNames(MriFile, {ProtocolSubjects.DefaultSubject.Anatomy.FileName}), 1); + % If a MRI was found in default subject : return it + if ~isempty(iMri) + bstContext = ProtocolSubjects.DefaultSubject; + bstIndex = 0; + bstSubIndex = iMri; + return + end + end + % Look for MRI file in all the MRIs of all subjects + for iSubj = 1:length(ProtocolSubjects.Subject) + % Find the first MRI that matches the MriFile + iMri = find(io_compareFileNames(MriFile, {ProtocolSubjects.Subject(iSubj).Anatomy.FileName}), 1); + % If a MRI was found in current subject : return it + if ~isempty(iMri) + bstContext = ProtocolSubjects.Subject(iSubj); + bstIndex = iSubj; + bstSubIndex = iMri; + return + end + end + + +%% ==== CHANNEL FILE ==== + % Usage: [sStudy, iStudy, iChannel] = bst_getContext('ChannelFile', ChannelFile) + case 'channelfile' + ProtocolInfo = bst_getContext('ProtocolInfo'); + % Parse inputs + if (nargin == 2) + ChannelFile = varargin{2}; + ChannelFile = strrep(ChannelFile, ProtocolInfo.STUDIES, ''); + else + error('Invalid call to bst_getContext().'); + end + % Look for Channel file in all the surfaces of all subjects + [bstContext, bstIndex, bstSubIndex] = findFileInStudies('Channel.FileName', ChannelFile); + + +%% ==== CHANNEL FILE FOR STUDY ==== + % Usage: [ChannelFile] = bst_getContext('ChannelFileForStudy', StudyFile/DataFile) + case 'channelfileforstudy' + % Parse inputs + if (nargin == 2) + StudyFile = varargin{2}; + else + error('Invalid call to bst_getContext().'); + end + % Get study in database + sStudy = bst_getContext('Study', StudyFile); + % Look if it is a DataFile + if isempty(sStudy) + sStudy = bst_getContext('DataFile', StudyFile); + end + if ~isempty(sStudy) && ~isempty(sStudy.Channel) + % Study has a channel file defined + ChannelFile = sStudy.Channel.FileName; + % Study not in database: look in StudyFile directory + else + % Get absolute path to StudyFile + ProtocolInfo = bst_getContext('ProtocolInfo'); + StudyFile = strrep(StudyFile, ProtocolInfo.STUDIES, ''); + dirPath = fileparts(fullfile(ProtocolInfo.STUDIES, StudyFile)); + % Get the directory to the data file + + % List all channel files in this dir + channelFileNames = dir(fullfile(dirPath, '*channel*.mat')); + % Check number of Channel files + if isempty(channelFileNames) + warning('Brainstorm:DBError', ['No channel file in directory "' strrep(dirPath,'\','\\') '".']); + ChannelFile = []; + elseif (length(channelFileNames) > 1) + warning('Brainstorm:DBError', ['Multiple channel files in "' strrep(dirPath,'\','\\') '".' 10 ... + 'Please alter this folder''s content so that it contains a single channel file.']); + ChannelFile = []; + else + ChannelFile = fullfile(fileparts(StudyFile), channelFileNames(1).name); + end + end + % Return Channel filename (relative) + bstContext = ChannelFile; + + +%% ==== CHANNEL STRUCT FOR STUDY ==== + % Usage: [sChannel, iChanStudy] = bst_getContext('ChannelForStudy', iStudies) + case 'channelforstudy' + % Parse inputs + if (nargin == 2) + iStudies = varargin{2}; + else + error('Invalid call to bst_getContext().'); + end + iChanStudies = []; + sListChannel = []; + for i = 1:length(iStudies) + % Get study + iStudy = iStudies(i); + sStudy = bst_getContext('Study', iStudy); + iChanStudy = iStudy; + % === Analysis-Inter node === + iAnalysisInter = -2; + iGlobalDefaultStudy = -3; + if (iStudy == iAnalysisInter) + % If no channel file is defined in 'Analysis-intra' node: look in + if isempty(sStudy.Channel) + % Get global default study + sStudy = bst_getContext('Study', iGlobalDefaultStudy); + iChanStudy = iGlobalDefaultStudy; + end + % === All other nodes === + else + % Get subject attached to study + [sSubject, iSubject] = bst_getContext('Subject', sStudy.BrainStormSubject, 1); + if isempty(sSubject) + return; + end + % Subject uses default channel/headmodel + if (sSubject.UseDefaultChannel ~= 0) + [sStudy, iChanStudy] = bst_getContext('DefaultStudy', iSubject); + if isempty(sStudy) + return + end + end + end + iChanStudies = [iChanStudies, iChanStudy]; + sListChannel = [sListChannel, sStudy.Channel]; + end + % Return Channel structure + bstContext = sListChannel; + bstIndex = iChanStudies; + + +%% ==== HEADMODEL STRUCT FOR STUDY ==== + % Usage: [sHeadModel] = bst_getContext('HeadModelForStudy', iStudy) + case 'headmodelforstudy' + % Parse inputs + if (nargin == 2) + iStudy = varargin{2}; + else + error('Invalid call to bst_getContext().'); + end + % Get study + sStudy = bst_getContext('Study', iStudy); + % === Analysis-Inter node === + iAnalysisInter = -2; + iGlobalDefaultStudy = -3; + if (iStudy == iAnalysisInter) + % If no channel file is defined in 'Analysis-intra' node: look in + if isempty(sStudy.iHeadModel) + % Get global default study + sStudy = bst_getContext('Study', iGlobalDefaultStudy); + end + % === All other nodes === + else + % Get subject attached to study + [sSubject, iSubject] = bst_getContext('Subject', sStudy.BrainStormSubject, 1); + if isempty(sSubject) + return; + end + % Subject uses default channel/headmodel + if (sSubject.UseDefaultChannel ~= 0) + sStudy = bst_getContext('DefaultStudy', iSubject); + if isempty(sStudy) + return + end + end + end + % Return HeadModel structure + if ~isempty(sStudy.iHeadModel) + bstContext = sStudy.HeadModel(sStudy.iHeadModel(1)); + else + bstContext = []; + end + + +%% ==== DATA FILE ==== + % Usage: [sStudy, iStudy, iData] = bst_getContext('DataFile', DataFile) + case 'datafile' + % Parse inputs + ProtocolInfo = bst_getContext('ProtocolInfo'); + if (nargin == 2) + DataFile = varargin{2}; + DataFile = strrep(DataFile, ProtocolInfo.STUDIES, ''); + else + error('Invalid call to bst_getContext().'); + end + % Look for surface file in all the surfaces of all subjects + [bstContext, bstIndex, bstSubIndex] = findFileInStudies('Data.FileName', DataFile); + + +%% ==== DATA FOR DATA LIST ==== + % Usage: [iFoundData] = bst_getContext('DataForDataList', iStudy, DataListName) + case 'datafordatalist' + iStudy = varargin{2}; + DataListName = varargin{3}; + % Get study structure + sStudy = bst_getContext('Study', iStudy); + % Get all the data files held by this datalist + removedTrialsFiles = cellfun(@strRemoveTrialTag, {sStudy.Data.FileName}, 'UniformOutput', 0); + iFoundData = find(strcmpi(removedTrialsFiles, DataListName)); + % Return found data files + bstContext = iFoundData; + + +%% ==== DATA FOR STUDY (INCLUDING SHARED STUDIES) ==== + % Usage: [iStudies, iDatas] = bst_getContext('DataForStudy', iStudy) + case 'dataforstudy' + % Get target study + iStudy = varargin{2}; + sStudy = bst_getContext('Study', iStudy); + isDefaultStudy = strcmpi(sStudy.Name, bst_getContext('DirDefaultStudy')); + isGlobalDefault = (iStudy == -3); + + % If study is the global default study + sStudies = []; + iStudies = []; + if isGlobalDefault + % Get all the subjects of the protocol + nbSubjects = bst_getContext('SubjectCount'); + for iSubject = 1:nbSubjects + sSubject = bst_getContext('Subject', iSubject, 1); + if sSubject.UseDefaultChannel + [tmp_sStudies, tmp_iStudies] = bst_getContext('StudyWithSubject', sSubject.FileName); + sStudies = [sStudies, tmp_sStudies]; + iStudies = [iStudies, tmp_iStudies]; + end + end + % Else, if study is a subject's default study (ie. channel file is shared by all studies of one subject) + elseif isDefaultStudy + % Get all the subject's studies + [sStudies, iStudies] = bst_getContext('StudyWithSubject', sStudy.BrainStormSubject, 'intra_subject', 'default_study'); + else + % Normal: one channel per condition + sStudies = sStudy; + iStudies = iStudy; + end + % Get all the DataFiles for all these studies + for i = 1:length(sStudies) + nData = length(sStudies(i).Data); + bstContext = [bstContext, repmat(iStudies(i), [1,nData])]; + bstIndex = [bstIndex, 1:nData]; + end + + +%% ==== DATA FOR STUDIES (INCLUDING SHARED STUDIES) ==== + % Usage: [iStudies, iDatas] = bst_getContext('DataForStudies', iStudies) + case 'dataforstudies' + iStudies = varargin{2}; + for i = 1:length(iStudies) + [tmp_iStudies, tmp_iDatas] = bst_getContext('DataForStudy', iStudies(i)); + bstContext = [bstContext, tmp_iStudies]; + bstIndex = [bstIndex, tmp_iDatas]; + end + +%% ==== DATA FILE FOR CHANNEL FILE ==== + % Usage: DataFiles = bst_getContext('DataForChannelFile', ChannelFile) + case 'dataforchannelfile' + ChannelFile = varargin{2}; + DataFiles = {}; + % Get study for the given channel file + [sStudy, iStudy] = bst_getContext('ChannelFile', ChannelFile); + if isempty(sStudy) + return; + end + % Get dependent data files + [iStudies, iDatas] = bst_getContext('DataForStudy', iStudy); + % Get all the Data filenames + for i = 1:length(iStudies) + sStudy = bst_getContext('Study', iStudies(i)); + DataFiles = cat(2, DataFiles, {sStudy.Data(iDatas(i)).FileName}); + end + bstContext = DataFiles; + + +%% ==== RESULTS FILE ==== + % Usage: [sStudy, iStudy, iResult] = bst_getContext('ResultsFile', ResultsFile) + case 'resultsfile' + ProtocolInfo = bst_getContext('ProtocolInfo'); + % Parse inputs + if (nargin == 2) + ResultsFile = varargin{2}; + ResultsFile = strrep(ResultsFile, ProtocolInfo.STUDIES, ''); + else + error('Invalid call to bst_getContext().'); + end + % Look for surface file in all the surfaces of all subjects + [bstContext, bstIndex, bstSubIndex] = findFileInStudies('Result.FileName', ResultsFile); + + +%% ==== RESULTS FOR DATA FILE ==== + % Usage: [sStudy, iStudy, iResults] = bst_getContext('ResultsForDataFile', DataFile) : search the whole protocol + % Usage: [sStudy, iStudy, iResults] = bst_getContext('ResultsForDataFile', DataFile, iStudies) : search only the specified studies + case 'resultsfordatafile' + ProtocolInfo = bst_getContext('ProtocolInfo'); + % Parse inputs + if (nargin >= 2) + DataFile = varargin{2}; + DataFile = strrep(DataFile, ProtocolInfo.STUDIES, ''); + else + error('Invalid call to bst_getContext().'); + end + % Determine in which studies to search for ResultsFile + if (nargin >= 3) + % Studies specified in argument + iStudies = varargin{3}; + else + % Get study in which DataFile is located + [sStudies, iStudies] = bst_getContext('DataFile', DataFile); + if isempty(iStudies) + return; + end + end + % Search selected studies + [bstContext, bstIndex, bstSubIndex] = findFileInStudies('Result.DataFile', DataFile, iStudies); + + + +%% ==== STAT FILE ==== + % Usage: [sStudy, iStudy, iData] = bst_getContext('StatFile', StatFile) + case 'statfile' + % Parse inputs + ProtocolInfo = bst_getContext('ProtocolInfo'); + if (nargin == 2) + StatFile = varargin{2}; + StatFile = strrep(StatFile, ProtocolInfo.STUDIES, ''); + else + error('Invalid call to bst_getContext().'); + end + % Look for surface file in all the surfaces of all subjects + [bstContext, bstIndex, bstSubIndex] = findFileInStudies('Stat.FileName', StatFile); + + + +%% ==== STAT FOR DATA FILE ==== + % Usage: [sStudy, iStudy, iResults] = bst_getContext('StatForDataFile', DataFile) : search the whole protocol + % Usage: [sStudy, iStudy, iResults] = bst_getContext('StatForDataFile', DataFile, iStudies) : search only the specified studies + case 'resultsfordatafile' + ProtocolInfo = bst_getContext('ProtocolInfo'); + % Parse inputs + if (nargin >= 2) + DataFile = varargin{2}; + DataFile = strrep(DataFile, ProtocolInfo.STUDIES, ''); + else + error('Invalid call to bst_getContext().'); + end + % Determine in which studies to search for ResultsFile + if (nargin >= 3) + % Studies specified in argument + iStudies = varargin{3}; + else + % Get study in which DataFile is located + [sStudies, iStudies] = bst_getContext('DataFile', DataFile); + if isempty(iStudies) + return; + end + end + % Search selected studies + [bstContext, bstIndex, bstSubIndex] = findFileInStudies('Stat.DataFile', DataFile, iStudies); + + +%% ==== ALL CONDITIONS FOR ONE SUBJECT ==== + % Usage: [Conditions] = bst_getContext('ConditionsForSubject', SubjectFile) + case 'conditionsforsubject' + % Parse inputs + if (nargin == 2) + SubjectFile = varargin{2}; + else + error('Invalid call to bst_getContext().'); + end + % Get list of studies associated with subject + sStudies = bst_getContext('StudyWithSubject', SubjectFile); + % Get Conditions for each study + Conditions = {}; + for i = 1:length(sStudies) + % Test if the condition of this study was not added previously + isNewCondition = 1; + for iCond = 1:length(Conditions) + % If new condition is found + % (and excludes DirAnalysisIntra and DirDefaultSubject from list) + if isequal(sStudies(i).Condition, Conditions(iCond)) || ... + strcmpi(sStudies(i).Condition{1}, bst_getContext('DirAnalysisIntra')) || ... + strcmpi(sStudies(i).Condition{1}, bst_getContext('DirDefaultSubject')) + isNewCondition = 0; + break; + end + end + % If Condition is not added yet : add it to the list + if isNewCondition + Conditions{end+1} = sStudies(i).Condition; + end + end + % Return conditions list + bstContext = Conditions; + + +%% ==== ANATOMY DEFAULTS ==== + % Returns an array of struct(fullpath, dir, name) of all the Brainstorm anatomy defaults + case 'anatomydefaults' + % Get template directory + baseDir = fullfile(bst_getContext('BrainStormHomeDir'), 'defaults', 'anatomy'); + % Get subdirectories + fileList = dir(baseDir); + defaultsList = repmat(struct('fullpath','', 'dir','', 'name',''), 0); + % Find all the valid defaults (subdirectory with a brainstormsubject.mat in it) + for i=1:length(fileList) + % Entry is a directory W/ a name that does not start with a '.' + if fileList(i).isdir && (fileList(i).name(1) ~= '.') + tmpDir = fileList(i).name; + tmpFullpath = fullfile(baseDir, tmpDir); + % Look for a 'brainstormsubject.mat' + SubjectFileList = dir(fullfile(tmpFullpath, '*brainstormsubject*.mat')); + % If there is only brainstorm subject file : accept template as valid + if (length(SubjectFileList) == 1) + tmpFile = fullfile(tmpFullpath, SubjectFileList(1).name); + % Load the 'Name' field of the 'brainstormsubject.mat' file (default subject's name) + try + % Load file + SubjectMat = load(tmpFile, 'Name'); + % Add an entry to the defaults list + defaultsList(end + 1) = struct('fullpath', tmpFullpath, ... + 'dir', tmpDir, ... + 'name', SubjectMat.Name); + catch + % Error loading file => go to next iteration + continue + end + end + end + end + % Return defaults list + bstContext = defaultsList; + + +%% ==== EEG DEFAULTS ==== + % Returns an array of struct(fullpath, name) of all the Brainstorm eeg nets defaults + % Usage: eegDefaults = bst_getContext('EEGDefaults') + case 'eegdefaults' + fullDefaultsList = repmat(struct('contents','', 'name',''), 0); + % Get template directory + eegDefaultsDir = fullfile(bst_getContext('BrainStormHomeDir'), 'defaults', 'eeg'); + % Get directory + dirList = dir(fullfile(eegDefaultsDir, '*')); + % For each template directory + for iDir = 1:length(dirList) + % Excludes '.' and '..' + if (dirList(iDir).name(1) == '.') + continue; + end + % Get full dir + fulldirName = fullfile(eegDefaultsDir, dirList(iDir).name); + if isdir(fulldirName) + % Get files list + fileList = dir(fullfile(fulldirName, '*channel*.mat')); + defaultsList = repmat(struct('fullpath','', 'name',''), 0); + % Find all the valid defaults (channel files) + for iFile = 1:length(fileList) + defaultsList(iFile).fullpath = fullfile(fulldirName, fileList(iFile).name); + [tmp__, baseName] = fileparts(fileList(iFile).name); + defaultsList(iFile).name = strrep(baseName, 'channel_', ''); + defaultsList(iFile).name = strrep(defaultsList(iFile).name, '_channel', ''); + defaultsList(iFile).name = strrep(defaultsList(iFile).name, '_', ' '); + end + % Add files list to defaults list + if ~isempty(defaultsList) + fullDefaultsList(end + 1) = struct('contents', defaultsList, ... + 'name', dirList(iDir).name); + end + end + end + + % Return defaults list + bstContext = fullDefaultsList; + + +%% ==== GET FILENAMES ==== + case 'getfilenames' + iStudies = varargin{2}; + iItems = varargin{3}; + DataType = varargin{4}; + FileNames = cell(1, length(iStudies)); + for i = 1:length(iStudies) + % Get study definition + sStudy = bst_getContext('Study', iStudies(i)); + % Recordings or sources + if strcmpi(DataType, 'data') + FileNames{i} = sStudy.Data(iItems(i)).FileName; + else + FileNames{i} = sStudy.Result(iItems(i)).FileName; + end + end + bstContext = FileNames; + + +%% ==== BEST FITTING SPHERE ==== + % Get a spherical approximation to head shape in the headmodels + % USAGE: [bfs_center, bfs_radius] = bst_getContext('BestFittingSphere', ChannelFile) + case 'bestfittingsphere' + % Parse inputs + if (nargin == 2) + ChannelFile = varargin{2}; + else + error('Invalid call to bst_getContext().'); + end + % Find study associated with this channel file + sStudy = bst_getContext('ChannelFile', ChannelFile); + if isempty(sStudy) || isempty(sStudy.HeadModel) + return + end + % Get protocol description + ProtocolInfo = bst_getContext('ProtocolInfo'); + % Load channel file + ChannelMat = load(fullfile(ProtocolInfo.STUDIES, sStudy.Channel.FileName)); + % Get MEG and EEG channels indices + iMeg = good_channel(ChannelMat.Channel, [], 'MEG'); + iEeg = good_channel(ChannelMat.Channel, [], 'EEG'); + % Get the list of head models to process (starting with the default one) + listHeadModels = [sStudy.iHeadModel, setdiff(1:length(sStudy.HeadModel), sStudy.iHeadModel)]; + % Initialize returned values + bfs_center = []; + bfs_radius = []; + % Process all the headmodels + for i = 1:length(listHeadModels) + iHeadModel = listHeadModels(i); + % Load "Param" structure from headmodel file + HeadModelFile = fullfile(ProtocolInfo.STUDIES, sStudy.HeadModel(iHeadModel).FileName); + warning off + HeadModelMat = load(HeadModelFile, 'Param', 'MEGMethod', 'EEGMethod'); + warning on + % Find a MEG spherical model in headmodel + if isfield(HeadModelMat, 'MEGMethod') && strcmpi(HeadModelMat.MEGMethod, 'meg_sphere') && ~isempty(iMeg) + bfs_center = HeadModelMat.Param(iMeg(1)).Center; + bfs_radius = max(HeadModelMat.Param(iMeg(1)).Radii); + break; + end + % Find an EEG spherical model in headmodel + if isfield(HeadModelMat, 'EEGMethod') && ismember(HeadModelMat.EEGMethod, {'eeg_sphere','eeg_3sphereBerg', 'eeg_3sphere'}) && ~isempty(iEeg) + bfs_center = HeadModelMat.Param(iEeg(1)).Center; + bfs_radius = max(HeadModelMat.Param(iEeg(1)).Radii); + break; + end + end + % Return BFS if found somewhere + bstContext = bfs_center; + bstIndex = bfs_radius; + + +%% ==== GUI ==== + case 'gui' + bstContext = getappdata(0, 'BrainStormGUI'); + + case 'progressbar' + bstContext = getappdata(0, 'BrainStormProgressBar'); + + case 'layout' + if ispref('BrainStorm', 'sLayout') + bstContext = getpref('BrainStorm', 'sLayout'); + else + bstContext = []; + end + + case 'layoutmanager' + if ispref('BrainStorm', 'LayoutManager') + bstContext = getpref('BrainStorm', 'LayoutManager'); + else + bstContext = 'TileWindows'; + end + + case 'byteorder' + if ispref('BrainStorm', 'ByteOrder') + bstContext = getpref('BrainStorm', 'ByteOrder'); + else + bstContext = 'l'; + end + + case 'uniformizetimeseriesscales' + if ispref('BrainStorm', 'UniformizeTimeSeriesScales') + bstContext = getpref('BrainStorm', 'UniformizeTimeSeriesScales'); + else + bstContext = 1; + end + + case 'displayaveragereference' + if ispref('BrainStorm', 'DisplayAverageReference') + bstContext = getpref('BrainStorm', 'DisplayAverageReference'); + else + bstContext = 1; + end + + case 'autoupdates' + if ispref('BrainStorm', 'AutoUpdates') + bstContext = getpref('BrainStorm', 'AutoUpdates'); + else + bstContext = 1; + end + case 'displaygfp' + if ispref('BrainStorm', 'DisplayGFP') + bstContext = getpref('BrainStorm', 'DisplayGFP'); + else + bstContext = 1; + end + + + case 'tsdisplaymode' + if ispref('BrainStorm', 'TSDisplayMode') + bstContext = getpref('BrainStorm', 'TSDisplayMode'); + else + bstContext = 'Butterfly'; + end + + case 'expandtrialslists' + if ispref('BrainStorm', 'ExpandTrialsLists') + bstContext = getpref('BrainStorm', 'ExpandTrialsLists'); + else + bstContext = 0; + end + + case 'reloaddbatstartup' + if ispref('BrainStorm', 'ReloadDbAtStartup') + bstContext = getpref('BrainStorm', 'ReloadDbAtStartup'); + else + bstContext = 0; + end + + case 'usedoublescreen' + if ispref('BrainStorm', 'UseDoubleScreen') + bstContext = getpref('BrainStorm', 'UseDoubleScreen'); + else + bstContext = 1; + end + + case 'usesigproctoolbox' + % Check if Signal Processing Toolbox is installed + isToolboxInstalled = exist('fir2') || exist('myfir2'); + % Return user preferences + if ~isToolboxInstalled + bstContext = 0; + elseif ispref('BrainStorm', 'UseSigProcToolbox') + bstContext = getpref('BrainStorm', 'UseSigProcToolbox'); + else + bstContext = 1; + end + + case 'lastuseddirs' + LastUsedDirs = struct('ImportData', '', ... + 'ImportChannel', '', ... + 'ImportAnat', '', ... + 'Export', ''); + % Check if preference is weel defined + if ispref('BrainStorm', 'LastUsedDirs') && all(ismember(fieldnames(LastUsedDirs), fieldnames(getpref('BrainStorm', 'LastUsedDirs')))) + bstContext = getpref('BrainStorm', 'LastUsedDirs'); + % Check if directories still exist + if ~exist(bstContext.ImportData, 'dir') + bstContext.ImportData = ''; + end + if ~exist(bstContext.ImportChannel, 'dir') + bstContext.ImportChannel = ''; + end + if ~exist(bstContext.ImportAnat, 'dir') + bstContext.ImportAnat = ''; + end + if ~ischar(bstContext.Export) + bstContext.Export = ''; + warning('*** LastUsedDirs.Export = 0 ***'); + elseif ~exist(bstContext.Export, 'dir') + bstContext.Export = ''; + end + else + bstContext = LastUsedDirs; + setpref('BrainStorm', 'LastUsedDirs', LastUsedDirs); + end + + case 'defaultformats' + bstContext = struct('DataIn', '', ... + 'DataOut', '', ... + 'ChannelIn', '', ... + 'ChannelOut', '', ... + 'NoiseCovIn', '', ... + 'ResultsIn', '', ... + 'ResultsOut', '', ... + 'MriIn', '', ... + 'MriOut', '', ... + 'SurfaceIn', '', ... + 'SurfaceOut', ''); + if ispref('BrainStorm', 'DefaultFormats') + bstPref = getpref('BrainStorm', 'DefaultFormats'); + if ~isempty(bstPref) && all(isfield(bstPref, fieldnames(bstContext))) + bstContext = bstPref; + end + end + + case 'bemoptions' + bstContext = struct(... + 'Interpolative', 0, ... + 'Basis', 'linear', ... + 'Test', 'collocation', ... + 'ISA', 1, ... + 'checksurf', 1, ... + 'NVertMax', 1000, ... + 'ForceXferComputation', 1); + if ispref('BrainStorm', 'BEMOptions') + bstPref = getpref('BrainStorm', 'BEMOptions'); + if ~isempty(bstPref) && all(isfield(bstPref, fieldnames(bstContext))) + bstContext = bstPref; + end + end + + case 'bfsproperties' + if ispref('BrainStorm', 'BFSProperties') && ~isempty(getpref('BrainStorm', 'BFSProperties')) + bstContext = getpref('BrainStorm', 'BFSProperties'); + else + bstContext = [.33 .0042 .33 .88 .93]; + end + + case 'importctfoptions' + bstContext = struct('UseAllTrials', 1, ... + 'TrialsTimeRange', [], ... + 'MarkersTimeRange', [-.05, .2], ... + 'KeepNativeFormat', 0, ... + 'UseMarkers', 1, ... + ... 'EEGRef', 0, ... + 'MarkersSelection', 'all', ... {'all', 'common', 'individual'} + 'DCOffset', 0 ... {0:do no remove, 1:remove/whole, 2:remove/pretrigger} + ); + if ispref('BrainStorm', 'ImportCTFOptions') + bstPref = getpref('BrainStorm', 'ImportCTFOptions'); + if ~isempty(bstPref) && all(isfield(bstPref, fieldnames(bstContext))) + bstContext = bstPref; + end + end + + case 'importfifoptions' + bstContext = struct('UseEvents', 1, ... + 'EventsTimeRange', [-.05, .2], ... + 'SplitRaw', 1, ... + 'SplitLength', 4, ... + 'KeepNativeFormat', 0, ... + 'Resample', 0, ... + 'ResampleFreq', 0, ... + 'UseCtfCompensators', 0, ... + 'LastCtfCompensator', 3, ... + 'UseNeuromagSSP', 0); + if ispref('BrainStorm', 'ImportFifOptions') + bstPref = getpref('BrainStorm', 'ImportFifOptions'); + if ~isempty(bstPref) && all(isfield(bstPref, fieldnames(bstContext))) + bstContext = bstPref; + end + end + + case 'importeegrawoptions' + bstContext = struct('isCanceled', 0, ... + 'BaselineDuration', 0, ... + 'SamplingRate', 1000, ... + 'MatrixOrientation', 'channelXtime', ... % {'channelXtime', 'timeXchannel'} + 'VoltageUnits', 'V', ... % {'\muV', 'mV', 'V'} + 'SkipLines', 0); + if ispref('BrainStorm', 'ImportEegRawOptions') + bstPref = getpref('BrainStorm', 'ImportEegRawOptions'); + if ~isempty(bstPref) && all(isfield(bstPref, fieldnames(bstContext))) + bstContext = bstPref; + end + end + + case 'bugreportoptions' + bstContext = struct('isEnabled', 0, ... + 'SmtpServer', 'mailhost.chups.jussieu.fr', ... + 'UserEmail', ''); + if ispref('BrainStorm', 'BugReportOptions') + bstPref = getpref('BrainStorm', 'BugReportOptions'); + if ~isempty(bstPref) && all(isfield(bstPref, fieldnames(bstContext))) + bstContext = bstPref; + end + end + + case 'inverseoptions' + if ispref('BrainStorm', 'InverseOptions') && ~isempty(getpref('BrainStorm', 'InverseOptions')) + bstContext = getpref('BrainStorm', 'InverseOptions'); + end + if isempty(bstContext) || ~isfield(bstContext.LMMS, 'UseNoiseCov') + bstContext = struct('LMMS', struct(... + 'FFNormalization', 1, ... + 'Tikhonov', 10, ... + 'ComputeKernel', 1, ... + 'UseNoiseCov', 1), ... + 'LCMV', struct(... + 'NNormalization', 0, ... + 'Tikhonov', 10, ... + 'isConstrained', 1, ... + 'OutputFormat', 2)); + end + + case 'defaultsurfacedisplay' + bstContext = struct('SurfShowCurvature', 0, ... + 'SurfSmoothValue', 0, ... + 'DataIntThreshold', 0.5,... + 'DataExtThreshold', 0); + if ispref('BrainStorm', 'DefaultSurfaceDisplay') + bstPref = getpref('BrainStorm', 'DefaultSurfaceDisplay'); + if ~isempty(bstPref) && all(isfield(bstPref, fieldnames(bstContext))) + bstContext = bstPref; + end + end + + case 'magneticextrapoptions' + bstContext = struct('ForceWhitening', 0, ... + 'EpsilonValue', 0.0001); + if ispref('BrainStorm', 'MagneticExtrapOptions') + bstPref = getpref('BrainStorm', 'MagneticExtrapOptions'); + if ~isempty(bstPref) && all(isfield(bstPref, fieldnames(bstContext))) + bstContext = bstPref; + end + end + + +%% ==== PANEL CONTAINERS ==== + case 'panelcontainer' + % Get Brainstorm GUI context structure + bst_GUI = getappdata(0, 'BrainStormGUI'); + if (isempty(bst_GUI) || ~isfield(bst_GUI, 'panelContainers')) + error('Brainstorm GUI is not yet initialized'); + end + + % Get ContainerName in argument + if ((nargin >= 2) && (ischar(varargin{2}))) + ContainerName = varargin{2}; + % If no container name in argument : just display all the container names + else + disp('Registered panel containers :'); + for iContainer = 1:length(bst_GUI.panelContainers) + disp([' - ' bst_GUI.panelContainers(iContainer).name]); + end + return + end + + % Look for containerName in all the registered panel containers + iContainer = 1; + found = 0; + while (~found (iContainer <= length(bst_GUI.panelContainers))) + if (strcmpi(ContainerName, bst_GUI.panelContainers(iContainer).name)) + found = 1; + else + iContainer = iContainer + 1; + end + end + % If container is found : return it + if (found) + bstContext = bst_GUI.panelContainers(iContainer).jHandle; + else + warning('Brainstorm:InvalidContainer', 'Container ''%s'' could not be found.', ContainerName); + end + + +%% ==== PANELS ==== + case 'panel' + % Get Brainstorm GUI context structure + bst_GUI = getappdata(0, 'BrainStormGUI'); + if (isempty(bst_GUI) || ~isfield(bst_GUI, 'panels')) + return + end + % Get Panel in argument + if ((nargin >= 2) && (ischar(varargin{2}))) + PanelName = varargin{2}; + % If no panel name in argument : just display all the panels names + else + disp('Registered panels :'); + for iContainer = 1:length(bst_GUI.panels) + disp([' - ' get(bst_GUI.panels(iContainer), 'name')]); + end + return + end + % Look for panelName in all the registered panels + iPanel = find(strcmpi(PanelName, get(bst_GUI.panels, 'name')), 1); + if ~isempty(iPanel) + bstContext = bst_GUI.panels(iPanel); + bstIndex = iPanel; + end + + +%% ==== PANEL CONTROLS ==== +% Calls : bst_getContext('PanelControls', PanelName) + case 'panelcontrols' + % Get Panel name in argument + if ((nargin >= 2) && (ischar(varargin{2}))) + PanelName = varargin{2}; + else + error('Invalid call to bst_getContext()'); + end + % Find BstPanel with this name + bstPanel = bst_getContext('Panel', PanelName); + % If panel was found : return its controls + if ~isempty(bstPanel) + bstContext = get(bstPanel, 'sControls'); + end + + +%% ==== PANEL ELEMENTS ==== +% Calls : bst_getContext('PanelElement', PanelName, ElementName) +% bst_getContext('PanelElement', ElementName) + case 'panelelement' + % Get Panel name in argument + if ((nargin == 2) && (ischar(varargin{2}))) + panelName = ''; + objectName = varargin{2}; + elseif ((nargin == 3) && (ischar(varargin{2}) && (ischar(varargin{2})))) + panelName = varargin{2}; + objectName = varargin{3}; + else + error('Invalid call to bst_getContext()'); + end + + % Look for control + % If panel name is known + if ~isempty(panelName) + % Get panel that holds the object + bstPanel = bst_getContext('Panel', panelName); + if (isempty(bstPanel)), return, end + % Get the object name in the objects referenced in this panel + panelElement = get(bstPanel, objectName); + if (isempty(panelElement)) + warning('Brainstorm:InvalidElement', 'Object ''%s'' could not be found in panel ''%s''.', objectName, panelName); + return + end + + % If panel name is not defined : need to search all panels + else + % Get Brainstorm GUI context structure + bst_GUI = bst_getContext('GUI'); + if (isempty(bst_GUI) || ~isfield(bst_GUI, 'panels')) + error('Brainstorm GUI is not yet initialized'); + end + % Process all panels + [panelElement, bstPanel] = get(bst_GUI.panels, objectName); + end + + % If element was found : return object and panel + if (~isempty(panelElement)) + bstContext = panelElement; + bstIndex = bstPanel; + end + + +%% ==== DIRECTORIES ==== + case 'dirdefaultsubject' + bstContext = '@default_subject'; + case 'dirdefaultstudy' + bstContext = '@default_study'; + case 'diranalysisintra' + bstContext = '@intra'; + case 'diranalysisinter' + bstContext = '@inter'; + case 'normalizedsubjectname' + bstContext = 'Group analysis'; + % bstContext = 'Default subject'; + + + +%% ==== ERROR ==== + otherwise + error(sprintf('Invalid context : "%s"', contextName)); +end +end + + + + +%% ==== HELPERS ==== +% Return all the protocol studies that have a given file in its structures +% Possible field names: Result.DataFile, Result.FileName, Data.FileName, Channel.FileName +% +% USAGE: [sFoundStudy, iFoundStudy, iItems] = findFileInStudies(fieldName, fieldFile, iStudiesList) +% [sFoundStudy, iFoundStudy, iItems] = findFileInStudies(fieldName, fieldFile) +function [sFoundStudy, iFoundStudy, iItems] = findFileInStudies(fieldName, fieldFile, iStudiesList) + sFoundStudy = []; + iFoundStudy = []; + iItems = []; + % Get protocol information + ProtocolInfo = bst_getContext('ProtocolInfo'); + ProtocolStudies = bst_getContext('ProtocolStudies'); + % Remove STUDIES path from fieldFile + fieldFile = strrep(fieldFile, ProtocolInfo.STUDIES, ''); + % List studies to process + if (nargin < 3) + iStudiesList = 1:length(ProtocolStudies.Study); + else + % Remove default studies (processed anyway) + iStudiesList = setdiff(iStudiesList, [-2 -3]); + end + + try + % NORMAL STUDIES: Look for surface file in all the surfaces of all subjects + for iStudy = iStudiesList + % Get list of fields of this study + try + studyList = eval(['{ProtocolStudies.Study(iStudy).', fieldName, '}']); + catch + continue + end + % Nothing found: continue with next study + if isempty(studyList) + continue; + end + % Replace empty cells with empty strings + iEmtpyCells = find(cellfun(@isempty, studyList)); + for i = 1:length(iEmtpyCells) + studyList{iEmtpyCells(i)} = ''; + end + % Remove STUDIES path from all elements in list + studyList = strrep(studyList, ProtocolInfo.STUDIES, ''); + % Find target in this list + iItems = find(io_compareFileNames(studyList, fieldFile)); + if ~isempty(iItems) + sFoundStudy = ProtocolStudies.Study(iStudy); + iFoundStudy = iStudy; + return + end + end + % SPECIAL INDICES + iAnalysisStudy = -2; + iDefaultStudy = -3; + % ANALYSIS STUDY + if isempty(iItems) && ~isempty(ProtocolStudies.AnalysisStudy) + % Get list of fields of this study + studyList = eval(['{ProtocolStudies.AnalysisStudy.', fieldName, '}']); + % Remove STUDIES path from all elements in list + studyList = strrep(studyList, ProtocolInfo.STUDIES, ''); + % Find target in this list + iItems = find(io_compareFileNames(studyList, fieldFile)); + if ~isempty(iItems) + sFoundStudy = ProtocolStudies.AnalysisStudy; + iFoundStudy = iAnalysisStudy; + return + end + end + % DEFAULT STUDY + if isempty(iItems) && ~isempty(ProtocolStudies.DefaultStudy) + % Get list of fields of this study + studyList = eval(['{ProtocolStudies.DefaultStudy.', fieldName, '}']); + % Remove STUDIES path from all elements in list + studyList = strrep(studyList, ProtocolInfo.STUDIES, ''); + % Find target in this list + iItems = find(io_compareFileNames(studyList, fieldFile)); + if ~isempty(iItems) + sFoundStudy = ProtocolStudies.DefaultStudy; + iFoundStudy = iDefaultStudy; + return + end + end + catch + warning('Database error. Please reload all the protocol.'); + end +end + + + + + + + diff --git a/brainstorm3/toolbox/core/bst_safeCall.m b/brainstorm3/toolbox/core/bst_safeCall.m new file mode 100644 index 0000000..b4d722f --- /dev/null +++ b/brainstorm3/toolbox/core/bst_safeCall.m @@ -0,0 +1,46 @@ +function varargout = bst_safeCall( varargin ) +% BST_SAFECALL: Call any function with an error catching. + +% @============================================================================= +% This software is part of The Brainstorm Toolbox +% http://neuroimage.usc.edu/brainstorm +% +% Copyright (c)2010 Brainstorm by the University of Southern California +% This software is distributed under the terms of the GNU General Public License +% as published by the Free Software Foundation. Further details on the GPL +% license can be found at http://www.gnu.org/copyleft/gpl.html. +% +% FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE +% UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY +% WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF +% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY +% LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE. +% +% For more information type "brainstorm licence" at command prompt. +% =============================================================================@ +% +% Authors: Francois Tadel, 2008 +% ----------------------------- Script History --------------------------------- +% FT 24-Jun-2008 Creation +% ------------------------------------------------------------------------------ + +try + if (nargout) + [varargout{1:nargout}] = feval(varargin{:}); + else + feval(varargin{:}); + end +catch + bst_error(); + if (nargout > 0) + varargout(1:nargout) = cell(1, nargout); + end +end + +end + + + + + + diff --git a/brainstorm3/toolbox/db/db_getDataTemplate.m b/brainstorm3/toolbox/db/db_getDataTemplate.m new file mode 100644 index 0000000..aee8b4c --- /dev/null +++ b/brainstorm3/toolbox/db/db_getDataTemplate.m @@ -0,0 +1,538 @@ +function [ template ] = db_getDataTemplate(structureName) +% DB_GETDATATEMPLATE: Defines a template structure for all the Brainstorm data types. +% +% USAGE : [template] = db_getDataTemplate(structureName); +% +% INPUT: String - {'Anatomy', 'Surface', 'Channel', 'Data', 'HeadModel', 'Results', 'Stat', +% 'Subject', 'Study', 'ProtocolInfo', 'ProtocolSubjects', 'ProtocolStudies', +% 'Layout', 'Colormap', 'Measures', 'LoadedResults', 'FigureId', +% 'Figure', 'Dataset', 'DisplayHandlesTimeSeries', 'DisplayHandlesTopography', +% 'DisplayHandles3DViz', 'Scout', 'GlobalData', 'Landmark', +% 'SCS', 'NCS', 'TessInfo', ...} + +% @============================================================================= +% This software is part of The Brainstorm Toolbox +% http://neuroimage.usc.edu/brainstorm +% +% Copyright (c)2009 Brainstorm by the University of Southern California +% This software is distributed under the terms of the GNU General Public License +% as published by the Free Software Foundation. Further details on the GPL +% license can be found at http://www.gnu.org/copyleft/gpl.html. +% +% FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE +% UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY +% WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF +% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY +% LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE. +% +% For more information type "brainstorm licence" at command prompt. +% =============================================================================@ +% +% Authors: Francois Tadel, 2008 +% ----------------------------- Script History --------------------------------- +% FT 24-Jun-2008 Creation +% ------------------------------------------------------------------------------ + +% Define templates that might be used in different ways +templateAnatomy = struct(... + 'Comment', '', ... + 'FileName', ''); + +templateSurface = struct(... + 'Comment', '', ... + 'FileName', '', ... + 'SurfaceType', ''); + +templateChannel = struct(... + 'FileName', '', ... + 'Comment', '', ... + 'nbChannels', 0, ... + 'Modalities', [], ... + 'DisplayableSensorTypes', []); +templateData = struct(... + 'FileName', '', ... + 'Comment', ''); +templateImage = struct(... + 'FileName', '', ... + 'Comment', ''); +templateNoiseCov = struct(... + 'FileName', '', ... + 'Comment', ''); +templateHeadModel = struct(... + 'FileName', '', ... + 'HeadModelName', '', ... + 'HeadModelType', '', ... + 'MEGMethod', '', ... + 'EEGMethod', '', ... + 'GridName', ''); +templateResult = struct(... + 'FileName', '', ... + 'Comment', '', ... + 'DataFile', '', ... + 'isLink', 0); +templateStat = struct(... + 'FileName', '', ... + 'Comment', '', ... + 'Type', '', ... + 'pThreshold', '', ... + 'DataFile', ''); % FOR STATS ON RESULTS ONLY + +switch lower(structureName) + % ==== ANATOMY ==== + case 'anatomy' + template = templateAnatomy; + % ==== SURFACE ==== + case 'surface' + template = templateSurface; + + % ==== CHANNEL ==== + case 'channel' + template = templateChannel; + % ==== DATA ==== + case 'data' + template = templateData; + % ==== HEADMODEL ==== + case 'headmodel' + template = templateHeadModel; + % ==== RESULTS ==== + case 'results' + template = templateResult; + % ==== STAT ==== + case 'stat' + template = templateStat; + % ==== IMAGE ==== + case 'image' + template = templateImage; + % ==== NOISECOV ==== + case 'noisecov' + template = templateNoiseCov; + % ==== FILES ==== + case 'datamat' + template = struct('F', [], ... + 'Comment', '', ... + 'ChannelFlag', [], ... + 'Time', [], ... + 'Device', ''); + case 'channelmat' + template = struct(... + 'Comment', 'FIF channels', ... + 'MegRefCoef', [], ... % CTF compensators matrix, [nMeg x nMegRef] + 'Projector', [], ... % SSP matrix, [nChannels x nChannels] + 'TransfMeg', [], ... % MEG sensors: Successive transforms from device coord. system to brainstorm SCS + 'TransfEeg', [], ... % EEG sensors: Successive transforms from device coord. system to brainstorm SCS + 'HeadPoints', struct(...% Digitized head points + 'Loc', [], ... + 'Label', [], ... + 'Type', []), ... + 'Channel', []); % [nChannels] Structure array, one structure per sensor + % ==== SUBJECT ==== + case 'subject' + template = struct(... + 'Name', 'Unnamed', ... + 'Comments', '', ... + 'FileName', '', ... + 'DateOfAcquisition', '', ... + 'Anatomy', repmat(templateAnatomy,0), ... + 'Surface', repmat(templateSurface,0), ... + 'iAnatomy', [], ... + 'iScalp', [], ... + 'iCortex', [], ... + 'iInnerSkull', [], ... + 'iOuterSkull', [], ... + 'iOther', [], ... + 'UseDefaultAnat', 0 , ... + 'UseDefaultChannel', 1); + + % ==== STUDY ==== + case 'study' + template = struct(... + 'Name', 'Unnamed', ... + 'FileName', '', ... + 'DateOfStudy', '', ... + 'BrainStormSubject', '', ... + 'Condition', '', ... + 'Channel', repmat(templateChannel,0), ... + 'iChannel', [], ... + 'Data', repmat(templateData,0), ... + 'HeadModel', repmat(templateHeadModel,0), ... + 'iHeadModel', [], ... + 'Result', repmat(templateResult,0), ... + 'Stat', repmat(templateStat, 0), ... + 'Image', repmat(templateImage, 0), ... + 'NoiseCov', repmat(templateNoiseCov, 0)); + + % ==== PROTOCOLINFO ==== + case 'protocolinfo' + template = struct(... + 'Comment', 'default_protocol', ... + 'STUDIES', '', ... + 'SUBJECTS', '', ... + 'iStudy', [], ... + 'UseDefaultAnat', 0, ... + 'UseDefaultChannel', 1); + + case 'protocolsubjects' + template = struct(... + 'Subject', repmat(db_getDataTemplate('Subject'), 0), ... + 'DefaultSubject', []); + + case 'protocolstudies' + template = struct(... + 'Study', repmat(db_getDataTemplate('Study'), 0), ... + 'DefaultStudy', db_getDataTemplate('Study'), ... + 'AnalysisStudy', db_getDataTemplate('Study')); + + % ==== LAYOUT ==== + case 'layout' + template = struct(... + 'MainWindowPos', [0 0 0 0], ... + 'MainWindowSplitHorizontal', 0, ... + 'MainWindowExplorationMode', 'Subjects', ... + 'ScreensDef', [], ... + 'NoGUI', 0); + + % ==== FILE IMPORT ==== + case 'sfile' + template = struct(... + 'filename', '', ... % Full path to the file + 'format', '', ... % {fif,ctf,egi,neuroscan,brainamp,lena,cartool,...} + 'device', '', ... % {neuromag,ctf,egi,neuroscan,brainamp,...} + 'comment', '', ... + 'fid', [], ... % Matlab file identifier + 'prop', struct(... % Properties of the recordings + 'times', [], ... % [tStart, tStop] + 'samples', [], ... % [iStart, iStop] + 'sfreq', 0, ... % Sampling frequency + 'rejected_segments', [], ... % [nBadSeg,2], list of bad segments, defined in number of samples + 'currCtfComp', [], ... % {0,1,3} Current CTF compensation order already applied to the recordings + 'destCtfComp', [], ... % {0,1,3} Destination CTF compensation order + 'inactiveProjector', []), ... % SSP projectors that need to be applied to the recordings + 'epochs', repmat(struct(... + 'label', '', ... + 'samples', [], ... % [iStart, iStop] + 'times', [], ... % [tStart, tStop] + 'nave', 0, ... % Number of epochs averaged to produce this block + 'select', 1), 0), ... + 'events', repmat(struct(... + 'label', '', ... + 'epochs', [], ... % [list of epochs indices] + 'samples', [], ... % [list of time indices] + 'times', [], ... % [list of time values] + 'reactTimes', [], ... % [list of reaction times, when applicable] + 'select', 1), 0), ... + 'header', [], ... + 'channelmat', [], ... + 'channelflag', []); + + case 'importoptions' + template = struct(... + 'UseEvents', 0, ... % {0,1}: If 1, perform epoching around the selected events + 'EventsTimeRange', [-0.1000 0.3000], ... % Time range for epoching, zero is the event onset (if epoching is enabled) + 'GetAllEpochs', 0, ... % {0,1}: Import all arrays, no matter how many they are + 'iEpochs', 1, ... % Array of indices of epochs to import (if GetAllEpochs is not enabled) + 'SplitRaw', 0, ... % {0,1}: If 1, and if importing continuous recordings (no epoching, no events): split recordings in small time blocks + 'SplitLength', 2, ... % Duration of each split time block, in seconds + 'Resample', 0, ... % Enable resampling (requires Signal Processing Toolbox) + 'ResampleFreq', 0, ... % Resampling frequency (if resampling is enabled) + 'UseCtfComp', 1, ... % Get and apply CTF 3rd gradient correction if available + 'UseSsp', 1, ... % Get and apply SSP (Signal Space Projection) vectors if available + 'RemoveBaseline', 'no', ... % Method used to remove baseline of each channel: {no, all, time, sample} + 'BaselineRange', [], ... % [tStart,tStop] If RemoveBaseline is 'time'; [sampleStart,sampleStop] If RemoveBaseline is 'sample'; Else ignored + 'ImportMode', 'Epoch', ... % Import mode: {Epoch, Time, Event} + 'events', [], ... % Events structure: (label, epochs, samples, times, reactTimes, select) + 'AutoAnswer', 0, ... % Answer automatically to all the questions that can be asked to the user (USE CAREFULLY) + 'CreateConditions', 0); % {0,1} If 1, create new conditions in Brainstorm database if it is more convenient + + % ==== COLORMAPS ==== + case 'colormap' + template = struct(... + 'Name', '', ... + 'CMap', [], ... + 'isAbsoluteValues', 0, ... + 'isNormalized', 0, ... + 'DisplayColorbar', 0, ... + 'MaxValue', [], ... + 'MaxValueType', '', ... + 'Contrast', 0, ... + 'Brightness', 0); + + % ==== GLOBAL DATA ==== + case 'measures' + template = struct(... + 'Time', [], ... + 'NumberOfSamples', [], ... + 'ChannelFlag', [], ... + 'F', [], ... + 'isStatic', 0); + + case 'loadedresults' + template = struct(... + 'FileName', '', ... + 'Comment', '', ... + 'Time', [], ... + 'SamplingRate', [], ... + 'NumberOfSamples', [], ... + 'ChannelFlag', [], ... + 'ImageGridAmp', [], ... + 'ImagingKernel', [], ... + 'nbVertices', [], ... + 'SurfaceFile', '', ... + 'Fsynth', [], ... + 'GoodChannel', [], ... + 'isStatic', 0); %, ... + %'Residuals', []); + + case 'loadedsurface' + template = struct(... + 'FileName', '', ... + 'Name', '', ... + 'Comment', '', ... + 'Vertices', [], ... + 'Faces', [], ... + 'VertConn', [], ... + 'VertNormals', [], ... + 'Curvature', [], ... + 'tess2mri_interp', []); % Interpolation matrix (Surface -> MRI) + + case 'loadedmri' + template = struct(... + 'FileName', '', ... + 'Comment', '', ... + 'Cube', [], ... + 'Voxsize', [], ... + 'SCS', [], ... + 'NCS', [], ... + 'Histogram', [], ... + 'InitTransf', []); + + + case 'figureid' + template = struct(... + 'Type', '', ... + 'SubType', '', ... + 'Modality', ''); + + case 'figure' + template = struct(... + 'Id', db_getDataTemplate('FigureId'), ... + 'hFigure', 0, ... + 'Handles', struct(), ... + 'SelectedChannels', []); + + case 'channeldesc' + template = struct(... + 'Name', '', ... + 'Comment', '', ... + 'Type', '', ... + 'Loc', [], ... + 'Orient', [], ... + 'Weight', []); + + case 'dataset' + template = struct(... + 'DataFile', '', ... + 'StudyFile', '', ... + 'SubjectFile', '', ... + 'ChannelFile', '', ... + 'Surfaces', repmat(db_getDataTemplate('LoadedSurface'), 0), ... + 'Measures', db_getDataTemplate('Measures'), ... + 'Results', repmat(db_getDataTemplate('LoadedResults'), 0), ... + 'Channel', repmat(db_getDataTemplate('ChannelDesc'), 0), ... + 'HeadPoints', [], ... + 'MouseSelectedChannels', [], ... + 'Figure', repmat(db_getDataTemplate('Figure'), 0), ... + 'isStat', 0, ... + 'isZscore', 0); + + case 'displayhandlestimeseries' + template = struct(... + 'hTimeCursor', [], ... + 'hTextCursor', [], ... + 'hTimeZeroLine', [], ... + 'hLines', [], ... + 'LinesColor', [], ... + 'DataMinMax', [], ... + 'DisplayFactor', [], ... + 'DisplayMode', []); + case 'displayhandlestopography' + template = struct(... + 'hSurf', [], ... + 'Wmat', [], ... + 'DisplayMegGrad', '', ... % {'first','second','norm'} + 'DataMinMax', [], ... + 'hSensorMarkers', [], ... + 'hSensorLabels', [], ... + 'hSensorOrient', [], ... + 'hContours', [], ... + 'MarkersLocs', []); + + case 'displayhandles3dviz' + template = struct(... + 'hSensorMarkers', [], ... + 'hSensorLabels', [], ... + 'hSensorOrient', []); + + case 'scout' + template = struct(... + 'SurfaceFile', '', ... % File on which the scout is defined + 'Vertices', [], ... % Index of closest vertices to current scout + 'Seed', [], ... % Original vertex of the scout area + 'Area', [], ... % Scout area, in cm + 'Handles', repmat(struct( ... + 'hFig', [], ... % Figure handle in which the scout is displayed + 'hScout', [], ... % Handles to the graphical scout objects + 'hLabel', [], ... + 'hVertices', [], ... + 'hPatch', []), 0), ... + 'Color', [0 1 0], ... + 'Label', ''); + + case 'cluster' + template = struct(... + 'Sensors', '', ... % File on which the scout is defined + 'Label', ''); + + case 'globaldata' + template = struct(... + 'DataSet', repmat(db_getDataTemplate('DataSet'), 0), ... + 'Mri', repmat(db_getDataTemplate('LoadedMri'), 0), ... + 'Surface', repmat(db_getDataTemplate('LoadedSurface'), 0), ... + 'MaxTimeWindow', struct(... + 'Time', [], ... + 'SamplingRate', [], ... + 'NumberOfSamples', 0), ... + 'UserTimeWindow', struct(... + 'Time', [], ... + 'SamplingRate', [], ... + 'NumberOfSamples', 0, ... + 'CurrentTime', []), ... + 'ChannelEditor', struct(... + 'ChannelFile', '', ... + 'ChannelMat', [], ... + 'DataFile', [], ... + 'isPathAbsolute', 0, ... + 'LocColumns', [], ... + 'OrientColumns', [], ... + 'isModified', 0), ... + 'HeadModeler', struct(... + 'sSubject', [], ... + 'iStudies', [], ... + 'Channel', [], ... + 'isEEG', 0, ... + 'isMEG', 0, ... + 'HeadPoints', [], ... + 'nbSpheres', 0, ... + 'BFS', [], ... + 'GUI', struct(... + 'hFig', [], ... + 'Figure3DButtonDown_Bak', [], ... + 'Figure3DButtonMotion_Bak', [], ... + 'Figure3DButtonUp_Bak', [], ... + 'Figure3DCloseRequest_Bak', [], ... + 'selectedButton', [], ... + 'mouseClicked', [], ... + 'isClosing', [], ... + 'hButtonTransX', [], ... + 'hButtonTransY', [], ... + 'hButtonTransZ', [], ... + 'hButtonResize', [])), ... + 'InverseSolutions', struct(... + 'iStudyList', [], ... + 'iDataList', [], ... + 'isSurfHeadmodel', 1, ... + 'AvailableModalities', [], ... + 'Time', [], ... + 'SamplingRate', [], ... + 'TimeSegment', [], ... + 'BaselineSegment', [], ... + 'Modalities', {}), ... + 'Stat', struct(... + 'TimeWindowAA', [], ... + 'TimeWindowAB', [], ... + 'TimeWindowUnique', [], ... + 'SampleFilesA', [], ... + 'SampleFilesAA', [], ... + 'SampleFilesAB', [], ... + 'FilterFileTag', ''), ... + 'Scouts', repmat(db_getDataTemplate('Scout'), 0), ... + 'Clusters', repmat(db_getDataTemplate('Cluster'), 0), ... + 'CurrentFigure', struct(... + 'Type3D', [], ... + 'Type2D', [], ... + 'Last', []), ... + 'CurrentScoutsSurface', '', ... + 'VisualizationFilters', struct(... + 'LowPassEnabled', 0, ... + 'LowPassValue', 40, ... + 'HighPassEnabled', 0, ... + 'HighPassValue', 1), ... + 'MIP', struct(... + 'isMipAnatomy', 0, ... + 'isMipFunctional', 0), ... + 'Colormaps', [], ... + 'ChannelSelections', struct(... + 'CurrentSelection', [], ... + 'Selections', [], ... + 'DefaultFactor', 1)); + + + case 'landmark' + template = struct(... + 'Name', '', ... + 'mriLoc', ''); + case 'scs' + template = struct(... + 'NAS', [], ... + 'LPA', [], ... + 'RPA', [], ... + 'R', [], ... + 'T', []); + case 'ncs' + template = struct(... + 'AC', [], ... + 'PC', [], ... + 'IH', []); + + % ===== 3DViz appdata structure ===== + case 'tessinfo' + antomyColor = [.35*[1 1 1]; .6*[1 1 1]]; + %antomyColor = [.7*[0.93 0.82 0.80]; [0.93 0.82 0.80]]; + template = struct(... + 'SurfaceFile', '', ... + 'Name', '', ... + 'DataSource', struct(... + 'Type', '', ... % {'data', 'results', 'stats', ...} + 'FileName', '', ... + 'isStat', 0, ... + 'isZscore', 0), ... + 'hPatch', [], ... + 'nVertices', 0, ... + 'nFaces', 0, ... + 'SurfAlpha', 0, ... % Surface transparency + 'SurfShowCurvature', 0, ... % Toggle show/hide surface curvature viewing + 'SurfCurvatureThreshold', 0, ... % Threshold to apply to color coding of curvature values + 'SurfShowEdges', 0, ... % Toggle oon/off surface edges display + 'AnatomyColor', antomyColor, ... % RGB color for curvature encoding + 'SurfSmoothValue', 0, ... + 'Data', [], ... % MEG, EEG or current density for overlay to anatomy (needs to be compatible in size with FaceVertexCdata patch property) + 'DataMinMax', [], ... % Minimum and maximum of the DataSource.FileName file + 'DataWmat', [], ... % Interpolation matrix (transformation to map Data on hPatch surface) + 'OverlayCube', [], ... % Interpolated results in a MRI volume + 'DataAlpha', 0, ... % Alpha for blending of anatomy and surface data + 'DataIntThreshold', 0.5, ... % Threshold to apply to color coding of data values + 'DataExtThreshold', 0, ... % Hide activation cluster smaller than ... + 'DataLimitValue', [], ... % Relative limits for colormapping + 'CutsPosition', [0 0 0], ... % Position of the three orthogonal MRI slices + 'Resect', 'none', ... % Either [x,y,z] resect values, or {'left', 'right', 'none'} + 'MipAnatomy', [], ... % 3 cells: Maximum intensity power in each direction (MRI amplitudes) + 'MipFunctional', [] ... % 3 cellsMaximum intensity power in each direction (sources amplitudes) + ); + template.MipAnatomy = cell(3,1); + template.MipFunctional = cell(3,1); + otherwise + error('Unknown data template : %s', structureName); +end + + + + diff --git a/brainstorm3/toolbox/gui/gui_figure3DViz.m b/brainstorm3/toolbox/gui/gui_figure3DViz.m new file mode 100644 index 0000000..0a72054 --- /dev/null +++ b/brainstorm3/toolbox/gui/gui_figure3DViz.m @@ -0,0 +1,2183 @@ +function varargout = gui_figure3DViz( varargin ) +% GUI_FIGURE3DVIZ: Creation and callbacks for 3D visualization figures. +% +% USAGE: +% [hFig] = gui_figure3DViz() : Create default figure +% [hFig] = gui_figure3DViz(FigureId, figureName) : Create named figure +% [hFig] = gui_figure3DViz(FigureId) : Create unnamed figure +% gui_figure3DViz('CurrentTimeChangedCallback', iDS, iFig) +% gui_figure3DViz('ColormapChangedCallback', iDS, iFig, ColormapType) +% gui_figure3DViz('FigureClickCallback', hFig, event) +% gui_figure3DViz('FigureMouseMoveCallback', hFig, event) +% gui_figure3DViz('FigureMouseUpCallback', hFig, event) +% gui_figure3DViz('FigureMouseWheelCallback', hFig, event) +% gui_figure3DViz('FigureKeyPressedCallback', hFig, keyEvent) +% gui_figure3DViz('ResetView', hFig) +% gui_figure3DViz('SetStandardView', hFig, viewNames) +% gui_figure3DViz('DisplayFigurePopup', hFig) +% [Chan,ChanLoc]= gui_figure3DViz('GetSelectedChannels', iDS, iFig) +% gui_figure3DViz('UpdateSelectedChannels', iDS, iFig) +% gui_figure3DViz('UpdateSurfaceColor', hFig, iTess) +% gui_figure3DViz('ViewSensors', hFig, isMarkers, isLabels) +% gui_figure3DViz('ViewAxis', hFig, isVisible) +% [hFig,hs] = gui_figure3DViz('PlotSurface', hFig, faces, verts, cdata, dataCMap, transparency) + +% @============================================================================= +% This software is part of The Brainstorm Toolbox +% http://neuroimage.usc.edu/brainstorm +% +% Copyright (c)2009 Brainstorm by the University of Southern California +% This software is distributed under the terms of the GNU General Public License +% as published by the Free Software Foundation. Further details on the GPL +% license can be found at http://www.gnu.org/copyleft/gpl.html. +% +% FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE +% UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY +% WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF +% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY +% LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE. +% +% For more information type "brainstorm licence" at command prompt. +% =============================================================================@ +% +% Authors: Francois Tadel, 2008 +% ----------------------------- Script History --------------------------------- +% FT 24-Jun-2008 Creation +% ------------------------------------------------------------------------------ + +%% ===== CALL TO A SUBFUNCTION ===== +if (nargin >= 1) && ischar(varargin{1}) + if (nargout) + [varargout{1:nargout}] = bst_safeCall(str2func(varargin{1}), varargin{2:end}); + else + bst_safeCall(str2func(varargin{1}), varargin{2:end}); + end + return + +%% ===== CREATE FIGURE ===== +% Create default figure +% CALL: [hFig] = gui_figure3DViz() +elseif (nargin == 0) + % Create a new 3DViz figure + FigureId = db_getDataTemplate('FigureId'); + FigureId.Type = '3DViz'; + FigureId.SubType = ''; + FigureId.Modality = ''; + [hFig, hAxes] = gui_figure3DViz(FigureId, '3D'); + % Set figure visible + set(hFig, 'Visible', 'on'); + % Return values + if (nargout >= 1) + varargout{1} = hFig; + end + if (nargout >= 2) + varargout{2} = hAxes; + end + +% CALL: [hFig] = gui_figure3DViz(FigureId, figureName) : Create named figure +% [hFig] = gui_figure3DViz(FigureId) : Create unnamed figure +elseif isstruct(varargin{1}) + % Get FigureId and figure Name + FigureId = varargin{1}; + if (nargin >= 2) + figureName = varargin{2}; + else + figureName = '3D'; + end + % Get renderer name + rendererName = 'opengl'; + + % === CREATE FIGURE === + hFig = figure('Visible', 'off', ... + 'NumberTitle', 'off', ... + 'IntegerHandle', 'off', ... + 'MenuBar', 'none', ... + 'Toolbar', 'none', ... + 'DockControls', 'on', ... + 'Units', 'pixels', ... + 'Color', [0 0 0], ... + 'Tag', FigureId.Type, ... + 'Name', figureName, ... + 'Renderer', rendererName, ... + 'CloseRequestFcn', @(h,ev)gui_figuresManager('DeleteFigure',h,ev), ... + 'KeyPressFcn', @(h,ev)bst_safeCall(@FigureKeyPressedCallback,h,ev), ... + 'WindowButtonDownFcn', @FigureClickCallback, ... + 'WindowButtonMotionFcn', @FigureMouseMoveCallback, ... + 'WindowButtonUpFcn', @FigureMouseUpCallback, ... + 'ResizeFcn', @(h,ev)bst_colormaps('ResizeCallbackForColorbar', h, ev), ... + 'BusyAction', 'queue', ... + 'Interruptible', 'off'); + % Define Mouse wheel callback separately (not supported by old versions of Matlab) + bstVersion = bst_getContext('Version'); + if bstVersion.MatlabVersion >= 7.4 + set(hFig, 'WindowScrollWheelFcn', @FigureMouseWheelCallback); + end + + % === CREATE AXES === + hAxes = axes('Parent', hFig, ... + 'Units', 'normalized', ... + 'Position', [.05 .05 .9 .9], ... + 'Tag', 'Axes3D', ... + 'Visible', 'off', ... + 'BusyAction', 'queue', ... + 'Interruptible', 'off'); + axis vis3d + axis equal + axis off + + % === APPDATA STRUCTURE === + setappdata(hFig, 'Surface', repmat(db_getDataTemplate('TessInfo'), 0)); + setappdata(hFig, 'iSurface', []); + setappdata(hFig, 'StudyFile', []); + setappdata(hFig, 'SubjectFile', []); + setappdata(hFig, 'DataFile', []); + setappdata(hFig, 'ResultsFile', []); + setappdata(hFig, 'isSelectingCorticalSpot', 0); + setappdata(hFig, 'isSelectingCoordinates', 0); + setappdata(hFig, 'hasMoved', 0); + setappdata(hFig, 'isPlotEditToolbar', 0); + setappdata(hFig, 'AllChannelsDisplayed', 0); + setappdata(hFig, 'FigureId', FigureId); + setappdata(hFig, 'isStatic', 0); + + % === LIGHTING === + hl = []; + % Fixed lights + hl(1) = camlight( 0, 40, 'infinite'); + hl(2) = camlight(180, 40, 'infinite'); + hl(3) = camlight( 0, -90, 'infinite'); + hl(4) = camlight( 90, 0, 'infinite'); + hl(5) = camlight(-90, 0, 'infinite'); + % Moving camlight + hl(6) = light('Tag', 'FrontLight', 'Color', [1 1 1], 'Style', 'infinite', 'Parent', hAxes); + camlight(hl(6), 'headlight'); + % Mute the intensity of the lights + for i = 1:length(hl) + set(hl(i), 'color', .4*[1 1 1]); + end + + % Camera basic orientation + SetStandardView(hFig, 'top'); + + % Returned values + if (nargout >= 1) + varargout{1} = hFig; + end + if (nargout >= 2) + varargout{2} = hAxes; + end +end +end + + +%% ========================================================================================= +% ===== FIGURE CALLBACKS ================================================================== +% ========================================================================================= +function Compile() %#ok + % Nothing to do... just to force the compilation of the file +end + +%% ===== CURRENT TIME CHANGED ===== +function CurrentTimeChangedCallback(iDS, iFig) %#ok + global GlobalData; + panel_surface('UpdateSurfaceData', GlobalData.DataSet(iDS).Figure(iFig).hFigure); +end + +%% ===== COLORMAP CHANGED ===== +% Usage: ColormapChangedCallback(iDS, iFig, ColormapType) : Update display only if target colormap is used in figure +% ColormapChangedCallback(iDS, iFig) : Update display anyway +function ColormapChangedCallback(iDS, iFig, ColormapType) %#ok + global GlobalData; + panel_surface('UpdateSurfaceColormap', GlobalData.DataSet(iDS).Figure(iFig).hFigure); +end + + + +%% ========================================================================================= +% ===== KEYBOARD AND MOUSE CALLBACKS ====================================================== +% ========================================================================================= +% Complete mouse and keyboard management over the main axes +% Supports : - Customized 3D-Rotation (LEFT click) +% - Pan (SHIFT+LEFT click, OR MIDDLE click +% - Zoom (CTRL+LEFT click, OR RIGHT click, OR WHEEL) +% - Colorbar contrast/brightness +% - Restore original view configuration (DOUBLE click) + +%% ===== FIGURE CLICK CALLBACK ===== +function FigureClickCallback(hFig, varargin) +%disp('=== MouseDown ==='); + % Find axes + hAxes = findobj(hFig, 'Tag', 'Axes3D'); + if isempty(hAxes) + warning('Brainstorm:NoAxes', 'Axes could not be found'); + return; + end + % Get figure type + FigureId = getappdata(hFig, 'FigureId'); + % Double click: reset view + if strcmpi(get(hFig, 'SelectionType'), 'open') + ResetView(hFig); + end + % Check if MouseUp was executed before MouseDown + if isappdata(hFig, 'clickAction') && strcmpi(getappdata(hFig,'clickAction'), 'MouseDownNotConsumed') + % Should ignore this MouseDown event + setappdata(hFig,'clickAction','MouseDownOk'); + return; + end + + % Start an action (pan, zoom, rotate, contrast, luminosity) + % Action depends on : + % - the mouse button that was pressed (LEFT/RIGHT/MIDDLE), + % - the keys that the user presses simultaneously (SHIFT/CTRL) + clickAction = ''; + switch(get(hFig, 'SelectionType')) + % Left click + case 'normal' + % 2DLayout: pan + if strcmpi(FigureId.SubType, '2DLayout') + clickAction = 'pan'; + % 2D: nothing + elseif ismember(FigureId.SubType, {'2DDisc', '2DSensorCap'}) + % Nothing to do + % Else (3D): rotate + else + clickAction = 'rotate'; + end + % CTRL+Mouse, or Mouse right + case 'alt' + clickAction = 'popup'; + % SHIFT+Mouse, or Mouse middle + case 'extend' + clickAction = 'pan'; + end + + % Record action to perform when the mouse is moved + setappdata(hFig, 'clickAction', clickAction); + setappdata(hFig, 'clickSource', hFig); + % Reset the motion flag + setappdata(hFig, 'hasMoved', 0); + % Record mouse location in the figure coordinates system + setappdata(hFig, 'clickPositionFigure', get(hFig, 'CurrentPoint')); + % Record mouse location in the axes coordinates system + setappdata(hFig, 'clickPositionAxes', get(hAxes, 'CurrentPoint')); +%disp('=== MouseDown: END ==='); +end + + +%% ===== FIGURE MOVE ===== +function FigureMouseMoveCallback(hFig, varargin) + % Get axes handle + hAxes = findobj(hFig, 'tag', 'Axes3D'); + % Get current mouse action + clickAction = getappdata(hFig, 'clickAction'); + clickSource = getappdata(hFig, 'clickSource'); + % If no action is currently performed + if isempty(clickAction) + % Colorbar help message +% ColorbarHelpMessage(hFig); + return + end + % If MouseUp was executed before MouseDown + if strcmpi(clickAction, 'MouseDownNotConsumed') || isempty(getappdata(hFig, 'clickPositionFigure')) + % Ignore Move event + return + end + % If source is not the same as the current figure: fire mouse up event + if (clickSource ~= hFig) + FigureMouseUpCallback(hFig); + FigureMouseUpCallback(clickSource); + return + end + + % Set the motion flag + setappdata(hFig, 'hasMoved', 1); + % Get current mouse location in figure + curptFigure = get(hFig, 'CurrentPoint'); + motionFigure = 0.3 * (curptFigure - getappdata(hFig, 'clickPositionFigure')); + % Get current mouse location in axes + curptAxes = get(hAxes, 'CurrentPoint'); + oldptAxes = getappdata(hFig, 'clickPositionAxes'); + if isempty(oldptAxes) + return + end + motionAxes = curptAxes - oldptAxes; + % Update click point location + setappdata(hFig, 'clickPositionFigure', curptFigure); + setappdata(hFig, 'clickPositionAxes', curptAxes); + % Get figure size + figPos = get(hFig, 'Position'); + + % Switch between different actions (Pan, Rotate, Zoom, Contrast) + switch(clickAction) + case 'rotate' + % Else : ROTATION + % Rotation functions : 5 different areas in the figure window + % ,---------------------------. + % | 2 | + % .75 |---------------------------| + % | 3 | 5 | 4 | + % | | | | + % .25 |---------------------------| + % | 1 | + % '---------------------------' + % .25 .75 + % + % ----- AREA 1 ----- + if (curptFigure(2) < .25 * figPos(4)) + camroll(hAxes, motionFigure(1)); + camorbit(hAxes, 0,-motionFigure(2), 'camera'); + % ----- AREA 2 ----- + elseif (curptFigure(2) > .75 * figPos(4)) + camroll(hAxes, -motionFigure(1)); + camorbit(hAxes, 0,-motionFigure(2), 'camera'); + % ----- AREA 3 ----- + elseif (curptFigure(1) < .25 * figPos(3)) + camroll(hAxes, -motionFigure(2)); + camorbit(hAxes, -motionFigure(1),0, 'camera'); + % ----- AREA 4 ----- + elseif (curptFigure(1) > .75 * figPos(3)) + camroll(hAxes, motionFigure(2)); + camorbit(hAxes, -motionFigure(1),0, 'camera'); + % ----- AREA 5 ----- + else + camorbit(hAxes, -motionFigure(1),-motionFigure(2), 'camera'); + end + camlight(findobj(hAxes, 'Tag', 'FrontLight'), 'headlight'); + + case 'pan' + % Get camera textProperties + pos = get(hAxes, 'CameraPosition'); + up = get(hAxes, 'CameraUpVector'); + target = get(hAxes, 'CameraTarget'); + % Calculate a normalised right vector + right = cross(up, target - pos); + up = up ./ realsqrt(sum(up.^2)); + right = right ./ realsqrt(sum(right.^2)); + % Calculate new camera position and camera target + panFactor = 0.001; + pos = pos + panFactor .* (motionFigure(1).*right - motionFigure(2).*up); + target = target + panFactor .* (motionFigure(1).*right - motionFigure(2).*up); + set(hAxes, 'CameraPosition', pos, 'CameraTarget', target); + + case 'zoom' + if (motionFigure(2) == 0) + return; + elseif (motionFigure(2) < 0) + % ZOOM IN + Factor = 1-motionFigure(2)./100; + elseif (motionFigure(2) > 0) + % ZOOM OUT + Factor = 1./(1+motionFigure(2)./100); + end + zoom(hFig, Factor); + + case {'moveSlices', 'popup'} + % Get MRI + [sMri,TessInfo,iTess] = panel_surface('GetSurfaceMri', hFig); + if isempty(iTess) + return + end + + % === DETECT ACTION === + % Is moving axis and direction are not detected yet : do it + if (~isappdata(hFig, 'moveAxis') || ~isappdata(hFig, 'moveDirection')) + % Guess which cut the user is trying to change + % Sometimes some problem occurs, leading to values > 800 + % for a 1-pixel movement => ignoring + if (max(motionAxes(1,:)) > 20) + return; + end + % Convert MRI-CS -> SCS + motionAxes = motionAxes * sMri.SCS.R; + % Get the maximum deplacement as the direction + [value, moveAxis] = max(abs(motionAxes(1,:))); + moveAxis = moveAxis(1); + % Get the directions of the mouse deplacement that will + % increase or decrease the value of the slice + [value, moveDirection] = max(abs(motionFigure)); + moveDirection = sign(motionFigure(moveDirection(1))) .* ... + sign(motionAxes(1,moveAxis)) .* ... + moveDirection(1); + % Save the detected movement direction and orientation + setappdata(hFig, 'moveAxis', moveAxis); + setappdata(hFig, 'moveDirection', moveDirection); + + % === MOVE SLICE === + else + % Get saved information about current motion + moveAxis = getappdata(hFig, 'moveAxis'); + moveDirection = getappdata(hFig, 'moveDirection'); + % Get the motion value + val = sign(moveDirection) .* motionFigure(abs(moveDirection)); + % Get the new position of the slice + oldPos = TessInfo(iTess).CutsPosition(moveAxis); + newPos = round(saturate(oldPos + val, [1 size(sMri.Cube, moveAxis)])); + + % Plot a patch that indicates the location of the cut + PlotSquareCut(hFig, TessInfo(iTess), moveAxis, newPos); + + % Draw a new X-cut according to the mouse motion + posXYZ = [NaN, NaN, NaN]; + posXYZ(moveAxis) = newPos; + panel_surface('PlotMri', hFig, posXYZ); + end + + case 'colorbar' + % Delete legend + % delete(findobj(hFig, 'Tag', 'ColorbarHelpMsg')); + % Get colormap name + [AllColormapTypes, ColormapType] = gui_figuresManager('GetDisplayedDataTypes', hFig); + % Changes contrast + sColormap = bst_colormaps('ColormapChangeModifiers', ColormapType, [motionFigure(1), motionFigure(2)] ./ 100, 0); + set(hFig, 'Colormap', sColormap.CMap); + end +%disp('=== MouseMove: END ==='); +end + + +%% ===== FIGURE MOUSE UP ===== +function FigureMouseUpCallback(hFig, varargin) +%disp('=== MouseUp ==='); + global GlobalData; + % === 3DViz specific commands === + % Get application data (current user/mouse actions) + clickAction = getappdata(hFig, 'clickAction'); + hasMoved = getappdata(hFig, 'hasMoved'); + hAxes = findobj(hFig, 'tag', 'Axes3D'); + isSelectingCorticalSpot = getappdata(hFig, 'isSelectingCorticalSpot'); + isSelectingCoordinates = getappdata(hFig, 'isSelectingCoordinates'); + + % Remove mouse appdata (to stop movements first) + setappdata(hFig, 'hasMoved', 0); + if isappdata(hFig, 'clickPositionFigure') + rmappdata(hFig, 'clickPositionFigure'); + end + if isappdata(hFig, 'clickPositionAxes') + rmappdata(hFig, 'clickPositionAxes'); + end + if isappdata(hFig, 'clickAction') + rmappdata(hFig, 'clickAction'); + else + setappdata(hFig, 'clickAction', 'MouseDownNotConsumed'); + end + if isappdata(hFig, 'moveAxis') + rmappdata(hFig, 'moveAxis'); + end + if isappdata(hFig, 'moveDirection') + rmappdata(hFig, 'moveDirection'); + end + % Remove SquareCut objects + PlotSquareCut(hFig); + % Get figure description + [hFig, iFig, iDS] = gui_figuresManager('GetFigure', hFig); + if isempty(iDS) + return + end + Figure = GlobalData.DataSet(iDS).Figure(iFig); + % Update figure selection + if strcmpi(Figure.Id.Type, '3DViz') || strcmpi(Figure.Id.SubType, '3DSensorCap') + gui_figuresManager('SetCurrentFigure', hFig, '3D'); + else + gui_figuresManager('SetCurrentFigure', hFig); + end + + % ===== SIMPLE CLICK ===== + % If user did not move the mouse since the click + if ~hasMoved + % === POPUP === + if strcmpi(clickAction, 'popup') + DisplayFigurePopup(hFig); + + % === SELECTING CORTICAL SCOUTS === + elseif isSelectingCorticalSpot + panel_scouts('SelectCorticalSpot', hFig); + + % === SELECTING POINT (COORDINATES PANEL) === + elseif isSelectingCoordinates + panel_coordinates('SelectPoint', hFig); + + % === SELECTING SENSORS === + else + % Check if sensors are displayed in this figure + hSensorsPatch = findobj(hAxes, 'Tag', 'SensorsPatch'); + if (length(hSensorsPatch) == 1) + % Select the nearest sensor from the mouse + [p, v, vi] = select3d(hSensorsPatch); + % If sensor index is not valid + if isempty(vi) || (vi > length(Figure.SelectedChannels)) || (vi <= 0) + return + end + % If clicked point is too far away (5mm) from the closest sensor + % (Do not test Topography figures) + if ~strcmpi(Figure.Id.Type, 'Topography') + if (norm(p - v) > 0.005) + return + end + end + % Is figure used only to display channels + AllChannelsDisplayed = getappdata(hFig, 'AllChannelsDisplayed'); + % If not all the channels are displayed: need to convert the selected sensor indice + if ~AllChannelsDisplayed + % Get channel indice (in Channel array) + iChannel = Figure.SelectedChannels(vi); + else + AllModalityChannels = good_channel(GlobalData.DataSet(iDS).Channel, ... + [], Figure.Id.Modality); + iChannel = AllModalityChannels(vi); + end + % If channel is not selected + if ~ismember(iChannel, GlobalData.DataSet(iDS).MouseSelectedChannels) + % If data is not supposed to have more than one channel selected + if isappdata(hFig, 'UniqueChannelSelection') && (getappdata(hFig, 'UniqueChannelSelection') == 1) + newList = iChannel; + else + newList = [GlobalData.DataSet(iDS).MouseSelectedChannels, iChannel]; + end + % Add it to mouse-selected channels list + bst_dataSetsManager('SetMouseSelectedChannels', GlobalData.DataSet(iDS).ChannelFile, newList); + % Channel is already mouse-selected : unselect it + else + % Remove it from mouse-selected channels list + bst_dataSetsManager('SetMouseSelectedChannels', GlobalData.DataSet(iDS).ChannelFile, ... + setdiff(GlobalData.DataSet(iDS).MouseSelectedChannels, iChannel)); + end + end + end + % ===== MOUSE HAS MOVED ===== + else + % COLORMAP HAS CHANGED + if strcmpi(clickAction, 'colorbar') + % Apply new colormap to all figures + [AllColormapTypes, ColormapType] = gui_figuresManager('GetDisplayedDataTypes', hFig); + bst_colormaps('FireColormapChanged', ColormapType); + % SLICES WERE MOVED + elseif strcmpi(clickAction, 'popup') + % Update "Surfaces" panel + panel_surface('UpdateSurfaceProperties'); + end + end +%disp('=== MouseUp: END ==='); +end + + +%% ===== FIGURE MOUSE WHEEL ===== +function FigureMouseWheelCallback(hFig, event) + % ONLY FOR 3D AND 2DLayout + if isempty(event) + return; + elseif (event.VerticalScrollCount < 0) + % ZOOM IN + Factor = 1 - event.VerticalScrollCount ./ 20; + elseif (event.VerticalScrollCount > 0) + % ZOOM OUT + Factor = 1./(1 + event.VerticalScrollCount ./ 20); + end + zoom(Factor); +end + + +%% ===== KEYBOAD CALLBACK ===== +function FigureKeyPressedCallback(hFig, keyEvent) + global GlobalData TimeSliderMutex; + % Prevent multiple executions + hAxes = findobj(hFig, 'Tag', 'Axes3D'); + set([hFig hAxes], 'BusyAction', 'cancel'); + % Get figure description + [hFig, iFig, iDS] = gui_figuresManager('GetFigure', hFig); + if isempty(hFig) + return + end + FigureId = GlobalData.DataSet(iDS).Figure(iFig).Id; + % ===== GET SELECTED CHANNELS ===== + isMenuSelectedChannels = 0; + if ~isempty(iDS) + % Get channel selection + MouseSelection = GlobalData.DataSet(iDS).MouseSelectedChannels; + if ~isempty(MouseSelection) && ~isempty(FigureId.Modality) && (FigureId.Modality(1) ~= '$') + isMenuSelectedChannels = 1; + end + end + % Get if figure should contain all the modality sensors (display channel net) + AllChannelsDisplayed = getappdata(hFig, 'AllChannelsDisplayed'); + % Check if it is a realignment figure + isAlignFig = ~isempty(findobj(hFig, 'Tag', 'AlignToolbar')); + % If figure is 2D + is2D = ~strcmpi(FigureId.Type, '3DViz') && ~strcmpi(FigureId.SubType, '3DSensorCap'); + + % ===== PROCESS BY CHARACTERS ===== + switch (keyEvent.Character) + % === NUMBERS : VIEW SHORTCUTS === + case '1' + if ~is2D + SetStandardView(hFig, 'left'); + end + case '2' + if ~is2D + SetStandardView(hFig, 'bottom'); + end + case '3' + if ~is2D + SetStandardView(hFig, 'right'); + end + case '4' + if ~is2D + SetStandardView(hFig, 'front'); + end + case '5' + if ~is2D + SetStandardView(hFig, 'top'); + end + case '6' + if ~is2D + SetStandardView(hFig, 'back'); + end + case '7' + if ~isAlignFig && ~is2D + SetStandardView(hFig, {'left', 'right'}); + end + case '8' + if ~isAlignFig && ~is2D + SetStandardView(hFig, {'bottom', 'top'}); + end + case '9' + if ~isAlignFig && ~is2D + SetStandardView(hFig, {'front', 'back'}); + end + case '0' + if ~isAlignFig && ~is2D + SetStandardView(hFig, {'left', 'right', 'top'}); + end + case '=' + if ~isAlignFig && ~is2D + ApplyViewToAllFigures(hFig); + end + % === SCOUTS : GROW/SHRINK === + case '+' + panel_scouts('EditScoutsSize', 'Grow1'); + case '-' + panel_scouts('EditScoutsSize', 'Shrink1'); + + otherwise + % ===== PROCESS BY KEYS ===== + switch (keyEvent.Key) + % === LEFT, RIGHT, PAGEUP, PAGEDOWN : Processed by TimeWindow === + case {'leftarrow', 'rightarrow', 'pageup', 'pagedown'} + if isempty(TimeSliderMutex) || ~TimeSliderMutex + panel_timeWindow('SliderKeyCallback',hFig, keyEvent); + end + % === DATABASE NAVIGATOR === + case {'f1', 'f2', 'f3', 'f4'} + if ~isAlignFig + gui_figuresManager('NavigatorKeyPress', hFig, keyEvent); + end + % === DATA FILES : OTHER VIEWS === + % CTRL+A : View axis + case 'a' + if ismember('control', keyEvent.Modifier) + ViewAxis(hFig); + end + % CTRL+D : Dock figure + case 'd' + if ismember('control', keyEvent.Modifier) + isDocked = strcmpi(get(hFig, 'WindowStyle'), 'docked'); + gui_figuresManager('DockFigure', hFig, ~isDocked); + end + % CTRL+R : Recordings time series + case 'r' + if ismember('control', keyEvent.Modifier) + gui_figuresManager('ViewTimeSeries', hFig); + end + % CTRL+T : Default topography + case 't' + if ismember('control', keyEvent.Modifier) + gui_figuresManager('ViewTopography', hFig); + end + % CTRL+S : Sources (first results file) + case 's' + if ismember('control', keyEvent.Modifier) + gui_figuresManager('ViewResults', hFig); + end + % CTRL+I : Save as image + case 'i' + if ismember('control', keyEvent.Modifier) + out_figure_image(hFig); + end + % CTRL+E : Sensors and labels + case 'e' + if ~isAlignFig && ismember('control', keyEvent.Modifier) && ~strcmpi(FigureId.Modality, 'Fsynth') && ~strcmpi(FigureId.Modality, 'Residuals') + hLabels = findobj(hFig, 'Tag', 'SensorsLabels'); + isMarkers = ~isempty(findobj(hFig, 'Tag', 'SensorsPatch')) || ~isempty(findobj(hFig, 'Tag', 'SensorsMarkers')); + isLabels = ~isempty(hLabels); + % All figures, except "2DLayout" + if ~strcmpi(FigureId.SubType, '2DLayout') + % Cycle between three modes : Nothing, Sensors, Sensors+labels + if isMarkers && isLabels + ViewSensors(hFig, 0, 0); + elseif isMarkers + ViewSensors(hFig, 1, 1); + else + ViewSensors(hFig, 1, 0); + end + % "2DLayout" + elseif isLabels + isLabelsVisible = strcmpi(get(hLabels(1), 'Visible'), 'on'); + if isLabelsVisible + set(hLabels, 'Visible', 'off'); + else + set(hLabels, 'Visible', 'on'); + end + end + end + + % === CHANNELS === + % RETURN: VIEW SELECTED CHANNELS + case 'return' + if ~isAlignFig && isMenuSelectedChannels && ~AllChannelsDisplayed + gui_figureTimeSeries('DisplayDataSelectedChannels', iDS, MouseSelection, ... + FigureId.Modality, hFig); + end + % DELETE: SET SELECTED CHANNELS AS BAD + case 'delete' + if ~isAlignFig && isMenuSelectedChannels && ~AllChannelsDisplayed + ChannelFlagSelectionBad = GlobalData.DataSet(iDS).Measures.ChannelFlag; + ChannelFlagSelectionBad(MouseSelection) = -1; + panel_channelEditor('UpdateChannelFlag', GlobalData.DataSet(iDS).DataFile, ... + 0, ChannelFlagSelectionBad); + % Reset MouseSelectedChannels + bst_dataSetsManager('SetMouseSelectedChannels', GlobalData.DataSet(iDS).ChannelFile, []); + end + % ESCAPE: RESET CHANNELS SELECTION + case 'escape' + if ~isAlignFig && isMenuSelectedChannels && ~AllChannelsDisplayed + bst_dataSetsManager('SetMouseSelectedChannels', GlobalData.DataSet(iDS).ChannelFile, []); + end + end + end + % Restore events + if ~isempty(hFig) && ishandle(hFig) && ~isempty(hAxes) && ishandle(hAxes) + set([hFig hAxes], 'BusyAction', 'queue'); + end +end + + +%% ===== RESET VIEW ===== +% Restore initial camera position and orientation +function ResetView(hFig) + zoom out + % Get Axes handle + hAxes = findobj(hFig, 'Tag', 'Axes3D'); + set(hFig, 'CurrentAxes', hAxes); + % Camera basic orientation + SetStandardView(hFig, 'top'); + % Try to find a light source. If found, align it with the camera + camlight(findobj(hAxes, 'Tag', 'FrontLight'), 'headlight'); +end + + +%% ===== SET STANDARD VIEW ===== +function SetStandardView(hFig, viewNames) + % Make sure that viewNames is a cell array + if ischar(viewNames) + viewNames = {viewNames}; + end + % Get Axes handle + hAxes = findobj(hFig, 'Tag', 'Axes3D'); + % Get the data types displayed in this figure + listTypes = gui_figuresManager('GetDisplayedDataTypes', hFig); + R = eye(3); + % If MRI displayed in the figure, use the orientation of the slices, instead of the orientation of the axes + if ismember('Anatomy', listTypes) + % Get the mri surface + TessInfo = getappdata(hFig, 'Surface'); + iTess = find(strcmpi({TessInfo.Name}, 'Anatomy')); + if ~isempty(iTess) + sMri = bst_dataSetsManager('GetMri', TessInfo(iTess).SurfaceFile); + % Get the rotation to change orientation + R = [0 1 0;-1 0 0; 0 0 1] * pinv(sMri.SCS.R); + end + end + % Apply the first orientation to the target figure + switch lower(viewNames{1}) + case 'left' + newView = [0,1,0]; + newCamup = [0 0 1]; + case 'right' + newView = [0,-1,0]; + newCamup = [0 0 1]; + case 'back' + newView = [-1,0,0]; + newCamup = [0 0 1]; + case 'front' + newView = [1,0,0]; + newCamup = [0 0 1]; + case 'bottom' + newView = [0,0,-1]; + newCamup = [1 0 0]; + case 'top' + newView = [0,0,1]; + newCamup = [1 0 0]; + end + % Update camera position + view(hAxes, newView * R); + camup(hAxes, newCamup * R); + + % Update head light position + camlight(findobj(hAxes, 'Tag', 'FrontLight'), 'headlight'); + + % If there are other view to represent + if (length(viewNames) > 1) + hClones = gui_figuresManager('GetClones', hFig); + % Process the other required views + for i = 2:length(viewNames) + if ~isempty(hClones) + % Use an already cloned figure + hNewFig = hClones(1); + hClones(1) = []; + else + % Clone figure + hNewFig = gui_figuresManager('CloneFigure', hFig); + end + % Set orientation + SetStandardView(hNewFig, viewNames(i)); + end + % If there are some cloned figures left : close them + if ~isempty(hClones) + close(hClones); + % Update figures layout + gui_layout(); + end + end +end + + +%% ===== APPLY VIEW TO ALL FIGURES ===== +function ApplyViewToAllFigures(hSrcFig) + % Get Axes handle + hSrcAxes = findobj(hSrcFig, 'Tag', 'Axes3D'); + % Get surface descriptions + SrcTessInfo = getappdata(hSrcFig, 'Surface'); + % Get all figures + hAllFig = gui_figuresManager('GetFiguresByType', '3DViz'); + hAllFig = setdiff(hAllFig, hSrcFig); + % Process all figures + for i = 1:length(hAllFig) + % Get Axes handle + hDestFig = hAllFig(i); + hDestAxes = findobj(hDestFig, 'Tag', 'Axes3D'); + % === COPY CAMERA === + % Copy view angle + [az,el] = view(hSrcAxes); + view(hDestAxes, az, el); + % Copy camup + up = camup(hSrcAxes); + camup(hDestAxes, up); + % Update head light position + camlight(findobj(hDestAxes, 'Tag', 'FrontLight'), 'headlight'); + + % === COPY SURFACES PROPERTIES === + DestTessInfo = getappdata(hDestFig, 'Surface'); + % Process each surface of the figure + for iTess = 1:length(DestTessInfo) + % Find surface name in source figure + iTessInSrc = find(strcmpi(DestTessInfo(iTess).Name, {SrcTessInfo.Name})); + % If surface is also available in source figure + if ~isempty(iTessInSrc) + % Copy surf properties + iTessInSrc = iTessInSrc(1); + DestTessInfo(iTess).SurfAlpha = SrcTessInfo(iTessInSrc).SurfAlpha; + DestTessInfo(iTess).SurfShowCurvature = SrcTessInfo(iTessInSrc).SurfShowCurvature; + DestTessInfo(iTess).SurfCurvatureThreshold = SrcTessInfo(iTessInSrc).SurfCurvatureThreshold; + DestTessInfo(iTess).SurfShowEdges = SrcTessInfo(iTessInSrc).SurfShowEdges; + DestTessInfo(iTess).AnatomyColor = SrcTessInfo(iTessInSrc).AnatomyColor; + DestTessInfo(iTess).SurfSmoothValue = SrcTessInfo(iTessInSrc).SurfSmoothValue; + DestTessInfo(iTess).DataAlpha = SrcTessInfo(iTessInSrc).DataAlpha; + DestTessInfo(iTess).DataIntThreshold = SrcTessInfo(iTessInSrc).DataIntThreshold; + DestTessInfo(iTess).DataExtThreshold = SrcTessInfo(iTessInSrc).DataExtThreshold; + DestTessInfo(iTess).CutsPosition = SrcTessInfo(iTessInSrc).CutsPosition; + DestTessInfo(iTess).Resect = SrcTessInfo(iTessInSrc).Resect; + % Update surfaces structure + setappdata(hDestFig, 'Surface', DestTessInfo); + % Update display + if strcmpi(DestTessInfo(iTess).Name, 'Anatomy') + UpdateMriDisplay(hDestFig, [], DestTessInfo, iTess); + else + UpdateSurfaceAlpha(hDestFig, iTess); + UpdateSurfaceColor(hDestFig, iTess); + end + % Update scouts displayed on this surfce + panel_scouts('UpdateScoutsVertices', DestTessInfo(iTess).SurfaceFile); + end + end + end +end + + +%% ===== POPUP MENU ===== +% Show a popup dialog about the target 3DViz figure +function DisplayFigurePopup(hFig) + import javax.swing.*; + import java.awt.*; + import java.awt.event.KeyEvent; + import org.brainstorm.icon.IconLoader; + + global GlobalData; + % Get figure description + [hFig, iFig, iDS] = gui_figuresManager('GetFigure', hFig); + if isempty(iDS) + return + end + % Get DataFile associated with this figure + ProtocolInfo = bst_getContext('ProtocolInfo'); + DataFile = GlobalData.DataSet(iDS).DataFile; + if ~isempty(ProtocolInfo) + DataFileFull = fullfile(ProtocolInfo.STUDIES, DataFile); + else + % Protocol not defined => running without without full brainstorm GUI + DataFileFull = []; + end + % Create popup menu + jPopup = JPopupMenu(); + + % ==== DISPLAY OTHER FIGURES ==== + % Only for MEG and EEG time series + Modality = GlobalData.DataSet(iDS).Figure(iFig).Id.Modality; + FigureType = GlobalData.DataSet(iDS).Figure(iFig).Id.Type; + if ~isempty(DataFile) && ~isempty(DataFileFull) + % === View RECORDINGS === + jItem = JMenuItem([Modality ' Recordings'], IconLoader.ICON_TS_DISPLAY); + jItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, KeyEvent.CTRL_MASK)); + set(jItem, 'ActionPerformedCallback', @(h,ev)gui_figuresManager('ViewTimeSeries',hFig)); + jPopup.add(jItem); + % === View TOPOGRAPHY === + if ~strcmpi(FigureType, 'Topography') + jItem = JMenuItem([Modality ' Topography'], IconLoader.ICON_TOPOGRAPHY); + jItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_T, KeyEvent.CTRL_MASK)); + set(jItem, 'ActionPerformedCallback', @(h,ev)gui_figuresManager('ViewTopography',hFig)); + jPopup.add(jItem); + end + % === View SOURCES === + if isempty(getappdata(hFig, 'ResultsFile')) + jItem = JMenuItem('View sources', IconLoader.ICON_RESULTS); + jItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, KeyEvent.CTRL_MASK)); + set(jItem, 'ActionPerformedCallback', @(h,ev)gui_figuresManager('ViewResults',hFig)); + jPopup.add(jItem); + end + jPopup.addSeparator(); + end + + % ==== CHANNELS MENU ===== + if ~isempty(DataFile) && ~isempty(DataFileFull) && ~strcmpi(Modality, 'Fsynth') && ~strcmpi(Modality, 'Residuals') + jMenuChannels = JMenu('Channels'); + jMenuChannels.setIcon(IconLoader.ICON_CHANNEL); + % ==== Selected channels submenu ==== + isMarkers = ~isempty(GlobalData.DataSet(iDS).Figure(iFig).Handles.hSensorMarkers); + MouseSelection = GlobalData.DataSet(iDS).MouseSelectedChannels; + % Excludes figures without selection and display-only figures (modality name starts with '$') + if ~isempty(DataFile) && ... + isMarkers && ... + ~isempty(MouseSelection) && ... + ~isempty(Modality) && ... + (Modality(1) ~= '$') + % === VIEW TIME SERIES === + jItem = JMenuItem('View selected', IconLoader.ICON_TS_DISPLAY); + jItem.setAccelerator(KeyStroke.getKeyStroke(int32(KeyEvent.VK_ENTER), 0)); % ENTER + set(jItem, 'ActionPerformedCallback', @(h, ev)gui_figuresManager('ViewTimeSeries', hFig, MouseSelection)); + jMenuChannels.add(jItem); + % === SET AS BAD CHANNELS === + ChannelFlagSelectionBad = GlobalData.DataSet(iDS).Measures.ChannelFlag; + ChannelFlagSelectionBad(MouseSelection) = -1; + jItem = JMenuItem('Mark selected as bad', IconLoader.ICON_BAD); + jItem.setAccelerator(KeyStroke.getKeyStroke(int32(KeyEvent.VK_DELETE), 0)); % DEL + set(jItem, 'ActionPerformedCallback', @(h, ev)panel_channelEditor('UpdateChannelFlag', ... + DataFile, 0, ChannelFlagSelectionBad)); + jMenuChannels.add(jItem); + % === RESET SELECTION === + jItem = JMenuItem('Reset selection', IconLoader.ICON_SURFACE); + jItem.setAccelerator(KeyStroke.getKeyStroke(int32(KeyEvent.VK_ESCAPE), 0)); % ESCAPE + set(jItem, 'ActionPerformedCallback', @(h, ev)bst_dataSetsManager('SetMouseSelectedChannels', ... + GlobalData.DataSet(iDS).ChannelFile, [])); + jMenuChannels.add(jItem); + end + % Separator if previous items + if (jMenuChannels.getItemCount() > 0) + jMenuChannels.addSeparator(); + end + + % ==== CHANNEL FLAG ===== + if ~isempty(DataFile) && isMarkers + % ==== MARK ALL CHANNELS AS GOOD ==== + ChannelFlagGood = ones(size(GlobalData.DataSet(iDS).Measures.ChannelFlag)); + jItem = JMenuItem('Mark all channels as good', IconLoader.ICON_GOOD); + set(jItem, 'ActionPerformedCallback', ... + @(h, ev)panel_channelEditor('UpdateChannelFlag', GlobalData.DataSet(iDS).DataFile, ... + 0, ChannelFlagGood)); + jMenuChannels.add(jItem); + % ==== EDIT CHANNEL FLAG ==== + jItem = JMenuItem('Edit good/bad channels...', IconLoader.ICON_GOODBAD); + set(jItem, 'ActionPerformedCallback', @(h,ev)gui_editChannelFlag(DataFile)); + jMenuChannels.add(jItem); + end + % Separator if previous items + if (jMenuChannels.getItemCount() > 0) + jMenuChannels.addSeparator(); + end + + % ==== View Sensors ==== + % Not for 2DLayout + if ~strcmpi(GlobalData.DataSet(iDS).Figure(iFig).Id.SubType, '2DLayout') + % Menu "View sensors" + jItem = JCheckBoxMenuItem('Display sensors', IconLoader.ICON_CHANNEL, isMarkers); + jItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_E, KeyEvent.CTRL_MASK)); + set(jItem, 'ActionPerformedCallback', @(h,ev)ViewSensors(hFig, ~isMarkers, [])); + jMenuChannels.add(jItem); + % Menu "View sensor labels" + isLabels = ~isempty(GlobalData.DataSet(iDS).Figure(iFig).Handles.hSensorLabels); + jItem = JCheckBoxMenuItem('Display labels', IconLoader.ICON_CHANNEL_LABEL, isLabels); + set(jItem, 'ActionPerformedCallback', @(h,ev)ViewSensors(hFig, [], ~isLabels)); + jMenuChannels.add(jItem); + else + % Menu "View sensor labels" + isLabels = ~isempty(GlobalData.DataSet(iDS).Figure(iFig).Handles.hSensorLabels); + if isLabels + isLabelsVisible = strcmpi(get(GlobalData.DataSet(iDS).Figure(iFig).Handles.hSensorLabels(1), 'Visible'), 'on'); + jItem = JCheckBoxMenuItem('Display labels', IconLoader.ICON_CHANNEL_LABEL, isLabelsVisible); + jItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_E, KeyEvent.CTRL_MASK)); + if isLabelsVisible + targetVisible = 'off'; + else + targetVisible = 'on'; + end + set(jItem, 'ActionPerformedCallback', @(h,ev)set(GlobalData.DataSet(iDS).Figure(iFig).Handles.hSensorLabels, 'Visible', targetVisible)); + jMenuChannels.add(jItem); + end + end + jPopup.add(jMenuChannels); + end + + + % ==== Menu colormaps ==== + % Get list of displayed data types + listTypes = gui_figuresManager('GetDisplayedDataTypes', hFig); + % Menu "Recordings (EEG/MEG) colormap" + if any(ismember({'data','eeg','meg'}, lower(listTypes))) && ~strcmpi(Modality, 'Residuals') + ColormapType = bst_colormaps('GetColormapType', Modality); + jMenuColormapsData = JMenu(['Colormap (' lower(ColormapType) ')']); + jMenuColormapsData.setIcon(IconLoader.ICON_COLORMAP_RECORDINGS); + set(jMenuColormapsData, 'MenuSelectedCallback', @(h,ev)bst_colormaps('CreateColormapMenu', ev.getSource(), ColormapType)); + jPopup.add(jMenuColormapsData); + end + % Menu "Sources colormap" + if ismember('Source', listTypes) || strcmpi(Modality, 'Residuals') + jMenuColormapsSource = JMenu('Colormap (sources)'); + jMenuColormapsSource.setIcon(IconLoader.ICON_COLORMAP_SOURCES); + set(jMenuColormapsSource, 'MenuSelectedCallback', @(h,ev)bst_colormaps('CreateColormapMenu', ev.getSource(), 'Source')); + jPopup.add(jMenuColormapsSource); + end + % Menu "Stat colormap" + if ismember('Stat', listTypes) + jMenuColormapsStat = JMenu('Colormap (stat)'); + jMenuColormapsStat.setIcon(IconLoader.ICON_COLORMAP_RECORDINGS); + set(jMenuColormapsStat, 'MenuSelectedCallback', @(h,ev)bst_colormaps('CreateColormapMenu', ev.getSource(), 'Stat')); + jPopup.add(jMenuColormapsStat); + end + % Menu "Anatomy colormap" + if ismember('Anatomy', listTypes) + jMenuColormapsSource = JMenu('Colormap (anatomy)'); + jMenuColormapsSource.setIcon(IconLoader.ICON_COLORMAP_ANATOMY); + set(jMenuColormapsSource, 'MenuSelectedCallback', @(h,ev)bst_colormaps('CreateColormapMenu', ev.getSource(), 'Anatomy')); + jPopup.add(jMenuColormapsSource); + end + + % ==== Maximum Intensity Projection ==== + if ismember('Anatomy', listTypes) + jMenuMIP = JMenu('Maximum Intensity Power'); + jMenuMIP.setIcon(IconLoader.ICON_RESULTS); + % MIP: Anatomy + jItem = JCheckBoxMenuItem('MIP: Anatomy', GlobalData.MIP.isMipAnatomy); + set(jItem, 'ActionPerformedCallback', @(h,ev)MipAnatomy_Callback(hFig,ev)); + jMenuMIP.add(jItem); + jPopup.add(jMenuMIP); + % MIP: Functional + if ismember('Source', listTypes) + jItem = JCheckBoxMenuItem('MIP: Functional', GlobalData.MIP.isMipFunctional); + set(jItem, 'ActionPerformedCallback', @(h,ev)MipFunctional_Callback(hFig,ev)); + jMenuMIP.add(jItem); + end + jPopup.add(jMenuMIP); + end + + % ==== Navigation submenu ==== + if ~isempty(DataFile) && ~isempty(DataFileFull) + jMenuNavigator = JMenu('Navigator'); + jMenuNavigator.setIcon(IconLoader.ICON_NEXT_SUBJECT); + bst_navigator('CreateNavigatorMenu', jMenuNavigator); + jPopup.add(jMenuNavigator); + jPopup.addSeparator(); + end + + % ==== Menu SNAPSHOT ==== + jMenuSave = JMenu('Snapshot'); + jMenuSave.setIcon(IconLoader.ICON_SNAPSHOT); + % Default output dir + LastUsedDirs = bst_getContext('LastUsedDirs'); + DefaultOutputDir = LastUsedDirs.Export; + % Is there a time window defined + isTime = ~isempty(GlobalData) && ~isempty(GlobalData.MaxTimeWindow.Time) ... + && ~isempty(GlobalData.UserTimeWindow.CurrentTime) && ~isempty(GlobalData.UserTimeWindow.Time) ... + && ~isempty(DataFileFull); + % === SAVE AS IMAGE === + jItem = JMenuItem('Save as image', IconLoader.ICON_SAVE); + jItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_I, KeyEvent.CTRL_MASK)); + set(jItem, 'ActionPerformedCallback', @(h,ev)out_figure_image(hFig)); + jMenuSave.add(jItem); + % === SAVE AS FIGURE === + jItem = JMenuItem('Save as Matlab figure', IconLoader.ICON_SAVE); + set(jItem, 'ActionPerformedCallback', @(h,ev)out_figure_matlab(hFig)); + jMenuSave.add(jItem); + % === MOVIES === + % WARNING: Windows ONLY (for the moment) + % And NOT for 2DLayout figures + if ispc && ~strcmpi(GlobalData.DataSet(iDS).Figure(iFig).Id.SubType, '2DLayout') + % Separator + jMenuSave.addSeparator(); + % === MOVIE (TIME) === + if isTime + jItem = JMenuItem('Movie (time)', IconLoader.ICON_MOVIE); + set(jItem, 'ActionPerformedCallback', @(h,ev)out_figure_movie(hFig, DefaultOutputDir, 'time')); + jMenuSave.add(jItem); + end + % If not topography + if ~strcmpi(FigureType, 'Topography') + % === MOVIE (HORIZONTAL) === + jItem = JMenuItem('Movie (horizontal)', IconLoader.ICON_MOVIE); + set(jItem, 'ActionPerformedCallback', @(h,ev)out_figure_movie(hFig, DefaultOutputDir, 'horizontal')); + jMenuSave.add(jItem); + % === MOVIE (VERTICAL) === + jItem = JMenuItem('Movie (vertical)', IconLoader.ICON_MOVIE); + set(jItem, 'ActionPerformedCallback', @(h,ev)out_figure_movie(hFig, DefaultOutputDir, 'vertical')); + jMenuSave.add(jItem); + end + end + % === CONTACT SHEETS === + % If time, and if not 2DLayout + if isTime && ~strcmpi(GlobalData.DataSet(iDS).Figure(iFig).Id.SubType, '2DLayout') + % Separator + jMenuSave.addSeparator(); + % === CONTACT SHEET (TIME) === + jItem = JMenuItem('Contact sheet (time)', IconLoader.ICON_CONTACTSHEET); + set(jItem, 'ActionPerformedCallback', @(h,ev)out_figure_contactSheet(hFig, DefaultOutputDir)); + jMenuSave.add(jItem); + end + % === CONTACT SHEET / SLICES === + if ismember('Anatomy', listTypes) + % === CONTACT SHEET (AXIAL) === + jItem = JMenuItem('Contact sheet (axial)', IconLoader.ICON_CONTACTSHEET); + set(jItem, 'ActionPerformedCallback', @(h,ev)out_figure_mriSlices(hFig, 'z', DefaultOutputDir)); + jMenuSave.add(jItem); + % === CONTACT SHEET (CORONAL) === + jItem = JMenuItem('Contact sheet (coronal)', IconLoader.ICON_CONTACTSHEET); + set(jItem, 'ActionPerformedCallback', @(h,ev)out_figure_mriSlices(hFig, 'y', DefaultOutputDir)); + jMenuSave.add(jItem); + % === CONTACT SHEET (SAGITTAL) === + jItem = JMenuItem('Contact sheet (sagittal)', IconLoader.ICON_CONTACTSHEET); + set(jItem, 'ActionPerformedCallback', @(h,ev)out_figure_mriSlices(hFig, 'x', DefaultOutputDir)); + jMenuSave.add(jItem); + end + jPopup.add(jMenuSave); + + % ==== Menu "Figure" ==== + jMenuFigure = JMenu('Figure'); + jMenuFigure.setIcon(IconLoader.ICON_LAYOUT_SHOWALL); + % Show axes + isAxis = ~isempty(findobj(hFig, 'Tag', 'AxisXYZ')); + jItem = JCheckBoxMenuItem('View axis', IconLoader.ICON_AXES, isAxis); + jItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_A, KeyEvent.CTRL_MASK)); + set(jItem, 'ActionPerformedCallback', @(h,ev)ViewAxis(hFig, ~isAxis)); + jMenuFigure.add(jItem); + % Show Head points + isHeadPoints = ~isempty(GlobalData.DataSet(iDS).HeadPoints) && ~isempty(GlobalData.DataSet(iDS).HeadPoints.Loc); + if isHeadPoints + % Are head points visible + hHeadPointsMarkers = findobj(GlobalData.DataSet(iDS).Figure(iFig).hFigure, 'Tag', 'HeadPointsMarkers'); + isVisible = ~isempty(hHeadPointsMarkers) && strcmpi(get(hHeadPointsMarkers, 'Visible'), 'on'); + jItem = JCheckBoxMenuItem('View head points', IconLoader.ICON_CHANNEL, isVisible); + set(jItem, 'ActionPerformedCallback', @(h,ev)ViewHeadPoints(hFig, ~isVisible)); + jMenuFigure.add(jItem); + end + jMenuFigure.addSeparator(); + % Change background color + jItem = JMenuItem('Change background color', IconLoader.ICON_COLOR_SELECTION); + set(jItem, 'ActionPerformedCallback', @(h,ev)ChangeBackgroundColor(hFig)); + jMenuFigure.add(jItem); + jMenuFigure.addSeparator(); + % Show Matlab controls + isMatlabCtrl = ~strcmpi(get(hFig, 'MenuBar'), 'none') && ~strcmpi(get(hFig, 'ToolBar'), 'none'); + jItem = JCheckBoxMenuItem('Matlab controls', IconLoader.ICON_MATLAB_CONTROLS, isMatlabCtrl); + set(jItem, 'ActionPerformedCallback', @(h,ev)gui_figuresManager('ShowMatlabControls', hFig, ~isMatlabCtrl)); + jMenuFigure.add(jItem); + % Show plot edit toolbar + isPlotEditToolbar = getappdata(hFig, 'isPlotEditToolbar'); + jItem = JCheckBoxMenuItem('Plot edit toolbar', IconLoader.ICON_PLOTEDIT, isPlotEditToolbar); + set(jItem, 'ActionPerformedCallback', @(h,ev)gui_figuresManager('TogglePlotEditToolbar', hFig)); + jMenuFigure.add(jItem); + % Dock figure + isDocked = strcmpi(get(hFig, 'WindowStyle'), 'docked'); + jItem = JCheckBoxMenuItem('Dock figure', IconLoader.ICON_DOCK, isDocked); + jItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_D, KeyEvent.CTRL_MASK)); + set(jItem, 'ActionPerformedCallback', @(h,ev)gui_figuresManager('DockFigure', hFig, ~isDocked)); + jMenuFigure.add(jItem); + jPopup.add(jMenuFigure); + + % ==== Menu "Views" ==== + % Not for Topography + if ~strcmpi(FigureType, 'Topography') + jMenuView = JMenu('Views'); + jMenuView.setIcon(IconLoader.ICON_AXES); + % Check if it is a realignment figure + isAlignFigure = ~isempty(findobj(hFig, 'Tag', 'AlignToolbar')); + % STANDARD VIEWS + % Create items + jItemViewLeft = JMenuItem('Left'); + jItemViewBottom = JMenuItem('Bottom'); + jItemViewRight = JMenuItem('Right'); + jItemViewFront = JMenuItem('Front'); + jItemViewTop = JMenuItem('Top'); + jItemViewBack = JMenuItem('Back'); + % Callbacks + set(jItemViewLeft, 'ActionPerformedCallback', @(h,ev)SetStandardView(hFig, {'left'})); + set(jItemViewBottom, 'ActionPerformedCallback', @(h,ev)SetStandardView(hFig, {'bottom'})); + set(jItemViewRight, 'ActionPerformedCallback', @(h,ev)SetStandardView(hFig, {'right'})); + set(jItemViewFront, 'ActionPerformedCallback', @(h,ev)SetStandardView(hFig, {'front'})); + set(jItemViewTop, 'ActionPerformedCallback', @(h,ev)SetStandardView(hFig, {'top'})); + set(jItemViewBack, 'ActionPerformedCallback', @(h,ev)SetStandardView(hFig, {'back'})); + % Keyboard shortcuts + jItemViewLeft.setAccelerator( KeyStroke.getKeyStroke('1')); + jItemViewBottom.setAccelerator(KeyStroke.getKeyStroke('2')); + jItemViewRight.setAccelerator( KeyStroke.getKeyStroke('3')); + jItemViewFront.setAccelerator( KeyStroke.getKeyStroke('4')); + jItemViewTop.setAccelerator( KeyStroke.getKeyStroke('5')); + jItemViewBack.setAccelerator( KeyStroke.getKeyStroke('6')); + % Add items to menu + jMenuView.add(jItemViewLeft); + jMenuView.add(jItemViewBottom); + jMenuView.add(jItemViewRight); + jMenuView.add(jItemViewFront); + jMenuView.add(jItemViewTop); + jMenuView.add(jItemViewBack); + + % MULTIPLE VIEWS + if ~isAlignFigure + % Create items + jItemViewLR = JMenuItem('[Left, Right]'); + jItemViewTB = JMenuItem('[Top, Bottom]'); + jItemViewFB = JMenuItem('[Front, Back]'); + jItemViewLTR = JMenuItem('[Left, Top, Right]'); + % Callbacks + set(jItemViewLR, 'ActionPerformedCallback', @(h,ev)SetStandardView(hFig, {'left', 'right'})); + set(jItemViewTB, 'ActionPerformedCallback', @(h,ev)SetStandardView(hFig, {'top', 'bottom'})); + set(jItemViewFB, 'ActionPerformedCallback', @(h,ev)SetStandardView(hFig, {'front','back'})); + set(jItemViewLTR, 'ActionPerformedCallback', @(h,ev)SetStandardView(hFig, {'left', 'top', 'right'})); + % Keyboard shortcuts + jItemViewLR.setAccelerator( KeyStroke.getKeyStroke('7')); + jItemViewTB.setAccelerator( KeyStroke.getKeyStroke('8')); + jItemViewFB.setAccelerator( KeyStroke.getKeyStroke('9')); + jItemViewLTR.setAccelerator( KeyStroke.getKeyStroke('0')); + % Add items to menu + jMenuView.add(jItemViewLR); + jMenuView.add(jItemViewTB); + jMenuView.add(jItemViewFB); + jMenuView.add(jItemViewLTR); + + % SET SAME VIEW FOR ALL FIGURES + jMenuView.addSeparator(); + jItem = JMenuItem('Apply this view to all figures'); + jItem.setAccelerator(KeyStroke.getKeyStroke('=')); + set(jItem, 'ActionPerformedCallback', @(h,ev)ApplyViewToAllFigures(hFig)); + jMenuView.add(jItem); + + % CLONE FIGURE + jMenuView.addSeparator(); + jItem = JMenuItem('Clone figure'); + set(jItem, 'ActionPerformedCallback', @(h,ev)gui_figuresManager('CloneFigure', hFig)); + jMenuView.add(jItem); + end + jPopup.add(jMenuView); + end + + % ==== Display menu ==== + gui_showPopup(jPopup); + +end + + +%% ===== FIGURE CONFIGURATION FUNCTIONS ===== +% CHECKBOX MIP ANATOMY +function MipAnatomy_Callback(hFig, ev) + global GlobalData; + GlobalData.MIP.isMipAnatomy = ev.getSource().isSelected(); + UpdateMriDisplay(hFig); +end +% CHECKBOX MIP FUNCTIONAL +function MipFunctional_Callback(hFig, ev) + global GlobalData; + GlobalData.MIP.isMipFunctional = ev.getSource().isSelected(); + UpdateMriDisplay(hFig); +end + +% %% ===== COLORBAR HELP MESSAGE ===== +% function ColorbarHelpMessage(hFig) +% % Get colorbar +% hColorbar = findobj(hFig, 'Tag', 'Colorbar'); +% % If a colorbar is found +% if ~isempty(hColorbar) && strcmpi(get(hColorbar,'Visible'), 'on') +% % Get mouse position and colorbar position +% barPos = get(hColorbar, 'Position'); +% curptFigure = get(hFig, 'CurrentPoint'); +% % Get previous legend +% hLabel = findobj(hFig, 'Tag', 'ColorbarHelpMsg'); +% % Check if mouse over the colorbar +% if (curptFigure(1) >= barPos(1)) +% % Display the help text for colorbar (only if not displayed yet) +% if isempty(hLabel) +% hLabel = uicontrol('Style', 'text', ... +% 'String', ['Click and move mouse to adjust colormap:' 10 'Horizontal: contrast, vertical:brightness'], ... +% 'Units', 'Pixels', ... +% 'Position', [6 0 220 28], ... +% 'HorizontalAlignment', 'left', ... +% 'FontUnits', 'points', ... +% 'FontSize', 7.5, ... +% 'ForegroundColor', [.3 1 .3], ... +% 'BackgroundColor', [0 0 0], ... +% 'Tag', 'ColorbarHelpMsg', ... +% 'Parent', hFig); +% end +% elseif ~isempty(hLabel) +% delete(hLabel); +% end +% end +% end + + +%% ============================================================================================== +% ====== SURFACES ============================================================================== +% ============================================================================================== +%% ===== GET SELECTED CHANNELS INDICES AND LOCATION ===== +function [SelectedChannels, PChanLocs, PChanOrient] = GetSelectedChannels(iDS, iFig) + global GlobalData; + PChanLocs = []; + PChanOrient = []; + Channel = GlobalData.DataSet(iDS).Channel; + + % Update selected channels + UpdateSelectedChannels(iDS, iFig); + % Get selected channels indices + SelectedChannels = GlobalData.DataSet(iDS).Figure(iFig).SelectedChannels; + % No channel available or locations not requested : return + if isempty(SelectedChannels) + return; + end + + % === CHANNELS LOCATIONS === + % If modality and selected channels are defined + if (nargout >= 2) + % Get channel locations + chanlocs = [Channel(SelectedChannels).Loc]'; + if isempty(chanlocs) + return + end + % Keep only one kind of sensors locations + nLocs = round(length(chanlocs) / length(SelectedChannels)); + chanlocs = chanlocs(1:nLocs:end,:); + % Check intergrity of Channel structure + if size(chanlocs,1) ~= length(SelectedChannels) + bst_error('Channel locations do not match the number of channels in Channel structure.','Perverted Channel structure.'); + return + end + % Get selected channels locations + PChanLocs = chanlocs; + end + + % === CHANNELS ORIENTATIONS === + if (nargout >= 3) + % Get channel locations + chanorient = [Channel(SelectedChannels).Orient]'; + if isempty(chanorient) + return + end + % Keep only one kind of sensors locations + nOrient = round(length(chanorient) / length(SelectedChannels)); + chanorient = chanorient(1:nOrient:end,:); + % Check intergrity of Channel structure + if size(chanorient,1) ~= length(SelectedChannels) + bst_error('Channel orientations do not match the number of channels in Channel structure.','Perverted Channel structure.'); + return + end + % Get selected channels locations + PChanOrient = chanorient; + end +end + + +%% ===== UPDATE SELECTED CHANNELS ===== +function UpdateSelectedChannels(iDS, iFig) + global GlobalData; + Channel = GlobalData.DataSet(iDS).Channel; + hFig = GlobalData.DataSet(iDS).Figure(iFig).hFigure; + Modality = GlobalData.DataSet(iDS).Figure(iFig).Id.Modality; + + % If Modality of the window is not defined yet : select EEG or MEG + if isempty(Modality) + % Get the possible modalities for this figure + %AvailableModalities = getChannelModalities(Channel, ones(length(Channel), 1)); + AvailableModalities = getChannelModalities(Channel); + Modality = ''; + + % If some modality is defined for this figure + if ~isempty(AvailableModalities) + % If both EEG and MEG are available for this figure + if all(ismember({'MEG', 'EEG'}, AvailableModalities)) + % Ask the user which one to display : MEG or EEG + button = java_dialog('question', 'Please select a modality for this figure.', ... + 'Get available modalities', [], {'MEG', 'EEG'}, 'MEG'); + % If user did not answer : abort... + if isempty(button) + return + else + Modality = button; + end + % Else if only MEG is available : set figure modality to MEG + elseif ismember('MEG', AvailableModalities) + Modality = 'MEG'; + % Else if only EEG is available : set figure modality to EEG + elseif ismember('EEG', AvailableModalities) + Modality = 'EEG'; + end + end + % If no modality is available : return + if isempty(Modality) + return; + end + % Else set this modality as the new window modality + GlobalData.DataSet(iDS).Figure(iFig).Id.Modality = Modality; + % Add '/modality' at the end of the figure title + set(hFig, 'Name', [get(hFig, 'Name'), '/', Modality]); + end + + % CHANNEL FLAG + ChannelFlag = GlobalData.DataSet(iDS).Measures.ChannelFlag; + if isempty(ChannelFlag) + ChannelFlag = ones(length(Channel), 1); + end + % SELECTED CHANNELS + GlobalData.DataSet(iDS).Figure(iFig).SelectedChannels = good_channel(... + Channel, ChannelFlag, Modality); + +end + + + +%% ===== PLOT SURFACE ===== +% Convenient function to consistently plot surfaces. +% USAGE : [hFig,hs] = PlotSurface(hFig, faces, verts, cdata, dataCMap, transparency) +% Parameters : +% - hFig : figure handle to use +% - faces : the triangle listing (array) +% - verts : the corresponding vertices (array) +% - surfaceColor : color data used to display the surface itself (CData for each vertex, or a unique color for all vertices) +% - dataColormap : colormap used to display the data on the surface +% - transparency : surface transparency ([0,1]) +% Returns : +% - hFig : figure handle used +% - hs : handle to the surface +function varargout = PlotSurface( hFig, faces, verts, surfaceColor, transparency) %#ok + % Check inputs + if (nargin ~= 5) + error('Invalid call to PlotSurface'); + end + % If vertices are assumed transposed (if the assumption is wrong, will crash below anyway) + if (size(verts,2) > 3) + verts = verts'; + end + % If vertices are assumed transposed (if the assumption is wrong, will crash below anyway) + if (size(faces,2) > 3) + faces = faces'; + end + % Set figure as current + set(0, 'CurrentFigure', hFig); + + % If cdata is a single RGB color + if isequal(size(surfaceColor), [1,3]) + % Create patch + hs = patch('Faces', faces, ... + 'Vertices', verts,... + 'FaceColor', surfaceColor, ... + 'FaceAlpha', 1 - transparency, ... + 'AlphaDataMapping', 'none', ... + 'EdgeColor', 'none', ... + 'BackfaceLighting', 'lit'); + else + surfaceColor = blend_anatomy_data(ones(size(verts,1),1), ... % Curvature + surfaceColor, ... % Current density + NaN, ... % Limit value + 0, ... % Current density transparency + [], ... % Anatomy color + []); % Data colormap + % Create patch + hs = patch('Faces', faces, ... + 'Vertices', verts,... + 'FaceVertexCData', surfaceColor, ... + 'FaceColor', 'interp', ... + 'FaceAlpha', 'flat', ... + 'AlphaDataMapping', 'none', ... + 'EdgeColor', 'none', ... + 'BackfaceLighting', 'lit'); + end + + % Configure patch material + material([ 0.5 0.50 0.20 1.00 0.5 ]) + lighting phong + + % Set output variables + if(nargout>0), + varargout{1} = hFig; + varargout{2} = hs; + end +end + + +%% ===== PLOT SQUARE/CUT ===== +% USAGE: PlotSquareCut(hFig, TessInfo, dim, pos) +% PlotSquareCut(hFig) : Remove all square cuts displayed +function PlotSquareCut(hFig, TessInfo, dim, pos) + % Get figure description and MRI + [hFig, iFig, iDS] = gui_figuresManager('GetFigure', hFig); + % Delete the previous patch + delete(findobj(hFig, 'Tag', 'squareCut')); + if (nargin < 4) + return + end + hAxes = findobj(hFig, 'tag', 'Axes3D'); + % Get maximum dimensions (MRI size) + sMri = bst_dataSetsManager('GetMri', TessInfo.SurfaceFile); + mriSize = size(sMri.Cube); + voxSize = sMri.Voxsize; + + % Get locations of the slice + nbPts = 50; + baseVect = linspace(-.01, 1.01, nbPts); + switch(dim) + case 1 + X = ones(nbPts) .* (pos + 2) .* voxSize(1); + Y = meshgrid(baseVect) .* mriSize(2) .* voxSize(2); + Z = meshgrid(baseVect)' .* mriSize(3) .* voxSize(3); + surfColor = [1 .5 .5]; + case 2 + X = meshgrid(baseVect) .* mriSize(1) .* voxSize(1); + Y = ones(nbPts) .* (pos + 2) .* voxSize(2) + .1; + Z = meshgrid(baseVect)' .* mriSize(3) .* voxSize(3); + surfColor = [.5 1 .5]; + case 3 + X = meshgrid(baseVect) .* mriSize(1) .* voxSize(1); + Y = meshgrid(baseVect)' .* mriSize(2) .* voxSize(2); + Z = ones(nbPts) .* (pos + 2) .* voxSize(3) + .1; + surfColor = [.5 .5 1]; + end + + % === Switch coordinates from MRI-CS to SCS === + % Apply Rotation/Translation + XYZ = [reshape(X, 1, []); + reshape(Y, 1, []); + reshape(Z, 1, [])]; + XYZ = mri2scs(sMri, XYZ); + % Convert to milimeters + XYZ = XYZ ./ 1000; + + % === PLOT SURFACE === + % Plot new surface + hCut = surface('XData', reshape(XYZ(1,:),nbPts,nbPts), ... + 'YData', reshape(XYZ(2,:),nbPts,nbPts), ... + 'ZData', reshape(XYZ(3,:),nbPts,nbPts), ... + 'CData', ones(nbPts), ... + 'FaceColor', surfColor, ... + 'FaceAlpha', .3, ... + 'EdgeColor', 'none', ... + 'AmbientStrength', .5, ... + 'DiffuseStrength', .9, ... + 'SpecularStrength', .1, ... + 'Tag', 'squareCut', ... + 'Parent', hAxes); +end + + +%% ===== UPDATE MRI DISPLAY ===== +% USAGE: UpdateMriDisplay(hFig, dims, TessInfo, iTess) +% UpdateMriDisplay(hFig, dims) +% UpdateMriDisplay(hFig) +function UpdateMriDisplay(hFig, dims, TessInfo, iTess) + % Parse inputs + if (nargin < 4) + [sMri,TessInfo,iTess] = panel_surface('GetSurfaceMri', hFig); + end + if (nargin < 2) || isempty(dims) + dims = [1 2 3]; + end + % Get the slices that need to be redrawn + newPos = [NaN, NaN, NaN]; + newPos(dims) = TessInfo(iTess).CutsPosition(dims); + % Redraw the three slices + panel_surface('PlotMri', hFig, newPos); +end + + + +%% ===== UPDATE SURFACE COLOR ===== +% Compute color RGB values for each vertex of the surface, taking in account : +% - the surface color, +% - the surface curvature and curvature threshold, +% - the data matrix displayed over the surface (and the data threshold), +% - the data colormap : RGB values, normalized?, absolute values?, limits +% - the data transparency +% Parameters : +% - hFig : handle to a 3DViz figure +% - iTess : indice of the surface to update +function UpdateSurfaceColor(hFig, iTess) + % Get surfaces list + TessInfo = getappdata(hFig, 'Surface'); + % Ignore empty surfaces and MRI slices + if isempty(TessInfo(iTess).hPatch) || ~any(ishandle(TessInfo(iTess).hPatch)) + return + end + % Get best colormap to display data + if ~isempty(TessInfo(iTess).DataSource) && TessInfo(iTess).DataSource.isStat || TessInfo(iTess).DataSource.isZscore + sColormap = bst_colormaps('GetColormap', 'Stat'); + else + sColormap = bst_colormaps('GetColormap', TessInfo(iTess).DataSource.Type); + end + + % === BUILD VALUES === + % If there is no data overlay + if isempty(TessInfo(iTess).Data) + tmp = []; + else + tmp = TessInfo(iTess).Data; + % Apply data threshold + % PERCENT OF LOCAL MAX + % tmp(abs(TessInfo(iTess).Data) < TessInfo(iTess).DataThreshold * max(abs(TessInfo(iTess).Data))) = 0; + % PERCENT OF GLOBAL MAX + tmp(abs(TessInfo(iTess).Data) < TessInfo(iTess).DataIntThreshold * max(abs(TessInfo(iTess).DataLimitValue))) = 0; + % Cluster size using DataExtThreshold + % knd: to do DataExtThreshold + if TessInfo(iTess).DataExtThreshold > 0 + sSurf = bst_dataSetsManager('GetSurface', TessInfo(iTess).SurfaceFile); + tmp = cluster_threshold(tmp,TessInfo(iTess).DataExtThreshold,sSurf.VertConn,1,0); + end + if sColormap.isAbsoluteValues + tmp = abs(tmp); + end + end + + % === MRI === + if strcmpi(TessInfo(iTess).Name, 'Anatomy') + % Update display + UpdateMriDisplay(hFig, [], TessInfo, iTess); + + % === SURFACE === + else + % SHOW CURVATURE + if TessInfo(iTess).SurfShowCurvature + % Get surface + sSurf = bst_dataSetsManager('GetSurface', TessInfo(iTess).SurfaceFile); + % Apply threshold to curvature mapping + curvTmp = sSurf.Curvature; + curvTmp( sSurf.Curvature >= TessInfo(iTess).SurfCurvatureThreshold) = .2; + curvTmp( sSurf.Curvature < TessInfo(iTess).SurfCurvatureThreshold) = -.2; + % DO NOT SHOW CURVATURE + else + % Set Curvature = 1 for all the vertices + curvTmp = ones(TessInfo(iTess).nVertices, 1); + end + + % Compute RGB values + FaceVertexCdata = blend_anatomy_data(curvTmp, ... % Curvature + tmp, ... % Current density + TessInfo(iTess).DataLimitValue, ... % Limit value + TessInfo(iTess).DataAlpha,... % Current density transparency + TessInfo(iTess).AnatomyColor([1,end], :), ... % Anatomy color + sColormap.CMap); % Data colormap + % Edge display : on/off + if ~TessInfo(iTess).SurfShowEdges + EdgeColor = 'none'; + else + EdgeColor = [0 .8 0]; + end + + set(TessInfo(iTess).hPatch, 'FaceVertexCdata', FaceVertexCdata, ... + 'FaceColor', 'interp', ... + 'EdgeColor', EdgeColor); + end +end + + +%% ===== SMOOTH SURFACE CALLBACK ===== +function SmoothSurface(hFig, iTess, smoothValue) + % Get surfaces list + TessInfo = getappdata(hFig, 'Surface'); + % Ignore MRI slices + if strcmpi(TessInfo(iTess).Name, 'Anatomy') + return + end + % Get surfaces vertices + sSurf = bst_dataSetsManager('GetSurface', TessInfo(iTess).SurfaceFile); + % If smoothValue is null: restore initial vertices + if (smoothValue == 0) + set(TessInfo(iTess).hPatch, 'Vertices', sSurf.Vertices); + return + end + + % ===== SMOOTH SURFACE ===== + gui_makeuswait('start'); + SurfSmoothIterations = 200 * smoothValue * length(sSurf.Vertices) / 100000; + % Calculate smoothed vertices locations + Vertices_sm = tess_smooth(sSurf.Vertices, ... + smoothValue, ... + SurfSmoothIterations, ... + sSurf.VertConn); + % Apply smoothed locations + set(TessInfo(iTess).hPatch, 'Vertices', Vertices_sm); + gui_makeuswait('stop'); +end + + + +%% ===== UPDATE SURFACE ALPHA ===== +% Update Alpha values for the given surface. +% Fields that are used from TessInfo: +% - SurfAlpha : Transparency of the surface patch +% - Resect : [x,y,z] doubles : Resect surfaces at these coordinates +% or string {'left', 'right', 'all'} : Display only selected part of the surface +function UpdateSurfaceAlpha(hFig, iTess) + % Get surfaces list + TessInfo = getappdata(hFig, 'Surface'); + Surface = TessInfo(iTess); + + % Ignore empty surfaces and MRI slices + if strcmpi(Surface.Name, 'Anatomy') || isempty(Surface.hPatch) || ~ishandle(Surface.hPatch) + return + end + % Apply current smoothing + SmoothSurface(hFig, iTess, Surface.SurfSmoothValue); + % Get surfaces vertices + Vertices = get(Surface.hPatch, 'Vertices'); + nbVertices = length(Vertices); + % Get vertex connectivity + sSurf = bst_dataSetsManager('GetSurface', TessInfo(iTess).SurfaceFile); + VertConn = sSurf.VertConn; + % Create Alpha data + FaceVertexAlphaData = ones(nbVertices,1) * (1-Surface.SurfAlpha); + + % ===== HEMISPHERE SELECTION (CHAR) ===== + if ischar(Surface.Resect) + % Assumptions for this process : + % - (Y,Z) plane represents more or less the inter-hemispheric plane + % - Y<0 : Right hemisphere + % - Y>0 : Left hemisphere + % - The two hemispheres are not connected (as they are extracted by BrainVisa) + % + % Get the start point + START_PERCENT = .3; + switch Surface.Resect + case 'right' + % Get maximal y value + [yMax, iHideVert] = max(Vertices(:,2)); + % Get all the vertices that are at more than START_PERCENT of the maximum + iNewVert = find(Vertices(:,2) > START_PERCENT * yMax)'; + iNewVert = setdiff(iNewVert, iHideVert); + case 'left' + % Get minimal y value + [yMin, iHideVert] = min(Vertices(:,2)); + % Get all the vertices that are at more than 40% of the maximum + iNewVert = find(Vertices(:,2) < START_PERCENT * yMin)'; + iNewVert = setdiff(iNewVert, iHideVert); + case 'none' + iHideVert = []; + end + + % If not displaying the whole brain + if ~isempty(iHideVert) + % Grow region until getting all the hemisphere + while ~isempty(iNewVert) + iHideVert = union(iHideVert, iNewVert); + iNewVert = patch_swell(iHideVert, VertConn); + end + % Check if we included all the brain (error...) + if length(iHideVert) >= .8 * nbVertices + %warning('The two hemispheres are connected. Cutting at y=0.'); + iHideVert = []; + % The two hemispheres are connected => Cut at y=0 + if strcmpi(Surface.Resect, 'left') + %iHideVert = find(Vertices(:,2) < 0); + Surface.Resect = [0 -0.0000001 0]; + else + %iHideVert = find(Vertices(:,2) > 0); + Surface.Resect = [0 0.0000001 0]; + end + end + end + % Update Alpha data + FaceVertexAlphaData(iHideVert) = 0; + end + + % ===== RESECT (DOUBLE) ===== + if isnumeric(Surface.Resect) && (length(Surface.Resect) == 3) && ~all(Surface.Resect == 0) + iNoModif = []; + % Get faces and vertices + Vertices = get(Surface.hPatch, 'Vertices'); + Faces = get(Surface.hPatch, 'Faces'); + % Compute mean and max of the coordinates + meanVertx = mean(Vertices, 1); + maxVertx = max(abs(Vertices), [], 1); + % Limit values + resectVal = Surface.Resect .* maxVertx + meanVertx; + % Get vertices that are kept in all the cuts + for iCoord = 1:3 + if Surface.Resect(iCoord) > 0 + iNoModif = union(iNoModif, find(Vertices(:,iCoord) < resectVal(iCoord))); + elseif Surface.Resect(iCoord) < 0 + iNoModif = union(iNoModif, find(Vertices(:,iCoord) > resectVal(iCoord))); + end + end + % Get all the faces that are partially visible + ShowVert = zeros(nbVertices,1); + ShowVert(iNoModif) = 1; + facesStatus = sum(ShowVert(Faces), 2); + iFacesVisible = find(facesStatus > 0); + + % Get the vertices of the faces that are partially visible + iVerticesVisible = Faces(iFacesVisible,:); + iVerticesVisible = unique(iVerticesVisible(:))'; + + % Get vertices to project + iVerticesToProject = [iVerticesVisible, patch_swell(iVerticesVisible, VertConn)]; + iVerticesToProject = setdiff(iVerticesToProject, iNoModif); + % If there are some vertices to project + if ~isempty(iVerticesToProject) + % === FIRST PROJECTION === + % For the projected vertices: get the distance from each cut + distToCut = abs(Vertices(iVerticesToProject, :) - repmat(resectVal, [length(iVerticesToProject), 1])); + % Set the distance to the cuts that are not required to infinite + distToCut(:,(Surface.Resect == 0)) = Inf; + % Get the closest cut + [minDist, closestCut] = min(distToCut, [], 2); + + % Project each vertex + Vertices(sub2ind(size(Vertices), iVerticesToProject, closestCut')) = resectVal(closestCut); + + % === SECOND PROJECTION === + % In the faces that have visible and invisible vertices: project the invisible vertices + % on the visible vertices + % + % Get the mixed faces (partially visible) + ShowVert = zeros(nbVertices,1); + ShowVert(iVerticesVisible) = 1; + facesStatus = sum(ShowVert(Faces), 2); + iFacesMixed = find((facesStatus > 0) & (facesStatus < 3)); + + % Get the + projectList = logical(ShowVert(Faces(iFacesMixed,:))); + for iFace = 1:length(iFacesMixed) + iVertVis = Faces(iFacesMixed(iFace), projectList(iFace,:)); + iVertHid = Faces(iFacesMixed(iFace), ~projectList(iFace,:)); + % Project hidden vertices on first visible vertex + Vertices(iVertHid, :) = repmat(Vertices(iVertVis(1), :), length(iVertHid), 1); + end + + % Update patch + set(Surface.hPatch, 'Vertices', Vertices); + end + % Hide some vertices + FaceVertexAlphaData(setdiff(1:nbVertices, iVerticesVisible)) = 0; + end + % Update surface + set(Surface.hPatch, 'FaceVertexAlphaData', FaceVertexAlphaData, ... + 'FaceAlpha', 'interp'); +end + + +%% ===== VIEW SENSORS ===== +%Display sensors markers and labels in a 3DViz figure. +% Usage: ViewSensors(hFig, isMarkers, isLabels) : Display selected channels of figure hFig +% ViewSensors(hFig, isMarkers, isLabels, Modality) : Display channels of target Modality in figure hFig +% Parameters : +% - hFig : target '3DViz' figure +% - isMarkers : Sensors markers status : {0 (hide), 1 (show), [] (ignore)} +% - isLabels : Sensors labels status : {0 (hide), 1 (show), [] (ignore)} +% - Modality : Sensor type to display ('EEG', 'MEG', ...) +function ViewSensors(hFig, isMarkers, isLabels, Modality) + global GlobalData; + % Parse inputs + if (nargin < 4) + Modality = ''; + end + % Get figure description + [hFig, iFig, iDS] = gui_figuresManager('GetFigure', hFig); + if isempty(iDS) + return + end + PlotHandles = GlobalData.DataSet(iDS).Figure(iFig).Handles; + isTopography = strcmpi(get(hFig, 'Tag'), 'Topography'); + is2D = 0; + + % ===== MARKERS LOCATIONS ===== + % === TOPOGRAPHY === + if isTopography + % Markers locations where stored in the Handles structure while creating topography patch + if isempty(PlotHandles.MarkersLocs) + return + end + % Get a location to display the Markers + markersLocs = PlotHandles.MarkersLocs; + % Flag=1 if 2D display + is2D = ismember(GlobalData.DataSet(iDS).Figure(iFig).Id.SubType, {'2DDisc','2DSensorCap'}); + % Get selected channels + SelectedChannels = GlobalData.DataSet(iDS).Figure(iFig).SelectedChannels; + textLocs = markersLocs; + markersOrient = []; + % === 3DVIZ === + % Display selected channels + elseif isempty(Modality) + % Get selected channel indices and locations + [SelectedChannels, PChanLocs, PChanOrient] = GetSelectedChannels(iDS, iFig); + if isempty(SelectedChannels) + return + end + % Get locations to display the Markers + markersLocs = PChanLocs; + markersOrient = PChanOrient; + textLocs = markersLocs; + % Else : display sensors of the target modality + else + Channel = GlobalData.DataSet(iDS).Channel; + % Find sensors of the target modality, select and display them + % SelectedChannels = GetSelectedChannels(iDS, iFig); + % SelectedChannels = find(strcmpi({Channel.Type}, Modality)); + SelectedChannels = good_channel(Channel, [], Modality); + % If no channels for this modality + if isempty(SelectedChannels) + bst_error(['No "' Modality '" sensors in channel file: "' GlobalData.DataSet(iDS).ChannelFile '".'], 'View sensors', 0); + return + end + % VectorView306 / CTF: keep all the coils defintions + if ismember(Modality, {'Vectorview306', 'CTF'}) + markersLocs = cell2mat(cellfun(@(c)c(:,:), {Channel(SelectedChannels).Loc}, 'UniformOutput', 0))'; + textLocs = cell2mat(cellfun(@(c)c(:,1), {Channel(SelectedChannels).Loc}, 'UniformOutput', 0))'; + % Else: only keep the first location + else + markersLocs = cell2mat(cellfun(@(c)c(:,1), {Channel(SelectedChannels).Loc}, 'UniformOutput', 0))'; + textLocs = markersLocs; + end + % Markers orientations: only for MEG + if ismember(Modality, {'MEG', 'MEG GRAD', 'MEG MAG', 'Vectorview306', 'CTF'}) + markersOrient = cell2mat(cellfun(@(c)c(:,1), {Channel(SelectedChannels).Orient}, 'UniformOutput', 0))'; + else + markersOrient = []; + end + end + % Make sure that electrodes locations are in double precision + markersLocs = double(markersLocs); + markersOrient = double(markersOrient); + + % ===== DISPLAY MARKERS OBJECTS ===== + % Put focus on target figure +% figure(hFig); + hAxes = findobj(hFig, 'Tag', 'Axes3D'); + % === SENSORS === + if ~isempty(isMarkers) + % Delete sensor markers + if ~isempty(PlotHandles.hSensorMarkers) && all(ishandle(PlotHandles.hSensorMarkers)) + delete(PlotHandles.hSensorMarkers); + delete(PlotHandles.hSensorOrient); + PlotHandles.hSensorMarkers = []; + PlotHandles.hSensorOrient = []; + end + + % Display sensors markers + if isMarkers + % Is display of a flat 2D topography map + if is2D + PlotHandles.hSensorMarkers = gui_plotSensors2D(hAxes, markersLocs); + % If VectorView306 + elseif strcmpi(Modality, 'Vectorview306') + [PlotHandles.hSensorMarkers, PlotHandles.hSensorOrient] = ... + gui_plotSensorsVectorview306(hAxes, markersLocs, markersOrient); + isLabels = 0; + % Display head points + ViewHeadPoints(hFig, 1); + % If CTF + elseif strcmpi(Modality, 'CTF') + [PlotHandles.hSensorMarkers, PlotHandles.hSensorOrient] = ... + gui_plotSensorsCTF(hAxes, markersLocs, markersOrient); + isLabels = 0; + % Display head points + ViewHeadPoints(hFig, 1); + % Define face and edge colors : + % If more than one patch : transparent sensor cap + elseif ~isempty(findobj(hAxes, 'type', 'patch')) || ~isempty(findobj(hAxes, 'type', 'surface')) + [PlotHandles.hSensorMarkers, PlotHandles.hSensorOrient] = ... + gui_plotSensorsNet(hAxes, markersLocs, 0, markersOrient); + % Else, sensor cap is the only patch => display its faces + else + [PlotHandles.hSensorMarkers, PlotHandles.hSensorOrient] = ... + gui_plotSensorsNet(hAxes, markersLocs, 1, markersOrient); + end + end + end + + % === LABELS === + if ~isempty(isLabels) + % Delete sensor labels + if ~isempty(PlotHandles.hSensorLabels) + delete(PlotHandles.hSensorLabels(ishandle(PlotHandles.hSensorLabels))); + PlotHandles.hSensorLabels = []; + end + % Display sensor labels + if isLabels + sensorNames = {GlobalData.DataSet(iDS).Channel.Name}'; + if ~isempty(sensorNames) + % Get the names of the seleected sensors + sensorNames = sensorNames(SelectedChannels); + PlotHandles.hSensorLabels = text(1.08*textLocs(:,1), 1.08*textLocs(:,2), 1.08*textLocs(:,3), ... + sensorNames, ... + 'Parent', hAxes, ... + 'HorizontalAlignment', 'center', ... + 'Fontsize', 10, ... + 'FontUnits', 'points', ... + 'FontWeight', 'normal', ... + 'Tag', 'SensorsLabels', ... + 'Color', [1,1,.2], ... + 'Interpreter', 'none'); + end + end + end + + GlobalData.DataSet(iDS).Figure(iFig).Handles = PlotHandles; + + % ===== Update MouseSelectedChannels ===== + bst_dataSetsManager('UpdateSelectionForFigure', iDS); +end + + + +%% ===== VIEW HEAD POINTS ===== +function ViewHeadPoints(hFig, isVisible) + global GlobalData; + % Get figure description + [hFig, iFig, iDS] = gui_figuresManager('GetFigure', hFig); + if isempty(iDS) + return + end + hAxes = findobj(hFig, 'Tag', 'Axes3D'); + % If no head points are available: exit + if isempty(GlobalData.DataSet(iDS).HeadPoints) || ~isfield(GlobalData.DataSet(iDS).HeadPoints, 'Loc') || isempty(GlobalData.DataSet(iDS).HeadPoints.Loc) + return + end + HeadPoints = GlobalData.DataSet(iDS).HeadPoints; + % Else, get previous head points + hHeadPointsMarkers = findobj(hFig, 'Tag', 'HeadPointsMarkers'); + hHeadPointsLabels = findobj(hFig, 'Tag', 'HeadPointsLabels'); + % If head points graphic objects already exist: set the "Visible" property + if ~isempty(hHeadPointsMarkers) + if isVisible + set(hHeadPointsMarkers, 'Visible', 'on'); + set(hHeadPointsLabels, 'Visible', 'on'); + else + set(hHeadPointsMarkers, 'Visible', 'off'); + set(hHeadPointsLabels, 'Visible', 'off'); + end + % If head points objects were not created yet: create them + elseif isVisible + % Get digitized points locations + digLoc = double(HeadPoints.Loc)'; + % Display markers + hDigMark = line(digLoc(:,1), digLoc(:,2), digLoc(:,3), ... + 'Parent', hAxes, ... + 'LineWidth', 2, ... + 'LineStyle', 'none', ... + 'MarkerFaceColor', [.3 1 .3], ... + 'MarkerEdgeColor', [.4 .7 .4], ... + 'MarkerSize', 6, ... + 'Marker', 'o', ... + 'Tag', 'HeadPointsMarkers'); + % Prepare display names + digNames = cell(size(HeadPoints.Label)); + for i = 1:length(HeadPoints.Label) + switch upper(HeadPoints.Type{i}) + case 'CARDINAL' + digNames{i} = HeadPoints.Label{i}; +% case 'EXTRA' +% if isnumeric(HeadPoints.Label{i}) +% digNames{i} = num2str(HeadPoints.Label{i}); +% else +% digNames{i} = HeadPoints.Label{i}; +% end + otherwise + if isnumeric(HeadPoints.Label{i}) + digNames{i} = [HeadPoints.Type{i}, '-', num2str(HeadPoints.Label{i})]; + else + digNames{i} = [HeadPoints.Type{i}, '-', HeadPoints.Label{i}]; + end + end + end + % Only display the legends for points that are not "EXTRA" or "EEG" + iNotExtra = find(~strcmpi(HeadPoints.Type, 'EXTRA') & ~strcmpi(HeadPoints.Type, 'EEG')); + % Display labels + if ~isempty(iNotExtra) + hDigLabel = text(1.08*digLoc(iNotExtra,1), 1.08*digLoc(iNotExtra,2), 1.08*digLoc(iNotExtra,3), ... + digNames(iNotExtra)', ... + 'Parent', hAxes, ... + 'HorizontalAlignment', 'center', ... + 'Fontsize', 10, ... + 'FontUnits', 'points', ... + 'FontWeight', 'normal', ... + 'Tag', 'HeadPointsLabels', ... + 'Color', [1,1,.2], ... + 'Interpreter', 'none'); + end + end +end + + +%% ===== VIEW AXIS ===== +function ViewAxis(hFig, isVisible) + hAxes = findobj(hFig, 'Tag', 'Axes3D'); + if (nargin < 2) + isVisible = isempty(findobj(hAxes, 'Tag', 'AxisXYZ')); + end + if isVisible + line([0 0.15], [0 0], [0 0], 'Color', [1 0 0], 'Marker', '>', 'Parent', hAxes, 'Tag', 'AxisXYZ'); + line([0 0], [0 0.15], [0 0], 'Color', [0 1 0], 'Marker', '>', 'Parent', hAxes, 'Tag', 'AxisXYZ'); + line([0 0], [0 0], [0 0.15], 'Color', [0 0 1], 'Marker', '>', 'Parent', hAxes, 'Tag', 'AxisXYZ'); + text(0.151, 0, 0, 'X', 'Color', [1 0 0], 'Parent', hAxes, 'Tag', 'AxisXYZ'); + text(0, 0.151, 0, 'Y', 'Color', [0 1 0], 'Parent', hAxes, 'Tag', 'AxisXYZ'); + text(0, 0, 0.151, 'Z', 'Color', [0 0 1], 'Parent', hAxes, 'Tag', 'AxisXYZ'); + else + hAxisXYZ = findobj(hAxes, 'Tag', 'AxisXYZ'); + if ~isempty(hAxisXYZ) + delete(hAxisXYZ); + end + end +end + + +%% ===== CHANGE BACKGROUND COLOR ===== +function ChangeBackgroundColor(hFig) + % Use previous scout color + newColor = uisetcolor([0 0 0], 'Select scout color'); + % If no color was selected: exit + if (length(newColor) ~= 3) || all(newColor == [0 0 0]) + return + end + % Set background + set(hFig, 'Color', newColor); +end + + diff --git a/brainstorm3/toolbox/gui/panel_scouts.m b/brainstorm3/toolbox/gui/panel_scouts.m new file mode 100644 index 0000000..bc22807 --- /dev/null +++ b/brainstorm3/toolbox/gui/panel_scouts.m @@ -0,0 +1,2678 @@ +function varargout = panel_scouts(varargin) +% PANEL_SCOUTS: Create a panel to add/remove/edit scouts attached to a given 3DViz figure. +% +% USAGE: bstPanelNew = panel_scouts('CreatePanel') +% panel_scouts('UpdatePanel') +% panel_scouts('UpdateScoutsList', sScouts) +% panel_scouts('UpdateScoutProperties') +% panel_scouts('CurrentFigureChanged_Callback') +% panel_scouts('SetCurrentSurface', newSurfaceFile) +% sScouts = panel_scouts('GetScouts', iScouts) +% [sScouts, iScouts] = panel_scouts('GetScouts') +% [sScouts, iScouts] = panel_scouts('GetCurrentScouts') +% [sScouts, iScouts] = panel_scouts('GetScoutsWithSurface', SurfaceFile) +% [sScouts, iScouts] = panel_scouts('GetScoutsWithFigure', hFig) +% [sScouts, iScouts] = panel_scouts('GetSelectedScouts') +% [sScout, iScout] = panel_scouts('CreateNewScout', SurfaceFile, newVertices, newSeed) +% panel_scouts('SetSelectedScouts', iSelScouts) +% panel_scouts('SetSelectionState', isSelected) +% panel_scouts('SelectCorticalSpot', hFig) +% panel_scouts('StartNewScoutSurface') +% panel_scouts('StartNewScoutMri') +% panel_scouts('StartNewScoutMax') +% panel_scouts('EditExistingScoutSurface') +% panel_scouts('EditExistingScoutMri') +% panel_scouts('EditScoutLabel') +% panel_scouts('EditScoutsSize', action) +% panel_scouts('EditScoutsColor', newColor) +% panel_scouts('ScoutEditorInMri', ) +% panel_scouts('PlotAllScouts') +% panel_scouts('PlotScout', iScout) +% panel_scouts('RemoveScoutsFromFigure', hFig) +% panel_scouts('RemoveScouts', iScouts) : remove a list of scouts +% panel_scouts('RemoveScouts', ) : remove the scouts selected in the JList +% panel_scouts('RemoveUnusedScouts') +% panel_scouts('JoinScouts') +% panel_scouts('UpdateScoutsVertices', SurfaceFile) +% panel_scouts('SaveScouts') +% panel_scouts('LoadScouts') +% panel_scouts('ForwardModelForScout') +% panel_scouts('ViewInMriViewer') +% ColorTable = panel_scouts('GetScoutsColorTable') +% ScoutsOptions = panel_scouts('GetScoutDisplayType') + +% @============================================================================= +% This software is part of The Brainstorm Toolbox +% http://neuroimage.usc.edu/brainstorm +% +% Copyright (c)2009 Brainstorm by the University of Southern California +% This software is distributed under the terms of the GNU General Public License +% as published by the Free Software Foundation. Further details on the GPL +% license can be found at http://www.gnu.org/copyleft/gpl.html. +% +% FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE +% UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY +% WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF +% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY +% LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE. +% +% For more information type "brainstorm licence" at command prompt. +% =============================================================================@ +% +% Authors: Francois Tadel, 2008 +% ----------------------------- Script History --------------------------------- +% FT 24-Jun-2008 Creation +% ------------------------------------------------------------------------------ + +% No parameters +if (nargin == 0) +% Else : execute appropriate local function +elseif ischar(varargin{1}) + if (nargout) + [varargout{1:nargout}] = bst_safeCall(str2func(varargin{1}), varargin{2:end}); + else + bst_safeCall(str2func(varargin{1}), varargin{2:end}); + end +end +end + + +%% ===== CREATE PANEL ===== +function bstPanelNew = CreatePanel() %#ok + panelName = 'Scout'; + % Java initializations + import java.awt.*; + import javax.swing.*; + import org.brainstorm.icon.IconLoader; + import java.awt.event.KeyEvent; + + % CONSTANTS + DEFAULT_HEIGHT = 20; + HFILLED_WIDTH = 40; + BUTTON_SIZE_WIDTH = 26; + BUTTON_SIZE1_WIDTH = 22; + TOOLBUTTON_SIZE = Dimension(28,24); + CHECKBOX_MARGIN = Insets(3,10,3,10); + jFontText = java.awt.Font('Dialog', java.awt.Font.PLAIN, 11); + % The the better JList height + JLIST_MAX_HEIGHT = 180; + + % Create tools panel + jPanelNew = JPanel(BorderLayout()); + + % ===== Create toolbar/menubar ===== + % Create menubar + jMenuBar = JMenuBar(); + % === TOOLBAR === + % Toolbar embedded in the menubar, to display correctly the first icons (non-menus) + jToolbarScouts = JToolBar('Edit scouts'); + jToolbarScouts.setBorderPainted(0); + jToolbarScouts.setFloatable(0); + jToolbarScouts.setRollover(1); + % Button "Add scout" + jButtonAddScout = JToggleButton(IconLoader.ICON_SCOUT_NEW); + jButtonAddScout.setPreferredSize(TOOLBUTTON_SIZE); + jButtonAddScout.setMaximumSize(TOOLBUTTON_SIZE); + jButtonAddScout.setFocusable(0); + jButtonAddScout.setToolTipText('Select point:

- If a scout selected in the list: point is added to the scout
- Else, a new scout is created.
') + set(jButtonAddScout, 'ActionPerformedCallback', @(h,ev)bst_safeCall(@SetSelectionState, ev.getSource.isSelected())); + jToolbarScouts.add(jButtonAddScout); + % Button "Display scouts" + jButtonDisplayScout = JButton(IconLoader.ICON_TS_DISPLAY); + jButtonDisplayScout.setPreferredSize(TOOLBUTTON_SIZE); + jButtonDisplayScout.setMaximumSize(TOOLBUTTON_SIZE); + jButtonDisplayScout.setFocusable(0); + set(jButtonDisplayScout, 'ActionPerformedCallback', @(h,ev)bst_safeCall(@DisplayScouts)); + jButtonDisplayScout.setToolTipText('Display scouts time series    [ENTER]'); + jToolbarScouts.add(jButtonDisplayScout); + jMenuBar.add(jToolbarScouts); + + % === MENU NEW === + jMenuNew = JMenu('New'); + jMenuNew.setIcon(IconLoader.ICON_MENU); + % Menu "New scout (design on surface)" + jButtonNewScoutSurface = JMenuItem('Define in 3D view', IconLoader.ICON_SCOUT_NEW); + set(jButtonNewScoutSurface, 'ActionPerformedCallback', @(h,ev)bst_safeCall(@StartNewScoutSurface)); + jMenuNew.add(jButtonNewScoutSurface); + % Menu "New scout (design on MRI)" + jButtonNewScoutMri = JMenuItem('Define in MRI slices', IconLoader.ICON_EDIT_SCOUT_IN_MRI); + set(jButtonNewScoutMri, 'ActionPerformedCallback', @(h,ev)bst_safeCall(@StartNewScoutMri)); + jMenuNew.add(jButtonNewScoutMri); + % Menu "New scout (SurfaceTiling)" + jButtonSurfaceTiling = JMenuItem('Surface tiling', IconLoader.ICON_EDIT_SCOUT_IN_MRI); + set(jButtonSurfaceTiling, 'ActionPerformedCallback', @(h,ev)bst_safeCall(@SurfaceTiling)); + jMenuNew.add(jButtonSurfaceTiling); + % Separator + jMenuNew.addSeparator(); + % Menu "Find vertex with maximal value" + jButtonNewScoutMax = JMenuItem('Find maximal value', IconLoader.ICON_FIND_MAX); + set(jButtonNewScoutMax, 'ActionPerformedCallback', @(h,ev)bst_safeCall(@StartNewScoutMax)); + jMenuNew.add(jButtonNewScoutMax); + jMenuBar.add(jMenuNew); + + % === MENU EDIT === + jMenuEdit = JMenu('Edit'); + jMenuEdit.setIcon(IconLoader.ICON_MENU); + % Button "Rename scout" + jButtonRenameScout = JMenuItem('Rename', IconLoader.ICON_EDIT); + set(jButtonRenameScout, 'ActionPerformedCallback', @(h,ev)bst_safeCall(@EditScoutLabel)); + jMenuEdit.add(jButtonRenameScout); + % Button "Edit scout color" + jButtonScoutColor = JMenuItem('Set color', IconLoader.ICON_COLOR_SELECTION); + set(jButtonScoutColor, 'ActionPerformedCallback', @(h,ev)bst_safeCall(@EditScoutsColor)); + jMenuEdit.add(jButtonScoutColor); + % Button "Remove scout" + jButtonRemoveScout = JMenuItem('Remove', IconLoader.ICON_DELETE); + set(jButtonRemoveScout, 'ActionPerformedCallback', @(h,ev)bst_safeCall(@RemoveScouts)); + jMenuEdit.add(jButtonRemoveScout); + % Button "Join scouts" + jButtonJoinScouts = JMenuItem('Merge', IconLoader.ICON_FUSION); + set(jButtonJoinScouts, 'ActionPerformedCallback', @(h,ev)bst_safeCall(@JoinScouts)); + jMenuEdit.add(jButtonJoinScouts); + % Button "Deselect all scouts" + jButtonDeselect = JMenuItem('Deselect all', IconLoader.ICON_RELOAD); + set(jButtonDeselect, 'ActionPerformedCallback', @(h,ev)bst_safeCall(@SetSelectedScouts, 0)); + jMenuEdit.add(jButtonDeselect); + % Separator + jMenuEdit.addSeparator(); + % Button "Add vertices" + jButtonAddVertices = JMenuItem('Add vertices', IconLoader.ICON_SCOUT_NEW); + set(jButtonAddVertices, 'ActionPerformedCallback', @(h,ev)bst_safeCall(@EditExistingScoutSurface)); + jMenuEdit.add(jButtonAddVertices); + % Menu "Edit in MRI" + jButtonEditInMri = JMenuItem('Edit in MRI', IconLoader.ICON_EDIT_SCOUT_IN_MRI); + set(jButtonEditInMri, 'ActionPerformedCallback', @(h,ev)bst_safeCall(@EditExistingScoutMri)); + jMenuEdit.add(jButtonEditInMri); + % Separator + jMenuEdit.addSeparator(); + % Menu "Expand with correlation" + jButtonCorrelation = JMenuItem('Expand (correlation)', IconLoader.ICON_RESIZE); + jButtonCorrelation.setToolTipText('Expand scout based on correlation with other sources.'); + set(jButtonCorrelation, 'ActionPerformedCallback', @(h,ev)bst_safeCall(@ExpandWithCorrelation)); + jMenuEdit.add(jButtonCorrelation); + % Menu "Find vertex with maximal value" + jButtonSelectMaximum = JMenuItem('Find maximal value', IconLoader.ICON_FIND_MAX); + set(jButtonSelectMaximum, 'ActionPerformedCallback', @(h,ev)bst_safeCall(@EditExistingScoutMax)); + jMenuEdit.add(jButtonSelectMaximum); + % Separator + jMenuEdit.addSeparator(); + % Menu "Remove scout vertices from surface" + jButtonRemoveScout = JMenuItem('Remove vertices', IconLoader.ICON_DELETE); + set(jButtonRemoveScout, 'ActionPerformedCallback', @(h,ev)bst_safeCall(@RemoveScoutFromSurface)); + jMenuEdit.add(jButtonRemoveScout); + % Separator + jMenuEdit.addSeparator(); + % Menu "Forward model" + jButtonForwardModel = JMenuItem('Simulation', IconLoader.ICON_SCOUT_FORWARDMODEL); + jButtonForwardModel.setToolTipText(['Simulation: Forward model of selected scouts:
' ... + '
Simulate the scalp data that would be recorded if
' ... + 'only the selected cortex region was activated.

' ... + 'If no scout is selected: simulate recordings produced
' ... + 'by the activity of the whole cortex.
']); + set(jButtonForwardModel, 'ActionPerformedCallback', @(h,ev)bst_safeCall(@ForwardModelForScout)); + jMenuEdit.add(jButtonForwardModel); + jMenuBar.add(jMenuEdit); + + % === MENU VIEW === + jMenuView = JMenu('View'); + jMenuView.setIcon(IconLoader.ICON_MENU); + % === FUNCTIONAL DISPLAY === + % Menu "View time series" + jButtonDisplayScout = JMenuItem('View time series', IconLoader.ICON_TS_DISPLAY); + set(jButtonDisplayScout, 'ActionPerformedCallback', @(h,ev)bst_safeCall(@DisplayScouts)); + jMenuView.add(jButtonDisplayScout); + % === ANTOMICAL DISPLAY === + % Separator + jMenuView.addSeparator(); + % Menu "View scout on cortex" + jButtonViewOnCortex = JMenuItem('View on cortex', IconLoader.ICON_CORTEX); + set(jButtonViewOnCortex, 'ActionPerformedCallback', @(h,ev)bst_safeCall(@ViewOnCortex)); + jMenuView.add(jButtonViewOnCortex); + % Menu "View in MRI" + jButtonViewInMriViewer = JMenuItem('View in MRIViewer', IconLoader.ICON_VIEW_SCOUT_IN_MRI); + set(jButtonViewInMriViewer, 'ActionPerformedCallback', @(h,ev)bst_safeCall(@ViewInMriViewer)); + jMenuView.add(jButtonViewInMriViewer); + % Menu "Center 3D MRI on scout" + jButtonCenterMri = JMenuItem('Center MRI on scout', IconLoader.ICON_VIEW_SCOUT_IN_MRI); + set(jButtonCenterMri, 'ActionPerformedCallback', @(h,ev)bst_safeCall(@CenterMriOnScout)); + jMenuView.add(jButtonCenterMri); + + % === DISPLAY OPTIONS === + % Separator + jMenuView.addSeparator(); + % Menu "Scouts displayed in 3D" + jMenuSelAll = JMenu('3D display options...'); + jMenuSelAll.setIcon(IconLoader.ICON_PROPERTIES); + % Display ALL/SELECTED scouts + jButtonGroup = ButtonGroup(); + jRadioScoutViewSelected = JRadioButtonMenuItem('Show selected scouts'); + jRadioScoutViewAll = JRadioButtonMenuItem('Show all scouts', 1); + set(jRadioScoutViewSelected, 'ActionPerformedCallback', @(h,ev)UpdateScoutsDisplay()); + set(jRadioScoutViewAll, 'ActionPerformedCallback', @(h,ev)UpdateScoutsDisplay()); + jRadioScoutViewSelected.setMargin(CHECKBOX_MARGIN); + jRadioScoutViewAll.setMargin(CHECKBOX_MARGIN); + jButtonGroup.add(jRadioScoutViewSelected); + jButtonGroup.add(jRadioScoutViewAll); + jMenuSelAll.add(jRadioScoutViewSelected); + jMenuSelAll.add(jRadioScoutViewAll); + % Separator + jMenuSelAll.addSeparator(); + % SHOW/HIDE SCOUTS PATCHES OVER MRI SLICES + jCheckViewPatchesInMri = JCheckBoxMenuItem('Show scout patch (in MRI/3D figures)', 0); + set(jCheckViewPatchesInMri, 'ActionPerformedCallback', @(h,ev)UpdateScoutsDisplay()); + jCheckViewPatchesInMri.setMargin(CHECKBOX_MARGIN); + jMenuSelAll.add(jCheckViewPatchesInMri); + % LIMIT MRI SCOURCES TO SCOUTS + jCheckLimitMriSources = JCheckBoxMenuItem('Limit MRI sources to scouts', 1); + set(jCheckLimitMriSources, 'ActionPerformedCallback', @(h,ev)LimitMriSourcesToScouts()); + jCheckLimitMriSources.setMargin(CHECKBOX_MARGIN); + jMenuSelAll.add(jCheckLimitMriSources); + + jMenuView.add(jMenuSelAll); + % Menu "Time series options" + jMenuTsOptions = JMenu('Time series options...'); + jMenuTsOptions.setIcon(IconLoader.ICON_PROPERTIES); + % OPTIONS : Sources values (Relative / Absolute) + jButtonGroupValues = ButtonGroup(); + jRadioValuesRelative = JRadioButtonMenuItem('Relative values', 0); + jRadioValuesAbsolute = JRadioButtonMenuItem('Absolute values', 1); + jRadioValuesRelative.setMargin(CHECKBOX_MARGIN); + jRadioValuesAbsolute.setMargin(CHECKBOX_MARGIN); + jButtonGroupValues.add(jRadioValuesRelative); + jButtonGroupValues.add(jRadioValuesAbsolute); + jMenuTsOptions.add(jRadioValuesRelative); + jMenuTsOptions.add(jRadioValuesAbsolute); + % Separator + jMenuTsOptions.addSeparator(); + % OPTIONS : Sources type (Mean, max, all) + jButtonGroupType = ButtonGroup(); + jRadioTypeMean = JRadioButtonMenuItem('Sources: Mean', 1); + jRadioTypeMax = JRadioButtonMenuItem('Sources: Max', 0); + jRadioTypePower= JRadioButtonMenuItem('Sources: Power', 0); + jRadioTypeAll = JRadioButtonMenuItem('Sources: All', 0); + jRadioTypeMean.setMargin(CHECKBOX_MARGIN); + jRadioTypeMax.setMargin( CHECKBOX_MARGIN); + jRadioTypePower.setMargin( CHECKBOX_MARGIN); + jRadioTypeAll.setMargin( CHECKBOX_MARGIN); + jButtonGroupType.add(jRadioTypeMean); + jButtonGroupType.add(jRadioTypeMax); + jButtonGroupType.add(jRadioTypePower); + jButtonGroupType.add(jRadioTypeAll); + set(jRadioTypeMean, 'ActionPerformedCallback', @SourcesTypeChanged_Callback); + set(jRadioTypeMax, 'ActionPerformedCallback', @SourcesTypeChanged_Callback); + set(jRadioTypePower,'ActionPerformedCallback', @SourcesTypeChanged_Callback); + set(jRadioTypeAll, 'ActionPerformedCallback', @SourcesTypeChanged_Callback); + jMenuTsOptions.add(jRadioTypeMean); + jMenuTsOptions.add(jRadioTypeMax); + jMenuTsOptions.add(jRadioTypePower); + jMenuTsOptions.add(jRadioTypeAll); + jMenuView.add(jMenuTsOptions); + jMenuBar.add(jMenuView); + jPanelNew.add(jMenuBar, BorderLayout.NORTH); + + + % ===== PANEL MAIN ===== + jPanelMain = JPanel(); + jPanelMain.setLayout(BoxLayout(jPanelMain, BoxLayout.Y_AXIS)); + jPanelMain.setBorder(BorderFactory.createEmptyBorder(7,7,7,7)); +% jPanelMain.setPreferredSize(Dimension()); + % ===== FIRST PART ===== + jPanelFirstPart = JPanel(BorderLayout()); + jPanelFirstPart.setPreferredSize(Dimension(100,JLIST_MAX_HEIGHT)); + jPanelFirstPart.setMaximumSize(Dimension(500,JLIST_MAX_HEIGHT)); + % Vertical Toolbar + jToolbarScouts2 = JToolBar('Edit scouts'); + jToolbarScouts2.setBorderPainted(0); + jToolbarScouts2.setFloatable(0); + jToolbarScouts2.setRollover(1); + jToolbarScouts2.setOrientation(jToolbarScouts2.VERTICAL); + % Button "Load scouts file" + jButtonLoadScouts = JButton(IconLoader.ICON_FOLDER_OPEN); + jButtonLoadScouts.setPreferredSize(TOOLBUTTON_SIZE); + jButtonLoadScouts.setFocusable(0); + jButtonLoadScouts.setToolTipText('Load scouts file'); + set(jButtonLoadScouts, 'ActionPerformedCallback', @(h,ev)bst_safeCall(@LoadScouts)); + jToolbarScouts2.add(jButtonLoadScouts); + % Button "Save scouts file" + jButtonSaveScouts = JButton(IconLoader.ICON_SAVE); + jButtonSaveScouts.setPreferredSize(TOOLBUTTON_SIZE); + jButtonSaveScouts.setFocusable(0); + jButtonSaveScouts.setToolTipText('Save scouts file'); + set(jButtonSaveScouts, 'ActionPerformedCallback', @(h,ev)bst_safeCall(@SaveScouts)); + jToolbarScouts2.add(jButtonSaveScouts); + jPanelFirstPart.add(jToolbarScouts2, BorderLayout.EAST); + + % ===== Scouts list ===== + jPanelScoutsList = JPanel(BorderLayout); + jPanelScoutsList.setBorder(BorderFactory.createTitledBorder('Available scouts')); + jListScouts = JList(); + set(jListScouts, 'ValueChangedCallback', @ScoutsListValueChanged_Callback, ... + 'KeyTypedCallback', @ScoutsListKeyTyped_Callback, ... + 'MouseClickedCallback', @ScoutsListClick_Callback); + jPanelScrollList = JScrollPane(); + jPanelScrollList.getLayout.getViewport.setView(jListScouts); + jPanelScrollList.setBorder([]); + jPanelScoutsList.add(jPanelScrollList); + jPanelFirstPart.add(jPanelScoutsList, BorderLayout.CENTER); + jPanelMain.add(jPanelFirstPart); + + % ===== Scouts options panel ===== + jPanelScoutOptions = getRiverPanel([0,3], [0,5,10,3], 'Scout size'); + % OPTIONS : Scout size +% jPanelScoutOptions.add('br', JLabel('Size: ')); + + % OPTIONS : Scout growth + jButtonSizeShrink = JButton('<<'); + jButtonSizeShrink1 = JButton('<'); + jButtonSizeGrow1 = JButton('>'); + jButtonSizeGrow = JButton('>>'); + jButtonSizeGrow.setPreferredSize( Dimension(BUTTON_SIZE_WIDTH, DEFAULT_HEIGHT)); + jButtonSizeGrow1.setPreferredSize( Dimension(BUTTON_SIZE1_WIDTH, DEFAULT_HEIGHT)); + jButtonSizeShrink.setPreferredSize( Dimension(BUTTON_SIZE_WIDTH, DEFAULT_HEIGHT)); + jButtonSizeShrink1.setPreferredSize(Dimension(BUTTON_SIZE1_WIDTH, DEFAULT_HEIGHT)); + jButtonSizeGrow.setFont(jFontText); + jButtonSizeGrow1.setFont(jFontText); + jButtonSizeShrink.setFont(jFontText); + jButtonSizeShrink1.setFont(jFontText); + jButtonSizeGrow.setMargin( Insets(0,0,0,0)); + jButtonSizeGrow1.setMargin( Insets(0,0,0,0)); + jButtonSizeShrink.setMargin( Insets(0,0,0,0)); + jButtonSizeShrink1.setMargin(Insets(0,0,0,0)); + jButtonSizeGrow.setFocusPainted(0); + jButtonSizeGrow1.setFocusPainted(0); + jButtonSizeShrink.setFocusPainted(0); + jButtonSizeShrink1.setFocusPainted(0); + jButtonSizeGrow.setToolTipText( 'Increase scout size'); + jButtonSizeGrow1.setToolTipText( 'Increase scout size (only one vertex)'); + jButtonSizeShrink.setToolTipText( 'Decrease scout size'); + jButtonSizeShrink1.setToolTipText('Decrease scout size (only one vertex)'); + set(jButtonSizeGrow, 'ActionPerformedCallback', @(h,ev)EditScoutsSize('Grow')); + set(jButtonSizeGrow1, 'ActionPerformedCallback', @(h,ev)EditScoutsSize('Grow1')); + set(jButtonSizeShrink, 'ActionPerformedCallback', @(h,ev)EditScoutsSize('Shrink')); + set(jButtonSizeShrink1,'ActionPerformedCallback', @(h,ev)EditScoutsSize('Shrink1')); + jPanelScoutOptions.add(jButtonSizeShrink); + jPanelScoutOptions.add(jButtonSizeShrink1); + jPanelScoutOptions.add(jButtonSizeGrow1); + jPanelScoutOptions.add(jButtonSizeGrow); + + % Separator + jPanelScoutOptions.add(JLabel(' ')); + + % OPTIONS : Constrained to data + jToggleConstrained = JToggleButton('Constrained'); + jToggleConstrained.setPreferredSize(Dimension(HFILLED_WIDTH, DEFAULT_HEIGHT)); + jToggleConstrained.setFont(jFontText); + jToggleConstrained.setMargin(Insets(0,0,0,0)); + jToggleConstrained.setFocusPainted(0); + jToggleConstrained.setToolTipText('Constrain patch growth to vertices with data above threshold.'); + jPanelScoutOptions.add('tab hfill', jToggleConstrained); + + % OPTIONS : Scout size in vertices/area + jPanelScoutOptions.add('br', JLabel('Number of vertices:')); + jLabelScoutSize = JLabel(''); +% jLabelScoutSize.setPreferredSize(Dimension(HFILLED_WIDTH, DEFAULT_HEIGHT)); + jPanelScoutOptions.add('tab hfill', jLabelScoutSize); + + + panelPrefSize = jPanelScoutOptions.getPreferredSize(); + jPanelScoutOptions.setMaximumSize(Dimension(32000, panelPrefSize.getHeight())); + jPanelMain.add(jPanelScoutOptions); + + % ===== Display options panel ===== + jPanelDisplayOptions = getRiverPanel([0,1], [2,4,4,0], 'Display options'); + % OPTIONS : Overlay scouts/conditions + jPanelDisplayOptions.add(JLabel('Overlay:')); + jCheckOverlayScouts = JCheckBox('Scouts', 0); + jCheckOverlayConditions = JCheckBox('Conditions', 0); + jCheckOverlayScouts.setMargin(Insets(0,0,0,0)); + jCheckOverlayConditions.setMargin(Insets(0,0,0,0)); + set(jCheckOverlayScouts, 'ActionPerformedCallback', @OverlayTypeChanged_Callback); + set(jCheckOverlayConditions, 'ActionPerformedCallback', @OverlayTypeChanged_Callback); + jPanelDisplayOptions.add('tab', jCheckOverlayScouts); + jPanelDisplayOptions.add(jCheckOverlayConditions); + panelPrefSize = jPanelDisplayOptions.getPreferredSize(); + jPanelDisplayOptions.setMaximumSize(Dimension(32000, panelPrefSize.getHeight())); + jPanelMain.add(jPanelDisplayOptions); + + jPanelMain.add(Box.createVerticalGlue()); + jPanelMain.setPreferredSize(Dimension(200,280)); + jScrollMain = JScrollPane(jPanelMain); + jScrollMain.setBorder([]); + jPanelNew.add(jScrollMain); + + % Create the BstPanel object that is returned by the function + % => constructor BstPanel(jHandle, panelName, sControls) + bstPanelNew = BstPanel(panelName, ... + jPanelNew, ... + struct('jPanelScoutsList', jPanelScoutsList, ... + 'jToolbarScouts', jToolbarScouts, ... + 'jButtonAddScout', jButtonAddScout, ... + 'jPanelScoutOptions', jPanelScoutOptions, ... + 'jLabelScoutSize', jLabelScoutSize, ... + 'jPanelDisplayOptions', jPanelDisplayOptions, ... + 'jRadioScoutViewSelected', jRadioScoutViewSelected, ... + 'jRadioScoutViewAll', jRadioScoutViewAll, ... + 'jCheckViewPatchesInMri', jCheckViewPatchesInMri, ... + 'jCheckLimitMriSources', jCheckLimitMriSources, ... + 'jToggleConstrained', jToggleConstrained, ... + 'jRadioTypeMean', jRadioTypeMean, ... + 'jRadioTypeMax', jRadioTypeMax, ... + 'jRadioTypePower', jRadioTypePower, ... + 'jRadioTypeAll', jRadioTypeAll, ... + 'jRadioValuesRelative', jRadioValuesRelative, ... + 'jRadioValuesAbsolute', jRadioValuesAbsolute, ... + 'jCheckOverlayScouts', jCheckOverlayScouts, ... + 'jCheckOverlayConditions', jCheckOverlayConditions, ... + 'jListScouts', jListScouts)); + + +%% ===== INTERNAL CALLBACKS ===== + % Sources type changed + function SourcesTypeChanged_Callback(varargin) + % If 'ALL' sources type: disable overlays options + if jRadioTypeAll.isSelected() + jCheckOverlayScouts.setEnabled(0); + jCheckOverlayConditions.setEnabled(0); + jCheckOverlayScouts.setSelected(0); + jCheckOverlayConditions.setSelected(0); + else + jCheckOverlayScouts.setEnabled(1); + jCheckOverlayConditions.setEnabled(1); + end + end + + % Overlay type changed + function OverlayTypeChanged_Callback(varargin) + % If an overlay is activated : disable 'ALL' sources type + if jCheckOverlayScouts.isSelected() || jCheckOverlayConditions.isSelected() + jRadioTypeAll.setEnabled(0); + if jRadioTypeAll.isSelected() + jRadioTypeMean.setSelected(1); + end + else + jRadioTypeAll.setEnabled(1); + end + end + + function LimitMriSourcesToScouts() +% global GlobalData; + % Update selected scouts + UpdateScoutsDisplay(); +% % Get all other figures +% hAllFig = []; +% for i = 1:length(GlobalData.Scouts) +% hAllFig = [hAllFig, [GlobalData.Scouts(i).Handles.hFig]]; +% end +% % Update all other figures +% panel_surface('UpdateOverlayCubes', unique(hAllFig)); + + end +end + + + +%% ================================================================================= +% === CONTROLS CALLBACKS ========================================================= +% ================================================================================= +%% ===== VIEW ONLY SELECTED SCOUTS ? ===== +function status = isViewOnlySelectedScouts() + % Get "Scouts" panel controls + ctrl = bst_getContext('PanelControls', 'Scout'); + status = ctrl.jRadioScoutViewSelected.isSelected(); +end + +%% ===== LIST SELECTION CHANGED CALLBACK ===== +function ScoutsListValueChanged_Callback(h, ev) + if ~ev.getValueIsAdjusting() + % Update panel "Scouts" fields + UpdateScoutProperties(); + % Display/hide scouts + if isViewOnlySelectedScouts() + UpdateScoutsDisplay(); + end + end +end + +%% ===== LIST KEY TYPED CALLBACK ===== +function ScoutsListKeyTyped_Callback(h, ev) + switch(uint8(ev.getKeyChar())) + % DELETE + case ev.VK_DELETE + RemoveScouts(); + case ev.VK_ENTER + gui_viewResultsScouts(); + case uint8('+') + EditScoutsSize('Grow1'); + case uint8('-') + EditScoutsSize('Shrink1'); + case ev.VK_ESCAPE + SetSelectedScouts(0); + end +end + +%% ===== LIST CLICK CALLBACK ===== +function ScoutsListClick_Callback(h, ev) + % If DOUBLE CLICK + if (ev.getClickCount() == 2) + % Rename selection + EditScoutLabel(); + end +end + + +%% ================================================================================= +% === EXTERNAL PANEL CALLBACKS =================================================== +% ================================================================================= +%% ===== UPDATE CALLBACK ===== +function UpdatePanel() + % Get "Scouts" panel controls + ctrl = bst_getContext('PanelControls', 'Scout'); + % Get current scouts + sScouts = GetCurrentScouts(); + % If a surface is available for current figure + if ~isempty(sScouts) + % Enable all panels + gui_setEnabledControls([ctrl.jPanelScoutsList, ... + ctrl.jPanelDisplayOptions, ctrl.jPanelScoutOptions], 1); + % Else : no figure associated with the panel : disable all controls + else + gui_setEnabledControls([ctrl.jPanelScoutsList, ... + ctrl.jPanelDisplayOptions, ctrl.jPanelScoutOptions], 0); + % Release "Add scouts" button + ctrl.jButtonAddScout.setSelected(0); + end + % Update scouts JList + UpdateScoutsList(sScouts); +end + + +%% ===== UPDATE SCOUTS LIST ===== +function UpdateScoutsList(sScouts) + % If scouts list was not defined : get it + if (nargin < 1) + sScouts = GetCurrentScouts(); + end + % Get "Scouts" panel controls + ctrl = bst_getContext('PanelControls', 'Scout'); + % Create a new empty list + listModel = awtcreate('javax.swing.DefaultListModel'); + % Add an item in list for each scout found for target figure + for iScout = 1:length(sScouts) + awtinvoke(listModel, 'addElement(Ljava.lang.Object;)', sScouts(iScout).Label); + end + % Update list model + awtinvoke(ctrl.jListScouts, 'setModel(Ljavax.swing.ListModel;)', listModel); + % Reset Scout comments + awtinvoke(ctrl.jLabelScoutSize, 'setText(Ljava.lang.String;)', ''); +end + + + +%% ===== UPDATE SCOUT PROPERTIES DISPLAY ===== +function UpdateScoutProperties() + % Get "Scouts" panel controls + ctrl = bst_getContext('PanelControls', 'Scout'); + % Get selected scouts + sScouts = GetSelectedScouts(); + % Add all the selected scouts to compute : area, nbVertices +% scoutArea = 0; + nbVertices = 0; + for i = 1:length(sScouts) + % NbVertices + nbVertices = nbVertices + length(sScouts(i).Vertices); + % Area +% if ~isempty(sScouts(i).Area) +% scoutArea = scoutArea + sScouts(i).Area; +% end + end + % Format results (NbVertices / Area) + if (nbVertices == 0) + strSize = ''; + else +% elseif (scoutArea == 0) + strSize = sprintf(' [ %d ]', nbVertices); +% else +% strSize = sprintf('%d (%0.2f cm2)', nbVertices, scoutArea); + end + % Update panel + ctrl.jLabelScoutSize.setText(strSize); +end + + +%% ===== CURRENT FIGURE CHANGED ===== +function CurrentFigureChanged_Callback(oldFig, hFig) + global GlobalData; + % === NO NEW FIGURE === + % If no figure is available + if isempty(hFig) || ~ishandle(hFig) + % Reset current surface + GlobalData.CurrentScoutsSurface = ''; + return + end + % Get cortex surface for new figure + [iNewSurf, TessInfo] = panel_surface('GetSurfaceCortex', hFig); + % If no cortex defined, try to find a MRI surface + if isempty(iNewSurf) + [sMri, TessInfo, iNewSurf] = panel_surface('GetSurfaceMri', hFig); + end + + % === COMPARE OLD AND NEW FIG === + if ~isempty(oldFig) && ishandle(oldFig) && (oldFig ~= hFig) + % Get surfaces for old figure + [iOldSurf, oldTessInfo] = panel_surface('GetSurfaceCortex', oldFig); + % If no cortex defined, try to find a MRI surface + if isempty(iOldSurf) + [sMri, TessInfo, iOldSurf] = panel_surface('GetSurfaceMri', oldFig); + end + % If cortical surface did not change: ignore callback + if ~isempty(iOldSurf) && ~isempty(iNewSurf) && io_compareFileNames(oldTessInfo(iOldSurf).SurfaceFile, TessInfo(iNewSurf).SurfaceFile); + return + end + end + + % === UPDATE CURRENT SURFACE === + newSurfaceFile = ''; + % If no cortex surface is found in target figure + if ~isempty(iNewSurf) + % If anatomy surface : use correspondant cortex surface instead + if strcmpi(TessInfo(iNewSurf).Name, 'Anatomy') + sSurfCortex = bst_getContext('SurfaceFileByType', TessInfo(iNewSurf).SurfaceFile, 'Cortex'); + if ~isempty(sSurfCortex) + newSurfaceFile = sSurfCortex.FileName; + end + else + newSurfaceFile = TessInfo(iNewSurf).SurfaceFile; + end + end + % Update current surface + SetCurrentSurface(newSurfaceFile); + + % === UPDATE SELECTED SCOUTS === + % Get current scouts (for new figure) + [sScouts, iScouts] = GetCurrentScouts(); + % If 3D figure: scouts have graphic handles + FigureId = getappdata(hFig, 'FigureId'); + switch(FigureId.Type) + case 'MriViewer' + iVisibleScouts = iScouts; + case '3DViz' + % Process each scout, to get if it is displayed in this figure + iVisibleScouts = []; + for i = 1:length(iScouts) + % Get handles corresponding to this figure + allhandles = [sScouts(i).Handles]; + iFigHandles = find([allhandles.hFig] == hFig); + % If scout is displayed in this figure, and is VISIBLE in this figure: add it to the visible list + if ~isempty(iFigHandles) && strcmpi(get(sScouts(i).Handles(iFigHandles).hScout, 'Visible'), 'on') + iVisibleScouts = [iVisibleScouts, iScouts(i)]; + end + end + otherwise + iVisibleScouts = []; + end + % Select visible scouts + SetSelectedScouts(iVisibleScouts); +end + + +%% ===== FOCUS CHANGED ====== +function FocusChangedCallback(isFocused) %#ok + if ~isFocused + SetSelectionState(0); + end +end + + +%% ===== SET CURRENT SURFACE ===== +function SetCurrentSurface(newSurfaceFile) + global GlobalData; + % Get previously selected surface + oldSurfaceFile = GlobalData.CurrentScoutsSurface; + % If SurfaceFile did not change did not change : return + if strcmpi(newSurfaceFile, oldSurfaceFile) + return; + end + % Update current surface file + GlobalData.CurrentScoutsSurface = newSurfaceFile; + % Update panel display + UpdatePanel(); +end + +%% ===== UPDATE CURRENT SURFACE ===== +function UpdateCurrentSurface() %#ok + curFig = gui_figuresManager('GetCurrentFigure', '3D'); + CurrentFigureChanged_Callback(curFig, curFig); +end + + +%% ===== GET ALL SCOUTS ===== +function [sScouts, iScouts] = GetScouts(iScouts) + global GlobalData; + if (nargin < 1) + sScouts = GlobalData.Scouts; + iScouts = 1:length(sScouts); + else + sScouts = GlobalData.Scouts(iScouts); + end +end + + +%% ===== GET CURRENT SCOUTS ===== +function [sScouts, iScouts] = GetCurrentScouts() + global GlobalData; + sScouts = []; + iScouts = []; + % If no surface is defined : do nothing + if isempty(GlobalData.CurrentScoutsSurface) + return + end + % If surface is defined : return scouts associated to this surface + [sScouts, iScouts] = GetScoutsWithSurface(GlobalData.CurrentScoutsSurface); +end + + +%% ===== GET SCOUTS WITH SURFACE===== +function [sScouts, iScouts] = GetScoutsWithSurface(SurfaceFile) + global GlobalData; + sScouts = []; + % If surface is defined : return scouts associated to this surface + iScouts = find(io_compareFileNames({GlobalData.Scouts.SurfaceFile}, SurfaceFile)); + if isempty(iScouts) + return + end + sScouts = GlobalData.Scouts(iScouts); +end + + +%% ===== GET SCOUTS WITH FIGURE ===== +function [sScouts, iScouts] = GetScoutsWithFigure(hFig) + global GlobalData; + sScouts = []; + iScouts = []; + if isempty(hFig) + return + end + % If figure is defined : return scouts associated to this figure + for i = 1:length(GlobalData.Scouts) + if ismember(hFig, [GlobalData.Scouts(i).Handles.hFig]) + iScouts = [iScouts, i]; + end + end + sScouts = GlobalData.Scouts(iScouts); +end + + +%% ===== GET SELECTED SCOUTS ===== +% NB: Returned indices are indices in GlobalData.Scouts array +function [sSelScouts, iSelScouts] = GetSelectedScouts() + sSelScouts = []; + iSelScouts = []; + % Get current scouts + [sScouts, iScouts] = GetCurrentScouts(); + if isempty(sScouts) + return + end + % Get "Scouts" panel controls + jListScouts = bst_getContext('PanelElement', 'Scout', 'jListScouts'); + % Get JList selected indices + iSelScouts = uint16(jListScouts.getSelectedIndices())' + 1; + if isempty(iScouts) + return + end + sSelScouts = sScouts(iSelScouts); + iSelScouts = iScouts(iSelScouts); +end + + +%% ===== SET SELECTED SCOUTS ===== +% WARNING: Input indices are references in the GlobalData.Scouts array, not in the JList +function SetSelectedScouts(iSelScouts) + % === GET SCOUT INDICES === + % No selection + if isempty(iSelScouts) || (any(iSelScouts == 0)) + iSelItem = -1; + % Find the selected scouts in the JList + else + [sScouts,iScouts] = GetCurrentScouts(); + iSelItem = []; + for i=1:length(iSelScouts) + iSelItem = [iSelItem, find(iSelScouts(i) == iScouts)]; + end + if isempty(iSelItem) + iSelItem = -1; + else + iSelItem = iSelItem - 1; + end + end + % === CHECK FOR MODIFICATIONS === + % Get 3DViz figure information + jListScouts = bst_getContext('PanelElement', 'Scout', 'jListScouts'); + if isempty(jListScouts) + return + end + % Get previous selection + iPrevItems = jListScouts.getSelectedIndices(); + % If selection did not change: exit + if isequal(iPrevItems, iSelItem) || (isempty(iPrevItems) && isequal(iSelItem, -1)) + return + end + % === UPDATE SELECTION === + % Temporality disables JList selection callback + jListCallback_bak = get(jListScouts, 'ValueChangedCallback'); + set(jListScouts, 'ValueChangedCallback', []); + % Select items in JList + jListScouts.setSelectedIndices(iSelItem); + % Restore JList callback + set(jListScouts, 'ValueChangedCallback', jListCallback_bak); + % Update panel "Scouts" fields + UpdateScoutProperties(); + % Display/hide scouts + if isViewOnlySelectedScouts() + UpdateScoutsDisplay(); + end +end + + + +%% ===== GET SCOUT DISPLAY TYPE ===== +% ScoutsOptions: +% |- function : {'Mean','Max','Power','All'} ? +% |- overlayScouts : {0, 1} +% |- overlayConditions : {0, 1} +% |- isAbsolute : {0, 1} +function ScoutsOptions = GetScoutDisplayType() %#ok + % Get "Scouts" panel controls + ctrl = bst_getContext('PanelControls', 'Scout'); + % Sources time series : MEAN, MAX, ALL + if ctrl.jRadioTypeMean.isSelected() + ScoutsOptions.function = 'Mean'; + elseif ctrl.jRadioTypeMax.isSelected() + ScoutsOptions.function = 'Max'; + elseif ctrl.jRadioTypePower.isSelected() + ScoutsOptions.function = 'Power'; + elseif ctrl.jRadioTypeAll.isSelected() + ScoutsOptions.function = 'All'; + end + % Overlay + ScoutsOptions.overlayScouts = ctrl.jCheckOverlayScouts.isSelected(); + ScoutsOptions.overlayConditions = ctrl.jCheckOverlayConditions.isSelected(); + % Display absolute values ? + ScoutsOptions.isAbsolute = ctrl.jRadioValuesAbsolute.isSelected(); +end + + +%% ===== CREATE NEW SCOUT ===== +function [sScout, iScout] = CreateNewScout(SurfaceFile, newVertices, newSeed) + global GlobalData; + % == NEW SCOUT == + % New scout structure + sScout = db_getDataTemplate('Scout'); + iScout = length(GlobalData.Scouts) + 1; + % Store current scout coordinates + sScout.SurfaceFile = SurfaceFile; + sScout.Vertices = newVertices; + sScout.Seed = newSeed; + + % == SCOUT LABEL == + % Get other scouts with same surface file + sOtherScouts = GetScoutsWithSurface(SurfaceFile); + % Define scouts labels (Label=index) + iDisplayIndice = length(sOtherScouts) + 1; + scoutLabel = int2str(iDisplayIndice); + % Check that the scout name does not exist yet (else, add a ') + if ~isempty(sOtherScouts) + while ismember(scoutLabel, {sOtherScouts.Label}) + scoutLabel = [scoutLabel, '''']; + end + end + sScout.Label = scoutLabel; + + % == SCOUT COLOR == + ColorTable = GetScoutsColorTable(); + iColor = mod(iDisplayIndice-1, length(ColorTable)) + 1; + sScout.Color = ColorTable(iColor,:); + % == Register new scout == + GlobalData.Scouts(iScout) = sScout; +end + + +%% ===== GET SCOUTS COLOR TABLE ===== +function ColorTable = GetScoutsColorTable() + ColorTable = [0 1 0 ; + 1 0 0 ; + .4 .4 1 ; + 1 .694 .392; + 0 1 1 ; + 1 0 1 ; + .4 0 0 ; + 0 .5 0]; +end + + +%% ===== VIEW SCOUT ===== +function DisplayScouts(varargin) + % Stop scout editing + SetSelectionState(0); + % Get selected scouts + [sSelScouts, iSelScouts] = GetSelectedScouts(); + % Warning message if no scout selected + if isempty(sSelScouts) + java_dialog('warning', 'No scout selected.', 'Display time series'); + return; + end + % Display scouts + gui_viewResultsScouts(); +end + + +%% =============================================================================== +% ====== POINTS SELECTION ======================================================= +% =============================================================================== +%% ===== SCOUT SELECTION : start/stop ===== +% Manual selection of a cortical spot : start(1), or stop(0) +function SetSelectionState(isSelected) + % Get "Scouts" panel controls + ctrl = bst_getContext('PanelControls', 'Scout'); + if isempty(ctrl) + return + end + % Get list of figures where it is possible to select a scout + hFigures = gui_figuresManager('GetFiguresForScouts'); + % No figure available + if isempty(hFigures) + if isSelected + java_dialog('warning', 'No 3D figure with sources available.', 'Select a cortical spot'); + end + % Release toolbar "AddScout" button + ctrl.jButtonAddScout.setSelected(0); + return + end + % Start scout selection + if isSelected + % Push toolbar "AddScout" button + ctrl.jButtonAddScout.setSelected(1); + % Unselect all the scouts in JList + SetSelectedScouts([]); + % Set 3DViz figures in 'SelectingCorticalSpot' mode + for hFig = hFigures + setappdata(hFig, 'isSelectingCorticalSpot', 1); + set(hFig, 'Pointer', 'cross'); + end + % Stop scout selection + else + % Release toolbar "AddScout" button + ctrl.jButtonAddScout.setSelected(0); + % Exit 3DViz figures from SelectingCorticalSpot mode + for hFig = hFigures + set(hFig, 'Pointer', 'arrow'); + setappdata(hFig, 'isSelectingCorticalSpot', 0); + end + end +end + + + +%% ===== SCOUT SELECTION : Selection performed ===== +% Usage : SelectCorticalSpot(hFig) : Scout location = user click in figure hFIg +% If only one scout is selected: add the selected point to the selected scout +% Else: Create a new scout +function SelectCorticalSpot(hFig) %#ok + global GlobalData; + % Get cortex and anatomy surface handle + [iCortex, TessInfo] = panel_surface('GetSurfaceCortex', hFig); + [sMri, TessInfo, iAnatomy] = panel_surface('GetSurfaceMri', hFig); + + + % === POINT SELECTION ON CORTEX === + if ~isempty(iCortex) + hSurface = TessInfo(iCortex).hPatch; + % Get mouse 3D selection + [pout vout vi] = select3d(hSurface); + % === POINT SELECTION ON MRI === + elseif ~isempty(iAnatomy) && ~isempty(TessInfo.DataSource.FileName) + % Get vertices + sSubject = bst_getContext('MriFile', sMri.FileName); + % If there is no cortex for this subject: exit + if isempty(sSubject.iCortex) + return + end + CortexFile = sSubject.Surface(sSubject.iCortex).FileName; + sSurfCortex = bst_dataSetsManager('LoadSurface', CortexFile); + + % Select a point in the MRI slices + [TessInfo, iTess, pout, vout, vi] = panel_coordinates('ClickPointInSurface', hFig, 'Anatomy'); + % Find the closest cortical point from this MRI coordinates + dist = (sSurfCortex.Vertices(:,1) - pout(1)) .^ 2 + ... + (sSurfCortex.Vertices(:,2) - pout(2)) .^ 2 + ... + (sSurfCortex.Vertices(:,3) - pout(3)) .^ 2; + [minDist, iMinDist] = min(dist); + % If selected point is too far away from cortical surface : return + if (sqrt(minDist) > 0.01) + return + end + % Select the closest point + vout = sSurfCortex.Vertices(iMinDist, :)'; + vi = iMinDist; + else + return; + end + % Check that a point was selected + if isempty(vout) + return + end + + % === CREATING SCOUT OR ADDING POINTS === + % Get selected scouts + [sSelScouts, iSelScouts] = GetSelectedScouts(); + % If there is more that one selected scout: select only the first one + if (length(iSelScouts) > 1) + SetSelectedScouts(iSelScouts(1)); + sSelScouts = sSelScouts(1); + iSelScouts = iSelScouts(1); + end + isNewScout = (length(sSelScouts) ~= 1); + + % ==== CHECK UNICITY OF VERTICES ==== + % Get current scouts + sScouts = GetCurrentScouts(); + if ~isempty(sScouts) + % Check unicity + scoutsVertices = [sScouts.Vertices]; + % If vertex was already set as a scout : return + if ismember(vi, scoutsVertices) + return + end + end + + % ==== NEW SCOUT ==== + if isNewScout + % Create new scout + [sScout, iScout] = CreateNewScout(GlobalData.CurrentScoutsSurface, vi, vi); + % ==== ADD POINT TO SELECTED SCOUT ==== + else + % Use selected scout + iScout = iSelScouts; + % Add clicked vertex + GlobalData.Scouts(iScout).Vertices = [GlobalData.Scouts(iScout).Vertices, vi]; + end + + % === UPDATE INTERFACE === + % Display scout patch + PlotScout(iScout); + % Update "Scouts" panel + UpdatePanel(); + % Select last scout in list + SetSelectedScouts(iScout); + % Deselect "AddScout" button + % (Only if creating new scout. If adding points to existing scouts, keep adding points) + if isNewScout + SetSelectionState(0); + end + % OverlayCube for 3D MRI display is not updated => Need to update it + UpdateScoutsDisplay(); +end + + +%% =============================================================================== +% ====== SCOUTS CREATION/EDITION ================================================ +% =============================================================================== +%% ===== START NEW SCOUT / SURFACE ===== +function StartNewScoutSurface() + % Start edition of a new scout + SetSelectionState(1); +end + +%% ===== START NEW SCOUT / MRI ===== +function StartNewScoutMri() + % Open MRI scout editor (for creation) + ScoutEditorInMri(-1); +end + +%% ===== START NEW SCOUT / MAX ===== +function StartNewScoutMax() + SelectMaximumValue(); +end + +%% ===== SURFACE TILING ===== +function SurfaceTiling() + nClust = 30; %Number of surface tiles (clusters); % CBB: Prompt user + VERBOSE = 1; % Turn on/off verbose of tiling process + + % Get cortex and anatomy surface handle + [iSurf, TessInfo] = panel_surface('GetSurfaceCortexOrAnatomy'); %CBB: works with only one 3D figure being displayed + infoProtocole = bst_getContext('ProtocolInfo'); + load(fullfile(infoProtocole.SUBJECTS,TessInfo.SurfaceFile),'VertConn'); + [CLASS, NumClass, nbre_zone] = cortex_cluster(VertConn,nClust,1:size(VertConn,1),1,VERBOSE); + + global GlobalData; + GlobalData = rmfield(GlobalData,'Scouts'); + for iNewScout=1:nbre_zone + GlobalData.Scouts(iNewScout) = db_getDataTemplate('Scout'); + GlobalData.Scouts(iNewScout).Vertices = find(CLASS{1}==iNewScout); + GlobalData.Scouts(iNewScout).Seed = GlobalData.Scouts(iNewScout).Vertices(1); + GlobalData.Scouts(iNewScout).SurfaceFile = TessInfo.SurfaceFile; + GlobalData.Scouts(iNewScout).Label = num2str(iNewScout); + GlobalData.Scouts(iNewScout).Color = rand(1,3); + PlotScout(iNewScout) + end + + UpdateScoutsDisplay(); +end + + +%% ===== EDIT EXISTING SCOUT / SURFACE ===== +function EditExistingScoutSurface() + % Get selected scouts + [sSelScouts, iSelScouts] = GetSelectedScouts(); + % Warning message if no scout selected + if isempty(sSelScouts) + java_dialog('warning', 'No scout selected.', 'Edit existing scout'); + return; + end + % Start edition of a scout (will deselect the scout) + SetSelectionState(1); + % Select again the first selected scout + SetSelectedScouts(iSelScouts(1)); +end + +%% ===== EDIT EXISTING SCOUT / MRI ===== +function EditExistingScoutMri() + % Get selected scouts + [sScout, iScout] = GetSelectedScouts(); + % Warning message if no scout selected + if isempty(sScout) + java_dialog('warning', 'No scout selected.', 'Edit existing scout'); + return; + % If more than one scout selected: keep only the first one + elseif (length(sScout) > 1) + sScout = sScout(1); + iScout = iScout(1); + SetSelectedScouts(iScout); + end + % Open MRI scout editor (for edition) + ScoutEditorInMri(iScout); +end + +%% ===== EDIT EXISTING SCOUT / MAX ===== +function EditExistingScoutMax() + % Get selected scouts + [sScouts, iScouts] = GetSelectedScouts(); + % Warning message if no scout selected + if isempty(sScouts) + java_dialog('warning', 'No scout selected.', 'Find maximum value'); + return; + end + % Process all the selected scouts + for i = 1:length(iScouts) + SelectMaximumValue(iScouts(i)); + end +end + + +%% =============================================================================== +% ====== SCOUTS OPERATIONS ====================================================== +% =============================================================================== +%% ===== PLOT ALL SCOUTS FOR CURRENT FIGURE ===== +function PlotAllScouts() %#ok + [tmp__, iScouts] = GetCurrentScouts(); + for i=iScouts + PlotScout(i); + end + UpdateScoutsDisplay(); +end + + +%% ===== DISPLAY SCOUT ===== +% Find all the figures where these scouts should be displayed, and plot them. +function PlotScout(iScout) + global GlobalData; + sScout = GlobalData.Scouts(iScout); + % Get cortex file + SurfaceFiles{1} = sScout.SurfaceFile; + % Get anatomy file + [sSubject, iSubject] = bst_getContext('SurfaceFile', SurfaceFiles{1}); + if ~isempty(sSubject) && ~isempty(sSubject.iAnatomy) + SurfaceFiles{2} = sSubject.Anatomy(sSubject.iAnatomy).FileName; + end + % Get all the figures concerned with Scout cortex and MRI surface + [hFigures, iFigures, iDataSets, iSurfaces] = gui_figuresManager('GetFigureWithSurface', SurfaceFiles); + if isempty(hFigures) + return + end + % Get Surface definition + TessInfo = getappdata(hFigures(1), 'Surface'); + iSurface = iSurfaces(1); + sSurface = TessInfo(iSurface); + % Get Faces and Vertices list of target surface + if strcmpi(sSurface.Name, 'Anatomy') + sDbCortex = bst_getContext('SurfaceFileByType', iSubject, 'Cortex'); + sSurfCortex = bst_dataSetsManager('LoadSurface', sDbCortex.FileName); + Faces = sSurfCortex.Faces; + Vertices = sSurfCortex.Vertices; + else + Faces = get(sSurface.hPatch, 'Faces'); + Vertices = get(sSurface.hPatch, 'Vertices'); + end + + % Process all figures + for hFig = hFigures + % Get indice of the target figure in the sScout.Handles array + iHnd = find([sScout.Handles.hFig] == hFig); + % If figure is not referenced yet : add it + if isempty(iHnd) + iHnd = length(sScout.Handles) + 1; + sScout.Handles(iHnd).hFig = hFig; + end + % Get axes handles + hAxes = findobj(hFig, 'tag', 'Axes3D'); + + % === SCOUT 3D MARKER === + % Force scout location to be XYZ because scouts may be dispatched on a smoothed surface, + % i.e. with surface vertices being away from true locations + MarkerLocation = double(Vertices(sScout.Seed, :)); + % Plot scout marker (if it does not exist yet) + if isempty(sScout.Handles(iHnd).hScout) || ~ishandle(sScout.Handles(iHnd).hScout) + sScout.Handles(iHnd).hScout = line(MarkerLocation(1), MarkerLocation(2), MarkerLocation(3), ... + 'Marker', 'o', ... + 'MarkerFaceColor', sScout.Color, ... + 'MarkerEdgeColor', sScout.Color, ... + 'MarkerSize', 5, ... + 'Tag', 'ScoutMarker', ... + 'Parent', hAxes); + % If scout marker already exist, just update its position + else + set(sScout.Handles(iHnd).hScout, 'XData', MarkerLocation(1), ... + 'YData', MarkerLocation(2), ... + 'ZData', MarkerLocation(3)); + end + % Plot scout label (if it does not exist yet) + if isempty(sScout.Handles(iHnd).hLabel) || ~ishandle(sScout.Handles(iHnd).hLabel) + try + sScout.Handles(iHnd).hLabel = text(1.1*MarkerLocation(1), 1.1*MarkerLocation(2), 1.1*MarkerLocation(3), ... + sScout.Label, ... + 'Fontname', 'helvetica', ... + 'FontUnits', 'Point', ... + 'FontSize', 10, ... + 'FontWeight', 'normal', ... + 'Color', [.9 1 .9], ... + 'HorizontalAlignment', 'center', ... + 'Tag', 'ScoutLabel', ... + 'Parent', hAxes, ... + 'Interpreter','none'); + catch + warning('Brainstorm:GraphicsError', 'Unknown error: could not display scout label.'); + end + % If label is already displayed: just update its position + else + set(sScout.Handles(iHnd).hLabel, 'Position', 1.1 .* MarkerLocation); + end + + % ===== SCOUT PATCH ===== + % === BUILD FACES/VERTICES === + % If there are more than one vertex available for the scout + if (length(sScout.Vertices) > 1) + % Get patch vertices + patchVertices = Vertices(sScout.Vertices, :) * 1.00001; + % Create a look-up table for the vertices indices + vertTable = zeros(size(Vertices, 1), 1); + vertTable(sScout.Vertices) = 1:length(sScout.Vertices); + % Replace the vertices indices in the Faces list + patchFaces = reshape(interp1(vertTable, Faces(:), 'nearest'), [], 3); + iPatchFaces = (sum(patchFaces>0, 2) == 3); + + patchFaces = patchFaces(iPatchFaces, :); +% % Compute scout area +% sScout.Area = sum(sSurface.TriArea(iPatchFaces)); + sScout.Area = 0; + else + patchFaces = []; + patchVertices = []; + sScout.Area = 0; + end + + % === DRAW VERTICES MARKERS === + if ~isempty(patchVertices) + % Plot scout vertices (if graphic object does not exist yet) + if isempty(sScout.Handles(iHnd).hVertices) || ~ishandle(sScout.Handles(iHnd).hVertices) + sScout.Handles(iHnd).hVertices = line(patchVertices(:,1), patchVertices(:,2), patchVertices(:,3), ... + 'Marker', '.', ... + 'MarkerFaceColor', sScout.Color, ... + 'MarkerEdgeColor', sScout.Color, ... + 'MarkerSize', 5, ... + 'LineStyle', 'none', ... + 'Tag', 'ScoutMarker', ... + 'Parent', hAxes); + else + set(sScout.Handles(iHnd).hVertices, 'XData', patchVertices(:,1), ... + 'YData', patchVertices(:,2), ... + 'ZData', patchVertices(:,3)); + end + else + delete(sScout.Handles(iHnd).hVertices); + sScout.Handles(iHnd).hVertices = []; + end + + % === DRAW PATCH === + % If a patch is available (enough faces and vertices) + if ~isempty(patchFaces) && ~isempty(patchVertices) + % If patch does not exist yet : create it + if isempty(sScout.Handles(iHnd).hPatch) || ~ishandle(sScout.Handles(iHnd).hPatch) + sScout.Handles(iHnd).hPatch = patch('Faces', patchFaces, ... + 'Vertices', patchVertices, ... + 'FaceVertexCData', sScout.Color, ... + 'FaceColor', sScout.Color, ... + 'EdgeColor', sScout.Color,... + 'FaceAlpha', .3, ... + 'BackFaceLighting','lit', ... + 'Tag', 'ScoutPatch', ... + 'Parent', hAxes) ; + % Else : only update vertices and faces + else + set(sScout.Handles(iHnd).hPatch, 'Faces', patchFaces, 'Vertices', patchVertices); + end + % Else : Remove previous scout patch, if it existed + elseif ishandle(sScout.Handles(iHnd).hPatch) + delete(sScout.Handles(iHnd).hPatch); + sScout.Handles(iHnd).hPatch = []; + end + + % === ALSO UPDATE 3D MRI SLICES === +% gui_figure3DViz('UpdateOverlayCube', hFig, iSurface); + end + % Update scout defintion + GlobalData.Scouts(iScout) = sScout; +end + + +%% ===== REMOVE SCOUTS FROM FIGURE ===== +function RemoveScoutsFromFigure(hFig) %#ok + global GlobalData; + % If removing scouts from a given figure + for iScout = 1:length(GlobalData.Scouts) + iHandles = 1; + while (iHandles <= length(GlobalData.Scouts(iScout).Handles)) + if (GlobalData.Scouts(iScout).Handles(iHandles).hFig == hFig) + GlobalData.Scouts(iScout).Handles(iHandles) = []; + else + iHandles = iHandles + 1; + end + end + end +end + +%% ===== REMOVE SCOUTS ===== +% Usage : RemoveScouts(iScouts) : remove a list of scouts +% RemoveScouts() : remove the scouts selected in the JList +function RemoveScouts(varargin) + global GlobalData; + % Stop scout edition + SetSelectionState(0); + % If scouts list is not defined + if (nargin == 0) + % Get selected scouts + [sScouts, iScouts] = GetSelectedScouts(); + % Check whether a scout is selected + if isempty(sScouts) +% java_dialog('warning', 'No scout selected.', 'Remove scout'); + return + end + elseif (nargin == 1) + iScouts = varargin{1}; + sScouts = GlobalData.Scouts(iScouts); + else + error('Invalid call to RemoveScouts.'); + end + hAllFig = []; + % Delete graphical objects + for i = 1:length(sScouts) + hAllFig = [hAllFig, [sScouts(i).Handles.hFig]]; + % Delete graphical scout markers + hMarkers = [sScouts(i).Handles.hScout]; + delete(hMarkers(ishandle(hMarkers))); + % Delete graphical scout labels + hLabels = [sScouts(i).Handles.hLabel]; + delete(hLabels(ishandle(hLabels))); + % Delete graphical scout patches + hPatches = [sScouts(i).Handles.hPatch]; + delete(hPatches(ishandle(hPatches))); + % Delete graphical scout vertices + hVertices = [sScouts(i).Handles.hVertices]; + delete(hVertices(ishandle(hVertices))); + end + + % Remove scouts definitions from global data structure + GlobalData.Scouts(iScouts) = []; + % Update "Scouts Manager" panel + UpdateScoutsList(); + % Update MRI display in all figures + panel_surface('UpdateOverlayCubes', unique(hAllFig)); +end + + +%% ===== REMOVE SCOUTS VERTICES FROM SURFACE ===== +function RemoveScoutFromSurface() + % === GET VERTICES TO REMOVE === + % Get selected scouts + [sScouts, iScouts] = GetSelectedScouts(); + % Check whether a scout is selected + if isempty(sScouts) + java_dialog('warning', 'No scout selected.', 'Remove scout'); + return + end + % Ask for user confirmation + isConfirmed = java_dialog('confirm', ['Warning: This operation is going to alter permanently the surface.', 10 ... + 'If you have some results based on this surface, they will be unusable.' 10 10 ... + 'Remove vertices ?'], 'Remove scout vertices'); + if ~isConfirmed + return + end + % Join scouts to get vertices to remove + iRemoveVert = [sScouts.Vertices]; + % Get cortex file + SurfaceFile = sScouts(1).SurfaceFile; + + % === DELETE ALL SCOUTS === + % Get all available scouts for this surface + [sCurScouts, iCurScouts] = GetCurrentScouts(); + % Remove scouts + RemoveScouts(iCurScouts); + + % === REMOVE VERTICES FROM SURFACE FILE === + % Get full surface file path + ProtocolInfo = bst_getContext('ProtocolInfo'); + SurfaceFileFull = fullfile(ProtocolInfo.SUBJECTS, SurfaceFile); + % Load surface file + SurfaceMat = in_tess_bst(SurfaceFileFull); + % Remove vertices + [Vertices, Faces] = tess_removeVertices(SurfaceMat.Vertices, SurfaceMat.Faces, iRemoveVert); + % Build new surface + newSurfaceMat.Comment = SurfaceMat.Comment; + newSurfaceMat.Vertices = Vertices; + newSurfaceMat.Faces = Faces; + % Save file back + save(SurfaceFileFull, '-struct', 'newSurfaceMat'); + % Unload surface file + bst_dataSetsManager('UnloadSurface', SurfaceFile); + + % === REMOVE VERTICES FROM FIGURES === + % Get all the figures concerned with Scout cortex and MRI surface + [hFigures, iFigures, iDataSets, iSurfaces] = gui_figuresManager('GetFigureWithSurface', SurfaceFile); + if isempty(hFigures) + return + end + % Process all the figures + for i = 1:length(hFigures) + % Get surface definition + TessInfo = getappdata(hFigures(i), 'Surface'); + surfaceFile = TessInfo(iSurfaces(i)).SurfaceFile; + % Unload surface and load it again + panel_surface('RemoveSurface', hFigures(i), iSurfaces(i)); + panel_surface('AddSurface', hFigures(i), surfaceFile); + end +end + +%% ===== REMOVE UNSUSED SCOUTS ===== +function RemoveUnusedScouts() %#ok + global GlobalData; + % Get list of all the subjects currently usefull + UsefulSubjects = unique({GlobalData.DataSet.SubjectFile}); + % For each subject get the list list of useful surfaces + UsefulSurfaces = {}; + for i = 1:length(UsefulSubjects) + sSubject = bst_getContext('Subject', UsefulSubjects{i}); + if ~isempty(sSubject) + UsefulSurfaces = cat(2, UsefulSurfaces, {sSubject.Surface.FileName}); + end + end + UsefulSurfaces = unique(UsefulSurfaces); + + % For each scout, check if associated cortex file is in the UsefulSurfaces array + iScoutsToRemove = []; + for iScout = 1:length(GlobalData.Scouts) + if ~any(io_compareFileNames(GlobalData.Scouts(iScout).SurfaceFile, UsefulSurfaces)) + iScoutsToRemove(end + 1) = iScout; + end + end + % Remove useless scouts + if ~isempty(iScoutsToRemove) + RemoveScouts(iScoutsToRemove); + end +end + + +%% ===== JOIN SCOUTS ===== +% Join the scouts selected in the JList +function JoinScouts() + global GlobalData; + % Stop scout edition + SetSelectionState(0); + % Get selected scouts + [sScouts, iScouts] = GetSelectedScouts(); + % Need TWO scouts + if (length(sScouts) < 2) + java_dialog('warning', 'You need to select at least two scouts.', 'Join selected scouts'); + return; + end + + % === Remove old scouts === + RemoveScouts(iScouts); + % === Join scouts === + % Create new scout + newScout = db_getDataTemplate('Scout'); + % Copy unmodified fields + newScout.SurfaceFile = sScouts(1).SurfaceFile; + newScout.Seed = sScouts(1).Seed; + % Vertices : concatenation + newScout.Vertices = unique([sScouts.Vertices]); + % Label : "Label1 & Label2 & ..." + newScout.Label = sScouts(1).Label; + for i = 2:length(sScouts) + newScout.Label = [newScout.Label ' & ' sScouts(i).Label]; + end + + % Add new scout to global structure + iNewScout = length(GlobalData.Scouts) + 1; + GlobalData.Scouts(iNewScout) = newScout; + % Display new scout + PlotScout(iNewScout); + % Update "Scouts Manager" panel + UpdateScoutsList(); + % Select last scout in list (new scout) + SetSelectedScouts(iNewScout); +end + + +%% ===== EDIT SCOUT LABEL ===== +% Rename one and only one selected scout +function EditScoutLabel() + global GlobalData; + % Stop scout edition + SetSelectionState(0); + % Get selected scouts + [sScout, iScout] = GetSelectedScouts(); + % Warning message if no scout selected + if isempty(sScout) + java_dialog('warning', 'No scout selected.', 'Rename selected scout'); + return; + % If more than one scout selected: keep only the first one + elseif (length(sScout) > 1) + iScout = iScout(1); + sScout = sScout(1); + SetSelectedScouts(iScout); + end + % Ask user for a new Scout Label + newLabel = java_dialog('input', sprintf('Please enter a new label for scout "%s":', sScout.Label), ... + 'Rename selected scout', [], sScout.Label); + if isempty(newLabel) || strcmpi(newLabel, sScout.Label) + return + end + % Update Scout definition + GlobalData.Scouts(iScout).Label = newLabel; + % Update graphical objects + set([sScout.Handles.hLabel], 'String', newLabel); + % Update JList + UpdateScoutsList(); + % Select back selected scout + SetSelectedScouts(iScout); +end + + +%% ===== EDIT SCOUTS SIZE ===== +function EditScoutsSize(action) + global GlobalData mutexGrowScout; + % Stop scouts edition + SetSelectionState(0); + % Get "Scouts" panel controls + ctrl = bst_getContext('PanelControls', 'Scout'); + % Use a mutex to prevent the function from being executed more than once at the same time + if isempty(mutexGrowScout) || (mutexGrowScout > 1) + % Entrance accepted + tic + mutexGrowScout = 0; + else + % Entrance rejected (another call is not finished,and was call less than 1 seconds ago) + mutexGrowScout = toc; + disp('Call to EditScoutsSize ignored...'); + return + end + + % Get selected scouts + [sScouts, iSelScouts] = GetSelectedScouts(); + % Can grow only scouts that are DISPLAYED IN AT LEAST ONE FIGURE + if isempty(sScouts) || isempty(sScouts(1).Handles) + return + end + % Get all current scouts + [sScouts, iAllScouts] = GetCurrentScouts(); + % Get figure + hFig = sScouts(1).Handles(1).hFig; + % Get cortex and anatomy surface handle + [iSurf, TessInfo] = panel_surface('GetSurfaceCortexOrAnatomy', hFig); + + % Process all the selected scouts + for iScout = iSelScouts + % Get cortex vertices + sDbCortex = bst_getContext('SurfaceFileByType', TessInfo(iSurf).SurfaceFile, 'Cortex'); + sSurfCortex = bst_dataSetsManager('LoadSurface', sDbCortex.FileName); + % Get cortex patch vertices + if strcmpi(TessInfo(iSurf).Name, 'Anatomy') + patchVertices = sSurfCortex.Vertices; + else + patchVertices = get(TessInfo(iSurf).hPatch, 'Vertices'); + end + % Get vertices of the scout (indices) + vi = GlobalData.Scouts(iScout).Vertices; + % Get coordinates of the seed + seedXYZ = patchVertices(GlobalData.Scouts(iScout).Seed, :); + % If constrained growth + isContrained = ctrl.jToggleConstrained.isSelected(); + % Get vertices with values below the data threshold + iUnderThresh = []; + if isContrained + if ~isempty( TessInfo(iSurf).Data ) + % iUnderThresh = find(abs(TessInfo(iSurf).Data) <= TessInfo(iSurf).DataThreshold * max(abs(TessInfo(iSurf).Data))); + iUnderThresh = find(abs(TessInfo(iSurf).Data) <= TessInfo(iSurf).DataIntThreshold * max(abs(TessInfo(iSurf).DataMinMax))); + % knd: to do: include DataExtThreshold + %cluster_threshold + iUnderThresh = [iUnderThresh ; ]; + + end + end + + % Now grow/shrink a patch around the selected probe by adding/removing a ring neighbors + switch (action) + case 'Grow' + viNew = patch_swell(vi, sSurfCortex.VertConn); + % Remove vertices under the threshold + viNew = setdiff(viNew, iUnderThresh); + if ~isempty(viNew) + % Get vertices of the scout (coordinates) + vXYZ = patchVertices(viNew, :); + % Compute the distance from each point to the seed + distFromSeed = sqrt((vXYZ(:,1)-seedXYZ(1)).^2 + (vXYZ(:,2)-seedXYZ(2)).^2 + (vXYZ(:,3)-seedXYZ(3)).^2); + % === LIMIT GROWTH WITH A SPHERE === + % Radius of the sphere = mean(dist(v, seed)) + 1.5*std + sphereRadius = mean(distFromSeed) + 1.5 * std(distFromSeed); + % Keep only vertices in a sphere around the Scout seed + vi = union(vi, viNew(distFromSeed <= sphereRadius)); + end + case 'Grow1' + % Get closest neighbours + viNew = setdiff(patch_swell(vi, sSurfCortex.VertConn), vi); + % Remove vertices under the threshold + viNew = setdiff(viNew, iUnderThresh); + if ~isempty(viNew) + % Get new vertices of the scout (coordinates) + vXYZ = patchVertices(viNew, :); + % Compute the distance from each point to the seed + distFromSeed = sqrt((vXYZ(:,1)-seedXYZ(1)).^2 + (vXYZ(:,2)-seedXYZ(2)).^2 + (vXYZ(:,3)-seedXYZ(3)).^2); + % === ADD ONLY THE CLOSEST VERTEX === + % Get the minimum distance + [minVal, iMin] = min(distFromSeed); + iMin = iMin(1); + % Add this vertex to scout vertices + vi = union(vi, viNew(iMin)); + end + + case 'Shrink1' + % Remove a layer of connected vertices + Expanded = patch_swell(vi, sSurfCortex.VertConn); + viToRemove = patch_swell(Expanded, sSurfCortex.VertConn); + viToRemove = intersect(viToRemove, vi); + % Get vertices of the scout (coordinates) + vXYZ = patchVertices(viToRemove, :); + % Compute the distance from each point to the seed + distFromSeed = sqrt((vXYZ(:,1)-seedXYZ(1)).^2 + (vXYZ(:,2)-seedXYZ(2)).^2 + (vXYZ(:,3)-seedXYZ(3)).^2); + % === REMOVE ONLY THE FAREST VERTEX === + % Get the maximum distance + [maxVal, iMax] = max(distFromSeed); + iMax = iMax(1); + % Remove this vertex from the scout vertices + vi = setdiff(vi, viToRemove(iMax)); + + case 'Shrink' + % Remove a layer of connected vertices + Expanded = patch_swell(vi, sSurfCortex.VertConn); + viToRemove = patch_swell(Expanded, sSurfCortex.VertConn); + % Get vertices of the scout (coordinates) + vXYZ = patchVertices(vi, :); + % Compute the distance from each point to the seed + distFromSeed = sqrt((vXYZ(:,1)-seedXYZ(1)).^2 + (vXYZ(:,2)-seedXYZ(2)).^2 + (vXYZ(:,3)-seedXYZ(3)).^2); + % === DEFINE SHRINK WITH A SPHERE === + % Radius of the sphere = mean(dist(v, seed)) + 1.5*std + sphereRadius = mean(distFromSeed); + % Keep only vertices in a sphere around the Scout seed + viOutsideSphere = vi(distFromSeed > sphereRadius); + % Remove only vertices that are removed by the two methods + viToRemove = intersect(viToRemove, viOutsideSphere); + vi = setdiff(vi, viToRemove); + end + + % Remove vertices that are already in other scouts + iOtherScouts = setdiff(iAllScouts, iScout); + vi = setdiff(vi, [GlobalData.Scouts(iOtherScouts).Vertices]); + + % Save new list of vertices + GlobalData.Scouts(iScout).Vertices = vi; + % If all the vertices were removed, keep initial scout vertex + if isempty(GlobalData.Scouts(iScout).Vertices) + GlobalData.Scouts(iScout).Vertices = GlobalData.Scouts(iScout).Seed; + end + % Display scout patch + PlotScout(iScout); + end + + % Release mutex + mutexGrowScout = []; + % Update panel "Scouts" fields + UpdateScoutProperties(); + % Display/hide scouts and update MRI overlay mask + UpdateScoutsDisplay(); +end + + +%% ===== EDIT SCOUTS COLOR ===== +function EditScoutsColor(newColor) + global GlobalData; + % Get selected scouts + [sSelScouts, iSelScouts] = GetSelectedScouts(); + if isempty(iSelScouts) + java_dialog('warning', 'No scout selected.', 'Edit scout color'); + return + end + % If color is not specified in argument : ask it to user + if (nargin < 1) + % Use previous scout color + newColor = uisetcolor(sSelScouts(1).Color, 'Select scout color'); + % If no color was selected: exit + if (length(newColor) ~= 3) || all(sSelScouts(1).Color == newColor) + return + end + end + % Update scouts color + for i = 1:length(iSelScouts) + GlobalData.Scouts(iSelScouts(i)).Color = newColor; + % Update color for all graphical instances + % Seed + set([sSelScouts(i).Handles.hScout], 'MarkerFaceColor', newColor, ... + 'MarkerEdgeColor', newColor); + % Patch + set([sSelScouts(i).Handles.hPatch], 'FaceVertexCData', newColor, ... + 'FaceColor', newColor, ... + 'EdgeColor', newColor); + % Vertices + set([sSelScouts(i).Handles.hVertices], 'MarkerFaceColor', newColor, ... + 'MarkerEdgeColor', newColor); + end +end + +%% ===== REDRAW SCOUTS ===== +% Update vertices of scouts for a given surface +function UpdateScoutsVertices(SurfaceFile) %#ok + global GlobalData; + % Get scouts to update + iScoutsToUpdate = find(io_compareFileNames({GlobalData.Scouts.SurfaceFile}, SurfaceFile) & ~cellfun(@isempty, {GlobalData.Scouts.Handles})); + if isempty(iScoutsToUpdate) || isempty(GlobalData.Scouts(iScoutsToUpdate(1)).Handles) + return; + end + % Get surface information in figure appdata + TessInfo = getappdata(GlobalData.Scouts(iScoutsToUpdate(1)).Handles(1).hFig, 'Surface'); + iSurface = find(io_compareFileNames({TessInfo.SurfaceFile}, SurfaceFile)); + if isempty(iSurface) + return; + end + % Get displayed vertices of surface + Vertices = get(TessInfo(iSurface).hPatch, 'Vertices'); + + % Update vertices of all scouts + for iScout = iScoutsToUpdate + sScout = GlobalData.Scouts(iScout); + % Update vertices for all graphical instances + for ihand = 1:length(sScout.Handles) + % Scout seed + set(sScout.Handles(ihand).hScout, 'XData', Vertices(sScout.Seed, 1), ... + 'YData', Vertices(sScout.Seed, 2), ... + 'ZData', Vertices(sScout.Seed, 3)); + % Scout vertices + set(sScout.Handles(ihand).hVertices, 'XData', Vertices(sScout.Vertices, 1), ... + 'YData', Vertices(sScout.Vertices, 2), ... + 'ZData', Vertices(sScout.Vertices, 3)); + % Scout label + set(sScout.Handles(ihand).hLabel, 'Position', 1.1 * Vertices(sScout.Seed, :)); + % Scout patch + set(sScout.Handles(ihand).hPatch, 'Vertices', Vertices(sScout.Vertices, :)); + end + end +end + + +%% ===== UPDATE SCOUTS DISPLAY ===== +% Display/hide scouts. +function UpdateScoutsDisplay() + % Get current scouts + [sScouts, iScouts] = GetCurrentScouts(); + % Get "Scouts" panel controls + ctrl = bst_getContext('PanelControls', 'Scout'); + % View mode : VIEW SELECTED SCOUTS + if isViewOnlySelectedScouts() + % Get selected scouts + [sSelScouts, iSelScouts] = GetSelectedScouts(); + % Get JList selected indices + iVisibleScouts = []; + for i = 1:length(iSelScouts) + iVisibleScouts = [iVisibleScouts, find(iScouts == iSelScouts(i))]; + end +% iVisibleScouts = iSelScouts; +% iHiddenScouts = setdiff(1:length(iScouts), iVisibleScouts); + % View mode : VIEW ALL SCOUTS + else + % All scouts are visible + iVisibleScouts = 1:length(iScouts); +% iHiddenScouts = []; + end + hAllFig = []; + % Check if + isDisplayIfOnlyAnatomy = ctrl.jCheckViewPatchesInMri.isSelected(); + % Loop on all scouts + for i = 1:length(iScouts) + % Is this scout supposed to be visible + isVisibleGlobal = ismember(i, iVisibleScouts); + % Process each figure in which this scout is accessible + for iFig = 1:length(sScouts(i).Handles) + hFig = sScouts(i).Handles(iFig).hFig; + isVisibleLocal = isVisibleGlobal; + % === IS SCOUT VISIBLE ? === + % If scout is visible according to selection, but should be hidden if there is only an anatomy surface + if isVisibleGlobal && ~isDisplayIfOnlyAnatomy + % Find cortex and anatomy surfaces + iCortex = panel_surface('GetSurfaceCortex', hFig); + iAnatomy = panel_surface('GetSurfaceMri', hFig); + % If only anatomy is accessible : hide scout + if isempty(iCortex) && ~isempty(iAnatomy) + isVisibleLocal = 0; + end + end + % === SET THE VISIBILITIES === + hGlobal = [sScouts(i).Handles(iFig).hScout, sScouts(i).Handles(iFig).hLabel]; + hLocal = [sScouts(i).Handles(iFig).hPatch, sScouts(i).Handles(iFig).hVertices]; + % Set the visibility of all the scout elements + if ~isempty(hGlobal) + if isVisibleGlobal + set(hGlobal, 'Visible', 'on'); + else + set(hGlobal, 'Visible', 'off'); + end + end + if ~isempty(hLocal) + if isVisibleLocal + set(hLocal, 'Visible', 'on'); + else + set(hLocal, 'Visible', 'off'); + end + end + % Add figure to the list of updated figures + hAllFig = [hAllFig, hFig]; + end + end + + % Add the list of the MriViewer figures + hFigMriViewer = gui_figuresManager('GetFiguresByType', 'MriViewer'); + % List of all the figures that have proper scouts drawn in them + hAllFig = unique([hFigMriViewer, hAllFig]); + % Update overlay mask (for 3D MRI slices) for each figure + panel_surface('UpdateOverlayCubes', hAllFig); +end + + + +%% ===== SAVE SCOUT ===== +function SaveScouts() + global GlobalData; + % Stop scout edition + SetSelectionState(0); + % Get protocol description + ProtocolInfo = bst_getContext('ProtocolInfo'); + % Get selected scouts + sScouts = GetSelectedScouts(); + if isempty(sScouts) + return + end + % Build a default file name + ScoutFile = fullfile(ProtocolInfo.SUBJECTS, fileparts(GlobalData.CurrentScoutsSurface), ... + ['scout', sprintf('_%s', sScouts.Label), '.mat']); + % Get filename where to store the filename + ScoutFile = java_fileSelector( 'save', 'Save selected scouts', ScoutFile, ... + 'single', 'files', ... + {{'_scout'}, 'Brainstorm cortical scouts (*scout*.mat)', 'BST'}, 1); + if isempty(ScoutFile) + return; + end + % Make sure that filename contains the 'scout' tag + if isempty(strfind(ScoutFile, '_scout')) && isempty(strfind(ScoutFile, 'scout_')) + [filePath, fileBase, fileExt] = fileparts(ScoutFile); + ScoutFile = fullfile(filePath, ['scout_' fileBase fileExt]); + end + + % Load tesselation vertices (just to get nb vertices) + SurfaceFile = strrep(sScouts(1).SurfaceFile, ProtocolInfo.SUBJECTS, ''); + sSurf = bst_dataSetsManager('LoadSurface', SurfaceFile); + % Prepare saved structure + ScoutMat.Tesselation = io_win2unix(SurfaceFile); + ScoutMat.TessNbVertices = length(sSurf.Vertices); + for i=1:length(sScouts) + ScoutMat.Scout(i).Vertices = sScouts(i).Vertices; + ScoutMat.Scout(i).Seed = sScouts(i).Seed; + ScoutMat.Scout(i).Label = sScouts(i).Label; + ScoutMat.Scout(i).Color = sScouts(i).Color; + end + % Save file + save(ScoutFile, '-struct', 'ScoutMat'); +end + + +%% ===== LOAD SCOUT ===== +function LoadScouts() + global GlobalData; + % === SELECT FILE TO LOAD === + % Stop scout edition + SetSelectionState(0); + % Get protocol description + ProtocolInfo = bst_getContext('ProtocolInfo'); + % Build default scouts directory + if ~isempty(GlobalData.CurrentScoutsSurface) + scoutsSubDir = fileparts(GlobalData.CurrentScoutsSurface); + else + % Get current subject directory + sSubject = bst_getContext('Subject'); + % If no current subject (no recordings were loaded yet) + curFig = gui_figuresManager('GetCurrentFigure', '3D'); + if isempty(sSubject) && ~isempty(curFig) + % Get subject of current figure + SubjectFile = getappdata(curFig, 'SubjectFile'); + if ~isempty(SubjectFile) + sSubject = bst_getContext('Subject', SubjectFile); + end + end + if isempty(sSubject) + return; + end + scoutsSubDir = fileparts(sSubject.FileName); + end + scoutsDir = fullfile(ProtocolInfo.SUBJECTS, scoutsSubDir); + % Ask user which are the files to be loaded + ScoutFiles = java_fileSelector( 'open', 'Import scouts', scoutsDir, ... + 'multiple', 'files', ... + {{'_scout'}, 'Cortical scouts (*scout*.mat)', 'BST'}, 1); + if isempty(ScoutFiles) + return + end + + % ==== CREATE AND DISPLAY ==== + iNewScoutsList = []; + bst_progressBar('start', 'Load scouts', 'Load scout file'); + % Load all files selected by user + for iFile = 1:length(ScoutFiles) + % Try to load scout file + ScoutMat = load(ScoutFiles{iFile}); + + % === INTEGRATION WITH CURRENT DATA === + % Find an existing figure with the same number of vertices + [hFig,iFig,iDS,iSurf] = gui_figuresManager('GetFigureWithSurfaceNbVert', ScoutMat.TessNbVertices); + % If no figure found: import scouts ignoring figures and currently loaded data + if isempty(hFig) + iFound = []; + elseif (length(hFig) == 1) + iFound = 1; + % If there are more than one figure + elseif (length(hFig) > 1) + % Get current figure + hCurFig = gui_figuresManager('GetCurrentFigure', '3D'); + % Find current figure in valid candidates for loaded scout + iFound = find(hFig == hCurFig, 1); + % If current figure is found: use it, else use first figure in the list + if isempty(iFound) + iFound = 1; + end + hFig = hFig(iFound); + iSurf = iSurf(iFound); + end + % Replace scout tess field with the surface we've just found + if ~isempty(iFound) + TessInfo = getappdata(hFig, 'Surface'); + if strcmpi(TessInfo(iSurf).Name, 'Anatomy') + sSurfCortex = bst_getContext('SurfaceFileByType', TessInfo(iSurf).SurfaceFile, 'Cortex'); + ScoutMat.Tesselation = sSurfCortex.FileName; + else + ScoutMat.Tesselation = TessInfo(iSurf).SurfaceFile; + end + end + + % === AUTO-CORRECT TESSELATION FIELD === + % If Figure not found && Tesselation pointed by the Scout does not exist + if isempty(iFound) && ~exist(fullfile(ProtocolInfo.SUBJECTS, ScoutMat.Tesselation), 'file') + CorrectedTess = ''; + bst_progressBar('start', 'Load scouts', 'Finding a valid cortex surface...'); + % If NbVertices is not defined: cannot find file + if isfield(ScoutMat, 'TessNbVertices') && ~isempty(ScoutMat.TessNbVertices) + % Look for a tesselation file with the same number of vertices in the scout directory + dirTess = fileparts(ScoutFiles{iFile}); + listTessFiles = dir(fullfile(dirTess, '*tess*.mat')); + % Load each tess file and check the number of vertices + for iTess = 1:length(listTessFiles) + TessMat = in_tess_bst(fullfile(dirTess, listTessFiles(iTess).name)); + % If number of vertices is correct: keep this tess file + if (length(TessMat.Vertices) == ScoutMat.TessNbVertices) + CorrectedTess = fullfile(fileparts(ScoutFiles{iFile}), listTessFiles(iTess).name); + break + end + end + end + % If a replacing surface was found: save it + if ~isempty(CorrectedTess) + ScoutMat.Tesselation = strrep(CorrectedTess, ProtocolInfo.SUBJECTS, ''); + save(ScoutFiles{iFile}, '-struct', 'ScoutMat'); + % Else, no replacing surface was found: error + else + error(['This scout file need surface file "' ScoutMat.Tesselation '",' 10 ... + 'which cannot be found in anatomy directory.' 10 10 ... + 'Please check the Tesselation field of the scout file.']); + end + end + + % === CREATE ALL SCOUT PATCHES === + bst_progressBar('start', 'Load scouts', 'Create scouts patches...'); + try + % Each scout file may contain many scouts definitions + for i = 1:length(ScoutMat.Scout) + if ~isempty(ScoutMat.Scout(i).Seed) + % Create new scout + iNewScout = length(GlobalData.Scouts) + 1; + iNewScoutsList = [iNewScoutsList, iNewScout]; + GlobalData.Scouts(iNewScout) = db_getDataTemplate('Scout'); + % Copy needed fields + GlobalData.Scouts(iNewScout).SurfaceFile = ScoutMat.Tesselation; + GlobalData.Scouts(iNewScout).Vertices = ScoutMat.Scout(i).Vertices; + GlobalData.Scouts(iNewScout).Seed = ScoutMat.Scout(i).Seed; + GlobalData.Scouts(iNewScout).Label = ScoutMat.Scout(i).Label; + if isfield(ScoutMat.Scout(i), 'Color') + GlobalData.Scouts(iNewScout).Color = ScoutMat.Scout(i).Color; + else + GlobalData.Scouts(iNewScout).Color = [0 1 0]; + end + % Display scout + PlotScout(iNewScout); + end + end + % Update display (call only useful for 3D MRI slices) + UpdateScoutsDisplay(); + catch + warning('Brainstorm:BadScoutFile', 'Unable to load scout file : "%s".', ScoutFiles{iFile}); + end + end + + % ===== CHECK CURRENT SURFACE ===== + % If current scouts surface is not defined, use the first scout surface + if isempty(GlobalData.CurrentScoutsSurface) + GlobalData.CurrentScoutsSurface = GlobalData.Scouts(iNewScoutsList(1)).SurfaceFile; + end + + % ===== UPDATE PANEL ===== + % Update "Scouts" panel + if (length(iNewScoutsList) > 10) + % Many scouts: select and display only the first one + ctrl = bst_getContext('PanelControls', 'Scout'); + ctrl.jRadioScoutViewSelected.setSelected(1); + UpdatePanel(); + SetSelectedScouts(iNewScoutsList(1)); + elseif ~isempty(iNewScoutsList) + % Only few scouts : select and display all + UpdatePanel(); + SetSelectedScouts(iNewScoutsList); + end + bst_progressBar('stop'); +end + + +%% ===== FORWARD MODEL FOR SCOUTS ===== +% Simulate the surface data that could be recorded if only the selected scouts were activated +function ForwardModelForScout() + global GlobalData; + ProtocolInfo = bst_getContext('ProtocolInfo'); + % Stop scout edition + SetSelectionState(0); + % ===== GET ALL ACCESSIBLE DATA ===== + % Get selected figure + hFig = gui_figuresManager('GetCurrentFigure', '3D'); + if isempty(hFig) || ~ishandle(hFig) || isempty(getappdata(hFig, 'ResultsFile')) + return + end + % Get ResultsFile and Surface + ResultsFile = getappdata(hFig, 'ResultsFile'); + + % Get selected scouts + sScouts = GetSelectedScouts(); + % Some scouts were found : get their vertices + if ~isempty(sScouts) && ~isempty(sScouts(1).Handles) + % Get all source vertices to perform simulation + iVertices = unique([sScouts.Vertices]); + % No scouts: use all vertices to do simulation + else + iVertices = []; + end + + % ===== LOAD GAIN MATRIX ===== + % Get study + [sStudy, iStudy, iResult] = bst_getContext('ResultsFile', ResultsFile); + % Get headmodel + sHeadModel = bst_getContext('HeadModelForStudy', iStudy); + if isempty(sHeadModel) + error('No headmodel available for this study.'); + end + % Load HeadModel file + HeadModelMat = load(fullfile(ProtocolInfo.STUDIES, sHeadModel.FileName), 'Gain'); + if isempty(HeadModelMat) + error('Invalid headmodel.'); + end + % Get gain file + if ischar(HeadModelMat.Gain{1}) + % Read gain matrix + gainfile = fullfile(ProtocolInfo.STUDIES, fileparts(sHeadModel.FileName), HeadModelMat.Gain{1}); + G = read_gain(gainfile); + elseif isnumeric(HeadModelMat.Gain{1}) + G = HeadModelMat.Gain{1}; + end + % If no vertices selected yet: use all + if isempty(iVertices) + iVertices = 1:size(G, 2); + end + % Keep only the vertices we want to project on surface + G = G(:,iVertices); + + % ===== GET RESULTS MATRIX ===== + % Load results matrix + [iDS, iResult] = bst_dataSetsManager('LoadResultsFileFull', ResultsFile); + if isempty(iDS) + return + end + % Progress bar + bst_progressBar('start', 'Simulation', 'Simulation of surface recordings...'); + % Get sources matrix + ResultsValues = bst_dataSetsManager('GetResultsValues', iDS, iResult, iVertices, 'UserTimeWindow'); + % Get ChannelFlag + ChannelFlag = GlobalData.DataSet(iDS).Results(iResult).ChannelFlag; + % Build time vector + timeWnd = round(10000 * GlobalData.DataSet(iDS).Measures.Time) / 10000; + smpRate = round(10000 * GlobalData.DataSet(iDS).Measures.SamplingRate) / 10000; + TimeVector = timeWnd(1) : smpRate : timeWnd(2); + + % ===== BUILD SIMULATED DATA FILE ===== + % Get a string to represent time + c = clock; + strTime = sprintf('%02.0f%02.0f%02.0f_%02.0f%02.0f', c(1)-2000, c(2:5)); + % Get a string to represent scouts + strScouts = ''; + if ~isempty(sScouts) + if (length(sScouts) > 1) + strScouts = '('; + end + for i=1:length(sScouts) + strScouts = [strScouts, sScouts(i).Label, ',']; + end + if (length(sScouts) > 1) + strScouts(end) = ')'; + else + strScouts(end) = []; + end + strScouts = [strScouts, '@']; + end + + % Build data file + DataMat = struct('Comment', ['Simulation: ' strScouts sStudy.Result(iResult).Comment ' (' strTime ')'], ... + 'Time', TimeVector, ... + 'F', G * ResultsValues, ... + 'ChannelFlag', ChannelFlag); + + % Output file + newDataFile = fullfile(ProtocolInfo.STUDIES, fileparts(ResultsFile), ... + ['data_simulation_', strTime, '.mat']); + newDataFile = io_makeUniqueFilename(newDataFile); + % Save file + save(newDataFile, '-struct', 'DataMat'); + % Hide progress bar + bst_progressBar('stop'); + + % ===== UPDATE DATABASE ===== + % Build data structure + sData = db_getDataTemplate('Data'); + sData.FileName = strrep(newDataFile, ProtocolInfo.STUDIES, ''); + sData.Comment = DataMat.Comment; + % Add it to study + sStudy.Data(end + 1) = sData; + bst_setContext('Study', iStudy, sStudy); + % Update display + tree_updateNode('Study', iStudy); + % Select node + tree_selectStudyNode(iStudy); +end + + +%% ===== VIEW SCOUT IN MRI ===== +function ViewInMriViewer() + % Stop scout edition + SetSelectionState(0); + % Get protocol description + ProtocolInfo = bst_getContext('ProtocolInfo'); + % Get selected scouts + [sScouts, iScouts] = GetSelectedScouts(); + if isempty(sScouts) + java_dialog('warning', 'No scout selected.', 'View scout in MRI.'); + return + elseif (length(sScouts) > 1) + % More than one scout selected: select only the first one + sScouts = sScouts(1); + iScouts = iScouts(1); + SetSelectedScouts(iScouts(1)); + end + % Get the subject associated with the first selected scout + sSubject = bst_getContext('SurfaceFile', sScouts.SurfaceFile); + % Get the anatomy file for this subject + if isempty(sSubject) || isempty(sSubject.iAnatomy) + error('No MRI defined for this subject'); + end + MriFile = fullfile(ProtocolInfo.SUBJECTS, sSubject.Anatomy(sSubject.iAnatomy).FileName); + % Build full surface path + SurfaceFile = fullfile(ProtocolInfo.SUBJECTS, sScouts.SurfaceFile); + + % Configure view to display only the scouts in MRI + ctrl = bst_getContext('PanelControls', 'Scout'); + ctrl.jCheckLimitMriSources.setSelected(1); + + % Progress bar + bst_progressBar('Start', 'MRI Viewer', 'Opening MRI Viewer...'); + % Display subject's anatomy in MRI Viewer + hFig = gui_viewMri(MriFile, SurfaceFile, 'ReadOnly'); + % Close progress bar + bst_progressBar('Stop'); + % Center view on first scout + CenterMriOnScout(); +end + + +%% ===== VIEW SCOUT ON CORTEX ===== +function ViewOnCortex() + % === Get selected scout === + [sScouts, iScouts] = GetSelectedScouts(); + if isempty(sScouts) + java_dialog('warning', 'No scout selected.', 'View scout on cortex.'); + return + elseif (length(sScouts) > 1) + % More than one scout selected: select only the first one + sScouts = sScouts(1); + iScouts = iScouts(1); + SetSelectedScouts(iScouts(1)); + end + % === Display associated surface === + gui_viewSurfaceFile(sScouts.SurfaceFile); +end + + +%% ===== EDIT SCOUT IN MRI ===== +function iScout = ScoutEditorInMri(iScout) + global GlobalData; + % Stop scout edition + SetSelectionState(0); + % === GET SCOUT === + % Create new scout or edit existing one + isNewScout = isempty(iScout) || (iScout <= 0); + % Get existing scout + if ~isNewScout + sScout = GetScouts(iScout); + if isempty(sScout) + isNewScout = 1; + end + end + + % === GET FIGURE AND SURFACE === + % If a scout already exists: use its surface + if ~isNewScout + % Get surface for this scout + SurfaceFile = sScout.SurfaceFile; + % Get subject for this surface + [sSubject, iSubject] = bst_getContext('SurfaceFile', SurfaceFile); + % Else use current figure/surface + else + % Get current 3d figure + [hFig,iFig,iDS] = gui_figuresManager('GetCurrentFigure', '3D'); + % Get cortical surface + [iSurf, TessInfo] = panel_surface('GetSurfaceCortexOrAnatomy'); + if isempty(iSurf) + % Display warning message + java_dialog('warning', 'No cortex surface available.', 'Edit scout in MRI'); + return + end + % Build surface filename + if strcmpi(TessInfo(iSurf).Name, 'Anatomy') + % If anatomy surface : use correspondant cortex surface instead + sSurfCortex = bst_getContext('SurfaceFileByType', TessInfo(iSurf).SurfaceFile, 'Cortex'); + SurfaceFile = sSurfCortex.FileName; + else + SurfaceFile = TessInfo(iSurf).SurfaceFile; + end + % Get subject for this figure + [sSubject, iSubject] = bst_getContext('Subject', GlobalData.DataSet(iDS).SubjectFile); + end + + % === LOAD MRI AND SURFACE === + % Get the anatomy file for this subject + if isempty(sSubject) || isempty(sSubject.iAnatomy) + error('No MRI defined for this subject'); + end + % Progress bar + bst_progressBar('start', 'Edit mask', 'Initialization...'); + % Load Mri + sMri = bst_dataSetsManager('LoadMri', iSubject); + % Load Vertices from Surface + [sSurf, iSurf] = bst_dataSetsManager('LoadSurface', SurfaceFile); + % Get interpolation matrix MRI<->Surface + tess2mri_interp = bst_dataSetsManager('GetTess2MriInterp', iSurf); + % Compute the position of the vertices in MRI coordinates + mriVertices = scs2mri(sMri, sSurf.Vertices' * 1000) ./ repmat(sMri.Voxsize', 1, length(sSurf.Vertices)); + + % === BUILD INITIAL MASK === + % If scout already exist, generate a mask for it + if ~isNewScout + % Get vertices to display + iVertices = sScout.Vertices; + % Display only specified vertices + [iMri,iVert] = find(tess2mri_interp(:,iVertices)); + Values = round(sum(tess2mri_interp(:,iVertices),2) * 255); + Indices = unique(iMri); + Values = full(Values(Indices)); + + % Apply a threshold to the values + iUnderThreshold = (Values < 0.5 * 255); + Values(iUnderThreshold) = []; + Indices(iUnderThreshold) = []; + + % Create mask volume (same size than the MRI) + initMask = zeros(size(sMri.Cube), 'uint8'); + % Set values in the cube + initMask(Indices) = Values; + else + % No scout: empty initial mask + initMask = []; + end + + % === INITIAL POSITION === + % If scout already exist, + if ~isNewScout + % Orientation: Display in axial slices + initPosition(1) = 3; + % Position: mean of the scout vertices + initPosition(2) = round(mean(mriVertices(initPosition(1), sScout.Vertices))); + else + % No scout: Default display + initPosition = []; + end + + % === COLORMAP === + % Get colormap + sColormap = bst_colormaps('GetColormap', 'Anatomy'); + + % === EDIT MASK === + % Open mask editor + newMask = mri_editMask(sMri.Cube, sMri.Voxsize, initMask, initPosition, sColormap.Name); + if isempty(newMask) + return + end + % Dilatation of the mask + newMask = vol_dilate(newMask, 6); + % Mask was modified: find the vertices inside the new mask + rVertices = round(mriVertices); + iVerticesInMri = sub2ind(size(sMri.Cube), rVertices(1,:), rVertices(2,:), rVertices(3,:)); + % Find the vertices inside the mask + iVerticesInMask = find(newMask(iVerticesInMri)); + % If no vertices : cannot define a scout + if isempty(iVerticesInMask) + error(['The mask you designed does not contain any surface vertices.' 10 'It cannot be used to create a scout']); + end + + % === OUTPUT SCOUT === + % Create new scout + if isNewScout + [sScout, iScout] = CreateNewScout(SurfaceFile, iVerticesInMask, iVerticesInMask(1)); + % Update vertices + else + sScout.Vertices = iVerticesInMask; + % If seed is not anymore inside the scout : use the first vertex available + if ~ismember(sScout.Seed, sScout.Vertices) + sScout.Seed = sScout.Vertices(1); + end + end + % Save updated scout + GlobalData.Scouts(iScout) = sScout; + % Update scout display + PlotScout(iScout); + % Update display or Scouts properties + UpdatePanel(); + % Stop scout edition + SetSelectionState(0); + % Select new scout + SetSelectedScouts(iScout); + % Close progress bar + bst_progressBar('Stop'); +end + + +%% ===== CENTER MRI ON SCOUT ===== +function CenterMriOnScout() + % === GET SELECTED SCOUT === + % Get selected scouts + [sScout, iScout] = GetSelectedScouts(); + if isempty(sScout) + java_dialog('warning', 'No scout selected.', 'Center MRI on scout'); + return + elseif (length(sScout) > 1) + % More than one scout selected: select only the first one + sScout = sScout(1); + iScout = iScout(1); + SetSelectedScouts(iScout); + end + % === GET FIGURE === + % Get current 3D figure + hFig = gui_figuresManager('GetCurrentFigure', '3D'); + if isempty(hFig) + java_dialog('warning', 'No 3D figure.', 'Center MRI on scout'); + return + end + % Get figure type + FigureId = getappdata(hFig, 'FigureId'); + % Get anatomy surface + [sMri, TessInfo, iAnatomy] = panel_surface('GetSurfaceMri', hFig); + if isempty(iAnatomy) + java_dialog('warning', 'No MRI displayed in current figure.', 'Center MRI on scout'); + return + end + % === CENTER MRI VIEW === + % Get MRI structure + sMri = bst_dataSetsManager('GetMri', TessInfo(iAnatomy).SurfaceFile); + % Get cortex surface + sDbCortex = bst_getContext('SurfaceFileByType', TessInfo(iAnatomy).SurfaceFile, 'Cortex'); + sSurfCortex = bst_dataSetsManager('LoadSurface', sDbCortex.FileName); + % Get new slices coordinates + newPosScs = sSurfCortex.Vertices(sScout.Seed,:); + newPosMri = scs2mri(sMri, newPosScs' * 1000)' ./ sMri.Voxsize; + % If figure is a MRIViewer + switch (FigureId.Type) + case 'MriViewer' + gui_figureMriViewer('SetLocation', 'mri', hFig, [], newPosMri); + case '3DViz' + TessInfo(iAnatomy).CutsPosition = round(newPosMri); + gui_figure3DViz('UpdateMriDisplay', hFig, [1 2 3], TessInfo, iAnatomy); + end +end + + +%% ===== EXPAND WITH CORRELATION ===== +function ExpandWithCorrelation() + global GlobalData; + % Stop scout edition + SetSelectionState(0); + % ===== GET ALL NEEDED INFO ===== + % Get selected scouts + [sScout, iScout] = GetSelectedScouts(); + if isempty(sScout) + java_dialog('warning', 'No scout selected.', 'Expand scout using correlation'); + return + elseif (length(sScout) > 1) + % More than one scout selected: select only the first one + sScout = sScout(1); + iScout = iScout(1); + SetSelectedScouts(iScout); + end + + % Get selected figure + hFig = gui_figuresManager('GetCurrentFigure', '3D'); + if isempty(hFig) + return + end + % Get results file + ResultsFile = getappdata(hFig, 'ResultsFile'); + if isempty(ResultsFile) + bst_error('No sources displayed in this figure.', 'Expand with correlation', 0); + return + end + + % ===== THRESHOLD ===== + % For selected scout, find sources that are strongly correlated + Threshold = 0.95; + % Ask confirmation of the thresolh level to the user + res = java_dialog('input', 'Thresold value for correlation', 'Sources correlation', [], num2str(Threshold)); + if isempty(res) + return; + end + Threshold = str2num(res); + if isempty(Threshold) + return; + end + + % ===== LOAD RESULTS ===== + % Progress bar + bst_progressBar('start', 'Sources correlation', 'Loading results...'); + % Load results file + [iDS, iResult] = bst_dataSetsManager('LoadResultsFileFull', ResultsFile); + % If no DataSet is accessible : error + if isempty(iDS) + warning(['Cannot load file : "', ResultsFile, '"']); + return + end + % Get results values over the current time window + ResultsValues = bst_dataSetsManager('GetResultsValues', iDS, iResult, [], 'UserTimeWindow'); + + % ===== CORRELATION BETWEEN SOURCES ===== + % Progress bar + nbSources = size(ResultsValues,1); + blockSize = round(nbSources / 100); + bst_progressBar('start', 'Sources correlation', 'Computing correlation...', 0, 100); + % Remove all vertices from initial scout + corrVertices = zeros(1,nbSources); + corrVertices(sScout.Seed) = 1; + % Process each vertex + for iVert = 1:nbSources + if (mod(iVert, blockSize) == 0) + bst_progressBar('inc', 1); + end + if (iVert == sScout.Seed) + continue + end + corr = corrcoef(ResultsValues([sScout.Seed iVert],:)'); + if (abs(corr(2,1)) > Threshold) + corrVertices(iVert) = 1; + end + end + % Final list of scouts + sScout.Vertices = find(corrVertices); + + % ===== DISPLAY ===== + % Update scout + GlobalData.Scouts(iScout) = sScout; + % Display scout patch + PlotScout(iScout); + % Update panel "Scouts" fields + UpdateScoutProperties(); + % Display/hide scouts and update MRI overlay mask + UpdateScoutsDisplay(); + % Hide progress bar + bst_progressBar('stop'); + +end + + +%% ===== SELECT MAXIMUM VALUE ===== +% Usage: SelectMaximumValue(iScout) : Keep only the maximum value in the selected scout +% SelectMaximumValue() : Create new scout with the vertex with maximum value at current time +function SelectMaximumValue(iScout) + global GlobalData; + % Parse input + if (nargin < 1) + isNewScout = 1; + else + isNewScout = 0; + sScout = GetScouts(iScout); + end + % Stop scouts edition + SetSelectionState(0); + % Get current cortical surface + [iSurf, TessInfo] = panel_surface('GetSurfaceCortexOrAnatomy'); + % If no cortex surface available + if isempty(iSurf) || isempty(TessInfo(iSurf).Data) || isempty(TessInfo(iSurf).DataSource.FileName) + java_dialog('warning', 'No 3D figure with sources avaialable.', 'Find maximum value'); + return; + end + + % Get the vertices with maximal value, at the present time ONLY + if isNewScout + % Get surface file to which the scout will be attached + if strcmpi(TessInfo(iSurf).Name, 'Anatomy') + % If anatomy surface : use correspondant cortex surface instead + sSurfCortex = bst_getContext('SurfaceFileByType', TessInfo(iSurf).SurfaceFile, 'Cortex'); + SurfaceFile = sSurfCortex.FileName; + else + SurfaceFile = TessInfo(iSurf).SurfaceFile; + end + % If creating scout: look for max in the whole surface + [valMax, iVertMax] = max(abs(TessInfo(iSurf).Data)); + % Create a new scout with the maximum (Keep only the FIRST maximal vertex) + [sScout, iScout] = CreateNewScout(SurfaceFile, iVertMax(1), iVertMax(1)); + else + % If editing scout: look for max only in the scout's vertices + [valMax, iVertMax] = max(abs(TessInfo(iSurf).Data(sScout.Vertices))); + iVertMax = sScout.Vertices(iVertMax); + % Update scout (Keep only the FIRST maximal vertex) + sScout.Vertices = iVertMax(1); + sScout.Seed = iVertMax(1); + GlobalData.Scouts(iScout) = sScout; + end + + % Display scout patch + PlotScout(iScout); + % Update "Scouts" panel + if isNewScout + UpdatePanel(); + % Select new scout + SetSelectedScouts(iScout); + else + % Update panel "Scouts" fields + UpdateScoutProperties(); + % Display/hide scouts and update MRI overlay mask + UpdateScoutsDisplay(); + end +end + + + + + + + + diff --git a/brainstorm3/toolbox/gui/panel_stat.m b/brainstorm3/toolbox/gui/panel_stat.m new file mode 100644 index 0000000..90ac134 --- /dev/null +++ b/brainstorm3/toolbox/gui/panel_stat.m @@ -0,0 +1,303 @@ +function varargout = panel_stat(varargin) +% PANEL_STAT: Creation and management of samples sets. +% +% USAGE: bstPanelNew = panel_statConditions('CreatePanel') +% panel_statConditions('ResetPanel') + +% @============================================================================= +% This software is part of The Brainstorm Toolbox +% http://neuroimage.usc.edu/brainstorm +% +% Copyright (c)2010 Brainstorm by the University of Southern California +% This software is distributed under the terms of the GNU General Public License +% as published by the Free Software Foundation. Further details on the GPL +% license can be found at http://www.gnu.org/copyleft/gpl.html. +% +% FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE +% UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY +% WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF +% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY +% LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE. +% +% For more information type "brainstorm licence" at command prompt. +% =============================================================================@ +% +% Authors: Francois Tadel, 2008 +% ----------------------------- Script History --------------------------------- +% FT 24-Jun-2008 Creation +% ------------------------------------------------------------------------------ + +fprintf('Karim''s version!!!'); + +% No parameters +if (nargin == 0) +% Else : execute appropriate local function +elseif ischar(varargin{1}) + if (nargout) + [varargout{1:nargout}] = bst_safeCall(str2func(varargin{1}), varargin{2:end}); + else + bst_safeCall(str2func(varargin{1}), varargin{2:end}); + end +end +end + + +%% ===== CREATE PANEL ===== +function bstPanelNew = CreatePanel() + panelName = 'Statistics'; + % Java initializations + import java.awt.*; + import javax.swing.*; + import org.brainstorm.icon.IconLoader; + import org.brainstorm.tree.*; + import org.brainstorm.dnd.*; + + % CONSTANTS + TB_BUTTON = 23; + jFontText = java.awt.Font('Dialog', java.awt.Font.PLAIN, 10); + % Create topmost panel (BORDER LAYOUT) + jPanelNew = JPanel(BorderLayout()); + % === TOOLBAR === + % Toolbar itself + jToolbarStat = JToolBar('Statistics', JToolBar.VERTICAL); + jToolbarStat.setBorderPainted(0); + jToolbarStat.setFloatable(0); + jToolbarStat.setRollover(1); + jButtonGroupType = ButtonGroup(); + % Button "Recordings" + jButtonRecordings = JToggleButton(IconLoader.ICON_DATA_LIST, 1); + jButtonRecordings.setPreferredSize(Dimension(TB_BUTTON,TB_BUTTON)); + jButtonRecordings.setToolTipText('Process recordings'); + jButtonRecordings.setFocusable(0); + set(jButtonRecordings, 'ActionPerformedCallback', @DataType_Callback); + jButtonGroupType.add(jButtonRecordings); + jToolbarStat.add(jButtonRecordings); + % Button "Sources" + jButtonSources = JToggleButton(IconLoader.ICON_RESULTS_LIST); + jButtonSources.setPreferredSize(Dimension(TB_BUTTON, TB_BUTTON)); + jButtonSources.setToolTipText('Process sources'); + jButtonSources.setFocusable(0); + set(jButtonSources, 'ActionPerformedCallback', @DataType_Callback); + jButtonGroupType.add(jButtonSources); + jToolbarStat.add(jButtonSources); + jToolbarStat.addSeparator(); + + % Button "RUN" + jButtonRun = JButton(IconLoader.ICON_RUN); + jButtonRun.setPreferredSize(Dimension(TB_BUTTON, TB_BUTTON)); + jButtonRun.setToolTipText('Start test'); + jButtonRun.setFocusable(0); + set(jButtonRun, 'ActionPerformedCallback', @(h,ev)bst_safeCall(@ButtonRun_Callback)); + jToolbarStat.add(jButtonRun); + jPanelNew.add(jToolbarStat, BorderLayout.WEST); + + % === PANEL "A/B" === + jPanelAB = JPanel(); + jPanelAB.setLayout(BoxLayout(jPanelAB, BoxLayout.LINE_AXIS)); + % === CONDITION A === + jPanelA = JPanel(BorderLayout()); + jPanelA.setBorder(BorderFactory.createTitledBorder('Samples A')); + % Tree of Condition A + jTreeFilesA = BstTree(); + jTreeFilesA.setEditable(0); + % Keyboard callback + set(jTreeFilesA, 'MouseClickedCallback', @(h,ev)gui_stat_common('TreeClicked_Callback', h, ev), ... + 'KeyPressedCallback', @(h,ev)gui_stat_common('TreeKeyboard_Callback', h, ev), ... + 'FocusLostCallback', @(h,ev)jTreeFilesA.getSelectionModel.setSelectionPath([])); + % Configure selection model + jTreeSelModel = jTreeFilesA.getSelectionModel(); + jTreeSelModel.setSelectionMode(jTreeSelModel.DISCONTIGUOUS_TREE_SELECTION); + % Enable drag'n'drop + jTreeFilesA.setDragEnabled(1); + transfHandler = TreeDropTransferHandler(); + jTreeFilesA.setTransferHandler(transfHandler); + set(jTreeFilesA.getModel(), 'TreeStructureChangedCallback', @(h,ev)gui_stat_common('UpdateItemProperties', jTreeFilesA, 'SampleFilesAA')); + % Scroll panel + jScrollListFilesA = JScrollPane(jTreeFilesA); + jPanelA.add(jScrollListFilesA, BorderLayout.CENTER); + jPanelAB.add(jPanelA); + + % === CONDITION B === + jPanelB = JPanel(BorderLayout()); + jPanelB.setBorder(BorderFactory.createTitledBorder('Samples B')); + % Tree of Condition B + jTreeFilesB = BstTree(); + jTreeFilesB.setEditable(0); + % Keyboard callback + set(jTreeFilesB, 'MouseClickedCallback', @(h,ev)gui_stat_common('TreeClicked_Callback', h, ev), ... + 'KeyPressedCallback', @(h,ev)gui_stat_common('TreeKeyboard_Callback', h, ev), ... + 'FocusLostCallback', @(h,ev)jTreeFilesB.getSelectionModel.setSelectionPath([])); + % Configure selection model + jTreeSelModel = jTreeFilesB.getSelectionModel(); + jTreeSelModel.setSelectionMode(jTreeSelModel.DISCONTIGUOUS_TREE_SELECTION); + % Enable drag'n'drop + jTreeFilesB.setDragEnabled(1); + transfHandler = TreeDropTransferHandler(); + jTreeFilesB.setTransferHandler(transfHandler); + set(jTreeFilesB.getModel(), 'TreeStructureChangedCallback', @(h,ev)gui_stat_common('UpdateItemProperties', jTreeFilesB, 'SampleFilesAB')); + % Scroll panel + jScrollListFilesB = JScrollPane(jTreeFilesB); + jPanelB.add(jScrollListFilesB, BorderLayout.CENTER); + jPanelAB.add(jPanelB); + jPanelNew.add(jPanelAB, BorderLayout.CENTER); + + % Create the BstPanel object that is returned by the function + % => constructor BstPanel(jHandle, panelName, sControls) + bstPanelNew = BstPanel(panelName, ... + jPanelNew, ... + struct('jPanelTop', jPanelNew, ... + 'jButtonRecordings', jButtonRecordings, ... + 'jButtonSources', jButtonSources, ... + 'jPanelAB', jPanelAB, ... + 'jTreeFilesA', jTreeFilesA, ... + 'jTreeFilesB', jTreeFilesB)); + + +%% ========================================================================= +% ===== LOCAL CALLBACKS =================================================== +% ========================================================================= +%% ===== CHANGE DATA TYPE ===== + function DataType_Callback(hObject, ev) + bst_progressBar('start', 'Updating samples list', 'Updating samples list...'); + % Update items counts in all trees + gui_stat_common('UpdateItemProperties', jTreeFilesA, 'SampleFilesAA', 1); + gui_stat_common('UpdateItemProperties', jTreeFilesB, 'SampleFilesAB', 1); + bst_progressBar('stop'); + end +end + +%% ========================================================================= +% ===== PROCESSING FUNCTIONS ============================================== +% ========================================================================= +%% ===== RESET PANEL ===== +function ResetPanel(varargin) %#ok + % Get panel controls + ctrl = bst_getContext('PanelControls', 'Statistics'); + % Remove all nodes from all the trees + awtinvoke(ctrl.jTreeFilesA.getModel.getRoot(), 'removeAllChildren()'); + awtinvoke(ctrl.jTreeFilesB.getModel.getRoot(), 'removeAllChildren()'); + % Update all trees + awtinvoke(ctrl.jTreeFilesA, 'refresh()'); + awtinvoke(ctrl.jTreeFilesB, 'refresh()'); +end + + +%% ===== RUN STATS ===== +function ButtonRun_Callback(varargin) +% bst_progressBar('start', 'Initialization', 'Initialization...'); + global GlobalData; + % ===== CHECK INPUTS ===== + % Get conditions + Conditions = gui_stat_common('GetConditions', 'Statistics'); + if isempty(Conditions) + return + end + + % Check samples number + if isempty(Conditions.SamplesA) %|| isempty(Conditions.SamplesB) + bst_error('Empty samples list.', 'Statistics', 0); + return + end + SampleFiles = cat(2, GlobalData.Stat.SampleFilesAA, GlobalData.Stat.SampleFilesAB); + % Check if nothing changed + if ~gui_stat_common('CheckConditions', 'Statistics') + % Empty all samples lists + ResetPanel(); + % Display error message + bst_error(['Database contents changed.' 10 ... + 'Please select again the files you want to process.'], 'Statistics', 0); + return + end + % Check if samples mix zscore and initial values + iZscore = find(~cellfun(@(c)isempty(strfind(c,'_zscore')), SampleFiles)); + iTimeMean = find(~cellfun(@(c)isempty(strfind(c,'_timemean')), SampleFiles)); + iTimeMean = setdiff(iTimeMean, iZscore); + iNormal = setdiff(1:length(SampleFiles), [iZscore, iTimeMean]); + + % If mixed input file types: warning + if (~isempty(iZscore) && ~isempty(iTimeMean)) || (~isempty(iZscore) && ~isempty(iNormal)) || (~isempty(iTimeMean) && ~isempty(iNormal)) + bst_progressBar('hide'); + % Build list of available file tags + fileTags = {'normal'}; + if ~isempty(iZscore) + fileTags{end + 1} = 'zscore'; + end + if ~isempty(iTimeMean) + fileTags{end + 1} = 'timemean'; + end + fileTags{end + 1} = 'all'; + % Ask user to choose between file tags + res = java_dialog('question', ['You mixed file types that cannot be processed together.' 10 ... + 'Please chose one in the list:' 10 10], ... + 'Statistics', [], fileTags, 'normal'); + % Record user selection, or exit if canceled + if isempty(res) + bst_progressBar('stop'); + return; + elseif strcmpi(res, 'all') + GlobalData.Stat.FilterFileTag = ''; + else + GlobalData.Stat.FilterFileTag = ['_' res]; + end + + bst_progressBar('show'); + else + GlobalData.Stat.FilterFileTag = ''; + end + + % ===== RUN TEST ===== + % Get samples + Conditions = gui_stat_common('GetConditions', 'Statistics', GlobalData.Stat.FilterFileTag); + % Display options panel + bstPanel = panel_statRun('CreatePanel', Conditions); + gui_showPanel(bstPanel, 'JavaWindow', 'Statistics', [], 'modal'); + + bst_progressBar('stop'); +end + + +%% ===== GET ROOT NODE ===== +function [nodeRoot, jTree] = GetRootNode(TreeName) %#ok + nodeRoot = []; + % Get panel controls + ctrl = bst_getContext('PanelControls', 'Statistics'); + if isempty(ctrl) + return + end + % Get input tree + switch (TreeName) + case 'StatA' + jTree = ctrl.jTreeFilesA; + case 'StatB' + jTree = ctrl.jTreeFilesB; + end + % Get root node + treeModel = awtinvoke(jTree, 'getModel()'); + nodeRoot = awtinvoke(treeModel, 'getRoot()'); +end + + +%% ===== SET SOURCES/RECORDINGS ===== +% Usage: SetFileType('results') +% SetFileType('data') +function SetFileType(datatype) %#ok + % Get panel controls + ctrl = bst_getContext('PanelControls', 'Statistics'); + % Select corect button + switch lower(datatype) + case 'results' + ctrl.jButtonSources.setSelected(1); + case 'data' + ctrl.jButtonRecordings.setSelected(1); + end +end + + + + + + + + + + \ No newline at end of file diff --git a/brainstorm3/toolbox/gui/panel_statRun.m b/brainstorm3/toolbox/gui/panel_statRun.m new file mode 100644 index 0000000..589b09a --- /dev/null +++ b/brainstorm3/toolbox/gui/panel_statRun.m @@ -0,0 +1,1555 @@ +function varargout = panel_statRun(varargin) +% PANEL_STATRUN: Selection and execution of the process to apply to the samples sets. +% +% USAGE: bstPanelNew = panel_statRun('CreatePanel', panelName) +% panel_statRun('CallBatch') +% panel_statRun('UpdatePanel') +% Comment = panel_statRun('FormatComment', ...) + +% @============================================================================= +% This software is part of The Brainstorm Toolbox +% http://neuroimage.usc.edu/brainstorm +% +% Copyright (c)2010 Brainstorm by the University of Southern California +% This software is distributed under the terms of the GNU General Public License +% as published by the Free Software Foundation. Further details on the GPL +% license can be found at http://www.gnu.org/copyleft/gpl.html. +% +% FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE +% UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY +% WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF +% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY +% LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE. +% +% For more information type "brainstorm licence" at command prompt. +% =============================================================================@ +% +% Authors: Francois Tadel, 2008 +% ----------------------------- Script History --------------------------------- +% FT 24-Jun-2008 Creation +% ------------------------------------------------------------------------------ + +% No parameters +if (nargin == 0) +% Else : execute appropriate local function +elseif ischar(varargin{1}) + if (nargout) + [varargout{1:nargout}] = bst_safeCall(str2func(varargin{1}), varargin{2:end}); + else + bst_safeCall(str2func(varargin{1}), varargin{2:end}); + end +end +end + + +%% ===== CREATE PANEL ===== +function bstPanelNew = CreatePanel(Conditions) %#ok + panelName = 'panel_statRun'; + % Java initializations + import java.awt.*; + import javax.swing.*; + % Constants + HFILLED_WIDTH = 10; + DEFAULT_HEIGHT = 20; + SPINNER_WIDTH = 70; + TEXT_WIDTH = 53; + JCOMBO_DM = java.awt.Dimension(10,25); + jFontText = java.awt.Font('Dialog', java.awt.Font.PLAIN, 10); + windowDim = [350, 0]; + isUpdateAvailable = 0; + + % Get some information about processes + isData = strcmpi(Conditions.DataType, 'data'); + nbSamplesA = length(Conditions.SamplesA); + nbSamplesB = length(Conditions.SamplesB); + [sProcesses, sSimpleTests, sPermTests, sAnova] = GetProcessesList(Conditions.NbSamplesSets, nbSamplesA, nbSamplesB); + + % ===== GET TIME DEFINITION ===== + % Get first input file time ranges + FileTimeVector = GetFileTimeVector(Conditions.SamplesA(1).iStudy, Conditions.SamplesA(1).iItem, isData); + % Get default values for baseline + if (FileTimeVector(1) < 0) && (FileTimeVector(end) > 0) + BaselineBounds = [FileTimeVector(1), 0]; + else +% BaselineBounds = [FileTimeVector(1), .8*FileTimeVector(1) + .2*FileTimeVector(end)]; + BaselineBounds = [FileTimeVector(1), FileTimeVector(end)]; + end + + % ===== CREATE PANEL ===== + jPanelNew = getRiverPanel([6,3], [5,5,0,10]); + % === COMMENT === + jPanelNew.add(JLabel('Comment:')); + jTextComment = JTextField(''); + jTextComment.setPreferredSize(Dimension(HFILLED_WIDTH, DEFAULT_HEIGHT)); + jPanelNew.add('hfill', jTextComment); + + % ===== PROCESSES PANEL ===== + jPanelOp = getRiverPanel([10,10], [0,20,5,20]); + % Statistics type + jComboTestOp = JComboBox(); + jComboTestOp.setPreferredSize(JCOMBO_DM); + jComboTestOp.setMaximumRowCount(30); + % set(jComboTestOp, 'ItemStateChangedCallback', @UpdatePanel); + jPanelOp.add('hfill', jComboTestOp); + % Fill the combo box + FillProcessComboBox(jComboTestOp, sProcesses); + + % === DESCRIPTION === + jLabelDescriptionOp = JLabel('No process available.'); + jLabelDescriptionOp.setVerticalAlignment(JLabel.TOP); + jPanelOp.add('br hfill', jLabelDescriptionOp); + + jTextFactors = []; + % ===== TABBED PANEL ===== + % If no tests possible: create a flat titled panel + if (Conditions.NbSamplesSets == 1) + % Initialize unsed controls to "Null" + jTabbedStat = []; + jComboTestSimple = []; + jComboTestPermut = []; + jLabelDescriptionSimple = []; + jLabelDescriptionPermut = []; + jSpinnerNbPermut = []; + % Add a title to the panel + jPanelOp.setPreferredSize(Dimension(150,120)); + jPanelOp.setBorder(javax.swing.BorderFactory.createTitledBorder('Process selection')); + jPanelNew.add('br hfill', jPanelOp); + windowDim(2) = 470; + % Else: Tabbed pane + else + jTabbedStat = JTabbedPane(); + % set(jTabbedStat, 'StateChangedCallback', @UpdatePanel); + % PROCESSES PANEL + jTabbedStat.addTab('Processes', jPanelOp); + % ===== SIMPLE TESTS ===== + jPanelSimple = getRiverPanel([10,10], [0,20,5,20]); + % === TEST TYPE === + % Statistics type + jComboTestSimple = JComboBox(); + jComboTestSimple.setPreferredSize(JCOMBO_DM); + % set(jComboTestSimple, 'ItemStateChangedCallback', @UpdatePanel); + jPanelSimple.add('hfill', jComboTestSimple); + % Fill the combo box + FillProcessComboBox(jComboTestSimple, sSimpleTests); + + % === DESCRIPTION === + jLabelDescriptionSimple = JLabel('No test available.'); + jLabelDescriptionSimple.setVerticalAlignment(JLabel.TOP); + jPanelSimple.add('br hfill', jLabelDescriptionSimple); + jTabbedStat.addTab('Simple tests', jPanelSimple); + + % ===== PERMUTATION TESTS ===== + jPanelPermut = getRiverPanel([10,10], [0,20,5,20]); + % Statistics type + jComboTestPermut = JComboBox(); + jComboTestPermut.setPreferredSize(JCOMBO_DM); + % set(jComboTestPermut, 'ItemStateChangedCallback', @UpdatePanel); + jPanelPermut.add('hfill', jComboTestPermut); + % Fill the combo box + FillProcessComboBox(jComboTestPermut, sPermTests); + + % === NUMBER OF PERMUTATIONS === + jPanelPermut.add('br', JLabel('Number of permutations: ')); + spinmodel = SpinnerNumberModel(10000, 1, 50000, 100); + jSpinnerNbPermut = JSpinner(spinmodel); + jSpinnerNbPermut.setPreferredSize(Dimension(SPINNER_WIDTH, DEFAULT_HEIGHT)); + jPanelPermut.add(jSpinnerNbPermut); + + % === DESCRIPTION === + jLabelDescriptionPermut = JLabel('No test available.'); + jLabelDescriptionPermut.setVerticalAlignment(JLabel.TOP); + jPanelPermut.add('br hfill', jLabelDescriptionPermut); + + jTabbedStat.addTab('Permutations', jPanelPermut); + + + % ===== ANOVA ===== + jPanelAnova = getRiverPanel([10,10], [0,20,5,20]); + % Statistics type + jComboAnova= JComboBox(); + jComboAnova.setPreferredSize(JCOMBO_DM); + set(jComboAnova, 'ItemStateChangedCallback', @UpdatePanel); + jPanelPermut.add('hfill', jComboAnova); + % Fill the combo box + FillProcessComboBox(jComboAnova, sAnova); + + % === FACTORS === + jPanelAnova.add('br', JLabel('Factors: ')); + % # of subjects: + NS = numel(unique({Conditions.SamplesA.SubjectName})); + + [u,i,j] = unique([Conditions.SamplesA.iItem]) + if numel(u)>1 + Comments = bst_getContext('Study', [Conditions.SamplesA.iStudy]); + Comments = [Comments.Data]; + + cellfun2('getfield', cellfun2('fliplr', getfield2(bst_getContext('Study', [Conditions.SamplesA.iStudy]), 'Data')), 'Comment')' + + SelectedItem = listdlg('ListString', ... + [strvcat({Conditions.SamplesA(i).Condition}) repmat(' : ',length(u),1) strvcat({Conditions.SamplesA(i).Comment})],... + 'SelectionMode','single', 'Name', 'Pick an inverse model'); + if isempty(SelectedItem) + return + end + Conditions.SamplesA = Conditions.SamplesA(j == SelectedItem); + assignin('base', 'sA', Conditions.SamplesA); + end + % Get samples + %GlobalData.Stat.FilterFileTag = ['_timemean' ]; + %Conditions = gui_stat_common('GetConditions', 'Statistics', GlobalData.Stat.FilterFileTag); + + tbl.h{1}='Condition'; + tbl.t = {Conditions.SamplesA.Condition}; + tbl.t = tbl.t(:) + + if NS > 1 + tbl.t = tbl.t(1+[0:3]*NS) + end + %tbl.h{end+1}='Comment'; + %tbl.t(:,end+1) = {Conditions.SamplesA(1+[0:3]*15).Comment}; + tbl.h{end+1}='Factor 1'; + tbl.t(:,end+1)={ '1' ; '2' ; '1' ; '2' }; + tbl.h{end+1}='Factor 2'; + tbl.t(:,end+1)={ '1' ; '1' ; '2' ; '2' }; + + jTableFactors = JTable(tbl.t,tbl.h); + jTableFactors.setPreferredSize(Dimension(4*SPINNER_WIDTH, 5*DEFAULT_HEIGHT)); + jPanelAnova.add('br', jTableFactors); + + % === DESCRIPTION === + jLabelDescriptionAnova = JLabel('No option available.'); + jLabelDescriptionAnova.setVerticalAlignment(JLabel.TOP); + jPanelAnova.add('br hfill', jLabelDescriptionAnova); + + jTabbedStat.addTab('ANOVA', jPanelAnova); + jTabbedStat.setEnabledAt(3, 1); + + % Statistical tests : min two samples in each condition + if (nbSamplesA <= 1) || (nbSamplesB <= 1) + % Disable "simple tests" and "permutations" tabs if not enough samples + jTabbedStat.setEnabledAt(1, 0); + jTabbedStat.setEnabledAt(2, 0); + end + jTabbedStat.setPreferredSize(Dimension(150,175)); + jPanelNew.add('br hfill', jTabbedStat); + windowDim(2) = 350; + end + + % === TIME WINDOWS === + jPanelTimeWindows = getRiverPanel([2,3], [0,18,10,0], 'Time windows'); + % === WINDOW A === + jLabelTimeA = JLabel('Time window: '); + jPanelTimeWindows.add(jLabelTimeA); + % Time window A : START + jTextTimeStartA = JTextField(''); + jTextTimeStartA.setPreferredSize(Dimension(TEXT_WIDTH, DEFAULT_HEIGHT)); + jTextTimeStartA.setHorizontalAlignment(JTextField.RIGHT); + jTextTimeStartA.setFont(jFontText); + jPanelTimeWindows.add('tab', jTextTimeStartA); + jLabelSeparatorA = JLabel('-'); + jPanelTimeWindows.add(jLabelSeparatorA); + % Time window A : STOP + jTextTimeStopA = JTextField(''); + jTextTimeStopA.setPreferredSize(Dimension(TEXT_WIDTH, DEFAULT_HEIGHT)); + jTextTimeStopA.setHorizontalAlignment(JTextField.RIGHT); + jTextTimeStopA.setFont(jFontText); + jPanelTimeWindows.add(jTextTimeStopA); + + % Set time controls callbacks + TimeUnit = gui_validateText(jTextTimeStartA, [], [], FileTimeVector, [], jTextTimeStopA, FileTimeVector(1), @UpdatePanel); + TimeUnit = gui_validateText(jTextTimeStopA, [], [], FileTimeVector, jTextTimeStartA, [], FileTimeVector(end), @UpdatePanel); + % Display time unit + jLabelTimeUnitA = JLabel(TimeUnit); + jPanelTimeWindows.add(jLabelTimeUnitA); + + % === WINDOW B === + jLabelTimeB = JLabel('Baseline: '); + jPanelTimeWindows.add('br', jLabelTimeB); + % Time window B : START + jTextTimeStartB = JTextField(''); + jTextTimeStartB.setPreferredSize(Dimension(TEXT_WIDTH, DEFAULT_HEIGHT)); + jTextTimeStartB.setHorizontalAlignment(JTextField.RIGHT); + jTextTimeStartB.setFont(jFontText); + jPanelTimeWindows.add('tab', jTextTimeStartB); + jLabelSeparatorB = JLabel('-'); + jPanelTimeWindows.add(jLabelSeparatorB); + % Time window B : STOP + jTextTimeStopB = JTextField(''); + jTextTimeStopB.setPreferredSize(Dimension(TEXT_WIDTH, DEFAULT_HEIGHT)); + jTextTimeStopB.setHorizontalAlignment(JTextField.RIGHT); + jTextTimeStopB.setFont(jFontText); + jPanelTimeWindows.add(jTextTimeStopB); + + % Set time controls callbacks + gui_validateText(jTextTimeStartB, [], [], FileTimeVector, [], jTextTimeStopB, BaselineBounds(1), []); + gui_validateText(jTextTimeStopB, [], [], FileTimeVector, jTextTimeStartB, [], BaselineBounds(2), []); + % Display time unit + jLabelTimeUnitB = JLabel(TimeUnit); + jPanelTimeWindows.add(jLabelTimeUnitB); + jPanelNew.add('br hfill', jPanelTimeWindows); + + % ===== SOURCES PANEL ===== + jPanelSources = getRiverPanel([0,0], [0,0,0,0], 'Sources'); + jCheckAbsoluteValues = JCheckBox('Use absolute values of sources activations'); + jCheckAbsoluteValues.setMargin(Insets(0,10,0,0)); + set(jCheckAbsoluteValues, 'ActionPerformedCallback', @CheckAbsoluteValues_Callback); + jPanelSources.add(jCheckAbsoluteValues); + % Add panel only if processing sources (results) + if ~isData + jPanelNew.add('br hfill', jPanelSources); + jCheckAbsoluteValues.setSelected(1); + windowDim(2) = windowDim(2) + 70; + else + jCheckAbsoluteValues.setVisible(0); + jCheckAbsoluteValues.setSelected(0); + end + + % ===== CLUSTER PANEL CONFIG ===== + jCheckCluster = []; + jCheckAvgCluster = []; + jListCluster = []; + iScouts = []; + if (Conditions.NbSamplesSets == 1) + errMsg = ''; + % === ELECTRODES CLUSTERS === + if isData + clusterType = 'Cluster'; + % Get all available clusters for these results + [sClusters, iClusters] = GetClusters(Conditions); + % ERROR 1 + if isnumeric(sClusters) && (sClusters == -1) + errMsg = 'No channel file available for at least one file.'; + % ERROR 2 + elseif isnumeric(sClusters) && (sClusters == -2) + errMsg = 'No cluster available. Use "Cluster" tab to create one.'; + % SUCCESS: Get selected clusters + else + [tmp__, iSelect] = panel_clusters('GetSelectedClusters'); + end + + % === SCOUTS === + else + clusterType = 'Scout'; + % Get selected scouts + [sClusters, iClusters] = GetScouts(Conditions); + [sSelClusters, iSelClusters] = panel_scouts('GetSelectedScouts'); + % ERROR 1 + if isnumeric(sClusters) && (sClusters == -1) + errMsg = 'The sources files were not computed for the same cortical surface.'; + % ERROR 2 + elseif isnumeric(sClusters) && (sClusters == -2) + errMsg = 'No scout available. Use "Scout" tab to create one.'; + % SUCCESS: Get selected scouts + else + iSelect = find(ismember(iClusters, iSelClusters)); + end + end + + % Create panel + jPanelCluster = getRiverPanel([0,0], [0,12,0,12], [clusterType 's']); + % If error: just add show the error message + if ~isempty(errMsg) + windowDim(2) = windowDim(2) + 50; + jPanelCluster.add(JLabel([' ' errMsg])); + % Else: clusters are available + else + windowDim(2) = windowDim(2) + 120; + % Use only selected scouts/clusters + jCheckCluster = JCheckBox(['Use only selected ' clusterType 's']); + jPanelCluster.add(jCheckCluster); + % Average cluster checkbox + if (length(sClusters) > 1) + jCheckAvgCluster = JCheckBox(['Merge selected ' clusterType 's'], 0); + %jPanelCluster.setMargin(Insets(0,12,0,15)); + jPanelCluster.add(jCheckAvgCluster); + end + + % === JLIST: CLUSTERS === + % Create list (display labels of all clusters) + paddedLabels = cellfun(@(c)cat(2,' ',c,' '),{sClusters.Label}, 'UniformOutput', 0); + jListCluster = JList(paddedLabels); + jListCluster.setLayoutOrientation(jListCluster.HORIZONTAL_WRAP); + jListCluster.setVisibleRowCount(-1); + % Pre-select clusters that are selected in the GUI + if ~isempty(iSelect) + jListCluster.setSelectedIndices(iSelect - 1); + end + % Create scroll panel + jScrollPanelCluster = JScrollPane(jListCluster); + jPanelCluster.add('br hfill vfill', jScrollPanelCluster); + end + jPanelNew.add('br vfill hfill', jPanelCluster); + end + + % ===== OUTPUT PANEL ===== + if (Conditions.NbSamplesSets == 1) + jPanelOutput = getRiverPanel([0,3], [0,10,10,10], 'Output'); + buttonGroupOutput = ButtonGroup(); + % OUTPUT: Brainstorm Database + jRadioOutputDatabase = JRadioButton('Brainstorm database', 1); + jRadioOutputDatabase.setToolTipText(['Brainstorm database:
' ... + 'All the files that are generated are automatically added to database.

'... + ' - One output file:
' ... + 'Most of the processes produce only one file that is stored either,
' ... + 'in the original condition or in an "Analysis" node.

' ... + ' - Multiple output files:
' ... + 'The processes that are marked "[sample by sample]" produce one file
' ... + 'for each file A (or couple of files A/B) in the sample list.']); + buttonGroupOutput.add(jRadioOutputDatabase); + jPanelOutput.add(jRadioOutputDatabase); + % Overwrite original files + jPanelOutput.add('br', JLabel(' ')); + jCheckOverwriteFiles = JCheckBox('Overwrite initial files', 1); + jPanelOutput.add(jCheckOverwriteFiles); + % OUTPUT: Condition path (check) + jPanelOutput.add('br', JLabel(' ')); + jCheckOutputCond = JCheckBox('Select output condition'); + jPanelOutput.add(jCheckOutputCond); + % OUTPUT: Condition path (text) + jTextOutputCond = JTextField(''); + jTextOutputCond.setMargin(Insets(0,0,0,20)); + jPanelOutput.add('hfill', jTextOutputCond); + % OUTPUT: User file + jRadioOutputFile = JRadioButton('User defined file: All data is stored in one unique file'); + buttonGroupOutput.add(jRadioOutputFile); + jPanelOutput.add('br', jRadioOutputFile); + % OUTPUT: Matlab variable + jRadioOutputMatlab = JRadioButton('Export to a Matlab variable'); + buttonGroupOutput.add(jRadioOutputMatlab); + jPanelOutput.add('br', jRadioOutputMatlab); + jPanelNew.add('br hfill', jPanelOutput); + % Callbacks + set(jRadioOutputDatabase, 'ActionPerformedCallback', @UpdateOutput); + set(jRadioOutputFile, 'ActionPerformedCallback', @UpdateOutput); + set(jRadioOutputMatlab, 'ActionPerformedCallback', @UpdateOutput); + set(jCheckOverwriteFiles, 'ActionPerformedCallback', @UpdateOutput); + set(jCheckOutputCond, 'ActionPerformedCallback', @UpdateOutput); + else + jRadioOutputDatabase = []; + jRadioOutputFile = []; + jRadioOutputMatlab = []; + jCheckOverwriteFiles = []; + jCheckOutputCond = []; + jTextOutputCond = []; + end + + % ===== VALIDATION BUTTONS ===== + % Help + jButtonHelp = JButton('Help'); + jButtonHelp.setForeground(Color(.7, 0, 0)); + if (Conditions.NbSamplesSets == 1) + set(jButtonHelp, 'ActionPerformedCallback', @(h,ev)bst_help('PanelStatRunProcess.html', 1)); + else + set(jButtonHelp, 'ActionPerformedCallback', @(h,ev)bst_help('PanelStatRunStat.html', 1)); + end + jPanelNew.add('br right', jButtonHelp); + % Cancel + jButtonCancel = JButton('Cancel'); + set(jButtonCancel, 'ActionPerformedCallback', @ButtonCancel_Callback); + jPanelNew.add(jButtonCancel); + % Run + jButtonRun = JButton('Run'); + set(jButtonRun, 'ActionPerformedCallback', @(h,ev)bst_safeCall(@ButtonRun_Callback)); + jPanelNew.add(jButtonRun); + + % Set main panel size + jPanelNew.setPreferredSize(Dimension(windowDim(1), windowDim(2))); + + % Create the BstPanel object that is returned by the function + % => constructor BstPanel(jHandle, panelName, sControls) + bstPanelNew = BstPanel(panelName, ... + jPanelNew, ... + struct('jListCluster', jListCluster)); + + % Set UpdatePanel callbacks + set(jComboTestOp, 'ItemStateChangedCallback', @ComboUpdatePanel); + set(jComboTestPermut, 'ItemStateChangedCallback', @ComboUpdatePanel); + set(jComboTestSimple, 'ItemStateChangedCallback', @ComboUpdatePanel); + set(jCheckCluster, 'ActionPerformedCallback', @UpdatePanel); + set(jTabbedStat, 'StateChangedCallback', @UpdatePanel); + % Update panel + drawnow(); + isUpdateAvailable = 1; + UpdatePanel(); + + + +%% ================================================================================= +% === INTERNAL CALLBACKS ========================================================== +% ================================================================================= +%% ===== COMBOBOX : UPDATE PANEL ===== + function ComboUpdatePanel(hObject, ev) + if (ev.getStateChange() == ev.SELECTED) + UpdatePanel(); + end + end + +%% ===== CHECKBOX : "ABSOLUTE VALUES" ===== + function CheckAbsoluteValues_Callback(varargin) + if ~jCheckAbsoluteValues.isSelected() + isConfirmed = java_dialog('confirm', ['Please keep this option selected, unless you know exactly what you are doing.' 10 10 ... + 'Are you sure you want to use relative values for sources activations ?'], 'Processes'); + if ~isConfirmed + jCheckAbsoluteValues.setSelected(1); + end + end + end + +%% ===== UPDATE OUTPUT PANEL ===== + function UpdateOutput(varargin) + % Force condition path + isDatabase = jRadioOutputDatabase.isEnabled() && jRadioOutputDatabase.isSelected(); + jCheckOutputCond.setEnabled(isDatabase); + jTextOutputCond.setEnabled(isDatabase); + if ~isDatabase + jCheckOutputCond.setSelected(0); + end + % Condition path + isForceCond = jCheckOutputCond.isSelected(); + jTextOutputCond.setVisible(isForceCond); + % Cannot select both options at the same time + if isForceCond + jCheckOverwriteFiles.setSelected(0); + end + end + +%% ===== FILL PROCESSES COMBO BOX ===== + function FillProcessComboBox(jCombo, sList) + import org.brainstorm.list.BstListItem; + % Fill the combo box + for i = 1:length(sList) + % Convert labels to HTML + label = sList(i).Comment; + if isempty(sList(i).Name) + label = ['

' label]; + else + label = ['

' label]; + end + % Add item in combo list + jCombo.addItem(BstListItem(sList(i).Name, '', label, sList(i).Description)); + end + end + +%% ===== RUN ===== + function ButtonRun_Callback(varargin) + CallBatch(); + end + +%% ===== GET SELECTED PROCESS ===== + function [sProcess, selectedTab] = GetSelectedProcess() + % Get selected tab + if isempty(jTabbedStat) + selectedTab = 0; + else + selectedTab = jTabbedStat.getSelectedIndex(); + end + % Get the selected combo box + switch (selectedTab) + case 0 + jCombo = jComboTestOp; + sList = sProcesses; + case 1 + jCombo = jComboTestSimple; + sList = sSimpleTests; + case 2 + jCombo = jComboTestPermut; + sList = sPermTests; + case 3 + jCombo = jComboAnova; + sList = sAnova; + end + % Get selected process + jItem = jCombo.getSelectedItem(); + if isempty(jItem) + sProcess = []; + return + end + processName = jItem.getType(); + % Find in it in processes list + iProcess = find(strcmpi({sList.Name}, processName)); + sProcess = sList(iProcess(1)); + end + +%% ===== UPDATE PANEL ===== + function UpdatePanel(varargin) + if ~isUpdateAvailable + return + end + % Get selected process + [sProcess, selectedTab] = GetSelectedProcess(); + if isempty(sProcess) + return + end + isProcessTitle = isempty(sProcess.Category); + % Convert description to HTML + processDesc = ['' strrep(sProcess.Description, char(10), '
')]; + % Update tabbed panels + switch(selectedTab) + % === PROCESSES === + case 0 + jLabelDescriptionOp.setText(processDesc); + % === SIMPLE TESTS === + case 1 + jLabelDescriptionSimple.setText(processDesc); + % === PERM TESTS === + case 2 + jLabelDescriptionPermut.setText(processDesc); + % Compute maximum number of permutations + Na = nbSamplesA; + Nb = nbSamplesB; + if sProcess.isPaired + % nbPermMax = 2^(Na)-1; + nbPermMax = 2^(Na-1)-1; + else + nbPermMax = factorial(Na+Nb) / (factorial(Na)*factorial(Nb)) - 1; + end + % Limit default number of permutations + NB_PERM_LIMIT = 10000; + nbPerm = min(nbPermMax, NB_PERM_LIMIT); + % Update spinner model + spinmodel = awtinvoke(jSpinnerNbPermut, 'getModel()'); + awtinvoke(spinmodel, 'setValue(Ljava.lang.Object;)', java.lang.Double(nbPerm)); + awtinvoke(spinmodel, 'setMaximum(Ljava.lang.Comparable;)', java.lang.Double(nbPermMax)); + end + + % ===== UPDATE COMMENT ===== + % Get time + TimeRange = GetTimeWindows(TimeUnit); + % Format comment + Comment = FormatComment(sProcess, Conditions, TimeRange); + % Too long comment + if (length(Comment) > 80) + % Replace comment by date + c = clock; + strTime = sprintf('%02.0f%02.0f%02.0f_%02.0f%02.0f', c(1)-2000, c(2:5)); + Comment = [sProcess.Name ': ' strTime]; + end + % Update comment + awtinvoke(jTextComment, 'setText(Ljava.lang.String;)', Comment); + + % ===== UPDATE TIME WINDOW PANEL ===== + if ~isempty(jTextTimeStartB) + % Time window + jLabelTimeA.setEnabled(~isProcessTitle); + jTextTimeStartA.setEnabled(~isProcessTitle); + jTextTimeStopA.setEnabled(~isProcessTitle); + jLabelSeparatorA.setEnabled(~isProcessTitle); + jLabelTimeUnitA.setEnabled(~isProcessTitle); + % Baseline + jLabelTimeB.setEnabled(sProcess.UseBaseline); + jTextTimeStartB.setEnabled(sProcess.UseBaseline); + jTextTimeStopB.setEnabled(sProcess.UseBaseline); + jLabelSeparatorB.setEnabled(sProcess.UseBaseline); + jLabelTimeUnitB.setEnabled(sProcess.UseBaseline); + end + + % ===== UPDATE SOURCES PANEL ===== + jCheckAbsoluteValues.setSelected(sProcess.isSourceAbsolute > 0); + jCheckAbsoluteValues.setEnabled(sProcess.isSourceAbsolute ~= 2); + + % ===== UPDATE CLUSTER PANEL ===== + % Cluster checkbox + isAllowCluster = ismember(sProcess.Category, {'Extract'}); + if ~isempty(jCheckCluster) + jCheckCluster.setEnabled(isAllowCluster); + if ~isAllowCluster + jCheckCluster.setSelected(0); + end + % Cluster jlists + isCluster = ~isempty(jCheckCluster) && jCheckCluster.isSelected(); + if ~isempty(jListCluster) + jListCluster.setEnabled(isCluster); + end + if ~isempty(jCheckAvgCluster) + jCheckAvgCluster.setEnabled(isCluster); + end + else + isCluster = 0; + end + + % ===== UPDATE OUTPUT PANEL ===== + if ~isempty(jRadioOutputDatabase) + AllowExport = ismember(sProcess.Category, {'Extract'}); + % Enable Database: only if no cluster defined + jRadioOutputDatabase.setEnabled(~isCluster && ~isProcessTitle); + % Enable File/Matlab + jRadioOutputFile.setEnabled(AllowExport > 0); + jRadioOutputMatlab.setEnabled(AllowExport > 0); + % Select "File": if cluster, or time/mean, time/var, or extract + if (isCluster || AllowExport) + % Change selection only if Database is selected + if ~jRadioOutputMatlab.isSelected() + jRadioOutputFile.setSelected(1); + end + else + jRadioOutputDatabase.setSelected(1); + end + + % "Overwrite file" checkbox + AllowOverwrite = ismember(sProcess.Category, {'Filter'}); + isOverwriteEnabled = jRadioOutputDatabase.isEnabled() && AllowOverwrite; + jCheckOverwriteFiles.setEnabled(isOverwriteEnabled); + isOverwriteSelected = isOverwriteEnabled && sProcess.DefaultOverwrite; + jCheckOverwriteFiles.setSelected(isOverwriteSelected); + % Update object selections + UpdateOutput(); + end + end + + +%% ===== GET TIME WINDOWS ===== + function [Time, Baseline, iTime, iBaseline] = GetTimeWindows(TimeUnit) + % Get time window to process + Time = [getValue(jTextTimeStartA), getValue(jTextTimeStopA)]; + % Get baseline, if defined + if ~isempty(jTextTimeStartB) + Baseline = [getValue(jTextTimeStartB), getValue(jTextTimeStopB)]; + else + Baseline = []; + end + % Apply correct time units + switch(TimeUnit) + case 'ms' + % Convert miliseconds -> seconds + Time = Time / 1000; + Baseline = Baseline / 1000; + otherwise + % Nothing to do + end + % Get indices in intial FileTimeVector + iTimeBounds = findclosest(Time, FileTimeVector); + % If whole data wanted and sampling frequency very high: might lack of time precision... + % Try to correct this and select the last sample if it is obviously this that the user wants + if (iTimeBounds(2) ~= length(FileTimeVector)) && (FileTimeVector(end)-FileTimeVector(iTimeBounds(2)) < 1e-5) + iTimeBounds(2) = length(FileTimeVector); + end + % Build time indices + iTime = iTimeBounds(1) : iTimeBounds(2); + iBaselineBounds = findclosest(Baseline, FileTimeVector); + iBaseline = iBaselineBounds(1) : iBaselineBounds(2); + end + +%% ===== GET VALUES ===== + function val = getValue(jText) + % Get and check value + val = str2double(char(jText.getText())); + if isnan(val) || isempty(val) + val = []; + end + end + + +%% ===== COMPUTE TEST ===== + function CallBatch() + % ===== PREPARE OPTIONS ===== + OPTIONS = struct(); + % Is processing recordings or results + OPTIONS.isData = isData; + % Get new file comment + OPTIONS.Comment = deblank(strtrim(char(jTextComment.getText()))); + % Is absolute values (for sources only) + OPTIONS.isAbsoluteValues = ~isData && jCheckAbsoluteValues.isSelected(); + % Get selected process + [OPTIONS.sProcess, selectedTab] = GetSelectedProcess(); + if isempty(OPTIONS.sProcess) || isempty(OPTIONS.sProcess.Name) + bst_error('Please select a process.', 'Processes', 0); + return + end + % Get number of permutations + if ~isempty(jSpinnerNbPermut) + OPTIONS.nbPermutation = jSpinnerNbPermut.getValue(); + else + OPTIONS.nbPermutation = 0; + end + % List factors associated with each dataset + if ~isempty(jTextFactors) + OPTIONS.Factors= char(jTextFactors.getText()); + else + OPTIONS.Factors = []; + end + + % Overwrite initial files ? + OPTIONS.isOverwriteFiles = ~isempty(jCheckOverwriteFiles) && jCheckOverwriteFiles.isSelected(); + % Get output mode + if isempty(jRadioOutputDatabase) || jRadioOutputDatabase.isSelected() + OPTIONS.OutputType = 'database'; + elseif jRadioOutputFile.isSelected() + OPTIONS.OutputType = 'file'; + elseif jRadioOutputMatlab.isSelected() + OPTIONS.OutputType = 'matlab'; + end + % Force output condition + if ~isempty(jCheckOutputCond) && jCheckOutputCond.isSelected() + % Get the user defined condition name + outputCond = char(jTextOutputCond.getText()); + % Split condition path + condPath = strSplit(outputCond); + % Check if condition name is valid + if isempty(outputCond) || (~strcmpi(outputCond,'@inter') && (length(condPath) ~= 2)) + bst_error(['Invalid condition path.' 10 10 ... + 'Condition path must be one the following:' 10 ... + ' - SubjectName/ConditionName' 10 ... + ' - SubjectName/@intra' 10 ... + ' - @inter'], 'Processes', 0); + return + end + % === Convert SubjectName in SubjectPath === + if (length(condPath) == 2) + % Get subject and condition names + SubjectName = condPath{1}; + ConditionName = condPath{2}; + % Get subject + sSubject = bst_getContext('Subject', SubjectName, 1); + if isempty(sSubject) + bst_error(['Subject does not exist: "' SubjectName '"'], 'Processes', 0); + return + end + % Build new output condition path + SubjectPath = fileparts(sSubject.FileName); + outputCond = fullfile(SubjectPath, ConditionName); + end + % Valid + OPTIONS.ForceOutputCond = outputCond; + else + OPTIONS.ForceOutputCond = []; + end + % Is "Cluster" analysis + OPTIONS.isCluster = ~isempty(jCheckCluster) && jCheckCluster.isSelected(); + OPTIONS.isClusterAverage = OPTIONS.isCluster && ~isempty(jCheckAvgCluster) && jCheckAvgCluster.isSelected(); + % Get selected clusters + if OPTIONS.isCluster + if OPTIONS.isData + % Get selected clusters + iClusters = jListCluster.getSelectedIndices() + 1; + sClusters = panel_clusters('GetClusters', iClusters); + % If no cluster selected: ERROR + if isempty(iClusters) + error('No channel selected in cluster panel.'); + end + % If user asked to merge clusters + if OPTIONS.isClusterAverage + sClustersAvg.Sensors = unique(cat(2, sClusters.Sensors)); + sClustersAvg.Label = ['Clusters:', sprintf(' %s', sClusters.Label)]; + OPTIONS.Clusters = sClustersAvg; + else + OPTIONS.Clusters = sClusters; + end + % Get clusters options + OPTIONS.ClustersOptions = panel_clusters('GetClusterDisplayType'); + else + % Get selected scouts + iSelScouts = jListCluster.getSelectedIndices() + 1; + % If no scout selected: ERROR + if isempty(iSelScouts) + error('No scout selected in cluster panel.'); + end + % Get scouts description + [sScouts, iScouts] = panel_scouts('GetCurrentScouts'); + iScouts = iScouts(iSelScouts); + sScouts = panel_scouts('GetScouts', iScouts); + % If user asked to merge scouts + if OPTIONS.isClusterAverage + sScoutsAvg = sScouts(1); + sScoutsAvg.Vertices = unique([sScouts.Vertices]); + sScoutsAvg.Label = ['Scouts:', sprintf(' %s', sScouts.Label)]; + OPTIONS.Clusters = sScoutsAvg; + else + OPTIONS.Clusters = sScouts; + end + % Get scouts options + OPTIONS.ClustersOptions = panel_scouts('GetScoutDisplayType'); + end + else + OPTIONS.sClusters = []; + OPTIONS.ClustersOptions = []; + end + % Get time + [OPTIONS.Time, OPTIONS.Baseline, OPTIONS.iTime, OPTIONS.iBaseline] = GetTimeWindows(TimeUnit); + + % Get files to batch + OPTIONS.Conditions = Conditions; + % Close panel + gui_hidePanel('panel_statRun'); + + % ===== PERFORM SOME VERIFICATIONS ===== + % Not supposed to compute zscore on recordings + if OPTIONS.isData && strcmpi(OPTIONS.sProcess.Name, 'zscore') + res = java_dialog('confirm', ['Warning: You are about to compute the z-score normalization on recordings.' 10 10 ... + 'We recommand first to solve the inverse problem, and then to ' 10 ... + 'apply the zscore normalization on the cortical sources.' 10 10 ... + 'Are you sure you want to compute the z-score of the recordings ?'], 'Warning'); + if ~res + return; + end + end + + % ===== CALL BATCH FUNCTION ===== + bst_batch(OPTIONS); + + % Update "Processes" panels + if OPTIONS.isOverwriteFiles + panel_processes('ResetPanel'); + else + if (Conditions.NbSamplesSets == 1) && ~gui_stat_common('CheckConditions', 'Processes') + panel_processes('ResetPanel'); + elseif (Conditions.NbSamplesSets == 2) && ~gui_stat_common('CheckConditions', 'Statistics') + panel_stat('ResetPanel'); + end + end + end +end + + +%% ================================================================================= +% === EXTERNAL CALLBACKS ========================================================== +% ================================================================================= +%% ===== CANCEL ===== +function ButtonCancel_Callback(varargin) + % Close panel + gui_hidePanel('panel_statRun'); +end + + +%% ===== BUILD LIST OF PROCESSES ===== +% USAGE: [sProcesses, sSimpleTests, sPermTests, sAnova] = GetProcessesList(NbSamplesSets, nbSamplesA, nbSamplesB, isData) +% [sProcesses, sSimpleTests, sPermTests, sAnova] = GetProcessesList('All') +function [sProcesses, sSimpleTests, sPermTests, sAnova] = GetProcessesList(NbSamplesSets, nbSamplesA, nbSamplesB, isData) + % If get all processes: ignore all other arguments + isAll = strcmpi(NbSamplesSets, 'All'); + % Initialize list + sModel = struct('Name', '', ... + 'Comment', '', ... + 'Description', '', ... + 'FileTag', '', ... + 'Category', '', ... + 'UseBaseline', 0, ... + 'DefaultOverwrite', 0, ... + 'isSourceAbsolute', 1, ... % If value=2, absolute value for sources is FORCED + 'isPaired', 0, ... + 'blockDimension', 0, ... % Dimension in which the data matrix can be split (0=none, 1=channels, 2=time) + 'isAvgRef', 1); % Compute EEG average reference before processing + sProcesses = repmat(sModel, 0); + sSimpleTests = repmat(sModel, 0); + sPermTests = repmat(sModel, 0); + sAnova = repmat(sModel, 0); + + % ===== "A" PROCESSES ===== + if isAll || (NbSamplesSets == 1) + % === FILTERS TO APPLY TO FILES === + sProcess = sModel; + sProcess.Comment = '==== PROCESS FILES ==========='; + sProcess.Description = 'Please select a process...'; + sProcess.isSourceAbsolute = 2; + sProcesses(end + 1) = sProcess; + % Z-SCORE + sProcess = sModel; + sProcess.Name = 'zscore'; + sProcess.Comment = ' Z-score noise normalization'; + sProcess.FileTag = '| zscore'; + sProcess.Description = ['For each channel:' 10 ... + '1) Compute mean m and variance v for baseline.' 10 ... + '2) For each time sample, substract m and divide by v.']; + sProcess.Category = 'Filter'; + sProcess.UseBaseline = 1; + sProcess.isSourceAbsolute = 2; + sProcess.blockDimension = 1; + sProcess.isAvgRef = 0; + sProcesses(end + 1) = sProcess; + % BASELINE REMOVAL + sProcess = sModel; + sProcess.Name = 'baseline'; + sProcess.Comment = ' Remove baseline mean (DC offset)'; + sProcess.FileTag = '| bl'; + sProcess.Description = ['For each channel:' 10 ... + '1) Compute the mean m for the baseline.' 10 ... + '2) For all the time samples, substract m.']; + sProcess.Category = 'Filter'; + sProcess.UseBaseline = 1; + sProcess.isSourceAbsolute = 0; + sProcess.DefaultOverwrite = 1; + sProcess.blockDimension = 1; + sProcess.isAvgRef = 0; + sProcesses(end + 1) = sProcess; + % EEG AVERAGE REFERENCE + if isAll || isData + sProcess = sModel; + sProcess.Name = 'avgref'; + sProcess.Comment = ' EEG Average reference'; + sProcess.FileTag = '| avgref'; + sProcess.Description = ['Note that all the processes that are not in the "Process files" category use average reference by default. ' 10 ... + 'No effect on MEG recordings.']; + sProcess.Category = 'Filter'; + sProcess.UseBaseline = 0; + sProcess.isSourceAbsolute = 0; + sProcess.DefaultOverwrite = 1; + sProcess.blockDimension = 2; + sProcesses(end + 1) = sProcess; + end + % BANDPASS FILTERING + sProcess = sModel; + sProcess.Name = 'bandpass'; + sProcess.Comment = ' Bandpass filtering'; + sProcess.FileTag = '| bandpass'; + sProcess.Description = 'Apply a frenquency filter to all the files.'; + sProcess.Category = 'Filter'; + sProcess.isSourceAbsolute = 0; + sProcess.blockDimension = 1; + sProcess.isAvgRef = 0; + sProcesses(end + 1) = sProcess; + % SPATIAL SMOOTHING + if isAll || ~isData + sProcess = sModel; + sProcess.Name = 'ssmooth'; + sProcess.Comment = ' Spatial smoothing'; + sProcess.FileTag = '| ssmooth'; + sProcess.Description = 'Spatial smoothing of the sources.'; + sProcess.Category = 'Filter'; + sProcess.isSourceAbsolute = 2; + sProcess.blockDimension = 0; + sProcess.isAvgRef = 0; + sProcesses(end + 1) = sProcess; + end + % RESAMPLE + if isAll || isData + sProcess = sModel; + sProcess.Name = 'resample'; + sProcess.Comment = ' Resample recordings'; + sProcess.FileTag = '| resample'; + sProcess.Description = 'Resample all the files with a new sampling frequency.'; + sProcess.Category = 'Filter'; + sProcess.isSourceAbsolute = 0; + % sProcess.blockDimension = 1; % PROBLEM WITH PROCESSING BY BLOCKS: DIMENSIONS CHANGE + sProcess.blockDimension = 0; + sProcess.isAvgRef = 0; + sProcesses(end + 1) = sProcess; + end + % CUT STIMULATION ARTIFACT + if isAll || isData + sProcess = sModel; + sProcess.Name = 'cutstim'; + sProcess.Comment = ' Cut stimulation artifact'; + sProcess.FileTag = '| cutstim'; + sProcess.Description = ['Remove the values in the "baseline" time window.' 10 ... + 'Replace them with a linear interpolation.']; + sProcess.Category = 'Filter'; + sProcess.UseBaseline = 1; + sProcess.DefaultOverwrite = 1; + sProcess.blockDimension = 1; + sProcess.isAvgRef = 0; + sProcesses(end + 1) = sProcess; + end + % OPPOSITE VALUES + sProcess = sModel; + sProcess.Name = 'opposite'; + sProcess.Comment = ' Opposite values: -A'; + sProcess.FileTag = '| opposite'; + sProcess.Description = 'Save opposite values for A files.'; + sProcess.Category = 'Filter'; + sProcess.DefaultOverwrite = 1; + sProcess.isSourceAbsolute = 0; + sProcess.blockDimension = 1; + sProcess.isAvgRef = 0; + sProcesses(end + 1) = sProcess; + + % === AVERAGING === + if isAll || (nbSamplesA >= 2) + sProcess = sModel; + sProcess.Comment = '==== AVERAGING ============='; + sProcess.Description = 'Please select a process...'; + sProcess.isSourceAbsolute = 2; + sProcesses(end + 1) = sProcess; + % GRAND-AVERAGE A (MEAN/CONDITION) + sProcess = sModel; + sProcess.Name = 'GAVE'; + sProcess.Comment = ' Average by condition (Grand-average)'; + sProcess.FileTag = ''; + sProcess.Description = ['Grand averages for each condition' 10 ... + 'One output file per condition.']; + sProcess.Category = 'Average'; + sProcesses(end + 1) = sProcess; + % AVERAGE BY SUBJECT + sProcess = sModel; + sProcess.Name = 'SubjAvg'; + sProcess.Comment = ' Average by subject'; + sProcess.FileTag = ''; + sProcess.Description = ['Average all the recordings for each subject.' 10 ... + 'One output file per subject.']; + sProcess.Category = 'Average'; + sProcesses(end + 1) = sProcess; + % AVERAGE A + sProcess = sModel; + sProcess.Name = 'meanA'; + sProcess.Comment = ' Average everything'; + sProcess.FileTag = '<#A#>'; + sProcess.Description = ['Average of all files.' 10 'Only one output file.']; + sProcess.Category = 'Average'; + sProcesses(end + 1) = sProcess; + end + + % === DATA EXTRACTION === + sProcess = sModel; + sProcess.Comment = '==== EXTRACT DATA ==========='; + sProcess.Description = 'Please select a process...'; + sProcess.isSourceAbsolute = 2; + sProcesses(end + 1) = sProcess; + % MEAN FOR A TIME WINDOW + sProcess = sModel; + sProcess.Name = 'timemean'; + sProcess.Comment = ' Average over a time window'; + sProcess.FileTag = '| timemean#TIME#'; + sProcess.Description = ['Average for each file over the selected time window.' 10 ... + 'Use absolute values for sources.']; + sProcess.Category = 'Extract'; + sProcesses(end + 1) = sProcess; + % VARIANCE FOR A TIME WINDOW + sProcess = sModel; + sProcess.Name = 'timevar'; + sProcess.Comment = ' Variance over a time window'; + sProcess.FileTag = '| timevar#TIME#'; + sProcess.Description = 'Variance for each file over the selected time window.'; + sProcess.Category = 'Extract'; + sProcesses(end + 1) = sProcess; + % EXTRACT DATA FROM SAMPLES + sProcess = sModel; + sProcess.Name = 'extract'; + sProcess.Comment = ' Extract data block (cluster,time)'; + sProcess.FileTag = '| extract#TIME#'; + sProcess.Description = 'Get a block of data from each file.'; + sProcess.Category = 'Extract'; + sProcess.isSourceAbsolute = 0; + sProcesses(end + 1) = sProcess; + + % === NEUROMAG RECORDINGS === + if isAll || isData + sProcess = sModel; + sProcess.Comment = '==== NEUROMAG RECORDINGS ====='; + sProcess.Description = 'Please select a process...'; + sProcess.isSourceAbsolute = 2; + sProcesses(end + 1) = sProcess; + % GRADIOMETERS NORM + sProcess = sModel; + sProcess.Name = 'gradnorm'; + sProcess.Comment = ' Norm of gradiometers couples'; + sProcess.FileTag = '| gradnorm'; + sProcess.Description = 'Compute the norm for each gradiometer couple.'; + sProcess.Category = 'Filter'; + sProcess.UseBaseline = 0; + sProcess.isAvgRef = 0; + sProcesses(end + 1) = sProcess; + end + + % === SPECTRAL ANALYSIS === + if isAll || ~isData + sProcess = sModel; + sProcess.Comment = '==== SPECTRAL ANALYSIS ========'; + sProcess.isSourceAbsolute = 2; + sProcess.Description = 'Please select a process...'; + sProcesses(end + 1) = sProcess; + % Frequency-Band SAMPLE SPECTRAL DECOMPOSITION + sProcess = sModel; + sProcess.Name = 'spectDecomp'; + sProcess.Comment = ' Spectral decomposition and statistics'; + sProcess.FileTag = ''; + sProcess.Description = ['Computes power in multiple, standard frequency bands.' 10 ... + 'Yields average, standard deviation and t-statistics across samples.' 10 ... + 'WARNING: Only applies to KERNEL sources files.']; + sProcess.Category = 'Spectral'; + sProcess.isSourceAbsolute = 0; + sProcesses(end + 1) = sProcess; + + % Power spectrum analysis + sProcess = sModel; + sProcess.Name = 'powerSpectrum'; + sProcess.Comment = ' Fourier magnitude'; + sProcess.FileTag = ''; + sProcess.Description = ['Transforms each time series in file into spectral domain (modulus of Fourier transform). ' ... + 'Time vector is replaced by corresponding frequency bin used in Fourier tranform.' 10 ... + 'WARNING: Only applies to KERNEL sources files.']; + sProcess.Category = 'Spectral'; + sProcess.isSourceAbsolute = 0; + sProcesses(end + 1) = sProcess; + end + + % === RECURRENCE MAPS=== + if isAll || ~isData + sProcess = sModel; + sProcess.Comment = '==== RECURRENCE MAPS ========'; + sProcess.isSourceAbsolute = 2; + sProcess.Description = 'Please select a process...'; + sProcesses(end + 1) = sProcess; + sProcess = sModel; + sProcess.Name = 'recMaps'; + sProcess.Comment = ' Recurrence maps of activations'; + sProcess.FileTag = ''; + sProcess.Description = ['Computes recurrence maps across samples.' 10 ... + 'Source maps above 75% activation are thresholded and binarized before being summed across samples.' 10 ... + 'WARNING: Only applies to KERNEL sources files.']; + sProcess.Category = 'Recurrence'; + sProcess.isSourceAbsolute = 1; + sProcesses(end + 1) = sProcess; + end + + % === SCOUTS functional Connectivity === + if isAll || ~isData + sProcess = sModel; + sProcess.Comment = '==== Scouts f-Connectivity ========'; + sProcess.isSourceAbsolute = 2; + sProcess.Description = 'Please select a process...'; + sProcesses(end + 1) = sProcess; + sProcess = sModel; + sProcess.Name = 'fScoutConnect'; + sProcess.Comment = ' Functional connectivity between cortical scouts'; + sProcess.FileTag = ''; + sProcess.Description = ['Evaluate functional connectivity between cortical scouts.' 10 ... + 'Under development: use with caution.' 10 ... + 'WARNING: Only applies to KERNEL sources files.']; + sProcess.Category = 'fConnectivity'; + sProcess.isSourceAbsolute = 0; + sProcesses(end + 1) = sProcess; + end + end + + % ===== "A/B" PROCESSES ===== + if isAll || (NbSamplesSets == 2) + % Differences : same number of samples in each set + if isAll || (nbSamplesA == nbSamplesB) + % DIFFERENCE: A - B + sProcess = sModel; + sProcess.Name = 'diffAB'; + sProcess.Comment = ' A - B'; + sProcess.FileTag = '#A#-#B#'; + sProcess.Description = ['Difference of each couple of samples (A-B).' 10 10 ... + 'Each pair must share the same anatomy.' 10 ... + 'Result is stored in a new condition.']; + sProcess.Category = 'Filter2'; + sProcesses(end + 1) = sProcess; + % AVERAGE: Average(A,B) + sProcess = sModel; + sProcess.Name = 'meanAB'; + sProcess.Comment = ' Average(A,B)'; + sProcess.FileTag = 'Average(#A#,#B#)'; + sProcess.Description = ['Average of each couple of samples (A,B).' 10 10 ... + 'Each pair must share the same anatomy.' 10 ... + 'Result is stored in a new condition.']; + sProcess.Category = 'Filter2'; + sProcesses(end + 1) = sProcess; + end + end + + + % ===== SIMPLE T-TESTS ===== + % NEW: t-test equal variance + sTest = sModel; + sTest.Name = 'ttest'; + sTest.Comment = ' t-test (equal variances)'; + sTest.FileTag = 'ttest: #A# vs. #B#'; + sTest.Description = ['Student''s t-test for equal variances.' 10 10 ... + 'New version: use much less memory.']; + sTest.Category = 'TTest'; + sSimpleTests(end + 1) = sTest; + % NEW: t-test unequal variance + sTest = sModel; + sTest.Name = 'uttest'; + sTest.Comment = ' t-test (unequal variances)'; + sTest.FileTag = 'uttest: #A# vs. #B#'; + sTest.Description = ['Student''s t-test for unequal variances.' 10 10 ... + 'New version: use much less memory.']; + sTest.Category = 'TTest'; + sSimpleTests(end + 1) = sTest; + % Paired t-test and old versions need the same number of samples in both sets + if isAll || (nbSamplesA == nbSamplesB) + % NEW: paired t-test + sTest = sModel; + sTest.Name = 'pttest'; + sTest.Comment = ' t-test (paired)'; + sTest.FileTag = 'pttest: #A# vs. #B#'; + sTest.Description = ['Student''s t-test for paired samples.' 10 10 ... + 'Use for testing conditions across different subjects.' 10 ... + 'New version: use much less memory.']; + sTest.Category = 'TTest'; + sTest.isPaired = 1; + sSimpleTests(end + 1) = sTest; + % OLD: t-test equal variances + sTest = sModel; + sTest.Name = 'old_ttest'; + sTest.Comment = ' OLD t-test (equal variances)'; + sTest.FileTag = 'old_ttest: #A# vs. #B#'; + sTest.Description = ['Student''s t-test for equal variances.' 10 10 ... + 'Old version: might be faster and more precise.']; + sTest.Category = 'TTest'; + sSimpleTests(end + 1) = sTest; + % OLD: t-test unequal variance + sTest = sModel; + sTest.Name = 'old_uttest'; + sTest.Comment = ' OLD t-test (unequal variances)'; + sTest.FileTag = 'old_uttest: #A# vs. #B#'; + sTest.Description = ['Student''s t-test for unequal variances.' 10 10 ... + 'Old version: might be faster and more precise.']; + sTest.Category = 'TTest'; + sSimpleTests(end + 1) = sTest; + % OLD: paired t-test + sTest = sModel; + sTest.Name = 'old_pttest'; + sTest.Comment = ' OLD t-test (paired)'; + sTest.FileTag = 'old_pttest: #A# vs. #B#'; + sTest.Description = ['Student''s t-test for paired samples.' 10 10 ... + 'Use for testing conditions across different subjects.' 10 ... + 'Old version: might be faster and more precise.']; + sTest.isPaired = 1; + sTest.Category = 'TTest'; + sSimpleTests(end + 1) = sTest; + end + + + % ===== PERMUTATION TESTS ===== + % PERM: t-test (independent) + sTest = sModel; + sTest.Name = 'ttest'; + sTest.Comment = ' t-test (independent)'; + sTest.FileTag = 'ttest: #A# vs. #B#'; + sTest.Description = 'Student''s t-test for independent samples.'; + sTest.Category = 'PermTest'; + sPermTests(end + 1) = sTest; + % PERM: t-test (paired) + sTest = sModel; + sTest.Name = 'pairedttest'; + sTest.Comment = ' t-test (paired)'; + sTest.FileTag = 'pairedttest: #A# vs. #B#'; + sTest.Description = 'Student''s t-test for paired samples.'; + sTest.Category = 'PermTest'; + sTest.isPaired = 1; + sPermTests(end + 1) = sTest; + % PERM: Sign of the differences + sTest = sModel; + sTest.Name = 'signtest'; + sTest.Comment = ' signtest (paired)'; + sTest.FileTag = 'signtest: #A# vs. #B#'; + sTest.Description = 'Sign of the differences.'; + sTest.Category = 'PermTest'; + sTest.isPaired = 1; + sPermTests(end + 1) = sTest; + % PERM: Wilcoxon + sTest = sModel; + sTest.Name = 'wilcoxon'; + sTest.Comment = ' wilcoxon (paired)'; + sTest.FileTag = 'wilcoxon: #A# vs. #B#'; + sTest.Description = 'Signed ranks.'; + sTest.Category = 'PermTest'; + sTest.isPaired = 1; + sPermTests(end + 1) = sTest; + % PERM: Difference of the means + sTest = sModel; + sTest.Name = 'difftest'; + sTest.Comment = ' Difference of the means'; + sTest.FileTag = 'difftest: #A# vs. #B#'; + sTest.Description = 'Difference of the means.'; + sTest.Category = 'PermTest'; + sPermTests(end + 1) = sTest; + + % ===== ANOVA ===== + % + sTest = sModel; + sTest.Name = 'RM-Anova'; + sTest.Comment = 'Anova (under dev.)'; + sTest.FileTag = 'Anova'; + sTest.Description = 'Repeated measures parametric Analysis of Variance'; + sTest.Category = 'Anova'; + sAnova(end + 1) = sTest; + +end + + + +%% ===== FORMAT CONDITIONS STRING ===== +function str = FormatConditionsString(Samples, Default) + % If no samples + if isempty(Samples) + str = ''; + return + end + % Get the best way to display the sets names + uniqueCond = unique({Samples.Condition}); + uniqueSubj = unique({Samples.SubjectName}); + isUniqueCond = (length(uniqueCond) == 1); + isUniqueSubj = (length(uniqueSubj) == 1); + % Switch + if isUniqueCond && isUniqueSubj + str = fullfile(uniqueSubj{1}, uniqueCond{1}); + elseif isUniqueCond + str = uniqueCond{1}; + elseif isUniqueSubj + str = uniqueSubj{1}; + else + str = Default; + end +end + + +%% ===== FORMAT COMMENT ===== +function Comment = FormatComment(sProcess, Conditions, TimeRange) + % ===== BUILD COMMENT STRINGS ===== + sA = Conditions.SamplesA; + sB = Conditions.SamplesB; + CommentA = ''; + CommentB = ''; + % Extract some more information + isSampleBySample = ismember(sProcess.Category, {'Filter2', 'Filter', 'Extract'}); + + % === SAMPLE BY SAMPLE: A/B === + if isSampleBySample && (length(sA) == 1) && (length(sB) == 1) + if strcmpi(sA.SubjectFile, sB.SubjectFile) && strcmpi(sA.Condition, sB.Condition) + CommentA = sA.Comment; + CommentB = sB.Comment; + elseif io_compareFileNames(sA.SubjectFile, sB.SubjectFile) + CommentA = sA.Condition; + CommentB = sB.Condition; + elseif strcmpi(sA.Condition, sB.Condition) + CommentA = fullfile(sA.SubjectName, sA.Condition); + CommentB = fullfile(sB.SubjectName, sB.Condition); + else + CommentA = fullfile(sA.SubjectName, sA.Condition); + CommentB = fullfile(sB.SubjectName, sB.Condition); + end + % === AVERAGE === + elseif ~isSampleBySample + CommentA = FormatConditionsString(sA, 'A'); + CommentB = FormatConditionsString(sB, 'B'); + if strcmpi(CommentA, CommentB) + CommentA = ''; + CommentB = ''; + end + end + + % Build string for time interval + if (max(abs(TimeRange)) > 2) + strTime = sprintf('(%1.2fs,%1.2fs)', TimeRange); + else + strTime = sprintf('(%dms,%dms)', round(TimeRange * 1000)); + end + % Apply process naming + if ~isempty(CommentA) || isempty(sB) + Comment = sProcess.FileTag; + Comment = strrep(Comment, '#A#', CommentA); + Comment = strrep(Comment, '#B#', CommentB); + Comment = strrep(Comment, '#TIME#', strTime); + else + Comment = ''; + end +end + + +%% ===== GET CLUSTERS ===== +% OUTPUT: +% - sClusters : -1, if no channel file available for at least one sample +% -2, if no clusters available +% Else, return an array of Cluster structures +function [sClusters, iClusters] = GetClusters(Cond) + % Get unique list of studies + iStudies = unique([Cond.SamplesA.iStudy, Cond.SamplesB.iStudy]); + % Process all studies + for i = 1:length(iStudies) + % Get study structure + iStudy = iStudies(i); + %sStudy = bst_getContext('Study', iStudy); + sChannel = bst_getContext('ChannelForStudy', iStudy); + % Check that a ChannelFile is defined for this study + if isempty(sChannel) + sClusters = -1; + iClusters = []; + return + end + end + % Get the scouts available for this surface + [sClusters, iClusters] = panel_clusters('GetClusters'); + % If no scout avaialable: error + if isempty(sClusters) + sClusters = -2; + iClusters = []; + return; + end +end + + +%% ===== GET SCOUTS ===== +% OUTPUT: +% - sScouts : -1, if all samples do not refer to the same cortical surface +% -2, if no scout avaialable +% Else, return an array of scout structures +function [sScouts, iScouts] = GetScouts(Cond) + iScouts = []; + % Get studies and results indices + iStudies = [Cond.SamplesA.iStudy, Cond.SamplesB.iStudy]; + iResults = [Cond.SamplesA.iItem, Cond.SamplesB.iItem]; + SurfaceFile = []; + % Process all studies + for i = 1:length(iStudies) + iStudy = iStudies(i); + sStudy = bst_getContext('Study', iStudy); + % Get result file + ResultsMat = in_results_bst(sStudy.Result(iResults(i)).FileName, 0, 'SurfaceFile'); + % If it is first results: store the surface file + if isempty(SurfaceFile) + SurfaceFile = ResultsMat.SurfaceFile; + % If surface file is not the same than previous files: error + elseif ~io_compareFileNames(ResultsMat.SurfaceFile, SurfaceFile) + sScouts = -1; + return + end + end + % Get the scouts available for this surface + [sScouts, iScouts] = panel_scouts('GetScoutsWithSurface', SurfaceFile); + % If no scout avaialable: error + if isempty(sScouts) + sScouts = -2; + return; + end +end + + +%% ================================================================================================ +% ===== HELPERS ================================================================================== +% ================================================================================================ +%% ===== GET FILE TIME VECTOR ===== +function TimeVector = GetFileTimeVector(iStudy, iItem, isData) + TimeVector = []; + % Get protocols directories + ProtocolInfo = bst_getContext('ProtocolInfo'); + % Get study + sStudy = bst_getContext('Study', iStudy); + % Load time range from this file + if (isData) + % Build filename + filename = fullfile(ProtocolInfo.STUDIES, sStudy.Data(iItem).FileName); + % Load time vector + DataMat = load(filename, 'Time'); + if ~isempty(DataMat) && isfield(DataMat, 'Time') && ~isempty(DataMat.Time) + TimeVector = DataMat.Time; + end + else + % Build filename + refFilename = fullfile(ProtocolInfo.STUDIES, sStudy.Result(iItem).FileName); + % Resvole link + refFilename = resolveResultsLink(refFilename); + % Is results file is a kernel-only file + isKernel = ~isempty(strfind(refFilename, 'KERNEL')); + % KERNEL + if isKernel + % No time information available in file => Use DataMat results + DataFile = strrep(sStudy.Result(iItem).DataFile, ProtocolInfo.STUDIES, ''); + DataFileFull = fullfile(ProtocolInfo.STUDIES, DataFile); + DataMat = load(DataFileFull, 'Time'); + if ~isempty(DataMat) && isfield(DataMat, 'Time') && ~isempty(DataMat.Time) + TimeVector = DataMat.Time; + end + % FULL RESULTS FILE + else + % Load time vector + ResultMat = in_results_bst(refFilename, 0, 'ImageGridTime'); + if ~isempty(ResultMat) && isfield(ResultMat, 'ImageGridTime') + TimeVector = ResultMat.ImageGridTime; + end + end + end +end + + diff --git a/brainstorm3/toolbox/gui/panel_statRun.m.orig b/brainstorm3/toolbox/gui/panel_statRun.m.orig new file mode 100644 index 0000000..22c7715 --- /dev/null +++ b/brainstorm3/toolbox/gui/panel_statRun.m.orig @@ -0,0 +1,1547 @@ +function varargout = panel_statRun(varargin) +% PANEL_STATRUN: Selection and execution of the process to apply to the samples sets. +% +% USAGE: bstPanelNew = panel_statRun('CreatePanel', panelName) +% panel_statRun('CallBatch') +% panel_statRun('UpdatePanel') +% Comment = panel_statRun('FormatComment', ...) + +% @============================================================================= +% This software is part of The Brainstorm Toolbox +% http://neuroimage.usc.edu/brainstorm +% +% Copyright (c)2010 Brainstorm by the University of Southern California +% This software is distributed under the terms of the GNU General Public License +% as published by the Free Software Foundation. Further details on the GPL +% license can be found at http://www.gnu.org/copyleft/gpl.html. +% +% FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE +% UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY +% WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF +% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY +% LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE. +% +% For more information type "brainstorm licence" at command prompt. +% =============================================================================@ +% +% Authors: Francois Tadel, 2008 +% ----------------------------- Script History --------------------------------- +% FT 24-Jun-2008 Creation +% ------------------------------------------------------------------------------ + +% No parameters +if (nargin == 0) +% Else : execute appropriate local function +elseif ischar(varargin{1}) + if (nargout) + [varargout{1:nargout}] = bst_safeCall(str2func(varargin{1}), varargin{2:end}); + else + bst_safeCall(str2func(varargin{1}), varargin{2:end}); + end +end +end + + +%% ===== CREATE PANEL ===== +function bstPanelNew = CreatePanel(Conditions) %#ok + panelName = 'panel_statRun'; + % Java initializations + import java.awt.*; + import javax.swing.*; + % Constants + HFILLED_WIDTH = 10; + DEFAULT_HEIGHT = 20; + SPINNER_WIDTH = 70; + TEXT_WIDTH = 53; + JCOMBO_DM = java.awt.Dimension(10,25); + jFontText = java.awt.Font('Dialog', java.awt.Font.PLAIN, 10); + windowDim = [350, 0]; + + % Get some information about processes + isData = strcmpi(Conditions.DataType, 'data'); + nbSamplesA = length(Conditions.SamplesA); + nbSamplesB = length(Conditions.SamplesB); + [sProcesses, sSimpleTests, sPermTests, sAnova] = GetProcessesList(Conditions.NbSamplesSets, nbSamplesA, nbSamplesB); + + % ===== GET TIME DEFINITION ===== + % Get first input file time ranges + FileTimeVector = GetFileTimeVector(Conditions.SamplesA(1).iStudy, Conditions.SamplesA(1).iItem, isData); + % Get default values for baseline + if (FileTimeVector(1) < 0) && (FileTimeVector(end) > 0) + BaselineBounds = [FileTimeVector(1), 0]; + else +% BaselineBounds = [FileTimeVector(1), .8*FileTimeVector(1) + .2*FileTimeVector(end)]; + BaselineBounds = [FileTimeVector(1), FileTimeVector(end)]; + end + + % ===== CREATE PANEL ===== + jPanelNew = getRiverPanel([6,10], [5,5,0,10]); + % === COMMENT === + jPanelNew.add(JLabel('Comment:')); + jTextComment = JTextField(''); + jTextComment.setPreferredSize(Dimension(HFILLED_WIDTH, DEFAULT_HEIGHT)); + jPanelNew.add('hfill', jTextComment); + + % ===== PROCESSES PANEL ===== + jPanelOp = getRiverPanel([10,10], [0,20,5,20]); + % Statistics type + jComboTestOp = JComboBox(); + jComboTestOp.setPreferredSize(JCOMBO_DM); + jComboTestOp.setMaximumRowCount(30); + set(jComboTestOp, 'ItemStateChangedCallback', @UpdatePanel); + jPanelOp.add('hfill', jComboTestOp); + % Fill the combo box + FillProcessComboBox(jComboTestOp, sProcesses); + + % === DESCRIPTION === + jLabelDescriptionOp = JLabel('No process available.'); + jLabelDescriptionOp.setVerticalAlignment(JLabel.TOP); + jPanelOp.add('br hfill', jLabelDescriptionOp); + + jTextFactors = []; + % ===== TABBED PANEL ===== + % If no tests possible: create a flat titled panel + if (Conditions.NbSamplesSets == 1) + % Initialize unsed controls to "Null" + jTabbedStat = []; + jComboTestSimple = []; + jComboTestPermut = []; + jLabelDescriptionSimple = []; + jLabelDescriptionPermut = []; + jSpinnerNbPermut = []; + % Add a title to the panel + jPanelOp.setPreferredSize(Dimension(150,120)); + jPanelOp.setBorder(javax.swing.BorderFactory.createTitledBorder('Process selection')); + jPanelNew.add('br hfill', jPanelOp); + windowDim(2) = 470; + % Else: Tabbed pane + else + jTabbedStat = JTabbedPane(); + set(jTabbedStat, 'StateChangedCallback', @UpdatePanel); + % PROCESSES PANEL + jTabbedStat.addTab('Processes', jPanelOp); + % ===== SIMPLE TESTS ===== + jPanelSimple = getRiverPanel([10,10], [0,20,5,20]); + % === TEST TYPE === + % Statistics type + jComboTestSimple = JComboBox(); + jComboTestSimple.setPreferredSize(JCOMBO_DM); + set(jComboTestSimple, 'ItemStateChangedCallback', @UpdatePanel); + jPanelSimple.add('hfill', jComboTestSimple); + % Fill the combo box + FillProcessComboBox(jComboTestSimple, sSimpleTests); + + % === DESCRIPTION === + jLabelDescriptionSimple = JLabel('No test available.'); + jLabelDescriptionSimple.setVerticalAlignment(JLabel.TOP); + jPanelSimple.add('br hfill', jLabelDescriptionSimple); + jTabbedStat.addTab('Simple tests', jPanelSimple); + + % ===== PERMUTATION TESTS ===== + jPanelPermut = getRiverPanel([10,10], [0,20,5,20]); + % Statistics type + jComboTestPermut = JComboBox(); + jComboTestPermut.setPreferredSize(JCOMBO_DM); + set(jComboTestPermut, 'ItemStateChangedCallback', @UpdatePanel); + jPanelPermut.add('hfill', jComboTestPermut); + % Fill the combo box + FillProcessComboBox(jComboTestPermut, sPermTests); + + % === NUMBER OF PERMUTATIONS === + jPanelPermut.add('br', JLabel('Number of permutations: ')); + spinmodel = SpinnerNumberModel(10000, 1, 50000, 100); + jSpinnerNbPermut = JSpinner(spinmodel); + jSpinnerNbPermut.setPreferredSize(Dimension(SPINNER_WIDTH, DEFAULT_HEIGHT)); + jPanelPermut.add(jSpinnerNbPermut); + + % === DESCRIPTION === + jLabelDescriptionPermut = JLabel('No test available.'); + jLabelDescriptionPermut.setVerticalAlignment(JLabel.TOP); + jPanelPermut.add('br hfill', jLabelDescriptionPermut); + + jTabbedStat.addTab('Permutations', jPanelPermut); + + + % ===== ANOVA ===== + jPanelAnova = getRiverPanel([10,10], [0,20,5,20]); + % Statistics type + jComboAnova= JComboBox(); + jComboAnova.setPreferredSize(JCOMBO_DM); + set(jComboAnova, 'ItemStateChangedCallback', @UpdatePanel); + jPanelPermut.add('hfill', jComboAnova); + % Fill the combo box + FillProcessComboBox(jComboAnova, sAnova); + + % === FACTORS === + jPanelAnova.add('br', JLabel('Factors: ')); + % # of subjects: + NS = numel(unique({Conditions.SamplesA.SubjectName})); + + [u,i,j] = unique([Conditions.SamplesA.iItem]) + if numel(u)>1 + Comments = bst_getContext('Study', [Conditions.SamplesA.iStudy]); + Comments = [Comments.Data]; + + cellfun2('getfield', cellfun2('fliplr', getfield2(bst_getContext('Study', [Conditions.SamplesA.iStudy]), 'Data')), 'Comment')' + + SelectedItem = listdlg('ListString', ... + [strvcat({Conditions.SamplesA(i).Condition}) repmat(' : ',length(u),1) strvcat({Conditions.SamplesA(i).Comment})],... + 'SelectionMode','single'); + if isempty(SelectedItem) + return + end + Conditions.SamplesA = Conditions.SamplesA(j == SelectedItem); + assignin('base', 'sA', Conditions.SamplesA); + end + % Get samples + %GlobalData.Stat.FilterFileTag = ['_timemean' ]; + %Conditions = gui_stat_common('GetConditions', 'Statistics', GlobalData.Stat.FilterFileTag); + + tbl.h{1}='Condition'; + tbl.t = {Conditions.SamplesA.Condition}; + tbl.t = tbl.t(:) + tbl.t = tbl.t(1+[0:3]*NS) + %tbl.h{end+1}='Comment'; + %tbl.t(:,end+1) = {Conditions.SamplesA(1+[0:3]*15).Comment}; + tbl.h{end+1}='Factor 1'; + tbl.t(:,end+1)={ '1' ; '2' ; '1' ; '2' }; + tbl.h{end+1}='Factor 2'; + tbl.t(:,end+1)={ '1' ; '1' ; '2' ; '2' }; + + jTableFactors = JTable(tbl.t,tbl.h); + jTableFactors.setPreferredSize(Dimension(4*SPINNER_WIDTH, 5*DEFAULT_HEIGHT)); + jPanelAnova.add('br', jTableFactors); + + % === DESCRIPTION === + jLabelDescriptionAnova = JLabel('No option available.'); + jLabelDescriptionAnova.setVerticalAlignment(JLabel.TOP); + jPanelAnova.add('br hfill', jLabelDescriptionAnova); + + jTabbedStat.addTab('ANOVA', jPanelAnova); + jTabbedStat.setEnabledAt(3, 1); + + % Statistical tests : min two samples in each condition + if (nbSamplesA <= 1) || (nbSamplesB <= 1) + % Disable "simple tests" and "permutations" tabs if not enough samples + jTabbedStat.setEnabledAt(1, 0); + jTabbedStat.setEnabledAt(2, 0); + end + jTabbedStat.setPreferredSize(Dimension(150,175)); + jPanelNew.add('br hfill', jTabbedStat); + windowDim(2) = 350; + end + + % === TIME WINDOWS === + jPanelTimeWindows = getRiverPanel([2,3], [0,18,10,0], 'Time windows'); + % === WINDOW A === + jLabelTimeA = JLabel('Time window: '); + jPanelTimeWindows.add(jLabelTimeA); + % Time window A : START + jTextTimeStartA = JTextField(''); + jTextTimeStartA.setPreferredSize(Dimension(TEXT_WIDTH, DEFAULT_HEIGHT)); + jTextTimeStartA.setHorizontalAlignment(JTextField.RIGHT); + jTextTimeStartA.setFont(jFontText); + jPanelTimeWindows.add('tab', jTextTimeStartA); + jLabelSeparatorA = JLabel('-'); + jPanelTimeWindows.add(jLabelSeparatorA); + % Time window A : STOP + jTextTimeStopA = JTextField(''); + jTextTimeStopA.setPreferredSize(Dimension(TEXT_WIDTH, DEFAULT_HEIGHT)); + jTextTimeStopA.setHorizontalAlignment(JTextField.RIGHT); + jTextTimeStopA.setFont(jFontText); + jPanelTimeWindows.add(jTextTimeStopA); + + % Set time controls callbacks + TimeUnit = gui_validateText(jTextTimeStartA, [], [], FileTimeVector, [], jTextTimeStopA, FileTimeVector(1), @UpdatePanel); + TimeUnit = gui_validateText(jTextTimeStopA, [], [], FileTimeVector, jTextTimeStartA, [], FileTimeVector(end), @UpdatePanel); + % Display time unit + jLabelTimeUnitsA = JLabel(TimeUnit); + jPanelTimeWindows.add(jLabelTimeUnitsA); + + % === WINDOW B === + jLabelTimeB = JLabel('Baseline: '); + jPanelTimeWindows.add('br', jLabelTimeB); + % Time window B : START + jTextTimeStartB = JTextField(''); + jTextTimeStartB.setPreferredSize(Dimension(TEXT_WIDTH, DEFAULT_HEIGHT)); + jTextTimeStartB.setHorizontalAlignment(JTextField.RIGHT); + jTextTimeStartB.setFont(jFontText); + jPanelTimeWindows.add('tab', jTextTimeStartB); + jLabelSeparatorB = JLabel('-'); + jPanelTimeWindows.add(jLabelSeparatorB); + % Time window B : STOP + jTextTimeStopB = JTextField(''); + jTextTimeStopB.setPreferredSize(Dimension(TEXT_WIDTH, DEFAULT_HEIGHT)); + jTextTimeStopB.setHorizontalAlignment(JTextField.RIGHT); + jTextTimeStopB.setFont(jFontText); + jPanelTimeWindows.add(jTextTimeStopB); + + % Set time controls callbacks + gui_validateText(jTextTimeStartB, [], [], FileTimeVector, [], jTextTimeStopB, BaselineBounds(1), []); + gui_validateText(jTextTimeStopB, [], [], FileTimeVector, jTextTimeStartB, [], BaselineBounds(2), []); + % Display time unit + jLabelTimeUnitB = JLabel(TimeUnit); + jPanelTimeWindows.add(jLabelTimeUnitB); + jPanelNew.add('br hfill', jPanelTimeWindows); + + % ===== SOURCES PANEL ===== + jPanelSources = getRiverPanel([0,0], [0,0,0,0], 'Sources'); + jCheckAbsoluteValues = JCheckBox('Use absolute values of sources activations'); + jCheckAbsoluteValues.setMargin(Insets(0,10,0,0)); + set(jCheckAbsoluteValues, 'ActionPerformedCallback', @CheckAbsoluteValues_Callback); + jPanelSources.add(jCheckAbsoluteValues); + % Add panel only if processing sources (results) + if ~isData + jPanelNew.add('br hfill', jPanelSources); + jCheckAbsoluteValues.setSelected(1); + windowDim(2) = windowDim(2) + 70; + else + jCheckAbsoluteValues.setVisible(0); + jCheckAbsoluteValues.setSelected(0); + end + + % ===== CLUSTER PANEL CONFIG ===== + jCheckCluster = []; + jCheckAvgCluster = []; + jListCluster = []; + iScouts = []; + Channel = []; + if (Conditions.NbSamplesSets == 1) + jPanelCluster = JPanel(BorderLayout()); + % === SCALP: ELECTRODES CLUSTER === + if isData + jPanelCluster.setBorder(javax.swing.BorderFactory.createTitledBorder('Clusters')); + % Get all available scouts for these results + [sClusters, iClusters] = GetClusters(Conditions); + % If all samples defined for the same cortical surface, and some scouts are defined for it + if isstruct(sClusters) + % Get selected scouts + jPanelClusterTop = JPanel(BorderLayout()); + % === ENABLE CHECK BOX === + jCheckCluster = JCheckBox('Use only selected clusters'); + jCheckCluster.setMargin(Insets(5,10,0,0)); + set(jCheckCluster, 'ActionPerformedCallback', @UpdatePanel); + jPanelClusterTop.add(jCheckCluster, BorderLayout.WEST); + + % === AVERAGE CLUSTER CHECKBOX === + if (length(sClusters) > 1) + jCheckAvgCluster = JCheckBox('Merge selected clusters', 0); + jCheckAvgCluster.setMargin(Insets(5,10,0,15)); + jPanelClusterTop.add(jCheckAvgCluster, BorderLayout.EAST); + end + jPanelCluster.add(jPanelClusterTop, BorderLayout.NORTH); + + % === JLIST: CLUSTERS === + % Create list (display labels of all clusters) + jListCluster = JList({sClusters.Label}); + % Pre-select clusters that are selected in the "Cluster" panel + [tmp__, iSelect] = panel_clusters('GetSelectedClusters'); + if ~isempty(iSelect) + jListCluster.setSelectedIndices(iSelect - 1); + end + % Create scroll panel + jScrollPanelCluster = JScrollPane(jListCluster); + jScrollPanelCluster.setBorder(BorderFactory.createCompoundBorder(... + BorderFactory.createEmptyBorder(5, 30, 5, 30), ... + jScrollPanelCluster.getBorder())); + jScrollPanelCluster.setPreferredSize(Dimension(250,300)); + jPanelCluster.add(jScrollPanelCluster, BorderLayout.WEST); + + % Set the dialog size + windowDim(2) = windowDim(2) + 80; + + % ERROR 1 + elseif isnumeric(sClusters) && (sClusters == -1) + jPanelCluster.add(JLabel(' No channel file available for at least one file.')); + % ERROR 2 + elseif isnumeric(sClusters) && (sClusters == -2) + jPanelCluster.add(JLabel(' No cluster available. Use "Cluster" tab to create one.'), BorderLayout.CENTER); + end + + % === SOURCES: VERTICES CLUSTER (=SCOUT) === + else + jPanelCluster.setBorder(javax.swing.BorderFactory.createTitledBorder('Scouts')); + % Get all available scouts for these results + [sScouts, iScouts] = GetScouts(Conditions); + % If all samples defined for the same cortical surface, and some scouts are defined for it + if isstruct(sScouts) + % Get selected scouts + [sSelScouts, iSelScouts] = panel_scouts('GetSelectedScouts'); + jPanelClusterTop = JPanel(BorderLayout()); + % === ENABLE CHECK BOX === + jCheckCluster = JCheckBox('Use only selected scouts'); + jCheckCluster.setMargin(Insets(5,10,0,0)); + set(jCheckCluster, 'ActionPerformedCallback', @UpdatePanel); + jPanelClusterTop.add(jCheckCluster, BorderLayout.WEST); + + % === AVERAGE CLUSTER CHECKBOX === + if (length(sScouts) > 1) + jCheckAvgCluster = JCheckBox('Merge selected scouts', 0); + jCheckAvgCluster.setMargin(Insets(5,10,0,15)); + jPanelClusterTop.add(jCheckAvgCluster, BorderLayout.EAST); + end + jPanelCluster.add(jPanelClusterTop, BorderLayout.NORTH); + + % === JLIST: CHANNELS === + % Create list (display labels of all scouts) + jListCluster = JList({sScouts.Label}); + % Pre-select scouts that are selected in the "Scouts" panel + iSelect = find(ismember(iScouts, iSelScouts)); + if ~isempty(iSelect) + jListCluster.setSelectedIndices(iSelect - 1); + end + % Create scroll panel + jScrollPanelCluster = JScrollPane(jListCluster); + jScrollPanelCluster.setBorder(BorderFactory.createCompoundBorder(... + BorderFactory.createEmptyBorder(5, 30, 5, 30), ... + jScrollPanelCluster.getBorder())); + jScrollPanelCluster.setPreferredSize(Dimension(250,300)); + jPanelCluster.add(jScrollPanelCluster, BorderLayout.WEST); + + % Set the dialog size + windowDim(2) = windowDim(2) + 80; + + % ERROR 1 + elseif isnumeric(sScouts) && (sScouts == -1) + jPanelCluster.add(JLabel(' The sources files were not computed for the same cortical surface.'), BorderLayout.CENTER); + % ERROR 2 + elseif isnumeric(sScouts) && (sScouts == -2) + jPanelCluster.add(JLabel(' No scout available for these files.'), BorderLayout.CENTER); + end + end + jPanelNew.add('br vfill hfill', jPanelCluster); + end + + % ===== OUTPUT PANEL ===== + if (Conditions.NbSamplesSets == 1) + jPanelOutput = getRiverPanel([0,3], [0,10,10,10], 'Output'); + buttonGroupOutput = ButtonGroup(); + % OUTPUT: Brainstorm Database + jRadioOutputDatabase = JRadioButton('Brainstorm database', 1); + jRadioOutputDatabase.setToolTipText(['Brainstorm database:

' ... + 'All the files that are generated are automatically added to database.

'... + ' - One output file:
' ... + 'Most of the processes produce only one file that is stored either,
' ... + 'in the original condition or in an "Analysis" node.

' ... + ' - Multiple output files:
' ... + 'The processes that are marked "[sample by sample]" produce one file
' ... + 'for each file A (or couple of files A/B) in the sample list.']); + buttonGroupOutput.add(jRadioOutputDatabase); + jPanelOutput.add(jRadioOutputDatabase); + % Overwrite original files + jPanelOutput.add('br', JLabel(' ')); + jCheckOverwriteFiles = JCheckBox('Overwrite initial files', 1); + jPanelOutput.add(jCheckOverwriteFiles); + % OUTPUT: Condition path (check) + jPanelOutput.add('br', JLabel(' ')); + jCheckOutputCond = JCheckBox('Select output condition'); + jPanelOutput.add(jCheckOutputCond); + % OUTPUT: Condition path (text) + jTextOutputCond = JTextField(''); + jTextOutputCond.setMargin(Insets(0,0,0,20)); + jPanelOutput.add('hfill', jTextOutputCond); + % OUTPUT: User file + jRadioOutputFile = JRadioButton('User defined file: All data is stored in one unique file'); + buttonGroupOutput.add(jRadioOutputFile); + jPanelOutput.add('br', jRadioOutputFile); + % OUTPUT: Matlab variable + jRadioOutputMatlab = JRadioButton('Export to a Matlab variable'); + buttonGroupOutput.add(jRadioOutputMatlab); + jPanelOutput.add('br', jRadioOutputMatlab); + jPanelNew.add('br hfill', jPanelOutput); + % Callbacks + set(jRadioOutputDatabase, 'ActionPerformedCallback', @UpdateOutput); + set(jRadioOutputFile, 'ActionPerformedCallback', @UpdateOutput); + set(jRadioOutputMatlab, 'ActionPerformedCallback', @UpdateOutput); + set(jCheckOverwriteFiles, 'ActionPerformedCallback', @UpdateOutput); + set(jCheckOutputCond, 'ActionPerformedCallback', @UpdateOutput); + else + jRadioOutputDatabase = []; + jRadioOutputFile = []; + jRadioOutputMatlab = []; + jCheckOverwriteFiles = []; + jCheckOutputCond = []; + jTextOutputCond = []; + end + + % ===== VALIDATION BUTTONS ===== + % Help + jButtonHelp = JButton('Help'); + jButtonHelp.setForeground(Color(.7, 0, 0)); + if (Conditions.NbSamplesSets == 1) + set(jButtonHelp, 'ActionPerformedCallback', @(h,ev)bst_help('PanelStatRunProcess.html', 1)); + else + set(jButtonHelp, 'ActionPerformedCallback', @(h,ev)bst_help('PanelStatRunStat.html', 1)); + end + jPanelNew.add('br right', jButtonHelp); + % Cancel + jButtonCancel = JButton('Cancel'); + set(jButtonCancel, 'ActionPerformedCallback', @ButtonCancel_Callback); + jPanelNew.add(jButtonCancel); + % Run + jButtonRun = JButton('Run'); + set(jButtonRun, 'ActionPerformedCallback', @(h,ev)bst_safeCall(@ButtonRun_Callback)); + jPanelNew.add(jButtonRun); + + % Set main panel size + jPanelNew.setPreferredSize(Dimension(windowDim(1), windowDim(2))); + + % Create the BstPanel object that is returned by the function + % => constructor BstPanel(jHandle, panelName, sControls) + bstPanelNew = BstPanel(panelName, ... + jPanelNew, ... + struct('jListCluster', jListCluster)); + % Update panel + UpdatePanel(); + + + +%% ================================================================================= +% === INTERNAL CALLBACKS ========================================================== +% ================================================================================= +%% ===== CHECKBOX : "ABSOLUTE VALUES" ===== + function CheckAbsoluteValues_Callback(varargin) + if ~jCheckAbsoluteValues.isSelected() + isConfirmed = java_dialog('confirm', ['Please keep this option selected, unless you know exactly what you are doing.' 10 10 ... + 'Are you sure you want to use relative values for sources activations ?'], 'Processes'); + if ~isConfirmed + jCheckAbsoluteValues.setSelected(1); + end + end + end + +%% ===== UPDATE OUTPUT PANEL ===== + function UpdateOutput(varargin) + % Force condition path + isDatabase = jRadioOutputDatabase.isSelected(); + jCheckOutputCond.setEnabled(isDatabase); + jTextOutputCond.setEnabled(isDatabase); + if ~isDatabase + jCheckOutputCond.setSelected(0); + end + % Condition path + isForceCond = jCheckOutputCond.isSelected(); + jTextOutputCond.setVisible(isForceCond); + % Cannot select both options at the same time + if isForceCond + jCheckOverwriteFiles.setSelected(0); + end + end + +%% ===== FILL PROCESSES COMBO BOX ===== + function FillProcessComboBox(jCombo, sList) + import org.brainstorm.list.BstListItem; + % Fill the combo box + for i = 1:length(sList) + % Convert labels to HTML + label = sList(i).Comment; + if isempty(sList(i).Name) + label = ['

' label]; + else + label = ['

' label]; + end + % Add item in combo list + jCombo.addItem(BstListItem(sList(i).Name, '', label, sList(i).Description)); + end + end + +%% ===== RUN ===== + function ButtonRun_Callback(varargin) + CallBatch(); + end + +%% ===== GET SELECTED PROCESS ===== + function [sProcess, selectedTab] = GetSelectedProcess() + % Get selected tab + if isempty(jTabbedStat) + selectedTab = 0; + else + selectedTab = jTabbedStat.getSelectedIndex(); + end + % Get the selected combo box + switch (selectedTab) + case 0 + jCombo = jComboTestOp; + sList = sProcesses; + case 1 + jCombo = jComboTestSimple; + sList = sSimpleTests; + case 2 + jCombo = jComboTestPermut; + sList = sPermTests; + case 3 + jCombo = jComboAnova; + sList = sAnova; + end + % Get selected process + jItem = jCombo.getSelectedItem(); + if isempty(jItem) + sProcess = []; + return + end + processName = jItem.getType(); + % Find in it in processes list + iProcess = find(strcmpi({sList.Name}, processName)); + sProcess = sList(iProcess(1)); + end + +%% ===== UPDATE PANEL ===== + function UpdatePanel(varargin) %#ok + % Get selected process + [sProcess, selectedTab] = GetSelectedProcess(); + if isempty(sProcess) + return + end + % Convert description to HTML + processDesc = ['' strrep(sProcess.Description, char(10), '
')]; + % Update tabbed panels + switch(selectedTab) + % === PROCESSES === + case 0 + jLabelDescriptionOp.setText(processDesc); + % === SIMPLE TESTS === + case 1 + jLabelDescriptionSimple.setText(processDesc); + % === PERM TESTS === + case 2 + jLabelDescriptionPermut.setText(processDesc); + % Compute maximum number of permutations + Na = nbSamplesA; + Nb = nbSamplesB; + if sProcess.isPaired + % nbPermMax = 2^(Na)-1; + nbPermMax = 2^(Na-1)-1; + else + nbPermMax = factorial(Na+Nb) / (factorial(Na)*factorial(Nb)) - 1; + end + % Limit default number of permutations + NB_PERM_LIMIT = 10000; + nbPerm = min(nbPermMax, NB_PERM_LIMIT); + % Update spinner model + spinmodel = awtinvoke(jSpinnerNbPermut, 'getModel()'); + awtinvoke(spinmodel, 'setValue(Ljava.lang.Object;)', java.lang.Double(nbPerm)); + awtinvoke(spinmodel, 'setMaximum(Ljava.lang.Comparable;)', java.lang.Double(nbPermMax)); + end + + % ===== UPDATE COMMENT ===== + % Get time + TimeRange = GetTimeWindows(TimeUnit); + % Format comment + Comment = FormatComment(sProcess, Conditions, TimeRange); + % Too long comment + if (length(Comment) > 80) + % Replace comment by date + c = clock; + strTime = sprintf('%02.0f%02.0f%02.0f_%02.0f%02.0f', c(1)-2000, c(2:5)); + Comment = [sProcess.Name ': ' strTime]; + end + % Update comment + awtinvoke(jTextComment, 'setText(Ljava.lang.String;)', Comment); + + % ===== UPDATE TIME WINDOW PANEL ===== + if ~isempty(jTextTimeStartB) + jLabelTimeB.setEnabled(sProcess.UseBaseline); + jTextTimeStartB.setEnabled(sProcess.UseBaseline); + jTextTimeStopB.setEnabled(sProcess.UseBaseline); + jLabelSeparatorB.setEnabled(sProcess.UseBaseline); + jLabelTimeUnitB.setEnabled(sProcess.UseBaseline); + end + + % ===== UPDATE SOURCES PANEL ===== + jCheckAbsoluteValues.setSelected(sProcess.isSourceAbsolute > 0); + jCheckAbsoluteValues.setEnabled(sProcess.isSourceAbsolute ~= 2); + + % ===== UPDATE CLUSTER PANEL ===== + % Cluster checkbox + isAllowCluster = ismember(sProcess.Category, {'Extract'}); + if ~isempty(jCheckCluster) + jCheckCluster.setEnabled(isAllowCluster); + if ~isAllowCluster + jCheckCluster.setSelected(0); + end + % Cluster jlists + isCluster = ~isempty(jCheckCluster) && jCheckCluster.isSelected(); + if ~isempty(jListCluster) + jListCluster.setEnabled(isCluster); + end + if ~isempty(jCheckAvgCluster) + jCheckAvgCluster.setEnabled(isCluster); + end + else + isCluster = 0; + end + + % ===== UPDATE OUTPUT PANEL ===== + if ~isempty(jRadioOutputDatabase) + AllowExport = ismember(sProcess.Category, {'Extract'}); + % Enable Database: only if no cluster defined + jRadioOutputDatabase.setEnabled(~isCluster); + % Enable File/Matlab + jRadioOutputFile.setEnabled(AllowExport > 0); + jRadioOutputMatlab.setEnabled(AllowExport > 0); + % Select "File": if cluster, or time/mean, time/var, or extract + if (isCluster || AllowExport) + % Change selection only if Database is selected + if ~jRadioOutputMatlab.isSelected() + jRadioOutputFile.setSelected(1); + end + else + jRadioOutputDatabase.setSelected(1); + end + + % "Overwrite file" checkbox + AllowOverwrite = ismember(sProcess.Category, {'Filter'}); + isOverwriteEnabled = jRadioOutputDatabase.isEnabled() && AllowOverwrite; + jCheckOverwriteFiles.setEnabled(isOverwriteEnabled); + isOverwriteSelected = isOverwriteEnabled && sProcess.DefaultOverwrite; + jCheckOverwriteFiles.setSelected(isOverwriteSelected); + % Update object selections + UpdateOutput(); + end + end + + +%% ===== GET TIME WINDOWS ===== + function [Time, Baseline, iTime, iBaseline] = GetTimeWindows(TimeUnit) + % Get time window to process + Time = [getValue(jTextTimeStartA), getValue(jTextTimeStopA)]; + % Get baseline, if defined + if ~isempty(jTextTimeStartB) + Baseline = [getValue(jTextTimeStartB), getValue(jTextTimeStopB)]; + else + Baseline = []; + end + % Apply correct time units + switch(TimeUnit) + case 'ms' + % Convert miliseconds -> seconds + Time = Time / 1000; + Baseline = Baseline / 1000; + otherwise + % Nothing to do + end + % Get indices in intial FileTimeVector + iTimeBounds = findclosest(Time, FileTimeVector); + % If whole data wanted and sampling frequency very high: might lack of time precision... + % Try to correct this and select the last sample if it is obviously this that the user wants + if (iTimeBounds(2) ~= length(FileTimeVector)) && (FileTimeVector(end)-FileTimeVector(iTimeBounds(2)) < 1e-5) + iTimeBounds(2) = length(FileTimeVector); + end + % Build time indices + iTime = iTimeBounds(1) : iTimeBounds(2); + iBaselineBounds = findclosest(Baseline, FileTimeVector); + iBaseline = iBaselineBounds(1) : iBaselineBounds(2); + end + +%% ===== GET VALUES ===== + function val = getValue(jText) + % Get and check value + val = str2double(char(jText.getText())); + if isnan(val) || isempty(val) + val = []; + end + end + + +%% ===== COMPUTE TEST ===== + function CallBatch() + % ===== PREPARE OPTIONS ===== + OPTIONS = struct(); + % Is processing recordings or results + OPTIONS.isData = isData; + % Get new file comment + OPTIONS.Comment = deblank(strtrim(char(jTextComment.getText()))); + % Is absolute values (for sources only) + OPTIONS.isAbsoluteValues = ~isData && jCheckAbsoluteValues.isSelected(); + % Get selected process + [OPTIONS.sProcess, selectedTab] = GetSelectedProcess(); + if isempty(OPTIONS.sProcess) || isempty(OPTIONS.sProcess.Name) + bst_error('Please select a process.', 'Processes', 0); + return + end + % Get number of permutations + if ~isempty(jSpinnerNbPermut) + OPTIONS.nbPermutation = jSpinnerNbPermut.getValue(); + else + OPTIONS.nbPermutation = 0; + end + % List factors associated with each dataset + if ~isempty(jTextFactors) + OPTIONS.Factors= char(jTextFactors.getText()); + else + OPTIONS.Factors = []; + end + + % Overwrite initial files ? + OPTIONS.isOverwriteFiles = ~isempty(jCheckOverwriteFiles) && jCheckOverwriteFiles.isSelected(); + % Get output mode + if isempty(jRadioOutputDatabase) || jRadioOutputDatabase.isSelected() + OPTIONS.OutputType = 'database'; + elseif jRadioOutputFile.isSelected() + OPTIONS.OutputType = 'file'; + elseif jRadioOutputMatlab.isSelected() + OPTIONS.OutputType = 'matlab'; + end + % Force output condition + if ~isempty(jCheckOutputCond) && jCheckOutputCond.isSelected() + % Get the user defined condition name + outputCond = char(jTextOutputCond.getText()); + % Split condition path + condPath = strSplit(outputCond); + % Check if condition name is valid + if isempty(outputCond) || (~strcmpi(outputCond,'@inter') && (length(condPath) ~= 2)) + bst_error(['Invalid condition path.' 10 10 ... + 'Condition path must be one the following:' 10 ... + ' - SubjectName/ConditionName' 10 ... + ' - SubjectName/@intra' 10 ... + ' - @inter'], 'Processes', 0); + return + end + % === Convert SubjectName in SubjectPath === + if (length(condPath) == 2) + % Get subject and condition names + SubjectName = condPath{1}; + ConditionName = condPath{2}; + % Get subject + sSubject = bst_getContext('Subject', SubjectName, 1); + if isempty(sSubject) + bst_error(['Subject does not exist: "' SubjectName '"'], 'Processes', 0); + return + end + % Build new output condition path + SubjectPath = fileparts(sSubject.FileName); + outputCond = fullfile(SubjectPath, ConditionName); + end + % Valid + OPTIONS.ForceOutputCond = outputCond; + else + OPTIONS.ForceOutputCond = []; + end + % Is "Cluster" analysis + OPTIONS.isCluster = ~isempty(jCheckCluster) && jCheckCluster.isSelected(); + OPTIONS.isClusterAverage = OPTIONS.isCluster && ~isempty(jCheckAvgCluster) && jCheckAvgCluster.isSelected(); + % Get selected clusters + if OPTIONS.isCluster + if OPTIONS.isData + % Get selected clusters + iClusters = jListCluster.getSelectedIndices() + 1; + sClusters = panel_clusters('GetClusters', iClusters); + % If no cluster selected: ERROR + if isempty(iClusters) + error('No channel selected in cluster panel.'); + end + % If user asked to merge clusters + if OPTIONS.isClusterAverage + sClustersAvg.Sensors = unique(cat(2, sClusters.Sensors)); + sClustersAvg.Label = ['Clusters:', sprintf(' %s', sClusters.Label)]; + OPTIONS.Clusters = sClustersAvg; + else + OPTIONS.Clusters = sClusters; + end + % Get clusters options + OPTIONS.ClustersOptions = panel_clusters('GetClusterDisplayType'); + else + % Get selected scouts + iSelScouts = jListCluster.getSelectedIndices() + 1; + % If no scout selected: ERROR + if isempty(iSelScouts) + error('No scout selected in cluster panel.'); + end + % Get scouts description + [sScouts, iScouts] = panel_scouts('GetCurrentScouts'); + iScouts = iScouts(iSelScouts); + sScouts = panel_scouts('GetScouts', iScouts); + % If user asked to merge scouts + if OPTIONS.isClusterAverage + sScoutsAvg = sScouts(1); + sScoutsAvg.Vertices = unique([sScouts.Vertices]); + sScoutsAvg.Label = ['Scouts:', sprintf(' %s', sScouts.Label)]; + OPTIONS.Clusters = sScoutsAvg; + else + OPTIONS.Clusters = sScouts; + end + % Get scouts options + OPTIONS.ClustersOptions = panel_scouts('GetScoutDisplayType'); + end + else + OPTIONS.sClusters = []; + OPTIONS.ClustersOptions = []; + end + % Get time + [OPTIONS.Time, OPTIONS.Baseline, OPTIONS.iTime, OPTIONS.iBaseline] = GetTimeWindows(TimeUnit); + + % Get files to batch + OPTIONS.Conditions = Conditions; + % Close panel + gui_hidePanel('panel_statRun'); + + % ===== PERFORM SOME VERIFICATIONS ===== + % Not supposed to compute zscore on recordings + if OPTIONS.isData && strcmpi(OPTIONS.sProcess.Name, 'zscore') + res = java_dialog('confirm', ['Warning: You are about to compute the z-score normalization on recordings.' 10 10 ... + 'We recommand first to solve the inverse problem, and then to ' 10 ... + 'apply the zscore normalization on the cortical sources.' 10 10 ... + 'Are you sure you want to compute the z-score of the recordings ?'], 'Warning'); + if ~res + return; + end + end + + % ===== CALL BATCH FUNCTION ===== + bst_batch(OPTIONS); + + % Update "Processes" panels + if OPTIONS.isOverwriteFiles + panel_processes('ResetPanel'); + else + if (Conditions.NbSamplesSets == 1) && ~gui_stat_common('CheckConditions', 'Processes') + panel_processes('ResetPanel'); + elseif (Conditions.NbSamplesSets == 2) && ~gui_stat_common('CheckConditions', 'Statistics') + panel_stat('ResetPanel'); + end + end + end +end + + +%% ================================================================================= +% === EXTERNAL CALLBACKS ========================================================== +% ================================================================================= +%% ===== CANCEL ===== +function ButtonCancel_Callback(varargin) + % Close panel + gui_hidePanel('panel_statRun'); +end + + +%% ===== BUILD LIST OF PROCESSES ===== +% USAGE: [sProcesses, sSimpleTests, sPermTests, sAnova] = GetProcessesList(NbSamplesSets, nbSamplesA, nbSamplesB, isData) +% [sProcesses, sSimpleTests, sPermTests, sAnova] = GetProcessesList('All') +function [sProcesses, sSimpleTests, sPermTests, sAnova] = GetProcessesList(NbSamplesSets, nbSamplesA, nbSamplesB, isData) + % If get all processes: ignore all other arguments + isAll = strcmpi(NbSamplesSets, 'All'); + % Initialize list + sModel = struct('Name', '', ... + 'Comment', '', ... + 'Description', '', ... + 'FileTag', '', ... + 'Category', '', ... + 'UseBaseline', 0, ... + 'DefaultOverwrite', 0, ... + 'isSourceAbsolute', 1, ... % If value=2, absolute value for sources is FORCED + 'isPaired', 0, ... + 'blockDimension', 0, ... % Dimension in which the data matrix can be split (0=none, 1=channels, 2=time) + 'isAvgRef', 1); % Compute EEG average reference before processing + sProcesses = repmat(sModel, 0); + sSimpleTests = repmat(sModel, 0); + sPermTests = repmat(sModel, 0); + + % ===== "A" PROCESSES ===== + if isAll || (NbSamplesSets == 1) + % === FILTERS TO APPLY TO FILES === + sProcess = sModel; + sProcess.Comment = '==== PROCESS FILES ==========='; + sProcess.Description = 'Please select a process...'; + sProcesses(end + 1) = sProcess; + % Z-SCORE + sProcess = sModel; + sProcess.Name = 'zscore'; + sProcess.Comment = ' Z-score noise normalization'; + sProcess.FileTag = '| zscore'; + sProcess.Description = ['For each channel:' 10 ... + '1) Compute mean m and variance v for baseline.' 10 ... + '2) For each time sample, substract m and divide by v.']; + sProcess.Category = 'Filter'; + sProcess.UseBaseline = 1; + sProcess.isSourceAbsolute = 2; + sProcess.blockDimension = 1; + sProcess.isAvgRef = 0; + sProcesses(end + 1) = sProcess; + % BASELINE REMOVAL + sProcess = sModel; + sProcess.Name = 'baseline'; + sProcess.Comment = ' Remove baseline mean (DC offset)'; + sProcess.FileTag = '| bl'; + sProcess.Description = ['For each channel:' 10 ... + '1) Compute the mean m for the baseline.' 10 ... + '2) For all the time samples, substract m.']; + sProcess.Category = 'Filter'; + sProcess.UseBaseline = 1; + sProcess.isSourceAbsolute = 0; + sProcess.DefaultOverwrite = 1; + sProcess.blockDimension = 1; + sProcess.isAvgRef = 0; + sProcesses(end + 1) = sProcess; + % EEG AVERAGE REFERENCE + if isAll || isData + sProcess = sModel; + sProcess.Name = 'avgref'; + sProcess.Comment = ' EEG Average reference'; + sProcess.FileTag = '| avgref'; + sProcess.Description = ['Note that all the processes that are not in the "Process files" category use average reference by default. ' 10 ... + 'No effect on MEG recordings.']; + sProcess.Category = 'Filter'; + sProcess.UseBaseline = 0; + sProcess.isSourceAbsolute = 0; + sProcess.DefaultOverwrite = 1; + sProcess.blockDimension = 2; + sProcesses(end + 1) = sProcess; + end + % BANDPASS FILTERING + sProcess = sModel; + sProcess.Name = 'bandpass'; + sProcess.Comment = ' Bandpass filtering'; + sProcess.FileTag = '| bandpass'; + sProcess.Description = 'Apply a frenquency filter to all the files.'; + sProcess.Category = 'Filter'; + sProcess.isSourceAbsolute = 0; + sProcess.blockDimension = 1; + sProcess.isAvgRef = 0; + sProcesses(end + 1) = sProcess; + % SPATIAL SMOOTHING + if isAll || ~isData + sProcess = sModel; + sProcess.Name = 'ssmooth'; + sProcess.Comment = ' Spatial smoothing'; + sProcess.FileTag = '| ssmooth'; + sProcess.Description = 'Spatial smoothing of the sources.'; + sProcess.Category = 'Filter'; + sProcess.isSourceAbsolute = 2; + sProcess.blockDimension = 0; + sProcess.isAvgRef = 0; + sProcesses(end + 1) = sProcess; + end + % RESAMPLE + if isAll || isData + sProcess = sModel; + sProcess.Name = 'resample'; + sProcess.Comment = ' Resample recordings'; + sProcess.FileTag = '| resample'; + sProcess.Description = 'Resample all the files with a new sampling frequency.'; + sProcess.Category = 'Filter'; + sProcess.isSourceAbsolute = 0; + % sProcess.blockDimension = 1; % PROBLEM WITH PROCESSING BY BLOCKS: DIMENSIONS CHANGE + sProcess.blockDimension = 0; + sProcess.isAvgRef = 0; + sProcesses(end + 1) = sProcess; + end + % CUT STIMULATION ARTIFACT + if isAll || isData + sProcess = sModel; + sProcess.Name = 'cutstim'; + sProcess.Comment = ' Cut stimulation artifact'; + sProcess.FileTag = '| cutstim'; + sProcess.Description = ['Remove the values in the "baseline" time window.' 10 ... + 'Replace them with a linear interpolation.']; + sProcess.Category = 'Filter'; + sProcess.UseBaseline = 1; + sProcess.DefaultOverwrite = 1; + sProcess.blockDimension = 1; + sProcess.isAvgRef = 0; + sProcesses(end + 1) = sProcess; + end + % OPPOSITE VALUES + sProcess = sModel; + sProcess.Name = 'opposite'; + sProcess.Comment = ' Opposite values: -A'; + sProcess.FileTag = '| opposite'; + sProcess.Description = 'Save opposite values for A files.'; + sProcess.Category = 'Filter'; + sProcess.DefaultOverwrite = 1; + sProcess.isSourceAbsolute = 0; + sProcess.blockDimension = 1; + sProcess.isAvgRef = 0; + sProcesses(end + 1) = sProcess; + + % === AVERAGING === + if isAll || (nbSamplesA >= 2) + sProcess = sModel; + sProcess.Comment = '==== AVERAGING ============='; + sProcess.Description = 'Please select a process...'; + sProcesses(end + 1) = sProcess; + % GRAND-AVERAGE A (MEAN/CONDITION) + sProcess = sModel; + sProcess.Name = 'GAVE'; + sProcess.Comment = ' Average by condition (Grand-average)'; + sProcess.FileTag = ''; + sProcess.Description = ['Grand averages for each condition' 10 ... + 'One output file per condition.']; + sProcess.Category = 'Average'; + sProcesses(end + 1) = sProcess; + % AVERAGE BY SUBJECT + sProcess = sModel; + sProcess.Name = 'SubjAvg'; + sProcess.Comment = ' Average by subject'; + sProcess.FileTag = ''; + sProcess.Description = ['Average all the recordings for each subject.' 10 ... + 'One output file per subject.']; + sProcess.Category = 'Average'; + sProcesses(end + 1) = sProcess; + % AVERAGE A + sProcess = sModel; + sProcess.Name = 'meanA'; + sProcess.Comment = ' Average everything'; + sProcess.FileTag = '<#A#>'; + sProcess.Description = ['Average of all files.' 10 'Only one output file.']; + sProcess.Category = 'Average'; + sProcesses(end + 1) = sProcess; + end + + % === DATA EXTRACTION === + sProcess = sModel; + sProcess.Comment = '==== EXTRACT DATA ==========='; + sProcess.Description = 'Please select a process...'; + sProcesses(end + 1) = sProcess; + % MEAN FOR A TIME WINDOW + sProcess = sModel; + sProcess.Name = 'timemean'; + sProcess.Comment = ' Average over a time window'; + sProcess.FileTag = '| timemean#TIME#'; + sProcess.Description = ['Average for each file over the selected time window.' 10 ... + 'Use absolute values for sources.']; + sProcess.Category = 'Extract'; + sProcesses(end + 1) = sProcess; + % VARIANCE FOR A TIME WINDOW + sProcess = sModel; + sProcess.Name = 'timevar'; + sProcess.Comment = ' Variance over a time window'; + sProcess.FileTag = '| timevar#TIME#'; + sProcess.Description = 'Variance for each file over the selected time window.'; + sProcess.Category = 'Extract'; + sProcesses(end + 1) = sProcess; + % EXTRACT DATA FROM SAMPLES + sProcess = sModel; + sProcess.Name = 'extract'; + sProcess.Comment = ' Extract data block (cluster,time)'; + sProcess.FileTag = '| extract#TIME#'; + sProcess.Description = 'Get a block of data from each file.'; + sProcess.Category = 'Extract'; + sProcess.isSourceAbsolute = 0; + sProcesses(end + 1) = sProcess; + + % === NEUROMAG RECORDINGS === + if isAll || isData + sProcess = sModel; + sProcess.Comment = '==== NEUROMAG RECORDINGS ====='; + sProcess.Description = 'Please select a process...'; + sProcesses(end + 1) = sProcess; + % GRADIOMETERS NORM + sProcess = sModel; + sProcess.Name = 'gradnorm'; + sProcess.Comment = ' Norm of gradiometers couples'; + sProcess.FileTag = '| gradnorm'; + sProcess.Description = 'Compute the norm for each gradiometer couple.'; + sProcess.Category = 'Filter'; + sProcess.UseBaseline = 0; + sProcess.isAvgRef = 0; + sProcesses(end + 1) = sProcess; + end + + % === SPECTRAL ANALYSIS === + if isAll || ~isData + sProcess = sModel; + sProcess.Comment = '==== SPECTRAL ANALYSIS ========'; + sProcess.Description = 'Please select a process...'; + sProcesses(end + 1) = sProcess; + % Frequency-Band SAMPLE SPECTRAL DECOMPOSITION + sProcess = sModel; + sProcess.Name = 'spectDecomp'; + sProcess.Comment = ' Spectral decomposition and statistics'; + sProcess.FileTag = ''; + sProcess.Description = ['Computes power in multiple, standard frequency bands.' 10 ... + 'Yields average, standard deviation and t-statistics across samples.' 10 ... + 'WARNING: Only applies to KERNEL sources files.']; + sProcess.Category = 'Spectral'; + sProcess.isSourceAbsolute = 0; + sProcesses(end + 1) = sProcess; + + % Power spectrum analysis + sProcess = sModel; + sProcess.Name = 'powerSpectrum'; + sProcess.Comment = ' Fourier magnitude'; + sProcess.FileTag = ''; + sProcess.Description = ['Transforms each time series in file into spectral domain (modulus of Fourier transform).' 10 ... + 'Time vector is replaced by corresponding frequency bin used in Fourier tranform.' 10 ... + 'WARNING: Only applies to KERNEL sources files.']; + sProcess.Category = 'Spectral'; + sProcess.isSourceAbsolute = 0; + sProcesses(end + 1) = sProcess; + end + + % === RECURRENCE MAPS=== + if isAll || ~isData + sProcess = sModel; + sProcess.Comment = '==== RECURRENCE MAPS ========'; + sProcess.Description = 'Please select a process...'; + sProcesses(end + 1) = sProcess; + sProcess = sModel; + sProcess.Name = 'recMaps'; + sProcess.Comment = ' Recurrence maps of activations'; + sProcess.FileTag = ''; + sProcess.Description = ['Computes recurrence maps across samples.' 10 ... + 'Source maps above 75% activation are thresholded and binarized before being summed across samples.' 10 ... + 'WARNING: Only applies to KERNEL sources files.']; + sProcess.Category = 'Recurrence'; + sProcess.isSourceAbsolute = 1; + sProcesses(end + 1) = sProcess; + end + + % === SCOUTS functional Connectivity === + if isAll || ~isData + sProcess = sModel; + sProcess.Comment = '==== Scouts f-Connectivity ========'; + sProcess.Description = 'Please select a process...'; + sProcesses(end + 1) = sProcess; + sProcess = sModel; + sProcess.Name = 'fScoutConnect'; + sProcess.Comment = ' Evaluate functional connectivity between cortical scouts'; + sProcess.FileTag = ''; + sProcess.Description = ['Evaluate functional connectivity between cortical scouts.' 10 ... + 'Under development: use with caution.' 10 ... + 'WARNING: Only applies to KERNEL sources files.']; + sProcess.Category = 'fConnectivity'; + sProcess.isSourceAbsolute = 0; + sProcesses(end + 1) = sProcess; + end + end + + % ===== "A/B" PROCESSES ===== + if isAll || (NbSamplesSets == 2) + % Differences : same number of samples in each set + if isAll || (nbSamplesA == nbSamplesB) + % DIFFERENCE: A - B + sProcess = sModel; + sProcess.Name = 'diffAB'; + sProcess.Comment = ' A - B'; + sProcess.FileTag = '#A#-#B#'; + sProcess.Description = ['Difference of each couple of samples (A-B).' 10 10 ... + 'Each pair must share the same anatomy.' 10 ... + 'Result is stored in a new condition.']; + sProcess.Category = 'Filter2'; + sProcesses(end + 1) = sProcess; + % AVERAGE: Average(A,B) + sProcess = sModel; + sProcess.Name = 'meanAB'; + sProcess.Comment = ' Average(A,B)'; + sProcess.FileTag = 'Average(#A#,#B#)'; + sProcess.Description = ['Average of each couple of samples (A,B).' 10 10 ... + 'Each pair must share the same anatomy.' 10 ... + 'Result is stored in a new condition.']; + sProcess.Category = 'Filter2'; + sProcesses(end + 1) = sProcess; + end + end + + + % ===== SIMPLE T-TESTS ===== + % NEW: t-test equal variance + sTest = sModel; + sTest.Name = 'ttest'; + sTest.Comment = ' t-test (equal variances)'; + sTest.FileTag = 'ttest: #A# vs. #B#'; + sTest.Description = ['Student''s t-test for equal variances.' 10 10 ... + 'New version: use much less memory.']; + sTest.Category = 'TTest'; + sSimpleTests(end + 1) = sTest; + % NEW: t-test unequal variance + sTest = sModel; + sTest.Name = 'uttest'; + sTest.Comment = ' t-test (unequal variances)'; + sTest.FileTag = 'uttest: #A# vs. #B#'; + sTest.Description = ['Student''s t-test for unequal variances.' 10 10 ... + 'New version: use much less memory.']; + sTest.Category = 'TTest'; + sSimpleTests(end + 1) = sTest; + % Paired t-test and old versions need the same number of samples in both sets + if isAll || (nbSamplesA == nbSamplesB) + % NEW: paired t-test + sTest = sModel; + sTest.Name = 'pttest'; + sTest.Comment = ' t-test (paired)'; + sTest.FileTag = 'pttest: #A# vs. #B#'; + sTest.Description = ['Student''s t-test for paired samples.' 10 10 ... + 'Use for testing conditions across different subjects.' 10 ... + 'New version: use much less memory.']; + sTest.Category = 'TTest'; + sTest.isPaired = 1; + sSimpleTests(end + 1) = sTest; + % OLD: t-test equal variances + sTest = sModel; + sTest.Name = 'old_ttest'; + sTest.Comment = ' OLD t-test (equal variances)'; + sTest.FileTag = 'old_ttest: #A# vs. #B#'; + sTest.Description = ['Student''s t-test for equal variances.' 10 10 ... + 'Old version: might be faster and more precise.']; + sTest.Category = 'TTest'; + sSimpleTests(end + 1) = sTest; + % OLD: t-test unequal variance + sTest = sModel; + sTest.Name = 'old_uttest'; + sTest.Comment = ' OLD t-test (unequal variances)'; + sTest.FileTag = 'old_uttest: #A# vs. #B#'; + sTest.Description = ['Student''s t-test for unequal variances.' 10 10 ... + 'Old version: might be faster and more precise.']; + sTest.Category = 'TTest'; + sSimpleTests(end + 1) = sTest; + % OLD: paired t-test + sTest = sModel; + sTest.Name = 'old_pttest'; + sTest.Comment = ' OLD t-test (paired)'; + sTest.FileTag = 'old_pttest: #A# vs. #B#'; + sTest.Description = ['Student''s t-test for paired samples.' 10 10 ... + 'Use for testing conditions across different subjects.' 10 ... + 'Old version: might be faster and more precise.']; + sTest.isPaired = 1; + sTest.Category = 'TTest'; + sSimpleTests(end + 1) = sTest; + end + + + % ===== PERMUTATION TESTS ===== + % PERM: t-test (independent) + sTest = sModel; + sTest.Name = 'ttest'; + sTest.Comment = ' t-test (independent)'; + sTest.FileTag = 'ttest: #A# vs. #B#'; + sTest.Description = 'Student''s t-test for independent samples.'; + sTest.Category = 'PermTest'; + sPermTests(end + 1) = sTest; + % PERM: t-test (paired) + sTest = sModel; + sTest.Name = 'pairedttest'; + sTest.Comment = ' t-test (paired)'; + sTest.FileTag = 'pairedttest: #A# vs. #B#'; + sTest.Description = 'Student''s t-test for paired samples.'; + sTest.Category = 'PermTest'; + sTest.isPaired = 1; + sPermTests(end + 1) = sTest; + % PERM: Sign of the differences + sTest = sModel; + sTest.Name = 'signtest'; + sTest.Comment = ' signtest (paired)'; + sTest.FileTag = 'signtest: #A# vs. #B#'; + sTest.Description = 'Sign of the differences.'; + sTest.Category = 'PermTest'; + sTest.isPaired = 1; + sPermTests(end + 1) = sTest; + % PERM: Wilcoxon + sTest = sModel; + sTest.Name = 'wilcoxon'; + sTest.Comment = ' wilcoxon (paired)'; + sTest.FileTag = 'wilcoxon: #A# vs. #B#'; + sTest.Description = 'Signed ranks.'; + sTest.Category = 'PermTest'; + sTest.isPaired = 1; + sPermTests(end + 1) = sTest; + % PERM: Difference of the means + sTest = sModel; + sTest.Name = 'difftest'; + sTest.Comment = ' Difference of the means'; + sTest.FileTag = 'difftest: #A# vs. #B#'; + sTest.Description = 'Difference of the means.'; + sTest.Category = 'PermTest'; + sPermTests(end + 1) = sTest; + + % ===== ANOVA ===== + % + sTest = sModel; + sTest.Name = 'RM-Anova'; + sTest.Comment = 'Anova (under dev.)'; + sTest.FileTag = 'Anova'; + sTest.Description = 'Repeated measures parametric Analysis of Variance'; + sTest.Category = 'Anova'; + sAnova(end + 1) = sTest; + +end + + + +%% ===== FORMAT CONDITIONS STRING ===== +function str = FormatConditionsString(Samples, Default) + % If no samples + if isempty(Samples) + str = ''; + return + end + % Get the best way to display the sets names + uniqueCond = unique({Samples.Condition}); + uniqueSubj = unique({Samples.SubjectName}); + isUniqueCond = (length(uniqueCond) == 1); + isUniqueSubj = (length(uniqueSubj) == 1); + % Switch + if isUniqueCond && isUniqueSubj + str = fullfile(Samples.SubjectName, Samples.Condition); + elseif isUniqueCond + str = Samples.Condition; + elseif isUniqueSubj + str = Samples.SubjectName; + else + str = Default; + end +end + + +%% ===== FORMAT COMMENT ===== +function Comment = FormatComment(sProcess, Conditions, TimeRange) + % ===== BUILD COMMENT STRINGS ===== + sA = Conditions.SamplesA; + sB = Conditions.SamplesB; + CommentA = ''; + CommentB = ''; + % Extract some more information + isSampleBySample = ismember(sProcess.Category, {'Filter2', 'Filter', 'Extract'}); + + % === SAMPLE BY SAMPLE: A/B === + if isSampleBySample && (length(sA) == 1) && (length(sB) == 1) + if strcmpi(sA.SubjectFile, sB.SubjectFile) && strcmpi(sA.Condition, sB.Condition) + CommentA = sA.Comment; + CommentB = sB.Comment; + elseif io_compareFileNames(sA.SubjectFile, sB.SubjectFile) + CommentA = sA.Condition; + CommentB = sB.Condition; + elseif strcmpi(sA.Condition, sB.Condition) + CommentA = fullfile(sA.SubjectName, sA.Condition); + CommentB = fullfile(sB.SubjectName, sB.Condition); + else + CommentA = fullfile(sA.SubjectName, sA.Condition); + CommentB = fullfile(sB.SubjectName, sB.Condition); + end + % === AVERAGE === + elseif ~isSampleBySample + CommentA = FormatConditionsString(sA, 'A'); + CommentB = FormatConditionsString(sB, 'B'); + end + + % Build string for time interval + if (max(abs(TimeRange)) > 2) + strTime = sprintf('(%1.2fs,%1.2fs)', TimeRange); + else + strTime = sprintf('(%dms,%dms)', round(TimeRange * 1000)); + end + % Apply process naming + if ~isempty(CommentA) || isempty(sB) + Comment = sProcess.FileTag; + Comment = strrep(Comment, '#A#', CommentA); + Comment = strrep(Comment, '#B#', CommentB); + Comment = strrep(Comment, '#TIME#', strTime); + else + Comment = ''; + end +end + + +%% ===== GET CLUSTERS ===== +% OUTPUT: +% - sClusters : -1, if no channel file available for at least one sample +% -2, if no clusters available +% Else, return an array of Cluster structures +function [sClusters, iClusters] = GetClusters(Cond) + % Get unique list of studies + iStudies = unique([Cond.SamplesA.iStudy, Cond.SamplesB.iStudy]); + % Process all studies + for i = 1:length(iStudies) + % Get study structure + iStudy = iStudies(i); + %sStudy = bst_getContext('Study', iStudy); + sChannel = bst_getContext('ChannelForStudy', iStudy); + % Check that a ChannelFile is defined for this study + if isempty(sChannel) + sClusters = -1; + iClusters = []; + return + end + end + % Get the scouts available for this surface + [sClusters, iClusters] = panel_clusters('GetClusters'); + % If no scout avaialable: error + if isempty(sClusters) + sClusters = -2; + iClusters = []; + return; + end +end + + +%% ===== GET SCOUTS ===== +% OUTPUT: +% - sScouts : -1, if all samples do not refer to the same cortical surface +% -2, if no scout avaialable +% Else, return an array of scout structures +function [sScouts, iScouts] = GetScouts(Cond) + iScouts = []; + % Get studies and results indices + iStudies = [Cond.SamplesA.iStudy, Cond.SamplesB.iStudy]; + iResults = [Cond.SamplesA.iItem, Cond.SamplesB.iItem]; + SurfaceFile = []; + % Process all studies + for i = 1:length(iStudies) + iStudy = iStudies(i); + sStudy = bst_getContext('Study', iStudy); + % Get result file + ResultsMat = in_results_bst(sStudy.Result(iResults(i)).FileName, 0, 'SurfaceFile'); + % If it is first results: store the surface file + if isempty(SurfaceFile) + SurfaceFile = ResultsMat.SurfaceFile; + % If surface file is not the same than previous files: error + elseif ~io_compareFileNames(ResultsMat.SurfaceFile, SurfaceFile) + sScouts = -1; + return + end + end + % Get the scouts available for this surface + [sScouts, iScouts] = panel_scouts('GetScoutsWithSurface', SurfaceFile); + % If no scout avaialable: error + if isempty(sScouts) + sScouts = -2; + return; + end +end + + +%% ================================================================================================ +% ===== HELPERS ================================================================================== +% ================================================================================================ +%% ===== GET FILE TIME VECTOR ===== +function TimeVector = GetFileTimeVector(iStudy, iItem, isData) + TimeVector = []; + % Get protocols directories + ProtocolInfo = bst_getContext('ProtocolInfo'); + % Get study + sStudy = bst_getContext('Study', iStudy); + % Load time range from this file + if (isData) + % Build filename + filename = fullfile(ProtocolInfo.STUDIES, sStudy.Data(iItem).FileName); + % Load time vector + DataMat = load(filename, 'Time'); + if ~isempty(DataMat) && isfield(DataMat, 'Time') && ~isempty(DataMat.Time) + TimeVector = DataMat.Time; + end + else + % Build filename + refFilename = fullfile(ProtocolInfo.STUDIES, sStudy.Result(iItem).FileName); + % Resvole link + refFilename = resolveResultsLink(refFilename); + % Is results file is a kernel-only file + isKernel = ~isempty(strfind(refFilename, 'KERNEL')); + % KERNEL + if isKernel + % No time information available in file => Use DataMat results + DataFile = strrep(sStudy.Result(iItem).DataFile, ProtocolInfo.STUDIES, ''); + DataFileFull = fullfile(ProtocolInfo.STUDIES, DataFile); + DataMat = load(DataFileFull, 'Time'); + if ~isempty(DataMat) && isfield(DataMat, 'Time') && ~isempty(DataMat.Time) + TimeVector = DataMat.Time; + end + % FULL RESULTS FILE + else + % Load time vector + ResultMat = in_results_bst(refFilename, 0, 'ImageGridTime'); + if ~isempty(ResultMat) && isfield(ResultMat, 'ImageGridTime') + TimeVector = ResultMat.ImageGridTime; + end + end + end +end + + diff --git a/brainstorm3/toolbox/gui/panel_surface.m b/brainstorm3/toolbox/gui/panel_surface.m new file mode 100644 index 0000000..582dceb --- /dev/null +++ b/brainstorm3/toolbox/gui/panel_surface.m @@ -0,0 +1,1840 @@ +function varargout = panel_surface(varargin) +% PANEL_SURFACE: Panel to load and plot surfaces. +% +% USAGE: bstPanel = panel_surface('CreatePanel') +% panel_surface('UpdatePanel') +% panel_surface('CurrentFigureChanged_Callback') +% nbSurfaces = panel_surface('CreateSurfaceList', % % iSurface = panel_surface('AddSurface', hFig, surfaceFile) +% panel_surface('UpdateSurfaceProperties') +% iSurface = panel_surface('AddSurface', hFig, surfaceFile) +% panel_surface('RemoveSurface', hFig, iSurface) +% panel_surface('SetSurfaceTransparency', hFig, iSurf, alpha) +% panel_surface('SetSurfaceColor', hFig, iSurf, newColor) +% panel_surface('ApplyDefaultDisplay') +% [isOk] = panel_surface('SetSurfaceData', hFig, iTess, dataType, dataFile, isStat, isZscore) + +% [isOk] = panel_surface('UpdateSurfaceData', hFig, iSurfaces) +% [iCortex,TessInfo,hFig] = panel_surface('GetSurfaceCortex') +% iCortex = panel_surface('GetSurfaceCortex', hFig) +% panel_surface('UpdateOverlayCube', hFig, iTess) + +% @============================================================================= +% This software is part of The Brainstorm Toolbox +% http://neuroimage.usc.edu/brainstorm +% +% Copyright (c)2009 Brainstorm by the University of Southern California +% This software is distributed under the terms of the GNU General Public License +% as published by the Free Software Foundation. Further details on the GPL +% license can be found at http://www.gnu.org/copyleft/gpl.html. +% +% FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE +% UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY +% WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF +% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY +% LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE. +% +% For more information type "brainstorm licence" at command prompt. +% =============================================================================@ +% +% Authors: Francois Tadel, 2008 +% ----------------------------- Script History --------------------------------- +% FT 24-Jun-2008 Creation +% ------------------------------------------------------------------------------ + +% No parameters +if (nargin == 0) +% Else : execute appropriate local function +elseif ischar(varargin{1}) + if (nargout) + [varargout{1:nargout}] = bst_safeCall(str2func(varargin{1}), varargin{2:end}); + else + bst_safeCall(str2func(varargin{1}), varargin{2:end}); + end +end +end + + +%% ===== CREATE PANEL ===== +function bstPanelNew = CreatePanel() %#ok + panelName = 'Surface'; + % Java initializations + import java.awt.*; + import javax.swing.*; + import org.brainstorm.icon.IconLoader; + + % Constants + LABEL_WIDTH = 30; + BUTTON_WIDTH = 40; + SLIDER_WIDTH = 20; + DEFAULT_HEIGHT = 22; + TB_HEIGHT = 28; + jFontText = java.awt.Font('Dialog', java.awt.Font.PLAIN, 10); + % Create panel + jPanelNew = JPanel(BorderLayout()); + jPanelNew.setBorder([]); + + % ==================================================================== + % ==== TOOLBAR : SURFACES LIST ======================================= + % Create Toolbar + jToolbarSurfaces = JToolBar('Edit scouts'); + jToolbarSurfaces.setBorderPainted(0); + jToolbarSurfaces.setFloatable(0); + jToolbarSurfaces.setRollover(1); + jToolbarSurfaces.setPreferredSize(Dimension(100, TB_HEIGHT)); + % Add title "Surfaces" + jLabelSurfacesTitle = JLabel(' Surfaces:'); + jLabelSurfacesTitle.setFont(jFontText); + jToolbarSurfaces.add(jLabelSurfacesTitle); + % Separation panel + jToolbarSurfaces.add(JPanel()); + % Add separator + jToolbarSurfaces.addSeparator(Dimension(10, TB_HEIGHT)); + % Create "Add" button + jButtonAddSurface = JButton(IconLoader.ICON_SURFACE_ADD); + jButtonAddSurface.setMaximumSize(Dimension(26, 26)); + jButtonAddSurface.setPreferredSize(Dimension(26, 26)); + jButtonAddSurface.setToolTipText('Add a surface'); + jButtonAddSurface.setFocusPainted(0); + set(jButtonAddSurface, 'ActionPerformedCallback', @ButtonAddSurfaceCallback); + jToolbarSurfaces.add(jButtonAddSurface); + % Create "Remove" button + jButtonRemoveSurface = JButton(IconLoader.ICON_SURFACE_REMOVE); + jButtonRemoveSurface.setMaximumSize(Dimension(26, 26)); + jButtonRemoveSurface.setPreferredSize(Dimension(26, 26)); + jButtonRemoveSurface.setToolTipText('Remove surface from figure'); + jButtonRemoveSurface.setFocusPainted(0); + set(jButtonRemoveSurface, 'ActionPerformedCallback', @ButtonRemoveSurfaceCallback); + jToolbarSurfaces.add(jButtonRemoveSurface); + jPanelNew.add(jToolbarSurfaces, BorderLayout.NORTH); + + % ==================================================================== + % ==== OPTIONS PANEL ================================================== + % Create OPTIONS scrollpane + jPanelOptions = getRiverPanel([3,5]); + % ===== Create panel : surface configuration ===== + jPanelSurfaceOptions = getRiverPanel([1,1], [1,1,1,1], 'Surface options'); + % Alpha title + jLabelTranspTitle = JLabel('Transp.:'); + jLabelTranspTitle.setFont(jFontText); + jPanelSurfaceOptions.add('br', jLabelTranspTitle); + % Alpha slider + jSliderSurfAlpha = JSlider(0, 100, 0); + jSliderSurfAlpha.setPreferredSize(Dimension(SLIDER_WIDTH, DEFAULT_HEIGHT)); + set(jSliderSurfAlpha, 'MouseReleasedCallback', @(h,ev)SliderCallback(h, ev, 'SurfAlpha'), ... + 'KeyPressedCallback', @(h,ev)SliderCallback(h, ev, 'SurfAlpha')); + jPanelSurfaceOptions.add('tab hfill', jSliderSurfAlpha); + % Alpha label + jLabelSurfAlpha = JLabel(' ', JLabel.RIGHT); + jLabelSurfAlpha.setFont(jFontText); + jLabelSurfAlpha.setPreferredSize(Dimension(LABEL_WIDTH, DEFAULT_HEIGHT)); + jLabelSurfAlpha.setToolTipText('Set surface transparency'); + jPanelSurfaceOptions.add(jLabelSurfAlpha); + % Color button + jButtonSurfColor = JButton('Color'); + jButtonSurfColor.setFont(jFontText); + jButtonSurfColor.setPreferredSize(Dimension(BUTTON_WIDTH, DEFAULT_HEIGHT)); + jButtonSurfColor.setMargin(Insets(0,0,0,0)); + jButtonSurfColor.setToolTipText('Set surface color'); + jButtonSurfColor.setFocusPainted(0); + set(jButtonSurfColor, 'ActionPerformedCallback', @ButtonSurfColorCallback); + jPanelSurfaceOptions.add(jButtonSurfColor); + + % Curvature title and slider + jLabelCurvTitle = JLabel('Curvature:'); + jLabelCurvTitle.setFont(jFontText); + jPanelSurfaceOptions.add('br', jLabelCurvTitle); + % Curvature : [-0.20, 0.20] with step=0.01 => Integer:[-20,20] + jSliderSurfCurvature = JSlider(-20, 20, 0); + jSliderSurfCurvature.setPreferredSize(Dimension(SLIDER_WIDTH,DEFAULT_HEIGHT)); + set(jSliderSurfCurvature, 'MouseReleasedCallback', @(h,ev)SliderCallback(h, ev, 'SurfCurvature'), ... + 'KeyPressedCallback', @(h,ev)SliderCallback(h, ev, 'SurfCurvature')); + jPanelSurfaceOptions.add('tab hfill', jSliderSurfCurvature); + % Curvature label + jLabelSurfCurvature = JLabel(' ', JLabel.RIGHT); + jLabelSurfCurvature.setFont(jFontText); + jLabelSurfCurvature.setPreferredSize(Dimension(LABEL_WIDTH,DEFAULT_HEIGHT)); + jPanelSurfaceOptions.add(jLabelSurfCurvature); + % Curvature 'View' button + jButtonSurfCurvature = JToggleButton('Show'); + jButtonSurfCurvature.setFont(jFontText); + jButtonSurfCurvature.setPreferredSize(Dimension(BUTTON_WIDTH,DEFAULT_HEIGHT)); + jButtonSurfCurvature.setMargin(Insets(0,0,0,0)); + jButtonSurfCurvature.setToolTipText('Show/hide surface curvature'); + jButtonSurfCurvature.setFocusPainted(0); + set(jButtonSurfCurvature, 'ActionPerformedCallback', @ButtonShowCurvatureCallback); + jPanelSurfaceOptions.add(jButtonSurfCurvature); + + % Smooth title + jLabelSmoothTitle = JLabel('Smooth:'); + jLabelSmoothTitle.setFont(jFontText); + jPanelSurfaceOptions.add('br', jLabelSmoothTitle); + % Smooth slider + jSliderSurfSmoothValue = JSlider(0, 100, 0); + jSliderSurfSmoothValue.setPreferredSize(Dimension(SLIDER_WIDTH, DEFAULT_HEIGHT)); + jSliderSurfSmoothValue.setToolTipText('Smooth surface'); + set(jSliderSurfSmoothValue, 'MouseReleasedCallback', @(h,ev)SliderCallback(h, ev, 'SurfSmoothValue'), ... + 'KeyPressedCallback', @(h,ev)SliderCallback(h, ev, 'SurfSmoothValue')); + jPanelSurfaceOptions.add('tab hfill', jSliderSurfSmoothValue); + % Smooth ALPHA label + jLabelSurfSmoothValue = JLabel(' ', JLabel.RIGHT); + jLabelSurfSmoothValue.setFont(jFontText); + jLabelSurfSmoothValue.setPreferredSize(Dimension(LABEL_WIDTH, DEFAULT_HEIGHT)); + jPanelSurfaceOptions.add(jLabelSurfSmoothValue); + + % Edge button + jButtonSurfEdge = JToggleButton('Edge'); + jButtonSurfEdge.setFont(jFontText); + jButtonSurfEdge.setPreferredSize(Dimension(BUTTON_WIDTH,DEFAULT_HEIGHT)); + jButtonSurfEdge.setMargin(Insets(0,0,0,0)); + jButtonSurfEdge.setToolTipText('Show/hide surface triangles'); + jButtonSurfEdge.setFocusPainted(0); + set(jButtonSurfEdge, 'ActionPerformedCallback', @ButtonShowEdgesCallback); + jPanelSurfaceOptions.add(jButtonSurfEdge); + jPanelOptions.add('br hfill', jPanelSurfaceOptions); + + % ===== Create panel : data description ===== + jPanelDataOptions = getRiverPanel([1,1], [1,8,1,10], 'Data options'); + % ====================== + % Threshold label + jLabelIntThreshTitle = JLabel('Int. Thresh.:'); + jLabelIntThreshTitle.setFont(jFontText); + jPanelDataOptions.add(jLabelIntThreshTitle); + % Threshold slider + jSliderDataIntThresh = JSlider(0, 100, 50); + jSliderDataIntThresh.setPreferredSize(Dimension(SLIDER_WIDTH, DEFAULT_HEIGHT)); + set(jSliderDataIntThresh, 'MouseReleasedCallback', @(h,ev)SliderCallback(h, ev, 'DataIntThreshold'), ... + 'KeyPressedCallback', @(h,ev)SliderCallback(h, ev, 'DataIntThreshold')); + jPanelDataOptions.add('tab hfill', jSliderDataIntThresh); + % Threshold label + jLabelDataIntThresh = JLabel(' ', JLabel.RIGHT); + jLabelDataIntThresh.setFont(jFontText); + jLabelDataIntThresh.setPreferredSize(Dimension(LABEL_WIDTH, DEFAULT_HEIGHT)); + jPanelDataOptions.add(jLabelDataIntThresh); + + % ====================== + % Cluster size (extent) threshold + jLabelExtThreshTitle = JLabel('Extent Thr.:'); + jLabelExtThreshTitle.setFont(jFontText); + jPanelDataOptions.add('br', jLabelExtThreshTitle); + % Threshold slider + jSliderDataExtThresh = JSlider(0, 100, 50); + jSliderDataExtThresh.setPreferredSize(Dimension(SLIDER_WIDTH, DEFAULT_HEIGHT)); + set(jSliderDataExtThresh, 'MouseReleasedCallback', @(h,ev)SliderCallback(h, ev, 'DataExtThreshold'), ... + 'KeyPressedCallback', @(h,ev)SliderCallback(h, ev, 'DataExtThreshold')); + jPanelDataOptions.add('tab hfill', jSliderDataExtThresh); + % Threshold label + jLabelDataExtThresh = JLabel(' ', JLabel.RIGHT); + jLabelDataExtThresh.setFont(jFontText); + jLabelDataExtThresh.setPreferredSize(Dimension(LABEL_WIDTH, DEFAULT_HEIGHT)); + jPanelDataOptions.add(jLabelDataExtThresh); + + % ====================== + % Alpha title and slider + jLabelAlphaTitle = JLabel('Transp.:'); + jLabelAlphaTitle.setFont(jFontText); + jPanelDataOptions.add('br', jLabelAlphaTitle); + jSliderDataAlpha = JSlider(0, 100, 0); + jSliderDataAlpha.setPreferredSize(Dimension(SLIDER_WIDTH, DEFAULT_HEIGHT)); + set(jSliderDataAlpha, 'MouseReleasedCallback', @(h,ev)SliderCallback(h, ev, 'DataAlpha'), ... + 'KeyPressedCallback', @(h,ev)SliderCallback(h, ev, 'DataAlpha')); + jPanelDataOptions.add('tab hfill', jSliderDataAlpha); + % Data alpha label + jLabelDataAlpha = JLabel(' ', JLabel.RIGHT); + jLabelDataAlpha.setFont(jFontText); + jLabelDataAlpha.setPreferredSize(Dimension(LABEL_WIDTH, DEFAULT_HEIGHT)); + jPanelDataOptions.add(jLabelDataAlpha); + + jPanelOptions.add('br hfill', jPanelDataOptions); + + % ===== Create panel : surface resect ===== + jPanelSurfaceResect = getRiverPanel([0,4], [1,8,8,0], 'Resect [X,Y,Z]'); + % === RESECT SLIDERS === + % Sub panel + panelResect = JPanel(); + panelResect.setLayout(BoxLayout(panelResect, BoxLayout.LINE_AXIS)); + % Resect X : Slider + jSliderResectX = JSlider(-100, 100, 0); + jSliderResectX.setPreferredSize(Dimension(SLIDER_WIDTH, DEFAULT_HEIGHT)); + set(jSliderResectX, 'MouseReleasedCallback', @(h,ev)SliderCallback(h, ev, 'ResectX'), ... + 'KeyPressedCallback', @(h,ev)SliderCallback(h, ev, 'ResectX')); + panelResect.add('hfill', jSliderResectX); + % Resect Y : Title and Slider + jSliderResectY = JSlider(-100, 100, 0); + jSliderResectY.setPreferredSize(Dimension(SLIDER_WIDTH, DEFAULT_HEIGHT)); + set(jSliderResectY, 'MouseReleasedCallback', @(h,ev)SliderCallback(h, ev, 'ResectY'), ... + 'KeyPressedCallback', @(h,ev)SliderCallback(h, ev, 'ResectY')); + panelResect.add('hfill', jSliderResectY); + % Resect Z : Title and Slider + jSliderResectZ = JSlider(-100, 100, 0); + jSliderResectZ.setPreferredSize(Dimension(SLIDER_WIDTH, DEFAULT_HEIGHT)); + set(jSliderResectZ, 'MouseReleasedCallback', @(h,ev)SliderCallback(h, ev, 'ResectZ'), ... + 'KeyPressedCallback', @(h,ev)SliderCallback(h, ev, 'ResectZ')); + panelResect.add('hfill', jSliderResectZ); + jPanelSurfaceResect.add('hfill', panelResect); + + % === HEMISPHERES SELECTION === + % Title + jPanelSurfaceResect.add('br', JLabel(' ')); + % Left Hemisphere + jToggleResectLeft = JToggleButton('Left'); + jToggleResectLeft.setFont(jFontText); + jToggleResectLeft.setMargin(Insets(0,0,0,0)); + jToggleResectLeft.setPreferredSize(Dimension(50, 20)); + jToggleResectLeft.setFocusPainted(0); + set(jToggleResectLeft, 'MouseReleasedCallback', @ButtonResectLeftToggle_Callback); + jPanelSurfaceResect.add(jToggleResectLeft); + + % Right Hemisphere + jToggleResectRight = JToggleButton('Right'); + jToggleResectRight.setFont(jFontText); + jToggleResectRight.setMargin(Insets(0,0,0,0)); + jToggleResectRight.setPreferredSize(Dimension(50, 20)); + jToggleResectRight.setFocusPainted(0); + set(jToggleResectRight, 'MouseReleasedCallback', @ButtonResectRightToggle_Callback); + jPanelSurfaceResect.add(jToggleResectRight); + + % Separator + jPanelSurfaceResect.add('hfill', JLabel(' ')); + + % Reset button + jButtonResectReset = JButton('Reset'); + jButtonResectReset.setFont(jFontText); + jButtonResectReset.setMargin(Insets(0,0,0,0)); + jButtonResectReset.setPreferredSize(Dimension(50, 20)); + jButtonResectReset.setFocusPainted(0); + set(jButtonResectReset, 'ActionPerformedCallback', @ButtonResectResetCallback); + jPanelSurfaceResect.add(jButtonResectReset); + % Separator from the border + jPanelSurfaceResect.add(JLabel(' ')); + + jPanelOptions.add('br hfill', jPanelSurfaceResect); + + % NbVertices Title + jLabelNbVertTitle = JLabel(' Vertices : '); + jLabelNbVertTitle.setFont(jFontText); + jPanelOptions.add('br', jLabelNbVertTitle); + % NbVertices + jLabelNbVertices = JLabel('0'); + jLabelNbVertices.setFont(jFontText); + jPanelOptions.add(jLabelNbVertices); + % NbFaces Title + jLabelNbFacesTitle = JLabel(' Faces : '); + jLabelNbFacesTitle.setFont(jFontText); + jPanelOptions.add(jLabelNbFacesTitle); + % NbFaces + jLabelNbFaces = JLabel('0'); + jLabelNbFaces.setFont(jFontText); + jPanelOptions.add(jLabelNbFaces); + + jScrollPaneOptions = JScrollPane(jPanelOptions); + jScrollPaneOptions.setBorder([]); + jPanelNew.add(jScrollPaneOptions, BorderLayout.CENTER); + + % Create the BstPanel object that is returned by the function + % => constructor BstPanel(jHandle, panelName, sControls) + bstPanelNew = BstPanel(panelName, ... + jPanelNew, ... + struct('jToolbarSurfaces', jToolbarSurfaces, ... + 'jPanelOptions', jPanelOptions, ... + 'jPanelSurfaceOptions', jPanelSurfaceOptions, ... + 'jPanelSurfaceResect', jPanelSurfaceResect, ... + 'jPanelDataOptions', jPanelDataOptions, ... + 'jLabelNbVertices', jLabelNbVertices, ... + 'jLabelNbFaces', jLabelNbFaces, ... + 'jSliderSurfAlpha', jSliderSurfAlpha, ... + 'jLabelSurfAlpha', jLabelSurfAlpha, ... + 'jButtonSurfColor', jButtonSurfColor, ... + 'jLabelSurfSmoothValue', jLabelSurfSmoothValue, ... + 'jSliderSurfSmoothValue', jSliderSurfSmoothValue, ... + 'jSliderSurfCurvature', jSliderSurfCurvature, ... + 'jLabelSurfCurvature', jLabelSurfCurvature, ... + 'jButtonSurfCurvature', jButtonSurfCurvature, ... + 'jButtonSurfEdge', jButtonSurfEdge, ... + 'jSliderResectX', jSliderResectX, ... + 'jSliderResectY', jSliderResectY, ... + 'jSliderResectZ', jSliderResectZ, ... + 'jToggleResectLeft', jToggleResectLeft, ... + 'jToggleResectRight', jToggleResectRight, ... + 'jSliderDataAlpha', jSliderDataAlpha, ... + 'jLabelDataAlpha', jLabelDataAlpha, ... + 'jSliderDataExtThresh', jSliderDataExtThresh, ... + 'jLabelDataExtThresh', jLabelDataExtThresh, ... + 'jSliderDataIntThresh', jSliderDataIntThresh, ... + 'jLabelDataIntThresh', jLabelDataIntThresh)); + + + + %% ===== RESET RESECT CALLBACK ===== + function ButtonResectResetCallback(varargin) + import java.awt.event.MouseEvent; + % Reset initial resect sliders positions + jSliderResectX.setValue(0); + jSliderResectY.setValue(0); + jSliderResectZ.setValue(0); + SliderCallback([], MouseEvent(jSliderResectX, 0, 0, 0, 0, 0, 1, 0), 'ResectX'); + SliderCallback([], MouseEvent(jSliderResectY, 0, 0, 0, 0, 0, 1, 0), 'ResectY'); + SliderCallback([], MouseEvent(jSliderResectZ, 0, 0, 0, 0, 0, 1, 0), 'ResectZ'); + end + + %% ===== RESECT LEFT TOGGLE CALLBACK ===== + function ButtonResectLeftToggle_Callback(varargin) + if jToggleResectLeft.isSelected() + jToggleResectRight.setSelected(0); + SelectHemispheres('left'); + else + SelectHemispheres('none'); + end + end + + %% ===== RESECT RIGHT TOGGLE CALLBACK ===== + function ButtonResectRightToggle_Callback(varargin) + if jToggleResectRight.isSelected() + jToggleResectLeft.setSelected(0); + SelectHemispheres('right'); + else + SelectHemispheres('none'); + end + end +end + + +%% ================================================================================= +% === CONTROLS CALLBACKS ========================================================= +% ================================================================================= +%% ===== SLIDERS CALLBACKS ===== +function SliderCallback(hObject, event, target) + % Get panel controls + ctrl = bst_getContext('PanelControls', 'Surface'); + % Get slider pointer + jSlider = event.getSource(); + % If slider is not enabled : do nothing + if ~jSlider.isEnabled() + return + end + + % Get handle to current 3DViz figure + hFig = gui_figuresManager('GetCurrentFigure', '3D'); + if isempty(hFig) + return + end + % Get current surface index (in the current figure) + iSurface = getappdata(hFig, 'iSurface'); + % If surface data is not accessible + if isempty(hFig) || isempty(iSurface) + return; + end + % Get figure AppData (figure's surfaces configuration) + TessInfo = getappdata(hFig, 'Surface'); + if (iSurface > length(TessInfo)) + return; + end + % Is selected surface a MRI/slices surface + isAnatomy = strcmpi(TessInfo(iSurface).Name, 'Anatomy'); + + % Get slider value and update surface value + switch (target) + case 'SurfAlpha' + % Update value in Surface array + TessInfo(iSurface).SurfAlpha = jSlider.getValue() / 100; + % Display value in the label associated with the slider + ctrl.jLabelSurfAlpha.setText(sprintf('%d%%', round(TessInfo(iSurface).SurfAlpha * 100))); + % Update current surface + setappdata(hFig, 'Surface', TessInfo); + % For MRI: redraw all slices + if isAnatomy + figure_callback(hFig, 'UpdateMriDisplay', hFig, [], TessInfo, iSurface); + % Else: Update color display on the surface + else + figure_callback(hFig, 'UpdateSurfaceAlpha', hFig, iSurface); + end + + case 'SurfSmoothValue' + TessInfo(iSurface).SurfSmoothValue = jSlider.getValue() / 100; + ctrl.jLabelSurfSmoothValue.setText(sprintf('%d%%', round(TessInfo(iSurface).SurfSmoothValue * 100))); + % Update current surface + setappdata(hFig, 'Surface', TessInfo); + % For MRI display : Smooth slider changes threshold + if isAnatomy + figure_callback(hFig, 'UpdateMriDisplay', hFig, [], TessInfo, iSurface); + % Else: Update color display on the surface + else + % Smooth surface + figure_callback(hFig, 'UpdateSurfaceAlpha', hFig, iSurface); + % Update scouts displayed on this surfce + panel_scouts('UpdateScoutsVertices', TessInfo(iSurface).SurfaceFile); + % Set the new value as the default value + DefaultSurfaceDisplay = bst_getContext('DefaultSurfaceDisplay'); + DefaultSurfaceDisplay.SurfSmoothValue = TessInfo(iSurface).SurfSmoothValue; + bst_setContext('DefaultSurfaceDisplay', DefaultSurfaceDisplay); + end + + case 'SurfCurvature' + % Update value in Surface array + % Correspondance : [-80,80] <=> [-0.80, 0.80] + TessInfo(iSurface).SurfCurvatureThreshold = jSlider.getValue() / 100; + ctrl.jLabelSurfCurvature.setText(sprintf('%d%%', round(100 * TessInfo(iSurface).SurfCurvatureThreshold))); + % Update current surface + setappdata(hFig, 'Surface', TessInfo); + % Update surface display (ONLY if Curvature is currently displayed) + if TessInfo(iSurface).SurfShowCurvature + figure_callback(hFig, 'UpdateSurfaceColor', hFig, iSurface); + end + + case 'DataAlpha' + % Update value in Surface array + TessInfo(iSurface).DataAlpha = jSlider.getValue() / 100; + ctrl.jLabelDataAlpha.setText(sprintf('%d%%', round(TessInfo(iSurface).DataAlpha * 100))); + % Update current surface + setappdata(hFig, 'Surface', TessInfo); + % Update color display on the surface + figure_callback(hFig, 'UpdateSurfaceColor', hFig, iSurface); + + case 'DataIntThreshold' + % Update value in Surface array + TessInfo(iSurface).DataIntThreshold = jSlider.getValue() / 100; + ctrl.jLabelDataIntThresh.setText(sprintf('%d%%', round(TessInfo(iSurface).DataIntThreshold * 100))); + % Update current surface + setappdata(hFig, 'Surface', TessInfo); + % Update color display on the surface + figure_callback(hFig, 'UpdateSurfaceColor', hFig, iSurface); + % Set the new value as the default value (NOT FOR MRI) + if ~isAnatomy + DefaultSurfaceDisplay = bst_getContext('DefaultSurfaceDisplay'); + DefaultSurfaceDisplay.DataIntThreshold = TessInfo(iSurface).DataIntThreshold; + bst_setContext('DefaultSurfaceDisplay', DefaultSurfaceDisplay); + end + + case 'DataExtThreshold' + % Threshold based on cluster size + % I use a very gross exponential scale + % Update value in Surface array + %TessInfo(iSurface).DataExtThreshold = round(exp(jSlider.getValue()/15))-1; + TessInfo(iSurface).DataExtThreshold = round(jSlider.getValue())-1; + ctrl.jLabelDataExtThresh.setText(sprintf('%d', TessInfo(iSurface).DataExtThreshold)); + % Update current surface + setappdata(hFig, 'Surface', TessInfo); + % Update color display on the surface + figure_callback(hFig, 'UpdateSurfaceColor', hFig, iSurface); + % Set the new value as the default value (NOT FOR MRI) + if ~isAnatomy + DefaultSurfaceDisplay = bst_getContext('DefaultSurfaceDisplay'); + DefaultSurfaceDisplay.DataExtThreshold = TessInfo(iSurface).DataExtThreshold; + bst_setContext('DefaultSurfaceDisplay', DefaultSurfaceDisplay); + end + + case {'ResectX', 'ResectY', 'ResectZ'} + % Get target axis + dim = find(strcmpi(target, {'ResectX', 'ResectY', 'ResectZ'})); + % JSliderResect values : [-100,100] + if isAnatomy + % Get MRI size + sMri = bst_dataSetsManager('GetMri', TessInfo(iSurface).SurfaceFile); + cubeSize = size(sMri.Cube); + % Change slice position + newPos = round((jSlider.getValue()+100) / 200 * cubeSize(dim)); + newPos = saturate(newPos, [1, cubeSize(dim)]); + TessInfo(iSurface).CutsPosition(dim) = newPos; + % Update MRI display + figure_callback(hFig, 'UpdateMriDisplay', hFig, dim, TessInfo, iSurface); + else + ResectSurface(hFig, iSurface, dim, jSlider.getValue() / 100); + end + + otherwise + error('Unknow slider'); + end +end + + + +%% ===== BUTTON SURFACE COLOR CALLBACK ===== +function ButtonSurfColorCallback(hObject, event) + % Get handle to current 3DViz figure + hFig = gui_figuresManager('GetCurrentFigure', '3D'); + if isempty(hFig) + return + end + % Get figure AppData (figure's surfaces configuration) + TessInfo = getappdata(hFig, 'Surface'); + % Get current surface index (in the current figure) + iSurface = getappdata(hFig, 'iSurface'); + % Ignore MRI slices + if strcmpi(TessInfo(iSurface).Name, 'Anatomy') + return + end + % Ask user to select a color + newColor2 = uisetcolor(TessInfo(iSurface).AnatomyColor(2,:), 'Select surface color'); + if (length(newColor2) ~= 3) + return + end + % Change surface color + SetSurfaceColor(hFig, iSurface, newColor2); +end + + + +%% ===== BUTTON SURFACE "SHOW CURVATURE" CALLBACK ===== +function ButtonShowCurvatureCallback(hObject, event) + % Get handle to current 3DViz figure + hFig = gui_figuresManager('GetCurrentFigure', '3D'); + if isempty(hFig) + return + end + % Get current surface index (in the current figure) + iSurface = getappdata(hFig, 'iSurface'); + % Get handle to "View" button + jButtonSurfCurvature = event.getSource(); + % Show/hide curvature in figure display + SetShowCurvature(hFig, iSurface, jButtonSurfCurvature.isSelected()); + % Set the new value as the default value + DefaultSurfaceDisplay = bst_getContext('DefaultSurfaceDisplay'); + DefaultSurfaceDisplay.SurfShowCurvature = jButtonSurfCurvature.isSelected(); + bst_setContext('DefaultSurfaceDisplay', DefaultSurfaceDisplay); +end + +%% ===== SET SHOW CURVATURE ===== +% Usage : SetShowCurvature(hFig, iSurfaces, status) +% Parameters : +% - hFig : handle to a 3DViz figure +% - iSurfaces : can be a single indice or an array of indices +% - status : 1=display, 0=hide +function SetShowCurvature(hFig, iSurfaces, status) + % Get surfaces list + TessInfo = getappdata(hFig, 'Surface'); + gui_makeuswait('start') + % Process all surfaces + for iSurf = iSurfaces + % Shet status : show/hide + TessInfo(iSurf).SurfShowCurvature = status; + end + % Update figure's AppData (surfaces configuration) + setappdata(hFig, 'Surface', TessInfo); + % Update surface display + figure_callback(hFig, 'UpdateSurfaceColor', hFig, iSurf); + gui_makeuswait('stop'); +end + + +%% ===== SHOW SURFACE EDGES ===== +function ButtonShowEdgesCallback(varargin) + % Get handle to current 3DViz figure + hFig = gui_figuresManager('GetCurrentFigure', '3D'); + if isempty(hFig) + return + end + % Get current surface (in the current figure) + TessInfo = getappdata(hFig, 'Surface'); + iSurf = getappdata(hFig, 'iSurface'); + % Set edges display on/off + TessInfo(iSurf).SurfShowEdges = ~TessInfo(iSurf).SurfShowEdges; + setappdata(hFig, 'Surface', TessInfo); + % Update display + figure_callback(hFig, 'UpdateSurfaceColor', hFig, iSurf); +end + + +%% ===== HEMISPHERE SELECTION RADIO CALLBACKS ===== +function SelectHemispheres(name) + % Get handle to current 3DViz figure + hFig = gui_figuresManager('GetCurrentFigure', '3D'); + if isempty(hFig) + return + end + % Get surface properties + TessInfo = getappdata(hFig, 'Surface'); + iSurf = getappdata(hFig, 'iSurface'); + % Ignore MRI + if strcmpi(TessInfo(iSurf).Name, 'Anatomy') + return; + end + % Update surface Resect field + TessInfo(iSurf).Resect = name; + setappdata(hFig, 'Surface', TessInfo); + + % Reset all the resect sliders + ctrl = bst_getContext('PanelControls', 'Surface'); + ctrl.jSliderResectX.setValue(0); + ctrl.jSliderResectY.setValue(0); + ctrl.jSliderResectZ.setValue(0); + + % Display progress bar + bst_progressBar('start', 'Select hemisphere', 'Selecting hemisphere...'); + % Update surface display + figure_callback(hFig, 'UpdateSurfaceAlpha', hFig, iSurf); + % Display progress bar + bst_progressBar('stop'); +end + + +%% ===== RESECT SURFACE ===== +function ResectSurface(hFig, iSurf, resectDim, resectValue) + % Get surfaces description + TessInfo = getappdata(hFig, 'Surface'); + % If previously using "Select hemispheres" + if ischar(TessInfo(iSurf).Resect) + % Reset "Resect" field + TessInfo(iSurf).Resect = [0 0 0]; + end + % Update value in Surface array + TessInfo(iSurf).Resect(resectDim) = resectValue; + % Update surface + setappdata(hFig, 'Surface', TessInfo); + % Hide trimmed part of the surface + figure_callback(hFig, 'UpdateSurfaceAlpha', hFig, iSurf); + + % Deselect both Left and Right buttons + ctrl = bst_getContext('PanelControls', 'Surface'); + ctrl.jToggleResectLeft.setSelected(0); + ctrl.jToggleResectRight.setSelected(0); +end + + +%% ===== ADD SURFACE CALLBACK ===== +function ButtonAddSurfaceCallback(varargin) + % Get target figure handle + hFig = gui_figuresManager('GetCurrentFigure', '3D'); + if isempty(hFig) + return + end + % Get Current subject + SubjectFile = getappdata(hFig, 'SubjectFile'); + if isempty(SubjectFile) + return + end + sSubject = bst_getContext('Subject', SubjectFile); + if isempty(sSubject) + return + end + + % List of available surfaces types + typesList = {}; + if ~isempty(sSubject.iScalp) + typesList{end+1} = 'Scalp'; + end + if ~isempty(sSubject.iOuterSkull) + typesList{end+1} = 'OuterSkull'; + end + if ~isempty(sSubject.iInnerSkull) + typesList{end+1} = 'InnerSkull'; + end + if ~isempty(sSubject.iCortex) + typesList{end+1} = 'Cortex'; + end + if ~isempty(sSubject.iAnatomy) + typesList{end+1} = 'Anatomy'; + end + if isempty(typesList) + return + end + % Ask user which kind of surface he wants to add to the figure 3DViz + surfaceType = java_dialog('question', 'What kind of surface would you like to display ?', 'Add surface', [], typesList, typesList{1}); + + % Switch between surfaces types + switch (surfaceType) + case 'Anatomy' + SurfaceFile = sSubject.Anatomy(sSubject.iAnatomy(1)).FileName; + case 'Cortex' + SurfaceFile = sSubject.Surface(sSubject.iCortex(1)).FileName; + case 'Scalp' + SurfaceFile = sSubject.Surface(sSubject.iScalp(1)).FileName; + case 'InnerSkull' + SurfaceFile = sSubject.Surface(sSubject.iInnerSkull(1)).FileName; + case 'OuterSkull' + SurfaceFile = sSubject.Surface(sSubject.iSurfaceFile(1)).FileName; + otherwise + return; + end + % Add surface to the figure + AddSurface(hFig, SurfaceFile); + % 3D MRI: Update Colormap + if strcmpi(surfaceType, 'Anatomy') + % Get figure + [hFig,iFig,iDS] = gui_figuresManager('GetFigure', hFig); + % Update colormap + gui_figure3DViz('ColormapChangedCallback', iDS, iFig); + end +end + + +%% ===== REMOVE SURFACE CALLBACK ===== +function ButtonRemoveSurfaceCallback(varargin) + % Get target figure handle + hFig = gui_figuresManager('GetCurrentFigure', '3D'); + if isempty(hFig) + return + end + % Get current surface index + iSurface = getappdata(hFig, 'iSurface'); + if isempty(iSurface) + return + end + % Remove surface + RemoveSurface(hFig, iSurface); + % Update "Surfaces" panel + UpdatePanel(); +end + + +%% ===== SURFACE BUTTON CLICKED CALLBACK ===== +function ButtonSurfaceClickedCallback(hObject, event, varargin) + % Get current 3DViz figure + hFig = gui_figuresManager('GetCurrentFigure', '3D'); + if isempty(hFig) + return + end + % Get index of the surface associated to this button + iSurface = str2num(event.getSource.getName()); + % Store current surface index + setappdata(hFig, 'iSurface', iSurface); + % Update scouts surface + panel_scouts('UpdateCurrentSurface'); + % Update surface properties + UpdateSurfaceProperties(); +end + + + +%% ================================================================================= +% === EXTERNAL CALLBACKS ========================================================= +% ================================================================================= +%% ===== UPDATE PANEL ===== +function UpdatePanel(varargin) + % Get JComboBox pointer + panelSurfacesCtrl = bst_getContext('PanelControls', 'Surface'); + if isempty(panelSurfacesCtrl) + return + end + % If no current 3D figure defined + hFig = gui_figuresManager('GetCurrentFigure', '3D'); + if isempty(hFig) + % Remove surface buttons + CreateSurfaceList(panelSurfacesCtrl.jToolbarSurfaces, 0); + % Disable all panel controls + gui_setEnabledControls([panelSurfacesCtrl.jToolbarSurfaces, panelSurfacesCtrl.jPanelOptions], 0); + else + % Enable Surfaces selection panel + gui_setEnabledControls(panelSurfacesCtrl.jToolbarSurfaces, 1); + % Update surfaces list + nbSurfaces = CreateSurfaceList(panelSurfacesCtrl.jToolbarSurfaces, hFig); + % If no surface is available + if (nbSurfaces <= 0) + % Disable "Display" and "Options" panel + gui_setEnabledControls(panelSurfacesCtrl.jPanelOptions, 0); + % Else : one or more surfaces are available + else + % Enable "Display" and "Options" panel + gui_setEnabledControls(panelSurfacesCtrl.jPanelOptions, 1); + % Update surface properties + UpdateSurfaceProperties(); + end + end +end + + +%% ===== DISPATCH FIGURE CALLBACKS ===== +function figure_callback(hFig, CallbackName, varargin) + % Get figure type + FigureId = getappdata(hFig, 'FigureId'); + % Different figure types + switch (FigureId.Type) + case 'MriViewer' + gui_figureMriViewer(CallbackName, varargin{:}); + case '3DViz' + gui_figure3DViz(CallbackName, varargin{:}); + end +end + + +%% ===== CURRENT FIGURE CHANGED ===== +function CurrentFigureChanged_Callback() %#ok + UpdatePanel(); +end + + +%% ===== CREATE SURFACES LIST ===== +function nbSurfaces = CreateSurfaceList(jToolbarSurfaces, hFig) + % Java initializations + import java.awt.*; + import javax.swing.*; + import org.brainstorm.icon.IconLoader; + + nbSurfaces = 0; + % Remove all toolbar surface buttons + for iComp = 1:jToolbarSurfaces.getComponentCount()-5 + jToolbarSurfaces.remove(1); + end + % If no figure is specified : return + if isempty(hFig) || ~ishandle(hFig) || (hFig == 0) + return; + end + % Create a button group for Surfaces and "Add" button + jButtonGroup = ButtonGroup(); + + % If a figure is defined + if ishandle(hFig) + % Get selected surface index + iSurface = getappdata(hFig, 'iSurface'); + % Loop on all the available surfaces for this figure + TessInfo = getappdata(hFig, 'Surface'); + for iSurf = 1:length(TessInfo) + % Select only one button + isSelected = (iSurf == iSurface); + % Get button icon (depends on surface name) + switch lower(TessInfo(iSurf).Name) + case 'cortex' + iconButton = IconLoader.ICON_SURFACE_CORTEX; + case 'scalp' + iconButton = IconLoader.ICON_SURFACE_SCALP; + case 'innerskull' + iconButton = IconLoader.ICON_SURFACE_INNERSKULL; + case 'outerskull' + iconButton = IconLoader.ICON_SURFACE_OUTERSKULL; + case 'other' + iconButton = IconLoader.ICON_SURFACE; + case 'anatomy' + iconButton = IconLoader.ICON_ANATOMY; + end + % Create surface button + jButtonSurf = JToggleButton(iconButton, isSelected); + jButtonSurf.setMaximumSize(Dimension(24,24)); + jButtonSurf.setPreferredSize(Dimension(24,24)); + % Store the surface index as the button Name + jButtonSurf.setName(sprintf('%d', iSurf)); + % Attach a click callback + set(jButtonSurf, 'ActionPerformedCallback', @ButtonSurfaceClickedCallback); + % Add button to button group + jButtonGroup.add(jButtonSurf); + % Add button to toolbar, at the end of the surfaces list + iButton = jToolbarSurfaces.getComponentCount() - 4; + jToolbarSurfaces.add(jButtonSurf, iButton); + end + % Return number of surfaces added + nbSurfaces = length(TessInfo); + else + % No surface available for current figure + nbSurfaces = 0; + end + + % Update graphical composition of panel + jToolbarSurfaces.updateUI(); +end + + +%% ===== UPDATE SURFACE PROPERTIES ===== +function UpdateSurfaceProperties() +% disp('=== panel_surface > UpdateSurfaceProperties ==='); + import org.brainstorm.list.BstListItem; + % Get current figure handle + hFig = gui_figuresManager('GetCurrentFigure', '3D'); + if isempty(hFig) + return + end + % Get panel controls + panelControls = bst_getContext('PanelControls', 'Surface'); + if isempty(panelControls) + return + end + % Get selected surface properties + TessInfo = getappdata(hFig, 'Surface'); + if isempty(TessInfo) + return; + end + % Get selected surface index + iSurface = getappdata(hFig, 'iSurface'); + % If surface is sliced MRI + isAnatomy = strcmpi(TessInfo(iSurface).Name, 'Anatomy'); + + % ==== Surface properties ==== + % Number of vertices + panelControls.jLabelNbVertices.setText(sprintf('%d', TessInfo(iSurface).nVertices)); + % Number of faces + panelControls.jLabelNbFaces.setText(sprintf('%d', TessInfo(iSurface).nFaces)); + % Surface alpha + panelControls.jSliderSurfAlpha.setValue(100 * TessInfo(iSurface).SurfAlpha); + panelControls.jLabelSurfAlpha.setText(sprintf('%d%%', round(100 * TessInfo(iSurface).SurfAlpha))); + % Surface color + surfColor = TessInfo(iSurface).AnatomyColor(2, :); + panelControls.jButtonSurfColor.setBackground(java.awt.Color(surfColor(1),surfColor(2),surfColor(3))); + % Surface smoothing ALPHA + panelControls.jSliderSurfSmoothValue.setValue(100 * TessInfo(iSurface).SurfSmoothValue); + panelControls.jLabelSurfSmoothValue.setText(sprintf('%d%%', round(100 * TessInfo(iSurface).SurfSmoothValue))); + % Correspondance : [-0.80, 0.80] <=> [-80,80] + panelControls.jSliderSurfCurvature.setValue(round(100 * TessInfo(iSurface).SurfCurvatureThreshold)); + panelControls.jLabelSurfCurvature.setText(sprintf('%d%%', round(100 * TessInfo(iSurface).SurfCurvatureThreshold))); + % Show surface curvature button + panelControls.jButtonSurfCurvature.setSelected(TessInfo(iSurface).SurfShowCurvature); + % Show surface edges button + panelControls.jButtonSurfEdge.setSelected(TessInfo(iSurface).SurfShowEdges); + + % ==== Resect properties ==== + % Ignore for MRI slices + if isAnatomy + sMri = bst_dataSetsManager('GetMri', TessInfo(iSurface).SurfaceFile); + ResectXYZ = double(TessInfo(iSurface).CutsPosition) ./ size(sMri.Cube) * 200 - 100; + radioSelected = 'none'; + elseif ischar(TessInfo(iSurface).Resect) + ResectXYZ = [0,0,0]; + radioSelected = TessInfo(iSurface).Resect; + else + ResectXYZ = 100 * TessInfo(iSurface).Resect; + radioSelected = 'none'; + end + % X, Y, Z + panelControls.jSliderResectX.setValue(ResectXYZ(1)); + panelControls.jSliderResectY.setValue(ResectXYZ(2)); + panelControls.jSliderResectZ.setValue(ResectXYZ(3)); + + % Select one radio button + switch (radioSelected) + case 'left' + panelControls.jToggleResectLeft.setSelected(1); + panelControls.jToggleResectRight.setSelected(0); + case 'right' + panelControls.jToggleResectRight.setSelected(1); + panelControls.jToggleResectLeft.setSelected(0); + case 'none' + panelControls.jToggleResectLeft.setSelected(0); + panelControls.jToggleResectRight.setSelected(0); + end + + % ==== Data properties ==== + % Data alpha + panelControls.jSliderDataAlpha.setValue(100 * TessInfo(iSurface).DataAlpha); + panelControls.jLabelDataAlpha.setText(sprintf('%d%%', round(100 * TessInfo(iSurface).DataAlpha))); + % Data intensity threshold + panelControls.jSliderDataIntThresh.setValue(100 * TessInfo(iSurface).DataIntThreshold); + panelControls.jLabelDataIntThresh.setText(sprintf('%d%%', round(100 * TessInfo(iSurface).DataIntThreshold))); + % Cluster extent threshold + panelControls.jSliderDataExtThresh.setValue(TessInfo(iSurface).DataExtThreshold); + panelControls.jLabelDataExtThresh.setText(sprintf('%d', TessInfo(iSurface).DataExtThreshold)); + +end + + +%% ===== ADD A SURFACE ===== +% Add a surface to a given 3DViz figure +% USAGE : iTess = panel_surface('AddSurface', hFig, surfaceFile) +% OUTPUT: Indice of the surface in the figure's surface array +function iTess = AddSurface(hFig, surfaceFile) + % ===== CHECK EXISTENCE ===== + % Check whether filename is an absolute or relative path + if exist(surfaceFile, 'file') + ProtocolInfo = bst_getContext('ProtocolInfo'); + surfaceFile = strrep(surfaceFile, ProtocolInfo.SUBJECTS, ''); + end + % Get figure appdata (surfaces configuration) + TessInfo = getappdata(hFig, 'Surface'); + % Check that this surface is not already displayed in 3DViz figure + iTess = find(io_compareFileNames({TessInfo.SurfaceFile}, surfaceFile)); + if ~isempty(iTess) + warning('Brainstorm:SurfaceAlreadyDisplayed', 'This surface is already displayed. Ignoring...'); + return + end + % Get figure type + FigureId = getappdata(hFig, 'FigureId'); + % Progress bar + isNewProgressBar = ~bst_progressBar('isVisible'); + bst_progressBar('start', 'Add surface', 'Updating display...'); + + % ===== BUILD STRUCTURE ===== + % Add a new surface at the end of the figure's surfaces list + iTess = length(TessInfo) + 1; + TessInfo(iTess) = db_getDataTemplate('TessInfo'); + % Set the surface properties + TessInfo(iTess).SurfaceFile = surfaceFile; + TessInfo(iTess).DataSource.Type = ''; + TessInfo(iTess).DataSource.FileName = ''; + + % ===== PLOT OBJECT ===== + % Get file type (tessalation or MRI) + [fileFormat, fileType] = io_getFileType(surfaceFile); + % === TESSELATION === + if ismember('tess', fileType) + % === LOAD SURFACE === + % Load surface file + sSurface = bst_dataSetsManager('LoadSurface', surfaceFile); + % Get some properties + TessInfo(iTess).Name = sSurface.Name; + TessInfo(iTess).nVertices = size(sSurface.Vertices, 1); + TessInfo(iTess).nFaces = size(sSurface.Faces, 1); + + % === PLOT SURFACE === + switch (FigureId.Type) + case 'MriViewer' + % Nothing to do: surface will be displayed as an overlay slice in gui_figureMriViewer.m + case {'3DViz', 'Topography'} + % Create and display surface patch + [hFig, TessInfo(iTess).hPatch] = gui_figure3DViz('PlotSurface', hFig, ... + sSurface.Faces, ... + sSurface.Vertices, ... + TessInfo(iTess).AnatomyColor(2,:), ... + TessInfo(iTess).SurfAlpha); + end + % Update figure's surfaces list and current surface pointer + setappdata(hFig, 'Surface', TessInfo); + setappdata(hFig, 'iSurface', iTess); + % Show curvature if needed + if TessInfo(iTess).SurfShowCurvature + SetShowCurvature(hFig, iTess, 1); + end + + % === MRI === + elseif ismember('subjectimage', fileType) + % === LOAD MRI === + sMri = bst_dataSetsManager('LoadMri', surfaceFile); + TessInfo(iTess).Name = 'Anatomy'; + % Initial position of the cuts : middle in each direction + TessInfo(iTess).CutsPosition = round(size(sMri.Cube) / 2); + TessInfo(iTess).SurfSmoothValue = .3; + % Update figure's surfaces list and current surface pointer + setappdata(hFig, 'Surface', TessInfo); + setappdata(hFig, 'iSurface', iTess); + + % === PLOT MRI === + switch (FigureId.Type) + case 'MriViewer' + % Configure MRIViewer + gui_figureMriViewer('SetupMri', hFig); + case '3DViz' + % Camera basic orientation: TOP + gui_figure3DViz('SetStandardView', hFig, 'top'); + end + % Plot MRI + PlotMri(hFig); + end + % Automatically set transparencies (to view different layers at the same time) + SetAutoTransparency(hFig); + drawnow; + % Close progress bar + if isNewProgressBar + bst_progressBar('stop'); + end + % Update panel + UpdatePanel(); +end + + + +%% ===== SET DATA SOURCE FOR A SURFACE ===== +%Associate a data/results matrix to a surface. +% Usage : SetSurfaceData(hFig, iTess, dataType, dataFile, isStat, isZscore) +% Parameters : +% - hFig : handle to a 3DViz figure +% - iTess : indice of the surface to update (in hFig appdata) +% - dataType : type of data to overlay on the surface {'Source', 'Data', ...} +% - dataFile : filename of the data to display over the surface +% - isStat : 1, if results is a statistical result; 0, else +% - isZscore : 1, if results is a result of z-score normalization; 0, else +function isOk = SetSurfaceData(hFig, iTess, dataType, dataFile, isStat, isZscore) %#ok + % Get figure index in DataSet figures list + [tmp__, iFig, iDS] = gui_figuresManager('GetFigure', hFig); + if isempty(iDS) + error('No DataSet acessible for this 3D figure'); + end + % Get surfaces list for this figure + TessInfo = getappdata(hFig, 'Surface'); + + % === GET DATA THRESHOLD === + % Cortex + if strcmpi(TessInfo(iTess).Name, 'Cortex') + % Get defaults for surface display + DefaultSurfaceDisplay = bst_getContext('DefaultSurfaceDisplay'); + % Data threshold + try + dataIntThreshold = DefaultSurfaceDisplay.DataIntThreshold; + catch + DefaultSurfaceDisplay + dataIntThreshold = 1; + end + try + dataExtThreshold = DefaultSurfaceDisplay.DataExtThreshold; + catch + DefaultSurfaceDisplay + dataExtThreshold = 0; + end + % Anatomy or Statistics : 0% + elseif strcmpi(TessInfo(iTess).Name, 'Anatomy') || isStat + dataIntThreshold = 0; + dataExtThreshold = 0; + % Else: normal data on scalp + else + dataIntThreshold = 0.5; + dataExtThreshold = 0; + end + + % === PREPARE SURFACE === + TessInfo(iTess).DataSource.Type = dataType; + TessInfo(iTess).DataSource.FileName = dataFile; + TessInfo(iTess).DataSource.isStat = isStat; + TessInfo(iTess).DataSource.isZscore = isZscore; + TessInfo(iTess).DataIntThreshold = dataIntThreshold; + TessInfo(iTess).DataExtThreshold = dataExtThreshold; + % Type of data displayed on the surface: sources/recordings/nothing + switch (dataType) + case 'Data' + setappdata(hFig, 'DataFile', dataFile); + case 'Source' + setappdata(hFig, 'ResultsFile', dataFile); + case 'Surface' + % Nothing to do... + otherwise + TessInfo(iTess).Data = []; + TessInfo(iTess).DataWmat = []; + end + % Update figure appdata + setappdata(hFig, 'Surface', TessInfo); + % Plot surface + isOk = UpdateSurfaceData(hFig, iTess); + % Update panel + UpdatePanel(); +end + + + +%% ===== UPDATE SURFACE DATA ===== +% Update the 'Data' field for given surfaces : +% - Load data/results matrix (F, ImageGridAmp, ...) if it is not loaded yet +% - Store global minimum/maximum of data +% - Interpolate data matrix over the target surface (interp_mail) if number of vertices does not match +% - And update color display (ColormapChangedCallback) +% +% Usage: UpdateSurfaceData(hFig, iSurfaces) +% UpdateSurfaceData(hFig) +function isOk = UpdateSurfaceData(hFig, iSurfaces) +% disp('=== panel_surface > UpdateSurfaceData ==='); + global GlobalData; + isOk = 1; + % Get surfaces list + TessInfo = getappdata(hFig, 'Surface'); + % If the aim is to update all the surfaces + if (nargin < 2) || isempty(iSurfaces) + iSurfaces = 1:length(TessInfo); + end + + % Get figure index (in DataSet structure) + [tmp__, iFig, iDS] = gui_figuresManager('GetFigure', hFig); + % Find the DataSet indice that corresponds to the current figure + if isempty(iDS) + error('No DataSet acessible for this 3D figure'); + end + + % For each surface + for iTess = iSurfaces + % If surface patch object doesn't exist => error + if isempty(TessInfo(iTess).hPatch) + error('Patch is not displayed'); + end + + % ===== GET SURFACE DATA ===== + % Switch between different data types to display on the surface + switch (TessInfo(iTess).DataSource.Type) + case 'Data' + % Get TimeVector and current time indice + [TimeVector, CurrentTimeIndex] = bst_dataSetsManager('GetTimeVector', iDS); + % If 'F' matrix is not loaded for this file + if isempty(GlobalData.DataSet(iDS).Measures.F) + % Load recording matrix + bst_dataSetsManager('LoadRecordingsMatrix', iDS); + end + + % If surface is displayed : update it + if ~isempty(TessInfo(iTess).hPatch) && ishandle(TessInfo(iTess).hPatch) + % Get vertices of surface + Vertices = get(TessInfo(iTess).hPatch, 'Vertices'); + % Warn user if tessellation is huge + if size(Vertices,1) > 20000 + if ~java_dialog('confirm', {sprintf('Tessellation has %d vertices; computation time for rendering might be excessive.',size(Vertices,1)),'Do you still want to proceed ?'},'Long computation expected') + % Stop computation + isOk = 0; + return + end + end + + % Get selected channels indices and location + [SelectedChannels, PChanLocs] = gui_figure3DViz('GetSelectedChannels', iDS, iFig); +% % Get Selected channels +% SelectedChannels = good_channel(GlobalData.DataSet(iDS).Channel, ... +% GlobalData.DataSet(iDS).Measures.ChannelFlag, ... +% GlobalData.DataSet(iDS).Figure(iFig).Id.Modality); + + % Interpolate data on scalp surface (only if Matrix is not computed yet, or channels changed) + % => TRICK : it is difficult to test if the sensors locations or the surface vertices changed + % => Just the the number of channels and vertices (should be ok...) + if isempty(TessInfo(iTess).DataWmat) || ... + (size(TessInfo(iTess).DataWmat,2) ~= length(SelectedChannels)) || ... + (size(TessInfo(iTess).DataWmat,1) ~= length(Vertices)) + interType = [GlobalData.DataSet(iDS).Figure(iFig).Id.Modality, 'ToScalp']; + TessInfo(iTess).DataWmat = interp_mail(Vertices, PChanLocs, interType); + end + % Set data for current time frame + TessInfo(iTess).Data = single(TessInfo(iTess).DataWmat * ... + double(GlobalData.DataSet(iDS).Measures.F(SelectedChannels, CurrentTimeIndex))); + % Store minimum and maximum of displayed data + TessInfo(iTess).DataMinMax = [min(min(TessInfo(iTess).Data)), ... + max(max(TessInfo(iTess).Data))]; + end + % Update "Static" status for this figure + setappdata(hFig, 'isStatic', GlobalData.DataSet(iDS).Measures.isStatic); + + case 'Source' + % === LOAD RESULTS VALUES === + % Get results index + iResult = bst_dataSetsManager('GetResultInDataSet', iDS, TessInfo(iTess).DataSource.FileName); + % If Results file is not found in GlobalData structure + if isempty(iResult) + % Load Results file + [iDS, iResult] = bst_dataSetsManager('LoadResultsFile', ... + TessInfo(iTess).DataSource.FileName, ... + GlobalData.DataSet(iDS).DataFile, ... + '', ... + GlobalData.DataSet(iDS).StudyFile, ... + GlobalData.DataSet(iDS).SubjectFile); + if isempty(iResult) + return + end + end + % If 'ImageGridAmp' matrix is not loaded for this file + if isempty(GlobalData.DataSet(iDS).Results(iResult).ImageGridAmp) && ... + isempty(GlobalData.DataSet(iDS).Results(iResult).ImagingKernel) + % Load recording matrix + bst_dataSetsManager('LoadResultsMatrix', iDS, iResult); + end + + % === GET CURRENT VALUES === + % Get results values + TessInfo(iTess).Data = bst_dataSetsManager('GetResultsValues', iDS, iResult, [], 'CurrentTimeIndex'); + % If min/max values for this file were not computed yet + if isempty(TessInfo(iTess).DataMinMax) + TessInfo(iTess).DataMinMax = bst_dataSetsManager('GetResultsMaximum', iDS, iResult); + end + % Reset Overlay cube + TessInfo(iTess).OverlayCube = []; + + % Check the consistency between the number of results points (number of sources) + % and the number of vertices of the target surface patch + % IGNORE TEST FOR MRI + if (length(TessInfo(iTess).Data) ~= TessInfo(iTess).nVertices) && ~strcmpi(TessInfo(iTess).Name, 'Anatomy') + bst_error(sprintf(['Number of sources (%d) is smaller than number of vertices (%d).\n\n' ... + 'Please compute the sources again.'], size(TessInfo(iTess).Data, 1), TessInfo(iTess).nVertices), 'Data mismatch', 0); + isOk = 0; + return; + end + % Update "Static" status for this figure + setappdata(hFig, 'isStatic', GlobalData.DataSet(iDS).Results(iResult).isStatic); + + case 'Surface' + % Get loaded surface + SurfaceFile = TessInfo(iTess).DataSource.FileName; + sSurf = bst_dataSetsManager('LoadSurface', SurfaceFile); + % Build uniform data vector + TessInfo(iTess).Data = ones(length(sSurf.Vertices),1); + TessInfo(iTess).DataMinMax = [1 1]; + setappdata(hFig, 'isStatic', 1); + + otherwise + % Nothing to do + end + % Error if all data values are null + if (max(abs(TessInfo(iTess).DataMinMax)) == 0) +% bst_error('All values are null. Please check your input file.', 'Surface error'); + warning('All values are null. Please check your input file.'); + end + end + % Update surface definition + setappdata(hFig, 'Surface', TessInfo); + % Update colormap + UpdateSurfaceColormap(hFig, iSurfaces); +end + + + +%% ===== UPDATE SURFACE COLORMAP ===== +function UpdateSurfaceColormap(hFig, iSurfaces) +% disp('=== panel_surface > UpdateSurfaceColormap ==='); + global GlobalData; + % Get surfaces list + TessInfo = getappdata(hFig, 'Surface'); + if isempty(TessInfo) + return + end + % If the aim is to update all the surfaces + if (nargin < 2) || isempty(iSurfaces) + iSurfaces = 1:length(TessInfo); + end + + % Get default colormap to use for this figure + [listTypes, defaultColormapType] = gui_figuresManager('GetDisplayedDataTypes', hFig); + % Get figure axes + hAxes = [findobj(hFig, 'Tag', 'Axes3D'), findobj(hFig, 'Tag', 'axc'), findobj(hFig, 'Tag', 'axa'), findobj(hFig, 'Tag', 'axs')]; + hasData = 0; + hasSource = 0; + + % Get figure index (in DataSet structure) + [tmp__, iFig, iDS] = gui_figuresManager('GetFigure', hFig); + % Find the DataSet indice that corresponds to the current figure + if isempty(iDS) + error('No DataSet acessible for this 3D figure'); + end + + % For each surface + for iTess = iSurfaces + % ===== COLORMAPPING ===== + % Get recordings and source colormap + if isempty(TessInfo(iTess).DataSource.Type) || strcmpi(TessInfo(iTess).DataSource.Type, 'Surface') + sColormap = bst_colormaps('GetColormap', 'Anatomy'); + elseif TessInfo(iTess).DataSource.isStat || TessInfo(iTess).DataSource.isZscore + sColormap = bst_colormaps('GetColormap', 'Stat'); + else + sColormap = bst_colormaps('GetColormap', TessInfo(iTess).DataSource.Type); + end + % === Colormap : Normalized or Absolute ? + % Normalized : Color bounds (CLim) are set to a local extrema (at this time frame) + if sColormap.isNormalized + absMaxVal = max(abs(TessInfo(iTess).Data(:))); + % Fixed : Color bounds are fixed, defined statically by the user (sColormap.MaxValue) + elseif ~isempty(sColormap.MaxValue) + absMaxVal = sColormap.MaxValue; + % Absolute : Color bounds (CLim) are set to the global extrema (over all the time frames) + else + absMaxVal = max(abs(TessInfo(iTess).DataMinMax)); + end + + % === Data values : Absolute | Normal ? === + if sColormap.isAbsoluteValues + % Display absolute values of data + TessInfo(iTess).Data = abs(TessInfo(iTess).Data); + TessInfo(iTess).DataLimitValue = [0, absMaxVal]; + else + % Display normal data (positive and negative values) + TessInfo(iTess).DataLimitValue = [-absMaxVal, absMaxVal]; + end + % If current colormap is the default colormap for this figure (for colorbar) + if (strcmpi(defaultColormapType, 'Stat') && (TessInfo(iTess).DataSource.isStat || TessInfo(iTess).DataSource.isZscore)) || ... + strcmpi(defaultColormapType, TessInfo(iTess).DataSource.Type) + if all(~isnan(TessInfo(iTess).DataLimitValue)) && (TessInfo(iTess).DataLimitValue(1) < TessInfo(iTess).DataLimitValue(2)) + set(hAxes, 'CLim', TessInfo(iTess).DataLimitValue); + else + %warning('Brainstorm:AxesError', 'Error using set: Bad value for axes property: CLim: Values must be increasing and non-NaN.'); + set(hAxes, 'CLim', [0 1e-30]); + end + end + + % ===== DISPLAY ON MRI ===== + if strcmpi(TessInfo(iTess).Name, 'Anatomy') && ~isempty(TessInfo(iTess).DataSource.Type) && ... + (isempty(TessInfo(iTess).OverlayCube) )...|| ~strcmpi(TessInfo(iTess).DataSource.Type, 'Surface')) + % Progress bar + isProgressBar = bst_progressBar('isVisible'); + bst_progressBar('start', 'Display MRI', 'Updating values...'); + % ===== Update surface display ===== + % Update figure's appdata (surface list) + setappdata(hFig, 'Surface', TessInfo); + % Update OverlayCube + UpdateOverlayCube(hFig, iTess); + % Hide progress bar + if ~isProgressBar + bst_progressBar('stop'); + end + % Put focus back on previous figure + curFig = gui_figuresManager('GetCurrentFigure', '3D'); + if ~isempty(curFig) + figure(curFig); + end + else + % Update figure's appdata (surface list) + setappdata(hFig, 'Surface', TessInfo); + % Update surface color + figure_callback(hFig, 'UpdateSurfaceColor', hFig, iTess); + end + + % ===== Colorbar ticks and labels ===== + if strcmpi(TessInfo(iTess).DataSource.Type, 'Data') + hasData = 1; + elseif strcmpi(TessInfo(iTess).DataSource.Type, 'Source') + hasSource = 1; + end + end + + % ===== Colorbar ticks and labels ===== + % Set figure colormap + set(hFig, 'Colormap', sColormap.CMap); + % Create/Delete colorbar + bst_colormaps('SetColorbarVisible', hFig, sColormap.DisplayColorbar); + % Display only one colorbar (preferentially the results colorbar) + if GlobalData.DataSet(iDS).isStat % || GlobalData.DataSet(iDS).isZscore (NOT FOR THE COLORMAP, ONLY FOR THE UNITS) + bst_colormaps('ConfigureColorbar', hFig, 'stat'); + elseif hasSource + bst_colormaps('ConfigureColorbar', hFig, 'Results'); + elseif hasData + bst_colormaps('ConfigureColorbar', hFig, GlobalData.DataSet(iDS).Figure(iFig).Id.Modality); + end +end + + +%% ===== GET SURFACE: ANATOMY ===== +function [sMri,TessInfo,iTess,iMri] = GetSurfaceMri(hFig) + % Get list of surfaces for the figure + TessInfo = getappdata(hFig, 'Surface'); + % Find "Anatomy" + iTess = find(strcmpi({TessInfo.Name}, 'Anatomy')); + if isempty(iTess) + sMri = []; + return + elseif (length(iTess) > 1) + iTess = iTess(1); + end + % Get Mri filename + MriFile = TessInfo(iTess).SurfaceFile; + % Get loaded MRI + [sMri,iMri] = bst_dataSetsManager('GetMri', MriFile); +end + + +%% ===== GET SURFACE: CORTEX ===== +% Usage: [iCortex, TessInfo, hFig] = GetSurfaceCortex() +% [iCortex, TessInfo] = GetSurfaceCortex(hFig) +function [iCortex, TessInfo, hFig] = GetSurfaceCortex(hFig) + % If target figure is not defined: use the current 3D figure + if ((nargin < 1) || isempty(hFig)) + % Get current 3d figure + hFig = gui_figuresManager('GetCurrentFigure', '3D'); + % No current 3D figure: error + if isempty(hFig) + iCortex = []; + TessInfo = []; + return + end + end + % Get surfaces list + TessInfo = getappdata(hFig, 'Surface'); + % Find 'Cortex' surfaces + iCortex = find(strcmpi({TessInfo.Name}, 'Cortex')); + % No cortex + if isempty(iCortex) + iCortex = []; + return; + % More than one + elseif (length(iCortex) > 1) + % Try to get the one that is selected in "surfaces" panel + iSurf = getappdata(hFig, 'iSurface'); + % Selected surface does not help + if isempty(iSurf) || ~ismember(iSurf, iCortex) + % Use first cortex surface available + iCortex = iCortex(1); + return + % Use selected surface + else + iCortex = iSurf; + end + end +end + + +%% ===== GET CORTEX OR ANATOMY SURFACE ===== +% Usage: [iSurf, TessInfo, hFig] = GetSurfaceCortexOrAnatomy() +% [iSurf, TessInfo] = GetSurfaceCortexOrAnatomy(hFig) +function [iSurf, TessInfo, hFig] = GetSurfaceCortexOrAnatomy(hFig) %#ok + % If target figure is not defined: use the current 3D figure + if ((nargin < 1) || isempty(hFig)) + % Get current 3d figure + hFig = gui_figuresManager('GetCurrentFigure', '3D'); + % No current 3D figure: error + if isempty(hFig) + iSurf = []; + TessInfo = []; + return + end + end + % Get surfaces + iCortex = GetSurfaceCortex(hFig); + [sMri, TessInfo, iAnatomy] = GetSurfaceMri(hFig); + % Get target surface + if ~isempty(iCortex) + iSurf = iCortex; + elseif ~isempty(iAnatomy) + iSurf = iAnatomy; + else + iSurf = []; + end +end + + +%% ===== REMOVE A SURFACE ===== +function RemoveSurface(hFig, iSurface) + % Get figure appdata (surfaces configuration) + TessInfo = getappdata(hFig, 'Surface'); + if (iSurface < 0) || (iSurface > length(TessInfo)) + return; + end + % Remove associated patch + iRemPatch = ishandle(TessInfo(iSurface).hPatch); + delete(TessInfo(iSurface).hPatch(iRemPatch)); + + % Remove surface from the figure's surfaces list + TessInfo(iSurface) = []; + % Update figure's surfaces list + setappdata(hFig, 'Surface', TessInfo); + % Set another figure as current figure + if isempty(TessInfo) + setappdata(hFig, 'iSurface', []); + elseif (iSurface <= length(TessInfo)) + setappdata(hFig, 'iSurface', iSurface); + else + setappdata(hFig, 'iSurface', iSurface - 1); + end + +% % Get figure description +% [hFig, iFig, iDS] = gui_figuresManager('GetFigure', hFig); +% % Update colormaps +% figure_callback(hFig, 'ColormapChangedCallback', iDS, iFig); +end + + + +%% ===== PLOT MRI ===== +% Usage: hs = panel_surface('PlotMri', hFig, posXYZ) : Set the position of cuts and plot MRI +% hs = panel_surface('PlotMri', hFig) : Plot MRI for current positions +function hs = PlotMri(hFig, posXYZ) + global GlobalData; + % Get MRI + [sMri,TessInfo,iTess,iMri] = GetSurfaceMri(hFig); + % Set positions or use default + if (nargin < 2) || isempty(posXYZ) + posXYZ = TessInfo(iTess).CutsPosition; + iDimPlot = ~isnan(posXYZ); + else + iDimPlot = ~isnan(posXYZ); + TessInfo(iTess).CutsPosition(iDimPlot) = posXYZ(iDimPlot); + end + % Get initial threshold value + threshold = TessInfo(iTess).SurfSmoothValue * 2 * double(sMri.Histogram.bgLevel); + % Get colormaps + switch (TessInfo(iTess).DataSource.Type) + case 'Source' + if TessInfo(iTess).DataSource.isStat || TessInfo(iTess).DataSource.isZscore + sColormapData = bst_colormaps('GetColormap', 'Stat'); + else + sColormapData = bst_colormaps('GetColormap', 'Source'); + end + case 'Surface' + sColormapData = bst_colormaps('GetColormap', 'Overlay'); + otherwise + sColormapData = bst_colormaps('GetColormap', 'Source'); + end + sColormapMri = bst_colormaps('GetColormap', 'Anatomy'); + % Define OPTIONS structure + OPTIONS.sMri = sMri; + OPTIONS.iMri = iMri; + OPTIONS.cutsCoords = posXYZ; % [x,y,z] location of the cuts in the volume + OPTIONS.MriThreshold = threshold; % MRI threshold (if value + for i = 1:length(hFig) + [sMri, TessInfo, iTess] = GetSurfaceMri(hFig(i)); + if ~isempty(iTess) && ~isempty(TessInfo(iTess).Data) + UpdateOverlayCube(hFig(i), iTess); + end + end +end + + +%% ===== UPDATE OVERLAY MASK ===== +% Usage: UpdateOverlayCube(hFig, iTess) +function UpdateOverlayCube(hFig, iTess) +% disp('=== panel_surface > UpdateOverlayCube ==='); + % Get MRI + TessInfo = getappdata(hFig, 'Surface'); + sMri = bst_dataSetsManager('GetMri', TessInfo(iTess).SurfaceFile); + if isempty(sMri) || isempty(sMri.Cube) || isempty(TessInfo(iTess).Data) + return + end + % Process depend on overlay data file + switch (TessInfo(iTess).DataSource.Type) + case 'Data' + % Get scalp surface + error('Not supported yet'); + case 'Source' + % Get cortex surface + sSubject = bst_getContext('MriFile', sMri.FileName); + SurfaceFile = sSubject.Surface(sSubject.iCortex).FileName; + case 'Surface' + % Get surface specified in DataSource.FileName + SurfaceFile = TessInfo(iTess).DataSource.FileName; + end + % Get transformation MRI<->Surface + [sSurf, iSurf] = bst_dataSetsManager('LoadSurface', SurfaceFile); + tess2mri_interp = bst_dataSetsManager('GetTess2MriInterp', iSurf); + % If no interpolation tess<->mri accessible : exit + if isempty(tess2mri_interp) + return + end + % === GET SCOUTS === + % Get "Scouts" panel controls + ctrl = bst_getContext('PanelControls', 'Scout'); + % View mode : VIEW SELECTED SCOUTS + if ctrl.jRadioScoutViewSelected.isSelected() + % Get selected scouts + [sSelScouts, iSelScouts] = panel_scouts('GetSelectedScouts'); + % View mode : VIEW ALL SCOUTS + elseif ctrl.jRadioScoutViewAll.isSelected() + % Get all available scouts + [sSelScouts, iSelScouts] = panel_scouts('GetScouts'); + end + % Display only scouts that are related to this figure + FigureId = getappdata(hFig, 'FigureId'); + switch (FigureId.Type) + case 'MriViewer' + % Take all the scouts + case '3DViz' + [sScoutsFig, iScoutsFig] = panel_scouts('GetScoutsWithFigure', hFig); + iSelScouts = intersect(iSelScouts, iScoutsFig); + sSelScouts = panel_scouts('GetScouts', iSelScouts); + end + % Progress bar + isProgressBar = bst_progressBar('isVisible'); + bst_progressBar('start', 'Display MRI', 'Updating values...'); + + % === UPDATE MASK === + mriSize = size(sMri.Cube); + % If no selected scouts + if isempty(sSelScouts) || ~ctrl.jCheckLimitMriSources.isSelected() + % Build interpolated cube + TessInfo(iTess).OverlayCube = tess_projectDataInMri(... + tess2mri_interp, ... + mriSize, ... + TessInfo(iTess).Data); + else + % Get all the vertices concerned by the interpolation + iVertices = [sSelScouts.Vertices]; + % Build interpolated cube + TessInfo(iTess).OverlayCube = tess_projectDataInMri(... + tess2mri_interp(:,iVertices), ... + mriSize, ... + TessInfo(iTess).Data(iVertices)); + end + % Reset MIP functional fields + TessInfo(iTess).MipFunctional = cell(3,1); + % === UPDATE DISPLAY === + % Get surface description + setappdata(hFig, 'Surface', TessInfo); + % Redraw surface vertices color + figure_callback(hFig, 'UpdateSurfaceColor', hFig, iTess); + % Hide progress bar + if ~isProgressBar + bst_progressBar('stop'); + end +end + + +%% ===== SET SURFACE TRANSPARENCY ===== +function SetSurfaceTransparency(hFig, iSurf, alpha) + % Update surface transparency + TessInfo = getappdata(hFig, 'Surface'); + TessInfo(iSurf).SurfAlpha = alpha; + setappdata(hFig, 'Surface', TessInfo); + % Update panel controls + UpdateSurfaceProperties(); + % Update surface display + figure_callback(hFig, 'UpdateSurfaceColor', hFig, iSurf); + figure_callback(hFig, 'UpdateSurfaceAlpha', hFig, iSurf); +end + + + +%% ===== SET THRESHOLD ===== +function SetDataThreshold(hFig, iSurf, value) + % Update surface transparency + TessInfo = getappdata(hFig, 'Surface'); + TessInfo(iSurf).DataIntThreshold = value; + setappdata(hFig, 'Surface', TessInfo); + % Update panel controls + UpdateSurfaceProperties(); + % Update color display on the surface + figure_callback(hFig, 'UpdateSurfaceColor', hFig, iSurf); +end +%% ===== SET AUTO TRANSPARENCY ===== +function SetAutoTransparency(hFig) + % Get surfaces definitions + TessInfo = getappdata(hFig, 'Surface'); + % Look for different surfaces types + iCortex = find(ismember({TessInfo.Name}, {'Cortex', 'Anatomy'})); + iOther = find(ismember({TessInfo.Name}, {'Scalp', 'InnerSkull', 'OuterSkull'})); + % Set other surfaces transparency if cortex at the same time + if ~isempty(iCortex) && ~isempty(iOther) + for i = 1:length(iOther) + SetSurfaceTransparency(hFig, iOther(i), 0.7); + end + end +end + + +%% ===== SET SURFACE COLOR ===== +function SetSurfaceColor(hFig, iSurf, newColor2) + % Compute the color used to display curvature (newColor1) + newColor1 = .6 .* newColor2; + + % Get description of surfaces + TessInfo = getappdata(hFig, 'Surface'); + % Update surface description (figure's appdata) + TessInfo(iSurf).AnatomyColor(1,:) = newColor1; + TessInfo(iSurf).AnatomyColor(2,:) = newColor2; + % Update Surface appdata structure + setappdata(hFig, 'Surface', TessInfo); + + % Get panel controls + ctrl = bst_getContext('PanelControls', 'Surface'); + % Change button color + ctrl.jButtonSurfColor.setBackground(java.awt.Color(newColor2(1), newColor2(2), newColor2(3))); + % Update panel controls + UpdateSurfaceProperties(); + + % Update color display on the surface + figure_callback(hFig, 'UpdateSurfaceColor', hFig, iSurf); +end + +%% ===== APPLY DEFAULT DISPLAY TO SURFACE ===== +function ApplyDefaultDisplay() %#ok + % Get panel controls + ctrl = bst_getContext('PanelControls', 'Surface'); + % Get defaults for surface display + DefaultSurfaceDisplay = bst_getContext('DefaultSurfaceDisplay'); + % Surface smooth + if (ctrl.jSliderSurfSmoothValue.getValue() ~= DefaultSurfaceDisplay.SurfSmoothValue * 100) + ctrl.jSliderSurfSmoothValue.setValue(DefaultSurfaceDisplay.SurfSmoothValue * 100); + event = java.awt.event.MouseEvent(ctrl.jSliderSurfSmoothValue, 0, 0, 0, 0, 0, 1, 0, 0); + SliderCallback([], event, 'SurfSmoothValue'); + end + % Surface edges + if DefaultSurfaceDisplay.SurfShowCurvature && ~ctrl.jButtonSurfCurvature.isSelected() + ctrl.jButtonSurfCurvature.doClick(); + end +end + diff --git a/brainstorm3/toolbox/io/import_data.m b/brainstorm3/toolbox/io/import_data.m new file mode 100644 index 0000000..819ff61 --- /dev/null +++ b/brainstorm3/toolbox/io/import_data.m @@ -0,0 +1,545 @@ +function import_data(varargin) +% IMPORT_DATA: Imports a list of datafiles in a Study of Brainstorm database +% +% USAGE: import_data(iStudyInit, DataFiles, FileFormat) +% import_data(iStudyInit, DataFiles, FileFormat, DataCfg) +% import_data(iStudyInit) +% import_data( [], iSubjectInit) : imports data in the target subject (after having created a default condition) +% import_data() : Rely on data filenames to create automatically subjects/conditions +% +% INPUT: +% - iStudyInit: Indices of the studies where to import the Data +% If 0, a study is created automatically before importation (iSubject must be specified) +% - DataFiles : Cell array of full filenames of the data files to import (format is autodetected) +% => if not specified : file to import is asked to the user +% - iSubjectInit: The subject indice must be specified if and only if iStudyInit is not defined (0) +% => in this case, a default study is created for the target subject +% +% NOTE : Some data filenames can be interpreted as subjects/conditions/run : +% - cell__obs.erp : subject #j, condition #i, conditionName + +% @============================================================================= +% This software is part of The Brainstorm Toolbox +% http://neuroimage.usc.edu/brainstorm +% +% Copyright (c)2009 Brainstorm by the University of Southern California +% This software is distributed under the terms of the GNU General Public License +% as published by the Free Software Foundation. Further details on the GPL +% license can be found at http://www.gnu.org/copyleft/gpl.html. +% +% FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE +% UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY +% WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF +% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY +% LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE. +% +% For more information type "brainstorm licence" at command prompt. +% =============================================================================@ +% +% Authors: Francois Tadel, 2008 +% ----------------------------- Script History --------------------------------- +% FT 24-Jun-2008 Creation +% ------------------------------------------------------------------------------ + +% ===== Parse inputs ===== +DataFiles = {}; +FileFormat = ''; +iSubjectInit = 0; +% Check command line +if (nargin==0) + iStudyInit = 0; +elseif (nargin==1) && isnumeric(varargin{1}) && (varargin{1} > 0) + iStudyInit = varargin{1}; +elseif (nargin>=2) && isnumeric(varargin{1}) + iStudyInit = varargin{1}; + if ischar(varargin{2}) + DataFiles = {varargin{2}}; + elseif iscell(varargin{2}) + DataFiles = varargin{2}; + elseif isnumeric(varargin{2}) + iSubjectInit = varargin{2}; + end + if nargin>=3 + FileFormat = varargin{3}; + end + if nargin>=4 + sSubject = varargin{4}; + end +else + error('Usage: import_data(iStudyInit, [,DataFiles || iSubjectInit])'); +end +% Get Protocol information +ProtocolInfo = bst_getContext('ProtocolInfo'); +% Get current byte order +ByteOrder = bst_getContext('ByteOrder'); + + +%% ===== SELECT DATA FILE ===== +% If MRI file to load was not defined : open a dialog box to select it +if isempty(DataFiles) + % Get default import directory and formats + LastUsedDirs = bst_getContext('LastUsedDirs'); + DefaultFormats = bst_getContext('DefaultFormats'); + % Get MRI file + [DataFiles, FileFormat, FileFilter] = java_fileSelector( 'open', ... + 'Import EEG/MEG recordings...', ... % Window title + LastUsedDirs.ImportData, ... % Last used directory + 'multiple', 'files_and_dirs', ... % Selection mode + {{'.meg4','.res4'}, 'MEG/EEG: CTF (*.ds;*.meg4;*.res4)', 'CTF' ; ... + {'.fif'}, 'MEG/EEG: Neuromag FIFF (*.fif)', 'FIF'; ... + {'_data'}, 'MEG/EEG: Brainstorm MAT (*data*.mat)', 'BST-MAT'; ... + {'.lena'}, 'MEG/EEG: LENA (*.lena)', 'LENA-BIN'; ... + {'.raw'}, 'EEG: EGI Netstation epoch-marked RAW (*.raw)', 'EEG-EGI-RAW'; ... + {'.set'}, 'EEG: EEGLab (*.set)', 'EEG-EEGLAB'; ... + {'.sef','.ep','.eph'}, 'EEG: Cartool (*.sef;*.ep;*.eph)', 'EEG-CARTOOL'; ... + {'.erp','.hdr'}, 'EEG: ERPCenter (*.hdr;*.erp)', 'EEG-ERPCENTER'; ... + {'.eeg'}, 'EEG: BrainAmp (*.eeg)', 'EEG-BRAINAMP'; ... + {'.cnt','.avg','.eeg','.dat'}, 'EEG: Neuroscan (*.cnt;*.eeg;*.avg;*.dat)', 'EEG-NEUROSCAN'; ... + {'.mat'} 'EEG: Matlab matrix (*.mat)', 'EEG-MAT'; + {'*'}, 'EEG: ASCII text (*.*)', 'EEG-ASCII' ... + }, DefaultFormats.DataIn); + % If no file was selected: exit + if isempty(DataFiles) + return + end + % Save default import directory + LastUsedDirs.ImportData = fileparts(DataFiles{1}); + bst_setContext('LastUsedDirs', LastUsedDirs); + % Save default import format + DefaultFormats.DataIn = FileFormat; + bst_setContext('DefaultFormats', DefaultFormats); + % Process the selected directories : + % 1) If they are .ds/ directory with .meg4 and .res4 files : keep them as "files to open" + % 2) Else : add all the data files they contains (subdirectories included) + DataFiles = ExpandDataFilesList(FileFilter, DataFiles); + if isempty(DataFiles) + bst_error(['No data ' FileFormat ' file in the selected directories.'], 'Import EEG/MEG recordings...', 0); + return + end +end + +%% ===== CHECK DATA FORMAT ===== +if isempty(FileFormat) + error('Unknown FileFormat'); +end + + +%% ===== IMPORT SELECTED DATA ===== +% Reset data selection in study +nbCall = 0; +iNewAutoSubject = []; +isReinitStudy = 0; +iAllStudies = []; +iAllSubjects = []; +% Process all the selected data files +for iFile = 1:length(DataFiles) + nbCall = nbCall + 1; + DataFile = DataFiles{iFile}; + [DataFile_path, DataFile_base] = fileparts(DataFile); + % Check file location (a file cannot be directly inside the brainstorm directories) + itmp = strfind(DataFile_path, ProtocolInfo.STUDIES); + if ~isempty(itmp) && (itmp(1) == 1) + error(['You are not supposed to put your original files in the Brainstorm data directory.' 10 ... + 'This directory is part of the Brainstorm database and its content can be altered only' 10 ... + 'by the Brainstorm GUI.' 10 10 ... + 'Please create a new folder somewhere else, move all you original recordings files in it, ' 10 ... + 'and then try again to import them.']); + end + + % List or directories where to copy the channel file + iStudies = []; + iStudyCopyChannel = []; + % If needed: reinitialize target study + if isReinitStudy + iStudyInit = 0; + end + + % ===== CONVERT DATA FILE ===== + bst_progressBar('start', 'Import MEG/EEG recordings', ['Loading file "' DataFile '"...']); + bst_message_window(sprintf('Loading MEG/EEG "%s"...', DataFile)); + % Load file(s) + [ImportedDataMat, ChannelMat, loadedFiles] = in_data(DataFile, FileFormat, ByteOrder, nbCall); + if isempty(ImportedDataMat) + continue + end + % Detect auxiliary EEG channels + if ~isempty(ChannelMat) + ChannelMat = channel_detectType(ChannelMat); + end + + % ===== CREATE STUDY (IIF SUBJECT IS DEFINED) ===== + bst_progressBar('start', 'Import MEG/EEG recordings', 'Preparing output studies...'); + % Check if subject/condition is in filenames + [SubjectName, ConditionName] = ParseDataFilename(ImportedDataMat(1).FileName,loadedFiles); + % If subj/cond are defined in filenames => default (ignore node that was clicked) + if ~isempty(SubjectName) + iSubjectInit = NaN; + end + if ~isempty(ConditionName) + iStudyInit = NaN; + end + + % If study is already known + if (iStudyInit ~= 0) + iStudies = iStudyInit; + % If a study needs to be created AND subject is already defined + elseif (iStudyInit == 0) && (iSubjectInit ~= 0) + % Get the target subject + sSubject = bst_getContext('Subject', iSubjectInit, 1); + % Try to get default study + [sStudies, iStudies] = bst_getContext('StudyWithCondition', fullfile(fileparts(sSubject.FileName), DataFile_base)); + % If does not exist yet: Create the default study + if isempty(iStudies) + iStudies = db_addCondition(fileparts(sSubject.FileName), DataFile_base); + if isempty(iStudies) + error('Default study could not be created : "%s".', DataFile_base); + end + isReinitStudy = 1; + end + iStudyInit = iStudies; + % If need to create Subject + Condition + Study : do it file per file + else + iSubjectInit = NaN; + iStudyInit = NaN; + iStudies = []; + end + + % ===== STORE IMPORTED FILES IN DB ===== + bst_progressBar('start', 'Import MEG/EEG recordings', 'Saving imported files in database...', 0, length(ImportedDataMat)); + % Store imported data files in Brainstorm database + for iImported = 1:length(ImportedDataMat) + bst_progressBar('inc', 1); + % ===== CREATE STUDY (IF SUBJECT NOT DEFINED) ===== + % Need to get a study for each imported file + if isnan(iSubjectInit) || isnan(iStudyInit) + % === PARSE FILENAME === + % Try to get subject name and condition name out of the filename + [SubjectName, ConditionName] = ParseDataFilename(ImportedDataMat(iImported).FileName,loadedFiles); + sSubject = []; + % === SUBJECT NAME === + if isempty(SubjectName) + % If subject is defined by the input node: use this subject's name + if (iSubjectInit ~= 0) && ~isnan(iSubjectInit) + [sSubject, iSubject] = bst_getContext('Subject', iSubjectInit); + end + else + % Find the subject in DataBase + [sSubject, iSubject] = bst_getContext('SubjectWithName', SubjectName); + % If subject is not found in DB: create it + if isempty(sSubject) + [sSubject, iSubject] = db_addSubject(SubjectName); + % If subject cannot be created: error: stop everything + if isempty(sSubject) + bst_error(['Could not create subject "' SubjectName '"'], 'Import MEG/EEG recordings'); + return; + end + end + end + % If a subject creation is needed + if isempty(sSubject) + SubjectName = 'NewSubject'; + % If auto subject was not created yet + if isempty(iNewAutoSubject) + % Try to get a subject with this name in database + [sSubject, iSubject] = bst_getContext('Subject', SubjectName); + % If no subject with automatic name exist in database, create it + if isempty(sSubject) + [sSubject, iSubject] = db_addSubject(SubjectName); + iNewAutoSubject = iSubject; + end + % If auto subject was created for the previous imported file + else + [sSubject, iSubject] = bst_getContext('Subject', iNewAutoSubject); + end + end + % === CONDITION NAME === + if isempty(ConditionName) + % If a condition is defined by the input node + if (iStudyInit ~= 0) && ~isnan(iStudyInit) + sStudyInit = bst_getContext('Study', iStudyInit); + ConditionName = sStudyInit.Condition{1}; + else + ConditionName = 'Default'; + end + end + % Get real subject directory (not the default subject directory, which is the default) + sSubjectRaw = bst_getContext('Subject', iSubject, 1); + ConditionPath = fileparts(sSubjectRaw.FileName); + % Find study (subject/condition) in database + [sNewStudy, iNewStudy] = bst_getContext('StudyWithCondition', fullfile(ConditionPath, ConditionName)); + % If study does not exist : create it + if isempty(iNewStudy) + iNewStudy = db_addCondition(ConditionPath, ConditionName, 'NoRefresh'); + if isempty(iNewStudy) + warning(['Cannot create condition : "' fullfile(ConditionPath, ConditionName) '"']); + continue; + end + end + iStudies = [iStudies, iNewStudy]; + else + iSubject = iSubjectInit; + sSubject = bst_getContext('Subject', iSubject); + iStudies = iStudyInit; + end + % ===== CHANNEL FILE TARGET ===== + % If subject uses default channel + if (sSubject.UseDefaultChannel) + % Add the DEFAULT study directory to the list + [sDefaultStudy, iDefaultStudy] = bst_getContext('DefaultStudy', iSubject); + iStudyCopyChannel(iImported) = iDefaultStudy; + else + % Else add study directory in the list + iStudyCopyChannel(iImported) = iStudies(end); + end + + % ===== MOVE IMPORTED FILES IN STUDY DIRECTORY ===== + % Current study + sStudy = bst_getContext('Study', iStudies(end)); + % Get study subdirectory + studySubDir = fileparts(sStudy.FileName); + % Get the directory in which the file was stored by the in_data() function + [importedPath, importedBase, importedExt] = fileparts(ImportedDataMat(iImported).FileName); + % Remove ___COND and ___SUBJ tags + importedBase = removeStudyTags(importedBase); + % Build final filename + finalImportedFile = fullfile(ProtocolInfo.STUDIES, studySubDir, [importedBase, importedExt]); + finalImportedFile = io_makeUniqueFilename(finalImportedFile); + % If data file is not in study subdirectory : need to move it + if ~io_compareFileNames(importedPath, fileparts(finalImportedFile)) + bst_message_window(sprintf('Moving file : "%s" => "%s"...', ImportedDataMat(iImported).FileName, finalImportedFile)); + movefile(ImportedDataMat(iImported).FileName, finalImportedFile, 'f'); + ImportedDataMat(iImported).FileName = strrep(finalImportedFile, ProtocolInfo.STUDIES, ''); + end + + % ===== STORE DATA FILE IN DATABASE ====== + % New data indice in study + nbData = length(sStudy.Data) + 1; + % Add data to subject + sStudy.Data(nbData) = ImportedDataMat(iImported); + % Store current study + bst_setContext('Study', iStudies(end), sStudy); + % Record all subjects + iAllSubjects = [iAllSubjects, iSubject]; + end + clear iStudy sStudy studySubDir + + % Remove NaN and duplicated values in studies list + iStudies = unique(iStudies(~isnan(iStudies))); + iStudyCopyChannel = unique(iStudyCopyChannel(~isnan(iStudyCopyChannel))); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%% SECTION A REFAIRE COMPLETEMENT %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%% FONCTION "SET CHANNEL" SEPAREE %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % If a channel file was imported + if ~isempty(ChannelMat) + for i=1:length(iStudyCopyChannel) + iStudy = iStudyCopyChannel(i); + sStudy = bst_getContext('Study', iStudy); + studySubDir = fileparts(sStudy.FileName); + + % ===== SAVE CHANNEL FILE ===== + % Delete all the other channel files in the study directory + channelFiles = dir(fullfile(ProtocolInfo.STUDIES, studySubDir, '*channel*.mat')); + if ~isempty(channelFiles) + io_deleteFile({fullfile(ProtocolInfo.STUDIES, studySubDir, channelFiles.name)}, 1); + end + % Add comment field if it does not exist + if ~isfield(ChannelMat, 'Comment') + %ChannelMat.Comment = DataFile_base; + ChannelMat.Comment = [FileFormat ' channels']; + end + % Base channel filename + ChannelFile_base = DataFile_base; + + % VECTORVIEW 306: 204 Gradiometers + 102 Magnetometers + if (nnz(strcmpi({ChannelMat.Channel.Type}, 'MEG GRAD')) == 204) && ... + (nnz(strcmpi({ChannelMat.Channel.Type}, 'MEG MAG')) == 102) + % Add a tag in the filename + ChannelFile_base = [ChannelFile_base, '_vectorview306']; + % CTF: 29 references + elseif (nnz(strcmpi({ChannelMat.Channel.Type}, 'MEG REF')) > 26) + % Check if accuracy is 1 (MEG channel = axial gradio = 8 points) + iMeg = find(strcmpi({ChannelMat.Channel.Type}, 'MEG')); + if (size(ChannelMat.Channel(iMeg(1)).Loc, 2) == 8) + ChannelFile_base = [ChannelFile_base, '_ctf_acc1']; + end + end + + % Produce a default channel filename + BstChannelFile = fullfile(ProtocolInfo.STUDIES, studySubDir, ['channel_' ChannelFile_base '.mat']); + % Save new ChannelFile in Brainstorm format + save(BstChannelFile, '-struct', 'ChannelMat'); + bst_message_window(sprintf(' => Channels list saved in "%s".', strrep(BstChannelFile, ProtocolInfo.STUDIES, ''))) + + % ===== STORE NEW CHANNEL IN DATABASE ====== + % New channel structure + newChannel = db_getDataTemplate('Channel'); + newChannel(1).FileName = strrep(BstChannelFile, ProtocolInfo.STUDIES, ''); + newChannel(1).nbChannels = length(ChannelMat.Channel); + newChannel(1).Comment = sprintf('%s (%d)', ChannelMat.Comment, length(ChannelMat.Channel)); + newChannel(1).Modalities = getChannelModalities( ChannelMat.Channel ); + % Save displayable types list + newChannel(1).DisplayableSensorTypes = getChannelModalities( ChannelMat.Channel, 5 ); + + % Add channel to study + sStudy.Channel = newChannel; + % Store studies database + bst_setContext('Study', iStudy, sStudy); + end + end + iAllStudies = [iAllStudies, iStudies, iStudyCopyChannel]; +end +bst_progressBar('stop'); + +% === UPDATE DISPLAY === +% Update links +if ~isempty(iAllSubjects) + iAllSubjects = unique(iAllSubjects); + for i = 1:length(iAllSubjects) + db_updateLinkResults('Subject', iAllSubjects(i)); + end +end +% Update tree +if ~isempty(iAllStudies) + iAllStudies = unique(iAllStudies); + tree_updateNode('Study', iAllStudies); + if (length(iAllStudies) == 1) + tree_selectStudyNode(iAllStudies(1)); + end +end +% Edit new subject (if a new subject was created automatically) +if ~isempty(iNewAutoSubject) + db_editSubject(iNewAutoSubject); +end +% Save database +bst_saveDatabase(); + +return +end + + + + +%% ================================================================================ +% ===== HELPER FUNCTIONS ========================================================= +% ================================================================================ +% Expands a list of paths : +% 1) If it is a file => keep it unchanged +% 2) If they are .ds/ directory with .meg4/.res4 files, +% or ERPCenter directories with .hdr/.erp files, +% => keep them as "files to open" +% 3) Else : add all the data files they contains (subdirectories included) +function ExpFiles = ExpandDataFilesList(FileFilter, Files) + ExpFiles = {}; + for i = 1:length(Files) + % DIRECTORIES + if isdir(Files{i}) + isFormatCTF = strcmpi(FileFilter.getFormatName(), 'CTF'); + isFormatERP = strcmpi(FileFilter.getFormatName(), 'EEG-ERPCenter'); + isDirFile = 0; + % If CTF/ERP format + if isFormatCTF || isFormatERP + if isFormatCTF + % Check if there are .meg4 or .res4 files in directory + dirFiles = [dir(fullfile(Files{i}, '*.meg4')); + dir(fullfile(Files{i}, '*.res4'))]; + else + % Check if there are .hdr file in directory + dirFiles = dir(fullfile(Files{i}, '*.hdr')); + end + j = 1; + while (~isDirFile && (j <= length(dirFiles))) + if FileFilter.accept(java.io.File(fullfile(Files{i}, dirFiles(j).name))) + isDirFile = 1; + ExpFiles = cat(2, ExpFiles, Files{i}); + else + j = j + 1; + end + end + end + + % If directory is not a CTF/ERP dir to be opened + if ~isDirFile + % Get all files in this directory + dirFiles = dir(fullfile(Files{i}, '*')); + % Build a new files list + dirFullFiles = {}; + for j = 1:length(dirFiles) + % Process all the files that do not start with a '.' + if (dirFiles(j).name(1) ~= '.') + dirFullFiles{end + 1} = fullfile(Files{i}, dirFiles(j).name); + end + end + % And call function recursively + ExpFiles = cat(2, ExpFiles, ExpandDataFilesList(FileFilter, dirFullFiles)); + end + + % SINGLE FILE + else + % Test if is accepted by FileFilter + if FileFilter.accept(java.io.File(Files{i})) + % If single file is CTF : get the parent + if strcmpi(FileFilter.getFormatName(), 'CTF') + ExpFiles{end+1} = fileparts(Files{i}); + else + ExpFiles{end+1} = Files{i}; + end + end + end + end +end + + +% Parse filename to detect subject/condition/run +function [SubjectName, ConditionName] = ParseDataFilename(filename,loadedFiles) + SubjectName = ''; + ConditionName = ''; + % Get only short filename without extension + [fPath, fName, fExt] = fileparts(filename); + + % IMPORTED FILENAMES : '....___SUBJsubjectname___CONDcondname___...' + % Get subject tag + iTag_subj = strfind(fName, '___SUBJ'); + if ~isempty(iTag_subj) + iStartSubj = iTag_subj + 7; + % Find closing tag + iCloseSubj = strfind(fName(iStartSubj:end), '___'); + % Find closing tag + if ~isempty(iCloseSubj) + SubjectName = fName(iStartSubj:iStartSubj + iCloseSubj - 2); + end + elseif nargin>1 + % Uses path of the loaded files to define subject name + [p,n]=fileparts(loadedFiles{1}); + [p,SubjectName]=fileparts(p); + end + + % Get condition tag + iTag_cond = strfind(fName, '___COND'); + if ~isempty(iTag_cond) + iStartCond = iTag_cond + 7; + % Find closing tag + iCloseCond = strfind(fName(iStartCond:end), '___'); + % Find closing tag + if ~isempty(iCloseCond) + ConditionName = fName(iStartCond:iStartCond + iCloseCond - 2); + end + end +end + +% Remove study tags (___COND and ___SUBJ) +function fname = removeStudyTags(fname) + iTags = findstr(fname, '___'); + if iTags >= 2 + iStart = iTags(1); + if (iTags(end) + 2 == length(fname)) + iStop = iTags(end) + 2; + else + % Leave at least one '_' as a separator + iStop = iTags(end) + 1; + end + fname(iStart:iStop) = []; + end +end + diff --git a/brainstorm3/toolbox/misc/mri_drawCuts.m b/brainstorm3/toolbox/misc/mri_drawCuts.m new file mode 100644 index 0000000..1b6a7fc --- /dev/null +++ b/brainstorm3/toolbox/misc/mri_drawCuts.m @@ -0,0 +1,230 @@ +function [hCuts, OutputOptions] = mri_drawCuts(hFig, OPTIONS) +% MRI_DRAWMRICUTS: Plot a MRI volume in a 3D visualization figure (three orthogonal cuts). +% +% USAGE: hCuts = mri_drawCuts(hFig, OPTIONS) +% INPUT: (structure OPTIONS) +% - sMri : Brainstorm MRI structure +% - iMri : Indice of MRI structure in GlobalData.Mri array +% - cutsCoords : [x,y,z] location of the cuts in the volume +% (value that is set to NaN => cut is not displayed) +% - MriThreshold : Intensity threshold above which a voxel is displayed in the MRI slice. +% - MriAlpha : Transparency of MRI slices +% - MriColormap : Colormap to use to display the slices +% (optional) +% - OverlayCube : 3d-volume (same size than MRI) with specific data values +% - OverlayIntThreshold : Intensity threshold above which a voxel is overlayed in the MRI slices. +% - OverlayExtThreshold : Extent threshold, minimal size of clusters overlayed in the MRI slices. +% - OverlayAlpha : Overlayed voxels transparency +% - OverlayColormap : Colormap to use to display the overlayed data +% - OverlayBounds : [minValue, maxValue]: amplitude of the OverlayColormap +% - isMipAnatomy : 1=compute maximum intensity projection in the MRI volume +% - isMipAnatomy : 1=compute maximum intensity projection in the OVerlay volume +% +% OUTPUT: +% - hCuts : [3x1 double] Graphic handles to the images that were created +% - OutputOptions : structure with some output information +% |- MipAnatomy : {3x1 cell} +% |- MipFunctional : {3x1 cell} + +% @============================================================================= +% This software is part of The Brainstorm Toolbox +% http://neuroimage.usc.edu/brainstorm +% +% Copyright (c)2009 Brainstorm by the University of Southern California +% This software is distributed under the terms of the GNU General Public License +% as published by the Free Software Foundation. Further details on the GPL +% license can be found at http://www.gnu.org/copyleft/gpl.html. +% +% FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE +% UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY +% WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF +% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY +% LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE. +% +% For more information type "brainstorm licence" at command prompt. +% =============================================================================@ +% +% Authors: Francois Tadel, 2006 (University of Geneva) +% ----------------------------- Script History --------------------------------- +% FT 24-Jun-2008 Adaptation for brainstorm3 +% ------------------------------------------------------------------------------ + + +%% ===== INITIALIZATION ===== +mriSize = size(OPTIONS.sMri.Cube); +isOverlay = ~isempty(OPTIONS.OverlayCube); +% Output variable +hCuts = [-1 -1 -1]; +OutputOptions.MipAnatomy = cell(3,1); +OutputOptions.MipFunctional = cell(3,1); +% Colormap bounds +if isempty(OPTIONS.sMri.Histogram.intensityMax) + MriColormapBounds = []; +else + MriColormapBounds = [0 double(OPTIONS.sMri.Histogram.intensityMax)]; +end +% Get the type of figure +FigureId = getappdata(hFig, 'FigureId'); +% Get in which axes we are supposed to display the MRI +switch (FigureId.Type) + case '3DViz' + hTarget = findobj(hFig, 'tag', 'Axes3D'); + case 'MriViewer' + % Get figure handles + Handles = gui_figuresManager('GetFigureHandles', hFig); + hTarget = [Handles.imgs_mri, Handles.imgc_mri, Handles.imga_mri]; +end + + + +%% ===== DISPLAY SLICES ===== +for iCoord = 1:3 + % Ignore the slice if indice is NaN + if isnan(OPTIONS.cutsCoords(iCoord)) + continue + end + + % === GET MRI SLICE === + % If maximum intensity power required + if OPTIONS.isMipAnatomy + % If the maximum is not yet computed: compute it + if isempty(OPTIONS.MipAnatomy{iCoord}) + sliceMri = double(mri_getSlice(OPTIONS.sMri.Cube, OPTIONS.cutsCoords(iCoord), iCoord, OPTIONS.isMipAnatomy)'); + OutputOptions.MipAnatomy{iCoord} = sliceMri; + % Else: use the previously computed maximum + else + sliceMri = OPTIONS.MipAnatomy{iCoord}; + end + % Else: just extract a slice from the volume + else + sliceMri = double(mri_getSlice(OPTIONS.sMri.Cube, OPTIONS.cutsCoords(iCoord), iCoord, OPTIONS.isMipAnatomy)'); + end + + % === GET OVERLAY SLICE === + % Get Overlay slice + if isOverlay + % If maximum intensity power required + if OPTIONS.isMipFunctional + % If the maximum is not yet computed: compute it + if isempty(OPTIONS.MipFunctional{iCoord}) + sliceOverlay = double(mri_getSlice(OPTIONS.OverlayCube, OPTIONS.cutsCoords(iCoord), iCoord, OPTIONS.isMipFunctional)'); + OutputOptions.MipFunctional{iCoord} = sliceOverlay; + % Else: use the previously computed maximum + else + sliceOverlay = OPTIONS.MipFunctional{iCoord}; + end + % Else: just extract a slice from the volume + else + sliceOverlay = double(mri_getSlice(OPTIONS.OverlayCube, OPTIONS.cutsCoords(iCoord), iCoord, OPTIONS.isMipFunctional)'); + end + else + sliceOverlay = []; + end + + % === APPLY COLORMAP === + % Alpha value depends on if MIP is used + if OPTIONS.isMipFunctional && ~OPTIONS.isMipAnatomy + alphaValue = .3; + else + alphaValue = 0; + end + % Compute alpha map + sliceSize = size(sliceMri); + AlphaMap = ones(sliceSize) * (1 - OPTIONS.MriAlpha); + AlphaMap(sliceMri < OPTIONS.MriThreshold) = alphaValue; + % Apply colormap to slice + cmapSlice = applyColormap(sliceMri, OPTIONS.MriColormap, MriColormapBounds); + + % === Display overlay slice === + if isOverlay + % Get max value for slice + %MaxValue = max(abs(sliceOverlay(:))); + % Apply colormap to overlay slice + %cmapOverlaySlice = applyColormap(sliceOverlay, OverlayColormap, MaxValue * [0 1]); + cmapOverlaySlice = applyColormap(sliceOverlay, OPTIONS.OverlayColormap, OPTIONS.OverlayBounds); + % Build overlay mask + overlayMask = ones(sliceSize) * (1 - OPTIONS.OverlayAlpha); + overlayMask(abs(sliceOverlay) <= OPTIONS.OverlayIntThreshold .* OPTIONS.OverlayBounds(2)) = 0; + %knd: to do: + % overlayMask(abs(sliceOverlay) <= OPTIONS.OverlayExtThreshold .* OPTIONS.OverlayBounds(2)) = 0; + + % Draw overlay slice over MRI slice + cmapSlice(:,:,1) = cmapSlice(:,:,1) .* (1 - overlayMask) + cmapOverlaySlice(:,:,1) .* overlayMask; + cmapSlice(:,:,2) = cmapSlice(:,:,2) .* (1 - overlayMask) + cmapOverlaySlice(:,:,2) .* overlayMask; + cmapSlice(:,:,3) = cmapSlice(:,:,3) .* (1 - overlayMask) + cmapOverlaySlice(:,:,3) .* overlayMask; + end + + % Display function depends on figure type + switch (FigureId.Type) + case '3DViz' + hCut = plotSlice3DViz(hTarget, cmapSlice, OPTIONS.cutsCoords(iCoord), iCoord); + if ~isempty(hCut) + hCuts(iCoord) = hCut; + end + case 'MriViewer' + hCut = plotSliceMriViewer(hTarget(iCoord), cmapSlice); + end +end + + +%% ================================================================================================ +% ===== INTERNAL FUNCTIONS ======================================================================= +% ================================================================================================ +%% ===== PLOT SLICES IN 3D ====== + function hCut = plotSlice3DViz(hAxes, cmapSlice, sliceLocation, dimension) + % Get locations of the slice + nbPts = 50; + baseVect = linspace(0,1,nbPts); + switch(dimension) + case 1 + X = ones(nbPts) .* sliceLocation .* OPTIONS.sMri.Voxsize(1); + Y = meshgrid(baseVect) .* mriSize(2) .* OPTIONS.sMri.Voxsize(2); + Z = meshgrid(baseVect)' .* mriSize(3) .* OPTIONS.sMri.Voxsize(3); + case 2 + X = meshgrid(baseVect) .* mriSize(1) .* OPTIONS.sMri.Voxsize(1); + Y = ones(nbPts) .* sliceLocation .* OPTIONS.sMri.Voxsize(2); + Z = meshgrid(baseVect)' .* mriSize(3) .* OPTIONS.sMri.Voxsize(3); + case 3 + X = meshgrid(baseVect) .* mriSize(1) .* OPTIONS.sMri.Voxsize(1); + Y = meshgrid(baseVect)' .* mriSize(2) .* OPTIONS.sMri.Voxsize(2); + Z = ones(nbPts) .* sliceLocation .* OPTIONS.sMri.Voxsize(3); + end + + % === Switch coordinates from MRI-CS to SCS === + % Apply Rotation/Translation + XYZ = [reshape(X, 1, []); + reshape(Y, 1, []); + reshape(Z, 1, [])]; + XYZ = mri2scs(OPTIONS.sMri, XYZ); + % Convert to milimeters + XYZ = XYZ ./ 1000; + + % === PLOT SURFACE === + tag = sprintf('MriCut%d', dimension); + % Delete previous cut + delete(findobj(hFig, 'Tag', tag)); + % Plot new surface + hCut = surface('XData', reshape(XYZ(1,:),nbPts,nbPts), ... + 'YData', reshape(XYZ(2,:),nbPts,nbPts), ... + 'ZData', reshape(XYZ(3,:),nbPts,nbPts), ... + 'CData', cmapSlice, ... + 'FaceColor', 'texturemap', ... + 'FaceAlpha', 'texturemap', ... + 'AlphaData', AlphaMap, ... + 'AlphaDataMapping', 'none', ... + 'EdgeColor', 'none', ... + 'AmbientStrength', .5, ... + 'DiffuseStrength', .5, ... + 'SpecularStrength', .6, ... + 'Tag', tag, ... + 'Parent', hAxes); + end + + +%% ===== PLOT SLICES IN MRIVIEWER ====== + function hCut = plotSliceMriViewer(hImg, cmapSlice) + % Get locations of the slice + set(hImg, 'CData', cmapSlice); + hCut = hImg; + end +end diff --git a/brainstorm3/toolbox/stat/bst_batch.m b/brainstorm3/toolbox/stat/bst_batch.m new file mode 100644 index 0000000..9996589 --- /dev/null +++ b/brainstorm3/toolbox/stat/bst_batch.m @@ -0,0 +1,1258 @@ +function bst_batch(OPTIONS) +% BST_BATCH: Apply a process defined with panel_statRun, to a list a file. +% +% USAGE: bst_batch('CreatePanel', OPTIONS) +% +% INPUTS: (OPTIONS structure) +% - Conditions : Structure describing the files to process (from panel_stat and panel_process) +% - sProcess : Process to apply (defined in panel_statRun/GetProcessesList) +% - isData : 1 if files to process are recordings, 0 if they are cortical sources +% - Comment : Default comment for output files +% - isAbsoluteValues : For sources, apply "abs" before applying the process +% - nbPermutation : Number of permutations to perform +% - isOverwriteFiles : For filters, if 1 the input files will be replaced +% - OutputType : {'database', 'file', 'matlab'} +% - isCluster : If 1, use the clusters/scouts (only for data extraction functions) +% - isClusterAverage : If 1, merge the input clusters/scouts +% - sClusters : Clusters/scouts to apply (if isCluster = 1) +% - ClustersOptions : Structure that describes the way to compute the clusters/scouts values +% - Time : Time bounds to compute values +% - Baseline : Baseline bounds +% - iTime : Indices of the time samples to process +% - iBaseline : Indices of the baseline + +% @============================================================================= +% This software is part of The Brainstorm Toolbox +% http://neuroimage.usc.edu/brainstorm +% +% Copyright (c)2009 Brainstorm by the University of Southern California +% This software is distributed under the terms of the GNU General Public License +% as published by the Free Software Foundation. Further details on the GPL +% license can be found at http://www.gnu.org/copyleft/gpl.html. +% +% FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE +% UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY +% WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF +% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY +% LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE. +% +% For more information type "brainstorm licence" at command prompt. +% =============================================================================@ +% +% Authors: Francois Tadel, 2009 +% ----------------------------- Script History --------------------------------- +% FT 17-Jul-2009 Creation +% ------------------------------------------------------------------------------ + +% Get samples lists +SamplesA = OPTIONS.Conditions.SamplesA; +SamplesB = OPTIONS.Conditions.SamplesB; + +% ===== APPLY PROCESS ===== +sProcess = OPTIONS.sProcess; +% Switch according to category +switch (sProcess.Category) + case 'Filter' + iStudyToRedraw = BatchFilter(SamplesA, OPTIONS); + isNewConditions = 0; + case 'Extract' + iStudyToRedraw = BatchExtract(SamplesA, OPTIONS); + isNewConditions = 0; + case 'Filter2' + iStudyToRedraw = BatchFilter2(SamplesA, SamplesB, OPTIONS); + isNewConditions = 0; + case 'Average' + % === Grand-average (by condition) === + % Call several time the average function (once per condition) + if strcmpi(sProcess.Name, 'GAVE') + % Initial values + iStudyToRedraw = []; + isNewConditions = 0; + % Process each condition independently + uniqueConditions = unique({SamplesA.Condition}); + for i = 1:length(uniqueConditions) + % Process the average of condition #i + iSampCond = find(strcmpi(uniqueConditions{i}, {SamplesA.Condition})); + [tmpStudyToRedraw, tmpNewConditions] = BatchAverage(SamplesA(iSampCond), OPTIONS); + % Save the results + iStudyToRedraw = [iStudyToRedraw tmpStudyToRedraw]; + isNewConditions = isNewConditions || tmpNewConditions; + end + + % === Average by subject === + % Call several time the average function (once per condition) + elseif strcmpi(sProcess.Name, 'SubjAvg') + % Initial values + iStudyToRedraw = []; + isNewConditions = 0; + % Process each subject independently + uniqueSubjects = unique({SamplesA.SubjectFile}); + for i = 1:length(uniqueSubjects) + % Process the average of subject #i + iSampSubj = find(strcmpi(uniqueSubjects{i}, {SamplesA.SubjectFile})); + [tmpStudyToRedraw, tmpNewConditions] = BatchAverage(SamplesA(iSampSubj), OPTIONS); + % Save the results + iStudyToRedraw = [iStudyToRedraw tmpStudyToRedraw]; + isNewConditions = isNewConditions || tmpNewConditions; + end + + % Else, just call function once to average everything + else + [iStudyToRedraw, isNewConditions] = BatchAverage(SamplesA, OPTIONS); + end + + case {'Spectral'} + [iStudyToRedraw, isNewConditions] = BatchSpectralDecomposition(SamplesA, OPTIONS); + + case {'Recurrence'} + [iStudyToRedraw, isNewConditions] = BatchRecurrenceMaps(SamplesA, OPTIONS); + + case {'TTest', 'PermTest'} + iStudyToRedraw = BatchStat(SamplesA, SamplesB, OPTIONS); + isNewConditions = 0; + + case {'Anova'} + iStudyToRedraw = BatchStat(SamplesA,[],OPTIONS); + isNewConditions = 0; +end + +% ===== UPDATE INTERFACE ===== +% OUTPUT TYPE: DATABASE ONLY +if strcmpi(OPTIONS.OutputType, 'database') && ~isempty(iStudyToRedraw) + iStudyToRedraw = unique(iStudyToRedraw); + % Unload all datasets + bst_dataSetsManager('UnloadAll', 'Forced'); + % Update results links in target study + db_updateLinkResults('Study', iStudyToRedraw); + % Update tree model + if isNewConditions + tree_updateModel(); + else + tree_updateNode('Study', iStudyToRedraw); + end + % Select target study as current node + tree_selectStudyNode( iStudyToRedraw(1) ); + % Save database + bst_saveDatabase(); +end + +% Hide waitbar +bst_progressBar('stop'); + + +end + + + +%% ====================================================================================== +% ===== BATCH FUNCTIONS ================================================================ +% ====================================================================================== + +%% ===== BATCH: FILTERS ===== +function iStudyToRedraw = BatchFilter(SamplesA, OPTIONS) + % === SOME VERIFICATIONS === + % OUTPUT: DATABASE ONLY + if ~strcmpi(OPTIONS.OutputType, 'database') + error('Filtering results can only be stored in database.'); + end + % Get process + sProcess = OPTIONS.sProcess; + nbSamplesA = length(SamplesA); + iStudyToRedraw = []; + % Prepare OPTIONS structure for process_apply function + filterOPTIONS = struct('fcnFilter', str2func(['process_' sProcess.Name]), ... + 'FileA', '', ... + 'iTime', OPTIONS.iTime, ... + 'iBaseline', OPTIONS.iBaseline, ... + 'FileTag', sProcess.Name, ... + 'Comment', OPTIONS.Comment, ... + 'isAbsoluteValues', OPTIONS.isAbsoluteValues, ... + 'blockDimension', sProcess.blockDimension, ... + 'isAvgRef', sProcess.isAvgRef); + + % === FILES LOOP === + bst_progressBar('start', ['Apply process: ' sProcess.Name], 'Initialization...', 0, nbSamplesA); + % Process all the files + for i = 1:nbSamplesA + % Progress bar + bst_progressBar('text', ['File: ' SamplesA(i).FileName]); + bst_progressBar('inc', 1); + % Output study: same as input file + iTargetStudy = SamplesA(i).iStudy; + sTargetStudy = bst_getContext('Study', iTargetStudy); + % Ignore computation for files that are already processed + if ~isempty(strfind(SamplesA(i).FileName, ['_' sProcess.Name])) + res = java_dialog('confirm', ['The following file has already been processed with this filter: ' 10 SamplesA(i).FileName '.' 10 10 'Apply this process again ?'], 'Processes'); + if ~res + continue + end + end + % Update this study + iStudyToRedraw(end + 1) = iTargetStudy; + + % === COMPUTE FILTER === + % Complete filter configuration + filterOPTIONS.FileA = SamplesA(i).FullFileName; + % Apply process + [sNewFile, filterOPTIONS] = process_apply(filterOPTIONS); + % If process cancelled + if isempty(sNewFile) + return + end + + % === UPDATE STUDY === + % Overwrite initial file + if OPTIONS.isOverwriteFiles + % Delete the intial file + io_deleteFile(SamplesA(i).FullFileName, 1); + end + % Add new file to database + if OPTIONS.isData + if OPTIONS.isOverwriteFiles + iItem = SamplesA(i).iItem; + else + iItem = length(sTargetStudy.Data) + 1; + end + % Add new descriptor it to study + sTargetStudy.Data(iItem) = sNewFile; + else + if OPTIONS.isOverwriteFiles + iResult = SamplesA(i).iItem; + else + iResult = length(sTargetStudy.Result) + 1; + end + % Add new descriptor it to study + sTargetStudy.Result(iResult) = sNewFile; + end + bst_setContext('Study', iTargetStudy, sTargetStudy); + end +end + + +%% ===== BATCH: EXTRACT ===== +function iStudyToRedraw = BatchExtract(SamplesA, OPTIONS) + % Get process + sProcess = OPTIONS.sProcess; + nbSamplesA = length(SamplesA); + iStudyToRedraw = []; + isOneOutputMat = ~strcmpi(OPTIONS.OutputType, 'database'); + initTimeVector = []; + ProtocolInfo = bst_getContext('ProtocolInfo'); + + % === FILES LOOP === + bst_progressBar('start', 'Data extraction', 'Initialization...', 0, nbSamplesA); + % Process all the files + for iSample = 1:nbSamplesA + % Progress bar + bst_progressBar('text', ['File: ' SamplesA(iSample).FileName]); + bst_progressBar('inc', 1); + % Output study: same as input file + iTargetStudy = SamplesA(iSample).iStudy; + sTargetStudy = bst_getContext('Study', iTargetStudy); + % Update this study + iStudyToRedraw(end + 1) = iTargetStudy; + + % === READ FILE === + if sProcess.isAvgRef + [matValues, matName, ChannelFlag, TimeVector] = bst_readMatrixInFile(SamplesA(iSample).FullFileName, []); + else + [matValues, matName, ChannelFlag, TimeVector] = bst_readMatrixInFile(SamplesA(iSample).FullFileName, [], 'NoAvgRef'); + end + + % === TIME === + % Check time vectors + if (iSample == 1) + initTimeVector = TimeVector; + elseif (length(initTimeVector) ~= length(TimeVector)) || ~all(abs(initTimeVector - TimeVector) < 1e-6) + filedesc = fullfile(fileparts(SamplesA(iSample).FileName), SamplesA(iSample).Comment); + bst_error(['Time definition should be the same for all the files.' 10 10 'Ignoring file: "' filedesc '"'], 'bst_batch', 0); + return + end + % Keep only required time indices + matValues = matValues(:, OPTIONS.iTime); + TimeVector = TimeVector(OPTIONS.iTime); + + % === PROCESSES === + % Absolute values + if OPTIONS.isAbsoluteValues + matValues = abs(matValues); + end + % Process to apply to this block of data + switch (sProcess. Name) + case 'timemean' + % Compute the average of all the time samples on the defined time window + matValues = mean(matValues, 2); + isTimeMean = 1; + case 'timevar' + % Compute the variance of all the time samples on the defined time window + matValues = var(matValues, 0, 2); + isTimeMean = 1; + case 'extract' + % Nothing to do + isTimeMean = 0; + end + % TimeMean: Keep only the time window from which was computed the result + if isTimeMean + OPTIONS.iTime = OPTIONS.iTime([1,end]); + TimeVector = TimeVector([1, end]); + end + + % === APPLY CLUSTER === + if OPTIONS.isCluster + % Process each cluster + nbClusters = length(OPTIONS.Clusters); + tmpMatValues = zeros(nbClusters, size(matValues, 2)); + DescCluster = cell(nbClusters, 1); + for i = 1:nbClusters + sCluster = OPTIONS.Clusters(i); + % === GET ROWS INDICES === + if OPTIONS.isData + % Get channel file + [sStudy, iStudy] = bst_getContext('DataFile', SamplesA(iSample).FileName); + sChannel = bst_getContext('ChannelForStudy', iStudy); + % Load channel file + ChannelFile = fullfile(ProtocolInfo.STUDIES, sChannel.FileName); + ChannelMat = load(ChannelFile, 'Channel'); + % CLUSTERS: Get cluster channels + iRows = panel_clusters('GetChannelsInCluster', sCluster, ChannelMat.Channel, ChannelFlag); + if isempty(iRows) + return; + end + else + % SCOUTS: Get the indices of the sources of this scout + iRows = sCluster.Vertices; + end + + % === COMPUTE CLUSTER VALUES === + % Scouts: absolute or relative values + isDifference = strcmpi(sProcess.Name, 'diffAB'); + % If computing a difference : FORCE RELATIVE VALUES + if ~OPTIONS.ClustersOptions.isAbsolute || isDifference + tmp = matValues(iRows, :); + else + tmp = abs(matValues(iRows, :)); + end + % Scouts computation operation : mean or max + switch (OPTIONS.ClustersOptions.function) + case 'Max' + tmpMatValues(i,:) = max(tmp, [], 1); + case 'Power' + tmpMatValues(i,:) = sum(tmp .^ 2, 1); + case 'Mean' + tmpMatValues(i,:) = mean(tmp, 1); + otherwise + bst_error(['Cannot extract automatically all values.' 10 ... + 'Please change scout options: "Scouts" panel > View > Time series options'], ... + 'bst_batch', 0); + return; + end + % Save the name of the scout + DescCluster{i} = sCluster.Label; + end + % Return results matrix averaged by scout + matValues = tmpMatValues; + else + DescCluster = {}; + end + + % ===== OUTPUT STRUCTURE ==== + % Create new output structure + if ~isOneOutputMat || (iSample == 1) + newFileMat = CreateDefaultStruct(sProcess, SamplesA(iSample).FullFileName, OPTIONS.isData, TimeVector, OPTIONS.iTime, ChannelFlag, isOneOutputMat, 1); + % Append comment + newFileMat.Comment = [newFileMat.Comment, ' ', OPTIONS.Comment]; + end + % If new file to be saved at each loop: copy matValues + if ~isOneOutputMat + if isTimeMean + newFileMat.(matName) = repmat(matValues, [1, 2]); + else + newFileMat.(matName) = matValues; + end + else + % Get new matrix + prevMat = newFileMat.(matName); + % Check the orientation in which the new data should be appended: + % - Time mean : dimension 2 (time) + % - Channels mean : dimension 1 (channels) + if isTimeMean + % Check the number of electrodes + if ~isempty(prevMat) && (size(prevMat,1) ~= size(matValues,1)) + error(['The data files in samples set A do not have the same number of channels.' 10 ... + 'It is impossible to store their averages in the same matrix.']); + end + % Concatenate the results matrices & add description fields + newFileMat.(matName) = cat(2, prevMat, matValues); + newFileMat.DescFileName = cat(2, newFileMat.DescFileName, repmat({SamplesA(iSample).FileName}, [1, size(matValues,2)])); + newFileMat.DescCluster = cat(2, newFileMat.DescCluster, DescCluster); + else + if ~isempty(prevMat) && (size(prevMat,2) ~= size(matValues,2)) + error(['The files in samples set A do not have the same number of time samples.' 10 ... + 'It is impossible to store their averages in the same matrix.']); + end + % Concatenate the results matrices & add description fields + newFileMat.(matName) = cat(1, prevMat, matValues); + newFileMat.DescFileName = cat(1, newFileMat.DescFileName, repmat({SamplesA(iSample).FileName}, [size(matValues,1), 1])); + newFileMat.DescCluster = cat(1, newFileMat.DescCluster, DescCluster); + end + % Add bad channels from current ChannelFlag + newFileMat.ChannelFlag(ChannelFlag == -1) = -1; + end + + % ===== SAVE FILE ===== + if ~isOneOutputMat + SaveNewFile(newFileMat, sProcess, OPTIONS.isData, iTargetStudy, sTargetStudy, SamplesA(iSample).FileName); + end + end + + % === EXPORT DATA === + if isOneOutputMat + % Display waitbar + bst_progressBar('start', 'Processes', 'Saving results...'); + % Save file + switch(OPTIONS.OutputType) + case 'file' + % Save in a user defined file + if OPTIONS.isData || OPTIONS.isCluster + export_data(newFileMat); + else + export_result(newFileMat); + end + case 'matlab' + % Export to Matlab workspace + export_matlab(newFileMat); + end + end +end + + +%% ===== BATCH: AVERAGE ===== +function [iStudyToRedraw, isNewConditions] = BatchAverage(SamplesA, OPTIONS) + % Get process + sProcess = OPTIONS.sProcess; + % Get output study + [sTargetStudy, iTargetStudy isNewConditions, Comment] = GetOutputStudy(sProcess,SamplesA,repmat(SamplesA, 0),OPTIONS); + % Update this study + iStudyToRedraw = iTargetStudy; + % If no valid output study can be found + if isempty(iTargetStudy) + return; + end + + % === PROCESS AVERAGE === + bst_progressBar('start', ['Apply process: ' sProcess.Name], 'Initialization...', 0, length(SamplesA)); + % If absolute values + if OPTIONS.isAbsoluteValues + strAbsolute = 'AbsoluteValues'; + else + strAbsolute = []; + end + Stat = bst_statFiles('mean', {SamplesA.FullFileName}, OPTIONS.iTime, 'PercentProgressBar', strAbsolute); + + % === SAVE RESULTS === + % Get default file structure + newFileMat = CreateDefaultStruct(sProcess, SamplesA(1).FullFileName, OPTIONS.isData, Stat.Time, OPTIONS.iTime, Stat.ChannelFlag, 0, 0); + % Add averaged values in it + newFileMat.(Stat.MatName) = Stat.mean; + % Comment + if isempty(OPTIONS.Comment) && isempty(Comment) + % Use default commet: "ProcessName: date" + c = clock; + strTime = sprintf('%02.0f%02.0f%02.0f_%02.0f%02.0f', c(1)-2000, c(2:5)); + newFileMat.Comment = [sProcess.Name ': ' strTime]; + elseif isempty(OPTIONS.Comment) + newFileMat.Comment = Comment; + else + newFileMat.Comment = OPTIONS.Comment; + end + % Save and register file + SaveNewFile(newFileMat, sProcess, OPTIONS.isData, iTargetStudy, sTargetStudy, SamplesA(1).FileName); +end + + +%% ===== BATCH: FILTER2 ===== +function [iStudyToRedraw, isNewConditions] = BatchFilter2(SamplesA, SamplesB, OPTIONS) + % Get process + sProcess = OPTIONS.sProcess; + nbSamplesA = length(SamplesA); + iStudyToRedraw = []; + + % === FILES LOOP === + bst_progressBar('start', 'Comparisons', 'Initialization...', 0, nbSamplesA); + % Process all the files + for iSample = 1:nbSamplesA + % Progress bar + bst_progressBar('text', ['File: ' SamplesA(iSample).FileName]); + bst_progressBar('inc', 1); + % Get output study + [sTargetStudy, iTargetStudy isNewConditions, Comment] = GetOutputStudy(sProcess,SamplesA(iSample),SamplesB(iSample),OPTIONS); + % If no valid output study can be found + if isempty(iTargetStudy) + return; + end + % Update this study + iStudyToRedraw(end + 1) = iTargetStudy; + + % === READ FILES === + if sProcess.isAvgRef + % Sample A + [matValuesA, matNameA, ChannelFlagA, TimeVectorA] = bst_readMatrixInFile(SamplesA(iSample).FullFileName, []); + % Sample B + [matValuesB, matNameB, ChannelFlagB, TimeVectorB] = bst_readMatrixInFile(SamplesB(iSample).FullFileName, []); + else + % Sample A + [matValuesA, matNameA, ChannelFlagA, TimeVectorA] = bst_readMatrixInFile(SamplesA(iSample).FullFileName, [], 'NoAvgRef'); + % Sample B + [matValuesB, matNameB, ChannelFlagB, TimeVectorB] = bst_readMatrixInFile(SamplesB(iSample).FullFileName, [], 'NoAvgRef'); + end + + % === TIME === + % Check time vectors + if (length(TimeVectorA) ~= length(TimeVectorB)) || ~all(abs(TimeVectorA - TimeVectorB) < 1e-6) + bst_error('Each couple of files (A,B) must have the same time definition.', 'bst_batch', 0); + return + elseif (length(ChannelFlagA) ~= length(ChannelFlagB)) + bst_error('Each couple of files (A,B) must have the same number of channels.', 'bst_batch', 0); + return + end + % Keep only required time indices + matValuesA = matValuesA(:, OPTIONS.iTime); + matValuesB = matValuesB(:, OPTIONS.iTime); + TimeVector = TimeVectorA(OPTIONS.iTime); + % Combine bad channels + ChannelFlag = ones(size(ChannelFlagA)); + ChannelFlag((ChannelFlagA == -1) | (ChannelFlagB == -1)) = -1; + + % === PROCESSES === + % Absolute values + if OPTIONS.isAbsoluteValues + matValuesA = abs(matValuesA); + matValuesB = abs(matValuesB); + end + % Apply process + switch (sProcess.Name) + case 'diffAB' + matValues = matValuesA - matValuesB; + case 'meanAB' + matValues = (matValuesA + matValuesB) / 2; + end + + % ===== SAVE FILE ==== + % Create default structure + newFileMat = CreateDefaultStruct(sProcess, SamplesA(iSample).FullFileName, OPTIONS.isData, TimeVector, OPTIONS.iTime, ChannelFlag, 0, 1); + % Add averaged values in it + newFileMat.(matNameA) = matValues; + % Comment + if isempty(OPTIONS.Comment) + if ~isempty(Comment) + newFileMat.Comment = Comment; + else + % Build comments based on the samples couple + localCond = struct('SamplesA',SamplesA(iSample), 'SamplesB',SamplesB(iSample)); + newFileMat.Comment = panel_statRun('FormatComment', sProcess, localCond, OPTIONS.Time); + end + else + newFileMat.Comment = OPTIONS.Comment; + end + % Save file + SaveNewFile(newFileMat, sProcess, OPTIONS.isData, iTargetStudy, sTargetStudy, SamplesA(iSample).FileName); + end +end + + +%% ===== BATCH: STAT ===== +function iStudyToRedraw = BatchStat(SamplesA, SamplesB, OPTIONS) + % Get process + sProcess = OPTIONS.sProcess; + testType = sProcess.Name; + isPermTest = strcmpi(sProcess.Category, 'PermTest'); + % For old Karim t-test + isOldSimpleTest = ~isempty(strfind(sProcess.Name, 'old')); + if isOldSimpleTest + testType = strrep(testType, 'old_', ''); + end + isAnova = strcmpi(sProcess.Category, 'Anova'); + + + % Display progress bar + bst_progressBar('start', 'Statistical test', 'Initialization...', 0, length(SamplesA)); + % Get output study + if isAnova + [sTargetStudy, iTargetStudy isNewConditions, Comment] = GetOutputStudy(sProcess, SamplesA, SamplesA([]), OPTIONS); + else + [sTargetStudy, iTargetStudy isNewConditions, Comment] = GetOutputStudy(sProcess, SamplesA, SamplesB, OPTIONS); + end + iStudyToRedraw = iTargetStudy; + % If no valid output study can be found + if isempty(iTargetStudy) + return; + end + + % === Load all the samples === + % Needed only for permutation t-test, and OLD KARIM T-TEST + if isPermTest || isOldSimpleTest + % Samples set A + [X1, ChannelFlagA, TimeVector] = buildDataArray({SamplesA.FullFileName}, OPTIONS.iTime); + % Samples set B + [X2, ChannelFlagB] = buildDataArray({SamplesB.FullFileName}, OPTIONS.iTime); + % If is absolute values + if OPTIONS.isAbsoluteValues + X1 = abs(X1); + X2 = abs(X2); + end + % Combine channel flag + ChannelFlag = ChannelFlagA; + ChannelFlag(ChannelFlagB == -1) = -1; + end + + if isAnova + %knd: to do + warning('manually changed anova factors!') + [pmap,Fmap,fx,ChannelFlag, TimeVector] = knd_rmanova_files(SamplesA,OPTIONS); + + % === SIMPLE TESTS (OLD) === + elseif ~isPermTest && isOldSimpleTest + % Remove the "old" tag + testType = strrep(testType, 'old_', ''); + % Set permutation dimension + dim_p = 1; + % Display waitbar + bst_progressBar('start', 'Processes', 'Computing t-test...'); + % Compute test + [pmap,tmap] = knd_ttest(X1, X2, dim_p, testType); + + % === SIMPLE TESTS === + elseif ~isPermTest + % Compute test + if OPTIONS.isAbsoluteValues + [pmap,tmap,ChannelFlag, TimeVector] = knd_ttest_files({SamplesA.FullFileName}, {SamplesB.FullFileName}, testType, OPTIONS.iTime, OPTIONS.iTime, 'AbsoluteValues'); + else + [pmap,tmap,ChannelFlag, TimeVector] = knd_ttest_files({SamplesA.FullFileName}, {SamplesB.FullFileName}, testType, OPTIONS.iTime, OPTIONS.iTime); + end + + % === PERMUTATIONS TESTS === + elseif isPermTest + tic + % Set permutation dimension + if sProcess.isPaired + dim_p = 1; + else + dim_p = -1; + end + + % Compute test + [pmap,tmap] = permtest(X1, X2, {testType}, dim_p, [], OPTIONS.nbPermutation); + % Display computation time + timeLength = toc(); + bst_message_window(sprintf('\nTook: %3.2f secs', timeLength)); + + + end + + % === BUILD NEW FILES === + % Display waitbar + bst_progressBar('start', 'Processes', 'Saving results...'); + + if isAnova + Factors.N = 3; + NS = numel(unique({SamplesA.SubjectName})) + [u,i,j] = unique([SamplesA.iItem]) + if numel(u)>1 + SelectedItem = listdlg('ListString', [strvcat({SamplesA(i).Condition}) repmat(' : ',length(u),1) strvcat({SamplesA(i).Comment})],'SelectionMode','single'); + if isempty(SelectedItem) + return + end + SamplesA = SamplesA(j == SelectedItem); + end + f = {SamplesA.Condition}; + f=reshape(f(1+[0:3]*NS),[2 2]) + fa=sprintf('%s+',f{1,:}); + fb=sprintf('%s+',f{2,:}); + Factors.Names{1}=sprintf('%s vs. %s',fa(1:end-1),fb(1:end-1)); + fa=sprintf('%s+',f{:,1}); + fb=sprintf('%s+',f{:,2}); + Factors.Names{2}=sprintf('%s vs. %s',fa(1:end-1),fb(1:end-1)); + + %Factors.Names = { 'FB' 'Conf' 'Subj'}; + Factors.NLevels = [2 2 NS] + Factors.Levels = { {'C' 'I'} {'H' 'L'} [] }; + Factors.X = []; + %must loop on maps + for i_fx=1:size(pmap,1) + % Get default structure + newFileMat = CreateDefaultStruct(sProcess, SamplesA(1).FullFileName, OPTIONS.isData, TimeVector, OPTIONS.iTime, ChannelFlag, 0, 0); + % Comment + newFileMat.Comment = OPTIONS.Comment ; + if numel(fx{i_fx}) == 1 + newFileMat.Comment = [newFileMat.Comment ' - Main effect of ']; + else + newFileMat.Comment = [newFileMat.Comment ' - Interaction of ']; + end + newFileMat.Comment = [newFileMat.Comment sprintf('%s * ',Factors.Names{fx{i_fx}})]; + newFileMat.Comment(end-2:end)=[]; + % Save P and F maps + sz=[size(pmap) 1]; + newFileMat.pmap = single(reshape(pmap(i_fx,:),sz(2:end))); + newFileMat.tmap = single(reshape(Fmap(i_fx,:),sz(2:end))); + % Save new file and register in database + SaveNewFile(newFileMat, sProcess, OPTIONS.isData, iTargetStudy, sTargetStudy, SamplesA(1).FileName); + end + else + % Get default structure + newFileMat = CreateDefaultStruct(sProcess, SamplesA(1).FullFileName, OPTIONS.isData, TimeVector, OPTIONS.iTime, ChannelFlag, 0, 0); + % Comment + if isempty(OPTIONS.Comment) + newFileMat.Comment = Comment; + else + newFileMat.Comment = OPTIONS.Comment; + end + % Save P and T maps + newFileMat.pmap = single(pmap); + newFileMat.tmap = single(tmap); + % Save new file and register in database + SaveNewFile(newFileMat, sProcess, OPTIONS.isData, iTargetStudy, sTargetStudy, SamplesA(1).FileName); + end +end + + +%% ===== BATCH: SPECTRAL ANALYSIS ====== +function [iStudyToRedraw, isNewConditions] = BatchSpectralDecomposition(SamplesA, OPTIONS) + % Frequency bands of interest + freqBands = ... + [1 3.9;... + 4,7.9;... + 8,12.9;... + 13,34.9;... + 35,54.9;... + 65,85;... + 200,250]; + NamesFreqBands = {... + 'delta_1_4Hz',... + 'theta_4_8Hz',... + 'alpha_8_13Hz',... + 'beta_13_35Hz',... + 'gamma_35_55Hz',... + 'gamma_65_85Hz',... + 'gamma_200_250Hz'}; + + % Get process + sProcess = OPTIONS.sProcess; + nbSamplesA = length(SamplesA); + iStudyToRedraw = []; + + % === FILES LOOP === + bst_progressBar('start', 'Comparisons', 'Initialization...', 0, nbSamplesA); + % Process all the files + for iSample = 1:nbSamplesA + % Progress bar + bst_progressBar('text', ['File: ' SamplesA(iSample).FileName]); + bst_progressBar('inc', 1); + % Get output study + [sTargetStudy, iTargetStudy isNewConditions, Comment] = GetOutputStudy(sProcess,SamplesA(iSample),repmat(SamplesA, 0),OPTIONS); + % If no valid output study can be found + if isempty(iTargetStudy) + return; + end + % Update this study + iStudyToRedraw(end + 1) = iTargetStudy; + + % === READ FILES === + % Resolve link + [FileName, DataFile] = resolveResultsLink(SamplesA(iSample).FullFileName); + % Load results file + kernelMat = load(FileName); +% dataMat = load(DataFile,'F', 'Time'); + dataMat = in_data_bst( DataFile, [], 'Time', 'F'); + + %iMEG = good_channel(kernelMat.Channel,kernelMat.ChannelFlag, 'MEG'); %CBB: need to expand to EEG as well + iMEG = kernelMat.OPTIONS.GoodChannel; + dataMat.F = dataMat.F(iMEG, :); + + if iSample == 1 + F_RMS = zeros(size(kernelMat.ImagingKernel,1),length(NamesFreqBands),nbSamplesA); + end + sRate = abs(1/(dataMat.Time(2)-dataMat.Time(1))); + nTime = size(dataMat.F,2); + NFFT = 2^nextpow2(nTime); % Next power of 2 from length of y + freqVec = sRate/2*linspace(0,1,NFFT/2); + + fftF = fft(dataMat.F,NFFT,2)/nTime; + %powfftF = 2*abs(fftF(:,1:end/2)); + clear dataMat; + for ifreq = 1:length(NamesFreqBands) + VecInd = findclosest(freqBands(ifreq,:),freqVec); + %F = bandpassFilter_all(dataMat.F,sRate,freqBands(ifreq,1),freqBands(ifreq,2)); + F_RMS(:,ifreq,iSample) = sum(2*abs(kernelMat.ImagingKernel * fftF(:, VecInd(1):VecInd(2))),2)... + /length(VecInd(1):VecInd(2)); + % ImageGridAmp = sqrt(sum(ImageGridAmp.^2,2)/size(ImageGridAmp,2)); % compute root mean square power in current frequency band + end + end + + clear dataMat fftF + % Compute statistics + %F_RMS(F_RMS<.05*max(F_RMS(:)))=0; + aveStat = mean(F_RMS,3); + stdStat = std(F_RMS,0,3); + clear F_RMS + for ifreq = 1:length(NamesFreqBands) + current_aveStat = aveStat(:,ifreq); + tmp_current_aveStat = current_aveStat; + tmp_current_aveStat(tmp_current_aveStat<.5*max(tmp_current_aveStat))=0; + current_stdStat = stdStat(:,ifreq); + tStat = (tmp_current_aveStat./current_stdStat)*sqrt(nbSamplesA-1); + %tStat(isnan(tStat))=0; + + % === SAVE RESULTS === + kernelMat.ImageGridAmp = repmat(current_aveStat,1,2); + kernelMat.ImagingKernel = []; + kernelMat.ImageGridTime = [1,2]; + kernelMat.Comment = sprintf('GAVE: %s',NamesFreqBands{ifreq}); + % Save and register file + SaveNewFile(kernelMat, sProcess, OPTIONS.isData, iTargetStudy, sTargetStudy, SamplesA(1).FileName); + + kernelMat.ImageGridAmp = repmat(current_stdStat,1,2); + kernelMat.ImagingKernel = []; + kernelMat.ImageGridTime = [1,2]; + kernelMat.Comment = sprintf('STD: %s',NamesFreqBands{ifreq}); + % Save and register file + SaveNewFile(kernelMat, sProcess, OPTIONS.isData, iTargetStudy, sTargetStudy, SamplesA(1).FileName); + + + kernelMat.ImageGridAmp = repmat(tStat,1,2); + kernelMat.ImagingKernel = []; + kernelMat.ImageGridTime = [1,2]; + kernelMat.Comment = sprintf('T: %s',NamesFreqBands{ifreq}); + % Save and register file + SaveNewFile(kernelMat, sProcess, OPTIONS.isData, iTargetStudy, sTargetStudy, SamplesA(1).FileName); + + end + +end + +%% ===== BATCH: RECURRENCE MAPS ====== +function [iStudyToRedraw, isNewConditions] = BatchRecurrenceMaps(SamplesA, OPTIONS) + % Frequency bands of interest + ampThresh = .75; %Binarization Threshold + timeWindow = 30; % time window within which binarization is processed + bandPass1 = 1; + bandPass2 = 250; + + % Get process + sProcess = OPTIONS.sProcess; + nbSamplesA = length(SamplesA); + iStudyToRedraw = []; + + % === FILES LOOP === + bst_progressBar('start', 'Comparisons', 'Initialization...', 0, nbSamplesA); + % Process all the files + for iSample = 1:nbSamplesA + % Progress bar + bst_progressBar('text', ['File: ' SamplesA(iSample).FileName]); + bst_progressBar('inc', 1); + % Get output study + [sTargetStudy, iTargetStudy isNewConditions, Comment] = GetOutputStudy(sProcess,SamplesA(iSample),repmat(SamplesA, 0),OPTIONS); + % If no valid output study can be found + if isempty(iTargetStudy) + return; + end + % Update this study + iStudyToRedraw(end + 1) = iTargetStudy; + + % === READ FILES === + % Resolve link + [FileName, DataFile] = resolveResultsLink(SamplesA(iSample).FullFileName); + % Load results file + kernelMat = load(FileName); +% dataMat = load(DataFile,'F', 'Time'); + dataMat = in_data_bst( DataFile, [], 'Time', 'F'); + iMEG = good_channel(kernelMat.Channel,kernelMat.ChannelFlag, 'MEG'); %CBB: need to expand to EEG as well + dataMat.F = dataMat.F(iMEG, :); + + sRate = abs(1/(dataMat.Time(2)-dataMat.Time(1))); + nTime = size(dataMat.F,2); + + VecInd = findclosest([-timeWindow,timeWindow]/1000,dataMat.Time); + timeVec = VecInd(1):VecInd(2); + dataMat.F = bandpassFilter_all(dataMat.F,sRate, bandPass1,bandPass2); + ImageGridAmp = abs(kernelMat.ImagingKernel * dataMat.F(:, VecInd(1):VecInd(2))); + + % Thresholding + if iSample == 1; + iMask = zeros(size(ImageGridAmp,1),1); + iTimeMask = zeros(size(ImageGridAmp,1),nbSamplesA); + end + %tmp = zeros(size(ImageGridAmp)); + %tmp(find(ImageGridAmp>ampThresh*max(ImageGridAmp(:)))) = 1; + + % time-relative max + % maxx = max(ImageGridAmp); + % maxx = repmat(maxx,size(ImageGridAmp,1),1); + % time-absolute max + maxx = max(ImageGridAmp(:)); + maxx = maxx*ones(size(ImageGridAmp)); + + iMaxtmp = ImageGridAmp > (ampThresh * maxx); + iTimeMask(:,iSample) = min(... + iMaxtmp .* repmat(dataMat.Time(timeVec),size(ImageGridAmp,1),1), ... + [],2); + iMaxtmp = sum(iMaxtmp,2); + + iMask(find(iMaxtmp)) = iMask(find(iMaxtmp)) + 1; + %[I,J] = find(ImageGridAmp>ampThresh*max(ImageGridAmp(:))); + %iMask(I) = iMask(I) + 1; + %iTimeMask(I,iSample) = dataMat.Time(timeVec(J)); + end + clear dataMat + iMask = 100*iMask/nbSamplesA; + iTimeMask = 1000*mean(iTimeMask,2).*(iMask>0); + + % === SAVE RESULTS === + kernelMat.ImageGridAmp = repmat(iMask,1,2); + kernelMat.ImagingKernel = []; + kernelMat.ImageGridTime = [1,2]; + kernelMat.Comment = sprintf('Recurrence Map'); + % Save and register file + SaveNewFile(kernelMat, sProcess, OPTIONS.isData, iTargetStudy, sTargetStudy, SamplesA(1).FileName); + + kernelMat.ImageGridAmp = repmat(iTimeMask,1,2); + kernelMat.ImagingKernel = []; + kernelMat.ImageGridTime = [1,2]; + kernelMat.Comment = sprintf('Recurrence Map (latencies)'); + % Save and register file + SaveNewFile(kernelMat, sProcess, OPTIONS.isData, iTargetStudy, sTargetStudy, SamplesA(1).FileName); + + +end + +%% ====================================================================================== +% ===== HELPERS ======================================================================== +% ====================================================================================== +%% ===== CREATE DEFAULT OUTPUT STRUCTURE ===== +function sFileMat = CreateDefaultStruct(sProcess, FileName, isData, TimeVector, iTime, ChannelFlag, isOneOutputMat, isSampleBySample) +% Default structures for stat +isStat = ismember(sProcess.Category, {'TTest', 'PermTest', 'Anova'}); +if isStat + sStat = struct(... + 'Type', '', ... + 'Comment', '', ... + 'Time', TimeVector, ... + 'ChannelFlag', ChannelFlag, ... + 'pmap', [], ... + 'tmap', [], ... + 'pThreshold', 0.05); + end + + % If recordings + if isData + % Load file + sFileMat = load(FileName); + % Stat + if isStat + sStat.Type = 'data'; + sStat.Comment = sFileMat.Comment; + sFileMat = sStat; + % Data + else + sFileMat.Time = TimeVector; + sFileMat.F = []; + end + % Else: sources + else + % Get protocol directories + ProtocolInfo = bst_getContext('ProtocolInfo'); + % Resolve link, if it is a link + [FileName, DataFile] = resolveResultsLink(FileName); + % Load file + sFileMat = load(FileName); + % Stat + if isStat + sStat.Type = 'results'; + sStat.Comment = sFileMat.Comment; + sFileMat = sStat; + else + % Time + sFileMat.Time = iTime; + sFileMat.ImageGridTime = TimeVector; + % Reset some fields + sFileMat.OPTIONS.Data = []; + sFileMat.Fsynth = []; + sFileMat.ImagingKernel = []; + sFileMat.ImageGridAmp = []; + if ~isSampleBySample + sFileMat.DataFile = []; + elseif ~isempty(DataFile) + sFileMat.DataFile = strrep(DataFile, ProtocolInfo.STUDIES, ''); + end + end + % Remove "Kernel" indications in the Comment field + sFileMat.Comment = strrep(sFileMat.Comment, '(Kernel)', ''); + sFileMat.Comment = strrep(sFileMat.Comment, 'Kernel', ''); + end + % Bad channels list + sFileMat.ChannelFlag = ChannelFlag; + + % Add some fields for exported results + if isOneOutputMat + sFileMat.DescFileName = {}; + sFileMat.DescCluster = {}; + sFileMat.DescProcessType= sProcess.Name; + end +end + + +%% ===== SAVE NEW FILE ===== +function sTargetStudy = SaveNewFile(newFileMat, sProcess, isData, iTargetStudy, sTargetStudy, InputFile) + % Get protocol directories + ProtocolInfo = bst_getContext('ProtocolInfo'); + % Get default output file + OutputDir = fullfile(ProtocolInfo.STUDIES, fileparts(sTargetStudy.FileName)); + FullFileName = GetDefaultFileName(sProcess, isData, OutputDir, InputFile); + % Save in database + save(FullFileName, '-struct', 'newFileMat'); + % Register in database + isStat = ismember(sProcess.Category, {'TTest', 'PermTest', 'Anova'}); + FileName = strrep(FullFileName, ProtocolInfo.STUDIES, ''); + sTargetStudy = RegisterNewFile(newFileMat, FileName, isData, isStat, iTargetStudy); +end + + +%% ===== GET DEFAULT FILE NAME ===== +function OutputFile = GetDefaultFileName(sProcess, isData, OutputDir, InputFile) + % Get date and time for filename + c = clock; + strTime = sprintf('_%02.0f%02.0f%02.0f_%02.0f%02.0f', c(1)-2000, c(2:5)); + + % Output file tag + if ismember(sProcess.Category, {'TTest', 'PermTest', 'Anova'}) + if isData + fileTag = 'pdata'; + else + fileTag = 'presults'; + end + else + if isData + fileTag = 'data'; + else + fileTag = 'results'; + end + end + + % Other tags present in input file + if ~isempty(strfind(InputFile, '_zscore')) + strInputTags = '_zscore'; + else + strInputTags = ''; + end + + % Default filename + defFileName = [fileTag '_' sProcess.Name strTime strInputTags '.mat']; + % File in the target study + OutputFile = fullfile(OutputDir, defFileName); + % Make filename unique + OutputFile = io_makeUniqueFilename(OutputFile); +end + + +%% ===== REGISTER NEW FILE ===== +function sStudy = RegisterNewFile(FileMat, FileName, isData, isStat, iStudy) + % Get study + sStudy = bst_getContext('Study', iStudy); + % Stat + if isStat + % Create new stat file descriptor + sNewStat = db_getDataTemplate('Stat'); + sNewStat.FileName = FileName; + sNewStat.Comment = FileMat.Comment; + sNewStat.Type = FileMat.Type; + sNewStat.pThreshold = FileMat.pThreshold; + % Add it to study + sStudy.Stat(end+1) = sNewStat; + % Data + elseif isData + % Create new data descriptor + sNewData = db_getDataTemplate('Data'); + sNewData.FileName = FileName; + sNewData.Comment = FileMat.Comment; + % Add it to study + sStudy.Data(end+1) = sNewData; + % Results + else + % Create new results descriptor + sNewResult = db_getDataTemplate('Results'); + sNewResult.FileName = FileName; + sNewResult.Comment = FileMat.Comment; + sNewResult.DataFile = FileMat.DataFile; + % Add it to study + sStudy.Result(end+1) = sNewResult; + end + % Update database + bst_setContext('Study', iStudy, sStudy); +end + + +% ===== GET OUTPUT STUDY ===== +function [sOutputStudy, iOutputStudy, isNewConditions, Comment] = GetOutputStudy(sProcess,SamplesA,SamplesB,OPTIONS) + % Get some properties of the filter + isMixedSubjectsAB = isempty(SamplesB) || ~isequal({SamplesA.SubjectFile}, {SamplesB.SubjectFile}); + isSampleBySample = ismember(sProcess.Category, {'Filter2', 'Filter', 'Extract'}); + isConditionByCondition = strcmpi(sProcess.Name, 'GAVE'); + isNewConditions = 0; + % Get the list of involved studies + uniqueStudiesInd = unique([SamplesA.iStudy, SamplesB.iStudy]); +% Comment = OPTIONS.Comment; + Comment = ''; + + % ===== CHECK SUBJECT UNICITY ===== + % Get full subjects list without repetitions + uniqueSubjectFiles = unique(cat(2, {SamplesA.SubjectFile}, {SamplesB.SubjectFile})); + % For stat on sources: all the results files must be computed on the same surface. + % => else, the number of sources might be different between different files + % => user should realign surfaces before going on + if (length(uniqueSubjectFiles) > 1) + isUniqueSubject = 0; + if ~isSampleBySample && ~OPTIONS.isData + % If there is more than one subjects involved: need to check that all + % the subjects use the default anatomy + for i = 1:length(uniqueSubjectFiles) + sSubject = bst_getContext('Subject', uniqueSubjectFiles{i}); + if ~sSubject.UseDefaultAnat + gui_hidePanel('panel_statRun'); + sOutputStudy = []; + iOutputStudy = []; + bst_error(['The sources files you selected use different anatomies.' 10 ... + 'In order to perform any computation on sources, all the files' 10 ... + 'should use the same anatomy.' 10 10 ... + 'You have to project all your results on a template anatomy.' 10 ... + 'Popup menu: "Project sources on default anatomy".'], 'Processes', 0); + return; + end + end + end + else + isUniqueSubject = 1; + end + + % ===== GET OUTPUT STUDY ===== + % SUBJECT AVERAGE + if strcmpi(sProcess.Name, 'SubjAvg') + % Get subject + [sSubject, iSubject] = bst_getContext('Subject', SamplesA(1).SubjectFile); + % Get "intra" study for this subject + [sOutputStudy, iOutputStudy] = bst_getContext('AnalysisIntraStudy', iSubject); + % Comment + Comment = ['SubjectAvg(' SamplesA(1).SubjectName ')']; + % UNIQUE STUDY (OR OVERWRITE) + elseif (length(uniqueStudiesInd) == 1) || OPTIONS.isOverwriteFiles + % Get this unique study + [sOutputStudy, iOutputStudy] = bst_getContext('Study', uniqueStudiesInd(1)); + % MULTIPLE STUDIES : SAMPLE BY SAMPLE + elseif isSampleBySample && ~isMixedSubjectsAB + % Target study is a new condition (name = test comment) + % Get subject path + newCondPath = fileparts(SamplesA(1).SubjectFile); + % Get new condition name + localCond = struct('SamplesA',SamplesA,'SamplesB',SamplesB); + newCondName = panel_statRun('FormatComment', sProcess, localCond, OPTIONS.Time); + newCondName = io_standardizeFileName(newCondName); + % Comment + Comment = sProcess.FileTag; + Comment = strrep(Comment, '#A#', localCond.SamplesA(1).Comment); + Comment = strrep(Comment, '#B#', localCond.SamplesB(1).Comment); + % Try to get an existing condition with this path + [sOutputStudy, iOutputStudy] = bst_getContext('StudyWithCondition', fullfile(newCondPath,newCondName)); + % If does not exist: Create a new condition + if isempty(sOutputStudy) + iOutputStudy = db_addCondition(newCondPath, newCondName, 'NoRefresh'); + sOutputStudy = bst_getContext('Study', iOutputStudy); + isNewConditions = 1; + end + % MULTIPLE STUDIES : CONDITION BY CONDITION + elseif isConditionByCondition + Comment = ['GAVE(' SamplesA(1).Condition ')']; + % Grand average: stored in 'analysis-inter' node + [sOutputStudy, iOutputStudy] = bst_getContext('AnalysisInterStudy'); + % MULTIPLE STUDIES : GLOBAL + else + % UNIQUE SUBJECT : 'analysis-intra' + if isUniqueSubject + % Subject file is the same for all the studies => find common subject + [sSubject, iSubject] = bst_getContext('Subject', SamplesA(1).SubjectFile); + % Get 'analysis-intra' node of this subject + [sOutputStudy, iOutputStudy] = bst_getContext('AnalysisIntraStudy', iSubject); + % MULTIPLE SUBJECTS : 'analysis-inter' + else + % Get 'analysis-inter' node in current protocol + [sOutputStudy, iOutputStudy] = bst_getContext('AnalysisInterStudy'); + end + end + % Error ? + if isempty(sOutputStudy) + error('Cannot find an analysis study to store the results.'); + end + + % ===== COMBINE CHANNEL FILES ===== + % If source and target studies are not the same + if ~isequal(uniqueStudiesInd, iOutputStudy) + % Destination study for new channel file + [tmp__, iChanStudyDest] = bst_getContext('ChannelForStudy', iOutputStudy); + % Source channel files studies + [tmp__, iChanStudySrc] = bst_getContext('ChannelForStudy', uniqueStudiesInd); + % If target study has no channel file: create a new one by combination of the others + isNewChannelFile = db_combineChannelFiles(unique(iChanStudySrc), iChanStudyDest, [], ~OPTIONS.isData); + % Reload target study if it changed (new channel file) + if isNewChannelFile + sOutputStudy = bst_getContext('Study', iOutputStudy); + tree_updateModel(); + end + end +end + + +%% ===== BUILD DATA ARRAY ===== +function [X, ChannelFlag, Time] = buildDataArray(sampleFiles, iTime) + % Read out the data from multiple files and store in larger array X + % Parameters: + % - sampleFiles : cell array of data file names + % - Time : time values of the samples to extract + % - isData : a flag; 0 means read from Results file, 1 from surface data file + + % Open progress bar + isNewProgressBar = ~bst_progressBar('isvisible'); + if isNewProgressBar + bst_progressBar('start', 'Loading samples...', 'Processes', 0, length(sampleFiles)); + end + ChannelFlag = []; + + % Process all the samples files + for k = 1:length(sampleFiles) + % Progress bar + bst_progressBar('inc', 1); + bst_progressBar('text', ['File: ' sampleFiles{k}]); + % Read matrix + [matValues, matName, tmpChannelFlag, TimeVector] = bst_readMatrixInFile(sampleFiles{k}, iTime); + % Get time for the specified indices + % Bug: Time = TimeVector(iTime); + Time = TimeVector; + % Add bad channels to list of bad channels + if isempty(ChannelFlag) + ChannelFlag = tmpChannelFlag; + else + ChannelFlag(tmpChannelFlag == -1) = -1; + end + % Initialize large array + if (k == 1) + X = zeros(length(sampleFiles), size(matValues,1), size(matValues,2)); + end + + % Store read values in full data array + try + X(k,:,:) = matValues; + catch + error('Please first check that all the files in your samples have the same of channels/sources. Consider registering the MEG/EEG data to the same sensor cap'); + end + end + + if isNewProgressBar + bst_progressBar('stop'); + end +end diff --git a/brainstorm3/tvalue_paired.mexglx b/brainstorm3/tvalue_paired.mexglx new file mode 100755 index 0000000000000000000000000000000000000000..fab8b6d24a04523ff322b45fa02014ea40df97f9 GIT binary patch literal 7442 zcmb_h4{%&X8Q;5HA<)o27@)LO9!^G!=uKJzwEU5@={2?{CAPU1EpS|NuernIF5JDV zX{VtH*I>^_urm}vr#czYF{2%)4xOPYXhI6141*n}LV~>-omQ@<)~E=y7US=?e=gTj z)N%Z7zWesK`|Y>i?%QwQzTItsx)mOeM;Iy*C4y-0MM6|TcFk5LRl*X}!~*eAF;|uw zeaem*il-6G60%(8eGK_z7~BemNm=fN*oitjQAU;(bh!_(;kXm=q1^C%EZ^+E(~@l2JLc;!jFJS#@-zY z3QE@Brty6Ai)gxjj?#RHMYeTp;eng zYwVU-%CVC*t>IM4PKh?VV?{dN?8FjrrUS`jeX6Cg!x3#AD^tr8>84g2$y(c4EpuuT zZSAdg2UXXHlktKs6zZW+leEK*t*X_Bon)*dbaQ2;GSHBe7S^_hlPPU;RkJRx54W}^ znrXl!U~pwpmZly^!W+9|ot;e4%hp&^TZccDkO{kx3Ognb(ti_6Q%2tEn8;(4X!jT;7NTJGc;;kj z{yMmj-^age(AdEtG5IuCtI4O>2h!wIT)Co5o*0`1J;f19C3rhd{aalh^r-L z)#zQsl@i}Zd>^qTaW!$2c$UP=iQ9>V#55i4AfEW=^4ws#>!ss!0JaY}m%E+iZmo|w7s;G7D(ur0oZ<@4ybsHP{%lVN z8bz(lurJ$rlJ=I#>QA72Nub|B{lmI`73;(P#Wc_I?E~rAZt$e?;9;3T{cl}J{|&bH zVnO>Q!r>(^Qor<+>f~YM4KJ@K<+!?VZFz-n*jItT5cz{Ba)V`AZ=u6);o)TidkO~M zka+KKV1N}kR_TZvD9;R(W_z}>i|kn`J-;2cFjSwO%K5ysZpyz6OT&SE^a9o>#^<_^ zOB-^i+hj}obW89`^?#kr-tWo*9ULoRQ{R<&UFejLy;5Z?_@*q_p6TotGatPb9`~02 zoh@a92i(A(Y~XNj`TGjL8nbfVk>jHSBL|NT<%CYXaP;8O!OWm1 z^L!~?c{Tl;+>zXyqc7)r=<(4rTK;47;L+!G`t*p~fLU;K@JM=x8+gSHcA@P|;F!k^ z9Pk7VyTO+{!9(zkGje9nJ2St22`r7CIxz==&)O;f1znQk`Sl3K^F$y1+~D?X@Yzh~ zfN<_*QST;<_E%ZZxi9-%E9%NxZfF1CTP5TE99ru-QigW6AMs7(=1*WOJ*Srh`_soW zFM2XPtWdfn-9K|X`|tJ=%fTFfVQMbjd4}_HmyAO>-F_i6o^)#^8k1Jg>=`$(tG9f@^|+nS_WFji(v6(gRob0!c-WPB zM?`K8{T%dR;N9n>vB2&n&VJuE3}xQq2J`I8X`*l)+a zv*jXZc5YE%nVrTwWr?Db>bE8K(`{ax`gC;Nr57vx7J7r@819zT{Sp^&UM+zoJ zicO?{*?oY^-iCWa_Y7ej%yZw}Jy?b}q}$DP|DGYYOfQqg>-oC1`R6c--9x{}JGD16 z2F>1vvu^d-%zKm0%A!8bSK2OqXY4_>{4x0SMZ76iCmhR;C(0Qz)s{{#2)x-Fi%PSp*!ByF2U8{{6%N`<)z zO{v0YcoR%VZRXn6l!?|@94ZT9e%o&)no@RhQ<$6F)OB)UFO*XH{X3 zzsOotS-H6K=E^E-!5TYaeJ<>%{LfTXVa}YRy~w}l|B~|`TlJ?lw>jY^;ZHe9C8H)4 z4X2{QAK4s-ijq!J_*>#>|0eyRR189qbIL+cP1>!jt`hC7j_}9gG5k9=zW)69XpEsK z5eYkC;kToqjY-5;C>lXcgM`1?NhDKfU5WdfQOF9?b}}K|3b)0Y(RKnh@#mKfDWgp( zRBcYQagq7&XT?0I;&ID)%6U!h(-=a|cT6{NEtqqgbCUC&+yLergphii_goK{M?J;| znQ>vrBo`vX%xTVN@~4o;dL&BnXTc^G7#HNl$TMvKN~*^{V}*4XFXU>Z&)2&fd6$5x z$M_>N{)kC{G7fD>qsMq9cOj2@jCG?IN1A;?D9Ck!YXxK65cNngu+iHE9r7$Q7$~D2 znUBus@vKaqm(jQ;jxlKq*ywRxB6Hm|WZLaQ2K8kBxQKzd-Wf7_JCI@YiqFT+*ZW41 zo;5=V@?Ay=(%9?MdK`awRtG(~0S6`-y&oXWcB$8eb)@S<^nP5Vw*z`Rpl58LsE~fT zNRQv`k9>&U0Q5MlY*(HaLXVCRlW3d%9t0cz_`Saqhi-f)7@{8eRj|?Hy1yHGbw)|+ zQ16W*J+7U3=v`=Ba?oQw{h5H?1oT+nkV*d@8En@O^}c5Sq$%V3#`KNFyD*W)nb2hP zC`~M|s1z^RJ~YO;brYEFat<;!itY8!#k--XqOk|H4-nTFk~$GqYl*_j0M`tKRSR4j z6jlkY0SY6CK#X-#iF~3erWMAy2Z*stD$yg&7*&|PP_d{mBU{Cs!d&rGY$>dtJmUyd zB6kHC6V4rpRe~|0Fdhbo^Pg0rPaHYV73NdHTrObl2GE}Z=B_}WWkZ(eT@&XasYISe zt9^#Taz`ZJ$qK8Li1SBb?w+QoL|iTB2iF*q7c0E-NMRq&eEHms{~q|yHQM;^fi~A- z!uk-ZJTY@hSNBj(66R-T`cJXk3ofUH)J>4NQdqI|0!54Qg`V7cFr^=FGG zARRs49x+pVAGp|`p8)UGkB$8s1~$b0F&+&O9|Pd{bIN$ID?16vwUy*W31Y#>T(n8XJ2P8rN$5cY%w~_`O&QiqH6G=(WMvo2&61y8b6MHfQ|R8k;lzyU)`X z%{U@2I_4M6`0{en8#nr!ao%6kc;}i>-O9C%^7McCIcQZ)QBfojYH3Y0g8ZBleo?k{ z?x{PD{_PIEZ$&@M4whFJF%hb*yK~vny3m~~R;&#)h8mYHs|)aMBa%vlqTzT1Hx!`| zHk0bw1L@dJi*NF`*iNXuIpjps@h|(EJ4DEiNAM@No)=PSg39tQu3lQdvPOjDPFP-h z$Xg3}{c--?$^|Y{cx%9Wlfo^@)Ex?MF{-Q0l>)Cs{@tAijTB#$;BIB=0tCsaD;r+T bm}``CuWvr&F6P`jp(&Sm%K^(pw>tj(nv-1) + error(['In AIMS mesh format, vertices indices start at 0 in' ... + ' the ''Faces'' array']) + end + if (min(faces(:)) == 1 && max(faces(:)) == nv) + warning(['In AIMS mesh format, vertices indices start at 0 in' ... + ' the ''Faces'' array']) + end + + if nargin < 4 + nn=0; + % normals=normalsvertices(vertices, faces); + % nn=size(normals, 1); + else + nn=size(normals, 1); + end + + % By default, we are working in the AIMS referential, which is indirect. + if nargin<5 + indirect_referential = 1; + end + + if indirect_referential + faces=fliplr(faces); + end + + [ext,ext]=fileparts(filemesh); + + fid = fopen(filemesh ,'w') ; + + fprintf(fid, 'ascii\n'); + fprintf(fid, 'VOID\n'); + fprintf(fid, '%d\n',size(vertices,2)); + fprintf(fid, '%d\n',nt); + + for t=1:nt + % Instant in time (an integer !) + fprintf(fid, '%d\n',t-1); + + % VERTICES + fprintf(fid, '%d\n',nv); + for i = 1:nv + fprintf(fid, '(%1.4g,%1.4g,%1.4g)\n',vertices(i,1:3,t)); + end + + % NORMALS + fprintf(fid, '%d\n',nn); + for i = 1:nn + fprintf(fid, '(%1.4g,%1.4g,%1.4g)\n',normals(i,1:3,t)); + end + + % The following line HAS TO be here !!! + % There is no texture + fprintf(fid, '0\n\n'); + + % FACES + fprintf(fid, '%d\n',nf); + for i = 1:nf + fprintf(fid, '(%d,%d,%d)\n',faces(i,1:3,t)); + end + end + fclose(fid); + diff --git a/brodmann/MNITemplate_brodmann.m b/brodmann/MNITemplate_brodmann.m new file mode 100644 index 0000000..b36dd82 --- /dev/null +++ b/brodmann/MNITemplate_brodmann.m @@ -0,0 +1,195 @@ +mypath + +addpath(fullfile(HOMEDIR, 'mtoolbox/brainstorm/Toolbox')) +addpath(fullfile(HOMEDIR, 'mtoolbox/spm2')) +addpath(fullfile(HOMEDIR, 'matlab/')) +addpath(fullfile(HOMEDIR, 'matlab/ctf2tal/')) +addpath(fullfile(HOMEDIR, 'matlab/stormvisa')) +addpath(fullfile(HOMEDIR, 'matlab/brodmann')) +try + addpath(fullfile(USBDIR, 'matlab/')) + addpath(fullfile(USBDIR, 'matlab/ctf2tal/')) + addpath(fullfile(USBDIR, 'matlab/stormvisa')) + addpath(fullfile(USBDIR, 'matlab/brodmann')) +end + +load(fullfile(HOMEDIR, 'mtoolbox/brainstorm/MNITemplate/MNI_braincereb_small_tess_CTF')) +fv.vertices=Vertices{end}'; +try + fv.vertconn=VertConn{end}; +catch +end +clear Vertices; +fv.faces=Faces{end}; +clear Faces; +clear VertConn +clear tess2mri_interp +% try +% load(fullfile(USBDIR, 'data/subjects/montreal_subject/TalairachMRIscs.mat'), 'TAL') +% S(1)=load(fullfile(HOMEDIR, 'data/subjects/montreal_subject/montreal_subjectimage.mat'), 'SCS') +% S(2).SCS=S(1).SCS(1); +% S(3)=load(fullfile(HOMEDIR, 'mtoolbox/brainstorm/Phantom/montreal_subject/montreal_subjectimage_info.mat'), 'SCS') +% S(4).SCS=S(3).SCS(1); +% +% catch +% end + +S=load(fullfile(HOMEDIR, 'mtoolbox/brainstorm/MNITemplate/MNI_subjectimage'), 'talCS', 'SCS') + + +if not(exist('xyz2')) + DECIMATION=1000 + y=spm_vol(fullfile(HOMEDIR, 'data', 'subjects', 'colin27','aal.img')) + %y=spm_vol(fullfile(HOMEDIR, 'data', 'subjects', 'colin27','brodmann.img')) + v=spm_read_vols(y); + % Undo L-R flipping + if spm_flip_analyze_images + y.mat(1:5:11)=y.private.hdr.dime.pixdim(2:4); + y.mat(1,4)=-y.mat(1,4); + end + ref=v(v>0); + try + [ign,labels,ign]=textread(strrep(y.fname,'img', 'txt'), '%f%s%f'); + catch + labels=cellstr(num2str([1:max(ref)]')); + end + % in MRI + [xyz(:,1),xyz(:,2),xyz(:,3)]=ind2sub(size(v), find(v>0)); + xyz2=xyz(1:DECIMATION:end,:); + ref2=ref(1:DECIMATION:end); + % in MNI + xyz2=[xyz2 ones(size(xyz2,1),1)]*y.mat'; + xyz2=xyz2(:,1:3); + + fv2.vertices=fv.vertices; + + % I dunno why but brodmann from MRIcro is not centered?!? + % xyz2=xyz2-repmat([-3 10 -10], [size(xyz2,1) 1]); + fv2.vertices=fv2.vertices+repmat([0 10 -8],size(fv2.vertices,1),1); +end +plot3d([xyz2]/1000) + +% fv2.vertices=scs2mri(S(2),fv.vertices'*1000)'-repmat([92 128 74],[10001,1]); +fv2.faces=fv.faces; + +% fv2.vertices=scs2mri(S(end),fv.vertices'*1000); +% [ignore,fv2.vertices]=mri2scs(TAL,fv2.vertices); + +% Apply Talairach nomalization to vertex locations +%1)Load Talairach fiducials and bounding box from MRI file +% 2) Apply global Talairach transform +[fv2.vertices] = scs2tal(S.talCS.scsCubeFiducial/1000, S.talCS.cortexTess,fv.vertices', S.talCS.Type); + +fv2.vertices=fv2.vertices'*1000; +v2=fv2.vertices; + +figure(1) +clf +view_cortex(fv2.faces,v2,'FaceColor','none', 'EdgeColor', 'k' , 'FaceAlpha', .5) +hold on +view_cortex(fv2.faces,mni2tal(v2), 'FaceColor','none', 'EdgeColor', 'g') +legend({'orig','tal', 'mni2tal'},0) + +view(90,0) +rotate3d +drawnow + + +return + +disp('Lecture des fichiers Analyze') +apc=[0 0 0; 0 -23 0; 0 0 50]; +verttal = ctf2tal(v2, [], 'ACPCIH_ctf', [apc], 'brainvert', v2); +view_cortex(fv2.faces,verttal, 'FaceColor','none', 'EdgeColor', 'r') + +% Same story for new (target)tessellation +load(newTess,'Faces','Vertices') +newFaces = Faces{iGrid}; +newVertices = Vertices{iGrid}; + +MRI = load(newMRI,'talCS'); +[newVertices] = scs2tal(MRI.talCS.scsCubeFiducial/1000, MRI.talCS.cortexTess,newVertices, MRI.talCS.Type); + +return + + + + +% r=labelize(v2, xyz2, ref2, 'TimeBar', 'on'); + +return +labelviewer(struct('vertices', v2, 'faces', fv.faces), r, xyz2, ref2, labels) + + +r=ref2(closest(v2,xyz2)); + +if 0 + load(fullfile(HOMEDIR, 'matlab/ctf2tal/TTareas.mat')); + xyz=xyz(1:10:end,:); + ref=ref(1:10:end); + ref=double(ref); + xyz=ctf2tal(xyz, [], 'brainvert', xyz, 'CA', [0 0 0], 'CP', [0 -23 0], 'IH' ,[0 0 50]); + r=labelize(verttal, xyz2, ref, 'TimeBar', 'on'); + talairach.vertices=verttal; + talairach.faces=fv.faces; +end + +return + +save(fullfile(SCOUTDIR,date,[date '_brodmann.mat']), 'r','talairach', 'xyz', 'ref', 'labels' ) + +disp('Sauvegarde dans un fichier CorticalScout') + +scoutfile=dir(fullfile(RESDIR,date, [date '*CorticalScout_2.mat'])); +scouts=load(fullfile(RESDIR,date, scoutfile.name)); + +BA=strmatch('Brodmann', labels); + +SIDES=['R', 'L']; +for side=0:1 +for i=1:length(BA) + vba=find(r==BA(i) & left==side); + scouts.CorticalScouts.CorticalMarkersLabels{i+42*side}=[labels{BA(i)} '-' SIDES(side+1)]; + if not(isempty(vba)) + scouts.CorticalScouts.CorticalSpots(i+42*side)=vba(1); + scouts.CorticalScouts.CorticalMarkers(i+42*side,:) = v_ctf(vba(1),:); +else + scouts.CorticalScouts.CorticalSpots(i+42*side)=NaN; + scouts.CorticalScouts.CorticalMarkers(i+42*side,:) = NaN; +end + scouts.CorticalScouts.CorticalProbePatches{i+42*side}=vba; + scouts.CorticalScouts.CorticalProbeDepth(i+42*side)=0; + scouts.CorticalScouts.CorticalProbePatchesXYZ{i+42*side}=v_ctf(scouts.CorticalScouts.CorticalProbePatches{i},:)'; +end +end +bascoutfile=strrep(scoutfile.name, '_2.mat', '_BA.mat') + CorticalScouts=scouts.CorticalScouts; + ResultFile=scouts.ResultFile; + ActiveTess=scouts.ActiveTess; + save(fullfile(SCOUTDIR,date,bascoutfile), 'ResultFile','ActiveTess','CorticalScouts') + +return + + + +cmap=hsv(69); +hf=figure; +p=patch('vertices', fv.vertices, 'faces',fv.faces, ... + 'CData', r, 'CDataMapping', 'direct', ... + 'EdgeColor', 'none', 'FaceColor', 'flat'); +colormap(cmap) +h=colorbar; +axis image +light('position' , [-1 1 0]) +light('position' , [ 0 0 1]) +light('position' , [ 1 -1 -1]) +light('position' , [ 1 1 0]) +l=findobj(hf, 'type', 'light'); +set(l, 'color', .8*ones(1,3)) +set(h, 'YDir', 'reverse'); +set(h, 'position', [0.8314 0.1100 0.02 0.8150]) +view(3) +rotate3d +lighting gouraud + +return diff --git a/brodmann/MNI_brodmann.m b/brodmann/MNI_brodmann.m new file mode 100644 index 0000000..9c35a30 --- /dev/null +++ b/brodmann/MNI_brodmann.m @@ -0,0 +1,80 @@ +mypath +addpath(fullfile(HOMEDIR, 'mtoolbox/brainstorm/Toolbox')) +addpath(fullfile(HOMEDIR, 'mtoolbox/spm2')) +addpath((MATLABDIR)) +addpath(fullfile(MATLABDIR, 'ctf2tal')) +addpath(fullfile(MATLABDIR, 'stormvisa')) +addpath(fullfile(MATLABDIR, 'brodmann')) + + +try + load(fullfile(USBDIR, 'data/subjects/montreal_subject/TalairachMRIscs.mat'), 'TAL') + S(1)=load(fullfile(HOMEDIR, 'data/subjects/montreal_subject/montreal_subjectimage.mat'), 'SCS') + S(2).SCS=S(1).SCS(1); + S(3)=load(fullfile(HOMEDIR, 'mtoolbox/brainstorm/Phantom/montreal_subject/montreal_subjectimage_info.mat'), 'SCS') + S(4).SCS=S(3).SCS(1); +catch +end + + +if not(exist('xyz2')) + DECIMATION=1000 +% y=spm_vol(fullfile(HOMEDIR, 'data', 'subjects', 'colin27','aal.img')) + y=spm_vol(fullfile(HOMEDIR, 'data', 'subjects', 'colin27','brodmann.img')) + vol=spm_read_vols(y); + % Undo L-R flipping + if spm_flip_analyze_images + y.mat(1:5:11)=y.private.hdr.dime.pixdim(2:4); + y.mat(1,4)=-y.mat(1,4); + end + ref=vol(vol>0); + try + [ign,labels,ign]=textread(strrep(y.fname,'img', 'txt'), '%f%s%f'); + catch + labels=cellstr(num2str([1:max(ref)]')); + end + % in MRI + [xyz(:,1),xyz(:,2),xyz(:,3)]=ind2sub(size(vol), find(vol>0)); + xyz2=xyz(1:DECIMATION:end,:); + ref2=ref(1:DECIMATION:end); + % in MNI + xyz2=[xyz2 ones(size(xyz2,1),1)]*y.mat'; + xyz2=xyz2(:,1:3); + + % I dunno why but brodmann from MRIcro is not centered?!? + % xyz2=xyz2-repmat([-3 10 -10], [size(xyz2,1) 1]); + v2=fv2.vertices+repmat([0 10 -8],10001,1); + +end + +% +% tessfile='d:\ndiaye\data\subjects\MNI\MNI_LhemiMNI_Rhemi_10000V_tess.mat' +% fv=tess2patch(tessfile); +% % in mm: +% fv.vertices=fv.vertices*1000; +% % Re-center +% % Z-flip: +% fv.vertices(:,3)=-fv.vertices(:,3); +% % Y (antero-post) flip +% fv.vertices(:,2)=-fv.vertices(:,2); +% fv.vertices=fv.vertices+repmat(y.mat(1:3,4)', [size(fv.vertices,1),1]); +% delete(findTessellationHandles);view_cortex(fv);pause + +s=load('MNI_subjectimage.mat', 'talCS', 'SCS') +tessfile='MNI_braincereb_small_tess_CTF.mat' +fv=tess2patch(tessfile); +v=scs2tal(s.talCS.scsCubeFiducial/1000, s.talCS.cortexTess, fv.vertices', 'mnicereb'); +view_cortex(fv.faces, v*1000) + +return + +fv2.faces=fv.faces; +% fv2.vertconn=fv.vertconn; +fv2.vertices=scs2mri(S(end),fv.vertices'*1000); +[ignore,fv2.vertices]=mri2scs(TAL,fv2.vertices); +fv2.vertices=fv2.vertices'; +v2=fv2.vertices; + +disp('Lecture des fichiers Analyze') +apc=[0 0 0; 0 -23 0; 0 0 50]; +verttal = ctf2tal(v2, [], 'ACPCIH_ctf', [apc], 'brainvert', v2); diff --git a/brodmann/MNI_brodmann_karimovitch.m b/brodmann/MNI_brodmann_karimovitch.m new file mode 100644 index 0000000..a3cb29a --- /dev/null +++ b/brodmann/MNI_brodmann_karimovitch.m @@ -0,0 +1,170 @@ +% Transpose, Flip, etc so that Left is on Left +% and all the rest is okay +% TessFile='D:\ndiaye\data\subjects\MNI_4_Yann\MNI_brain_NoCollosum_12000V_tess.mat'; +% fv=tess2patch(TessFile); +% v=(fv.vertices*diag([1 -1 -1])+repmat([-90e-3 217e-3-126e-3 181e-3-72e-3], size(fv.vertices,1),1))*diag([-1 1 1])*1000; +% view_cortex(fv.faces, v) +% image_spm(y,vol); view(2); hold on +% h=findTessellationHandles +% set(h, 'visible', 'off') + + +clear +spm_defaults + +switch(1) + case 1 + TessFile='D:\ndiaye\data\subjects\MNI_4_Yann\MNI_brain_NoCollosum_12000V_tess_corrected.mat'; + fv=tess2patch(TessFile); + % Note that the right hemi at the occipital pole viewed from above goes towards the left + case 2 + % TessFile='D:\ndiaye\data\subjects\MNI_4_Yann\MNI_brain_tess.mat'; + + fv=tess2patch(TessFile); + % In MNI coordinates: + fv.vertices=(fv.vertices*diag([1 -1 -1])+repmat([-90e-3 217e-3-126e-3 181e-3-72e-3], size(fv.vertices,1),1))*diag([-1 1 1])*1000; + case 3 + TessFile=uigetpathfile + fv=tess2patch(TessFile); + % In MNI coordinates: + fv.vertices=(fv.vertices*diag([1 -1 -1])+repmat([-90e-3 217e-3-126e-3 181e-3-72e-3], size(fv.vertices,1),1))*diag([-1 1 1])*1000; +end + +AtlasMRI='d:/ndiaye/data/subjects/colin27/brodmann.img'; +%AtlasMRI='d:/ndiaye/data/subjects/colin27/aal.img'; +ColinMRI='d:/ndiaye/data/subjects/colin27/colin.img'; + +% Note: the right occipital pole of the single subject MNI (colin27) goes a +% little on the left side +if not(exist('xyz')) +% y=spm_vol(fullfile(HOMEDIR, 'data', 'subjects', 'colin27','aal.img')) + y=spm_vol(AtlasMRI) + vol=spm_read_vols(y); + % Undo L-R flipping + if spm_flip_analyze_images + y.mat(1:5:11)=y.private.hdr.dime.pixdim(2:4); + y.mat(1,4)=-y.mat(1,4); + end + + ref=vol(vol>0); + [xyz(:,1),xyz(:,2),xyz(:,3)]=ind2sub(size(vol), find(vol>0)); + % MRI to MNI coord. + xyz=[xyz ones(size(xyz,1),1)]; + xyz=xyz*y.mat'; + xyz=xyz(:,1:3); + + try + [ign,labels,ign]=textread(strrep(y.fname,'img', 'txt'), '%f%s%f'); + catch + labels=cellstr(num2str([1:max(ref)]')); + end + + % Import + yc=spm_vol(ColinMRI); + if spm_flip_analyze_images + yc.mat(1,:)=-yc.mat(1,:); + end + volc=spm_read_vols(yc); + +end + +if 0 + if not(exist('xyz2')) + DECIMATION=1; + xyz2=xyz(1:DECIMATION:end,:); + ref2=ref(1:DECIMATION:end); + end +end + + +figure(1); +clf +z=40; dz=2; +k=fv.vertices(:,3)z-dz; +p=plot3d(fv.vertices(k,:) ,'.'); +view(2);hold on; +% set(hp, 'FaceAlpha', 'interp');set(hp, 'FaceVertexAlphaData', k+(1-k)*0); +n=100; +colormap([ subarray(colorcube(n), randperm(n)) ; gray(100)]); +colorbar +hc=img_slice(yc,volc./max(volc(:))*100+n+1, [NaN NaN z]); +set(hc, 'CDataMapping', 'direct', 'FaceAlpha', 1) +hi=img_slice(y,[], [NaN NaN z]); +set(hi, 'CDataMapping', 'direct', 'AlphaData', logical(get(hi, 'CData'))*.51) + +return + +if 0 + % Make AAL for all vertices + fv.r=ref(nearest(fv.vertices, xyz)); +elseif BRODMANN + % Brodmann only for cerebral matter + r=zeros(size(fv.vertices,1), 1) + k=load(TessFile, 'Lhemi', 'Rhemi', 'Cerebellum') + r([ k.Rhemi ; k.Lhemi])=ref(nearest(fv.vertices([ k.Rhemi ; k.Lhemi]), xyz)); +end + + + +return + +% ========================================================================= +% ========================================================================= +% ========================================================================= + +mypath +addpath(fullfile(HOMEDIR, 'mtoolbox/brainstorm/Toolbox')) +addpath(fullfile(HOMEDIR, 'mtoolbox/spm2')) +addpath((MATLABDIR)) +addpath(fullfile(MATLABDIR, 'ctf2tal')) +addpath(fullfile(MATLABDIR, 'stormvisa')) +addpath(fullfile(MATLABDIR, 'brodmann')) + +AtlasMRI='d:/ndiaye/data/subjects/colin27/brodmann.img'; +AtlasMRI='d:/ndiaye/data/subjects/colin27/aal.img'; + +MNISubjectImage=fullfile(USBDIR,'/mtoolbox/brainstorm/MNItemplate','MNI_subjectimage.mat') +tessfile=fullfile(USBDIR,'/mtoolbox/brainstorm/MNItemplate','MNI_braincereb_small_tess_CTF.mat') + +% Note: the right occipital pole of the single subject MNI (colin27) goes a +% little on the left side +if not(exist('xyz')) +% y=spm_vol(fullfile(HOMEDIR, 'data', 'subjects', 'colin27','aal.img')) + y=spm_vol(AtlasMRI) + vol=spm_read_vols(y); + % Undo L-R flipping + if spm_flip_analyze_images + y.mat(1:5:11)=y.private.hdr.dime.pixdim(2:4); + y.mat(1,4)=-y.mat(1,4); + end + ref=vol(vol>0); + try + [ign,labels,ign]=textread(strrep(y.fname,'img', 'txt'), '%f%s%f'); + catch + labels=cellstr(num2str([1:max(ref)]')); + end + % in MRI + [xyz(:,1),xyz(:,2),xyz(:,3)]=ind2sub(size(vol), find(vol>0)); +end +if not(exist('xyz2')) + DECIMATION=1; + xyz2=xyz(1:DECIMATION:end,:); + ref2=ref(1:DECIMATION:end); + % in MNI + xyz2=[xyz2 ones(size(xyz2,1),1)]*y.mat'; + xyz2=xyz2(:,1:3); +end + +return + +s=load(MNISubjectImage, 'talCS', 'SCS') +fv=tess2patch(tessfile); + +v=scs2tal(s.talCS.scsCubeFiducial/1000, s.talCS.cortexTess, fv.vertices', 'mnicereb'); + +return + +view_cortex(fv.faces, v*1000) +r=ref2(closest(v'*1000, xyz2)); % labelize(v'*1000, xyz2,ref2, 'Timebar', 'on') +labelviewer(fv, r, xyz2, ref2, labels) + diff --git a/brodmann/MNI_karimovitch.m b/brodmann/MNI_karimovitch.m new file mode 100644 index 0000000..8208687 --- /dev/null +++ b/brodmann/MNI_karimovitch.m @@ -0,0 +1,42 @@ + +addpath(fullfile(HOMEDIR, 'mtoolbox/brainstorm/Toolbox')) +addpath(fullfile(HOMEDIR, 'mtoolbox/spm2')) +addpath((MATLABDIR)) +addpath(fullfile(MATLABDIR, 'ctf2tal')) +addpath(fullfile(MATLABDIR, 'stormvisa')) +addpath(fullfile(MATLABDIR, 'brodmann')) + +if not(exist('xyz2')) + DECIMATION=1000 +% y=spm_vol(fullfile(HOMEDIR, 'data', 'subjects', 'colin27','aal.img')) + y=spm_vol('brodmann.img') + vol=spm_read_vols(y); + % Undo L-R flipping + if spm_flip_analyze_images + y.mat(1:5:11)=y.private.hdr.dime.pixdim(2:4); + y.mat(1,4)=-y.mat(1,4); + end + ref=vol(vol>0); + try + [ign,labels,ign]=textread(strrep(y.fname,'img', 'txt'), '%f%s%f'); + catch + labels=cellstr(num2str([1:max(ref)]')); + end + % in MRI + [xyz(:,1),xyz(:,2),xyz(:,3)]=ind2sub(size(vol), find(vol>0)); + xyz2=xyz(1:DECIMATION:end,:); + ref2=ref(1:DECIMATION:end); + % in MNI + xyz2=[xyz2 ones(size(xyz2,1),1)]*y.mat'; + xyz2=xyz2(:,1:3); +end + + +s=load('MNI_subjectimage.mat', 'talCS', 'SCS') +tessfile='MNI_braincereb_small_tess_CTF.mat' +fv=tess2patch(tessfile); +v=scs2tal(s.talCS.scsCubeFiducial/1000, s.talCS.cortexTess, fv.vertices', 'mnicereb'); +view_cortex(fv.faces, v*1000) +r=labelize(v'*1000, xyz2,ref2, 'Timebar', 'on') +labelviewer(fv, r, xyz2, ref2, labels) + diff --git a/brodmann/TTareas2TTgyri.m b/brodmann/TTareas2TTgyri.m new file mode 100644 index 0000000..cd12985 --- /dev/null +++ b/brodmann/TTareas2TTgyri.m @@ -0,0 +1,4 @@ +function []=f(tta,ttg) +% index of ttareas into ttgyri +[tf,loc]=ismember(tta,ttg,'rows'); + diff --git a/brodmann/TTbasta.mat b/brodmann/TTbasta.mat new file mode 100644 index 0000000000000000000000000000000000000000..974d2164392e4eb44310d93c3e254cad7896d972 GIT binary patch literal 1992496 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%0A-I1-m(A!mgHuUS-qiHUgm z-2*x#K@E%gETj~K@`oQDuIF)Tv~N|Ab1|TttQ4)xVmRWMOd!3c>Ks}|-=gig^Q8Z! zbLh(=kv#pGV>@A$et6sI4jJ72yb`f4rGg}6sqC9~jvD8+!kGm%)Y(MKO_nP=wXlq` zS;j_q8+M2T1Os(B5=zu)1tw-J#{g!hK??(i3~BF$-IT#shE)wi7cT;O39-9XQ^arR z@W+7t0%^au#Xyt}V+8)Hw*AsMT}bo#8^&@dfi+_OwABjX!Xj?)T@EKAWY|j!hRix1 z>H>kXhyKy2uFnT00f#y@OgglB0SQq|(PL1@J3fL`X{EC2p)IX&522a3Gf(MkjLm{2 zl!in^j2n>Qhlz_|e(0oG>GnYI6;dD*bzVn@6b|>$*Y08_z&w6r(iMsV%N^R1Eazr8 zH)}#=)I1_90$nLV9-PvJAEPmy$i^LZ@~5nvr#G2wHhioyn6iDCPiXm|_9znIIwxEk zp=$V|w4p56!t@(o({9$vbn+^q;KSU(7>$gB@FEU5tX~LshS3@6l&bQP%sC0UNfF5~ zuIgerz>yS!A1ug)Egj5sxfEEkL)s&{>X^fvL`LsPkY%L}r9qho3lT}GRkjBcQ1BS) zPmRFiNkwq}Fq0VI^ie+1fIIAUodG2ph}#k9p*oc+>PO*G zoe;f-3br)IYf0wHPH#i#Y^j!cBJ5W=kf4{v5D0k4rQR6jMBdz^nh%`djN6WEn z)pdFev;f{+8t7hN7u4tBIiE*sx$O669QhnqONOW4er^v;q2h5e5vG)G(P-b@`FVJj zMaR0;!t0HEu4}^T>c!#@z6xORr*r+FeBlqbB!~DFe3RWFgS+ocBs5e$56`h=^?*Gd ze*FcW0_dagvOf>c_Dmg*NcgSO9&bjcy8bIEo`B5F$0s-Gh$lCb$mdG$hhmCC7>|E7 zsN)X~g*!h^I*g<|>GqqOX}LE}?T7UQ>G1;Pq$6=KYmzk?YS%#JBqIqzcAlOuH*#yX z_-@PO?&KEx5HKbVDC21L9<(tgR+bpztWoNar_zOA0gst}9-iy!osYHsT$ff{?qcvE zt=lIC?=QRH4W>0o8DrjAxP2DWSTCWj>IiakB!$pJTF4@7>3klZ*8^-n=i1{?JTlqZ z%44aS@(^23#Q~mB#o4vN^Hn~CiK2iK(=a@ZOXw?me|mwU)A) zdUF;`fCAMUd4{WNGjDbS>4p2kIo)49T!(hw6`n#(W=G}{F`eD_sUcg)l*;GfIo9#> z@Qh!sKReHSL_~|<;qfcHKg!if5stY+ni@hbGt7K?gj|}B52y+$5VG_1DnUr$a1VW& zf4B)i-Z*Mze6CG_@RqyD&GBH-tyzds69bo%OdWO+J%<|ODRDHW6WK7=e4@&k)Zq6lPvR7g!+=nm%_{#7%l+k#P_n z8L}c@k#t5nrM-?e{g=>Q9xi2$d>)?jY17ulpKa`l)WfSjY0jr%ZW0?M(6;*WSo(Q* z=5u1Ug>^j_3V1|3`cY{nA;1p{nM@9;kh|DR=+0lx8E9YgQL+-U-Ty8 z8E5Dlbo`>a^zV3#E+D%P1iDxI9rPW$1TPbuh^aDti@coPo%HJ6ps(;wd?baV#Bav` z3U?O>TI&`m#_*inxVBCPAF8kLwZg`p58*9r)}dY4sXcHR{#ob-2=vg2PDLDg)wwWi z$VpvTD6|_F3WH2w_bwC+84VX@u0^xViZ5ybRv*66*8|~StJuSvsTs#>11O^N})uBApwbV=t#Qa{9IFyq;dDUNwDg`K>#V(nD%vf#OSdG7p^! zCv~hQ;#`z>PM(fWhDy027G4%Mvfj7>F?68n9PmDKt-ZZmw<5x58mvK$j${{R(}lZ; z2Pu;d#sXV+;WI?)Z+L?zuCgcK?xr`}H>R@~)@>3jSqGRq-#xpKn-X7YoQnjRAu`iA zvUpnMF@ed7Oy&F>ZxWAKBiBxJvu?baBxxtj9>rSKOqvt#pHMRFk)&1-#QhrzRV|L z`B?%^GqM!qG$*)v5^LhG!Jg117seB8eOr%%fsK3MjQ=2uj{@njVAiFkqTGHTdLl9M zC=B%r?w8XCcXgIJM70cOmg zylxE035crTBu7<{^}@gWCA~FCRt>w*2y6yoqWq**fnl^}6KZN?{QT#SoQS~?InhQB z`RF)9rrls#7m^RjauPb*2Vw-9(CiJyi4*tX6rY*q_Ny_Y+%Z*g4lqEZyGsWEX_b}4 zqhnGa?#bbYv=ba`#|+>AC$>=sw3Q!pD+37Qs7#&+h!Idd7i}%f%I#gIFTHX#7VK+0 zVKu&y*X3$pt8(g^z?w8w35}a22rWsJE{ZXzOJKt$@I73jQmnN)wTgIPF1Mt~F)vx= z6k|qz_`~1Y@i%|-HwCRr@@IzzaDXB+cYAR?PNg`okk#5hxVB$l(n8G=eTC9=lngrr zklGY*^56b#&BrDblNthuehZ&$q;7}B&ts?W@lD|pKBxzMgh%%0ZVo15yhuG{7#~wG zdy05(&K`Udz{T_nic1d-oNNxKMA0K2&&C7I4z?Bg%J@To<4kBFJu6E8?ZC>ME^viX z)!Ih3azhIkFpnGF@Fd&DF`DQc^hFZ}CR8`BggxMk;|novc*I!(zwKyFm-{XFzpqab zW|mvRULyn(8!_0`3DzQM&_ttd9~2bmgIBZkP`~{xX&!;!c;f~C{A6C%_7PNmwslv- z-dv1%jeUZD<#*+T0FzERYYH9Y5qoW;_=NxFZ!X;5{x;Zu>-5lYSbRIsE21;wOMYyT zkWCVic&_p_VkPbdE(*!UYR0rCNqUjEY<=v+FG}-VP5s!;8*=I3YLCMd_5cJ&MXD99XU+qB9jsA;Gwg)4A`v6Sa6SJ*a!UTm9y8orw>a=Yif+boDOsaNn{GAVtHr$1bI`juh>QwZe>_E5}^vz ztB(YMQNPEG@fgk|CYE*nIl6ZXR0d)wl6u*+Cbsyj?N=%G5g7H>Q8hNK$Pq9g(2b

_W!G z=X56MmZOPSX zvo$f+74PA3jl*XYb$}r%6u6+mwHzi-O8ITt9d!dFR)VU)p1iDGL9W55^GH!ks_h-k zF?m8ohzApFHIJ-tl}_z}ZY)K!b!~w*5Y_@)SLQUK7jVcv!j{{_Yvc#)NVOC^YK*B^ zCZi6c#0_N^;#FZ$P>qC;3-k%jK01#^EF}X!pq$cLX%%=VqO+ky3a}w?j_eQDo>~LU za_zc7B*M-kfQ3}i7EI8*i6}}~ffV)(+7~%xN+ta%FubHr@f#E6nJ68l*Qrs@(s2wY z7QpBkAUp13rMCQ0`^h3tfK%q;VWlfvd2X&*S$!k0i;fHoG)r_u4Y^g?dxh1=8>f*q z_RIJpNWHE^R+fcI45r~ucVN&bWBAsS-my z1|=83+#RP(4%5?$4U02DAU+Y1nZjo&$ukLsHyNIl(K0Nha|zh3C?25DQj!CKXifoO z+VovoLeBOA#?T4$kpq3;1R*|-)({80tx+?E108j`eIPh+Aaj6zZSPn`z+R|&a4@4E zZ*+7x#-q+eHCwHT$({nr{$E0tN*O9wgKp5n(bZ?yTVe*W5W|a3dCE8-H;^tp38g@T z4z?WkmV@37&(xl-N0vGUMIGmChZmi}otaFpFc(Me$^dOY`ywGGy{i>On+wBk1Exj) zrJcapyTHfLL?69@?aqPof%emi=gEzxH(EX{F4PISRGY>%hY-s@Eq2ifV1rqZnI)Hf zJ3Uv177wv!)T8vX84GS*pGeOlt%)U9pWUoEZXbVL}zCNF)f1`dui-V>p#3_EXHW zWs_2r)R!L1UjySEtm&ti_pppy8*!r|XmvDM$O{PdHRF7Wd9(gj1w9ticH!$&%tI-n z4%92gVWki&k(57;f-JX<*T@gpAzKO_>?u@NmJIL5SuaX10Rshe%m%0d*%h_UW55_M z+XvJU)Zqmlis&rWsu2pXA&?o_AFe&M2AJhp)Iv&xoksu*siG~IAg$6hkgx(NjFC@J zUcu5yM5&}d1%{W@DSl(324N)!W2e`tpJLu9W3i&d^3$i7S1aL5;T0|}658oz0N`8DHH%&QVztz<4;G&O3*aG;}3w+{pd4rC6{ zuk9VH2-pjy&qMuqqoZcGV?63i)CRqPf_R79=u{oON6sVbi7{lm^8jx^SQDmR@#rvZ zanqzr+h5T+TZN?ktK$=#M!Dd8=zZBxFsYmTa{ye_?=ikdxT0^fy#Q}8zY8x3-?2V= zQMMsJo>}I;M6uC3CFIum3cX&@D>4R`W_Pa~6fX>NP5nyvB8X|dpqcABolf!#Hngv~ zio#2YU;U~+*Q}JZzeqhQ6tClNiGH2wh_|8WvzTNSp68EJ>KS~igaYaUE|3bS97FAX`UABk78Sl7sQ{o*3NkPcpWSWSOVZI}9S%NKd(YdX{kKAvU z%dwa9%Q@LBuQHNPYn)5+K<6bX>XrFb+ab z*GjWIx>Q=C6}EuW<(0~knE$T!pN2JoQ zG=;ieD?a_qE5CR{0sm1t07d!I;R5mkrQ=xMw3eEJn_tEu*aCCn6|^d&FqFXnCNHyn z0WWeSHh6^XZe{ci@8oklH4`3EHfB}*9ExxH0F0G>?wfd( zzPAF5s~~;(IFLBY#=(K7ZU#K5_dQFcB4enD8mA;|{%xuqm8kBnugvuq(-G|a9 zr*FZ-{TY$=t&$eaD6f<2&DDLI+4Ntzjg=#@1TJOMI$3 zjU>f6Qs_H9dbS*|lha(`%9o=TDb%kP3LBoDzyx=@l>mH*#1R05RXQSH#OTFUJVcO!R zNmsX4E3hYh`lW41S`Wwr=K~MmnSWCaj>4LRZ?jQqeVg$;!V_9kaNFvBm&^|szODBL zv}sv7DDcPw+eRc7-m;FSEaLES1OkUI|#zXUfBbsRZh6I-uo2=tCO< z_gnT2i7)8SL6Nx~f6@{pZja5KpMKO3QC77~FH_%7!fpUIL9ed<8## z+&4e_&XrTh$eFXy_CafIO|W<>TDLt4*L@Tmn;oV&#H^!Psr@LJQ>Bh_#J?De;a%QO8yKCeuV_E{cuZr`5A+jkoC}7xl{gDsr)JYiNr9zuTnDe_CVpID7uf zY7?d@{{)u_l2ZukwlHN7+?ccqQAWzXhx80Kw4|o-m;?9|I(pze%``#}cy*lDKb||w zS5(uO>^u26?PT-ch6j43x{Min{^wex7R$SnDqxx?36!^PBmybrt?Stap6Fh1wyg&= z!9*ugHz^Yq+mOGc6t^MGn;vWE?0v{z=q9DPF)dQ@>py1YI8!|fBcl^6onc?T4` zjaJxha1`EK%<9tU=%B)s5~ep?taI*F z_{A-h{%CmcKrK+k1jG>dl6F8!W&xIec#WU#Vl&rN|6kao#C8XE5G+62qp$$ILz$j% zhM(IQ6D9joxkE6~raM%1S5tO_D312CNx3&Wdtb^eT(f=j;ax=DEAr&l{|9yp;+W_cY^z9Nv&ku;MN7%K`yEp9nq~x zmmM=#0A0zjhk()f|!pp@$JQCwBOy_0FgGb>B+OIAM`T@vB3GhE9~3y;z>7uDt-Zi& z=!W;PLLA1TeB~eT1Ka}xH#&_utbx>br9BI6C2J#{w2@j#y*aLbOov<5bBKoRCM)$l-M=oX~PpuQCn43=cBS79RO34Iy zBi-{e0;JDJh>ea)*wCyZPFk-CK~v!uyGlXUg~cG`a3D!_C;Eg2#sSC zMi-%Hb8s&o5fi2mXR7$2jUV7jw`e3aBYwpB1D|c+6Dc4l2D+x>Q;`TCXUKfjG$IE2c(2XezZ-Zet+1fN@9cZ zioqgTl<0$>_$Z`#kgTbdJdA-l0bjVE6lB_rc2hzPu8spxo=cMLd*OVc7*)>%jB4e` zsbK~Mj7P2%8m0oVaS&VWg=FNxnNf@>oQiak4%!3n-B_FxTH(}8Mq&gDFieHJ9g`WN zjmf1 zd#giQBJI8bd~ym|Aj_c-Q`2M6m^9jE&{A63%6=j%t?^fMLmQ}bVJ6i!;r)aLr{YLH zrQW65f>mvopcOY|vS#8SAhLE6GQhB?GK!lcGnQMxX}IwiIod17%9|BHQ%S0D7=>-q z4BLsDn;si?(-}}Ysh%V8Qp{8|SPg+i;TWYVS_$IAljl7tRZ0!ElWVzy+*E>2nvklV zS!f#83XZ0DY0@PvUD}G9rn=Ln3+-^LqwN8yPWx$j3m^%UVoKRK8+ zUDg}k8z2}P7X2^PE&-2-Bd3nj+8pbVk#-E*Z@~6@cS12#15K+ThxPb?Gn%y%A(Dc5 z4>VCTk0TkK37jL1m(D<5T&t^D!x$hUp7ceM&4r-sb2>P+|``j9CwS-$Yol__^lDjz?(U67`ggD*T zuJ$}S46=M{mWOxwPx_ji3uwN>Z{9q5*?I!7dJ9^ih z{DjTgnikJWd`n|$cI1syFd8?wVd>6RQK~m#ih>=;4E+SH6;F=Gpde!)iW;9npshm}w@{Xu zCB~|9p;QIzJc2OJT_DqX-6Sia@d&}A!ZfsjDqKJsO-Sp`8wm|g;hBrI6qmIsSS=Aj zD{jhU(ZoSOWZ@)afT^kqbjnsa1o#vJu^M;)v;&WLICx{R z)dej(D18oUpGnrX$irw2w_3t2<(ED9=%hw+3zvq%4QVJrh|_)TYR{u1LJz(9gT9y$ zZ&_F;;NsVO2jAFv^s@B?&Scq(QLGofG)4u(6IvEA=-4 z{T+ZeO_A0lLgP~i#J{1rwDupKallnauKoZ>sm_;~&LX*SGN<2qCj?RlY}VF}dfknK zso9Y?4il_)he&Ms!%&pVrQU=o3U(mBfM!6QOL8q`(|1wH>eBxd0_#o1&l`bKPGjZ9 z$~ICOgSkMrVxBV^hicEzdErv|gICVO(~6%$U@lG<;`9>ALiI{Ll|l)I;y6^>?YKvP zFI;2trb$nZ$DohJNDO3=MLS6kPj##mbJ~URJEeNat2!v|LXtR5m-9Ej`MW;;ha5fXmUv(;Z*&(XyI1Jn{oNgW6TTr!w45B_ zz*l{VM2fX!p_Sn=zJ0(c&ut~UeIRn+0Or6G-o~FO7{Ai4gpE8v4QN-7nbpH)4bOI7 z{D~hL)!Aa~7hoCm-`WKo-5XxU2KMX-@@k%pF>>Y9;DF9fVWu%b)tjKd`#X8uEKC{- z&1X$M=l)T#jU%dHf_n>5C!br3JvG4AG74}3?!bB^5Dw!MD5u=r$S5>E^3`v5~%V*KwAvf|H#5bRuUEj&h!Ax z?f@S-0$D{0KzVekZUwH+nTW*fV=DKZ7?hP{P-iLL~u z8isAe?-9-veLHe7)~I*@FmEb2r)kbkZjK&G8KEycH5j`+`63tq$-+BgRP^OZ%4Y&C1O*4_N96D6xX$IK#P8Rh&GG0}S6qKB{k|lLlF&P(%zdGn|yroclly=IU zN}$^j?})h>Z{LN|%nEFW)K_PEbiA5Za1U-z(e@18x=^=XgJAie8q6nsCJ<@p$7TD0 zJbm7T5`1`8@1aJ^pns6oJ&BL!d*xMTgs~-*ttQchx<~pfWVpyw=+sMt22)_m6xd#b zFhn|A)EextE`?@NMzOku9a}F2g1!XgRW;H}fP`+Zfg`A%n-EsfIAg6FCGmli3{{6M z=oG&I3uF*;x5n2FAU12pMOegaRY?j?UYrJ0*br}sihQLaA`znXg$fk(?TCdDl&oGL z8Z{r3!W_ex5BZr~FykgLmGaY&|YX@ZH z7YJtI5T`ov&&DK+%YgNYl3o0bzwJjk3kWUiS$Qj9ArfEL2Z$Y2mvf8(tn?5bbw)uA z3xvu<`6tAyXg`SYG*AKIeEPymN?sy)Kv-Vxj#Gous`aCRiR2+XO#n=y(Gu=$adsqS z2NpXG@z^rh~DYs2+?2lWrCzw zOBPxg9^=~wobudOvfBqD2M%BkJmGEpiGuMf?Mm3l1JrbY}W8>=f$7+p;4VJ z#(n{oLI15?(9yl&Wo%&2jv%k*$rvM7P7My|>=b4i6I8tk^7@iJQ~HBsVbV}2pF8=) zso2I5#V7JEAL$+O0+mvQXdzbI7>tme(DH|SPsqsT;2cOfif!$9={lSce!-pYnTrx% zdVFO#1Bu6V$!qHKC(PH>B>K!7_d7!NX+W^}NCe-)HD40YFN>00}Wkm)kyTGzUKQ%loq0|h# zB!w|8UX2=omiYoWp%CMfK3i9<6Is=I{Ba_&+9J16^kEE{FHh9D;YD4 z7|-jakVXn6e-;5{T}c1b@M5LnO2v}$%r&g{3$^?Lifm@JML;ChaTnsl#db47(7mhN5RI$jx zbN39>6Ae0^(Xr-VBUzRL(vBtDy7|-}sKznd>81+3pBi3Q=TLx))rz$oMYvB5Z;vGU&gx3p%@WMp$NH&TvTTsvO64kv_PaHo6bqQsXTUm4Co;&ENHR6&^4Wsp`w zA_AB2Q^Sku66!=ePz}o?7K;vV>0*01y09mO4L4bIU`zIDNoSjn{?=_lN}x4*R0)<2U+81$ zU z#)nYoqpX6sa-lGkM`Yw@2V_okTe}g=&LK_wyoVPHWtEe7_0NkUy^g(%N6Do@X*D)2 zDaVXL%w#G}7*h9}_;GEGT%3wA@G5@;gY`@(aTpcg;OOn9DSe0a5b7~E!AD|^>^Ub$ zGCyeIGgI6&CqR2d9Ib-dK43P`-n0(TL@c*EeN3J``RIpMhGDAf^aglgvb%)Xy@PjH zpl?X3w38!5f7O>rq*zN9S{WYW+XtNT+*Y#N2OsmQ7KhAEyRcnM#xTR`NO>@WMp&X=*(Zn#c`DB z7$(}O=ei1b>E6yJzV!IYa0b?rW~4m0GCfhN>;Oi9w~dOKNBM?Dpn?wA=RG`>r|iHo z>0!%d3l%<|)vxvqOU@zuc@NJkQ+!YESMiElel*qZ&BJd64%Dvd4sK)K%f+^W3H3qr z8kig3(1;2da>3nEGJK)$md=??P9+LB-ucwtxjmGfgd*T@+le=)>4r zTWO|Jy`f>9e9Shh1y#z3@w{FNX`~QK2SY$gEu?>Hc(GEp%C@{+V1dOo{M7JbU3xvt zvPa;)Gv&tm)bOGtz+0&o%~s4@9M{_mj&F<)q0mP`0&(R+VJMHt$j=VQoanZ8Bbc2- zn)s>V#X?yb0K7_-Zp8((cAsxK2H#agn^%J3N9KH!w+wvydG5IJxFbKnVY<4+WfUujpuMjoIBw5!L= z>S42nXFD(c#1D8){Zp%PR&EyRi&WeC{`Eq}Q8gp6zs=0?iPLfxdS`+GSd z+@go4GUwV9HI0K1iq&&DXJy9zmL7f0^8x<>p+Cv=Auulyyi)~tygKbM@t5}sd zH#Ua-8@8APRr`Q`@M9R44;L4j?B~!qN-zj^1OXU#+c00k#(*45+SWa;mH@;eLhRON;O?i|u}a=X*V;t?_) z4*PKumOL=ZGh<_>AHDXS{NWr_Vd;1d=TLzNsR1)M=B+xmZzORzUr%7_j*v@s`lj=h zZ_Z=#BKl&ZJJZVE1$RYWa2LG;wJ_iHe?edFwJH{D1G;}?(gO{eRwK4Gao*by>t9e9 zx*#$1fPTbZ88__~_Q!lYPXF~^7m!}~P7f+Q^cW?0{||d_-uGQmoDUi`YTN)p*^Q0@ zY5-9dml(}5FKTAq#r!`nW9HBKWmmx^hzp=B$|4{jJ4R45*LBs>Ro(r)pZkF%lgXUt z^Lg&mr|YWf>guh!PM`CYnD`?e{}Xw^F=g8p9Yo~&K0UaTGXe#;R$N2~gio-76NRIM z1}6F7ORmb`FkM2I6Hu{L{KPa0u###qd)_I+b6P-_I#je&)~OWf;0fB0REMyJDTq|J z019~&wg>^k0?ZInFVAGJ=#;(_ev(Nld$s18iD@b*@L5%sX8){=@waj|wozQLHj`F9 zqzmhSHwM>J@v4cra?ibdXzD3DujOaI-u>=bRFidvDt-pc-`=EFO;Xk;MKnc%n?I@0 zIJqNabYh?dolx(2&x#|5u#8_ceC#qQSN~Et@gsH;Oo4Y*22a2<*g}pidLSb%Lug=K zjrPM49(4&AAe0gBvl>-IlM?g^;os%+q6TP| zg*TIvkTXRjlO;N9=gcyh=zNW>a5a{N3o-^{;bO1s|9WY<8=S3!Rld%(XE=pl&=sh3 zmySDQQ9TxLWGqacAzm{@#126t zWjlTg0L1RU=0#|r(|8w@aqXCquT&*AQX$lQ&-SbY1tc?&h&){@VW!PtmBxsh&fBSP zmDYuJnVr__G0{}~#Le5Tmfvk~TjU0}k++~G%*E0#z&?>DtDvwMFzy#kM#cd3)IiLs zj?>)?!TkostPPS`C(tMHTaKOVAz0gT+wmqasHBP1C`VW?qZdkK2qO|1KUm^foe_7z zGOmT0$rd%KaiHXw7`7Y^o}f)ORU@on3PP$+fI_z9R>)clfCJgfv%(j#p0U#~)!i^j zWoMdeCccR$@Vc_9<6joziOSj7MsZu_%7=7e9q_3QP@=ysIx$!7xhKu~TIdUsJft&= zYO>ByCE_^tLMMeaNm-v1(G&^pBdn(g4gM&8<8Rkm7)3U6vQfRsJj~)YY=|SQ=OL&t zYq`3G$HncqgbWA5Q>TE@XcL3OOVK4cVR3637la|fYxY)PJ?k1V*A=% zxswT;{{uGu30VFtdIV2ggK=C7w+g=-BR_0n`VI7^3Bht5Jf*PbE4lXeTb0+sI4ac3;5#{!Owg~>C- zYbFTjay`WxMW0dfp7pfC6TSe~wvnC_gWV?EQWekGavfp4Zm!u#oyxlz(L;8^FOZID zE*^b|~`LX-2c@Y5UG~NYeTsvmuD^-b&R0uWSvpp+80m%#` zB2U*!m}zrZr7_~B^LFZ6rFEfQW~cRfOf(fgar3sT<#!w07P-M~VoK%;ZS{$uTi(IUGDen{4VsSi=-VRrP5CTjER+6MtvzoLMFl zov*PKuEw%(wfYvW#pY9KD!UYUiuI(9g`sL#@r(EhRJx*zMv}dNBV%Fm3>9Z;1$4Qd z;zj;oU$~U9XSF)+6rS(}xVFvd0+Ma7)ZB@pP_Uh1*mVT4Z5cw~xNcyB7^xW)`FVZ2 zfV26}rk?94-YjBg>4wRwG0`ZUbt3s~DtDvwMFzy#kM#cd3)IiLsj?>)?!TkostPPS`C(tMHTaKOV zAy}~j+wmqasHBP1C`VYY#Z%T3cNsre;#r*$cQu$}>B7w96v`O_l5=9%ayWQ`HrbR+ zSi=;AN}d3PY{{*VwH5#evX^ItFJe7or(vqQVUo(uG}lag6HnlEWmU(&EXEU+v$2ih zw#=0e>B2hTQyZYfl80Odf-*#cD0Gr3WP!7#T3c>-)`*~oXyL1z%|^Av z&NYgv45#o5x&oE%(z7nA#{!Owg~>BioS7h?%k>m*6n#d?dseICj%~sh;Mz9Qb7HXD zWLv7@8Cx#)iBk+~2XYZxhE@Cqc45$hn5A4S;B5Z0sY}!i@C%l|f%M>AlQ1bzX%oqB zD`KSICUyuKDckYeHa~X%H7^1HoyNPMjBCe?e5ER}kqV*ad$wmKC?J`EMC9pO2{UaD zt29R3bly&VtF$h(%j~pXkBO$@CvM($wft^_+afo(jl2alVJ?<_0rrVJSp|j7fN{TQ zGBO6Jrv_q9b)4>I2<|s1W^ItnI)Ofk-*W6^55d|n+cr6YK_yM3MmfTIB^vXV!#F0R z26HUkBNp2romi7|V%Tyxc!GMY#4Z#{Si=-V7Fqy>e1;G(EWjhICzHsi_9xqOZUdQ5 z@s!z$g?kPVmqxOrVlQY2kLb*zYJjoDnkPXLgFnYhxQq_jzc__wQ7(@_d5~zt!6Nd} zP!S;tSe(XdLcGaXSkN=_R*NC7gGLlBV^%J#$UMS&X6-alB;1zEvA$@u!1>kTEdrT0 z6Bf7DnhA)6fX8V)Y(Q-*9)Gcw;U>iPv+*~Ge&u%OOw2-00ivv8XNJTpp47`KG$vQ0a;;8mS%&I5HL{&roq@f`Bg9 zQ@qF@>VlZP3#afQrz)tlOJ3ExVN2u$@OGL1U#`K zwn2&xYeLQ-wXnuXrbd`UC!NTnz7SQgoqv4`)$d+Z6-msrv)vq@{jLMizr7s*0EveWFYBY1Z{ z)E2gsn9_GbPoqx=PiZZ&if&}xFg|b|9OFS-sF+u|mSbG2{Kz@Qr=L+cUEJJ!LktSU z#(Kj!AgP(ZlnHRRK!vgb+_7u7-jM?qAdeRi=KrFexDL2|Cyz|{XW;;ex6^9DT9isw zWAVQpNa(CqP)Cd~24p9|*`sbQ#$E?aQ0UjROj`_Lc|MH55QQr%17WIsR#w6c6!gHb zrioi2)rzcRRjZ2f65VbSa8@dGAH{7<&(fm2s31#x_q*Ro-Ssb<>5u;CG}_>TIxNLn zFvwZ}a^FcPfT*Oz1&*5y>T1Och&wZd163t%9U7EO9Y`iadD%&KN{M8MIsaI3@s~yE zpEg4JlSoelG_JvMqJ)6)8qg44MDJj^m)+xLX`|-AS9w`(2n79oL*{_Kx9AvHNqyfQ zPW&QVZAyPXN(lLM-B01)X&VG%F$VMWPcaNU(SZ$jdEKAePhycLE8fE%=n= zi^K0kr<#M9S&Q^aQF<_0Y5{Cf!fv6nyd84FxE#;T&Gdf1Tyi}b8|S#Lh`u{_T!#g- zu4j-=ZSGrs3kGxwM7N?g<(&s{4k0@+8El9ePP#<4U(cd8TH|Y!UScH;o{>B9lo|x4 z&Lp0jI%I~t$ccMiBahdTU$XbP2@a(D(e;u(^+;55PY=wR>d578WoS7I-ZC0y-0abV zUQ&B#-7H=hTz5qUt>g*$Ckl9*(FGMYYY_L7CXpT2MvhEf3#H_jTqFwt$xgGoj^N$- zP+QniVoKi$J&isgJf*e7D!P$%!#&}7aNH+up<-U;T8?q8@+0RI-x1BJwoWU>W?fg? zz4g#SiLU_Th=4?t3*ri~MPEEdfaWpKkU?3o(d3o=rj!;%O)v*(w6!u_R?yiWqLzuX z;wof-RD~m&L*tm4tN~y<{o@_?L)g+}V{L#Ah&vgQdzvUct@Mnx+L;q8DB;g<1)fs(O}uG&fWJliL%5fL3?^hu8=~noBpanfmd{en+la_DX42*Yn$nXpBbfe z6Or(dSQ*9D?QBSA&&|#B ze!pCDJsBJ4xUPu4J9k`%1+%VakWOvxTYd`$bP7bbqBiB72XPJ|J24q-h#F41M7Ceg zqBdINYm{DMB@LdDJMxqo1g6d;o|`&khP=p$dtM`t*OFhd_qhoUWb6vdH8ut1TBuX9 zfR^M0oo^R>&zRwU-$8k|M<=-^_R!o5Rp>tcAORg#?@q`+QNY`bE}W^$q)Anh=|*nE zLMizr7s*1HWjU}Tc-JM^-HO`6mJ*Y1C-gM>gz%Ks607J&)=l3m2gi!zCK1>GOxG1} zImR`C%k3$?BbrlfomSFLJ^uL4)Ay`wNBo0rfp$pjO&xMXbIcHKCrtsgR5PTkIJ$Y5 z-YnXXemLeho3q9}um#IL=ZWZxagE*D5zUFsE(}FiJq#CyqTh~m+AH9)u=)1JVhrZy zpJEtzq653iX8Rd7H)lnK(q`0VK*eHG7uhN#m5)T0Le|EaN@XH={ObBSQ=%+UuqnAh z3cZGBss;sK(G_kgR3@bh=VU6^xnCB3W>gs}zVqmAWfYfdyD-dfFQQa#uoP_3WIzWp zQia2k$(LLKovsTfGlJWgCl0^eG9J;K#aLTAT?n<%+wgReD+oE~E6!QBU}SD3pAx>u z??k8ALCma0dZj2mm@KsbwkTn@&{^INIbmFm=jLX5zh5r7o{WuiTvtTjojb0>f?3xy zNT)XUEx!c=It8LzQJeD4gE)tfotO+ZL=7ihBHOQLQ5&uCHA*kBk_OMn9eGL(0#j!a z&rKaNLtf;>J+G0+YsoL!``iQvvTO_^8UA;<25G$((2`uyEoZ^^jD{UI9h7$iP*X{+ zi3%v`3K{7F=*a<@Qyn+%W}M#-Kuztz6;aZA@Degz%Ks607J&)=g|}J9fo!J8RK`jI#l znF!4sXLD?KKKh5Jj%dz_-ETxQc$KX-+req0hg)&EX%~hW?nRU~87u|sFkS|9AR|>c zESY@C70~Ira55vfjd|km+b!b}%~_1KwbO-A3w;-*SwYA-UvbX51tW7S`IPWIekVG` z4q|34(kn&j!DOigutf>Gh0gMJ$O+?eJU2Jf`~7ms^<-?Eh6>?8IcSA!<1364`z|i`r<7uTgr5l{9!p?#NSW5STiXcy8*D z8S)}0?s<(oUQ2$--sdJbkm+@gL~-LIIf!#A5WDM)o$hxmC2vBb~7X6zJbO~ko$GeYpP@Nu0m=$0hXvFrW?5t3#H_jTqFx&mgT^X;9Zws zcPnZOTS`p6ozT(xmi01ffamSlh8ZmvFh|S`NxYU>nuz`$CEaNi+oT02g+8&)Da2&L$ zl6BN(5iULGsz(VSpMLvB8PnT*dt)&MbMsFz3_Q_+oh!;6^f+*1DCm%|x~*?eiFdp% zjS2je-2NTWoD;j>V(cTDvz~`qdfMo9W;ymU>+SR3fvhQV%}BeisG8oRPc$bOPvMbY z#kTZGo`xznTZTd($ptw`N-gxAmMaK3lOVg@B2R(A^6!A(>5*awF|!tJDn;qRWT^$P zMG3ow&hmE13FC4+SKf|Xo$!0c#wK%+DBjR!2T`d=S4O7a7WoY3O6p}+%A!vvc5NH2 z%e1=&@S7F4pk~Mg0bp1#UDXX@DrFMTsOP4g!PQ8Uz7uV(%uKfXNh!bGVK>MfY*H$@ zR&0_Yu|noL5V=8ZU<)b=$?J?rf+sqCqIM$hCID{FfoYH?p;36#sJvMaD`W-DQdS|m zqPC@xbrz@ljm#Zx1GB6h_>|xf@>Y0Gs2`$2^zIs%-E}%!5bP7w0s@9zBQz5@Ufgl` zir+Lu#2tcXV(KLX_+5V7A(Y0o01c&`3#B^90VHC7;#;94i)tipUxIG?TS_mqLEMuI zUGm$(j+AXhqui8iI*pg0q=*Y!&|U#Igeu88Kp6iSYbXL{{aYoN(k+YsROHGCRq;d2>Ptf%|VwU$-BPeW|oyv=*wWXl2?db3ByW?ZHK0}M8y zdE4}g8(0xVk!RA!1q9BoH2NMt0S7+eJDpCcg}MZvD>7fU2n*jiZEsLZLEz}9?A+lrEZXFw7xadG4`m@BCZt&~OKPVCib zv@X-`8o+N>+=7}R7X*M|!E{wOh^drGK%<_Ub_Q1?P5Mr>xiT}^?kA=Ec8A>{cd$vR zp1jFy}XPGKLH0m;X9pB#tFtO#_Ky@wrp0`4AuRD2R8ntjgeTGC&u8( zYh_ltOX%$6$}RA*arW&BEtYojy-e(6(wYfSqHJ#xp~McB`>hHkF!xOj5xJp4-1{}M z?Rpk*(2{3lA(gqi9c-lruyL20pikF=u;fJF6}v#zz3B;A#<`Sv7J3HHh0pZWCKp6Z zfT@I}j(p3qd#!+40BtR-YgFdcOkiuirELYxzcU~Smbf_b8O)W`g;vUF{%yl4ggWA9rR1}ie8Ic4}boxZ?MBYsR+@1r|AWcG}@TO6DvmjQ;3Yw*?LUu)M zOC#$nPWKy`JKP3lSv&A4!6D?W@SIRTM1|32GY|-?Ll@9NRXBPkUf; zPttK4JEh>6m^wp%kB#%OagNTIJ>=Lpulus<^dsFj2)Y}a+T)4yWUY<8(!%we|FLl% zI|!v@$G*q(&Bg?@cloVXuG^6mQ#PwZ0IAuZC-LHJ03plvCJ{=coC3@JR)vzDiZ(># zhJtbL*T}Z(S;Rq0o{@!A=I(Z|l^VdtU2cLtT?@jJ6Ma|g0y#F$T$W;q8g5VKOukUf zw{$LRmY4x=i+l!iCG|2ZrBv<0+BU{6bIUCJifdwcGea&20KbUNkF5Xn|20Q zBTf2Fw7D`f+3qK${C0=kAa}4yspML*Ns7b@nd?C02DO1Ls3;__Ga?C|==6!&iM*Qt zxIG7^L7Id{;Z39RWW6=<1zXue3tkNe86>J2qrsuB*d7{UDH{K_EHonjK?44gBYN!d#2M`n4OOnu z$;!34?gffuK_OY)__1*?_T)>JhAtb8QQWa{HWN>lvfE)MPo;%>vnfkS3u~ zc+;r7Sr98^1);XtA`D@AhH?m)1;x5=BHJl-R*? z4_cuFdRe&C;vzTl;@+>3ZP&AigO)ra3#rWA?O-c4fQ`G{1bw;|ge52XuGj@~Y@E4j zo^Y#5_O!lEOa?6Luacd8qMk+Uw7RZegxpbHh|8SF&^2?JI3K05Bu8dujwLeR6B9X> z>6^y%w@3D^=+gvh+3J z7St?f0o05cT<$HELn? zuF%L^HH^oMW9VAaX%=t*zQHbgwFOhvRC+-yGK^)#F=r7s@AjN+#ckxRptFkJ7QQW> zQp^*K5~6T(`*=4yjgvz-lTSF7;@V?(jxFFTcq)0tbU+nQDK%f|O0@I>v5Xa{oE>@# zt-uPX`P~|ga+-8fSZ8@FHf_5SD@RHxIVLNc@y5Mou*DPb+_)4bO9mmX9T2&GJ&V|B zJ`)fAP9FJ6C2{0OR^~*8u9?fk`6!hoIWjYIERp%1n8=4eTr9BIW@-E0LV=%c%=@5? z>w+4jOf8s=pc#|QyTlBx_dEQ^tH?P$P%SmX^1h+sCaEJ4q7Q{jSO2gea;Xd|B^SnY3LQ|WT3BtBO=cttq7>n! zlyVH3l^EItbO?n=lUfyCiCuV43Nw1G^wrh{UZp=8TduwFM?M6B}64ro;xS ztO;hf8!!pxas@|#%=6)0cgYs_XeQfzb-SDkoVYQCOLbY`{hQr4vB{adaj8 zzT6qWGYG+ANR<8=Cv)`=3nG`wpw9j*7fdas3o|M16eKeelX#XPq56%I`K{Qq2DejsTex#Jkx) zn%twAZ1)q$Z}-^M;SziWuRNd>W93)z#Es`Lu!y}=k3)GTKK)QlNi z?<;OiSLB>t(aM=&dB4RcbtHDUL9MA7S!qglKqD7Y$=~MEi6DVEIv##s?hN1=gkUiw zO8<O<4G11W4}aL2CH~5k_NTUDka;ZzT${`J&A^T9B1!?7tmsWP`| z9k9R_#0n95#WAM{isTj;#Btrg2C=6$P-S(L-)+F8n9CI$0WuGZcikmh!i6*0?yG|~ z(tGUWvjuzw2W4C*%pfpTiIr0Gm99ifFA&REfy&vTx6lf#fSTW}(I}@$CxvyEW)c%! zIW66b#N7fBLb+q+wV8F?bM?W$lFg)L=HH3B&?olfvqb0a#7>*bJ*V6uGQ?$0Wayf? zOq`EWS&}0&GshB{?}>@Lnyo8A_$;F6IY=pYWX+NaMag7|!~}?Jzg`rzmR%T|sF0uZ z$QaioMPiUjDv0%3$eyfJrB}$t)U_hUFw5D98Cf&9-Vf9+RgrUghezHF%lj=psUxw& z4Qfrz$VyYX0~)!IO8z#NP6P?W(ed#6a%TX~AOwpcQTk_`%+)_Eh+HazI{UL+FtwB} z%%r$ekjzL-;#r1}duUd|l&R1ml$DK!beu~qnpqt>6a^bz8W22$9{#X3OZ=57?N4pR zAoE%bxHgybn}HkIMU(GdN^Oded zOD_=1Sb@sfp|{Wqtbm%|ta`bob zjLgi-EX%Rmk}gRve1U{kF3LrRV85MmNj*(zq!2W5l_1wVH^*T?LHqsqqcY_+K85Q}wR$9&XbR~OwgX&@i&$*!m zIJmQ4wt{sd2hmHlMYjBfFo1lG&PxG|B+I41MAgKAS;~}D`3pnE7F-dxIloH3RX&?I zBp3`@!D2aE5aDk6Iyd8>N2`*tZs4hfs2es#@8l*i6Ei9aNK{(Q_jDzDdV}g>2G6;n1vt2~ zU$%mEb1a`e*~YxCtCogi`SeBBtid%=?toiLxtW>kF|z{+(}r2y$|vuM3rt~~vXfn6 z3w{*TIa8I{3(3r8qLK>XM0SHW zL6xDTi^!TRh?1;`k}e2O7GfkPP-MsT;>dvx?sg4gy*5DHRKQ3Fb-E5fO-&%(R=TBU zR7F%;&G&RAdwPSK`b;vN8(M(Lj6_$$?nFP9PoIP}$HpI9FkmLyNJ~WDM+Qz;1M_ce zCyk>lBE1Y_0IwAQVk^RhOW=etIi+-f!0Y09DYPM{Asjpd7fHeiW%^3TQiO6dGfQSR zbEjhbHuanDSy1HRTD2?LEx+O$M`6%9C1K3e;x*Q}vBI43>gfqIBQA~{=vj*HgIKTU z2A*1ox?xlFPHqx2F{6@zM5WbyPgk<1H>fUV@SGc3fP*{xWh+=W$MWfuZJn_F(LHQQ z8)=D%<-pJzFqe&34~?TNBE1YkbtJ$F0I?O}!grXZbb!F?;(2MB=|+%(@Z=0!BncN1 zjA|u0C$emz^x{i)sd*F=3Y@xUeqeA7*p%J!E530QIa;SAjG0=z#yU4vxbx=aGAM?@ zkJJ!hsYXny=QhZ(eEPJM)09tQnaxC**y>b!BD=vWz7qy_D{I^gKuryLOI3)a8`*9q z(2Te^a-e4^x({N#o*Q^-Av$5do!nZkrDs${R9emVbR~OwgF2=4xuFGEOYBP6o#@B% z>62}pu#e?4Rv=kL3#GP{S}EO9$MV@$$hWtAz61Y0jQ{WZzdrVk_weffae2odd;0(Q z-{1R=cOm^p`1`;7um2ma?|8?L{?CuSO_+!8L`J?}nlGi=?gzg^k+iS1+ zWy!hzx@)g_iW$HYp}R<44a$_h5g4#I<%Umx%I*#L@Iyai4rNm0qmNhy6}s+P6{-wV z%d~QqPkw^sKl~8cA9?tw0^4M=yc);tezd~*I@0yl^{j_KX8jJG_o}DW+Cr?*{f19xdj#p{kAjHW z!AMqUo68=4=)nh!z{$tvyhk2}HE2^6W5z}fjcn3wPNMOv;iJJ?w#`?_@>Yy{s=^BY z*XerJt~9h)$}*^A?PQj#%nqkvynm=)1WFyjMPk&T;l~Zy@`3x& zycm>hR%^{{M>fr9y`$~Wwany4+a2TXng4#{4*eUe#b;t!G%r?ty-vs1_4RLBBcJZn z2bMK7mVT`~m{SPIwI!A%xCWf{NW&tSjzDt*0&teA;Bywe1FXGt5QfXZ=~t_fRxhK< zeR%4Zp_^A*j7BobZJ)pO7B1EWKJY;+*vzDS_C5Y!}K zp~L+@0p{L+xt*{?QPgv&icLhE>4^bEj)s=R1|ebqoH_GQ8%PcYSL@!?9|T&BGN`ZD zJ&ftuDtK6!xtV1)M;7&$q0Mcfsj^yJb%5j_uab)x3(eNU5`>Y_ikz+t1o%J^CfVp$-ObNQx0!vw8f9W za1S|dAI%Znc<%o9|AgWV{YCd>KJlkiLGo1iJ$KOz>ZYELw*3BgzhlO!YK=}4_03i^ z#fy@(9u0fr4d!(GXN>;2_Svw{h{8r?mimG$+j4%zEaXt>XsrxF%H>rX;Nx&ab)vUTqbnxv z7>Ph`$J8>K+~7GfG>(=b#8xSzM+<>Q&VJM!gXS1CT(^%wa|{|zFvm(Gm+@F>uqOT2 zUui!4p%4Da``-K8fBns?uUzt>4}JjY_q>6-*Is?)Qjm22$8UPh%a>mA;hy#KOP2GV zKYsV`-h6{>FTZ4LBj=Tu_c`zXllKMabrkpVhdzj$_gWS-cwGuDS(Mc?$$QDgWbeVs ztqMS{DGr^&0=tYb*{d|;dw(l0Os=B2YTWyp=ct)svkRubVIH$Kl=N<<-R}KASv~%F zM@(mJMa`ZuQSRCMhQ?fE-x<@5@{L$*jeIAYwhx1E?G^4{W#66%FZ-efO8K%>!1-`?gx;gAA$>(GZ{g z42V~7$Ns?ma_B*fz3RDFmEBI8h=Epcxz$Y@WN!bXulcT`@~Ff1 z%F5)>eDK9CqcbW(UmpJne)%juhyTK-d@ZLd*Jle`S#zR;7A`iJ_tzWuOh5G=e9qM+T-R7xkHsaN$YXI? z_MrdCyUJs68FS*XxEza1nedOrB{w9#-u-~<`mu9ST=NK7eux`CMR`w!ERV(ISX`ca z_Sm^Zo>+3GW|zLL3fCWN4+h(rS#Zpu;x(7`Iq8WLToW};ts$EmsK=P$4` zA4x< zl|V(rU~M?~P$&Uek8QL#yN7N66g;$XjnhUbM4L|qU^B{#IKf#XdVmao8)8y<=))?8qtFR1sf&0Mfc zGe@I&8>q6bpJOEywf%F4TBzHs=g4ObYC+;}e$9{-XGSp;Tj`tsY#{W-5gtcX3n}bx z?P$%I<`?cSOhy}4)$hFA+uomIw!tU?|Fo0vnJOZCBIk(7b}3@*_Cf3JXeb80sWYD{DoP`}o=PF9{+;G>Ss(_hxux)&>~=Y*K? z!2md(2Ft>noz=v&_UlX;OdNnny zH8peY5PShzdD=;UfQL0lZ@QPKcQmb|nhvvt7Z|iP=v<@yNsm`X56|}5z{(P8XjqC4 z6+L%ze$9suS2$dHy0=Ep9Qv)E=YBt?Mw?*d&W_CId^S-wJcn@F=pn#+_>B>f_^Uh%);#2_EPc6;X zXrR+sk39lK-KWPd#N2YTlgFmu$3Fr;NI@vu0IbU(Tz!?61Fjr+3r%@g963?a**7n< zI$b1SYGAQ)3rtG!w?9I!UTV#5!_AnE!29DLxgSq^!YG#O-uvC(ZlVkyWql`&i5&?P zkwcxDz-Mr$`*vF%sOR&~%^tb0enxz@dErJ9*R%48ZsrU6NG zk;8SMFd9{!^u6wTg8F&6`gGLkFPnb>$>v$Ik?DScOkbE@3o36kb|84GY=3SLfBGOB z-$>@is*SaEH9~yo?{5!c-biU|+9{wG@ zd+uCL2DA_V?(pvp|IVYv5pg~IyTiYOdw#_4%H#LLzdQUp3#uORyG^ielO^x-h~L?{ zgd=|E3kt#|h>RTZJBE9{^5r9bcf{}X81{(Y*&|iAh`&wF*F-%>bne1WaHk^v z`#zsV%>R;?UwR>)>)OH8srDC&ezE~BsSmOkrANTs+KVSBdD_q1B?jU3z$GJtw1 z1*#fm|H%2*&(jhF*pbsb$f<|VXagEjmEx0Bqxy5`LbFB+DqBF0Ym%4W3rgnK3e|?) zmh4SP)gXSbhwz!+&Y-e*R!mhF`;P3gY0>d;nNQBW!c}R7KA(#-d8@I&#g6l|KhldM zyw#pB#PdO7P_|iZC}vOcpXUQ1AWi^&{d|y}y=XpY(eZ2XFTW)ElP3GEB2Z6L;HoRF zWoW%t3~aB*UG_{ApnXw*$zV@~O;6*@vgrP>35C@X8H#;Tw!iiS6&n8%fd@P)GYKXj z1`57k#c-}QAD*xM=!f+{iUJn#-tcoLrn=}rk_zkBMJPdq+9`p1L0^4xYDQ2Y4xYMW5bT2FH$j45Gw z#J6rQ5Yo#>y+u7f)&KbQS6wM_Vb<4Qd--J& z3wzc-hZRTte(KM*Lp9gRp7ugo5#`f81=(1zqY-GwNhkEKJUp%$=2ESd?2EKBTHk&F z0gaYt7~+FN>Y0_hqVpj>_72(g0G`iNKyW~_53s*eE);@DHZ%_y#QB1#z6Jd%tnJkD z$aM@LAoZ&>_5_?KNQmbci!%dpo*+1^1L~?RqKc(H-OkF&E_mjj>)sJ-eY3*m8zV}~9raa6X?D^K6z!~}DOfAO+y@<5KkX#11 zxnavOk2t6djNg@d}+I z8NHgvPfCqz2gN~-ulu>|VJCx|RI8ppA_4jz|KS_K5HWIfNmE98XwcfJ71O3Lp##}m zDkht-Y(cZ3p2u0#3Sj(86*m?v@EMmiUHwwp&`!%6PQmD`%NQs)4PygrLh=lE8b7t{?k8x{V%LD#L*fkl`}Cm;MTVYTMyY7 zbeyvGtvbQ$aT_}fRF^EyhL{Wq|Jr);hDN6a*AqCXJGOyZ_7s~huL~pb+fo2#e z9Ls}GQ5?5%cm`*e?9PpZ;)Xb@_=+3yJOUehcl6UgF_ zM(?m2p4+mX@*P&;3%B_kr5nv_cijH{?;R)OeImC#onHA<3_sN{yT-#qYVrL7BPSWa2VgfZl%O#kMHsj&iQXlo)O^r&-On@X6`<;+8X} z-QgzFZxD$Us|Q9P45pf0XEUI3x$n@e`0TX^MMbJkkTNu)c2T@SAF*42ks{n-dm37e zD$s?h2&15vBiGb<9otmP(Vi@D=!~5G!G8X1 z>8IUT>i){hS}ZL{Fq2Giue7ur(WBUUKl3!K3zKL&A7X>ZSU?XN!HS3iVC^!lY7QFs z*tuSF?Z{0$=la<0 z*uraUpRK$0!It}VEtawUvPIaV5oc}Bd6pRkYVG7=u6?%|$J(fDlrugXZh7pvwG!KduP@!jo%VUR*M-RA%bS?*`3Ni9cfDQdu8ft*j$i((!M5GV63J2xHf#u#DR?*e4Sek+_E z_RDFPJ35*9d3FDB2HUc|t$c8<)UnU8we_7X6tl_D-a)?iU$muVzflC3`&)D&tX{Se zj{Q>XBd@*+ZOkV9)iaT0Q<-P`1Qs-V%QUx*2J$KJsb8jBmp&r2I|XJ#xRJNJ%Sm>D zL#XMfdTKsRNI)`%aMvF7=^V+0uOZOBxz9E#@oEz)vjuK)KHW zP8K%*^fr)K%uqIovwAj`#$jEg4bLi*Rp`}$ufM;Y9`V31;->?zytXm7)o z;WX+yow$86HYb?j*du5c%sY{5v!x72yFM=SPrH+`E6IMN*ENpq$e;atZ%43*bIq4m zjO8c%V+<>#LBt5ry0pEC58Q-LtwEPE;xi`ca=V~FpO&lARW{V%6&L&^!@tL)&+6=Q zY+(?@s>w-EuF&Ctb51k2n)9GJLGpw3%f33pToD&to_roB5_&GxF_89G#`TTZ<^S7qQ`#T==<%1k;%WDaK z0LAbM*;|B|;3po}gB&|{FkayCIEPN;*|9PSWC;FUe&~h$75$cB<~VP{voL)6itL(8 zVV-@xJsq{@%E^Xm@F^c^956qAN&C2U>@^IWc*z<;c6yBXP=AHna~ReEI!U401Zg-f18#jc;Puxf(5)MQr>u5ne`_IhnXnq< zC>Uj+M}Z0(^k)GM!a+Vj?D200;0c0EQW5JOM-U$Ar#dF)1c+_;=xCBNP}hxtQ1(+) z_(_zqdw>Da{keaA?4g>nOH+YIPUC9l29WbFx+{&6=Ztjp2=ZJ`C%ze{kg}L)K|EKB z7qi~1=aHmP>7)fGm}>;hy;Gt;#!(n_ENh>~qlx+0*{r;<=>Xrnr_aRiB;@`fF; z%NA6Pp$%|0|7?I}L%piudT*gGTG4trhTtvMPxt|f3i5yoPqw5rd}h(cs{*GLk)&^+LId%z0#;WQHlrWH&_+Pa>eC)raBKk=3HojXwEa3p5{*$fa3uc zRA&;skd?haty{*t%_QWz4POwPIDIuROSsW;E?^6T?}t44a_cSRwLv&$r05gmj9G@w z1Fn8Zi_DrUXqlx@u#Q;|ym|V{4s+M;5U!K_@v;9qZijTu08O?v)4D33luUcX0iJRc ziRWWr-nkdg%P_^(K>%Ao;Qq9dNo;1|M5x7AzxyNKs!Dqt;&?Xaq`c2UMH$wn5xslM zBYg84j8L=5Wp|+9a!|?-#C@J8eIWIdt-VrayiE7nt2Q~=jKMCzGf5JMR*Ig_*?{tH zx^WWdDu$0ZMzr2Q#QbyrPl{^eH3ABZp6`zVQCE0ST7#miJorF8CO5wWva1*7^PHn*AaL8fYnfu4k6b^KAYm znVlTIt<4RX&}UYg2h>XA6*Td>^P%>%)^d-do?ZOMfB5^qvUka1fWnm+?fX3E+?Wcf zTSu#6HhFu?$-A-%Uv(+?yYAEhL-F`pVy!Kn#~jHP|{sH94BX-NuZ9W06`vSg+2-FG9z#Qh^gERQ=(7$x8!b zu2l7Iy3zT_MlSd6?L;M6Seh}IJ7IYFd>ZYxfZ9pRd+v7hmvq`vP$4;>G_v(p+&yX; z2*pIEX#Mya@>MrJ_`*>u zr$W_2V`{fY&Gp+ds&u$_{)Qa8DLPr8 zZGpeRU1@t!4hX!uNQ7RO)A|oL&Adf4IfuXApEFTk%Ta&h4IXa{=os-aTi$gihZ0-2 zL7}9u=u1TM%}Ewd9~3=QV9X8f2-CqZZxvWm6iYI}?9C8NpAss-uR;GL(td#rXcw#% z-0?SjnkJUUW-8)su9onEOCH*PsXUNH_1WkMgO?x z?%Z**nO9)C=q``_`EyNmc4X^1*7Eu>2pZSw&&~_-9Y&Px~M%VN4VWZ+b0ONQS;GIO;pP}KMe zhN`%Yi6HWS&8YMUNyiLtlWhi6Ie%hqh+X*`oFvM<-(Ig?WRV5=R*_!<%|gkPzwU zo6=?^b2UU)H!#`=6^nvy@g#qL1}v(kGNtLr9C@5lDLkOqR!7}S&=>ehusE+}qH}t1 z*Y2ShWcHm}QikX$oNEaARb$oWYK1|P+#bBO`!_kUs~Er6;=s1Gd)PJ8uoLQtR&prr z!7XOg8|}*8#*I2#79&lfe676p8cnW11ON)Mw>|nuERm2Q%m+e@O@9iHVm*9_IO}u+ z#_)yWSGypp!CYBKv8}mQGCTxfEp`edL#IN`d-o7Rn{L?^Q4~*#OR`#5yERFO37;y23{C~$ zcQ=n}q(Ab7+aRLNZjbLR;BS;WtGF~gCf8{EBM4kdOD>vSV3NYno_qj~Trp`tMMQm_Bq zF=j(_GueW{7P|aj?Q6jL;i*Re1dg4!8jPpU$7H}B?EZa3<}57o}Pu6ohC6t<4;RF*gE0#$IfD*_As#FZQc5YzZCnl z!nT;#9cw&oLSkgxb!Q8r836ltM!-ZyL;ExseM6ShCO;xqcikxi7*4g&3?*TZ6)%Ja`zKC=G`O!^tO zU*LvipiUSu;|3=88%(cxW7`cQ=%?N=8XkPW?ky(8=?|HlQ&>FXL+Wze``vg0ome<~ zSTFsDx)XwCZ;+?3U@1gp4InXw!R*3`ksBJWYLN6@kHxSkU{unlKgC|8Apq1C#2HA# zTOMUhS1FBFk?JmQOhv=H)OVnw_+&s$b9KD-8cqYYhC)|jGwcH#=Pi*%Q!En2-gPHP zKYbAW%3>;a-HC^*wq%+pRQvv)eD^y(YPg5phj3zC5pgnhLlCF3Q!yJXzJTy6T3`Ia zEvG^?-0cHJIdYZ^c*7c_vE@?&GeX0q+*gfH{Au_b7O(QvmZKh`zsl)>tA|zDtV}l= zZR`ta41IzzaPGucsajm-`x=Ch=X6x+KQktSb}rEv-1Q)gXSB$R*8E#XsN)E7T;S;4 z5#%_69GVKpy2G{AHc8#LjnisI!|Ll%4ZA;r9JJdSwSb*GlteFr-pLWh>YgwK`x2y*bd&PR|V9vn;upQ`f!Be9zuL5?HHfqB^ioohJc2y)beKybK^ zAV-Eljv&VoJRPyH?EK(} z`$XF}7pU(O=eGk5H=cJ|T$qmtj*}c8%U}e87`qV0*nV>=-yx15hdpk~&_y}#!OqL4 zAKZJ7utg&CXvVPe5#&%{`#8yQoaEpWIYuP>+0uF@e6wNgYL<3lEaQQ_~FPn>VZA%kB=b7 zagswX#~dd)Ueoz=zNV57n=X$JY}9vG@KetIZ~;f6^;XODZKQbiz@JLyX$vREz8}vf z{EeS2_jAvV57pSd%cSf-!SohB*CqX z3dxLGC$!YqVmF50MR0aJ5yasL!o$bv$Dv0B#~jV+V2|rmFi(rRi->ue6Zl3U1Viwy zMtNh$4h0&6@Y&7=&E~S2as_~BYc4w$Zn)9s~t8F-xS^S9poXFG-|2nGA0 zn8KqYi0n(~u&I(-m!}ddoF(w%Ca8e{buj1QhYW*bi%_FrKJFl$0ebi$wk=97FdLC< z3JrOK$I(<9bSsk&)0kU~DcWykl0M==IX1JnTHdG7rOb0n^>8vo)*lBtrQcOo`XFOy z%3hLs=IL=X7D9%yDZ;hQcG2&rf9Qi8epO6VI_9Dw2`1%$NI6z-7o%6LOpd)j#GkiC zT&;;QgWpae+zW@{_(SekR#FqaHs$<&7!{r59`QZzhX_m2j@rwh+e(ae7 zWOqBxGoLzzJ2d{f8GE!hquwQfXrJ@g9c)_9qt5!ol^ruqf#D%>M8?d( zW!&E5K<643khFSAOa09ETkU1E9NAk*)?L0gzmmq&&pySg+qn}{+zY)Onu|NC%7-7m z1`PXs^Pj5)B~|uoz8$N3*1KvFY+dzC$(q-Ej<%N3)HaVaeS5BI{m+ zyQodQ)V$6G<88Kgz4PCEEb_Z)_9Br@!7zz-y%3CUUjwud>%(&r>rOnTxFWR9aF9t4 z6HQmtjkg4>tHx+VhrnFnqA=Oz;HjtMc|vLWY3|RDmF5!~JnW8AM}||2uv6dvlX1vi zV*I;Kd|$yd8AGvlBu(YfFr%Jz^w_#QN-A|_ToN%TR5^;7#%21Twq!&>rH$&SnpR!^ zJx&&4Z-22WU$||v%c$Q=8(Xs8wJ<|V3sGIfOzaO&&>jiX*T)r(upSR2|vctVnQ*TnizOB1sP5AJF*Xw?I^XmMO zVtbqFExq{{@1}k2&-G5s9k(wUt}V+22P2Pm@Hg9{=Pb;#HNNDik8aiky*gF*Z zG7t&CXHe)kaJ5jU9Vor3$oCuh&FKdp(1H>W4GB)SnpuS53=NETV2ZI<$!vDFSyI6A zBI7qxX)fL9uq!xUX-o~>#)AX1rS0xb+5U!O45%1S^UIDjL~K_vd7=_}GqJJ-3)kcr zQNi*=u5$50qxJd|FG1S7mEPR8b4)&IsF`N7!i;T;`953_?X^g2bu3QeQpcgO$zbL9 z>z3X@*)Wa`4-V)Aa~l@#P{stN7b%M?3h~^03eRv*}o^Q8>#NrtRt0!Z`MhuoO9cVR2XbvP%(nc=eU|n){O_J`HS!YOlVEF+YXC z#?nV+SW`DUwdYs(5n%e0_c0!aM|S2{S%YBS)uWov+_daC#KKQdj}N%R#%i(0X)LwS z39k6MUEdBoAwKh0z$h>8(o5pYJHAf^i%k5)Jj)VYq{@c1#HGqI~99tR|31d-()ggLu@( zfj$wv-!g0{AkBv^KAg~|mcij-Bh0IUY(A&-E`!0!;jdA2)s;rV7^A;tBpW74fMJl& zDFc>6w;jTq!leKtly4-Ik9i6#Ae4ONVsgjo@TVWY_8MWKDWI6}xo}{SS_xPpVo}l4 z%U~N%5q*W%b4m7VAyn6aDNh>xK_gHIfZ5!F2C+ql4g|z$3AM$=1p?~U6Sh4nj7i%h z+FS(`>W_5{0=c+zxx)NTo2y*;)~B8$3Q^WVTe8<@h~AIbII+;%lSb=tSpieb=U$u; z$Q8Ricl55v(J?9=wON_QBgBE0i6-%INz5aObrS`s*YWwlGf$)SePJ5aNR~1>MsGE~ zs7-Ees}a~CjkmMC=}+>8ie_3p24pp^mB$B3>sjBH+<%?e+X~gSE zlb^3nd(G805p$IO_Fq9VPtkou&eB-w%-fM8bn7jexov_qH%P{7^Eq3aH7s*HE6+MZ zMU+HKvIrSLQv_#-A5$$GN@ktQE+y^77pyy&W@ESD&VB5_HIc#qzyFZ|$eWo22eRX=hpZ*j( zG_-fFHyO2)_76Wq19P9qy$7HgFZP>T>H{FciZyl zW_qOY^GCU?WxH$J%H1gUG3>#ybZTtM5n|MR_$xLZ&0P!^!F?8=82s@?Y;!5s9$rA? zkYodn%3+LRuHyp=+wDS)+pfZg47m-p9O|XumHMlh0a+it;@!W;+K$WQ{XJ?1A zb}u;po^c(o$K@aSeSB}w2;nwfaoPAiCJUDO3eLLMPx66?U|b>k&SH2U=Rzo|-lSaO zPLey_kRnVfh>_GG4j3(T=_U0Wofcxpu7{GK@}-gZ`eiS^;CWxa?1d+gq=7J2KZ1`u za)3tQ)s|O8sWob8NrfT5miacEe@==AUVY_bWZNey5f#^V8pGBogrk>yBpIa;Ec@$` z|NL{t;HrL&`TY2v<*Eb=`f&(@IA|G+V&B#bz_0Q>{#b-H{z1WN<{<|G3BV`=jgIyt zGgM5`++4AlQ>a&(a)b5^C!R`*O%jzQ(Hhgl!&n%&MTaTo%E^z-PNQn3kun$vP{Gy{%}Im=fPj6ysroNL#gqBn~~K zrADq6CR7_ee~|4dr44MaE9hpnG=^H?PS{sbLs--e>dTxR^unN5T`}G;^sGZ+oLm3k z3d`k^o%cKU*b>S0nt7RJt}hpOxe7#?`+WoiF;UpA0meb29|6I-ccLfhGqNw`=LraA zEYRi(c02AZd=JUhXcGq1Ob!ORlSF$IP-u%?GH!UGAYk=K%qQf2$X^}E2#}Ty4iK(j zpZ*kAs$KAps~0Ca`-xBU@IUU+E^XZqj~vtCgA`?p&vo$60T_$hpj>geX!-aZ%bN4p zFAJ|BJ+2T(Jnz};2obl-*0iYI5zq5|qxnQ0v^(N?+zir<{3V}VC?qB)0t>2EK)F7e zHL$T&?8#OGVq2WfHWmtI0v5B?5zm7QWqyY_6q-3%U^YlvN5zn)Lj3TH@Bo09njv0k)_J|{%cf|9KcpgG3+J<4U zU`KVt^L(>9Hn2xL57E9Oo`;>$ILF5z34(^l0lx=gV}ptG|8EEUj(FY?&$IKI`3&Ep zpyL6#yiyOuefUGi`8>sc;bUK=Yb}THSbaJ zRO&dNhv)pq`MiAEhDRyK`8+#$l~K3jeBNL`#oX_*eHauy@-)YXXUszX<^Lh0U z-Z*Io7j+!M(*emrtEA(6-tofjaXt?x%Iv{~$7PT6dB^!Y^UTZpwwGS=+RHq@--C7e z`Q&_nobIdM;>!1DRv;o%`at?cPF8=h>(20K--SAGNzq#sk`x(HQu!jnfe ziBXN7dzJ?uAG?uf{Xc$vR)-jSpVwe~=K-)ser7*-4=f7yatGGyjEVqj2V5g^GTyE? zuEF%L#^s8}he9*tk{jfF@?PJ^o8JwNQ?)1AEAwy03p?ZS9OH!};FG1JQTS#ztzz#J z{^oU@Ta80(Q7mFWZl`d zFbyhSPv;vXUM8h1U|=7f>E|0Iu%3Fh5K&b?U_nQ~DE6wfMji|6u=;+0LaO%FXT`vP ze1rBaZ$|r@1VYBq$E(y$^lqX|eFBbHt&jgJ^$l4y>~HN1CO`J65T*`cS|RKje>Tox z#_NG#!(8BP3N}~_Z65~lNZ$0@mN=SSM5LMvltQG2l3NHzL9IGAt(plj9b!i9JkkLd=ko{HBF*tMzp7 z&j-+`h?zg0s;bB2fl=!#Hi59<6U=j@(grQx((QB53Elh0qP^2%MneVkrtf|l{m~Cc z9q!04MYAb1ozi&5-R5A_z~1>mBXWa-9#435wl`!bR?~XOw;lF{TWk`XLe=Qd`PD5# zIri<-JMn6{jXAzsuFju_F4>3tkqXDzA9LD9Q~7+!nsK(VXD&VvIXSR3+5PSPB)#IM z_UjN1?C}y3XFd!HTTe|0#sepPk2?G8448dHKbA0#8k)kDR^{{q;(Q009c8RmKENN< zYT}hJygmYT6TAoKx$M%a$crz4Q$x(2szm)&Gq+FgUGkC2=>DyogE7D*$4q2RNK3P& z?eP`rm?vCqc7%6!sU}}F)#lZA@AMI(XFFd&;ANMp_wf_7(k`syXK!bL-?FH7S27L8 z3bQZkgSJy;WAV;zg_ja7*fFY`ue`_&O~u}ns2JMp$g;iOd9{Fv8EjZ9oyZ(4`QaKm z7#!S^p*LSW=F(-P1bxC?_8xDX$9_tylD$0}fg3$|jCO1ObgIJd?s#b2tt>hI3U3I? zx9vW26V0-SG9wpR&q5eHCk_^_?_Di4n6F)*nS@1qt&*(mZ3^Ohi>?{l9$=Wc&l)#H5{qw0RJ1>Xom85jZMqXO^z zH=9mrT5J+9L)-EMd2Akky*JS6xJ?zxx}*ndQo73pBs6*uVQ5``WMS?6$o8BiM$- zXAFcSP&iiQ=0D3_C%?V%``?W(8$eUyfwr&u3jH~E@!3ei*Uue*gFP`gxEL2W<}VhY z`Nm#w$9%{_h?nNrkWBdL!_c(#Npu%HL$jKgN8kAe`zWw=AR?i`|11@_DH^@-(o1~Y zmEEJ-yWzR(Le<{+M>f&3Zkj5n>HjdXTvM+khL9JZCAm<)PV!~?09mi5vw#GA^()*`edIFw0CW)S`wS1n`{@J9WyW^#{HbL6>2xRT z!wrw1PpT`~*M+r+V9=N|cDa2#p6J)&n+=aV?86$`TK|Oj_(0c=;n7EiMy|X2%~xDb zhabcJcW+t=+AKW!i1mK2sbLdJKc<1JSq*#jRaacjsRV+7<|pf(f6hNvEb#x0Cei>T zJix>n6rIHMFthxRgln}X&sA5d015up{2AoG6+=FJkoUvj$p=61`fDC5!K&#i%0K=C z;D7Vi`p!fmfieXELaWmbT^yt(CL4pWoV2Pe|H^X$N54>GH>g);G+60va^%?cxtizr zFioLm>%0`I*ha&0Oa1d`TpK4`WjOpKl0Nt^=*N{dr5hqiEYo%s_L&QfwDzqF$sh#w z^$%V+sJs{q{rM@8Y-BkNrM&c-^z#%Boj!pPV^HHn^UM|Tqg3%FjDXayVSE&*=eV6S z=L>EV%jFot;$2ik^o+Rb+H#iVG_C~}D+kP2)494iFYq+F%>QHYEp=rba=pM`0|A@-Iu#_GzVuq`iMq6YKufvBV{1m#g3 z9d>>Y15=ykWovUY#yQxilw8n%y5LwZxroC|%D zMC&`i`Ed_>pjBX89FN{8j7>AO7t?zGCucCI6tA6!NYU;ej zvVWhon4VNH5f*&2fv-U(Tws>zy+R#7vJl|@G(@amMXqxvO0C1}Z(qghZZ zR0`mjQ3CrFf*Tf4ZbX31nJTBV0XDe{G>yC>O{Pty(+6Nwen^auo5ajKwhpwO%|fWs zsJ;WgaRNAfXXKyI#jPpP32wR(rJ`#-g~<-aA5G42plv2c0j55{_z52WxheLkv2~DR z`UMAZlwjhFp(%4qF+gfAkh7&&f851I($N>Ja z{@fB30NOK4QaN|ssYbu$W&>%bmgABL)0Q7Wc18RfcBV-|NSB*v9-EtdXXN`~^dQbh z*7Hs8n{M<0o-7(Y&gT)4yT6^&*mwiu2J9zPs&eUjO18b8vL z7DQ*0A+W65=b4{3gVfkZSesqf{REFSl;d*QQdGp7#~42SDKoW-xa7kTNMJc^r%{q^ ztbO{0A@~ERK(mP=1j6snD=LtiTmS5Id>Kz(w2A=R;8Np=F?6!k3#G8)xo=enFv0=Z z4j%JhbNui=1^{eel!Ze5c9QiTk0=&#qhrVZ+GSdI?z+X^D)duiPck#`LDS?|=^}R$ zT}yr3Cc=>AtJh_b^hPTZ6an4`x&-6r+^ji=^V|5Ajv^@@?*Lov z*}ToH5y9SMpdKrX(Nl*MC-TaGPT?#vudGM$@KbbUY&5eIPz+P$e1<^ zlIabWq&-ObKw!(zsrRN~Ll2Xm6;=wJq3CGFHUdZK){u3&U`t|5$J`brbFV#O%wq=l zwa;ZwK4I<6NsB`r_yPaT>vQNUU?0o?yhbn$5hdyo8e~i({CV_753iv&V51A8UwX;g zIHl00qgZlI98wtZwidE+Ge`KsrwgtkG#Z)IxPAaZhE%Q4QvI|+k}xzuwAm)Rcdq*h z%V(NCFXhO( z2XGOv(X07G8|>_R(U6s^Og-|zbReL(Cug4p(1gx;6RL1diW9CEnr{z2{k7Jc13mVbbbUL{S^=zogT+h^CF+=k7 z0CFkex@)es4v#FdGfx9iy$BoW$TZu&@4e_+++Iia?Rc_}a|AwA_0U_}GKE8>%pJGe zLZNq{VoYm!;gEj%DNK@T4|mSoVW!g%jic6dyBm*9To2rDzcB<~FgAMeC?3OA!(8QH z#A<#-8=BiKw5<+mO+B-wzmCFvpnE=}Wv@B~RyCrjV)NM3PZ=9cE~*%fM&ldR{nP8N z0&3<3y)?Bd4geQ{{1jibKHC9hfi=#XIwl0leR}-Xps$xwx#;14w7sIm$)}F1RK<)rIS&v2mv7{HBuJE@-!J z*?!ZR)Tgv^d0AQ?%r{08(qAXfH?@<6=kCUE7hk_j&C%fwgw4@3ogxTiqPH(5XOWwO zijgav-{+r`uMa*3G{^-ec{FpY#9)ebUq%^hXe_QdjmfH|5g1(0V#Y^&6al?lx|Y9qB|ZFC1fd?Ct+)91)*0&Kb2P3gcigVg%N)kIl!N6pGLc6NrxiT5@|l()N)VL)xgc<~ zIf2i-0`AdFJW+c0@2U5=A9og3Oy zXsj!pSI?VD{&>=);RwA(Tpw(hec)_?gic9&6)K)Fg_pBDSYoW?)1TsU$hCqGlE|yE z`elr4EqPi+;*k+sN90cznzwvD%OnY7%^nF=eVl-is|P^RlMfMW(E=&p3 zpW5IH*j&%z(T?0SOlC&p6nE8?po7I9;ut~mHl4QDUyEpmJ*07huSDPcXPQa%5rJWK zTf-C=CGi_y$7B-3p#9T7lH`_fVtlL|oO~igGVCOOki&}py|JT#{^9SFpWH#`EG=v4 zk;gYaDYjog80R5=3VIPtU>*T zt*O`Z04W-rhm0P5#ASbjPZp>h{7Aae6#M8SST8_Fofu4l&8a-4hwJYckLBZ(19-FzG!Ry_54;&FB*kZ5sG zLMsdz7?hd(bz;vg6Qm-|Rs&lA_*;SsEL$dKd5v@%jF=fMn@$ubSt`Nl(sJn+@z?@- z;5p=>l6Wd?qSsab$`Co`rKQP+O|28CgyoAXttF z(F$S>E)Y%NejY*42N=+8lW$aZI`caVV}9%3{5rBib_2v7TL=K49s^a(Mp0B2x4c8i zxMBSjhNZ_Qhm>_>NYP@ha>+8*Dg$74D1lZjtn|RX&v9XkF<{}UxX*>(JF^v<$i!LT zHhivd2sm2Z@dVmKs?aH8soXx-AZi3ZwTM899In{4V!aFBn5`68I|{+@(918`oM|Ca zt4>5nwN=sNi?KdfTU<08cJg~2RY5G6_NqfPkMWP_f(nm*5P<`ov!fj|E+KYI1F?Z% z->DkADSFJ!`r=2)Lm&1H3$wP}zysLHv$@7Di{0;IA9V-zP4toMrP7Y3p|6E~G`k3K z^!u&D@z<90vxG);nu?`SXbj6k+u1KQ!pF88v;i|!fru4Kw6)HQFQ`4O5z&I`ehg9> zcf=DR=y|`Jnp!Xal z(S|nHVDwnUbSQ8R`8{Az^phiy#~m{k2`(X=>e2K6@O#fY0(rE`5y)#VbR2;^Z5(`v z$UDmQ=D$-QuPm_<{NY|mE}UizdAO${dg8|3TfRiB_`iOh5*~gCt^y|=Z8+rmW8U$Z zJG@5j+}+C=+|SuWPt$9cKwdg!;@xmE9uV&5#YFj9EbYHkobUZ_CD8YQ_oEeBo#biD z{MezuD{av}SRVLdx>yzoLf&5;@KO$b`NP;5Zs>) z9*6gS^5gS``aIC5Ct(*6 zigsMGytl1mZW9;*06R|3gPrH)c=Czz`JKV$(J6g>7+U$UiV>sw`J?z`P@d7t@+>gp zT-qMmnbkaAJ3bL=WjTXK!FVJxUc-3gVgJauADzW_{Tf(bS1Lzn^E6;;<#2r;c0Clh zKR$?eYFwA;wBZAM$E!Wqr$968Hu4K1%c#8lv*KwBCXO*6Xct4}}0 zk`u}n7mTe_U~FiDV8jyjRTBy|y|?`>;KsaUi%WLOF}YhvkMD1jVVuRsBxbb+bj-XC zyQ{v#{r|A{=I?tIM}FvYmyB1y7_b2ki$G#u3tO~r>jnPb?w#FM;ox{;wCOo2ih%|klI19Mm&JB zi6>BD(-Z2U@u)$?kHTX7v7Gh`d%S6pyhSmK`_?+Wz24Zty;89Z8re-YB`)E~Ph~ZvbcWpQb9WT31q-MYRGVW_-DQ zx#%V8U=+0t4ev5NlAL(YY{M-IuVxkZ#3(b`^I)AfQZ2Yja5t8Q!c5)(g=gwkc3diN zLN;(c*pT_|x#3eo!6qk+RQnDxlkqqD%o-Jblec+xKNhlcV4}`Z6DM1${}xO@gw|F! zW`k>n0-%9u^!o9SP}um(Vn!b{O63KTY)hpGL4t^kJhFodtNY7>Ek5kHpsQnfiEZ2n zu!5|gRTj&~Rq{{}?vE@glAxMToWZoAbcbA(B1C~u+! zbBwQw>-w;#f?g z!BdoUX7*O(tqXnK230oGTK2ejbHS{VnS#-JuESv;E>2vgRYI=2JWMQtURO8SRh(i< zN5eNaNvrgA9#3F%040}Gkj*Rq2K)2*XTH+()mJEj0h5&dm7IAdYPcigLJ$s3dhn)^ z+KL!|>8EGmN zsN!mQ-6Z1oQFT-Jjp}VI;9qc;2(F(OCeA!Z(Cyap@c19OyvG<>3~M=52uAV>dkqo; zhRYBm_$oXm3O$vR2;W{)hzF1JiM>bp&w2(vGhOlU^2?Beu;$dW5}?N(y?lByMC-9f zv6gyv@|bQi35<_D$`|S}-dDGp;Dz4DyxN4Bj~d_`PEzVfbFByd5r$#Z;;NH>(oH5e z)LkM!|Cxsk1%`XnHC+u$ay}4W zpyHRncf?yLt)_=&bZUH0G0U-@5wt?xr@{mn=3;%Dm#S#q)E!305wSc<8y7~=#dE(6 zY*@>Ih|^P(XhXxt_OuaH0$Qo$2WrLwq7k+?LrsK&fN^evVvk`utxRGNFzHGbFc+ue zQ8Li;F#Bil(sgM8SONPGE~a%;%u7BOoq6!8N6FzU6_(R?$ZM}(OsF?!z#ZC2CPzL7 za52=bAxWS8Yy{0vNQ_dH}GD0+wzy)r&bx1Zb}iPcabz5KL{V z*|bdB1VJSZ2!t8KKuF~nK-@qJZWg;-Bf}kp&GxRFWjJcSL`H+leTEFv%zME~gXwFf zw=AOaDGZa#2vs8>!6^FXn{@5HA{*|4)~djEq{rfJ1ZSFjFMkX9e)t2pdJ}EmcpcLl z;8DMD222-~H)yAY)BmYCe{|x_H@L{e(sLt7bp;X43YA5xOi`f$fAE7T4G}!fEG9w4 zz!OWqPgCHTLQudNAimoffxK8_a@_K50O6$opjk{e^BS3#B(G19Bc%n{v|d&3!du&I zLYaM&Ntm!WH*fC`cgDu=d4Y8n}f8D*JSXSPv6Rz)q68cjac6lcM0)LXl3q-YMUMD{e{xlJlXEp-lm zq%`q7LJb;+5K?BMjMZQ9K`cMHU}easXTnsio-QE8uslfdYzTd-pywg@3J;#CYgf5^ zO)FRotdvK}W)9H@?eH`rB5)m^T+RCL#9SxIn0FSjR`U}IHk53J&FDFLyH(k$jxDKuJX z+Gu*qqpA+x#00VJ7!w0VAw8xKZ-t%&T_!W*9_8~GWBVEG+N#UqLyQ3$5817h<0-wB z;76uRFtb3b%`tfX6RXibG4Tf-?>$!IgrT5gUZ1#LAPYvxfrlGC8CEL^<@^<89$&yk-81m8pC0jZV znRQWAMQwZOV%RCoI|&2yC!yjA8*0APrFOQQORZV{KpIXOK3R%5umBuJvBY2}m$C|; z^Uj0opzDGLh3JxtEn)ODz-=Id^*l|ZX-LnJY^oi5>d6Sm^nEb(B%9K>OoP)Hc3p~T*!r-Ng5JU~Bj9&$?9IiEf)&pg138ts0nCED%B3t22s-?u2!&=)CII$? z{kLU-kY))ljS6GD(kYPUvOv}sulxXjl#7_G?qtVgcOd_--7;c8l2B1+g;1`-{-3*b z!&hIyTb&tEQUIVUZ(S`D1|Y;hK)WtnI>bp<)&&6sHKq&%$dF6#d_Oj zRNR8SG0|5YYD8{BBH<1qx5p|gHAzlp=|+BU34hE8poD;d2#~`}1faYJ+2erYPRxMa^Y3s5tL^JRgW>v{0Pr91A~D_k7N}>(ii&YO zw{zF%XxE^BwFk$J8iM#iKXYWbkgmze3eKa~#>#@RhSt#r*2g?ogA!6@v$Ihgx}r}dDIIpl~xzZ<`+m_&~pu8Mx={JFE(Q~XMS)z93M|G*t= zKXRjj5b^7xZe+CDPvU&+(Rh)5!4LQo__^8RU;D1dzcI;o-w}t=tHe}Py5Y1zGt30A z@;AwNSX8cw?qxlt1mT&M3$Ihk#n_;z_23j3S{MO@75ia61GoEiSfr@&3m1D%MYxBb z$)nm3RvnbesvmI>I7%m=9+ykf*+9SvQpp4_#{>Ti?dpV&7c%qJg135X&6gih-ye7No*lw?Q79Jnez%N=qmDTa8aNB6?N)vn-u z;d0{fV~;|4!r{~t{tV-YpC5fhs`%i2+Fas-Y~WRpeOZ=x5RNORI7$|Rur0yw0PvX_ zvk@I#NmBgmUl|EO@bF4ZLuCkBd75@Id;RbvFaGy$;JDMLI4DXB9LJyX2f;z&N&(Pl@mu?Rf{;S{S{t8 zabVqAwMgSc7VVj=85Bzeo_rpGUxvjIA1bcJQ^&#x$aN|t9+IGZH?@qPP+P@>@B>r8 zd_RWVal8MfRo0*QU=E+tOv@hnPnvBp3ExC;nK}J(bG}^Bl%nK_^%e0cw@hu63xL*9 z>dj%OT%o`qkLIY!m*FloD|8UvikEtT)##feAKagZ-hPXk9Dop(d~f8130*GuMKRN} zUw)A`F7JqUl+8qI?Mz5j`chPY=jRv}ez(*v88gf)j+0=&v71|JwB zY#g`LuB8Qf>+QE}=(p4^tQm|;x()UjBCQEG-5%7I+NJljrFNf}BQFQP60-|mcT4S7 z7Ru?NEw#(I<3}r*S$NHi=M?QSoQ>0IaGepdlwWDnH6M#o0*-F8QEaK*;zvRZ`TQ9) zQ}n8()lMeVo4rtwbjg(e*{CzsJKlUFvP7)&UgHlY*I-ltcrO^HL7L2a0fti1Sds#1q`JaH0bSFQ8|pu!=~L6t6{(IZG_A#KWaYiHW<|P&WAP?5N23mj)D)$61luzQvwyc@8r zmlG7J3ktbX9y)#-5K;`3aGa}Q37z$M?69{aNt)PoT=(Nrq!7x*n1r z`L6hY=OIl)+^b)G1b6(1f&PiYS4dP1u1An<2t_{3azY*ErNmjzrB~b#Su4_$BtV=a zk5lsXYc5`6xqz8kMxL}P9C4B!66o=KfE|H4x%npSA4KlWHz1$DhLzwMHtUyntEjwR zOpXO%j9As0_Kz>&)gF5YG`;&Sl%%PlA(WO19#Ib=hWJuHqjDWOK-ASj2+5q@P>EM` zp_){|$zjEi;5(dsjIA*sbn{Jd&K3t)1`Q#4M)QZ>6N?$K94i6*DYBMCE#%u(12Eb+@Epk^!R>Ix^ zY8Tpc@myIloL(4*GB_h1ZLwn83(@cnVv#zafL#^Qbdn)Gg@LT;$T;~7BLZ1X~Hx3?24N_(A5HPIt3=$fW1Bc41<(X<_rSv&jfx$&wpz|ume!45*59SAw! z!8hEY=W!ZNb&5vM6xyZ7+C?o!Q0o!<9q-4UelHSvWZ-q94HNlv*G1 zh#jbvnPJAwVFQRrW(BFiJ z0?|nhWQWcG9lxIE`T23ds}T1aR% z6fDqL`a8beaCfDzAP(9CS@MLN3qqNU>Fr=!Z84BL57(t3BEKCpU(aZG?4JuuG}ym5_pW2FJS@0_ z0;U#R)vI(f7U5u*!efu>z-wMl*eL@X;f3st2NDxCE>c-8XE9VpVv=TuLxFFc*Hce} zc1^3(Nn_W{EY5=g#_eN2gEL315Xf3uVeH$}zi$)E{gM%Fvy!=|FQapQ^aBTiXP#~d zc)B^rJf=ZtIYK-^-GIR~9W=bhic9GPDWIe-3P{`o<}EfkSwRIgmyiG)Q3D$%7SKVw zVE7q&S!#3e9k1=2Nd})lS9&TKI0tAbCl!4|%o;QT@U-{gIQmYC04~XLi-?87J_*S6 zlS^g?`}5D@wTC6$qH%OkfbZn*|Kt)!+;;HUgmL6By^}sRbb)w*o+(3D?guRuoLO!} zI5aCz-~(K$eA}%F=-ac%fw+ir))u3?gI+8I6i#)5@m&N$&Is>Quoau9mI@g%I1slp zez}sT?N73q^A(tK*aP#()H^~3zwcg-#;^!z2XS|X(l^=v_QDUM9RH{Ph-$T*?i&>X z@)xy?05jw!J+2gA_38F8V)AhJtux3fSY@DoA}-@2%|QY_e7BxGu8Z~p8o7d@ls%5~ zQx}OviTN20(L+r>T1+J1d@5%+@>J7au8r6kKn!t+-#9!1vg!7e>0v#?!uSC5!fY{? zNskl!`*E?B~4Fd1JR!_`eA5G3O< z5MURPMSx>89*@?RK4cmJRZ;U84KBGqpbe*K=*ObVMGvFZg`^3U^e|?|q+UFR`ojEE zgQS^m**h#TJ7AKVsTXl<2ZLNM4OC329tS48Oe3-<2BZ`cb;elD`8{A|HC$+b(%@K# zWYEtYzA1&s5PWpW_}a2xOGxgCMj3Ay)Q4}9haM0n&V&jB(x#pnawG97*JCX`i;v6R*DztkhbLgI69NrUyxCZ7g zNXkghgbTwc)%`wRC#*%a{G^wWO%5;^UUcjX$1aXUVu^F014*OFHLZh>Ry%(Fx$M0hz<`;fXKynLrJxoQYWgzk~?n>`xn zZ4fBQBk2}e-i^~dJ?MAlhZv*&;RS`tkqu)$*eU!>4swWtX;iW6EC=>pX#^)c{%MY#IYAN@}Ueq{H4wV`nCz7LkT~ z$gKkz8rr_VfuI*H?UOmN>eBcKOyWZ+0b;#EQBww&d*`cUp zU?t*B*wFIzmWvWuxo2(R0VZeht*sRQ0kv6X)VET+Sa2};kjo*KrLscX!_I`=N^QB7 z;zwm6u1bS4f#7YWczKmuDc+v@L5A3Hci+F46Z$t6*Sm>?EC06@);lse0&vsPZ;pgy)E(_XpS!3& z78x#(b0o4me?>ukOv=&9R!yI;s6zm;RnvnX86ss0HeBd%%C~BI#!^#2K)-l+H2)h{ z(|6;MT2@v%kt0+hYw*fy0sQ;Bftw1tP7Dy4^Np}RnF!duuZ|(i8Xjs&2V zNq5W#IYpS?gH^?BK`-PeGr#TZGt!Aj(AEa*Myp#T9aV*`lHPB=8O7_Zk`5&V1r%j7 z)G=B~TKNG>hy9)bUx>QP$m7uvg+)j z^=c{o{oU+Dl(?er*);5iE>@JFqjXAs6o zB2uW1VvMpl2VLZDA>oQ3Iu2XD)tb_AYLBN321aHVaZqcd`gN=aHBue~)L{~hW#eOZ zAh9nUl_1sy*pu#K6&%_}GBJe7p7>D1czyDL{m%5Rjr2i*EOGbNbioiL^1{J0M6bgI zd)LJY$+hyB2hYRwKEKa(E+I}KMdC3c>Npr9cG3Ftn8=LaGoNh0W?_MWVI(SJ|IBlB zu%eFEG_IRk-~*4E*ITP6S27>8SgZ!jh~!%_2KqF0vP$!5cyLnn%0<mpbeE;BDQwJxHia^R>ET&Z7*ew=KDzVU*n0*4v zxKn!Lx^6H4@eWa>t~qrwPNqx;E8HjLYkZE<3skIy{J4noFVv@qx0-FPjFRKBfAdYpFWf$`x9`d_E53CLvffB_2Gc zgkUT1I&K9X@)==oA5Z?wN9tgIT3AUN!{Euke(kfZQOOOt(ewco=U(IC_fw?4iQl8z z8^^EwqZdfFr`UQiZe>45Cp(X6l%!PcIJ(ktqA*?@uYF4ARKGZh8FA8qUR- z2KBNE>Ivp;jMKGRS4wQ2w;5VkiYNCbPXJ`2QM{JlkqzYmzKTlF$u&tt-q$a07;e}H zYd0|bUFsc(HOwOxQRk#WoS%(@KcypX*r$_y4afLyC9tTH{!#nlVjO41p0QEJi`aL4 zoZCA$ma(-IR&3rqzXS$u)(ZGSM|mD6xGfB{(1nhjt8VckAvn6}wye3I(3iMg;+hL2 zi9SIL0L~EOZO?TebZGeThVJ+fLh7r*T(SZ*ygQgF0-3)BYNsGiy@sFFmb3#a0rBB? z@`mGz8H;RwTjk44ME>4xJ^DEJLF(3(-*bVl)}W68 zLBg{u1_<_geJVlv2z6+dY)W|6eYgM=Pbtpn-oylUbyILH-nHk;e-^NE=8NIF>Eg zi6c9nSY0fd_^iYow=Zx&w}VMKlX5Du`J>>Uo8v12z_Jq(qxfRN@m?ZnZSHL+w!%h5zqS`!NrWl0eoJ(y9jhj9p-z(G^_JnF8EU0DY%Asuj}qXezCLZI+m@ZRc` z{&^jV&-uch#pMb`{|A3%yHQXui=caiEC_^5uLLVnEudtHr5JQHgXsX7zCXUS!+A4g z`ZV8j!v*9Z6ITHrj9YE(Q27&vDawTLNbPhwj%6>=M2~P(wS7Vx5oXP!y34~t^H zcvMfMqhw3PEarDTuhnRXS<(#UK@$U#hI@>&uEz@pb@mup($$7y;=Ce)tPJL#M$q57 z_8LozM{xBVlE+0bBBTkPamwPE(-_fCl>GW(q3{UFOU^iVlh5g(69Zon&!2c4tL8K6 zo0K(E<%!212_X&<bjyB_^Yr_210c^_2ANMbN8$p7%nVy>Jfj0e%0YDU zZjaSPP9kX1NR{p+-S9Puhe3Ow83s6lZ9|2!p20Ma?(78JV9J00F%O3q`VGfNbo zpRscjn=DhLncsCMSt59;#;dFQ)Zsslafc2fGn>J0VEuxA=+poLRB|(zn#@;ol$Tq~ zM6z;%gFeW!PGR6r!#q^H&F?U%af9D;v z4t%1XVmdDIJRZwAjK^|o{5UdYY}z4$J^+?594{`P&I?CfdrgdS=_y9|#zPobV2S7O zgQBI{z`DTG(?gmoyRC(H`H23ioFzR7d~PRFlQz9JWyP+)&Nr&MkNUGuoj+6T#vwPe zj~ok2Es&IVianL$sgnj%wmlPeL5~I*m+)3PeHcdShxk0Ml8GZ=(YXNLROt() zklZqyMk<8T`JT|y{CSzF;?w*%rlW7!6QQMFmRvD{N3#K!3|6Q;8Cqtm8gEtOvLTJ@ zLiQNjycM_d`pWZG?IkbT^BFR?6pyWHT&g{#cdHu1b)z%2RgI^z@VTnSbeic~Z}no8 zndKCtpk$AYXJNvY`*KNy&0D=#{$6(s>;Y}{;!G};N63K05uzf9*~Ln?bCkd*pNz1I zL`T@I_~h0ZOB%>@yquRRmsBB92CGm0t|cYbkY&i6Q#v=YtzPV;)BkSu;=en)h!hxR zG0as$!r46p>SWpVuwW;gVQB;i5`L=~gA$SCja+V&SkqG*Ny*G4m&76;0-K=8w2+72~V3ys%Y_w~8@K@>Rw`nX~8Hw~8^o7lH!hRx#cx z#;mz!>TatT_bv{nB70)E)y9$57~$wvF(#Gh!Qtq@)@OX>&oc%eU&!7n#+jeE^5+nj zB9Kq)bmPn~;;5*ulT<#gqqM)yS8jdATc5E~7;@=Y?M2SC{;k>)Pc)ICvJ}iy8)C5b zLwlURZWZILVm$PW3PyQgaqBRSW?_|m`@y;qVP(N~`k?jJVO$5GgFees_N-HG9mb>c z<6)h|2A{1Q;LL;Vd0ceV7(qnE^o57f|DFY3oa^5z#-l%|j(4z?j~Y;pmNVW?(C%IB z)g2dcxVPFVqu#q!j6u)pb*7tPnpWMeioLzEse|bK6gH6Lo(vpY#W;I*w;tonL&z5E zEK79`gg>*xd0WN!R)nJxHDB7$?7>zs-YUlI_}rtu>u%as-Q8oHb^X?3y!9Bf;c)9Q zRi9AV#jj2E4j zyzdlE+?h1u&AVCU{qeUW(0+ftHy~~|m|9uhKR#+%?d1FU>c#Ix-}~I~k{Q&!>&)-H z>t1oa>LTC1oNH1=TI%4Px80AAvxdx*X#gLvfTnNB`|b1H)P`gWm~Ms6n+n)2t&oj-O5bga?=5iznTE(>q6xRGr}J71(Xb~AulEUqc2+IJ;>Kzr*vbsiDv~VH=ptJ z(f7vX(s5-2FHi<22vjscTl;7c&g)b&7$28Y$88~8qHd=~1R1ue>D51^sk-M`F z?Z^f_Phf3mZYhxu;6E`Dold zt!E~CJ#1&&*Y2%WW3cB^Vp`FMV2k^|JIh?F{Oanga zfCnvqRD%}XGTi0OjOOGc{wQ_rfVYtZ~FhKruMEb2`n% z4t#K~UQYtiqZr_S*Nr)EQ{l>~6O!9Fdl5^nNCO-fH$li$-=faK9+OJ9*m3E@rQwoX zQOKb-;qdfx(ln8g+xRT|P?1Q1=6GM9)ikK_*Wot;i0Z?W8hUNKpvL1X_D2p|0iGLT zIAf|C2TXUD%0Sd>&JT0RvZZ+T<+z%|+z-#NzfIOuZc4sx1(foa3QH(v454ZRf%I__ z-q%Y@k%Fy@A&F$hanX!~zD!&8`;g|!&!X#uG7MdbIjx~e1u}L^ zJ;5w9TZ22nJM~KKjjb+Y+(lOk963VYiD>2GnXTS2y*4-Uv98g3`u9ZSA|v@aCbf!a zm*lPhtC;@mGene?Mk;HlTT*Kc)smMGE^tPvbDcHM%4gtOM|EyYkya-r+OY5>iD~!d zY4SQX`7AD&nfxtJEo!n_<+;&O2pJHZ8{44RLl4TNqnCGgZi#D><7r|~Jt^WBdktLa ztVa+_?Z!}Js9T0=V}&6+<9h5pF@+M3pyzz}axZh4VvK2SfzO^%VD0Qb7$oPIy!P-z z5OMR`gfZeXV~qG}=o|#7#d5i=ag$iy6%<({lnL#unVuU-%y0mGG@ z1X}mKr7pU;<)n67nKPf4;%v3w*Y)v7lbI!&6gxUzG$T7mLS+qoPmiXY2rj*&z*6qL zr(Edaqwioj@gaSh#iACNq@#C{rE(hHUl{6XR=f0pUfDr5aSJuT8mj;J|Ewab1gZ%> z6Z*s|BFfV!@7;UPjW=|9cbot2I}Y`{rsBf%{^&apy!fW}2SKJ^ozxI__5}nec$7C( zgSirfVPzKfC&qXHZF1LWm11WGzKLC;-Le1b0n7F;G&OU8g@A1Q-n;$l4vlx)QPmp+%OF0*tT-5MSBEZZbLB= z?CODBwAWmlNj)o5Pmc1~jeNbUW=p?LfyE`uJ99^w#s#C(UCmb0Gy zNycA6y5z;avLf1pze?ql!%nxU*07yHMNlrL6kOo|QMAgYREP4*hmVRYg6}>lH<6;G zCU-gr{8hwIoIEjuEdFrpe~l5l6M+G*rx%Wp|y+qWs#@r&=ssZ0hAbk z;QixrGD_kKK`jL4Gj2#97!V)u0_5~hy5Ud)u@ge@Y=KAA!`H(|z#D-l(g{E*u1rI^ zo4$e#>~s-BaiuE0fcvjy+mp#Kj?yxLYx#qJ=%5D*Nfb zi|=_~8Lv!nez1}A<(6}MM6S5Kn?{PT^(N$?8DCIVMq(boV{1FO9Y!GN@UH^_y?4M@ znb*4=v!%NH>dRakv)&LHe(uP*b$KS3C^jAd;0}FKp$(^iIy}gN+_m;BlA}h2%d8qy zeCthV2!{v>T9&DReJi||7U@gxLgeQaJ0y6@(K(=9fmqxKpI=_Fxa`u@FRqCrESa{$ zU!&nm8%-(di@k_13e!VUojHUvo9ANmaC}L zE{|sy_6P6hh|7zmD_K&HF8gUcEN$Img`o#54m7Jf+iP7X=S-uvGZtkkzWibc*>=VP zo@C_k>O0^mE@as4j75Gg{DomKw=)*utqcpqgpxCM+Zl`h-=48hWKN$|p}d?|M^E-+ zoAdp^AwN(^#0Y0QQ;~k(4Ut4>-~T|El1ovGN)RKUkU0%JQ<5vL=nq7da4eTjZMQ?Z??h~tE+ zacyTR7+O}9ZaY)avcB7y3RKwhw8V4IZf7cdz{LkStT=QiV>?rk#kAcM8QYnP|K-nA z{Pa@XSi!^EJGWIRi?3RG9IwfAhR+tae800FWP_B8fM_|h|BtS!pb#=G{?(VtV=I2< zeEc*2RBJJQK{5sd@ynmy&QlV|72;PMErHBN|L|*d@5IW(*I!#)GEvZtvE!UlTq{BJ zNH_Ai`x%bJJrX#A!_z%{&U_?~Dmm`YJ0o;qgip}v+(MjhyjRD`;{1*uGoguvs$A`3 zY*;Q&Ox9zpTGs{W=vrh*{m=(b<1zCvYflNQ(*k_AN(x^1SwYh#FyBuD$@?yNFonl; z%2xCvq{$RnTgPd>aSq`QI$=Cn$fIU$gziMpx-kIQb@q^)s4<9g@P@nY^clu{OcZeS zj~vEh9v{r3W}KbRF6ucMlXYI5Jk+T$t974l6x;GKF)YuFwF8jTxP$lgk$zjuM&opV zk&DhL@q{L)GzPJr^AO0Ha|L;QG*Ul-xIXgu^zU7$o=0-NzaO_4G#q}yV$8w&@@Ms| z;-~!E$8U2F@fS#7_wosk z(LrSJJCF16R}Amh2Cyn7DTJ8eF`lwlNC)XmXBSN*=9EN_L3yhL(j{8(L+|#n?-Ci_eRp<81k*KAS&g(8|N+@=r7Zb%x zt@on#Kxm88F6d0Vgv`P$r3nnXT<8J_%t{TP6-6rgU7%eUiEIE729g%0xXuAAfR0gu z2!;$7+i_@rcc91_v@4C1;p_mC79{5wkc*~!G9mTSIYA%AOQB(=sU~2_8SAak9(X0+ z$PECL6mN&4JW-1deiYQp%y4Hj&plOYG-h+rKVx<0d4D2E!*na2WS^cXk-&nb64zZtaLXZ$>0=tsU_rkC zgE<>8VDl)^$D0gcf(L*+EH&}Odn6kZ7~48AKl-#-+^qss5?K44c1DzuHrAm4zLDN8 zf!R+Qre4ra6z&0y7Ai9wpaU4TnzolOK0hd}Fy8FfLcbd@+Wq34(SIGb1MdS6rfV2+ z^({AN+ciPBZ!!4T(TF}gm;_-X^-nJO(O0|#$PZ)9@hqKK`%RQ(jY`Tq@p$k1lffGP zYp4teJ8-(06JB2_vT_S{AU>)#)Tu{y2QtKTXvDNDdSzLqL-$V>$*#Z-?1>MXfuLxP z&beLil*geJ<8l1pXaUj=P>26U33~w*hD>Zquj-vUp z7rGO$8`lfygRX5ZV!Py+6rcAPR+-l19&kK3Mk7Us2hm)^2 z(h5{VbAZ8YU#~_;`WKiE+n3)%;1R?t}F%3hE#fh4VsW3TTfQAsJw|aihu7&LfQ?L2mfFGQ4RwmJ#Z+@ou0-UpMA#8vR`q>?J(_s z{w}O!zF?ZT228IH8PV(YG~Y=c)kFO%X6BzT5BLGj7&wMQv`+Ibe-*t^4 zj}P!yxFE=>FeOjKoT5<7KXYw#S@PE}KBuf{#r&Bl_&VuV488v#+%lxKrEvLY^rT7y zi@<&^!f3tfHU-3)(^|B4%3lTXNZIFO+AL96xzuba1)P;Gos0KGQ|=JE_vBN!lay)p zHtgMZFc70Emx8gh<@5+ZR?Q!qPdh{?pw8qa{10$wHY@@xpvcMt(B>1MoK$2@qjwab zH{QUxW#6bXd1@Mc=TAs%fN$uGVQO3gW~2}UGvWG>HzCi-kwHR+tMsNz3Y=3Mwi^KN zB0&QPIWq=GpCur2tpn-|6H5T2yCpvdaLCjGu6&Z^O6fuwi2e`UCMAmm6SwKA(Tuxb zcZO6=k}mo`wH~b5yMW>1WiCDl*&09Xh1cL*gWS{@K6*ZXWQ<>rrz9VYl7DxCShWYJ z=v?7$3n$i40aS)0u;c*1lTxhoY3$KjOwXG8pxH~CuWs3y)9TvXDh$Cw5EK&-l9Yn9 zRT#DkgPjKp-AJ!OtI!O|3f~uH z-%1DD%VMgdI0_yfoMdWvz$hU=vE2pQg;9_aK!kzc3=gF7=1(qJ0BscpW!@M$DNtd| z^hG(yaN89@GtSNpeDo1UeF|Y3Z36*p@K#|Ezd5#1P>I0eyZBb}R$);5ByQQQPiz&2 zl{;6Nh}|j-JxSWGVaR};ioepmTZLh(FtA<%cVnwC&=_^$p?yyuXsa;jjGeRpTZO@; zgssA$T=iCA*eVQf|1Vi#K*p0QT!n^+p5ae_I~4nBYeNKRd$ud6;5J3dtz5(aCaW`Z zpblAK8$gQ$&sQMn!R34JvNT2LIRB9!v{cBH9S6nH{Yld7v2n1IqH*-dzm7OXSf9y!*Wa?vD+%4mtja2yHHM3?XJGTiTSX) zdY*;}k2!bKgwUL;Z9)ThUJQGrBVS?RKpF0H&IBVsJsk=2*Dzx*^LZX^Q*BlS-1Px^ z-fmn(d?xm$67x15|#Fs{DR@LqI7=v7+efb8IR^ z_mRA{n8gF9EV=(?UV1%*YhY@da7OqJKn-bv?#p>kd|&X)PVuEW?*OeaZ;g9UW}E-i_Z0E zZVoYl66`6Sz;JtrAE-e@M=-`a=mznpelB4BC%22#E)vi;q}OSWyjoEQK@Qzd0_T~J zd?X}HS%OuFfR`_tsc+|@Vu0{RW|o81sa8TChOz-Y<2(!_;TY;|D1GNoRJO|tu>;2a zTOr2`aLz`_y}4MVG$77!#7u-f1ZN;;&WolgCWONTlC>9}&k13RDFCd##N5pwVY>!g zF%%)W4J1u|05(A5%#}Z9k;0t~Er(4Cl9J%$$xweFT)aII`0!K2Me&W>5WmF%qhf7| zaiHna*px*Od2x>DsV7skd;-d(xT53e?Jj3dvejnX(@OG#cM?Z#)X%OMYXQSfs^H`& z!hm@s|AQV6I600kL72bG_XMV`e)A3OJ$R7k0t1#W#_|a~L>wZ}{RA0$_X!0s_W>^s zV4M6s!JXO}@K`orsXlMHIggyy0h0OEkrmQm99|rl93XH^yXsoa#ThfNI>Z}1^CQ`l zOjDwQ@SerMbLsgIkqZVU${JCSgw5p|He^huRYkCk;!2;t6Fl~>!Rz}9i!xpJ)?z0Q z&j=253_Eqc4j-qN2oG9l0`vQq7g}bX!CCM4@;6Tc>eyiraW2m+>WoVaJYIE}7}#CI zgIqK8#Z#5C@*IA@)Dfx}aXj|UJ8r#Y0WI25covYLlAYK z_-6(`WJS1xVPt2bR$$?`$zc}~WY2`wiVz@DKb86zVErtCBf6M;g5rFCkI5{hENk&^)1 zSwE+Q7){C>NHg)XM0s!n18O>Z4O^aGhyP3xrD7cjckz9~rO}1N>VQ$g5g7TwCC8)m z$BD31^}v`qw%H|OOrMgU^LqOCCc2zE`z6Mg=+>~MW6^Ov|J>8YgjkT=sW9Q%G9Za& z+J4H=v7`2qvBVdif5s^IAxWb+u)i=2H*ID|2|7>8iB1fOfaL!rAKj05C?@h(qG7B= zLJQ3kk3)p-z6()LB)Yn|^+-CjC8mPSf|{fXFC4V{%9y18dmb!@{|JQ@25`ngPxhW) z-gV~+q8;jsSl2tbPZQnlVlr6efwa0VoHS> zR+;<1Gb^2N>E<1hL0=&pJg#r8K@HyGV(Ui2ReGFGVn$dESAGMxJT_c=ZN8kivQ_0i z{wUcnqCNNYTmd;plV{f6g;P~43?9Uqoq+ByzlzI z#|1A-T(Kr(aIHDWycnkJ#_xU$j;q%}6J8HL2AiRdw&Sg(tq*(nAvkF4S%Zq1y4vPJ zH0N#9?!4*Ml97xF{Kg>`B?>h>=wMrpxEe1@O0UwHnBX0PadtR2unm7)I?BAdGq|V` zFAsfYfm@E|Bol2!WudczyxmtO!XN*L2Q;a!&(#^{A!gm$ZaXn$Y=xz`Tkp|`W^RmT zFfHk(F=9n|Z0^$%8uA54a7`2<`!3)A9`7`Q4W{@M0y&YP*XqP+yhtucAK9ji)I&`f z${}Az*LjG?kAI{@K@G&bV7NJ!)E;iOT8Vz}edIIrg*Ql5=w%Jo)}*t0=}){n8VeQ~ zLrFMoB6^)-2Sdt`SH}yMv$Kw9E|*PPt++WQ-UC z=)sWT@XYxA=!(mJYTw2@cG?fcfDks>bWu?*REz=fZrdjPsToV5`H5+8M;Q3u3do2dARCI)rj!clLFFr)LRmU zN&<=`CrU#B`qP6VpG^+a%H1}yEFRxRw^<3fJ$Hxt9vp#q%{~>8myFbC4x88L$;z9o zAsiuPKpc3-ZMVX|0l2(Vh|H^W_^7iDtPvJHkC9Yy5XWd9OwC5SA(60sj_HS;=Lzq} zoC8;DXv|^vF$wLwFLgsc|Kf96x|fg8DdmX!BQ<>4PdSR~;1tiC_9$scj$+Z}=9}0- z=s1NUr;kHQi*o?dgm&5>R(P4R{17Socaez`o5nc4j)ZeLfG?E^K5|&wTtt!bgaMz< zIX!bH(F@7m7`P2uu=s!)X9ql9p2>QK2wHx6;)x+Fle#TW14}0IJqxV>5rI1YoX&uP zKL?Horm|s7SV#rn03kCtbO$D;08XU~{neKNhZKI~N_8!U-pnF1R5o5Jy8YWPShBta;^gE6g` zm!xLTW}`HqM^Pm{;hBcXyX+`~4q`D0*VR`IY^5|#o%m1{ z`Q~2YkK6BtiZ_q_RrAIFnVKDL0|E(YaT1=_9x@=vK{$&MA0f^ca)|4sRqCi0(3 zWbkkQskDX_rGKE+dr?35Zy8eloeU%Af%X=hnG6D*A)PHgpjo;$GG*FZ>%c%96-d43ff;<$dBLj{TQNh8O}On=53yfkw}wWV^u_Gsp-cU= z;IQ*|-0ojrc^P(}j)VWS6X1W_qgeg7oSAwe-zGQKZVZWKLq7f%@$@G>9sduA;CABw zNl8#j|JEMen>F|LlY(~8K0Lf9e+QQngS|>{wj73+nLxxU;SbXr24b+CIGGZW`6W7{ z+(C@Opk);uO7+Vx()48k-9y>UN}8uV_9jV>q{WpqU>5)oD`VB1_AGWZ(jOTA_FG4y zN^Ua90|V<fZaE-3kaDt+F%m>CMel;p*fXm7P&McV z)2h#DU-LZ$^8py+)mH$7#X+U?nsfL@0U|4@*Lc$LOc!S~~jiSKA9dBkjf%{2co6;cMC8U+J@%aX6^y=_n4 zNA38wa#um7W?WHljMDH0#V@97`0(EUQ#WbJMF|22}VOeeciE$mkUX_mY+ zaLB-|Bp9kH9LF#m0}BggL?gw)v^nO$-PC=&HHXlV!-e@a5Pcdv4xbW{5AEU8zXuXj zRHggCgR3~AhPsLb)O_|C;s7wS5H={_ zd4iX69g(vfbF2e&#f|`)iEly&a2PylgYr#i$s#sL@CJ>&NN7hE5mvMUXha1O^AZ;C z#mXrLe(ww!Q(jg*B?<%51dAZ0BF}>TD%3|+OkaA@u-JI1o7grZDENv_GL#VqW80~I zs1OQ?({Zf)aSh9esNIm1n5w=h*Fn4}WGrq%y~>nbTN&WAF?CR_)Zy`Cku!jU`}FTg z$u#!2!L%iUTu~T~)Qdja^!#&A6Ar~oycUNuX}7rt21?H*6{2t6hI>R9I+9%UUssP*1(l+gGz5LHv}+D*#pQqac-iM4Z~qgwUfpK3Pa9lA z>B%)Q;{Jor;cuuBDROCL|NRe25JR~~96;L6)vrVM%i5T%6ryhDKT0m4jRl}?|8cgD((w3o?4w}<`~i4Vgnxw!l?lhgGaSWS}M zntgtxqFqP zz%bel4~eus7|QO7f>~xVI0P=V{VMT!GCh5xyJ`fj7X1WSo2ahH`XL$e7rE4$0)a70 zW=Q_+FIkMCuRQ$-GVnKNQvU5PQEyrN0(`5#L{Wt=pvq7w1@cvO6h6rM31-WUQT~9O z$7fYK!CFYIxo!fp4-Qj$4mUSry+Kf^E=rFnPrW6DWx|r^)b7}QWW>U8k|2h^h2#d5 zo0AV?ER$~R+es50CKd%%Uo|KY(OsKC^sb6fwk}pZBwVU9r=1NE1h-0iGsb&LPN}8N zeeYdlECN7NXLE&%JvkxLt4->(ZdnuvW6~RMKwOsY!59hmjMqXkwU49$)~N8!@22Y( zr3y}t)?Y;mzKE5Hx8D*~LgC;!Trese|0FvJG)L`S+PCpaYo_? zFg)xP$p-#{7*N8g>Z$u^b2LJ%8B!i&U=(N)q|kP{^ejQ`ty{i*%eT`J-tz5Di{m6% zW}}&=3F(ME#JQ7+u1U+<=eM>yRzV0z8&G{mT%v8{g!Xn;eMy|wtTzmy<5Khwe5U6 zQ{>zE_U(K-YObu9;6MP1``h{UJZ89^Z{N8BRA)Q*v_|a=i6~Cl9j~me7ml=+RnE_U+_>9B9_XRkAq#IaCDT3 z?Eu^Pb{eVeeEaesI1T`@gCkwLFy*>%Ydhb*op0aHxATDgcD{Y*Nc?ub9X>pcysw^S z?`PnB;(g1vZ~6APOlQltyBFue^X=KRSk3=#rOK8EIChGvd$;rGbQ_27cf1@q$91*L z{1zUa=IN7lprd)`%xD7`C$rH8#S_)LUX`73oYEeb(ggg}nqhye^(VXE0cS_stwnCi zR{sFsW{Lz1S{n?K7uX<|&0Yt?^dsNjsCSvY5DD;lr2lcAm-M{q%5jGLTTq!pMYXy+3N~`eX)v+-*{~bhHIUIcupNl)&C<_i0=r+3IA`u z(G~_x^UjjEtf#1<-rD(opKK8zzcOkh8Z}xJ0oV?pR=B*xO{};qkW{b1#P~bdfD3E! z<_QmCShvB{LjDyQ#Wn+`;I(`MaqTNGUk6akT5WAJ(Yj9XCbpSQi=E-r!&6%t{9B+C zVBRQH8%34XI8LX-^>VDh}j1H6B-c*etIV0ATj-V|6 z;RUY34TR$^Z>T$ilC^>eo<0E8SPaks2lj|*?;rX?6c1gb`!@L+x8-8I#9_HVT2!`Q zdMFux#)-S6fylQ_@@ZFI%V%x+{SlzJuKz=qFyMWK4-W+5CujSkLu}FI6486i>N*RbSSH~F6>Y1S zvp%adiBOoyklWx_L-U1w!Mnh>-)8V++8>mpNB?GaS9Ua_s@iYWSY-@N-<%7FcC!|r z5=<@U&%dSD)6+Cq40(E1BgH@3_drBEpnRLlboUgg0z}QwG=>VI^6IJs+`W!ubSQS4 z0X)@C+?Wu&0$l(MxF(?JN_px%GoPM0`3a^&17l}!f>checQ>@9V z34J*1Q3XS?=X%e(uh@DH?+DIL2th1tNKi>YqMAM-H7(6)wH;iuM24$G=7}qJy5(8H zcVZ2pWzNPzVC_T3gL##^n@l-vVHEi7lHK75mm(p{WXi^(@%%s8nR11GoMw{(~Xt*U92!HXSf zlla2guaG@{Osu3Ez>=0gj9O#(3UC#O7VPDAc`41y2ik^J3e)#E38{=bZ#C2=<3bwV z>>{-GcF%!z7`uk_X!?9I;lc`}aa#;c@a#pm`8GwWE0)6r9WnOgP{)M92!*|vVMI~` zeZ$@^c9N$$-rz4gE!~f}s3+D?>z6u?jdi<(ODM@FF08 z-U;Y4(pX?mTPpO6o5|WIm`MNj0&J^jqL$56KlwJB%yk zj30cH%Jx^o%PNj^5p*Te!d2SQIOIzkX8fsvNg!TymWCm=cyN5;-;KvAno9hQkBWUi>G|1rH|{;l!$Fq7Og}O|3J0o~ z=@B#TJ&yLE`?h*BukoRiGRHnO#Ctlm33|0 z>J&=6^UV3~V@r-XSr@W_IPnJgk$8c~PKK{dO}Cv6>b2KYDlz;325q6y$`0zDwqlol z%@psWV$~_JV^4!&fc(IP;I4QK^VYSpVP*fze_q~o^}r#2ij*EP+=m;-Jzn0}7P#1l zEoTZedExnRtk-rmcqTqoM+XUx`#@iwRuU#V{GiQ{{2D7*m}YLQTe^5LUUIu9kHFx< zUcmHPy}=gMpC=P$@FVTh-FF2|5Vu@d)x2)dPlkl)tNFr02OE;k45w^KQ@NPl!|s*e z>A+Rr(V}Jt$qT15Eex`SaRuSd{+%$o;Akq6!uG`DLwR2iDj{K5WRZ@0+k`|6yNhHe zn_jVe>V>j|R0q1R%P?XuRZ_#W{qA2b3@Iy*rIC7bo zt4vE?az|=B^<=K2Fu>>z=}ld4EiiJmC7;FJA^tKH4PCs96|bW}nl+&zO1s!YlQ-ki1k6myzmIaQsakr!t3RAT2jdP?)Fs zy;K=L8D3^F&2H`%5fZYkP8=0ZQ4fdgQi|)qFR3RF-7oKgsg36o7`jjI+!K&vmJx6_Ov~W<&CoNx}EPSWpX{K1rWP?t+{HhYFLY9U6}slVd3{ zP}95Df{yTFsi|Lup66tSuqlS4=4ooa{GB2<-aI0AYNd)!wD1MGI8_pgwtP#;5NA|I zQI6BJ8<<;pV<yAkh`xrRCf~EzTnug|-HJls%vl;~5miW<`e*Rs6W!tH zN)S>Rc2u!0P>CvthHM1*IuhPI<=szp-F9oH@vtH+h&X89v6?W2AMPM2jWnjA?J|#8 ztc=AAB2{$5W7{UKL^Od+-FB-#I>J<;p<=- z2$7oy&LU7bibI6S&6F=Ofg~BA^4h%ZfXoY*#X_YyzWxfdI&*pe$~m^bhgr&3Af&0o zx=<+Pk_Heu-36=?b_l?S4(ekTva<-J*C$N16NzNZ_zy2Y@endTJ|ftg{9?V*-f#z? z>EgfHOc&D>;h-}&y}^gCiG zoc^_2I4Kq$aUk#E%`yrBy zaFmE7gAjqPZiy{&0F-%3@sO&ZXDbn;feMqZ)i2Z9jz0)cbiAwl`1k}l1-&cC}M{5Q@mBKbb{m}M@55rxh zDz=T&!J=}9f(7xI6FXw-Lvomn%J;%k=dO?w4D$xT4dj!i>ss=r0~8*bq;e|x;cdvj3E)8$8B|Iu5K!k4 z9>OI>0ok&euZX7|*-4AMbJ_+N$O85SPMr)=gHGj~DG1-|tPQ{iwDMhyxZYmN`bvjfNGZC8kObr*5Cu&F#EfOqjMlX^bsTMqVoS z37G57Z?T5+Bm8>D82F=Lq#FFf^Q2428_1^7`>rqhRjen7H?5=kypli}T0pMto~6ke zriE+At}hqXXaKE1t269UepEr+AMmf6e2lLG6zT?0HRmM|m7Ix@uOd5gml{aVxniCG z$qhDrU`DVvBVwA5zQEGZDH8~ZDd7Ow_X3FV%$((%E(l2aL-{r?e=uWa?yvlLlHH%` zitYROqd35lYl<-W5aBROMuQbZgslQk(YL}(`O-YYp;r!|D>8N{pn!S(HO0Tb(dsl_ z5$Dcu`6DFWSdT2fU6l3VALjVqMs?)YTMq0GpT<_THYe)F(qNvDQzQ>H5L?t(KnHkr zfUGLh3y} z)et;LUKc=)$8f57t^q(<_yz~T;1Z^#dhX;Yg*@INwr!A~f0m9t==$=JG0ot4of?>c zwJAf28~BmKNfrTj(B0RDt4e7;a#$?G9R=O#c|bp6Z~a=Sf}F7orGo$gGUyF{6Gya@ z1H#czAnQwVSurnA&=N&zP+3OKKee?ju;Y_ZOLR)p*t@8!$qY}y3{6x(M6C~(kLjFS zj3(7#xWV7fxJ(iv<8EMDk@^H}>^UZ$RNu1Z^4Y`coK{E$JYfP%mns567u99;g$d`40#&Hs+%+&!A(Q#iXZDon11{#y zgW%eKP6~&<7O2v?BqE0`)2`_oNQsJ$4DqBh9a9SJ5LoOAeEviU~tZhhrDw-m4d60L)B_o^j#uz zk@kMLS+X5)VKTO(+I3+Yl{7dr^dN;rUv^)h`JQ9ZoYyBGS2;U| z=@D5n9uXT$#C#)u!c6#=|Md^}4|sxa(En42@P}rL|FVJ`|AF;_S%5ri1M$1O>oho& zAj~s=W)YS;(h}hZkGJBi7#lxBxrK&AEC>LPe-cZ!rLzaGXYmMk!Macjslx9FUWE(x z5t~<18>Lnc5Dy7lqdX{!o7%YBJy47zX=w^UTJC(K)09nvv6cL05vHgrZ6J27lmms- z0wD9Ep~_;88LK)vj0k574fK;sG!{&G`V@63ofb{GkCdizK0rwOIDIV4&p37JasMTs zW&BCELu;b40+EgygcGNi{s3x2UYxLN|QGS4e3~9TZI2qDU$TOzWU) z$^{ponS{C|bEjz&GlSJCkAqf(4+Ca@>TU!&^vA+k2pdl|LD!PxOCDsdKpO{1ROVK0 z9K9yUM^i-r(^itI^eD7MYJjV+8nr}5dLwz;c#u2OHdJ6JXY+ULSD|5B^x2StP@_Csg^V?*H%t-jj3VW^)bX44DpD z8$A~}Djsib2r89o9MNc4d#iJHHsGpvK&W59vRJ5Npc3*$XA0!P!(4K9-Fn*THyn;c zwcH@i>GpM{Q-HuyM`)ZDk=hCr9wMm4&vl&=r?gxn?U1AvPKA za=7Tn5ie|Ip$ycS!$96^D+{TNhrGmA77B+oQ(zf!GMj-+(N-4P%0f={ysAtnGKI|j zZ)KrM)iA5)bnI3ZB5Q1Ap=DxSHg~G@IEmu?N*}$Ig_zmi%0gRND4WH$vQX4pq6|t? zvz3LQB(}0pocOi#v6Y3QT=IE#VPqj_K) zr;*0Ft|;rwCyJ`&hbN10!h=Uo8+1Rtx|M~tvXHX^V!Nm{t`g7_-th%S4_8Vq2aIT0QuG}q&6Egu%JJLKJL~nv~>%ir)cXILgdU- zjm|N!qBYusc;Zn#G=9jxwr-)VTL{OPn9~*YeScxN^k}ema-E@CMmVv-dS}%Pyrjnz_74ZnRowGiNG=49l{QDzA>4@0Z*2 z<4$=*Ho^Lhb86HQb*+Nt=C8M^kc9N3+Q_cDat!8Jwi!ju>5b?#ijJ|mXHJNhc;9&4 zY#lvnft&qe9Paj{#EV+zH2Atgt$sBPu1T;k>RxpB#bMr8Yswn??Fr1cUx#nMRoy(C z_qEr!f6#-!`Pbve+9X&>K_4LdgK~|zQ*ITq3Qv-7CM+rhh$%P&_;9}gayY)LsaJsB z9GpEyEO%n=F{+v?Z@D=`)YkYLJ%z~QPc2Q+8kuVwqh3Ugxmrm{i+t(S9`yn)v|AlD zcSyaUX4q-q{=_#oO5s$l#-e#h8<5$EoS^WkDx*(`f{xtM<*0U>-g9~72iuW~(nbgR zwWCITj@e9@Rv3hyYG&#}j=rzat6E*TXv*#B|Gc#(JEH2KT+OF#R0(#l3L4DR8#DJJ zt08R#kK14;RaASLrHBqXTf3m!JlO>ct#c;2HLrpfchi5ntg5Tw>#sX)V)jOInm75{)yi?R|O=wbDVP< zbaqj9nr54F%SGoI`lp_(TL7?kIU@EU9zwjR$|ZRqLZ3mZ_`DmSkRAMYyqbcuRm6n< z=$eCl@4myIeyed(yVS@U*1E^CZ49d2&1Hi5>$}Uktb-f(U9VnciYeYY-#XalN^Jt2 zJpfj2AAIgvuHvz=+`;(#^kv>i#Y;e~74*WD$|8`6FtcR;_4UQ&<@Og=WNd7J=(Q#S z0|IRT6Ibh4yPFU!~eS+^KkLSlyux=)u{ zjH*xvCakU<%KHq>sOJTgOB#TFWpcQ^OuNpj@3c2dv3mWr5T@Tg5xk9U;>CsaY0y;! z;n&KY6N_lCep6qnq{f@Dl=U7fpK-i8xFu6rwo)LzJXN{NaMbt4^nr*$1J!ZqgKm^q zAzJyeXt8`=PZD@wN=O^FEiu!oTNELw7m5K!3mBZkcWRF70Tai~>$)FBi?QCxP~m-D zU<%Nb*O?p#>7jNM^r3bzzSUNS7jQ`Q^F~GAz)1DiW?>w(jtz(+!J>MVdIGwc|oF-OxA)dgy-NW(AI&5XsQz ze8x?W`r19(@;Yg}yJ3Bo*u?(RtRkK=CfqgBQTYZ%prSQ{O8}nB`yZru5|QkTL#?-^ zvyL35kq;^Q=e%gn62K5)1(&X-#tOq~?P5#GX5U@j&9}JP@c{1aCljPr-`lE*~YObWb_Ihszq^dyazB!rD7PM0+%SIDmQTzh?-Y~b;g-OubU2#66v^RWH(RTO?wL;INuO7;Y*6khoeCy)?bi* zXl5TX9AQ_WZ%Yg3aA*N9pkz>=JFzkV;=KG}i8p(MfL8h_0$7O*69OY?aoPh|G*2KUzkV0TBwu4$B!-In!J9pm^!wu$2t3R%7u8? z$7q90tPBJ~52x&at>ZrdSP##?f{ggh8>p;L4A)FlJo?GyDQbQ7wh zE!{-@sd{;6f6zI_C4_tbSadPn$0>D*iHoGzX)0uh_5CVTnZmX_n00Gkl2Ccm<|7J? z=Dc0py667RoQ=pM4qc*v2@ZHMKA_=)8O%#~{<#E+>pQ2{Vf-f_x1`fcFM6#S8+s7( zuaH7s8Bm+sKoAZpmc@{|)$?VNODL#4@?uZ^$d&W&{Pz>JMfndr$*Vo{zuZAS=6^x{ zG<2u7a98A-h(WVQJyaW|+}qGgH!gm5u>zV@?0e489R%T&H)hMS4j zAV4m+U}!tg2%sdp`pg2~mD- z;h{-LB;5UyX5x?zT}(v_l0(}~5~4G0pX5mFCVJ2(t{YY}2wOT4jO>~$62-)@!co_) zmM7^Pa3RY@G)&r{r2uJrb1Lh*LkrND5xiYqeh;8h0XCNw<}4j7s9|tza{iJ{0ff4mVh2!o$d3>{TdiApmhv^m-! zcPap_IK(-@5g(6@N<-wA({-2?Rq%Yk=HA3RYsPuQt64pQThX^tX1U5vCwBD`M5NX5 zy(Xspa6Q8N&@X)rLmRigE^c&8Qyp6U+i$vUm>iM)nn8|FDU>(W-4m&Lr8zZTmkf7% z|4n#uHXRB_lJdM9A3&sHn7f=6G^RV!V&lT^u{F^bFo)9s*``&fIYk&F_6Plv&`_tE zdjuEZC1nmHM?qU@zCo4nOQntGO-@VbsC560`UKb*^3|8=Zf3NGsbG=Com7hzq`ejq zosSn(%seq;7S#D-$A57cVSV`CSKKz9%1y;wB9#_#g(46pNu*H?L5eqk%m^By#>OXt zFu20Ph$b7?W@zZ$Bo48#?vCMD|4!Fne@3D(S|522Gk zaWXLA$cyk=fhTMHaQV5!9jp8O?=2B>5&N#+S%ZaXcX}eeXdWP`Sh(mmJShp9=cyL~ zh&D2G!W5@bCZ@th)+i1d?Btw2B53R(Mp1T&Y97>%z{FPPI43)cl~W!VVQRa9j$ET* zEx@lR<;EL$I3-3+2UF&J#I@JNnE5n{@Fcs&h+LbLhX#0c(_=lvF%yb7mR`tpxa3GX zTH+^}2AfWk1kiZqGbDOt2cuT!xsls$4T#t9gH4Hlpw`#l@f?4`DItj^arm>H>d-z~ z60;;d%#uY)yztHNFYK05?{*d(&-uBLJMWkTiHtWnf%>9T*luEKqmvi9uLtC^w#`^y zak+BQVNyItWn1GmcT#~KfRWc1REe-`t zk5mK>a>fIOZkP{{Sl@~lLHPLnVgDLTkUz@54*g7{MT_C-a)efex1<6uiNFxekZW3d zD8T-(^+p%^jJ(p_9&Byu#lZk3D|5*3Ns$rO4C4D0tAm&|)}F-YC(*CHg9x>1aYWQs zupZc=ol#6b=bGnYG)CJCj@idZzqZBkBnHM_ISp1?yXb+@s3}t?u^4>SJTyxAZ*sD# z8^a#14|?lOB$Xb2jHTSb;cffq1Pj2-Lm!Db?AlPA#Cy7*{z4@ly z;cvbHrKq%lCU2VworrzYV1A2H{vVuwjikD9rZO`GAgzYlD7oS*5D?G>5swE}kd2>p z`pG4ItlUk;7Er&L1ohYqZBmeMw?S$b*gHoX22x@aQJ1pI4$=&?Gad@uTSyL)g$$R_ z?l8CzjA{?q`DYn z6?uYu&p($WSd`f2&{__w{SD1$1ykw6%%y{-*o<&kz+d#R1!q$}=tlrv&N;^3ab4D7 zq`CbZvNF6TGZSwN$iOzfm`yEH<0S^+Ejg}5l?5A_u`Z<1d;ll|X)Eser9^%|vz!cJ z@Cs;$mtNGyX|5o+2t=fBZ-t(-0F-;e|>hJ#V1_kt6u|&p!Fs{u;{@&>YYa zm<#C9|NjXR2^)_8ES`9jo>Oaucb1W_$3GFSSY%pKlIa;KgF*4pHyGmw{!Pw|KPg_6 zkaWhtegFQ^M~2%80d!Fy!as4Rerc|MYVdg~z>F8kJq0Z_JhKQUm~{fiRQ^s|ua`&m zWpDuXfsM=LAdYN|*E<8G)?Z2JYxfKzp($;Dz6`!MR3D3f?%9-#P%^m?<4wQVVo0MZ z7W5zwX|6BLs~kFW_7#_V1Y}!@IGb2Y9d*jR&eMC4>w&e}C-YQmr3a{$?QYXe%;bjU z)H;sYl~jt*PT;jBlrrH}Hk8MV!n}T&{ z#2Y-nmTJxwPu%lMWBB0@>IFg(93<h9AP|Pr_Ub~UnFX;y3*tDMvn5D zoIX2tbT@&TBSJ)x(A`a+{vAHZhd1@iDRoMneZUL(SoAlZrf%93Ikg@a1GZe~mJ4;G z-Ifde!IldhUVv3SO_Oae>=5*ISdJ|h`p|=0E;Jl;rM%!mrF%r1C{qO2Ox`TBHERzA z*dK1WP*7^ig>JdfEf<;zsx21^-@du_5w~o)(C|#0|6v|(%Y`D)-*TZ-YGuoX-s!~1 zmJ6+f&6W$@a-qy%Zn@CEY`M@a7y8zAE_6E=x}6I}ZZm7Gqd=c=*Om+2&V}*_L7iBN zY-USzZn;p^Mx%iH`Dfd?P@EgBL!WVAn7QulTqsoSb}n=~7rLDb)oBc!+1Sp7Zs$UW z+qsI72>&)nOE_6OSo}K#JxzIe!$%C+4 zF7$tBF0`L%&9gu%)ww;m&J1OB<^#3P>vK-owh z%};fX7l-zLa>;=E{Ik=i=IapNp0cOv(Z{*kIvKV*y!F@XL~+cpLHMA4zt-W6*L9RS zjw;7x4R}qQFD{C`pS?~vfgQ)M3B$JgtFFXR;V~X<2K~zrwzRqUYp+=yo`_^}gt*qJcdHD#FE7}zk3caQS4WFH@I--Q4V;G}aO?AuI_dSpMgio?}yD7Fe@ zj8Ql$pA96SCWwgR(}fcwafo}2oIeo}6wlGY0d2IK{&^Drwp+!qIrKYTuJFNYXm8?a zh4!roP#9ytj2MtleK7#B_|bQKfVLB~F3=e&7NOc^HwtnI>N&8dG`MuSFMk???#&x- zur09EAxXot`c)k62oa-WbniJ(?_hdj>TP(A35_-h-IBbo9X;>hnJfdpf-lq>Hs1W~ zGj8FC+k$xCGTy#}X?!}$$_lu|Vz0mERp?kuV7k|Y0VXWu3SUW3=l5r=bLu41RccHH zEwWpQc(^#>*`3OY3NkGdUf*dLt#`W^ve~RFKcOIT>AqxVp9J*6;76*O2>j`CAKhiM zfVy#u2zgasG}(N{4kW^pTM4X=Dc^9=It|ijW}igcS27cTqn%3N!5&;eE?|iu+7>z^ zYVK?=ni=e}pU$4lnLS{NB})*K)3~f9-WpVLFi2PIy~m*XJcRbS23g)shZq9wqX^Gr ztJ6y{8GyH^*#Cf?t{n?~S^O2U?lY8z0+q0+W1(?V(>pl3#YIj zVuUWNhZ83UMOY+4R0-0k9ciUwdXHFZGRtunD zS)NCLSVK|@K2%_QgC&>g!q8R47Ardtdjdx+WGnx?RKH&BZho6|QCZ!&fo9xpk!_k8 zuQd-mEU$Ib5W<6bi{=il)zjhmAtkxPL;AS+q>TK9yZd(o5tgNQHX+OzVngNH38CfG zH#;{ibnrf{Hkx{AxjIsd4jjytQb#E8LC2iv(o>7LlOn=EGN){MX0vVvoU=Da^Q=8G zN49y3Pn2y=yw?En=~IcXe9Xx{<$-fgu^djIP>5>`N8kI?+qJoJDxZvV>f8r86XWe6 zj`%DiZ_nV{sb)<1aoxt#u?k-NF(YYFJZJ-l9f3HotRYEKB$7u$>FENG&?VQgyO>#( zW8)K&ah1o@MN1(dEsektb|3&epqmnc&-?sy;g}`)bh#89G;1<3kdqzQVIcVAV~Ec?7VM?{d+%Mx#i`!^8+-5h zXjgIMi{3CZ4rCC)CPxtj*yJ(6o_FuR{bn37#u;p)!DNvnA(3^!^M0YKyLzp4_Bq&d z-+12Z^Fi9XyE;|ss_N?AZMncX==LDg89>Q|R0QUaUlOO*cL33)eZU0ope|J*kflmF zB&}o7Qdo?Zw%Mb3TC+vHwX1VJCP{N%-h6K5mED^qMG^r&=$}DPASQ7t_N+~$C3?)H zw`d$qfv7E-6bu72@Cr&vUiyKgl3>i*qpPP1p^0nt%LOW<6pTd_i&kPy3aqyh66${u z#xJqNBM2ge)uRMa@sxa0MI0!h=@0|J=Mq$t(;$zqmsmm!VW3b%n!xV&Til%hAL(?U zbjeeTUPtM0?`|{D+% zQCow8Q=*ladtg^hbz`DvV;*U`yY7Umb>WFigJXMY+}gGM3Z9zrl8fEMufr_&ntUTh zYHfU|Ar?~%7z7k60*!Y_U&MxO49d`Is9!%D!|8UXU9!7+<$Pq>?scAoo__l{QDj1B z!J0a!pvBtG>*MR$ZLb|6PEB8J)f9VLwv+MP4kh@sa3 zw}Q6PlrtU!U5l0+wKS!i+<%`IN?i8=hxizb49!-*F~pF9msqIN<0s`&^1u=8I8x_}2-`J8xv|SqCTd>X`L^3_r4jpSgMeqO~Sw2qm_HnVdEs)?d z`vkfggO7b&UgTM z?bV3p4N6fKp~fBAoPH>u5^koHx*wX}e8ctEUUSif^wb6#;sGGe2L?o%VQiawI|IvX zDBnVFBBHTh=i!(nj#|@xN!3*hoYs_X@M5F*EIWV^n!`&m+46uEmX#Pu0E@;Pn_S1H zZQh~&yVbn)x{plWFgU}Sq)B{5^PYlG?Wv!4;Rzd_`#CokfDbGlEfl&EPQcq~5&|i` z)%Dj&*F;3jJey)99Z_A89E!_9v&$}pOj&G)hNJ z2+0auq?_k~A4Pi9ka3!9&qVWYuLNNp2lY_&q-aTnP}H!^E-&T^kaTZ=}5hh91UmdIX|{c>+8Ri&+FVkx`)TUK|% zff!9%?j22|Yr@=w%E^!QCvod3m!U!;mq=-|zHZS%ibK#%bd&0QA02$gqhx9q0>wcx zkoEd&uh?M>n0}}W{5O(!eo;pqh-37lVu|1N*IiAv##&;qVBnA+N6pH4Fsk2uM-iPE`G*a}-ORWR`U_jvwB{ zbd#EAISp)VIxLJ=Gf6S>1i1~!P5n=+BLhQJnWC8VH>E?0kzP>jfW-p?EF;L*TgzCx ztWQy&q#y15DNayBpW?+^nHLy=2d~;oey=^9hex81ly}N^vqf5{^AEH!Xkr|@qi#Er zreOJJpOGCx+sHcx-|Fjv!5Mg4q-{ee$y-qwO5e6J`ELh&W_l=p=3>`thfgA$XMT%5J1KowSLI_cG9rT7NaZ2%5vfOjkoql2I> z2!R25eflWGGKO{q1{GG>1dQjxJUtAf7aZyGer{OI;ixMHFDu1cZY9qT3*&>0oRmv3 z8SW zFre@tKoWV~LG#%hSA;Tw1x$csn+rb7j#L8vAm3=N?3Mf?R%Qu%$O018bITIUu}_g! zbXH&hO&~p4%!yU!vR}-`ctHSy9y*60z6BumBqcZ)Lk)lcGR9gA8}ST|8PD0PWl$Fp z5%$bp{nmY{jF!S+tLMDfR<0u;a^7vyIpd@T6roDn{j=5q62x0JMg}GrEy|jl5kp2OUlzMGMi1Db%nM@BkijVe zUZ540g7RJ6=XVC2qX0Xp7;DiOK$dfbSPybecz0db2)a?cB_hW4EG$9a=hbDWs=Oya;vvgtwLnW0gH%Xjt&T1-%)pr)VVASY-5csNu69zlDILQVAZ$N#qidJ-vZlsw@C zw49}Me>5fHI1ngI2n_(nOfdhKUy?IMC9g=LPBAfaqK(!Sri^F4oi9frmF5$f#)B-GnF&jZ~fs3et(gC{QQ~`1JodyD}yxwnv#1sg{ z+MHs9zgdhTx(NYN=7b(xE8c(9;W^5WnxV>gst^C%EJ%Q*e+Oj z?pe~^o2Y0m!6V@VlOPZ}srHn9Yz7?P=>t$q(U43?OQsZ~4BEzdrp5~ys=#WJWGBw> zm_S56mOuJPo0cEg>GOH~XP!q`$>*wJ8lRrX!`}l)n8@^61=39JQzxmVWM`@;0?M}) znC*BdRZ1k(6L?rt0n`|Q2zUF`NsVbp7>Y+O!48^sxj9L`0X98GjR0)W#3odz&F;~Z z=LXGyXv3_{VojyQUt`^LK|3T!HHKT8*+}1C-NH9ioE1cMhz9@OO9uiRNF~lo5LE!&!|czJG+ZPH0%}wnbtH(^uR<-C zX4ZGH52*xufEdx5K>;L{f64HanAEz3HRqofB38nksVgb5J=iHbJl08~EJK|kecUu| zaFnygI5#4kmev60&-VcyyoA1iB~De_h~(HIKXwc5qhaQ*%WR0a9>TO?;Ls(RiV8!( zWF}y-MJy@mw-OIP@x>PzNHpU3v_M$TAIMQ`()FiKMl{m1LLEp%o>4W$2K0|j^ZDmu z6OE{2e>NVn@X5y_b;cXf5oe;X2)GcE=!p8+PxwB3jW3F%HKa&B^yD~v=T!w0TOdwr zyzl}&YY)!;{Id=iLB9;Z1!U^kAv9=~RWPvn+?G;ueni^F5Z*GMntFSe z3%-}(Sz`wrM+#^WN0NLFPS z9GDr8i%4Py)Zcn@=EwP1dFIFY@J)v%ao#7#ZRW@2fff&iGwQB0DP<_=7<=Z&&HOmT zh05VJk18Wtq*5MiqDy1?>)J4v1@%kgc&V0Vb4b+w%TZ05W9*_LL<(<`oTV!lb_vtRY zpe7dfo>#Y637sju4)bkXWX-G7NM_`OdpK~BJa=u}>_nd{(BVoevM=+3YI*5663>*j zste#P@zc*O`8t{)mS={evYF&Lrp zW{$?0#vKUVC-5qP4yA-#x7Gq!;^KAYC-XLdFuAx|4D56TH6%1f%D?WKidPT1W2|_T z&X{-IGIrF;7e1ID4qdrk1P@wHom2=+kjkA_mU3`wRg;RPI$Y++*x{n7Zo%%QAe|eX z!dEXGD-g#FM_+lygd3FSx!;}Rz0FI}ke8Jys4LB+~nW0xFL{aC!VMUncdK_NVY97J}xz ziWTfUOVMK%5vaBEfc$FD6YS_BvZsnO{3#J^OG(^hHc+DtAt?GCAL-Nbg42C`4F^>% zUolV>&@pKj_8|gG5mt3L+h0t_3(tu1Kg&QjWa+~*Ezn(Xtcu?6ur%MQM#5ypJTAof zSvaka@}UqgpgJ!JOTM~iEI5pTv6N&Eb#;S@DCpfM3}j|u7M543G? zpnVRW{b}Kvoi@Y@%E5pAs|;0Zbfal8dT>MCI>5b$1lDpCg8FdxtFy&qp;s5 z=dJ0yQSKK{k^hOk5@Yib;<#1*e+wB@C%S*k&6Jn=1V9$$lKd*JRL-5cTW%;^>{?te z*XzkY5K|r-u9yoj3?B9P*W5DqA}HS>C)nb23+#}2t-XEM*s!@|je`H;u9>c@?Ye3% z3>Pf}d8Oq>(xMCAAm^JewD=<;*t^LkV_kpaEKXT_G%ALCT^-rH(Gf&HhApOz#h$Ttod4xS?{es@pEYb zGqP^3tBDyXEM#mPSf&ND-C);H$#2$MdLE;(9xt(@B{m#bsNi-R?b3O5iOF55Py2oL z*snIPwRgr`hwj;q=HliR_x9j)x#(_qx(a6dxbe@oBTAC3p+YdA5CdhTj-<%-J?*yD zO9n#F03@R)x@lW^r*$&h0XO3NLk?X)Fb4PoBk-0c{D``3tvI8Z1>SAMnZ0QtR@<$C zbKZ6jT8kZn*u$s?Hsx-}E2D`EdOYObzQL`ANzlFAUfcda(1tPy=q6;$T-)#hAgVB> ztiV*bd81#J=)!A!e~g^vlD)EZ%Ev8LXB8(1ga^KKMfT_|1xnZVpM544l4!MGNxADo zsXIBniJ_nYOb?Qc(9NTY>B+lR!{D@Nn)K_0L+jqS`}8qAU&kLNNlVKE-_{y7@65hA zspR#0T~Z93EvSwQxx#{n`t%dOARwdK&j>VYw^>a@9)|+*(Ta9GhDke{3RiXjkz(r& z6QsDInc^~Trc4}8uSx|QE*Y9A%@9f4jsE;IVo}y&F`ly_=P6PY>YkMp6t1?^v?cMu zGLx#WBS=JU4hP=e@_FY5?0D=VY#B7%YC1pxs*sSKRnEA0n}n686p(V0G9#Lb+0vw$ z0#*jA(AZ`yOlmIiT7lKM+5z)=vS8bFqxFS78|Ri+)=%=UxXL55iJqJOf9qosjH~395qj}d*_;sd z8-_f2!b+bWs1bkikaR-bI{& zSau6oj&fG-r-o>U+JHc^C(lk(&odv10NEF81Q0WU7Y7jZLfH<5$SzFupa?qw2qYI0 zOmDCUiTuh148*xMMn-395ePzVjPB!WxPK z=rL?R#~}#cf&I?i()z9zBcKL0URrh6b=-4zqV?Cjq>jSI;R(ZMh`iOu29J%MhJeJC ztVOPH;0iUUkVG}*pZ!VRVG^JzrK-r3gd{~h4^IFF848=rOWKR^AwlSemJd4uh^s>h z!AwC7Do~2CPo3my6Q8yh z`+D#JNQQeyDQ%>BB(mgpqFf*_5smshYA~C(gN7%5j-Fw-bh3mT00pERdTG`vj1qwv z3(}aRZjMyFPjKEj-!vSgj|6TLDrXgbN7hFLea3oh_2FhtQMIKFMK?vBlufN(mHrDk zo~0Nuiuf&Bc|HvDpbvkEcy3c|57|K=mG4}Xt7o|X@y_z?)d z2mABKzyFwMXJY*HC*gJi=UAVY9R6{S;Y*5aYN|U7?0|y_p`}}7$ODhNQls3@<)ccY zxtxqcRH6b%bgxhT!gZ=(lRFc?zlxEJ-Y7*Gr+v`}nuu0_urKlPBhiAfT_XF(a_t}{ zu>7-GXND>&ClqUPi|(ske@jmLvlw{ice~ye~34*vpy$&85vCMKH=U|9)NxF7x>BY zB%WYVH*9V>AQWPT5p^8X?db#?8NI%#0e* zmRUE7DM$r*9-KST6TalSiM>W*mwE!=L>T84K`>3|(@zvLCZP;O)y1i>KgbU^91Y41 z2N2)a(IZurY(^Q>vNp&qRMXUnoEA^?fj=tr&p+cNxHI;67w3}bJzLns{J~nImOK_5 zM7(l#<_FG)6hny%@SV)rzWm||GZBp&7t%R3%d19Kk82#;(m2%j84IEmgd?Gszo{~y z3d6YZ1hpgcAxgAn{e!ewwl;QJNF1FoG;GjIMG=2-2M{NTCa#0Vk48!xOmj^8$*~$b zZ807CHW{IT1@#`$BgK`DU7M0A0@&uF5yuiHbXQ&mO+bw)Ar)P}6Fg5tg>)uIxyW0W zOG1K--e6wmpGYzsj=A|J*LU#&tBIfK>$?tN2piVfbbQEo=*zH|$2C{7{g z1#NAof`E!d2yy95F!BKi*~IAFJo^Cy7em-0!X!#56hRxqetnz*U8sn5$orrp*crgC zvS=U9-k)ElmnWW;P}7}}XMfR!y%0E6&SJ*)Ft`+j+z7~0U;jJCR7#V~@Bz13le6Y` zitu#v<@|#2rkno_=C5KSvwu0=e9M~bUZX^NcPMxZRLB@~J~vZsXSu~pH=nSHE^eADOf6s_zjh`L@kD4X!;AR#cFTUug~u^Eo^R1H=j6}ZayLd)6Lhf zI7~O+3i$D#O*cQ3EB&tN=G$wVZhksR)6Ji5zEcyXo6k5!92fSuO9rrdQi>3zxFnrM zgg6x6U}?r8Ayo4;f1c($a|)ggot_N9&C`54b`A|fsw6$wkP%3L3`LwVU}Be=r}^_V z-|>NYnm^rqCICz~f4cc!>Nrtx*6HRe?|?dTy7}`o-)A)QG@mKZ^ECg3=jUnuUz@a_ z=>zKw47n}7&CzosG63LXQ=Nn|!1gYE;hxj6&a&!|JbfIOoi_pe zGEwxOQ(zhphZdwVyZ=} z-W7nEUtcc}T%R`x@~C^b3CEfAjQoBBXmOEX)#i=fD6Ts}@UUJZTvr=Bx!Tf5FBgW& z&4P>>Tz4(_r*Vy1i^D+N(lD+L45?Elx!6xx-D(T0h%eS{m4|igjy`To9qzbVt}h?N zpuT@_=P^@_t+GkKgP{AQzJ{Q2@+N|M7UeUGP3y8a$p0GAzd`eV2fP7 z@wyuD?gJWV3CaI0uT)E5w}t```7XjSL@p<*dfk(F@Bwv0G9-S_Qs~qTd%+C~jD}eO zq(P`&wedC-DbyFq3CpZkDkMaPS^`RYTTy73ygSge$(6HVeNlr*C7RR%GZ(Hx%0S`Xh8T+^&}B2aIyR)m;}6!X{2I6Fq-pIRi`Oibjn&?Fsz<*3w$_rIo8S4*1vrD5=Jxc zqSsy@Q`0_8FkZE7iZ9UX#fmD47Vk{%`Q?|eDTW5`T-Z?QcFI%7My9F!=|#b}={Osw za3WEm9h2b7JYCKS8S}guBIHOpB){-!1tSNO>g<)A;)DF}m#Z|InOn3){Kg!^Jbe!0 z0m$>m45-HwH2r|EGSoF2Ltx|#ECfiCf+0m2EQNY4#WK>GlRL8Nv5Q>nD>$+6Eb~>I ze3IniRxIxEMFXN1Ta7z`O(#Y|=ZU~tXP#PCezAvUS=A8V+SOXmgcxqsXiLt7bj4cD zfEQgz`bH%g1~)?rRndH(daWkvp7=SN>A9ziFJjpTr?v}tn}-n8BOjb%8{GkV>{0B( zE=9+dSKRG9?RvVFc6=9 zm3}+nepIY03tEdN`U8`1ua}UevLP6$&73nxj?DVxM!lHVAd3J&6Htp;%L2Mc=Ng>S zR)P1suoSaM(7Ky=p@rO3R|&W`jy^*5X&wZn4RQnugr~wb)9E_X53)=MCS1;@-**yr zhz?dO)lem-G0Re0t5{-|(9}8EA!I>(R@c?I5b{v8)xZ5~lQtq#hLWiy>*&O=x|C<5 zoiBnI6kVP?(f52Ux^zoEqG@Sh!je#B5(U0NjMO_pjIao{sCSM7W;VJ?iK7a3TN$W(`5 z9N(T~Y|bQBZptKp7NEN#@-KJw(s6*vkTtR&ifmXAcOHe;)^L$1Y?y%S{9@US*#SjG zjH-=~xZrwP7iq5p6*T2}!GJ3%h5!|^YU@k?gG~bajd4CC^ zuEHQP&$(LB#@6|(L(`GB%pTIz2)Jo6;1!}o zG8Vc*X+sU?OPxU~yN6I7LWV`uqJasJ4sjC-Bx`rc*bt#UYYVPD1sfwrtCf;s8%#Er z``bj4Z1-!fzT$Ec&P<4LFgr~SVI$l)iy2xT24%!Z2OidG*ULOhu1zz1_Z__!Gbp8^ z5D^*lvpf!!xbY_+BSVCNOhpGnm2l`lYY%FIM|c_1&8WUU{)l89?zKHvFZ$`4-c{D% zZskbiqJ^&X)sR?Yh4aqs`%}LPsXKY?6_Wpu8-YM;8?*{yV}T+E@8JO>MAPWm#ic+J z12y)+#V6d2t}$r zW4>2N;M-DlRgeQ)MBGSi6xzIua*@H61`8Hfn6t*-mp~zNgewtx@9EJ|$|Yb-gH)S` zzigQA34Ox~_QNwc3@DnQZMC}g7}J${4tm8*q#`1yFC=k3fRjoj3|5mdf04ix0FMIi z#+;(f3hD~Ms!{lnG4D4dz7Tv68ZAzpBvVL8$mZNg9m`h?Ka0*Pa!bOsX^`(A=eTUEXTY5P!qdzJ>4Z@UPHd6@Ai~WfBy7frvHhws z=SK$O9Muq;EBAOyEo)jtm<9v|jD?&EHgOj5D?Y|2800jZScs2V?~g2m&CPc-Jt2Id z9YbUjt?^ZCBs52XFfTzv0lVai(L}42eWmDQU#NTxETp_PiFMX}9jm1gfc_qDug8;Q zM90X+L<~24dm3Q_lI(=E#1O9p(Nts#(zqel+-(4v*O(7XO&GN#oY>EmGT>#Txmtz^ znYM++GVJAgjxBCC4)^Q`aeUPCH(y1E2W!mh;`L5C=`DQdyTNf`AkY#bQYl&|eoCH|ml zynO7&#fico@PaYh?5yvCPH7L%c^`klP@hI&->_C`-4#atRj&#RORg z-lE*_{vxD#CY*(E;YI2rqhT0d>r;UyMF7UYRDyX533{XjJBSKlTxhw1)N|(2`>L~v z#p#Qf!Y3w8%O%^MJV8Cm^&?nB7K1-d`$YBJOdQc-??TG`st`(8_5*zd3EHRf;rpsI z2R@dcgM#`Rh)=G4NQH@OQ~IADqQYhcwQv$b6Ya*J+G%^XF)lFE=)w#50xry+y|ZWB zGk&gBipnN)_{YWk;W*g-Y~0wf19mW*`|k^fNmlv|*GJQ26KzUrSe-0|j~;u}N}p=I zMSQTVY#Mt%ESetf!I)S9bSpLknoXRpSB(9O5`6Y^Ew+Ssu;GD_kV4Sdj;m&>m=4WB zENqPC1nGs%2UBn2cOP~~a4ZgHm2A{zmP-OPgvK-8p|E?2k${KhSwI%XfCOMNQp1`O zrr}rd9C?smI0t@K7yLqD1aAgh`|@SjNg6c<)V#PMizsQ~7N>w~Ku0oB%nWBv2Ti#R z|4NH)@ez3oqfH^dC9PiUEDDOmuFhBpC6s8;Oz0gDHsb-{5s?i*!Qq$>CKEG(NNO&? zYq;SMS51Ct#LJw7QxY))a|HYr*`8}Nh{~dcGPt?`rM+aeB?%)~L@&gbC14>r!>C-* z3CAlaKX+mGUPdPpbO=ts2jD>@+@2bXAm|v0K&M=K_`6s>V-EzQ!B`^?08H}ILxA&T z^G~B&Un~L+3TM;Jpk>eJiNyvq?_Y1;4y=BqOhn~btQcMaRtRzlt-X6#(WpWLRE_o| zSj06~*%CL6!4SYN24mcS{xxZJ-np`D$&QK;o(jiZ0AeJP$Pe^8|bPFVp|>MhK|-c{uqE&a;o7rt{37xnZg^LJ9Kt=gBhyZ}a4No;>RZWEf9o{BE8++XtH`&p3LSC(rZb zd7eDaljnKzjI*M7@;pzTUwD3=JkOJ798Ldyo;>@sJ5Ix$fzmRwYvOh!@9;EBnWqWr zgK*?(`}{pm#0$7m)s&j51dBL2@7diwnLTqXGLs~a)#LnLIT22xmtU?^e5HCYH6hRB z{YXh9gL4JWEO)|*dp4+?d1r(Ne_jc1KNFqx%uIU_#EI0`TwQzdH3X-Rb86#skgUK2 z2<4KaR(Qes@>qf=fkIutMQ*vriF^YjK$J8qGziqWXD8Uybd76;8?3=QPYwm-eDX1x z^5Vep{o0Du#FP_0vaQI5R0mNYE!#+RFN1%Lc~w@)R2K3 z1h4bTZ(n|yTOdK^?c)UQ_jSr;orL}t#z=UAGyf~Pe)=h92n!p`T=B&O!)hu7_QaXm zs?;J*@k>) zoEg*uHf<3$d(8HQiBB6@A93Hz=$8DJ{^MM^(XOZqsWPb9D^NTMGzzuK^ZPB5^DKU=d>D=6U%)6G%WCV=_TC*EW)RuPmfHVH6B3iE% zJSTek1FsKA?`@lC5!-FWQy)C@N2Op_Kv(*!Q-V4WggymgUOccos%LL5!m^nDtr`ku-S_ z5gQfA;+l551FZY8p;|!7afk6VcwH=d2oX{4vF1TuvxW*jFouH7rF|$B%8ky6XN$Y4 z9g^g6OKFO}B-YkMNwN0k%Jz^!WzSM*RwHR>lZV;knrT~^eo@l%;{XY(0rK{F zQrubD?f`7Gj>*6zbZ8B9X1-{n-h5Mnywt~Z_bWtdr%!Oo+_FXf>U1inOy9-c zb@NR*$?+ARg8lJv9xGeOVYb6~R?1Ix4EB@!wxGN2lyza$OC$<118+Z$UxOa!3HHpf zAmn2!W)1LC$YT#F=i8G@XlEgt=@X9XF?Q|*Yb?O|>#hYm3{4u_d{;QJpL?wpT7rNH zBq7e`OtCm-y6E@4qgQY02^oOyQlPrnQL&J7C1sN|4KlVYm_?zQ6PilVs|!A36ZXxy zHt3_bBz!kbgc7qn9QbNKw?K60RYC<(#x^8EXg9o3HpC zn2@dQYT4HvhQpZl3JcIm030u*w17iHEpCT#0uAuoa{|;xvmyfqrV>kz;@Dw7x#P5n z1s^l9)&q}#44Q1a<{v|($)(#aCr?lv1S7`$!H7ArZhMrAHM&#}Uj=Bd3QVHpTWGS7 zMq{+$z!%fRE<%`9zYU}m6q`mOYX^Mj!NH0u6GGqmk#xVC#4Tb6BztA2TCBDc+Je<1 z`1F&HKGfJvONd!0pX%4t7o`LiNJ#2a+3bP~CQ)zao?}9C$gZ@y#%50yri_>zjIt{#G1dB-PhBTMs{(+I(Ayu1|Rm0d8_<3)^eW(9(KTwS4y zykU*qcP|=NA8e!?f>dEX`8@z`Gxw0t87AuBKy^NV+1}s#E@woVY%s-{X3a_BwT_^I z88$i(4joqnGtVd>?X{xt#>3(1 zj27RC?B<;;$7T)KoNMAUA?Av3 zvtoL$z5>h2QQcc2M(@aOLvuDp&SslzYitjqV>-IX>wLTgneC;7KK>{o?0);HM17JS z8C%9pL#TkhK{teb0yM|88?oB`NM2Cn4aubL>Wbu5@vL+ZAIMN&iVa=~LV*d_Gy;i$ zEr%NO3Z#e%=&cz7N#un%P|;1xcE}T<+SY805hZ4PYSPA9Gw%>t8d_m92*fU149cBAlEh; z2COAr5;P~F)x^crm|&>rfK2i7nN=Va%mq?dl^f1r#=Q9f5HU4k*z}iZxl~eDzEcRr z{F;Sf(C+>Bbow+LI^~`il=FE}guL^72_1mSfDj|)ky5gHJ4*667)E#qkqEUK$9R?o zOoSM)hLW1NWpOklNwyH8C05~v@4xr-l$`VG})~4_$vuy$NdgU{4Qn3OqB)5Ed^hi5`KDKicN*wKr2T4nG9fEPz~e7kfpy^eSdk z7F|PkFugHpcS&&7XYr;*Yly~B+gX=}OW!2iplztxvN0X;)~1|KaWoZ{M+>+uR!gMV!>cgOwOZJtAaa0EiBa>}`wNP>@Cj?$7y( zQ01a9=NK4*hhfJ}oZmPx#3Z?gEG9QDU0Il@ZHq02URcQks025S`sAPr$hS+r7lw5r zHtM_oV_Mjij7cOc(fIh!@)B;U{%}NeT3|_|?m{ha)T91%@J8=2+cy}^A&)N}dQi(@ zU^a(OmE&pr^d~Le^56p_lxe|WiGzw-GJEpc&`&x~j2sXq<~_g!W&(t+!Z-@+8K6LW z%JQn7P6}s|L5$|vsiUqqR?&L~C;*&#n2=<*Tt0{pVUg$&BV*!;&HytG0#H00_)Kgr z!}b`J%K+3NgZ5~^E{U@cE2FnVEdANpDS@2dwQZcAZ6*;*U=8F zMT8RemYP!Goj0lu5t1e%ssHN6?3h!t5-CdPP$-wG+!>G-5<(Q!sfn`;N2iw96^k|u zSbYpKH_=5sHTPI+InlF*xh#_lv5!y+KmAFlInJ-M1r;0UsJePKVwR#-3s@4{-@s4t z<$LwV*iZ(xN`JSQFX$f?o#1J$eEJD$G%#cpo^20ubrTJ~6ORVng;f?kFM!gPBU=E+ z2&`(42UzZs04lM)=#Lg3{K@=fk=Q7QP->eSKr@mWJLNs!B|~nkk9?VNRA8URL;O}- zdzV+N+hVAspZ0Ov@`YEcGgD45hxCBPU?{*3^s;wK5W z90NXMWDz%T*u&Twqav*f)x3i!8}G#iYEo!FrsG1SU#7u zv$$sl^Yo(*j-J(%+9A8NisB6B^*D&kX-DE_FmDF)7!{nsJcPT_%ALWy5=Q>}59ZbA zb3bs1WW$VY*3jn|+1w}jPnmd;t(URe3_-V1hEV7muRCwAg@-d@pWqqub&}s}3ilx( zy8w&)sjMYaqJwDT48Ibkjp~SNC)6uW_e19~bt!+&qt0QS_jef#i|BFR)IUtDN09@# zg^>I65vfreGS3z^_`XQ0AmZFxI4Q8ClnIg1qKBw^g9ys2MW^q2h)ingKYY>=xPp#@ zgqXeo8MzNYtW=qfZyK)6ITzRf3q|74q-9)qi7gTzJ+}rWv;)mt&T33lQ?yS9fq0{& zrBt*g%<8-L8cd#K;)xh$Z-l4Go##feT=08Pw58+anbB72Vt}*fO33sDf47yu$ioZG zrPDr#`0Z~LaA?y9o-75~M5h~gK;)&Ier(=6WotiWp2sK_##oLr8Vj#^iaLP%rIQ+a z{!AujO89%G*=Am2+-Y!PV<6L~J{sPK-jusVhGUDk@CZ={jcwJ)#;h7`Jj1J}bTRgm z(Zz78Hnuu*+ADq1X>N6>8*f->!)42Uh9@#HlOxVsmDNLDKClM~pd36lR}$s@_l*b# zrykf11f#7V2rPX8^9$*^_Q2kX9^uNsO6oAK+idh$HpOJfx;ZZT!)q<-PuZBDu^)Mf zQ%y-Scax9>o`sEKkC#a{ng?%3tJG`E%S>=(Y(8**jAlUBI}D+7^)?sRr!XjmAOJc( zx6(P4{l=czUUluPLWWPMpFUw)<;09?^ zQzCfD6!odWGo_0~Hf-{A!Cv9Tw%xWVXz`IhxX|j|&+a`eHZf-qZ$;ma-O=9^<5Mtw zzwCkd66?tAq@jC>rTCfDmJUNQCGY^tkR>|~f)EZe650pr2XN4xkcw<_aDdsK$WDdn z0A_MmOU5#A(5XHI0@jF`P*Pi)$O_68lEc9soi&1+cP8g{rVO5-`9cMQ#?5j;8WIF5 z$6XH#t_%$mjUkMMELJbsJod}T<}qqgKXGu3ofyhJ9fN01>_Dc_k&BrJClBI~&6n22 z*mS|f*(jzOqa_*l#&HO&aJKCpa7F?WPULGKPzj0m0#`^EY3BN2En)B~%#pyX^>*^(N)p=DL?HN*D$6>lj~DO?TMV*2HM4jrT=g8EcR z?yZLFCw|=4)A>J;HQd*u&C4>aT&XP<^Esg@L? zmbrRe#yl%8cUP4SBy_TB(ai=|%0mf=YU;h-|wCKoD@&nI%Z&d;sVTf6ws;kmR}mZ|6AD&*zzfpZt# zD3S>!06|Qrz3o*{>_8+UgAlDMbGlzW%;;`Jm zc69=(V1o%iVB9m^ELhNLo7;RkOnsv|HZuDe0OelP?bnE|i^eq2#9q?r5%zA4n)#Zs z>Rt2Kz5Lf+Z9}GAa=T-Xz~gR5{GGQI$xQpjHcH4(oF7Cyz}&6PBq9ni@sd?vlkac5 z;VRv+;ffhO;QsN}o0(G20qsjr?0gxg^Z!DDXQ=48CfF_fd_b@wS>&zkg62#JZ4aX_ zhu_)>S_nUTay;JA@W51_w3uMsu{&$-X)$s~g4GeFpoQVb(}vuj(wyH5w6`_&7&HaD ztW3O?B4iA;PKJ}Ev4@74;MLRza*(2At~ z0(xvA`L&`Yf%qU3&;dmNBob@1&eeiimnNec;E_6m1|1B|p9*AXCKLc?zocxz{4h83- zk2~E}x3l5akURm7U~Kjq;q-b&qyVQ^QiAR6O(nl?=6dW}d0g0j=po{x259lyI3n~J zN}LeZH;*D@&I6|f40DCt**v6d;m&zT2{|h~Q@Ib!!f5k8weyg25v!YrlOf8w)fk$d2b^$+}nTn5pHe6}mb+%>+~Hq^hrb>I(eOHmpDaiDg*9ero| zPm{QI1b=nwFtc_NCKdKu6um_yPOg2rOhu0dI&p62o-Wgg9!~ht8s|tu*^RXzG6!(T z-e43jcYc#j^lF{+G$hvr;!2*;xArBk;KfLC;N*0UZy=ot>ipdrf>2ziPP`T%yrhuz z{E2d58`$BZS$B-{;5jAfS}SP>=qFLUL}6MWL|LNOM{qiUa~faZLo?xh#RltB1fSL2 z9jQv~ZeRl(ZU!n)?9)`x%P`}eKtg2BLIuf_L1yDP=R4*FLs?J)yo-#SHVme2I4PgH}8|^B7ry z4(Zw}aGNVpofs=Gs&Tl)muk%s%w}Tei+>rcCM^y5vo2KA&jX{l@LY)eO?D3mlb9MH0 zk(=m-&W#cu7<-{%0FL<~xS1hfuPp9>aReYQ^ycp}_N$){Buk{qgWLl$vnX5OimAzf zB9e(QKyl8wID#=?0jPihF9L)CXsjpB=M~Zbh#60{LQ#G+~Ih{|ExuVDuFF^(+vhf$L`wybOZZdsqN-r|rklJp~gu znBtGXh@pc0yz+sIQt+@gg9bT?dg662NQuh-3u|yGzc82|^ubr(v?||A^&S`G%Q~%B z2ge=v&O()u3$s9MM!gkt;df3#oEl~1#UM!w)^OhDQ4Y+R>|513&)-3Cx3Qr1rT0;O zr-4GyRz~%yyCh>ARzriI-4k>U+wkrx3K+7b7D7ufd=x;{VCp&JCJ9zh0b^BZ$#uRt zI&Do-aY&4#1yr8)VTnizrS4AR&V!#pu+(>WC?xSagpSK35&^m~4=jOb00707S}r<~ zN&}<*>X)b{-449^12h|txrXQ|6@X@LlA5BVMu~hEYHwZp2`UDmvR$Pjs!ETp;mS!| zNV=4805G8evcV~d4g5>$!eTl=0og1f(T$AxlOL0-nM}Xf7o9YoI$HaxcX6?^*x0vm zn9!Wrt42X+c4gs)suqK?QMtiWO~Nt33E>6l4Umls7eD3Q9k0lWgG%A-=PltGZK4Gc z^@#S~qxG^-&lWm)B6b~gPhi3>Q*rgHG!0`6c>rrMopkofyrU*mniSUA&ryh(XpeP? z3qTHjWs^rA@fR$V_!+(k3KirqeFhr!kqko>MA{kW_(p#97m#T9La=l|3=uIK(h_w& z7u2=sm^}Yn^d!wMJ2M!G5W*k;$dN!F4GOqQ)R>&!7tslDM#c=b^(=N>KWTwoZn2b) z`@zVZ;8(hJ0!`rGU9~R3E7@)i3j}^VIOfzzWl4iQ!7{YKIMRLeoUm4SkzfYE(y$qB zkSN7RHo9(>tve)jCB`Q7=+e)H%W1 z&x;)u5V(xLuG)=HTPC0_6aFJvUX94VLlCqeqpoWi{=79DxfY#agh}eHTi2pn3kj9z zNytr5NO3D-l%(RAL<^e*VnV9=fJZK=(8S@%6Fq7mnZ5QJVSyM&uk4hVF4^GlfJJ-A z2zLQaTFWIi^D+Sj9#B(_JmRtCfTO1-4h_?seUM_IW``Z7^^)>xE}^y^6tN~{i8h3p z$FqnwGZ5(Kw`L%41_E!HfxyrZ((~H(xao7xr2VrXUm9VXDR?iG6fP+8wZ0BF2*G@ zEXi|W9%Cb@`S{ETL<+{t2t)?V%m{o0o18t#o^18V!&#^@=HX-kW$vd;tch0qJ~IOU z^5wA8^W=n>QwA4PfHIp-IWWK+GlgbGV95<-##7*h=T2rtXRM-XRWmaJ^ZDNy2%Ley z83?5FG&2HcMqtk$oEd@dy-P<(If>(4-$llI4MHHJv8EShoT6S^Vh&}bJ+8AAd*0_d zZ?Wb{F2BF#eAEPq4ARWv%;LO5 zT%HZSH0w(8=gXOLO1#hrR+9G~(?0V_;ai;no(}|Hdc@HS1D^eSnZx>gWbh{Z6_+Dz zc0>q5a86?6k7nk*PiL#=GA=!$CQ2c0IjC{7E_f;k!W{r5nFh=+OcfW=73o(Y{igOSm|xzJ8zkE0B= zX-t_2Mu6zUGnnjgzd-NsK#!s62%rr(A{tu4e4zZ|JUq*5$+0lx$6f2|?woK(8xy89 z)ksbjvUKbeH@KobZ_N@lM<|V8Atg5hf$zMHh}IY;RN8vukJgAu)X^Zgp(VNdfCIz< z6J_@RHwQUl#_*$loRy0(*f%qMiv$X8Q(=k24d&P-K=hl<=8A zNP2Hc(M5@XtrLP$Ac}$sAo05BNUmo+FVnJ~dG4+ThrIE+>K=L^DqZ1HWj1b<_moEo zU2B3Rqe{0e6bN9BzAKqZj#}M&@Qn&an12WBTB-1@c^SGK0Sid1x{p zJI1G1d-zG^LK=H{Ql-jMr>~tXUHxYseDgyoj}|4XhbaqL?pYax|h8vY;O= z!YV)d5cR}ewr$}PUN70rLNrXI$M`EiY)@6ZlD1RC9|2;DCl4UEHtCw z+(L{iS!-1i01A|N*Z>+~f)yWnkho@*Iu{yIa^AV%6BKGlU>38-KvF~8F51OJ*oY9E zUp(Q}dKdSl#Hz9?!e~|I4s5Km?F0AsoqXdr%$h#nW_%hwl%;})C>oUqj6*#Fkh~;) zXTJ2Eoal%fb5k85${U>}B5%489YTGkI!`mX?eC$4T2G`ZSTZpK3nyljJplD!pw^!E z-biK2Su8{M18=^`?MgFBf!lTE_p4uGyrR2~8JXD%6h)&yy;xU_(MDdq`z{V;ToHb` zqy}w5cfc24Xv-!wY{|OX^9(ZWTOkovty_u8>T@J;E~g7mp?)=II)En)tm0?FM*W4y zG@1e+=1eNXm~m}tVjk8pxMh;qpzc0%n!-a4QBRF84Y%TQwdl;1%`{1$p|wq*Y#0~?Z8Ys{0rAXzkdnO| zh~wli;FoL#MU^w;*2L|ai%97W$dD0Z4mwRc+}HkT&p)}H%7SdjbvNjkLn@bA&j(gU zDx+~%a1T&%b`~>fynnDJtqK#?ra&!!PHg3$r?^)SV?aN>U{&s3Mg~>c_O`7d!r0h` zh%LgKC->!l(C=634V!zh%q1m0!AgJgYby_W4a<675dsLh4Gm+@xALnU9Ru;E?j5(& z^}YT&V=UaDXP-eA&`?QVI@m-UtCPMQ#{02(hw~u}=TOO-ijI3Er$ZQeK;9;``)PaN zG`9E^oM#nCD&4dX1UgF&mZt`{=Ex;mu+Wh7Zi?5IYt*G+`-o7HGLmRvM}y;ZXkN*+ zvrY5;sZf`MfM$)D#j7+CHMeW!_hV)kUnI>CzHh&$`o?R*iLF;)^V(~0AyqK|t86up zl9#9a=IgIDatJr165XE`(>dpxj&*$cNw#`h%LoVbIU+P0;rX+6(b&A;2Wvh$_0HSn z%I}LWsEMZbtt+;b;cE<2# z3@?nm8N-_~ycxrrF+7L zoG)i%f8M1<06&vgebFG(3Flqf$cR+t*1StQ@6sZ(Z{DTtDcim+6fvikkv~6{r zX5OWpcWGxHUKbnNA~O$f=HVfYcjn>oeq!9{V_r0q2W*-%;>zGi+~{kuBSFtRyqSkL z^YCu5q|7|LnTI#?@Ma!ff4&G?=le4cZ@z~&V|X)$$JZkKCP91X4k4N^_HZl20<79v%~5ZBWJgZu337e9dopO8nJgcuuq)uHXLS z7ei2R@c^GwH3o?H-_t{n?fu0xdu8yx_pa`3Mh0_M>4GOK7Ug_vj`^w>g`n|GFK4PL z3ixs$K-5s~2Jbua)@;7I7+w0PC+c4F8--}+B)^ykYW;*^yuXP0wA)b?uQcZ4jHm<) z;`heB=yklp9gjV_E_&2cc&zk8kgm&#GV0vxI3PQXT2#g}l3u4@4JE+dS%C0PtY80h z=G~1q7%XxZ4fnYL9o>HZxnq6>3bXN>i|CSw8-bps!ojTKS;hh77!*^+=^-zm#%q{4 z?Y(>+kS}cNDcS&tWz}lF`y2i9$1yP3cF~tRKVPRMosg3Wrc*tlO?WMj2NisMlrNO# zvPDp4Zx&EyW(Lum18Fs|r{7|N$K{cS0lK>CAlqlEm${%yXvj*j_zs7@ey;-Yydfh@!R@8FftQTK%b$dn`Q-~~E z{FcNkJc{Q{I<+j1&WpT7jcAwz2ByFVgI1#YN6(@D?M?@Rc`c7a2*GR=8bD%2^+-kJ z*C{(F=2M=XuToZCzTyBc{Ylvb8~~_J8V++y9PX9%oIH_S73m1k!QEs65Vfb{lVn;J z=+9Z2uCXR^!Cjaun31c3R+PR63t(;yQc!mugC+tia4nQ?3qxd!kf)E`fpJ~0bi22Y zV?@8|8v$Rc*~8LA1Ddnn|E4Vw9x{g9oV4%wn2>43B$H%s%6vI#%NkK z1peB>o<*2?+f%PW_K6I)^E2j0Zvm@*8%>v7$A}O2koNMPTM^Yn9-jTxjz!^(`I-x* z>Oap!b?F?k5;YpJOBU`u;pJ#)M2VMlYi3n)1>7|9SD)RH`XRK}XPM;r*QL4*IkRH@Ine30L_F2rpY)@63Uu8)?DDE_N=d!y2A;P zW3&x&bA_eTw<97(8k;Q$-&jT`5m>?1X&Hg`#^q-aX}%;u#vHImE?Yxm1VWC87DG~K z3rCS_r5sETY6!8dLByP_4r<+dk685f+fdDSb4g6}0Q8Wa^!b1#@ggvFeE_}s3i+IX zfNM)7ddNdzDS&2FL3)fgcCxr*IU>kP;P&?5bYXI8I@=4Lm3|t>zj}jN#l$hK>#ntS z0r+BKPar1?80gQH>zRqwp;d?yf(|W}yKbu8 zocaI=6ahn4*8^@aKCuEm-~_18h|R=1cjy3Tx4Fh>@nrsJPiFs)t64`ozWXzZZ)>NMQy&?3|J|soZfvW;g zNQgz2m@)(yzt#WYY1Cp8ScKcEMxA5oq1&)F6hq`7N3ozp5*oP*l9+4Fx?qde6br5( z_oF^L#(=pNwB}0hIO=vUcykl~KIADvYBihKO!#{gUfaFNg|2TK$I}=VK(QM2ZrN8i z5EJr6Dx=gzVo{(9(t<5PkPfl|SfoqEBoXAufV}F;#a4S7+a(4dLd6Za^-yfR8vG7Z z-FH$b_lAAvC3+81ckG2kr@rfb!1J(Ukh@pqT5Uj>yKGlZr}ULwVG)ucc`!7tv4Jf& zz8??9V*&$KY`C9+s3{T={MEfT!cDvxvqvzN2ZhbVL0{1Mr6(5G}c-1e+5J zZU!s@1tC&cIme(j2fD7fJRiMpNKf=#jb!3(aS4yqnpq)=^f|{pU{P-b5Cn{jn*d8b! zzZ27Hr-_LkX-&ga&~Hu1)6)(lP5nEuI%tL60iN5X{7JuWkg^)XJDZu?A`u&`K>7O@ zb#872`fy1R@qS8jfAkjYR;^7e$Pes`tQZWli(*B$+Gs?xH;K=e0=7w}W&Gzk^nLaXJE)N^S5u9mPpC8^N}B zJn9VV$@`vRy&2YnI+$U-8P+>H!+O}6a`Nb16tQVd>d~Ui< zasX^2$|IOzJqMTRTZP3x!+JiBm|;CQ+B2-DpP8Luy&2YvKu!q&(DOh37Stv~qBE?= zkmwBS&9L5|UYub)1#!2+e>1H2`Daj@3ckKF!+JBUH^X`}TaO1NGpskmdi8v@9GuOt z-VEzGW~UnsnIID>EHkVJ49sl3-#JvJoUfU!hb*(1t@qULXSUwV)|=USGh6TM%+{OP zdNW&3S90ZK=@bL!7re)XQOm`fVZ9mFs~2bICEQ;^e^7bMOSpL#c3#58mD+g;cV5E9HK}&vmFQ4Hd;CvBnktB|4?uMIK+&>FLN;tCtR$u*Xcl z{UMyxXPApG@;NLYBSimxEAf>-k06+Y23i2=%|;J)S%O6Pk>bodzwx|it{fxqIY!p1 zFoYpyzU5{v+|PT))4*)Yi8)?v z>3;56m)($P%1|Zd-#b8IJl}ik(Hn0dPi5bHnexvsx96iPnw4MHd%jKJ{9E;ExfIg(d z3+)wD_}35wfb#iRzB{WE9zV{xg!S8;n9GYV0GR!1&DY~6C1Um+f%4lWVqh4MqrFax zseljkHmIjl)(d$35=Bf$Si;Ofe##f;ZeTu6X3$*ZZ$$^dlE?H6C?_v&AWkH-^`To4 zSHKq&Ayh=CRPT3c_24YKN(I9@_#J?V@~1jFuB6H8oc#+8_k6du;16?f=ONKT5u7E@LPPmsgg_AH2G*iXg!*o>rfxkJA<B_x>(K0ObqyR<~gSnPBKJ&6<~AGSofSU&pny`r}ZV z`cV&w2pNou$rDS0dRi3tWHh~qZ&jMm5Wz0#5Y5?urSiA7%oz1-Wth0(jkL^c9@q?mKkQgo>Y47w7T zp;7#31tw2k3iXh~B$m>RkR^c8n(pDQ`pYM4TS!#sjnD`{kM{oHs~Cl1nshx&3{pi= zadIM7WJhlyXOrBa6pXUnHKZ!9>q;iBzQ z@eO^%;7>n=KJ;{_<&X1BboDKTbJh=UVRgU#P5kVT8<3?&OmSxuusB3N0_V+2Mx1`C z$iEEm(T8uo0pXT<0xEzwk}m9ctCjW}VBE@Miyaz0gI1N^`cX9fLPuK#T85UzD)gIR z95HEcULhTCx)F4O4kZd~K8W-(c%dIbGcCwJi}icrJeJj4os+y^&Q~ip(z};lntp%` zgOH(z!G{XHoNK5U2-fFfr0(B%UCftl zsGBcilO@k^A=)6I_t`>AfSLw42dJ8X>;+J0e$7Y7;g$r)NmG$7^H1|}fl^A`;4Zr~ zCK^?QII<8U95sEo=(D?)WG%J`x8+NMMRGOHp^iu;+ zFeAJxEs1y%S28+a9K7|Wam3CF=>ww#?yyQb=W#_0viC;1@%fQx29)iG$($_(%Z?5=FDggZB&{pHdwUb zrPx#f%m@v^0qt#6yIn%hXb!9fnCdf{^Q8@_=hMla(Hv|@GnxZSwnb~2c{`&y2>QrG zP8)wlb7nM$2SXW?nb90M?lYP*qdAHp%xKPx=IotCNNeWi%-o!GKKHywJQD%*nVZAt zbsPyeq+ZVRW^T^R&Ec_|6Mtqjryon|5M<`&%-kHFM8v^W`sHzeg|opK&B1x?jOOrI zH8XBnn#hdiAPSE2wwarw+$^5G&)gi}oEy??W^T^R&0+q|jONT}&Wz^Fn^u|k>M+HO z=FFQ`2wTmYRx>vT7Bo^@OP=iu&(E7y^QKk1jMbCU<6Z@_#+knjAGD?1%xDhM#(5(v zGy342&zn|^?;*H{boIE$GNU>3rq#S@HE&umXg6GopY{L2fkzAdH< z9A0(fLmKnl9AD=`>gIemCvGv%Xil1zbV+zv$P39cHwV{Dnc@tj=z2S^KHs6see>O% z=jKhTa^KhwTtw;x%(9uAGjnrhG-pP0=DRtc`wI7bH^;B@c;;8A;Mr#%qKhm7(VtUtbU=EUWCn5R_otV53`K{4>9ABS!(z6j0x z(?$@Oc-p7F6U2w85d3l6$FBsnY7CBhIMBR{TIa&L3cD53W%0HUTREhZiPf&+yF&~l zxvoDb=UPUZdN2yLSV`Le-{&Ks$Vbk%ih5&CNQ==ERd4p^miX9Rj?V-vKC7eyvV1ZS z)4cEkPu=s*w*&ozsm3G7`G$dOlJ6$*kj)SJaH))o5jW^WFDv;7PS0$xny)ZL^(oft zfR43M$vVtWP31AOj%33EScj=|TvnliZ$%LaQFDT;imGe;FcxZANpxbp9PRd7H5pWM zNLF&NsQmNu!{ce_gkuwkqe`pVjn`%6e7;Pcyk7JoBmm!fQ*%cMgfH{>88SVur8IKB z`$a$yixciY^M<$V^x~bM%;JP|HmM-_=9)l|j^1Q|FpbaIAEERZ9&xb!8XeZlm-7Vm zZ9QD(TT?z$_RNkGf};`5mvXqq+8+`fZ?hiN-~QI6M?xM$+D^Rxwi>JQ3W#M&tDf(Y zM4sSdPy`}&$jp}>$yb&0T)Tk)LI9o5Rh5sa-4ZW%C4Y-~-ahX8@*h2XS!Qp)r?RHTJ@pdS~ryNe7h^1A40R<#4UxnIb&d?${k{ zw6d45e2z*&!LhNaT>LgfG`0zihQ{A4gRh@%QH&oa}*g zL!H=xFJqQhHcPeHh4mcT+!c-6yq|J)u#+DrK_&6VW-RA$jAFm|0#T#ERC=?EAr2__ z9Eb#HHL^JtsL(gL93lg*dL|~?VUCRVG9{&k4NxowoCJjE@%N=R!0rcjfBQI?9mq&D zV1?v9AQ`g&n_JOX4V}b(0F=8yB8K54vMXuVnjmORm0oT}sm{X{p*MY@wrT%qzwj)*r|wOR-QwF~D_wKf`a1rQu6 zgkTpiw#8G-+O-?mKejS5bn+W%8q&;)^)VFXUAiDs%9JkaPtVCu+zsQM2GXQPa-LkI9Z#|Ce6|qZZAyhz6v2;!67ornWQ- zfHzI@E!!G?2%qYll(hD_oW@_#g4zeVVx~DL&WBh%`wV%O<7x*9p1JsC6t$qp=|td%83)xc`& zc+G%xl$<2-EKxLKiF;VEeGetv2zE_1209awIHaiF=LNph(+SkSOew<%U_+Ln1cG#1 z<7xC}P?-vnvCe=oBN)}md*W%>>1+ctoT5d!!+_)zeGO-NC6p)13zCq)B&<{hV&5+ze zg&-ru%+eNv(JN<}U8vQiwKyxBsoFDD8*$8;s=ar1rfM^NfZ0&YOsc7hNbb%=J7;UnRPBCx9tjsr zHj9{_GU|{B=#)Gp9z>>nW{Zgwdt6B z{@F~`cD6W8EvLlmR)$mXbz51^K5BiQvZzI}f2)F*3;!3X@!V+b{^lhKR}t4tW^b-> zm}#z^jjsgJJyW%5;FED$F73SIycb%`7wmM?>*kv>ao=)F>5}Kf zTZFhgG*h*4*{t7unyK3Jj`O_Z?91X7jCse|_Y3D8=Xu9DqmR>Zo{sZ$od50|XC*O7 zMs;7?IZc*XoEP9~ce=QdG#}SD)0NE|)aPYR{i6Pa0>`+Yje0n=IKGKn<2=QAkDglG z-5x4wlIQybI>%s3zcv%j^*dR8kzDA%e961=aryn-ci1nM!&Y1ZyYob|1cNIl$JfJI z+SCp{ zoefh@e)ORp)*ys^{Z$vF3q4v|85dq)#O2?ix@Db80ciDH zIIh~qSipvt5&0Z(dgR)~l`Md$xy)6GFp{U0@X3ex21H|BzW*sAkURzi5dyQroJ2&D z)=*%oVR${JUkGg|uu#l|SR|r3A@X@gH6JWUR^v(l1Q>uUDJt#)#IbM+gB9H068IB! z84HQ?OFu5c2OyHW)Rs*#UCE1DWx75{7H()}ml0uF} zQ+)B{4x_3xX3f*8isx#~HinQsd-olqn@g{tsjPql&RR#nS(j&U-Nz&?OpVGA=;Zs<26?IrZ zqQ?R7tv)aCfCq+Jz|;(ux?jjeXO!B;DSD-`ax#`tUdrO=4O|Zwbwy2WN9Y`$y_#v4 z%GW&0wKS}O*9Zk&ugc($;0ozKK*U(FEmF>t-5~6O4NXbT9VZ50%#GkuF9bG{0Xa&A zz_R8DCdIMv1F%nE2gT^iStkaO`d5IxR*5Lx|#F( zYvfiFnfy=dfD#E43rtH4=ruU<5T+By6$G%=!FrD)v!Yd~-8)9W1?R&y!UDnCuM4AVZq=dEA>zz<&b5L@u&U1@wPyV1AAx=vZ0o8^2 zG(Fv=WiUEQgE!bBk!T8F-N}86`e_e$zLzIpMI?P8ZW#qg3?-F8h-$`?bJ{7!w1=C* zg*=c4Em~dw2#5Z0DGQ(&)liV=X%Am=QZ=e18=7vaOLBABL|u?O`(m?9v$@&QM7><$Um# zKSD6$!}J|ze0au(8J?W+VT7+{d>Ch2j0lcVB)Ic4KFqL>f<7}otk>uj&9w=h0e8d~ z9Yc0tQRxqc5aMQhSjIZf%4WVq&+RC2X!`u^^a%21BX$oiZ=SccKmfFqYZYsZlczt& zNz%-hh}gako8q`FP9b^p>Z8MH5BrQ+M|+gXX%B;Kz%mRm!6tMH#LSn7qhC0Uj!>RD znFrVushKY!-Aq(Oke|oRGhf0f5z0}T`4Y+nnE4W!{4w(-X8tfUUYfmIQ#YnPjMS8w zKRok?&DI(1($J3=A6;2#IQ?9!4{`KfE(bd0lBC>+6z^Sptjo^bz)VNpi; zkb_>~!SJYXiaM4fyd~Ov!Lr>3j4=x|5P8vs37trGT6+V|Fl>kQ<-<%U*MxbOMlbl( zsRcEWP)mYsRR=xWZ7;)}?@owWCcvCIP2%e20M5AWe+$>@^n8#2{0+7tloP6y;Co|% zBwBEwF@UxO@;U-T(Y~R7E6fs=r4VNMF_l~%fziOy3y@*)Uu*1$0a*GCLsrqQQi zQoc^`SGGAim(r#fGfeIl`ba-r96$$O>?Plw$mQzpjO0Dw1a>xbat2CYkv=PAU_~gP zj%q^(eOXLljxJbAloR6&I4-|OXP1+I0Fy%9G?ANzcJ&WmQzZM~rtn^?tttxxv)pIOQK?2s%3^vzK|}ic@8zqsk>zJS z9AD1Cqv+YtP4e5^K7k?%F}ThgQTH5i82M+pOU~_6=c+~Xfn}F_VoDm6#@s%=YeBWn z?bE6n+o~07VIpmdbNiIVIk(T^W@)RO+b86M+mjp_3eWUDyRRyHc6y)F`<&jV{G921 zhPybuPkWkk`<&Y+!Z->Vg~vOCw~F&NY-D(A25&Q9JA=28K)#H}w#cY{MsEgheZIng z*$mz)xH^Nk5#gR^pK(CbP8`}fsn6Et*{9rgo>awQ<2?He-$f@$aA4-yCt1V?mF+Nd zvFXmU&w2J~{oyEJIs5!Z`}_L%_qEmEKP~?LvH$%;`TL*c?|%$`|9$oM-zWn>k z^6$&?_l5r23cfL};Onaud~LCUf9fmv$FhQdC@c7%!wUY#Y6bs&xq|<;T*1FDSMZmz zf-mP1{^{?A3Kv~i|1Mbnoxk7Tc{~2j-S|6a@%PR8*ZH}ATJZMz>zdp5DOn1^`g;+J z#m6co!{;h8_46n`!?(38)smH5)q<6b)q<704Fy&B#?KbT#lL&2zq1>EpKt$tw*LEc z{QD&S{vKIx&0=5QP^n_F16gQaUfGu;D!B)px`UdNcBwHY?G~apiruGE?yBJJt9SQR zt2;I4N@ebCsLZq5D)aLN-S~AM+8xo~B^!Si@Beqv?!RgPY<^xF1-)cH>!6kIuoALr z!&oRq3$tPIwHguib1H?M|8HrGt+IWLibe5yLs*wAOpEo`Vbh2&)wUQuSAMRaD{uGD z!|viQmDl}w*lGM_rmcRcD>F=W6J~i2HA5xOr|3!ZNaUr(;S_PJ$cM2;iw`6lwwvfv0uWQLHC2Og4 zNtMk(4*8|ZUwrPOm!C!v9HnoNyrq=+`?hdp=}wU6v^1}u}Q{`4^wE`4wE0+4wE0| zPJ`Vh_m}?v+-XMP`F~@l`ELLF&iHrt`tPpw-<_+!W2?VAR)4o|{N1+tyLJ6{bo=j? z?Z2Bh|8Cm+yK&?1hUMS&%fIW&-?jL==9){}pEvRH?}i7QMf*F#uG#GkXYHmZyee!q z@%D$*TKoK;I&ACzm0nvGY+BT%aA{mGz8_?3_xiQ9u&ZQYGaTyc+L}0^WItnLtGrt6 z>(47Yz7}lQ^sAz-a(8_i)$#h<=nC|2gHy2iW!qEOEKNt@(Ek^w;VXSiOX#crzr$eK zr)r1!w13sOH|+kly>onp(!;nj2g2<3cMefoU7|yOKJ%6TPJe~J4-WZv>TrK2_xU@q z`g?!hzxOu(-rf3pr~mbR|GVS9|DE-|fA@OdziYMc-?`fNkFECoJ2v+H+gJPkZR>si z*6n@&==Q#U%jUj+^X9&P)5gAk<8t4>VY%;LU-td$4%ql}k01ZvZ^zHQeCz+!Rvrc0 z8~FtnY;WOTW#=w?bNjg1l;dmPd$&IA+I?Mjs7+Tu2kf(`-r7>LY`vYTq;8@9yxKKO z!B^il`}W9Bo4cY4H#fnt@Q_>K&fRavF1OE~h2r(z^nYR(I&fRr1USTQvIIHwj#2=> z^4@ahE9@<&54pE|aJaqY)INL5$<^L+V&A>x{ms4Qy{*0F-M+WHGdGvmYv$&H-Dc?p z=J&Rvln--r*$9mF?Pl$Tv1D#8hjnc>_l^S#mpjKHzL!1Y&>!dKqTOb0E-UwCdMBv@zkgo+J+$%n;Q05z_;>&Kci+a}y&He`EdPp2 zRX;9bclGN=9B(Mv2j+;dSqJ+{$vW;=O146Py=)x}97;AKgQaMnAYm5o6D-W) z)vh}hZ12IXbj#CgWvh5%FIYtuYe9`Nj-MHGT>o9O^>_99@2ZWzD>weWz4`a8&A%(Q z{x0A6yKM8X9h>g@P!3ZM_i>E59_HJAaoPRKx8k(hW&1^MSLOSKa~I{?(cM+)meI6P zmQML$|1UoKAwF)q_3P5D*ztDR!4c&Oaro$RLLMAtUP5Gq`7r-)*l}A$Yum1tlz{Y( zk3D#>`_p!4VXJfuH!Q#HAKNZ9V?=w$w?hWI18ir^d#FsC@xcH0H!4fbIx^l$c8AD& z?O`Jr`@JOYT>L#9e@~Ua-!K1uxBmO>#@}x? z|9-vm?^g%@{qn%SClC1h#m>JccKrQ(zrV-#`TN=C-%q#xezN-e@#61C!`~0{@89z8 zU;j(EXVnSw=fUqPUj~1xekq<-`&b;T_*lHE{;~T~{nOw>`Acz|@?-i*!~f|W4gZgx zQG9EzQ2WsQpZ1~qJ^fR4c>3qj-HC5&j|Ul-tG~6sbM=P5bJd2wbLFPL^X*N4=UZF; z&J`Q}&gGl_&Sg9NoWuDxggT5z(_r>vQ@8yZ&E#0$bmAc?Dahzsjn$pPx5O{oRKB`stIEQNOg|`Mp@{!^a)Q zdc2adzs)|F;EOLiE%9P&4AN&g>-V(*nk6d(69p^d z5d|wdkp&x;A&UN16L6Pt*Xj;1?Noh$dH43Vd3Su%ynAb5-n}{PyB+4;6Z@HWKi|i^ zdwkQp``MOx_tTYm_mhQr_v695`%#*AKTPxP-==*x7Bb=r4n+Ufsk->s~?Y2Rg7Z)f=K7t_A$DHdaP z#r+%E758mqSKPbIuDEA+PDKgX4IgXBZus1ScICrh+UZNN>iWlS)%8!)zMJ;lC70}P z&aG;ueK+m9A!^sZ%=q0_9B;?@bdA?7KMy9|@MYR})4oe{ZuS5FXYISk{P*atzef)E zdwA=wWaTbDu99?@1sgfLW6_wvJAUh?=K9mZ_#D4(nW8S+GDumrGD@?c8mIcaK4FfM z!8%=i8BEm8FU4Hl__(%Udr3E7w?B1bcI(rIfjboC5%qrm4@cHpUxwrB^_R^9Z5Fn( z?B$1cq`mKle!{)~r+(hO->1!U@LsraBHoHt2jk0vjf3(<(T3T*DB6m$F3Yx0)z_ul z=j`j!&6D?1xOE7hW!s1G+od~>==ZNW{NeopknP<5Kjy>ANBG+cc6u0xu56hDfBT1Z z?!jMQvdUWc+X^<)t-pHFLHQB8>JQGH7$I8T#O@FKXH^U}>53ine}>x+|Nk-w|5d;5 z3fdo3w{OzMeifO(vCsb-c^xV`Ai0Cpt9*`q3$_wHYFT#^5AkW2CwV~K{wX2{)C@-o zhxoGd)L~sYB$w>}rDU5N_;HnlazMfE9F=`)4@zCxw|>hP-c`24PhM0lUF8G556<(B zFI#@~R@qjr%(`qdduCC%olH|pcP7^K%H8?Yi`te@bGQ$ONxKF=Tx2uNEl;;jfKq}VT@(u0EuNlHW3mSunA=>gRzAO zm_Y2T+njUU$C&f~Pt&LSTUDpmxUIc=e`lY)|M`7mj6c2pX>|Q+aQ%w9epy`qgj~PG zuU{0`hw1epcKrgqKG?7SYrD`n^$06#4~uybY>VJl7>|+@C!8T^o?uD@J<>>!aVK;L zAwm?}vAVVJWyapZFDPy<+(`?IqcA85MM6jpLNX4g@6rut!lxScj7>5OHi255M(Bp5 z4=!I&2F(>b!R1P7VECF27!geY@CZ5kOS7H(`Q1M6lc;~b7n$S`uhNT$`Q^iU`6x~c zZ#j*xpSq7HZ#!x4PtPB%k0+w@^T_dY^6AM#GCTZs$2BPh9^1a=w=Sk$)PTMt)VDMt(({Mt+%`M*ayujr@{0jr<}#jeMA$Mm|JO zBfrp3BOlz4Bj-$RAH*f&w{+^3&D!$OS}{f|Rahayir4;ZP=XCdjERVA3gVo5zzcBo z3fzzwwW>HI#9>(yVqG@G3-IGLI9`CO7vFmTFTmqh=y(Bcz9Pp9@cx>dzQRsy^$I%) zkXP3QbG+Qv7G8k!SK@d9zQ07r3-Ax)1$fYZSZF;215RFvvnIXn-~~8dfSa$qk04%v z;{`Y)!_C**Du@^0cmYnSZpYR{w;KTiE{Je2;Dizf15PRM3IqOS{kphteT(LTWG;A_ z?xd_&??FtowrE$p)Tb_Md>IaJf$5$e9Wo=WA*Oy8tusYebl9ZsXuU}-(wh^Tq;tnL zm7rh-43$BKwCW%uLwoQoOO?Aol)ACWZS(-DK?xICLL~FOr^W6v{=#6?o_iz3sl+Sol#X!woMg$-ceQe zNSD==DH^Y4T>mgy$}FB(#be7YT*ia6S?ezLEgfI>v+*H!J$t)8<$r|5*0IWCqlN5l zmeq@POHB5$-OcYkaCa~mb8x$(XoktaA}b*u^ZM|n;&yXh9`)Myg19%V_9w!3FRc!$ z84ZD6sB4J(GmS|zK9?=qLF_O0`S{15aF$_5LjT?4ZoIH{9Ja$EI zc$_lA?#MXF72AsEsk(ODZnl(BtNHSb@fOVHjRCiAjh@VAjo!>|@g_~?so66#AlR#d z8xmYWQg(Yz__fB+psFfa`*p=`(x5DFG>t04CRCjy9GqinYR-wsp4G^w{j9Mqx?Lkz zcEZL!>yxcGyqC7-v5wo4Gum=PYwy{Oub=m?HhXB_$y@adQ@&@<=J#e{X^`e&ZPIpP z(`G%!j>B7zT~G8RTdwR=mcHm)4qVaF?7Ecbb!^7}3THG#bIVu~&sW5Mcf0;NyZ#!x zzH40X8rOG{>#xe|JLvUS&Z~3NU-rnd(v*L`?7=*rR%e_~!(^GaV2xldvdL#zN+RzC z5q%uf#aGs)ihIJOh+9zd!ly5};KMg*V9oYk5s&GW^sXj%O$L_*J@%Hw1#!E(ou+lh zMj0ndxmSZWd<=~IMhra}fLHTPf$Zy^Htvv;Eb#|OHB2fUr~J3qoC&-dKt zO3E(qCs@0jox3UmGLENgYHm#53QG>-D&J$?M7+Ykoi#SiWr*m6Z*ws%I*1rzF-|5YVP@Et zDN1F6Gnr&XLhgey9*yc~9LIMvj^nR0j^nQlj^n!q$MG)0aeSxbIQ}Z-IKBfUho(9} za!|ShBnLy17>^M;AUQ0a10;u_G_-3o89|g28IeaQ4Q+ztAjAeWJAY9!KytkQ{gp;3v|-*UwJ~gv93VMBa?I^Glnh3}2g%V{jKl!R z0g^+ucqlT%3xMPZn2aD*=Qes}k=USR7MJ11LSBOt<9=doCsH-=S->prFFNWHPitehR` za_Xlhy^P|S68@r-SDW$_!80E@d1h7-d{`5^nkTey=ts zSREAn-QO*b3V!TkbR00X3-f{>{b)Eb*pe36EMly|PCIO_P&;hWP&#b#P&#xH(YNJ{ zqT#z^izHE)U~DD9o-8|68f%nAlmnlzb@N{y8I<^x#X0f`kC5lNDe3jlkEbZjY(844 zD@wzbtSFznbSm1&<>Y;0LbI0=>VDqrJpPiAlK)mgI@)Oz1k;{Is*QTq?_?Bo#$;oo zMMqATX1?D0Wk<}JR6FRb0<*&I^s14tdOfX}9C8i(pfyyc+<91$uscUZR5X%aE?*O- zJUT@y>^4-Z??0ihzh_?W71!St*N@xR-*K-WbFaVcUO(zw?{Tjm@vk3tuOITRAJo?m z$m{#n^?myKUi~~{xg&$mdgmUW2CsT#YJAEhBjy#8j-eN9 zHL30e*aUmzT$*U<_U#MTU!J-Z@+^r^RlDpI}iBFYEVJm&I1(YP&1D+!JXNUSottL9MI};j(BlSh$ z`|={m1iGR~Rg}e}rm87M`eDnN!XXJp!r{nORYO)+mJMlPt{1k7iZCc@ZV(2yM?=le zNT#A6Q}LJ+Nq3PzH>0o_BG?up+Ez1(e6^X5WaS7@J?tt<)x?^xB%TVTqFS=37ae6q zRY-|BI*0mE9wn#@1W& z2A3^~>*o8U^_elK>LWyG>D_F+GrikH^AVLqGV=o_+2DySI@sfR112}5Z1**GQLm|gJ(ccf`_a5!1rlB@Fz?@ z@b3ve@V$Z${JSs;P))^f$yH-1OLYTc)%*}$Lgk-zpJ>y`t3CZ zw(mRjX3D|$MWkMvbVyje1v>BBr|7)D9MO5-Ht4)}g3ps&-tJTWPTHNmQwI3F&gu#N zPP&A(^G!k81upnJO58biW9#y};OZ(a_&m}L4?eF(;E5{mdCzkRJdvOj-IVQ8ayKQw z=XHWFZ0H4_2R`p4`51pE9q@UI#Txuq|yAx{|S&l8P$=+8rc z9&5)_4*K&{3*STP&wKsrjTSw%M)`Hl8f7zV(S1S8Wdc>gq6TTqALA7nd#cxu`t!bV zW?~xHDz#b%>oM6_IOD6{!`Xn1sz*-%d9Octd3Yab#1!(hC7+TUNFaK_LRlu zvu|)HBYcD1sN(s%AdctU+i2$NX=C7fGL+7TI|Xb~+;^fGa*>Nh%2m#sF=s(SVtc0D ztU97?_B^@s(R9~yX4c(spIToqs`KoHIx)O{yE{#$rw`1q?P+asTs|t(lk?M&eAK>B zkxt`R3u;sQ)hctGe=#VUgO}Z@PU4{Wre5OsVWB5HBzIn>2%pe92BL_ye;|lis|k#l zv8_Oa+~ox_(R-b1M%WQ;Ci6CFUZFLWC{oc|u9|c$LMuqtBdGM)mk?TgnSyDM4GWGr z7B4uKv5&zE*?StCsL|$~g~T3*-0gcEN`@mY8R4-4qGGkR5XF18JwoNZbrMcoY?;t5 z-8T`~!72)olFjRZAM~{wDPwho7qTQX7W4L7^mwr7Vx-cQ7rlV5xrn{7^1`)@^%rJn zbjcSisj`KB7u`xmqRjc}(oPM($k?ng6EaqA6hr=I4l7gG)?t@S^!$s`ic;v-6eUEf zEL!_v<)T#}G8e1{(J6mfh|KuQLqsN?J)l{+N5u2HQkfV+zqDPZYI=^djYNg5^&~20 z>?@HWV}A(=*{e)cRPHv>zT9@AeP>?64Esw_!i>K$CC~UfRoslwaO&~*t$jYQqRx|Ns*7|+U1_BbFA_sIH8&j9agxa@9w)8j{guF6OMs{j zH~5%IveqXz-{D{1nq1$aulsi?1PuvBO0^;!0or?T*rLtga{a!7D-$;nu&mW4;KVAO z0y8}USM6*kz*S!s|3{%v|M|Ldru1uN-<0o7x!w@wspj778NJH?BOc;br1Mz>rhY+%au=E zsq{>^$|yg1*shA6h!i)o10c4_Xi3(qft^-7DZiELZo#|%HfJo z7|4EBDpvDGc0$Mtr8bg`G`2GrsA^lv5Ye`f5Y0AM%43J`N@J$rwFkMRwR=*;;=)yv zp5$F=O(GBqPFvEHnzo=LG3{YTT)M)Zpw!}ygtUhP%_!D~B1y=Qe2!3{_Jrb&l5ASQ zk!U{h5v|HN3&FcUBQzK4271e$GK3W#@-&q*&Qz3NGvpFW@)$aXP${%3;fW5J%-~B1 zE#s>MS0+{lLgL>d^-0Vo??qG)^vrdc_?ED!b{ey&^(!V>^uyPD`4N%S_~{E8{B$YZ zO>CR#w&`t~#{Q|Fn&qxjUYan@r>Pj`GoX3pJ)*hfJz^5aM=<1w&t%RA?-lFJZ^Gk> zt=lmtYZ9$nFrrpRE~8X?zCfgo63Lx*zd)Ck)^%Hasn2tkGA^ZAOgw2MXN|QaRWV;l zct&9kI`ObLU3hv4OXx6wD(rB6c6g5X6C_^jnk*g-<}}_r-|6#s??`yOZ`C~Bw}8mI z=sbtCv%(#G9MTV*pF#hD^Rvi5!tYt8VE1g7k$l2w z)Si2%{GMfl^P|LH+IR}ikM?MS^Rp;E6)Q1&eo>LybIZW_X#!A@drrV6_nas=KfPgS zX2p=vp%;YEyw}_O(7Z?c_$Kz_pm|Sr@u7K-bn&5iuhP+H^ya0Z&fQHKMMh7TEpvP& zHcuAS={#ClrSnGJ0YSx6!^A^zkWCmF=!ywYd7$!+ z?!LRX?`<7?rH$bm!)I;>4}t-Q=ca6OFO90v-BQ4w?{%W-*e@+$L%s#a>JyA&l^(*Y zrjvzB#v;Qg9vefzYnB`x_WZs>JT};{apLG%@%qi<5=!{w)VROV<22~$sEwI+Dc|0{Ydwo?)^49cyMptCQ>1>{i zXSJ_vKC*pQ1Rb7ss?_)S!WWKN?Z7g|xX%!wfrV+Z!dH+@cJGc}pe;tc<2mnR06ZD? zzS3m0d&10{N23Zp{(YNQp_&(JQYw12V3N6>DcAJ%T_ej}c=18WhTqYrbossQL95@( zTA=6MN9K9^@CiZM-{Lkp|F-*t7?72Q3c++TmlUcM@#cVuti5DlE~-}&tVYaL2Db#= zH3&B(+zkphCiDV^)ref&u;%l14-1~$3^C=K;?rC(>pD;J-Xe-ZJlIu{q~cW;Nw#gY zSi1*%Fv3)@B%_*&muXb8mQq)Ud177|50t(_D%|A}CEHq$nY+6J#7mf~L5yJE43Zmk zS%_^XD@3j|UmIe3h6G7Ne6qmt`jQdb80*-D)i;q?Mzn%`tSgb0SYN_iy~~6}yu~IY zp6xhk`L@v{y{9WrdchD%W2;N1sX_G9)D%)`#bx?x$!S_^=}yI0-wtHiGiB~pAwg%& z3Ju#US6I~CzaoQV2TNqy`&o>ry|P6`y&W!Q+*|A-;%XVW8K8=>ZOf4m>8rIcI^YbA z(6GHUW<>4vF)Zruk`bB0Y8hHmTQei8+_f`qf=JgzGgG)#p3bhgHXmv!d1BFc$@Mnk zc=53fXUXa-a&9oj>YN*`zGbDyOU^nN0IVQs-&whP9#EU zv6Q?)a9q_HGuhD3PiuzbfvPrW)lDdZv~*4fJgZl=zw5bM)V}uY4dNbs^hQ08KEA5t zk>}P#Jo40nf=3^jQtwFdoMzXn6LC(b3$h$5NrX6+PP8|ct_yIXvPP3tDebJQNM@v_ zpp=o4P#lxVf+99bBKfQ5GX2Yww$8YrWnCdp$)cJSQK~Kh>X4TlCFuy;DpHFI4XL}9 zhIGUnAxSDnyU*mJ?l6_;bciN&noarH?nCtH;>}^Wx=pOqIND4o!Gv;)Bd?euild(p zfrOb<78y2^LWm;;km39KhoN)OkWx2Dz!WrkP$(G9HHC{UXzLYQR7w=h*UE&y$bLu< zhsBhJ!x5sw=~CU`u%#@p`>q0TCIa$b5&9$G()nkiMC13O5`o`~JjULMuh925koMc7 zx3TpZNAFVbEfI3;TQXtLBU0QX#08BlSZm2lbK?2CH1lk1lvnJsOCrZqF=c*j+h=_D zOvv=cJg+8k?FgZ6Gn4YTh7D?$lDnXGAAg(@x48{!cc*tj?Gk<$)NXAc2GlMieL?L8 zRBwsB4U0hST4XMdQNDJ<2DMB1*&A#^(XN_=q+K&9PrG0fwsy+wWbK@xx!WYdc-w-b zxZ5PaxZA`5wc8oq!P^$BPTxibs9l5J1+~jLUQoM@&ZT!yyN&G~Os=v(?fUdDs9i$u zg4)e?gE5Xb<8w*gpmS-cQM+FeTzf}v@iN{;APq&G!FEo!1(zedJ$T0JUBYt)Z5}Sz zyr($yC@1aN7FX@Xy!K=#o^&niv~FCkMhXqiX$k9eUUqsD^s+J9qi3z0XHP1sG`w9` z>$^6q(q?SGAZpW=xk}sisG*J8nN7O39kcas>pP|Wn@u{qhe>+8hsnFWyQkZ}N5p6V zKSNF%_#U=f!Q+nbqcvQ#g*R8UhwrY}BEH3)F7YGmw2Gf$A}COkiVMRQ9pv8HudB11 zbd+PIgJN{#EKBGJKqqkCHey!&)bC@EFlpr-IgjaK-@(49Ky+lOIl z$vvGrDKGrP+_A-ee5gA$+gl$d5MORgg|f`8$s1%H@6pvxe(O2Edw()BdiQU$xZrb} zHz5(_r7I$me&)uB0rq$s0l3SZ4g=h5-yktrkFIJ>_H%2plk?Pq0_8n2B}e(ibNZB5 zov2i$bV0qUl_ZkZbUKl}>gl>b=2X(Dhe>;GNP0&U$7Na@b)q^@LVovSQpqk(r0c)10$C9C*mTq!ycvlLr z5lBQ~n(kvglSUu#SumX0pK5qzMu&5lVZOZcXbT&Ig@@-luzi0DAXFi!K+S1Wr z(U~*Hi&{{O8C@=sqnjyZX>0eD9y@y1yNto3Eu?0T{D3=^6er@Dr7&v`F%_cmoKu($ zhoEFK9*R1%Oe9YoqLSB4Dx4>F)7Aa^H+xs0#D7ImhTqFl-Q6g#<@P9A7T$H?jQGxL z=M?x}U69{tNhrSaq$j`AxF@??aYu6Z!dh!jB30pC*QGjppQf`m@A+uMkOn#?%M}H?_Ey1@)li%rm=n(w zSH)MoMd{pltjXYay5CbkXCI5p;CJ$d1b#=$lELp3yHPR^9=}yE51;oW@f+fnD!v+< z)Xe?z+cfm?^lkb%c{lIoRUx_d&{6^<43VQn>H>>CL16XqS4KZ60UyVAN_*&SL z#TVkfI6jr}wDGxkNh(i@YBG7DYFa8!9;i#@Nu{HY*Ta+QIW4E!_?*2UpOZ%><#SR# zFQE4)BAL993)S){NtN=<_O$Xip6TU&K2*%TWT>9gWG0`Jys4SnK`7}$IhQUG&Xoxz z-BHt-WbQbwc+L`@em>%+d`^P4emDw#WVL=|5#>w39g2_^mA?{1>G zd54m&MfHMaZkL2wd7PADdFVq)-zD}5DSbstk3@5s)7rR3J z6}$u6(!8UXCwwRILS{lw24qI%%+m-JJrjTO=NRs66L&`Tsc2It(y0UE1&NPZBZ zZ;+b(M^^>s_&Hx+Udaqq`8JEF%4=p_X?7}CpxzPo02jrT^QAOj~Qkr3?XGFe#XV?j8``dV<3@zmfbPQ~EdCUS6K+M=)#)jF{o zY2((>-5J4VGuL9M+Hl8EwY2y^weq~dY8jVeu`Z+q;i}Y}aZ7+3r1= z)DDKU%?WK|CYz64f51?R9bx!qf0il0#2KP5vxwMB%%Sjh%aVHi;pmlVXlL&E3ac^a zJfP;CX9G>Lz0ci~dMDyk0-mykLL9hSFU}&ShFlFQg?TApsnLa4B2@Qu6Q!fOQm3sz zqEcV!u0Fd)*O-07QI)N5NnZ9# z^_=vGh<9lmt~EH_V)g(vkNVToVJ4oSu4T38yBM*B;F)Nw3uoC-Czi>IZv4)7reoJz zL2cantV#y0N!p*a<{5AB`ie*M*HDc!x%kQ_Ix$ZNN-<4E3bB<-)L}23$ihi_RwLH+ zHA%QowJsF1CvO&u`v+LkipN8%nsENylp0(u7J6{a)uM13p~`S%Lp6R_2mxN<5xk6N%YV zjt*Q86ZLn+gks#p;^R)0-&I>^ziY0Re-|R22s{;cG~l^#O%hJ33UY9vq$UX`RiPxD zB&7~q_vch%THQ3@x%;F{Ov=|}V)E3DLUDgQk%J3qUloorS`p47fhHWM5?$C&N6N65 z%T;1p5y-?OWoW~8$km5+B2tQ54vMiBihIOUid)>(iATa!idzDT@g*NaF`m(qCzN5Y zbV?f^ASS!G@k%AUnD`C3ty3+;U zn~fp2hb)p5=(giuVP?BiDwR9p1hv~XPEGwx7JZH}-#jTo1z>_A?e%H({ z`QB(0B;lka(uMt8rVs0UEDHx&UlL9-o+=#0sUDo$L>3NATOKx|S{QaCO%;yrP>j1i ztc|7&d-ov>yH*p1-8&RxD8@5V@m1@|z!PyQ0#DgO77kpk2xk#f39bf}_`4LavoIUtWEBbcJ=Z`wHuB zM7ylnB5N|2{DQv7+S^~f$BmWMen_vb4kA=r_H0{Owxcz9nN)G@c*?5?$!X21ynH>! zwBmWEpdE{nNhvv7MlJHQyQx2^w2zt=t?Z^=88u$pM!m9JnmjYD@fr{K+U-mx)@$do zD;lo(%x!XRe|k;IHJ)-b+odoOZSP^;LHIpC#BHmk>C=m=rLD)9sC$#MIydY-w>Cj+ zKeaGL>^%~Tz%yA#0$vW+#No-Rf>1nHQqzs+t3oMxGAZ@sTm3l=I;?Jz^L_44Zfk0)AoDea5erHod#E0I9o9;rmn=IKbqF6MH9yHpWK-_?{MhA)O(#e5+V ziR-gDM~t5jvlZc!j3;4+3)Yc-?n*l7|U@Ke?IF4Fo~N>nV_Nx3j=Z zxNQa_5j7kH8_j?I=gpnwI%sS;w?bm`x#b$$(9e*&6MavRRp``Y%h0<+R-v0_--;d> z*1~ir=1;|+8F!odPyh5!>C4=so^4B?T8+NYm>?dR=tuBNr@9lY;-E#Lmde@|{Cu@{ z!Kqv7U@#skG%!RZy?bHGh*}&}&T4TmVzsV^qsrZ?E))K8b(OK!s++OGu64RkUD@7( zb=vcA*m6{U^S%A*TRd5@ex&0)>ohCZt7kr4w4U4M3ifJnEM{M`T4zQ5PJ1p2HgCX~ z^1_~sLbBGFVOA~nW*m>rv|{wytn0$a%U+AnuQg&6w!H_t-pTHEZStM%+Do>$fAcrH zd)(1uQSY{KF>!+~pR%=C%Mg0|f?!G-1 zy|;qAKR9|SNFN0?r@AP3aoR6YDL_vJZL2^}1?iBOH;opF>CjB)gkL+?H4#^v{Shm{ z-W741VRT2Vg_fEpoQLkwNI^aZdMawI7H)6EYQpZ0Sc`fM5)1K}u8FvIxp~5QaI#yX zRzBA-QK+8pmZ&{3*)|cD-`YbVpAWkv=92g6rDz|e+9WYqmvl$0rFm1tjb5o~A}K0$ zPWVNY4hmf}8YcpaH%%m7v2P+OD|JldMb*ZMpl-2&!gy?Q?Xj(L{R1TOk^3SSE3p=+9Q@iQNu(bH`Pkf zi&fu56fpWHtPpxCL{|lRD$F*EwN8rhRK1s?RTlavXvtI;1urhNO@t+8w?r+7o{D!k z*HfjZqTETb?VVV-%Bv}4p}m@dMERBqV>3n6ArW^$(B1~ew)z@q%xh!l@te99;--V7 z*&y23K$k1nHea1UyhO|w+PvjzL7qpb@^09f_P*t%&@Ox-uDh-l*PVz`*HwT??4S2} zT~=p?rLCW_uBI!)kk`4Q&i53wtpx4fml8bIl=CK>Q`OvBL~!jPaP|xMs|4)r0rr-F z7Nw%KXLEfz^D6RmP;;AdJwN|{JRv6hBeypFPt|AFuFKMNd_S>gru!$R`e%mu=M?4{ zDcM(s%}gu+%j}HyJ1h0BDY@HIjxJjY6XA8iT+?dlxO&;P2GrA7O-U71tNQ65{^6Qd zdOWotjP8%bf~TK#6wP9|rex+-1@$s7si~ER}=a25Dam^fOul3?hLh_>n{gywZ`(r*pZcXI2CPUY0VHx^l?X=Zc9)maF6( zA#O6vgt!UEP~u7X{SqL^Kk2r8Aar)w7;3Wi)3d zSq~nMh&69~#;h^p5nCEKrbEW3O|-CP68TG;UG=gJOZ3vQbs4NOSZqxd>q%lU`}@B4 zS^85hG6kBEm&z0)k}s)5jvkb&I$fu+jYzEdQ6bG(s*IdmZN8dQ-2>)a>JR>)GnuL; zP>+B;hAQNe@0EIq96+_EPU#$}w6i(VBcBhD9u=mNQ3wv%V%lozMRKRgg=uqzqTq!J zMUbrN6ryTcw&*jRCBqo(3_{V2r#0ef3J#v!pocbFM2~B>NH(@9rZ%B4Xtlvn6=;~M z36|ZJ1kZrNphO0)Cuq-56=(EI1 z`%oB|4grOM$q`T(a#ev=ex z7$Ui$C?vRL6%`?>nUoTo@(EFam6qfLqsUVfWPAt;%A$&bw3(3LI!#CL?~b0J!$e8& zOeQ3_P8Skf0}6wsF=#cxia=o?DnevJVX!3zC=6V7nAR1>1BtTG(x$3Fg9S~2mjoh0 zm=fAS&4t47E#K1g1bxpGhX3-sgwP<}vhZ*fE{r!^kI3PLDWA+VO8(!LT6Lyr$PuONMRYB59 z>?j7b3NjI)>PT)E^b@y4jA~4}?kIxVKwGa2#84#>at-F?)al2FMTN$You+6 zH|EMkJ|%K^ZA66L$GAwh+T6&CU)&}~_B)*(dEpD`{KyN&TUnm(X=%Qpr88DyhW%L+ zCu5JGc+ob5Qonm6DtVksNYh?LSgO?ACO-A27w25qW6_jLo~zBl*j4UCOeJB6S5qM> zWzE<#vX@^wF>;fY&Wz+(wR2-SM)l0(j#WEkv?VLn+Dh3OL5|nX4gj(GcF#!NE4pOIePF~m`TGoI)-RG z7m`x4?<;9p1*E=g_^!Ta3=GFo3YBc9US;;zU{6RyrI6Q=I$1)YgEJ3OY|BFmqM zYnd?x=UeW4Tu+4W%y}jCz?_VBo7$G%pz!6{jifGTzs)>W%h>8?4s)Fmq+Bn$}_*~?hGe#W79^xTVtwEBW%S8`z_4oiV-w= zDrl(pP*6j3Pl$$zq$NT&KZs@r)!HCC8PwiKyCvaqyAffJl5PWSmf8&_S_}5lq@}=Z z7T}4=UV)eSJpm(=wE;weKb7`(n<@HK(5Iq373}#U$Cn6=p6c?u-4*%M*me1)#Gm^6 zDYv_#yQ!@oK}SY!VNW>c@w$Be>A6lWDyD?;iU5ArbRm1nVDfdr$yIDtVT;vnv?5mP znklWC7SCwapZJNs0$s>t%5$E#^yWBb=*wxy5|z`~iq>2bldU2y8W%jK6X(v1lAI{Q zMtWKoHf7yHupsCvHLM za#|AA56tSIqW%^c^ml&8mo~G^ACKgz6mn2N~(5!0N>Sv|Oy}m9<+LfsU z85hjUk>iPJ4f1%PE;NqHd0FvzVpc-j>nEhbRyv~%&WV6!fiKnaE`hFVZ$n&bN}4B< zN!jc2J)z5p+ai}4jr6X6{EwTi#qXB3)y1pbkOG#Jrkby%DNloX|NFJ#^bh`lr7uzK zsM}I0>b?|+h)-o9Y704NxIzYM?ofYDtLZ)CPIVn(r&3NL(^zSt$`o8%p^;Ik$5_5n zV`N|mFuujL7~)Y0rrVYVLu8g_HeF@hyOz=oDxVNrX(l6})e;i%gc1_>H4>7kNF%*w zUK4ruyJNw_$lBtD8=clUDzZkbTSVeSC`0(Ff};|A2bT#tv%eXd!SD1uGbAt1e96ZP zpVIJ_ol^2P|E}owWJnTxJNR}<$AfS0lsowLPY2)52zKGP%(|V>TzVx!DfB5De7oY` zQ*PZV0N-vf>vjly`wSBgzTKeDr$gY|JL?X!4n0N5)oOK7^{=YYZUEIO9U*x-_*Ol+H>?1=Mc5Zf#of_cVJJ-G&;M)%(9(?;n zzJqTcP3^>@&FqFv+uFO0{&vZz$3FOW@a?4kU39PKF8bSTvVDtI*mYeq#j(?3@a^E+ zRl_^_+f}prLSTkf_iMqoPm}A$1K``iw@=gT{Q;9VUkxr%?Chagp1swBZwKF=G`pj} z-Rx^`LG-timUd>+o_1=FhIU~Ip1rx?+rhV!#`eo3ee9k|y4Zb_@#&cZz8!o!?Q?(1 z@$IjF{nSR8UL3BLag*WBnKVfV`nqY*_j#z3`gKFl|E??c-Z-vo&o_Mb=0Mz$+j6Kc zHs=UeEz+?9a=DJ}tCc%uz}drNhW+I{Zbq-_DMm^gee$AM0-4Juf{B&pyMU_R?9QN8 zKQ;u-TG>V+Gas!Ps^#P|q7|PlFj{x$>Z5793rUj(o%1NzV)atwinUDAm1~-)FIF~D zcf75Vu;{xi37fy^(!#qgZEwZoZ-F{9ZRwMD=S!a=j|gHFGgry{iYfg}7T5PhMU_)4 zq^#n?CaF@17;jm3T}iH<(8AnGgz9rQ+H>-rQ(f3yHB*xBv6`)jcU;Y7l^nPju!eHx zV=H4k`Wb6S)>5pnN~=(!^v2o1j6P}A+H*h?(dm&PzO{UM6mz9!2m3&5spZrB)Bc+`+jA42FZifr&f#!9h=V(GPG_lB zXZz09^Xr?^vL~%~gSuO+%gGPVSkcpJ#b|xX<<2 z)Sq#Wvd7C$38>fBHIm9GhKh^7g(DQZ-^DPB-Tsh=EMcxTCTVjQzN0%fiWq%Zt=m= zHIGiAPTAJTY8-1@BNy&t!Q`yf_fW1FWMAd1Io4TLE!_(=5%GG3YH`1fD9&VkMR9(m z>xe&NY|$)-+{KznVtVi9@jzwy=2a8#;M}v(T=2Wz6>j3(lKwKzE933tyyAQ{r{U3U zoq??vdHNBy*)wq1lF!Hy3qNC*wnkO#w0)`<6!(PoMWDU0$u!)CCYDGWm}Ehs+%+^JI_5cX$N_mltcpzz;K%}?d`V!^r!PoB(V z^;-UJLi@kIyTD%3$Md55iRO2b$;&EtR(Lmuq=SyoN=!m?mGC6LzT(6aW)rB0!2GV$_#j%FFF zl)eIKWtJ7ly0~apE=Y{WBMX}1w0?Lzur3|`=$cx1K2_5RV=?fDe|S~~oK^nz`W4Y{ zduCSb`~Fnpt4et_y>6?Dgt=ID`<}vjT~|d5#4%SGGqf+RaS1(32v|a|a@8qAiDIEZ ziW(_jf@&E{eX^CNSX6q)I|@=#7m7>+vQSrpy3zF?$LJc6VL%p=QW0b!CJ<4HXT>Js z&brxEo@oQJ(5?H)|?T9d3sKDovXIn=maBKQJ_L3lz^#UKko77`Oe)KxP=BOo-P z&4nxkS?DJ*Ep&_zVOq$ZDZ;dn94ErG(06|~riH}N&{Jhv=yk7S+u~eDZiy@SRcXkn zEA5xlMa7MCM_C^uDj3dykrOt?jr@=vM2aGMcbyyQ(p1r~gsUVL>RLJ(?8O_YwhS-3 z_nmmLk#5X83gj%5;}=GxdI?#V7n+$ecIBHKmfnkc*7l-woDS(^s&@=Dy4l+x$u{S@Tyj`}RN6AUgmvlMaJ9c3T~2-rGJw zac8>)siXG{KL7dG`*HSFgS#Qt4IXZ?bMVumC4(~OFBXh5Y{g*e#G+v_+Z4iE?@BkE z4$KOTqkd^Y;r@`jjWA72t}CSFWEJ7G$@Rr)Z&8qJkIldB3|ls3nFg zfvSR;)LGMQtFfu6IH%ZOm{`lOwTbf%Q$-h@f^?deSn}yyY(iEIOSy=8qqn5NIuC_g z8tchB?Q?7TM z$0YrL`6|i9in?Z8uDsLCq53e{%E_DZ_uE&=Khl!+Tzlwl3bplt%$1c7DHw0Lyl1w$ zBy*-tO+`)zrpG9~2l`6Qn+<#a=2SFa)~PGVs?OA;YP@R?Ro=3+Qctz|U;IVmeINN- zJ+}98Y;WDDcYKbBv+c8m-L0QjLcSd%^ZgZ|`6ke|B-Gys+A^ihpuK2H@+ZYpL!q>| zyf-u`oLVUw6xX+nMwR7Fq*fvCH^@D)ztnX3Qd8Hnr&_~kJn|Z6t!=2a*z(R)`k=iP zbuqfy9zma*8=hs=Ym>C}sT-E9%17-+iOFcKuYy%&D_KQ8oxM#8Rz`E#GmGn6+helt zV#ZHn4klGk2GCwEgj8tpxE+1}TY=WnA}^W2@Vej?Zw>!jSa5F=sil{G?q)vV`< z6|{ycw$f@>?5VX~Ih~yN#=LSA3MQDHlt0kyWUM)79TUARk(>3sko?UHbH7ErZKB)U zawZBbEqjU6uHuGui%JwJw67GJRrV66Gev~`t`we$I#OuP?L`@hQZq`@cJj(s>fRC( z8NDRJ3R+6SCKA7gMD>aE_K(XDtsk=4BkbnLIyu}%jVn=Z(Xjg~qUb`W#bj57(+yDx zuk=ZzZ4p^3#GffOKb%FizK6%meuq7nH9A~*r^kVY%|3@MC|Vpo>$7NI!)CYE2HDOq zS{Y~q18Yd=JqNO*z-MYe7?WBv2lfn&*%tFUwLIha4y^ zPusKNkxb|1X4SbV9d1TMW2#rdp*mJ#uZUP>U8)r-VW}3lMPA~X(4<#WgX}6x+Ns9w zt@hke$eGb+&e}|r@gx;5zND}e^%Jd&sK}Z+LNt(5`Zxy@o6K>(hZ$8kPJJU*H=2OK z?^@1ag2m7MoRBP<3US?=BW z?0L{->`5Y|>Gxzz#`kQEfxoIKpFUwPknEHwk8DPZCl2Mm4O{Zs!uJVn#!6F1Fwo46 z*k(o>E2j~{?mc6M4YF5-I;$%TMeN!{?jUD{m9Vou`^qy8;VQ!h&#FTP&8k8k(@Nt8 z&B{WWWTg?CVWrW8UER|WojP(3dn&^YXUf8wEY*>SFYP%(j9R8mmXh}o0~acoPwjHX z)AuOf8QFvKbS-e9ln(_L>QST>($lE{7kZMRl=O6LaG{kEB$NN7L2#jr_%s}Fp@J3# z7h0l4!G$*RQ`~FRr>X)MdLuasE)-noAVk51`XuO;VN7T4aN5)K2<7RT;6e|IG@(CP z+TcHFNN}Ih1sA&0qToU){mE=bd>RgAKDA9*Pum6;Dj895p=aZ$<~^;5&wlbu#DCJ9 z%YcpqxKMDR9{)+>4$oN$g9`;0%KJf^hrUp|8+1g`7kc!IqA%2H1Qju__cMx@2vAZ4 zE>v)!%~dRCEdVYQT7e>1U=5h<02+T()$Gl`$80fHOg`Vcm zq)QBz8o1L}Hc&&ZZ}`+tbr(4N$-8ZEFp2sW%ojO)V!q1ZY-RSE zm}$JU+~E`atqvqpS@5vcly*EkXOQ-;HN)obwMG+j5Sr_6-mBGOhhiYz|G)}@We_SA zZikS`qADQE((MtWKr~DTZlRZ&m+*BG6;H05@O-t3LITcXtk z8O6FA<&og=J9d%l4>iAI!+j!KzTxDDs&a$1B*@5zip}bNnOfj66G)eRlnUG(>3M?e z4Jnz{UUd;0Z;7s~wvNQOpBqX_|2@~3)c^az`KxVzuix7ymBPKrXu-W{YU-{i}h*tMMp0v ziX(kRQOU5D7txrMs!yK`?|iwlr`=t^ZX{ws*~mHqvK6Lkv1LbU@s+!yOdQFAm7cB0_Nb}EwxCd8%?<5TbKUHwtm-@0Qe97psBRz^O=ZQL zP`X!}%BL4Bsi2{v*^^wR482IAG;*OR*^{>9*rQBTziUZ^>0-TfHjPY&+ce@Lso6+2i z*>>n`kHhwfYm2P*>F9)VPFdwLb6jMK&$1dxd{qf5xVa${EcjgQTC}Z>NmkbO6F!!t zC)n6Y?)50I6Rs|+suN{ybuX+?s#}d_PFdt8)>JU0YDxGWFv-p%s0dCLDDj%AXolH5 zYD4~#CujM~GHV%AlUw#?D)M1)jdN^bLJ`t*g(NI+fcK#L97>N8cHrY0T~`^^03X-5 zxcv@3&ZFMUIQY1YnzKnb5(OW3k#peVTv`r%9Odb}_U#ex?3*?Q0Vgc*ah#I<|_ShuYIjCm>PtPsnj@R4W}kCYhKlk6{c9XlE%ap?v>`bvnFbxQ#mo$ znz&@fwQ44_utvI#z)s)!lx*3P1GHsdlZR6g#V*bTY%a1AZJU&{yO{K?xq1$GF5=D! zX9;&~*iXoD;j%wJS4^Jp*it86B2i!Z(mqL?8sTuUOd12lNvMw$Cq|zw_6$B|%stZ1 z=$d(-<53}|jcs=f9$Qu)KsJm%g6tUKWO7e>Bg#D9rj*0`m|QMcl(H?@7OU&UX!oUW z>mAB8;_g#6)+t}Z)yJ*9t6{r~QS0WKBiA%IMy@?ejbdK`K9PNTdM^8U&*PKXbS#@s zWSjHY!B*moa6a-m>wH%5actkCbJ>y2X0!uGjcbPiF~J?U*>LxjCsW;lNr~&o9?hho zr5jAGh-ftpT(1k2#qAOC7PUsj&zLnZe#F$&_!*G1tLgN~fMnw*{CXoTt>jbfk!Mc4)1Wo|PGi+c__J5UVLntJdTio8aM}>QaTjy>m&?ZU?H#nvoIVSCL;E7`&+uCrYp%Z*^QZp{nWQ1v zN~cQ$jDqqW0bV$@Re%&vEf}E1ZM#6}9OdD=0*!RvPr;;%);~ru(UrNZxMkRCr5c{kT|`&|GtA0tx#26O0VoppZLi zeL_7@kdAbTRE(4%P>qqa^bQ46) zuI!5tHHGyNyyQt*Cb92!l^R&6awUS+o-PSkN}=-fD5WsjfkbHf;+nifnp1SP>P)-Y^S(^;`GGD&bQRw;loel9xtD&D$Rsa$ zNuZBNNrZ?WRPGoG2U9JmM8OmZDM563K^RO5>G&oabHAe87dtp zdreDd3c`7B!ohy0I>BxWg@6hJl>Tpof2aBf&rdmi);<30XZv)1!r}X5{g&}K`hb%E zcP<5Q@$H0YXAC;$$t54o32&R*X7u&tG`{+{Gt!#!)RdYYd~|1_3G+<&WD1Af;5@P+vDqm~W;pbuNjp;7@U$6S=uv@;_T#Kx zXW=X(X5vg%jRj3EjRiw0jU)pit)~noZ4YTSjWKC#(dFhavg~vOHKtvQ5@WF%7e=ZI z1s4L5)43%kTbQL3`xTEN&BS0A^K`8kIQot5Pv=HTUEW5zy zyyrcgrtuBM%;-SE%p7Tld@;j5^FlI>b?JA)v~<%TR#+$_Ry-C(6Czh6=0PNz5%@lT zk!KPDBTBmj#+68U3SQuH5kaEJOmv!|8!pEnCK9P?#2q~YV3pB(gY%x_119P|J14{^*V=X@OVkK;a$`NmNn$9(Ukug~;G z#5&mXn02-%G4p6ogE;2i~}VIOem{eRtfme2~B~-#q5? zpg8oqu3`hifYu$P8E7>b&p`5%l*3s*H%4$( zSC&NiHExeVB~j}oFWL%34GG{Twk2N8J27)y{Le@sDV(!m8$U^Fun zLI(8_g#uc{Ny*s<+b0tdwn^tCe0xk#m=+tWkPaQQXf_+SxEwikvH4=&Vz~CK1&^ka z7p3WovumTq*_>4#Q)9<{-bQ68Z&R>cY9~q<3!w@jx-%-W`4oul(b*8!mO~?+FNR4x zw{@Oace#N|Z=Y(TDC6FTQXFHhlR5;n@Q_d(;}*Au%?;qZ~HSqa8ZpW7%}< zhuWPpY)0n}n?WXmnn@;uxJl-O43mxy37bq3F_VrJQG1LSF-vZj(kr4!dlEDKUL~2S zj3|*BJ(uK03Ij{6MDv?gq|=S} zvxc8kES`p9SvCcgIBXs&aXdm*#sglpr`8D8pZOU%FlCNR6$hu0x)sybv`YQZ$d zr|O}Ihppx`IlC6INZhHIMe|O_OfGmKn9;{^o==C(diFG#9i=m7-4?@UKmOzXxY?Eo zCeDsjGvX$!KnSF;bz#{Tg$|Z7N8{-j>WhBR6OCF}k|Jrt4;$ zY!WZady{%;yp#QNM%)2tW`k+JN-CfG^DCl}zdV->1={&88_XJknovv?MF&#WhX{Sc z9WUewYr0Ux^mrkUc*BNVG}t^>DA*FAG|-bu3lITU1lSUc=ckTE^BY8dW9iAgk@5Xb zSogyWsP~j$_la_rsgUDajm>=1sLOTxB)83JOCFn&(-SB6j@QYt;vcWHUk)(h(zcIhteUQI|@*Ujn^9PN~C|of=rzly)uPOp{4|v3B zM2hxYfeP&<5f9pOHUoM^UGnoxc$8<0dt7IaTV&@EOBv5WbUydF4#WAJ=Tw-@t7eMS z?8O0{*;77y>1JHs(n+}NYgJMN7Bx}3p#U6!J{ zT7Z#SmXmN>KOgd0lC|3XPEUQ{1D&AiqkUv?4`rdsD|;elsX`%n1W}dp;dN_t&-K(4 z!!wGfd7a~dc^)UGX`JjVUvt*m{LF}UcBUJgrlA}SqhtohvRj-}VKfPGW_S@=yT*w4 zWCzdGS&lhgV~WQpu@^=7vI>51om2=Y0sE}KN8Mw7<$&zkZs$nMLLqJd?~hOq*jc zV$EXX6jeuzU8Js>%ZLJMW+} zLd?VXE}fLgZ8}R6*p%!{Y&L}xI&4Iz=FmZ&)Mk@DzN2P zMJ;9y7o9;qV#K{MaAa6*%@Dtjd8EryV@WLF%qX$2CXH2uvB=t?3y8Qahnoza%sbUg zHWgKL=)EP8gzPn%Gu`3&9Cg0h!84)Y(UO_mapgU!qsOO(j{d-!&T&3f6*#WCRM@x* zB01yCNe$!lyJ#^CU#1wD6`f*5k=UP^QX?#pVZ9dc-K_F7jbxg8jhL$GaJ$PS5A=j-1Ry8k;xf*QV|gLBbCtgBOP+}Bw1tX zNmWYJ_X3}!CmD;Lr?HrsXIYb$7s0{FYpXHx)}6EToQkKOLB{tGXXBeESb0Xl4~Mbt z!cmmF3~1J!ML6xgc?Q#dhH51HQB@?@ds)J)k92`iA0<A$wwx|A zqvSW`y*bg{Jw7=$6fi=vI;N&@CO4 z&^;S5&@CruXXOU`Gi8_!J0hJOnpqIaj43uqU&_uhn-Z_wF7vIOG!2a^kGj{T6zWxw za;KyqrcSS{Pnxz&r%Ssbc~h4s-4bZp^Re=HJOApB^gHQ z9HmD*hVh(pq!l^UXcLW!S9G_o`5a-(B$S{|D+16B2>wsHAoCsvnFliO;QT=5HQvs= zgUmA+K9G4Q=QoHu%aVj$1t-@>QaMHcD_l8JIiKt zUF-^b~+Da-p1>J%yYQC#?zJVlCtxCka>#6I|Cr|A~p|X9>~0e zxUwz?*>tSYuUWBHdOGxv5@HR%|iE zQ?ZCXp2|&d%~5H+sWhx){iXAc>PW2z%-+>{koUIEd#d?$J`lFaR+61upD`Tr-3wPp z`KzYTXj|#GW!<-<#kOvdR@-@Vbl^@~qbGOC3JL!-S(5?Ws#F%R3VeOQ$XGIgh{ZyI z$eKcd4i2Hfu$oR_-?@6gJlY8??cN4#7_ArBx{Ll`?v4gw(-Mus%#}^T%4a>o+EHD@ zR>18fCW#3h;x~G`aM>7*!zF994zF2L4ad5ecN*6{PsZSJ+M#UQv~M}EX-jix(|%`Z z)Ba~=_ZDc!XpPYJo%TW3ZQdC@<&j?LY10v4YT=SR;}`itO(7F$IdRV1B9aAL%XWHi z9owaQ>)X!my>mM;TJv^vcO;+@4#c1>Wi)j2(CzKrN4GcY@je}+)7uEKhVO#I`@joc zxmDaRjMS!TZbp^zYOAg1aUJP9zrifEtk;TK`}*{~x4Bmg*$Qv^QGdLbtM||iK%91<@ky7ZB;iLpa z$_cd+A|(?fEoVuVo9vI>L7VI!!W0^B3FfNfWW|gkY1A?aGIxd? zXiba@26aam5>AuUsgX_`B4t^uT#_coL#YlK7P#_bEn#VpY0Qu!{g4nO zEmz2pv8(k+Gh#@SBV>z{Ey`p`?nq&h`i3^yLzXz%VqBFJo+(X^AX6oUqeMv=(7Ggx zSem4Y5iQa+AyN*pvMx*VN-jovo-It$Xdq0|R47c+q!cEpuY^e&aJ0!Dw#CU7tqYU1 za!Quu#nZ|pd7!37I#s^@SjrTnNWUZ&BBN}kN1FLmoNNUpak3R<;$+Jw+GI~h;$+JW zHPW#lQpQT9uRWGCRUtB}^MuElcq~3vvo|P{c3MY`FVDk-Y83YkP)O;RPKDk*&aZU)Ai6tqwqbad}@t}(UqS;sFDLZRhK zx$yLbSh%@T7`%Kg0UjRH0lf)CHCdKQ>2v4#P?m^lV$ z2hA}+9UOyyo>h_9s@hr`jeMzZTB1;de0RJf+8&DrqGBjk78bO`V`Tq41ZBA@j3G7;UaawfkW1RJxh?~TT0_r_WG zduOM2qp{HYo$=3V z^E0EG^D`qJ{Wszu0tN4UntH<^i8KO+YsFs1zL0>{?k1Mh-3Iv>=nbHJci!cJPIh_D(28ckP`4bsM?#%>11a72Y zBm1Izqxb4}&GV%Zhv>_~ocyC6h(JR4d8SYP(F}+{yD2d30;C1#2z=SgWbdEt)9ydZ zW-WliA^m^aG#Ni9Xi|Q5(4_rza1ef;1tL%}eWjlA*3T&N2|q6r@qQ|2vVB3Y(SG{gxIcDp{9nI=2o!Xm@6Pak zsun~bh(N3F&yQ8Q|E!vc{y$wx`uysqNBqe+rTcvQ=|u$E)3uN5@md;jMs2My>$giC z){y5rdS+J+=du~x%~2$q#i@P1;L*CPSu`GT=ra~rP-{hTYs{4 zVXoE~wY@@Yy0+lVjNH;%!K+QW4!>3nSLruZSES*tGRCOIj!&TROvIgyT*#ueq(5KV zGGe+`xz>0PT&$L63-@7$Go@mx^!Mdh>G0)% z>G0L4DGShfQ)wIXrz|9hr)gI(Rm*N`Zo9)>^u*=vXp)O9*+S>~bja4U+2rl%sI8iN zAey_qa_0DLF({^VS6ySecRf@i{t8ut@|&|#R?sWxtl)%UkcL6X8%f^s#=x-=N@kCfVPiKfrjAQ))QM>2>RpLjW!i>l6l5)kcAmB(mhH4> zkrnv-*>6C8_B9KE>EFA=NmBi^1U$Hm{R*!r+XZ_6S+$m_A!) zXG)BnF4A0`7XgR&!WT}GcA~sW);V>Go2v%E;ZY*5@paZ69A4UUwikfI8|IDG zymLd`;OwL|I6G-E%1$|wx1&BI>L6FqD$iw)LkzP2s6(zXW62VA*NY6=j)gRDM8Q|~)jRy|TV(fGb9G=VN zfy1NScf+Cx z(>9~+m_vCxYE#~h+KjsE4mdn;ct@X}X6fPxI6QE8svB?cb(ZDubyk>Bbg9FbI^O|@ z2M&+S*wXpf8Cx{gO}b8hT{T>7yHj=O!=qeYqw z?L*|Gov4VJyODJ{JDD(fI~oLs2M!M$9vP+mFlK2j8n43G>6(zXW62VA*NY6=j)gRD zM8Q}21;eo?rv$C1va(69(K0J%Td&Mg(vd)R6v>hw(X*-qy zhX)Q19A1gc6TNnk>v!JqOw@fha$)~nOZxqHEkpS_%eJ_?76XR|4i6k2IK0l**@mFv zmfc3c-Qg}6y4)QsU2I9J&h;r>XWHQKz~Oxk`l5bfMGfxst&MJHw%j zowgZm#~jMrQJeC1)MnIOcfjF+!vlwx(RM7~s5Rnv5s_~iVfpfnB$%(> zRiXvRK00dhm2_$cowIlDb3fPKFLy;9>Z^tI$mxhg!>88!KlGUYMi8I<+0k1;`0x2Z zGla@Y%vy(9r5P=2HB+r)Jdkit5zTbuIHOeb2DwzV8qXBp8`o63TP#!@YkX94cj&4s z?7oY`r?_w9;J%52^X3igeHv^>zGp+|hi~ZMCM)msaMhIPEmc(C9p_Z~ZwujN#M?y# zE`6H_+fLpqf)HM@ITpxOtg%pT!m8|^lQsN}B4)hsog{*!^ClBXSiT3vy59-9Ta9qg zHcck)h|$vLHoe+^<)*!YFIVuTzFN!Y2k3&nz&6(Pk&w+H(rA@HT;mCIF3_%sc|o3a zSwS~pQiN{M;12DO`3@Uqu<*7UvZVNq8^7@z#W&e-`M^ElF3%eq@U)fgz8eL7wOwl5 zG9>E`$d1H#j|$&X?^Zzr{aq@UA>N^axRipEcG6b2r-Qet81t{fGGc%KeNG$xMAKN<;}l2lR9dX3ZD?t{eB7HGZB z_<`0NR9}tGYgI9YXJr#Qucblj9aJA^J)6#JQP6r0*B3B%VJhM8ob)=4C!f5P&Fjyn z%w1T}c)R3YBlNPj(r^jBl*W?l&}%o`cX;whW4dEFsAo-ieqM}5$GlE~wJmAMP+YK&c}YL>$@ z>Z>H4t}8Qntguey(aK3GkCs>2yk6&1cvdFh@QhT2zjJCr5UqLe?%#CUjm)gVwXjyb%Sh2U<^c?x9<6uzJ6&s5~0zOdc}?mq%Pm<4GH| z9;fl77t(drWQnk=6&W<$Y)I2})d*wj$_^##>Ne%+ChbnwRSk>0^J4~g7la(jt`a5` z-HPcmcVPy!9%wz*x`%GPm$jK&b*GcJ#KsUVowarCWb&4zDS7AH0h`xTp!GoOf!1@T zY?H7zYU{^?sauh1JXTnz@@VBGl}F2~Y+kSP zDLg9^aCkt}CGcz;v>s@^&g3y8WbM*qLg%$KXg!nk*4^wIYM2n+<8Yt{P!%UD=^zUEQWU-K5>=x~gH3cYe&^?t+j* z*;T@XqFXV2<}S>D)&s5gh|qeyDNem9#Sw7dnW87cx2PCNGU_T#y~mD3yp?wS9VwJy z(Pu0cacO7H?Ts4=(gx?7epB3$>FsZ(;rGPxcc5UNX34IIMV5u{HW9zG#AsKuthP#y z-YHkL+a6D46I9NdKFD``aNp>GC1j^r&1VCp(~cZ6js0-alqV$wXGkGZ>#kzc3AuaC_tow-GeEblP1(j?wrXflM6Q)^N9DI;u-{)nMfqDQ zIG+243L}vbJ}qzTS+hgtqO~3@Y*@QW%8S;H!s44K{PTaV=p8%Yj%XSQ{mu$jWW1Sz zw$gW1UEV~{(8v~ew;!XX*3IfKsun-dc_EXxU- z&;y%u@i|~~ObUktE%rvn5(0;%O|m8pPO~`e0m9JCn3}Y4BR+d0GbVYXf>R6*Ev`~G zMxD#yj0D)6lilgWjcI_*(Ok~2{Td@{Qm4Y+$Qp~dF=K+XaovEzA*qDHq3MLcVd)x$ zL#i_NM&>zHQ}85Z6Zk%TBQldDPA{*JHf{~roEohIHfN)De9p$t7?e#AqD)Q22Ak8V znu3wiH=@AhZ+HT1PH)ROjM07*TCyRB_@om@o9NBaJvyu#hdb!aVO==3Q|rZ%RnzS_ z>;XpBq)y1+@FdtAusMXWiA>4dSU#g`5+@~Y%z{SFOy6fH98&9YH##9X8{b#NjTxW> z&6I7hG}X``ZCnd%4%i&4C#S_M7N^Bh0*4hyrl#VX%#9Uv=^K$Cluck#x+bw1U6VM3 zr>U8et;zfXf5S@x_C}@zXXCq_o|!aUrlyhrn*%mSw&1wF&)N8yfVweK6$XdafXyk8 zIK4!WHL1%<8`lGy12$*t*dZM_zKPx(^yXZgPD7zUa7F@b4%nQ=+*tR<+*o%; z)}&5_y^%E*abv~=Y2&&9g+o#agG18^fy2@@3Wro>?2XKGs;1yc$|mrA`bK0XNt|9@ zA#L0musL9JT=qsLe9p$t7?e#AqD)Q22Ak8Vnu3wiH=@AhZ+HT14%i&9Io7tE5gQ!N zurO)G_?p-W`5T@Dn*%n7FgB4XnH$SzbWP%<#En_d$eHQ;4245#UG7FFBxmFMinuWY zl%Sci4VI=F8l;VDfz1J%W3f0bX0bRemJ&FuKr%HI-(+sApiAF~1fgsKlhQSb&FGrM zAv{gZlx$7r7x)`q60kQiB{&=3?exr~;W9Oq1lSy~Ig+$-eV?=OGXZsDq$&&!tpS@; zAaQz$AZt>WlQypRG-Gqd5@EI}_;8GkGDpZ=Cesprx5|-uC@m{|;)r|h{LzuH76hNM za7{2t#v6l=?4UXvYw#X{B`FcQbj5=$Ex3JtaOniHg>5R%jCE>F%q{NAt zkDlc$N9+|ikbFV#ULm&t+rHiaEF(9q0QOhLjEubsm__})!1-2SX9F+~JBxpNSTFf? zA`xdHau#J_#1i7dNSjne5}al*`U8ZXm@+kG;YWP(!pxWiMiQLjG(J{fFkH&wNF=<) zzX=!qs&&31;Oz5l;jzA#TWwh{zn}bq`medsMF#5|UjD`0!rr;9y?wo}y{*@e8tZyj zLXC{L>E2k2**haKT&Kcj=o*X4h+=}xD7XR3;inSLW26(p!%x@v4^@>BATrO{jtNh) zACtb%jzpPB!sIBgkRw;k3`ern{-g30E*7@-1@~CE5_nJ6SdE`4FOcyU0n=^1yUkbh z3eTk$wuHeU7!1J?q7;B{Pm%-SyEq-m$^zYNbuQ2Ijk$@}*5!Kle$ID|8&CYnK_3U_ z=i}hKot<%TmS<@koX0^L2WNe9PR__?x}Srmjj7WzDfW-aI%6J>{a|uNHq$sb{}w+m zH%}dz&wxB5n?4TC$2t3^19N+L_Q2fg-Lo=F=tFTO5*?DQT;ZfFO8nDtkQpcA#PUzZ zNk$!sYXNyM&isj^GB2GxD9iHbczBk?5L3sAg&d07(IVS5G>u?;!#HqO82FGJRl^mBxIyoNu>DoD2Rb>Zd zndhhB2~VDcCw>2L9A$8D#=+STo^f!-!I>SDrGtaB?9V1XTDAMpI5v!fvJBzijDzzy zCTlA>Bb(`d4xToqPRpd&KPKypc|7)m$r;&9gGY-!BY#c_SL$Z}CoRme0e>x5_ z<7Avz{>eDWs3UPLAP>fwKXFv%rIQC`Ssopa{TvR?I5^|r{9buX);<(xc6P?WnH-d* ziGy=|UhY|WN;Z5PoN;j0$K??*PsT}b`n=p9AZOtzQy-80h<`>lGv>)S3F6?4gEJ1! z;W1e!t;2B?6Q|;W8yt`QRB~J%>Ew9qr)%eARh1o-WuBjgCp>u)p7j00ag@Qq83$(^ zod4Jl&SS-uv*F6wWp#yvMOJ4@9_^SPo%5o;;&3L{-e;xuSczp-#PwHI^*vO==;yI+ z7zZ&L$`57sk|`FI`=vJDS}}EP!uD=seUxpn^-;+L@>b{}0KZw1m8H;w~GDC$DZ zmmK~5zn`x;8cU4udYHTtK25wHrmu|EOJEi-Uj>gKUJs`)kJ(GyDaU@*>qm{3zGjHL z;7v@t9=2ZzpHaLXF1|p<>*4l-*?86Khvds&RrTwH& zn(wAPhRyn988_%qS+n(U8GYGDUiDGlld_()-Sydw!W5*XO;_$wHsZydHs>YobiRw) z?rStds}Dj$sxMfnn!gt$Ol8u#s$X9D5#u$M9kO3#InngRS1)iFNfNfn$&Y^29OqI& zIo}odeDo{u*fiL1_#_ya|Dq#b(Tg&YBYb4hEYp@-VP_>>LvfK?fV`R#rh3)n1d1=F z&N4Z9RrIZKEFDV+w7l`GJx&L)Lc6_mitkp}sp>C11G1&`^krkI9H#1nQQYue*7$O_ zJpiS#V6`IDO*uDbAkIKL0&Gbas!PIH`@(Cr+O@eOdx2w9i2N4E0W$ zw9cA3hSTShbNb}U?<46+Rr?Y2rBMF}`gRAxBNcUeD-Z?(4;umN*BxSy1_JuU3nGsS z0`!F`>_-cM;4p#|5C8&CYXnlR9|b@F2yg_nWQGV300JQb6EZbQfdCNL5t!f00f@;2$Yts zq6i280U!VbfB+Bx0zlxAL4dqNDoh{%1b_e#00KY&2mpbn0RsOL7Z3mfKmZ5;0U!Vb zfB+Bx0zd!=00AHX1c1OpLf|)X0RbQY1b_e#00KY&2mk>f@Z=%zTeyG#5C8%|00;m9 zAOHk_01$Zc5cpTPfB+Bx0zd!=00AHX1b_e#c=8bVZCpSA2mk>f00e*l5C8%|00;m9 zAOHk_01yBIKmZ5;0U!Vbo@@mEH7+0k1b_e#00KY&2mk>f00e*l5C8%|00=xC5coH^ zfB+Bx0zd!=00AHX1b_e#00KY&2mk>f@N_`nqqu+o5C8%|00;m9AOHk_01yBIKmZ5; z0U+>nK;U<90RbQY1b_e#00KY&2mk>f00e*l5C8%|00;m9AOHk_01yBIKmZ5;0U!Vb zfWT7)fsf$=0zd!=00AHX1b_e#00KY&2mk>f00e*l5C8%|;E_S#cX0s$AOHk_01yBI zKmZ5;0U!VbfB+Bx0zd!=00AKI$RP0l-~s|b00;m9AOHk_01yBIKmZ5;0U!VbfB+Bx z0zlxALE!gr0RbQY1b_e#00KY&2mk>f00e*l5C8%|00;m9AOHk_01yBIKmZ5;0U!Vb zfB+Bx0zlx&LE!gs0RbQY1b_e#00KY&2mk>f00e*l5C8%|00;m9An?>j;NRi`0zd!= z00AHX1b_e#00KY&2mk>f00e*l5C8%|;Aw@xAK(H4KmZ5;0U!VbfB+Bx0zd!=00AHX z1b_e#00KbZQ9$7T#RUX_01yBIKmZ5;0U!VbfB+Bx0zd!=00AHX1b_e#00KY&2mk>f z00e*l5C8%|00;nqyCd+2xPSl<00KY&2mk>f00e*l5C8%|00;m9AOHk_01yBIK;ThB z;E!+t0U!VbfB+Bx0zd!=00AHX1b_e#00KY&2mk>f00e-*qlUo$hYJV*0U!VbfB+Bx z0zd!=00AHX1b_e#00KY&2mk>f00e*l5C8%|00;m9AOHk_01&t*0{;#d5C8%|00;m9 zAOHk_01yBIKmZ5;0U!VbfB+Bx0zd!=00AHX1b_e#00KY&2mk>fKoI!%xPSl<00KY& z2mk>f00e*l5C8%|00;m9AOHk_01yBIKmZ5;0U!VbfB+Bx0zlvyjKBk2KmZ5;0U!Vb zfB+Bx0zd!=00AHX1b_e#00KY&2mk>f00e*l5C8%|00;m9An*)D;90nU01yBIKmZ5; z0U!VbfB+Bx0zd!=00AHX1b_e#00KY&2mk>f00e*l5C8%|;2Da*pTGqKfB+Bx0zd!= z00AHX1b_e#00KY&2mk>f00e*l5C8%|00;m9AOHk_01yBIKmZ5;0U(ef@N8T_00;m9 zAOHk_01yBIKmZ5;0U!VbfB+Bx0zd!=00AHX1b_e#00KY&2mk>f00e*l5C8%Sfsexl z1b_e#00KY&2mk>f00e*l5C8%|00;m9AOHk_01yBIKmZ5;foTLj9v2V*0zd!=00AHX z1b_e#00KY&2mk>f00e*l5C8%|00;m9AOHlW5qJ(RAOHk_01yBIKmZ5;0U!VbfB+Bx z0zd!=00AHX1b_e#00KY&2mk>f00e*l5C8%|00;nqCm(_5;sOFd00;m9AOHk_01yBI zKmZ5;0U!VbfB+Bx0zd!=00AHX1a3y)dANW85C8%|00;m9AOHk_01yBIKmZ5;0U!Vb zfB+Bx0zd!=0D+qk_yk-)00;m9AOHk_01yBIKmZ5;0U!VbfB+Bx0zd!=00AHX1c1QJ z2>b`QfB+Bx0zd!=00AHX1b_e#00KY&2mk>f00e*l5C8%|00;m9AOHk_01yBIKmZ5; z0U!VbfB+Bx0?%*+{zF_q00;m9AOHk_01yBIKmZ5;0U!VbfB+Bx0zd!=00AHX1b_e# z00Peh1U?ZL5C8%|00;m9AOHk_01yBIKmZ5;0U!VbfB+Bx0zd!=00AHX1c1OZ0fA4# z1q6Tq5C8%|00;m9AOHk_01yBIKmZ5;0U!VbfB+Bx0zd!=00AKIOhDk1aRC7!00e*l z5C8%|00;m9AOHk_01yBIKmZ5;0U!VbfB+Bx0zd!=00AHX1b_e#00KY&2mpac9D)A` z7Z3mfKmZ5;0U!VbfB+Bx0zd!=00AHX1b_e#00KbZ8Gyj2-~s|b00;m9AOHk_01yBI zKmZ5;0U!VbfB+Bx0zd!=JOdE;lemBY5C8%|00;m9AOHk_01yBIKmZ5;0U!VbfB+Bx z0?z;hJ{1=b00KY&2mk>f00e*l5C8%|00;m9AOHk_01yBIKmZ5;0U!VbfB+Bx0zd!= z0D(Ua1pZ@OKmZ5;0U!VbfB+Bx0zd!=00AHX1b_e#00KY&2s~*B{3%>O00;m9AOHk_ z01yBIKmZ5;0U!VbfB+Bx0zd!=JZT7g8ZICJ1b_e#00KY&2mk>f00e*l5C8%|00;m9 zAOHlOGz2~!7Z3mfKmZ5;0U!VbfB+Bx0zd!=00AHX1b_e#00KY&2mk>f00e*l5O}5{ z@EN#(01yBIKmZ5;0U!VbfB+Bx0zd!=0D-3q0-uQs2mk>f00e*l5C8%|00;m9AOHk_ z01$Y(An<%#KmZ5;0U!VbfB+Bx0zd!=00AHX1c1QP1%W?}3kU!KAOHk_01yBIKmZ5; z0U!VbfB+Bx0zd!=00AHX1b_e#00K`g0-uEo2mk>f00e*l5C8%|00;m9AOHk_01$`~ zcmXaT00e*l5C8%|00;m9AOHk_01yBIKp;log}8tK5C8%|00;m9AOHk_01yBIKmZ5; zff#`o;Q|6c00;m9AOHk_01yBIKmZ5;0U!VbfB+Bx0zd!=00AHX1b_e#00K{A1YV2_ z2mk>f00e*l5C8%|00;m9AOHk_01yBIKmZ5;fky*@$8Z4wAOHk_01yBIKmZ5;0U!Vb zfB+Bx0zd!=00AKIXdv*}xPSl<00KY&2mk>f00e*l5C8%|00;m9AOHk_01yBIKmZ5; z0U!VbfB+Bx0#7Cae+Cy200KY&2mk>f00e*l5C8%|00;m9AOHk_01yBIKmZ5;0U!Vb zfB+Bx0zlx&MBpX3fB+Bx0zd!=00AHX1b_e#00KY&2mk>f00e*l5P13{@Hx1E01yBI zKmZ5;0U!VbfB+Bx0zd!=00AHX1b_e#c={vopWp%lKmZ5;0U!VbfB+Bx0zd!=00AHX z1b_e#00KbZ>5srmaRC7!00e*l5C8%|00;m9AOHk_01yBIKmZ5;0U+>{M&NUC0RbQY z1b_e#00KY&2mk>f00e*l5C8%|00;m9An=q%;PY?+0U!VbfB+Bx0zd!=00AHX1b_e# z00KY&2mk>f@RUa2Kg9(EfB+Bx0zd!=00AHX1b_e#00KY&2mk>f00e-*QyPKK#{~p{ z01yBIKmZ5;0U!VbfB+Bx0zd!=00AHX1c1O(8i6ms1q6Tq5C8%|00;m9AOHk_01yBI zKmZ5;0U!VbfWV`Mz{_v}0U!VbfB+Bx0zd!=00AHX1b_e#00KY&2mk>f@Tej1pWy-m zKmZ5;0U!VbfB+Bx0zd!=00AHX1b_e#00KbZQA6O*;sOFd00;m9AOHk_01yBIKmZ5; z0U!VbfB+Bx0zlwVL*PHh1q6Tq5C8%|00;m9AOHk_01yBIKmZ5;0U!VbfWV`Mz!%~I z0zd!=00AHX1b_e#00KY&2mk>f00e*l5O{f00e-*BZR;cxPSl<00KY&2mk>f00e*l5C8%|00;m9AOHj&Aq2h% z7Z3mfKmZ5;0U!VbfB+Bx0zd!=00AK2Bk)RGKmZ5;0U!VbfB+Bx0zd!=00AHX1b~2# zz!&2J0zd!=00AHX1b_e#00KY&2mk>f00evlUWE$?00AHX1b_e#00KY&2mk>f00e*l z5bzOrH7+0k1b_e#00KY&2mk>f00e*l5C8%|z(?RUxPSl<00KY&2mk>f00e-*GZ=wC zhYJV*0U!VbfB+Bx0zd!=JcAMV5?nw42mk>f00e*l5C8%|;2Dg-e}M}K00AHX1b_e# z00KY&2t0!k_)=Uz00;m9AOHk_01yBIK;RjSz?b0y0zd!=00AHX1ZEL|HAFh9^3!yHQ(@tuX^J*eB=4tYqwx~?&G%H zezoWJ`u}tZ*)yL%_loPew_Ts1 zedPSf$FC>fdi+1U_4t4J`tASbo3772{@=dns{F?7|Lm)Rui=RYkMEdle=;2Z(pK}V z?azMgIDX3J{P_*9uYK@!+vf+8JCZw&|I_v-`hNV}El+X$uQz{uKc0WH@_X)U51ij{ zd>_7L`_BA}$9LnKwrKmV8-MdpY#*=k|M7X^|Kq>ieERo!kN=bX|KqLOp9qf^ivN$- zZ2rLG4cqtE{_o?>+xPJ|9N+JcZU1uje>wZVN%=4GmD{uXRr$Ox+Un@v!8|8DJn~`&6=T~ljqObEaw>l}ZJ`_HYn^nb(S z=he%%-|iJWpD#YoD>k2geYMZDJ+xi>=li+sPxN*DH(Q>5UGLcZ@pYX)9mnUCKZ#!6 zUmeKb^7JcjuLRED(qDVL(s;Z2srHHo?YH^<6XC12p9T6S-xCk^p9Ph#YyUY8mmmNBvad7yJw5jsH;}gL_PM#CU_usy? z&zt?du6*YG>;38NA4T+Y?*G_o=)ar)Wpl>&aXV|%SK42_{ak(XcHTIDOYC3&V-H@s z{n^j^`%m4S4|wbGo$%)4C-BY&yk`6JKWur*5L7=l{ddw%_Fr?n>i@>;HNgBwdc42x zc;j~J|0eVQnSS0^ZvX!~hoN5`j<5O~wy*Y0$Lo%--YV(SufCqP&Pw_7k6%9>&hMk_ z@ALl@f1m&Bv*gdgldr+w=pX;z=>G5XGq=B==-=lziNDW(vH9cQ=gDAo|4a?}~ruF)9e80bC`_A28$Gmm>v;X#wUd26ra0dHdVde8$=K}ioz4Dp&&zqfp ztn=OYdg<5aF-(s?*txg;s(e1{d`G{|na{X?zT0?<{il3;MY^-W<7ds|4^Ce2E4R-P zeZOSq{rEF%AAGx3aT|Y6=d1kvg!rc8YuJDG>Ytxakn>{peUY7uXFlWp^?hjj6XE#Z zZF&0h?%Owie0}=2@u?W@tkZrzc-wZ*PLNOZ*Y4Kq{*{k+p3=_0zwGP#^Wy9Kj?Jgv z_fO@oaR2-Jk?l|P_xBaz>-$TaKmPvqpB?+(()oLT|5rx;tv%6yKR5Qx`&aoy|NYF* k@jg@h#DiBJ|GwUv{Qv*|b47E17m$w}KLhInpksgl05=QR7ytkO literal 0 HcmV?d00001 diff --git a/brodmann/TTdaemon.mat b/brodmann/TTdaemon.mat new file mode 100644 index 0000000000000000000000000000000000000000..c4862917ad3646270e115192f3b295461cbaa1c9 GIT binary patch literal 3091840 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

J*?pEnEd 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 r6;pLT+1^6D)Wb30q5 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%0D?|5TP-!x9@Zk^V%|22_ z(?rTJrr<|R1Ko`|8AkD2FLJONh!)sFhz$HMWn@MVNQk_$U=X&K4h)iToR@?wa+n9t zzGo+jv;xIv@lhdh~Ez4N&t|exL*!-7T{y_AKGiszcD5RsvgP>a;4Mn4LTuwqHR-M&u=$1@N^(v(2T$`#Po7oa3HK2vy z{K0^9+1UNTvl6FrC^M=F6l^S;{rq!7#we9U9=pRdAy{nFz`6)xbnYg|&Y;esdWN=# zToum^RHKeK8Ni$rr33887*{CYpGim z3)Pda>Gs02S$C@-R4=bL(IkQcb(%1z6Ogea&3T2@NSrvvPF8}BYeR+P$Avh;w-Zv6 z#%LN#@jIUmttrKE5>}AEMAR*)k7~~P!mad|vd2 z@p6qFE&bt~ke{ups#rKMpj}>?@vdZNy7&-`>Q&LvA=vI`K2yt}Lk)3ClToa>v8f&4 zlud!RIMqlL3#}BFD_1lw-o$X6_Mq^}dS;kUL1nv!388;b?Lu+ru7gkyv7TfVP_UlD zjc~B&>+3|9Dg@@jgY5UO(yr@k_8$+b7}I~82%Vl^ihA`{mEW9cQ#C|0!DM+pr&*|x z_3S_%xJ@E6!Hg0VlTwLWbc6@}ehCL^*^)B1b5uN#U>SoQsmB5}LY=B8TSIMXiB1j7 zGfPW!X1d@$aVjpkkJF?n_Yo*cgJX)Lib*A_XvH|ulrS_3zjN4G>QkO$c7KVQ6eQmf zb)o5YS*C%^Zpc^wd#iihbyDLLhpq6mk?sVoL6+FW1`Z0jOs2K#|B~zEe&-^vqhQBjPbjmE=C+MJht?P1EtUSl&6tf zfMcx&MGbNsX1UKhh&RLHk>m^=cR9V=QyFugS%S(!(eXQx#-wV=9kh2^$4N0cFSm=M zo!hB?-6lQBU#$u)Y(k1jo*bJp$IkEaTZKD$bL|4g;#>{6av%Uh0H$+Sk2??o}szP)J{)KYAlRrl%0E-Z}bqa zCYv?H&6D>}{D+)l#>)}p==`v&dUh}XUrbm^pJ^X&K?n5wM$pJbW~^m~kzMFy;=(1w zz1YXbICKN=8H@Fq&!BMleEZg?5YJ)XKk#zOOwG;MiYScD{#0T@pH}gNog*e^=V>^| zrw@5!nNp9`KorjlaG?byxbAXRCSv&t`_OLeH9G@k2{w|530Mbfs7V%55W5HWktx1k z`t0OOdq?mQsuig{FR_qSW-0~IP{DDgr6421Nf(h4|L!ygv(pTuSoHHPP9ZqLHjKoG z)y0gYn_KY~B|gAmngekBPV6;Fw&OiqqlG)CS!ShW_t;J*%S|H{__x-Bw$4Q?75j81 zhuSc}UqX!pOZI=c+j+CKPi+Z$drLoAf_WXjd68EXxiD+8A}O3i!0$Zypu3)s%rK9; z;M!A&Mgz#J9u)QHKvIwE-f{_&7_?IzK{7I-2yE8EY-iz|*58QbsS|5np%k}d88}Mo zX_DI#58eiyi~OO)L#wOBSZr3KAs(tj%yVE-SVshOvYuldW3Iu?2|&X$$7kygAFl-r zWMTl$G7w%xL;b-vMo)xrJkkt@@rr_>iau%5Lss)jG>m(|s?ZD!U$!Q>gLpX-;^Jiu z!!z2En23kIIEvvCG!}?+obJCb9SF|gGa&=?)AtWJ;98M|ZD69y$G{Jj(*pN@3sv&NAQGZcH_j3n1VToDGbe1RKfsikZ+UGaYh$> z9s|8n`qZP`l@PVp9L$U=Xh`B%$f40| z)9(2o95qeju(C0q|N5lB>93mEj#sWu5H&O*=Zx*-!!}i;iq5x6)zU^P8z_Vo%Vq`r zKRT}UWyY-|07fUOAKHi%2*Sl?Lp{Sfx}g??tZ;(xKn){5#zo#OaE^N9#KzebR_7P0 z-4fFf-`d#-zs69Nn1i^U4f&R{SaH+_v}jmwRylx;?Q-TuEm9{7!X_c;McrV_90Mxg zH`5r_mk92WVWVM@cfSIin_$3ziq}&45)jL&IfsB!6y-9gjNo$5%Bx!8JOi3vAhq*lK~2QVPE13aoC00%G)(s{g> zg#se2O^n(l8^>{W7DrHu1XbD!8k?JPst zaDe+R-EnMiZbf;~Ae86T@iA5-u^?t(bwEjE18*L$c+)3sKq-`N840;jFNS9XEqyKy)QNX!sMrB_!SVE8da$z_XBpi6ZIOn8B2M-wxcUf zDg4G^GbT4nLRF(CPVvl~Mu?)|imT?`Q%P}>po*=AWael+U0uPSESy_8eq zr42+;9eJJapgkp_+g4L;K;tI2Qf0~z7HKl~0Z`_lHGRZHF;S1^ua$3=nEE%+hC!U< zl$ttxHyIdljN^#9n|#~Vu!s|*jDXF!qnpDS1cT{QD&Z>pq(AQj!9dvgF+`oAt3U2u zyc5|-jDbpdI7~orYXm$6V6$W*nmG1(yPW0bg3V?EvX%Pm^_*6D0|8B~VB&GIkO zabjAPLt}EN>vD9vhX+l0s8B9G?Xq%8jIpcT`}t?=GcWZfZ1WIB0JrsK%$ig+TObDq zhHzPTRlRoC_cXX^yypmFgAyfT-SZtDBm9)mZ@Wz7VULOHry8Ot5IHnG;M*bS(*8>$ zRg))f5!DM%r(uw40rp-QqHIL!cl|Cr8;_7Tr^a|>!&B_x){N1q-77;CvP0XM7at8$ z1vN5Rs^8#?WsEUt{!u1gANzxUW5_tO#k^g2p>Y1(LH?uor!6v>0_y{M9z(QA4YyGO%M@fjr7X5$^6M zh7ok;?qLKKWz}&2BFj*9STth@vnYp;DgX6--t3yrP8IA12EBM7RoDlL(x5Z@Wnl0Q zDErnQyLO|tX%Ojix1&TFLwK-i;$knx2(w)5@rWY{CD(BSm`(=VoW&`?m})9dfihxX zn>TCk1>ZCILp!G7kFEl@<5?9$0!?DO-Q1W_OMC2!ulIK@I_3beVMySp5ch;?5eP0q z)?RcE-qS@W=9_N0_I%auF@YV{4gzJD14i8S8S`20)DeJp(=r@PZ^%AW(9VY*Wno7C zh}oSwwz>hsZ{u-SS0?7-p-8>Jd%xf1!!4|;k#6(sI<%hmbo8y3rT8xP$K7;8*Hdhf z7d76l^IP8W0#3dY(ckjkzF=K1XT>{Oo434gtJkeBUeCUOS6-I*ADN&I@z-~Iht!v> zuix^1la%!S_wFt4^~LaA!uA0#bj5p8Kj5Y5I99c0dNc0n6uoF2FJ0y9!~F+m%rkif zI}-AH+iCcreW!W&z~9a|rJP7AnfiCU-$8<2kzOlSUkdLD?JL_?uX6wPSYFTGX23h% z@$32{-pk&bCj1_|w7mNL*GNh4e(&G$4rh^RNIKl>l796!!(aB2cY9s?&QXanRJ^R2|os;v6+57jq!%I~A@$}vF zHJ$HgFUEO0!M5oybGO&IZ~0`?4!u?0`u_YmZ>au`WFK|yI~pCv_a+aoNCx=% zZP0D~ws*MY>)Q2Za=jRuWgP+Hwdy-Sdk1`e+xv0)4*28vX7J4s(qYMahpNO)e^>lY%*^Pp<{dBx z^iFd(%Mfz2)CL;bhL@lJN8k3Ym$&m(>)hab*vs49xii^U>-f5Mz3v?seErpnL5=J)izcfJ5#rmuOEN<}cAz1&@3y>BdD0}pivQC_C2G&yz2j2jep zA6^*|E-!36&w@L<=hry-P{K49PPm<&fwOlmq&d1GreZ(6p&vXRKqkk-;KJ~39Sd{eoS3xs*Wu?v3KLCHWpx$ zYdTB2ZJZ3lG#gK5{Ls*TOOs{l084}&H)!U69k;_&ea9>q)~(9Aohd{1W}+-#{+jHm zcLQ8p48VJcz$VRn&v$%k35Ly#SnM>jW7p8gF^h)nvWZD9o41?$1{0eH zD-&1DY_)NJ?p2_r3{E-Mm5V96x1Z{QEBLl6o4sR4f?+o%sXb>)9f!=m5Z*oJZ;IG zIh3ojUG3I;9omGvXeMfla%vz~w|5WgZq_W@dnU`&H~o$7)PB9o!2xfCBQW05_T1xg zp}b>F_Ve*%GENQLNc5BWYM-|^+MBs-tR zD9*c**X~$<=&mYN%31FXHC&q_g)OzO&7<02Ja=j&F~*w=8RS{B-psxxyQ?%`FOj~w%;pMg}NR0MVfRi z@b>Wlq5gIO@*WWTk`-j~Z z{ED<%l}hRcq)Q9;6rN@1to^&K?Op5*)!X3|S2t_!mi=B$ij;IYZ~59TJMfgYy-MYN z5LsAYz39`X-wH}{Y%Ko53tyXndh$dqSO11OY!0TY*-vI3z;r&wE*;p9iOuDSE0&iS z{we?WD^VFpjEa5U$=kU4X`|>p>a2^H7;hcs-bz7By&kEK<@!WozFd7buGZMa_|1!V z=kDriTwl6yTziVejMrkm=*QINO4Xo@O6USY#cwu(v)^1~&{eG=wo}r7cP~{8hbYwr z=MR6@g0#~~5KEy|T({WvYcpM%>4KVWS~cob$#S_ei52Mo#$by?mSBHuEWO$|9OyhN zmtHyTc!I#KQrFiY^4lc@%eaJaY2cbj*Oa8dCWq`bZt=UvBFENMxaKtIK4u ztux+jZnfQH)+vl?L=Eh;_bLoKvDA!j-hQ5U97wy#iI{hy~89c+KvNM3Byc?{9mtXn$afBxrBv5TXE1Y~694PAJ3We2Jpv1TKV*7V5m%Jg16y@ft^J8gOA zv21pu9S&@4Wvg!ZnLOzP%ezQ-c>IGx@jw1w|KX2*>W$bb5K#T}SEpP3$CL2KnsegM zq86$KJHTtCEfy8U+9vh_fLhZ3N&+_g#}xwYx;(99>(>-&VFA|{V5y>hn1R8sH#78Z z;FXsSe_dAVEh@9D<(P10m14Vg)7v_FNeM@NejUpS8R#t)MGHzoyWViQc5#(qg=1Az z6y284tZ4l9+iz>7N=mF-RB!mwDqlS^wLZWN7yHGJXOxydb_sxO@#6|VyJXSV85T#8 zp<91={K^t+CE(UB9mMj(SkUKw9u@ON0z0B#Vm5d33eKlal;9~?n5d3GxwiVSw4V6y_z6ST&zN$6J;mO^fy&@N7F7AJf_3FW?Wdo|EF;bjcqS!2NM9Jfd#JmC#iz2&qjaZ|I zOj}w&2q0&{ZkO8HmOWpm*i{_K(F^btJD>n{$2ZIB#K*Dzkk$n)s}kWZz_O(c+!Xtz zitjZfP1Y)E#m}Lv6x52J&p#fy{-|3AbgLDNv49cl606$)#|4bjRgJb*@zCWTRxo}% ze#YBH*Vg%B#W@;FAa^ZcjMau%n5dDOYpL#ZwIUvf-~z221X+q|;T80(lZJgk!j>Rw z;kk?93bTccW=4m;5)n%!g}qLg8`+XRB=@Tm8_1UIals-k#oD&nf<=}AXXYMMr+m)YEy4(4aL7VNmxjy%T?P_&f$SGmuH7>-yBL;qrPk-?i3%sf=D`K zb(Rw#+Y<54XNB7Brbqfn%Iq#+_%IFENUu;>t?(LA3813K&g!lre!^4aNR9tsPgj1Z zwvW7yW}Fq-BxMw9y(FvN`jmHvd##QukDirB#VGMyccG-WS&}tLd>U$^yF#r^lHM(z zyLxqR-Z}qEC0P+sLvzNId-zQinLjFKIjEOHs&yKRS68p&>vLtD@j{S6cKzQX$m-om ztfUCC#|uR^f&Vy3)(Ewt$%-1mvAzJDYBD|$*6T0Is~(#np@b+SG4Kw$twUhaiL!1t zm+68k2J>0nslJqsf4J>JtKCTn>&>FUmb&b{bcu01A8C#=Y|38%er~L$3LsZ&v!^z8 zs7(K{i8{9f~X?*hm#W)E_;2YYF}?t=6D}Dm#$Z4k%U8Tz_>aXn1HX7N+OGP;dc5 zLG4#%$edJJqg+7S|43D)l2vw9YQl;FLu;7MpJawBH35b}QBXI184rz_!XQ+hBQuD{ zan`t#BFx3hmn@v+;isDL&*gwJWgi5%Uz`SDg}BTDh-r+r^2qZM*(9y#f_}=Lb|ms5 zg`Q&DZDr3ap3(kbAD8i1Rsl_i>FLf8XCFUh#iqL;rfkU9#d zw?;WORI_F@c_dt10mHAG*DzMi78lQATQE@9DU1h5CT?y!hCP%0#yuRZwO`yz5 z36=VE35_nx8KMNrvIcMD8YR{uVg?61QF>w5z^_WT(UDFomiH=xYXd6zQcwt#1H)TH zDVfZ}4m6AyI^ zcFZxAV-q2zPK!`cl%ls2lnc&JnkwFMunTNovTh8Jt;1k1o^PiDWZT4-Ns63sCjqkO z?5Ez-V{}IsA**+#B*-YS7Ru`~n1fJMBeYXdS?eH~zi%Xz9*3|bzTm(Hw+7t-&SVYW zcQVMY3ARh6brNipX&9D&IW5CZsIp@krfif?8mrY|jY3OS^G#a2C`+3a^wgOGn_(TB zAqT4ZtI(MSoy5=>SxX@>_ps}YAs1>jD!@;pEYrI|_R2EURi-A(K866>Bi0r{v^HGU z$ufu_j?|=`Q(5-ulmeqFw7?jS!CaP273fr!3B+qW=CUl%R+a@SSyseagHnn}feFRf z{>REPoowPhx(g;*hN4sHeGE+Uu?IW3bxwlSz!(l^mltZnu~vdDWm#(mWph@Rxv)t> zEf5yY^6|slF-+K0WCf_o%HtL%0V*WgSt+*TyLbi1D9j+BTv2s7Sjmz-cB}?0Lxu2P z-|9CAkEPjt%k)|&%`&LkfBg6M?|)NeZQEDq^snA<3y5Kaz7<5c`Auw?tIwzX1$>-a zqkNTA3%C6>UNUT6+sI%Ec;(&61U3*=^h~K6N`sZ;Zp=E8?ly&9tr+!sX@-@SQlfbO zi+ccCI6G3F@@|;P2obzS|{ZBJ@V{Nkyuit znmoU}d#81VeF{X}$poxeWCN2g&tVB#8|qbt#-b}Ee7D`p39}xN?LS&u9=`9G@Z&rI z?=$?z_urX!Xd)ErIV|Bj7R#R{z!PytpF8@?FaKJC%HA1nJ@`LA{sX0&CbNS6^{;>Z z&p!iXo$&ujlf#TU%B^-lD66*$XnVYERAtr;W@gZHhk*UhKLdJ>eE4O^knLYvF>++< zkV$iB`B)Wj$K7s|_V|!3v8oUi$6Jf_t9yWb2MLe)KOivd_nM@~>}#56u(tbs%K02_ zHa&M>I=r*;jXmW68;5KzS zMU$n`Bx_iLYptdZHleU*4|cXYQ5H6s-XS1_xugN36yM7%hDy74E~pH{-O1rIp2jrsrR4|+d;@(cquIDL1>y;vGF2mcQYPKfREIbG%L}kfP#v8w?#kaR z=|7Or6p=*|>t%H+42qClEv-m0Q~cYDq&*40Ghc`#WdH~5M3VQA60%65dChVnMIuR3 zsLOI9!DJ8RQ=2Ds$0zTOEQQ=yTpkU>FXYoC#YNI2WV=VCa_C}*r|K5LnHprlKQG5oU zW>)0lAWf}=2obj0&8?!ACWFbQjr8_!u`%BW@eczf2DJMl_}TsYG14(s!eTJ_y`4u* zKJ>Oi|0pok3fD2~%95sNt$*DA%`^?($-%8qQh@u_!yEcN-VVG!%RU8<&GGw6$IlJ# zJFx_H2P_cv+<_vNf+i(f?7v3F33>*k8~m8l%S8nYyA+%@)V$x!<-9^OkNYrdS85-7 z(0=J}Y^pCUwQZSQf^L?q+|`J9!hinHtloE4i<$Gi*kp+tZgF`PfGyZV%$_SG@SY6+ zvG(m6xOm!Uj&{anQD19)4qkcb@Vbz#b-%_Z{ZQ7dt$ZvC-IZ`+so!!9;5-3K{gw&M zn!fTiuoH{@{1L2J*cFT3;$-!Ob6V|}Ub2x_`+YmC_A_g&_A|ceKI}2BjOHh>IJpmc z8;>^CC$LVz#as*f@Q9du>F`*O_in(`lD25Mxj@yUD&{+Uvx8=2FZ*JaoRU`h zbtzn!hM-rs)DQKugnkWRe)8(nLcj7djIsjG95NQbAD+Cz@4vgyuV0<`*lR}l;|mwn z39$r^D*z(_F`{k>V3Pt*=B>U9(HZ-rSNuR& zPPy??zyIL7Fvg{RTEF-IFZJtQEU|Z~-(>aQxreQ^((lJ^W#2E|TE5e}kYYA`LrUc_ zKCkri@g1fx)+;UAWq&DQzPVW5hm!pT8QPc(xU0vnnq@FpT$302%^tt{_@8@OMzefS zO9Jm`p`X^?eJl@Y@Tn}8bH0O1)DH{bKpvbWPtBjAeHAu8X@~=qInIm-LmT zg6&gT(}jLLc*stkJ3-iqI4J^CGdb5RoGb}%Ul@|YD2^w4<$_hST zJ_qc}g24vx1j4KU=1Tmv7*3D4iZLzgV@_mG2=|HLKmK_6Ua-)w=AjcVpes%iK&PH; z7Ij}4lsa8`Cy(@59C!vVOkT~kl?VS;3;yd{zOgx?cVx^L`jtAIUFi27ug7@5cm@lz zcX-^!?%C318}8YIFw0gU)(kU6=Zv1>roxT>x(vn%!S4*CFN?}r38%d{$B0=cL~|xZ zCh3J$!?h|co>ft;O#jFfdd5oFpAbxN$gv|KKYvx9m@uiP(eeXUkn={ZLtl;*`Y1nv zh0fQceh0t8izLu(jVZANN{^LimEsYp7L{;er$~<#;{r#trtj=?kIY#CWRzvWZ9-%ktqZY zGue|_U5a%LUZ9gs2^fcU6cHNkvZSJ~Mu$6+s`i4+$=a(+sM4!RII!)m$q;1n=4Zw9 z;>Ep4Xr&aM19C-XH!o9UeD>-`y_~Rh!Uw5lDG+3QUEl0UDobgEm(|tY5ID;+`syE< ziZ(Pj^wkRAkZ?kfm8YS;L1%2dmE_J~%kVVP9X))dN3}5g%%8KGqvxNqnm>ItM=4Y6 zhf^kGO>Hngq&yQ_Q*3xj3weL+-(C0^P%ach@C->NvS{~bI?d$KvjuwY;nA>KgN0y&UKu+8js5)d z14Y>2ph`0rZ2n|cFW5|k^)J_OG`~ipK5hkV!rMC;W(B3!@M;WzmrFA~msKCkvZl6_ zK76(!ZvG5bkItXLnxm3tA>-ckDs1{-)-%2@qqmfdT7i@cZzQbnwMm_aAK z|9~54X5ngZywcHLjB))zs%!vFfK6rDBS&3OImU32}om62Vs(RTr#cv|Qq|Tth+BajA za9LJo9JK!%@@%ErVxYpYPjAw=B`SR(g+{k(a!fgT+-V`ue(c|wf%9Ra#%9>W?2Kqz zX*7#~Jw~!5Th(aCGI8pyc>H)S%c#0aN+--h$5jn>nMV7_`!W6_5kUCNcnPuhj!g*+ zz|SwyXcsBB1Jy*Qu|7`0?mZh)V_)Ju8KXuU(a>m32T-GZNsTs$JiI(&qTCFX#4_df z=@Zl#(5lhE3||*CG#W{zL96uT2)XI?R}?4QLZUG z*&#bRADPaAA-%F8a;D5ya>bGyqfSsE1w*O8*dr5(Rxy~t&dsdG4lVTO>jUHqe{P1{ zJ%X1_nhi}%_3)49nFFjVZAzBiK%c|+r`1;>UICVcB5jZ5wmWTtY-u99c0+0JKYP=v zbr_mNqi&-b<6Un8s>vVUKk|Da`B&Tv!PRin7A0%KXE&B;jaVv5Y>p}bHBnrRbroEf z!GrTv`_Toa!zMKuoXFViIi^6#3|i6gMuxHOGd#!Onhd8BsysW?-a&?nGj0Z2tBSVh z`$xEOwgTlS%etD)RGfkSF@hr4BtH7+nn{v_YcsQb_2FRfIdS$#Z@+&vh&G9{R~av& zO-d`YQv~H0H)32X@R>H_1<r?0~ z^q2BbYe<@1o`Nfbs94#@cKpwWyrZ@=6b>K{;KP0NtP*Lx_wCk5LBS zb1_k8)HxMLcTk))3Lf2Y$41NG+DEg1=?JT&C#mhJe-D)e3=}}6eJ{C#w5`yt1$vI zmuO4$*;JxE!-u!PmZ})@m6KC7wikT%rPN#(1)HEnqUFXyqCNZaa2O@pvoG%hZGvlP zZG%d=fcyz9674_^3B@;}@J8@Hy(WofV5i$M=)&t0G!nCSD;yo9oGQYrG?lam|B-kf zJ$vXbkJnlD2Bbu*vJ07uM4O_m#PZnMp+za&4)-S@wB2Z#LCOl(Sg4oDK4AtL|uhAnX9z6#8NG?*NVbn8@_={W;c0jRV;kGIH8<8G2^FJFJaE!UPPVBNZA z5*0~n#2l=^;VOi#H;CfG$AQxr4UeNPnxhM2!oJ!Dg;l}Q!`^aZ7fGL@2{+ScW4#+c zjoe~o&g!$Iwz7SN2Xxe6G!~lW8o_Dv-4Zu;)0%oAz3Hk!uSuF=RbFfCisS$p);Mwe zQQ)zrG3*iZvJBE^Rfipz;L$S(#gst76)F4>R*_F5Yy54Cr+*|vyB zkad}>k#7AWmO4IK^a!;}XDzXH7H$-qSsz@3RmtXFcHp(pMsbz)+!$5MTdj(r&;0(> zs6LFNl4w&<3dZAfVc26+-IiBFGNUQE8aVsq$Diq^0yUC`r7>+_?TG1_GK%AVg;5+U zI1zg*5j132>>!~OLn~j^cy_azdY&mS(PSdI4(_ipVs})>SPey5!*xiTICuSQj*gKy z+AdI5APG9g%$^-5((ME|C^zLd)FjsX&p2_~we{m?&l=?>-SzR~VTK+9bvf`ZIf1Q6Yrr=VaiisZPyuSZs@w>AY%bzx5IzFu$}JyOw>&hKGZ4xJ zJ|i@ilv}H@LeGsT9JHW>+Z61C8zWiLQ&B$kNvFI#z$Hsk_4oZToyf7NMw^1C#2geY ze6vj{As>_**PG15;6L?Iqh(WjSj+#*S4O|5A?e$3OjM!IU6NY?X7N zbeGGv_YA&T(Nr zW3&DCgj$=n+Z|M?B?lt(?22%!@Z3qg23gda_j*lHwpZ67jUKJ^+BzYtvPQ{0$|-ym zM^SQp3gpy#P|W@Wc=5&Xlp@SLrDQ<@>2k?3m1S0g zb4iao#g_HbiKI@h;U^(_6)|vi=G&XHl-4BJHu= zot87z!>7=ul8tJpM$(GHX};_A&2#IJCdS6o#Ue|WE4Ex{)mk1+%fYwXNHz+;>YRGP z71L?7m0@^DaTKfnUCH)yl}?mogJc_ks>mL*HGQy@t=X^ILaCjVYRO=x+WMO_7e(3} zbIVnz+ZtK}(Lt{Bd4rNv;p4&3ZP}Kn)Ern$VN0S{jxyxtbgA0H^cWvmH3!dJqS3Ux z=p(=yvV+2NFe|j<41{c(bO22maYOO)+Gw#7hilMKXdkDj>$XnKeWGpAZ753-H-TYk z674IqY{54MN=_YGF^4#s8o=B|;o)E@y4oIASyIzv%NYn0YC#!NBqf2i10=1Y>T0t0 zp!8V=`6Sl}Qh9`uducua;dCmZZ4lxp*DPrmmJ<-KksK`-*z4*MhY@8{pfwl^${0^= zU|FCMN?0SbMlsfikeE;z&Dak)vgQ(p&Y6NV`?~`O)A(By*#vOkHO0#E{Z6x~_PAH5 zI#I#MU|{`M=}~vUUj5kOuAl~QIt$F2jn*}wThpM}s*KYa!kb`sXE?lk{XTBnSS2Re z+$pvaYV*5$Yqo`qW8PlI?ZF^Zj>XP}4zXTC-2lU7#~9PB(f5CRNNv# zT1~>T&(f*5xmLqDAL?o%?o272(rTTItIBJ8TaqBks0OMiD`CG;t|f)n2{}tifwH3g zpf_USq~&333#Y?dNW2SF+~QPe^q8;0ti|9Wk&t|h7eba`SkhzCLNz7Hu|B1!u^T6# zRynSQiY=trg(=Z7Nu}wup-L-StmwT6td$gYmIbiRtF%$S-Fe8(mCi|(HU`|XkTR7v z*R3}TtYz<2Nu7~tr{&w=#@19;ICNT4Z@nTj+1aBw9XLOOv%!W)#-l=_wzd>a=#UFQ z2^d$8l3cSFjSR81+Y7bS92@cfaG}<{USu{dzn@SPuq+9+%x1uj7D5fCy->rUn#i}8 z9*k>6olq#PBGj;W5QRb0Ifxq@gjxa>bu$o#Zb|Sk1(W!ix78KVT&Q8xoya%Lo2F12 z?#iZ=a)50L!oV$|VbH9ZB%6W@okp2gbsDuFf^GswO^O~2(<0Pzeu1E^S}zH@V?vFR z4&ZK~_L`Q+4WU+a+8jgGlU-3%#9mt7g29txfZ(6n{W6frffeGzjC8#t=uqD9~QM{xXZPSs)Bn-IB)AE>7fA;iz3IiOu2$%A|Yz%?JS@ z+9(Pt>QsQ(h`$%eF&9lNKPsX^9WV0isD+%W{6e0oj@gG?7?3sz2#gS#?}ka~EMEFi zUVyrDbl^vUM}~}fN269Tg(d|eV<@yT{75Nb+1eYD5eyc7N8PljqUgBBYKne^ez@TPY%mQLP)|;)`?YUhF;;K^|47p<==S2|m4H*}* z4whBbT}Sk7K&8K^%1RAsOZ3pAt0s!IA0Dy1>_qS*iLwHt^&!d-IBiv2O~^5{S2;(M z@T$04ta*HNLVT@d+3u9w9p%Q->oS*k2*(v2wY|-f?QkkdPsX__QYOaaUVPQ*GV`aT zUa~c6j)cC{;%$?wOM(XMv{_MYV^WcAlr5d2%Y8)J>34$?Nb9puXerL1=70) zRU^HVzg-iVf@pt9+rbr5=qzmxQM4czTwpw>B!5{2J!V?H!^_adbp{- z&!F^_fyuN*!Pkrzk-Zn&9;GBhQQ0zWz7gH`h%tW0#G5y zqmV(*FFpUqJ&k5tECD3d-LwA@0qNuk;DN|jZY0M0?Fpw4o{ zR?S~(JWZC?vY}=M{WU@jBIz|Es$127A-dWE0)@8f1sW9EfG%5SQmj>I5LPo7-x)9= z1|3}m+Tl=i7-x}-a08xYSW3Y1!=lf&!$MRo6W*?JAe^-DHKwe(!vvMdFtmi1!J`A; zo>EsVHVSQ#BpsG=RatjRp0s^lBiM9?LNE$oIW@cPgzGruA)tRN~(&ZG||hh@=ZD zQ1s6VkSi<8ymo-)s|ID?9folQ9Np<#={Dv3ge)yI#F9OI+gUCq*^P?u9w(5$f=rF zc8pw5loeTb4b`*}Wpu2C$5x6-y_fl;MykD9NVP1KCdc%koyF2{URo~7u`H8p6=uk>CPmU}nsNhgeDz33 zIE>L*sGUlwfw7cl(mR^L0tH`4JEV9h4PuF&q%Cb7q{qvcz+*yz4&5*nb}KxdoErq$LZ>a@UcRx4 z{7I1wBCF`M5H%ce-rMx!ab`X0G`JYSqUe4eS4KjDNxL=or-CLf`+}^OnsFY zorJar22q31OBlYS zbdq*Uprn(7rl-&k0WxUTg3?I^R3t5n7;mS0knwG08l_y4WG2&k%w$@&W55mPN0nq= znS#AE(b(9%Y!*B!ubP;ZXf&}^TUJhsEp(csTUB7Rf==V2#fVy=j?`%r=;f%>AUdF& zkOe6*u{p8%H`XA|GA>ZKoRk7|jYG*HRW)rsmR)lk^`&ckF)n^l#4=&?9Fu+sA8M`2 z4dXp2QSz84rLY;bvZX_ir%OR6Mh$M1f=sEL?Cslczde!iZ=N1jbetZw@vR%}MPjP% zx8F7?H4|0~?oBH0`aE}+Fn9uO$M0Q-vqK!hLK|H#ziz+Fa{Cnt{J!<}l~pq*;3f6v z6Yl+5Jw*n%+V1_jN_fYVAF#&t-Mf4ESs8fs<#w-6dGP|(vXpnfzWe=$lEo8VAj|25 zx05XBjJ`V%iG};$`Jzgz6AD1V_*b9x{{8oL(hti__EyK4zaJJ!_;&Ry36(gX@_k$L zlt(Eq`1v}ui7CIZobTU#|L!}iJ4`95YwVTn+XplLM{kU?<~zFiv82540xabhrhF&= z>PPnFwjqlpe8+-o0`Z@i!Qkhr>9+my%fFWBHZ%QhliQU4e}4Q2D#Wq^{MWz!^*{go z2NWK`%}QdsQ9YcB(QLD#&~c+}y8wdsKI3Mp$x3u*E~He6ZF`Ioe)?x%T#!g;Yam+T zm#qLXuIm1=P~c_)!gFgni8aX-{S4 zy(Z~#TR)BttY69cTsh?v5h~#ACI)s#QB1SBAtMKURtEoS3;*)}+5v#^eqawc_ISfz zODR6#OS1fY7qZ~A_@plcZ3G?F!%k<=H?ZYURBhqlEuf*&Too%QYG9wKMr{oQz3#ycHY*U)Bl&d801;+F z0{~i6t@m?F-H_`8x36$A$WS0^-=NekpR-M;QHHQ!)Y%ff1Mo{LA05Rbfi9uX5j=j@ zHWUlR0u<4=MrrZb#isLuB0OWAF7h%=ifw_&Js6tae3-dm|+`Ufd(%h0K> z(K7mXD_oq|I*-uA3&LM)odRgkSlB-E^Z)j~S%knjikPp%eHQm#POCk^J#=Ly+{0eT9MLfkaFK3}D3&F1QmC=Duvegm z>m2r~sD-_>u(1WP1QK3>jX|#juDU=GGKR8x4-IBvD-?uyQaA<1mkk?M!&|@<-}(qa z%^E32^@tHF#H||aT?aC3^++IsR$*!2I}c@bXhgUiX2@#NPEJ8%HNN6xs0LrzWL0~G z)q*AR)xvc(BEPBPt&7El9?9xqb|oWL6^}@kMYw?DwHrCY^>#-BGSmo{g}!jCZ1G@C zKrDzsv6_O2ROt$xia6_SHu}BCXC866W64n0A;Q%nfdMD`aNDp7(PB{wQ*o`~&Ej5t zn94p8a5}ifaK#wGIuR1faj!Ey#6~^V1?%q~84F?r$Aepla?}tOrf+MQXq|#!P0e#b zKum!u3y9(L*c-xHhrJZsddz@Sb++uB65GldYM=`5TRfeBBQ}J#He~sKsC&0HIc{TV zv>$?UTc${np3z6ztOM>B#yq(4N2SYweI=6J0da@NSvyxha;`ETVhWkkVqV= z3WxwuU3KXxtzj)Aj;j<8$A};GTaRS+HIKCTw2UAHFRN9yyj7NWIE=&%wQqSV*2z-N z%3$a67S?)f2cg^F)AAP9=9*vK*t@)iOC^TO`SRAo(XlOWO;)nFn=mhBVIfRA3^V2s zuGE%$E-^ z7K6-yA@W)GGLzhbG3i{|z`+04FhD2djo47_kmU zVyU{L83e-0mb|bY!?Afrz`c|v~T4zyKxMGPdl>og}gW~$J-|N8bQF;LkF}$`d zqE%{!V=LmpmD&lN7Q0fA&V7;}lgaZ=WT(Y{$5+=YxRZ4>4ymSx(By)I;5;<%W<**E zL+o)Z?q{c#~J1luGnR*C(yLnx3?5$YJ()$Or06ZN@aD%x_+cA zfg*y&32|0IsI3IPFv}M7^nA5xZI`rxx&*z7pMuHtOJd@>LGb9Dnh;pVo5 zKZocu`ExvC2V$eWsMb`RHiFyPPDj1y+@(|=1$D7%4J+9+NsV#=c*TtVc5Jjtj$)l| z?S0#HXmw(uE2L^-@Pzd((1>x}$TW<3VKHq+im^0TZ48uDad&2vL>7bz9hMgIyEZzA zTDV5C4Q1f@Gs&i?B)?p;QOzB{UrDxg%vGC)QmSgHD)aCZS{q^85x@Om5%FXiA-zUv zRD3~yv1zJ2seMM-a`*$KQBJ{_8~S>8b_~Qwn?{-q&OD+Dtt93q(#(E_e-dGu?V%|! zSB!Esi|8d)ky=}AvfhyxQmbgcPHQdkPRfO!yYEPpG&9-fkDp=0b79rN6TNj6bC(!V zZICOs%fy-jYtVuQPSn{MlC4v5Cfc4JLn|SR1@z>b#)!JjfwC{Luh}h}6)UWgHKDn3 zd-}ZKJZ$Bf$uwa_uf_I6>x;-Nx>0%^@rP%Z!Nz!lf>oJv8&Hro2&x7K|9d6Wo|ZY7J9E}HReGIv1Gj&LOv;00%xr;G>83Kt z?#nO`K1sps&oDzE=1`WR+a{;FSX0>`0Ba1FN3T~O980E=ul~kYr`6Oz5x4g*FN6H) z!d+07CrX{Y?LI!plHa9uJT2wd*Oz|2a}y!r@b#fcGqxR;(5;g?G~H2FY?B)UeIOOv z&_7GDJ$;H__BcFsn0PfKSg8&=P(gN2x)4-Si%kG6=4m zhHAviypDmgscGz|9B(AK1~Im3nIh7L6eXsUV)oPdGhx?>w9)`2MJwyRw*pfm7Fnkz zi-^!-c#D~}BSI}zbyb>#-8BkZ)Qc9}Mte3$RZP{-m*Fe*c4{5&kPCx_ zM1OKDMAm8fR;D!jLG;&sN=*-xF}w5RTh?5bc8^y3>B(SKbr@cW_E3+Nosw^;S(0y0 za|qh3=rl-2JJueZdR*aHZmat3sq44;cR7A}#H1@HZ)#W&*y1Z`HIO6m7~btB?4;FN z80-sb4PvJ;E+{j!D;+Kh+KRX>y;dz_Rc)r%I#mWTTGrpD^riJdbJJ^nQ?B*eO4p5= zE0*Ngc;;`vt~0fz-n^tO3at9mFBr}VT5Iik4XR6tq3iG=W5;)3Y4vQX*OaD}a0WFB zj!-M=kVk<$#}Q&lWQ8-qt7NNYC^u}YE>lfL1V+_npLVkrab1C8;N4~_aY1a6E2%Au z*#xE{h-J3l{7@Fm#y{tVnT+MmpEJXqxq?>ePEAFrO@rlR=@?JfXuN;d*Y->G`gnnk}ZC_ zu-FpjYXPvh7AW!h2sp6e!Su2$8VqH1D%%jGDx z%I}Iyqh<}Fiynnr3A8G5+SNbEx_gY0b{id*#kWONC&Rv_cAG0S$+~3}ud%A@*i80I zbXC5onW}uFd}`9vsE0!o^pi=eh|*?C$g$(j7+j8_2fH+i;XcE`TN@J+t`Ta-?;jkC zxSPai)$CgeGB^uG6f1D;6p%GfEVN|}2d$)R#wECon3|-MYUf8l+@X>XOfK%8*Q#_8 zN!(cqN-tZQi^_Y7s>zO&JKt*s>T@UfOOp&*4fZtyuEe;8Tj7vkQ~MDQvDTUwX?L89 zwc}i@oq}0%7kpfSWBt3~^3AXEQH7b*;qwlJ5Bw%b1&cCUgQoS0ArkvW%B0R5OM>lb zK(W5|9Fa7Kn*s2Zs;vYQDF-(bEeEMJ!V+yaKU8z} z&JcMf=0;pv&ItXtM`$^kt3!90*F_aECy_PPc}GN*r89FFsh}$sG2SM2A2Wa46vjE1 zNgBbH^69nQZx%I`c_?};skJgsE!#vz)7l#6B31R^n zXWALLj-g~!n)4N#rznc8cXc{q)OaT4(gNNICD$-ZzpbPqN9osUEnML)3HRdCMBgtI zp2;GQvd3Tf zGTr5+W~g~LvnPD>3eU;G{?OuNcDJ|BfM;t%VU2=VhYbZoI*7nNki=-wFcTMYo7Eih zGE6_ws46gjl@!%cS4cHySgYJ-7%FrNYiKM_Dn&NKLMmmuz|@xfA{*Y+Jlo1mS7__B zmubkPhZ>pojCF36+cR`&9fXq(h~kU)gmN^9D_m8~xs^d(y_h@{5PirN)mL)01Q97L zI&2ZDvJsI^V=B6Vfh)I+3?{DaXzXcp5+FniU>e1O@Op45H&sQFGf2R{;li*<*j<+R zAuKOK?WhZH21_Il?n}sdS;DvdRR)#`c2HV>VqKO_FxrA@4JuMS$`~_?;L_Q6o2vsZ zGOi3c(pSHt2Tmfci-bEk97Nf4g)WJ^MwVTN7;M!VR?KE1&IK5S0?WeNo=T7~b=J4H z#|2DdTH~Lr*DZ05Ui%Gg8)?TPYdNk%^;jH~O9N}Wqm6c+ zEkdqIle{Ha=vpbx;lrx%`WRDDtnE5xuGWrK;SD;@pS*)Qh*FPgO?xLtwH7+=$)NDo zjgxd7uerI7t1(F}wgRt4oVg-*TCFYh-KLB?N{}`(PV&xhUy%A8Wt_>rPMC!n>|j(( z+aygXwd(Hal?y0?Lzxwri)rtkOyPvO*5Qg=88)q}9YtRjXXc`laaFM;ZP#eFvt-<$ z*?#Io8@z=AlO%K*M};+)aZ$4(+@QV|GL9;Z%WZU)6x&SColtT3sr)*Mk-1ia^Ys|D z)@$TSMksiHR(&Ud*G8PsYtVODnMub{uL-v?ha7}d)^r^03+|!~_EYdfYbghvVHI+0 z0COBO^;=4g^{xudo{>9^&6ONW-==-V8X06N5WhFF!raVsh}&f>DLJbmBcqC3gk$0L zDI#hHNsz&;N-fi9Qz!|tuxg-%Y@=jiA@DmcJhNtHP>(>?l%=doojF#aRtu?^oJyh5 zZ6+OnTYF`dHX?&eI}8XG|12vxrq*)6#fbx<#Y~NvR4Eo^d26hQ6dW&p8N*p5RoFe0 zc(anVMa8$LFY)HbgKDGlWewB>>BY0sfn;1Sm(Y^vPL$frQ;Il@V$h3Y=^*lCj5bvE zS@7api#X0N9XyHM$LyGpr1o*_{>m+$Rb0lk+J?2bvJh92n7{*XF%7rE*~92sgm4P2 z)@eSdlhAyvwhO$|c1zeL-%7->h@~vP6c=hHVr(FyFyoF$Pk=;HeZQezJ14hk!CDb_ zeGNG!O?D`?fcgSzKTWZ$_Dq)f3SDXqi851hP-|2}Y*+kiBYGKk{Pv;AxS0KejPn%I zciJcEiYyeK6xmwFZ796cQC{v&#?2@v%Ngr;kasbxdBSYtwL%EDAjczdi=?&L!ax zQ&-7a5ofZbs;D1|h$}JsMVyjkU+;X$Z*PQYI5in%)NGB;UA228Z6jz+(TKZg3Tilu zNKy?uN)3OCc1-va;E+s5EFyh>RU}LvP&Clwm9?B8Dz*G2Cssafwe&SHc&=cTYg3Sh=Pbqf0-4J* zChZj4M8g$}vJ%&vSyk+t{Sr<~bPEaBth>RcgrWI(oALAOguz5qqhDUJWWL~*^%&?@ zS%~X#t)D@6C1oe!WlL2SVIzSnGFz`QrS^ft;Ra4VZwe0f&}Cs?@xvVMY(vB4^dCy| zG;JV4N-gPw9*f89J`m%0tP*GJ=w1z5uEsHON((z_vy2@bGqW=^9Lm>fIP@M^1u}_n z{QB34gj4RGky-bu4h{%q2{(bHiCU``l7x$dR9DT)+hNda+X#Gb2p_6IQ*abLR8bY7 zKFA1#!vujClU|b_*|Y$|Na_Q_GEx z#g#eSA6EV)wU^k6wG34ChSK_7K-~av)BMz&18&Os7c`EjwaQXQ-Bx+(@)OCs0SA^WTa_bW1C|}Sk zS+RJn-YZdqKwHNojd-4_gKV;g^GYFB(yiBLCzacLkuro+-F!KbZmQ@hfwq=zn=wU( ztwr5B#^s)Ga6~@vmiJLgzCa`~#-3t}^*0F>M5w2kZckn&IUP|o9n?q7UDAy=ipiud zLIN#{#XO2a>r-q*{PgaK%#$nCoR6^yFVf9gBpz=|lJ1BG!5+e{i^Tx_s};$%Go}QM z6kA9-6J4qs%{^Oaow-8m;3CD`I;kM7rY&Ht)Qt-5sp+{YujJ$DbBVv*x^1$|#uctT zy3N+Yw9FN{EkUm3_K37|b=zm|4b8d@d;q8Qn}^>-Zd!Wt5an6K=-4&jRJK)>*6g0- z^Z2EQo^Y4gRMJMVR9JJ;BzC1jRf81?rdw&iZJlAK4Z#Mugm zqfVenU8PJ>DN|gJOF=#eWBYvlbuVcoTq)2VN3oA)ZlbLbXeQ(aF=pyceMb>;gFw4y z(Kgj=se)(_76?4sW`ic0Z33OvY@x<%2@g$;3>vZ5K&IIY(i&SRG=nG-0|Aw1Sx3Z` z=#g5dua$I?8lK7Vf7NVIO$}l?BijVxa?954n5YGkhI3(3EqWm232LFD)i7%T#df@H zY5}`nX@ll4Q(mZT&@@ho%|Qq8$I7Aa>WcN7hs@m4HOCvJq&IwE?kUW9pxI3ry1f#h zr}4dzV+&x~NV_EQBZ%n?vu#RD7okP;&}q0Rc&*2!J}#q~?3(JdX1%QFu9Z#$UiDbh zzbE{2TJp>G370mq^*HFal5K+b>bFL)b`4=l4T{hD>24P$J4ZGf{;`zWoO$50bR)b# zTk+&BrVD4_IV@YB-OU1)kH^(IiM0kT171EA>h8EKCvg|{5>i@Z+tO;OkgmLzBJOOh z_GzuvK4BXU#bQ=?>eODp-i}%+yifC%LFa;5@kLIi3Tq)$0DgsFtP1aps92rzhsTtX z4wJ$&*`=7dB16<-jSCTXh>`5tXdyIEU6}K_npA`4`F&yrsfIO3)wHg^g;aZr7}Ga> z*%0HBD5RRF0e4PXNsDZ@H0Tj%8!?H7!ipzlLDnQW9Raa4OChzyTfI?g)LoQOUlh>@ zvgBz8{^VGN-Bk#+PS)#gs*k(n6j`YqA3z6L9YM_hYB7`Sz#o1V^< z+7fo{12}QBzsC8U~qadM2KAduk7Y^_tAIRu*dC0D`K~2F2K7*JXcz^BMj>YUN!M{Gt{lP3CP}f9z;O6U5)DJV$pe!sRlBVs zXm58=UC^=;&0q*GhpUo0TvA_|520G@>NFP?1o`m8q!oTOh2{>5%9?M39Je<@p$%Ie z2MkMSOH8fk%XyGqvNOyDkRZ2OLag5LYq;r8GI%aO?lIEWostxBoy?j*&@R2z3X&MJ z2i&Ncm@rGPJqY+eQb^?MVGaC zE8W^^4Qz#rdNjl%YbX+ZQF{2#_;G<#T+Qrv;H?<@^@F>~FmfH>+7Q+UvLSF$n%KiS zTbm+15|{l43>SjYZ5EA2F*ZjZkG7CqBZ}vLX6V#b5-q!s7VE?n%4S<0c@f~;p@8h@ zqw0eg(-uiRS2k+2vg0w;@?N!=I~!yW$tmJ)Ct8eAFV|Y^M3jx})-dhOaeUYaIP3kD z6-tq1+7J^mt!gI}!W3M{w6mgSGR<}s4(@uvRh8W`=4Bt_PoSZyj6nmfvxh6rqTY$e)8({P#1l1y|JB92&pn+dKTsH#rEvY|iZ7XE{Ad|)DeZ~3EbYr~#~Ep7kzD6k5=)V_6;1;_ z#P>3nG56iTF0SsX#H6iuE3tRkd{9`Zc@dS3#_|Gp7@M=`C9a~m zIy+B^Nq6DY6i)(93bAX>RdK;=Cd9r;qAYBlJ}pPC$RNm`c$Mrgf>3Esov{X8;pr@N z#R&SS!del=B)DG5CMjd6wYVzf>O>N0J3>kN6k1)PD|)iK$6Q53ud6;lGQhkORv+f@ zt-)1s$+u8yGtV%TRTe6?1ZGY2E4`3{s0qTF+GSH;Jq8mtm09hCb(*WeEDOt{(_AdO zLvBSa(m((Ph*S(#*ftX~j8v8$octyk29E9NiwrAJT_&4BR3R)&vRXlA1ObE(S_nyq z@cSH~)W@hf(?)%YGmw5?`!es40RSiiDX@A0MoU6_VW0*&^b9 z{_z9kRF?wvj>hK}_#SOS|yJ-GRjn0Kty z)dMu-L6w5H_sr+r&AXew=a|C--2rx;l%|pKe@u-3&X^xd%vEG?cz~GqjQP7|pqA9v z^BVJaW*spHMf{{BhCf$f56|Df|8K%7D1XGBRam3%WqWgw>uUV|zkd9W8@&7*X#@Q4 z|9<~p{|WGU1)uXqZYytA{2~5G`Mf~!`rg&yzeh$MSi{NN`h~Zz!5N${J=8UNEfMXr2vkrK+48^&nyx!!mYC8kP;=<$6!`4Dpw$}QVE#h%EYOcKf0%Nme zrQZNqtYiuPp)t>EPx?)dSg~s(P+7@;%uMBoZhN3bjXMW9g^#$6g{<9Cm}T$}mX~za zw9H%DC8kZAHM2HRv&Pj&K~MOmM~h+6nUhw(HlhxbRI}AD`NKLE7t`7_$mr^2THz|c zaL6?&kBF^)WrgcR3~!C0eQc)`m6Hm$9vf0L`v$9{vX;Z2I$SPG z@oKXruVz{5;w+ejhm%8>rLS!z)YhNu39ndxdg7~6-)Vy&t_9zA=e-;{k!CyLH}AOQ z^Ym!u;lvkmrXcqzmNK{@v<(-U)S9t~vCexn70cnA)Q@esCukN5O zRiTV!)v6v9=(#2Eb*v{`yz;qzu298XaG4}tt!hk{s4y3Q{@NASmbgNdmZ-Fu?^P^1 zX(h?-8Qc5ki&6a|m9It(i&wrLH7rQQ;#IrwM09iUs+(b_ef=$s<)W0fCdNY5tK(?r zShIN5VZ7})L67x_X6=fLB?WS$b09ze+(Ot@Zn}1*3R_V;7o2y0b}Ncf5v@RJnALBtWvS*xXR>VthCI-{i+pw_$rmzci|d@h+=-U<`;so zXca-6((Og7YRJ5b8^Cn#vC~kj5t*fh+%N~=h5yOQjuF&HxQchwgJ#_YQf=BJB_jkk z`oRqX_@7>Vr(o;{T&`F7eObC9?PlX`^9SL_!fsl`ByVLLV-L&a(| z(g4>MzuV6oFoZi@b_pcum8D1{jrdC!vgi1Mk4coTf8D@C8_~Sj<7YQQ>g>Sza~u2I+pMvk!C81!W1tf;HJ4cZ zzCzzB2>6dt3d&2ss=MSRF9JW=x;*$R^gQ(?0gCb(SAcdLqAX@>vdDE8%sUu#|M)G- zn63Tss~Ukztd~%cW)j5*a%kHGTXmDh+wkWy#WfU4 zkv13R0-Cy-D5w(=t$;FVWl4q@D5w-uwAJUTt(2rhB+*qO*rNDIbMZ+=5azaRNE(}; zD%43(n(wLLrpn6nOVv*)jg-wgrs}6Y%_JrZD=L_hq(uB|(x#6nn#UvBjf2URMWlwo z)y_2RHPJBo4T;cCCwxi7n5_Ge#cI_}$Q~0`=E_po?!Bc>8WNnaA9PjHPYw>jopd3ZR2SHc2-Sh53Y!H|PW%N1TWfni@zPKyQsceW zp^kPdDCwpAlNkaUHKZ%gvJTCIS`y${##c!K*Z|c727FY<2vc0Z;ml2u6qllQj45df zgW^{`#jJ8zDx=xer}(OuEXeX0#?;DFU(^p>(B+fs6C1Lk&~n2sX_3|qF(4L+KYfUi zCRidhLo)OzJ0^txWQ6#7tixNrP6B27zy`IU&MyoW$;|bOgt9gWsG|6nFu(Alg|F7W z%5+T1g>}=f*6>cg%=vpvk_+u6>m8+NqsmMMAY~xk#GiC1A|OXha4zk9eLjvgCC`}o z`b)w1dxiIbLpgPBj40!(Kc$>*wU`wzDJx4>okR*@*1=WwR565guNizARYA0VLWL2n z)u6pZ!l}4fDW~J3vF;>4R8G|oUMuYi3~(og_DLsnEouE}t(=ZOfP5Tv4NT+_qu7mk z)Jz^Bit9ed&ugW#5oH$y)B(0-v~n^fLMe1ZOSd!{Z5@MBwP~OZOgDX5goz_pI>i;0 zPNSN(V%VS%oG+ntZjTgt#hYT#oYX+2W!NyKb3pHRs~^F)wH;63NwRF(71PP4lqUTu z1%*rbyu5patES#nQB5TxuT!PWT{YSKgUQLZS<vcI;%Flx z)%#*9B<8d!;s;N;&WeurQO$Fy_RQLzcPTIO{j618Qhn~9`qim~gd!>?0bu<{ zD_RkzvlQ%EE9O@RW{1+z}^{91&`NyW1e zLJOD@bmDuC_L|3*vU=2nO@nHo$hD#B-pk~vJ^~Y@Y&!nYtZ_|A@t8gW-oVGt{JC|$ zNGirGSqF^b_)zAss~ML^>~}%~g)rF{#gv^q3ehrAPHhxA4d-{HyK$x9mU(p>h4@zT z&2A^$Oj;$swby3kX^`{+AM!rbrCGPwWRM@vUV*Ax@e7r z>M14xD9O(ugb>}RB2HVy(s4bFU!aP#e)J7~Ny0gYvt#vN&W>B&oMWoG2^Ax1lVwke zc=Iahn7~JUqYP}DTC#8TO_50sy9zP5S3Pso*w>~O0=;#0(yI~5UAE2InB?z9VvTV8MhtIZp zRNa?lpf-l`BP7c@CMhajfl*Kt?mwg_*n0D7778Dr9BJXA*m8?%{9qjd}?IJZ$Yr2q#2}g?1tWs!;UTwjD{%MNL;(FY)ReC^RoSwR(fSmZr9eW zBid!26F43^n%WC5%Q;!LY{;Ccec|b?S}N)Xmxd1|=s)oG!aJ%T3YUrc30TS{%N#sd zMCo#l%;{~H;%8eDl`EhdDNDu6<4IC4?K;s#NSbE(C(E2x4doRftjIKvD?uK)u=&Y_ zj@E46|NL_w_GsyzP(gZ7NJIXIz9N&=pmqspv1^WNK+Z5Oj03Cpa}NB$M%zhDl|05t zv;cx_UtfZ=7mJ0I?1ylN7j07&54YVt!(}~M7kN<^Y3f2x@!SZZf_~tm(Q+MW zAaTHWG12qkh=|9x*HY)8$f12QPNA{F5rGGhj$ilo9gd3lI*jZga6T#DJvg3cg8k>g z9%OqTZo*{N7GQ5&4Wp-=L8D|M-w*bzP_KImOr1B8LkpN$&PB@zOSSaIgO+*C@~zMh zRpGEcM_vfJc|XjgOi?(Wi|~1A8pNzW7lGq2o)&^kn!z^%Y-$MlrgakXhCgM9Ej#uSRc7Qa3QrRp`zUnMbmaf4-!kQlA%Sw~d0 z!W32CT-1pAO9&VjmUvb-)z1Yah8M|%FnUO}N9iHa9$DNVfl`Eq`8kSY5k^U6oCU7XRt+Bx5r2%{LPv zvs>8A2l|u3M&e7$HxfJuVj1V~G)Wh96d2Wy*G}AxJYJB}BL*+wKnw@iSFs!Ig6aYn z)U!_11a9JBr`W+gGqo>xT@IS7mtkGSrO}tDn#aaQxyaQu!A#Xquaxa_u8fFvt2@oQ z&9y`fjJUXCbrmv5j8VkwK_)^c)k(y&p4L9kAu&!-yfzn?0nXP3K>lkMCUcOW0#rgB zKx0|(I-v7qI4Tg#xm4h>r(}BO1y6hh6N3?!67eviH-bo)W97oIR>eRoC<__8ieYw2 zARS>$|Dy1Dl-D&tYuATRc0$q@tcU{q!;eXq#UXRoztAR0(Db_FSy3cmJpC(wB`sEL z*&`q=7CiB(Y9?Z8m*6tTQxx3SCmuhth;kCS3#04d&>xv(86!f=Fh$iYAM|huHICuT zf+K$5*b_&I)54NCc;fHFC*hd>H}fUV-vxza8SH^W4M5^_ekQn-30l%tuJ~L_bipoV z=;=}RWGR%x3buNIRJ)dWY}N(#oo@q-r4W1`OFp?gfsPNG1U)Nd#V$hsy|*nKGMF`ECC~mt0|vMvg|@7Z?p_#p529hBLP>2 z=NG6i-z2#F%mG(#GG6rnWXC*$?D{iyTC+y@M+q@&0CifR>ZS+AG&$zjc#9YalF!G> zTgwNMAn#rSvAp+~zp_4)e2&Mz;y=?Q@v96{7?|eS6H{78%QQ|;dL5H|g@)?$FclN> z277odbFMLDd83F>F*yR%{jZ@VniP#FVTzCpO$62-PDhx?khbE=hb2I}W_;cr*U~Yj zN}2@pGr+d6(!xcn0LE{;g_Cn(2pj%F0p{r&CxX!i1qa{1XvKl@JPE7%+|d-hH`yvj zhs5J0R66gWUl#Cgp+XHb%9lBMf1(ER1s~1t`Jgqab`eIMhI-m9%-B;s?FKZ7gN0hb z!VFJatizOrn1WDA&?GIzAecfaFjNHHpU&S*zXS&V&9aPwho>NdoL;(ke8EO(1d8+a zA*K=)jkI&=0-wDUk)$~T_jQ-v&6g<`6Eu7D%;Un5`=&9C(rKlcaG^@+%qGG`x}^~= zhM=j~hW9CDo(**>%+-Vo7gxYVk*5on41{nYTa`rykPJy(+K@UMuq|AC_2rqff5{Cw zXCYjEwBb?#C&|~f0)tS=!UbjPf)X2W&U_G?gQihf2CqmPOVCWpkGnCwEqE}F1}0mK zxL-UDN?%H=H`^Aw6k=+ALbUj?FoT;zavj8UjWYn zrAV5KPM~BR@|T*TkrkKAh?Ga0qC|88#XN1aMkhqvzSk!!crHXBA$(g|BU?7%L7zy6 z*iuK)CzM=OpA@*&CkETXh+CICMZImg7_dBxB?a0~gvef4yxxuRQ)yX<+y!mrNq7)i zR#$qoDMFZH4S}Dp{SLrP+cjozI1naLmefi7sG5WbYT;YNY};5-Cv_=hOPrYU7^KJm zBsH?sCd+CR9YP^elt?*H`4nNX;CW~cO!yAuyD-3rDV!>^6(pXr72v&~Sm~05{#e1y zwyUx^tydb01(lgBnahlI^%pBA{c6fmluRTD@5GQGqbSJ;1;HBwVuMx^E>%VNf=dF{ z73w8PO-l+4_gDZaf@2~{vPNMFmm~*m8AFf)g9-$~psuBOn6@&AFw?stp*@2ZR{1Y+ zrOp*evFsd8gGF*!YzZQ-tX>vDQGKS7Ej0lVhFH`hNbu;rE_s~XSbTsKC|0!Qhbinx z$2$(qT)D6C2I_zrY&A+s z42@}R&?a~d*!+O`!o3R_z*Qj>_+`M*Lm=`G-5P2D*6>nLml=*yPSDnsgP~Fmcrizy zNl(nt6Vijvy@SQ8-WRC1T|;akt2QM9&RXUCraTs)p7am8?IHydW9ndmPGLOV()dWi z1M3VWLool)BMTUAiaF&#tvG%W8wxE|+*mpZahoF3_a>%@jv-Bnp&Xf~F`+^^;?km| zF-ne@zxJZTVjv(+#*CW7ldL1k6_FbTlH+l%KPpplPp)vTHX<&gynTwxzgDRbvWBjp zI%19aH|`NDylDibi~s-V24=$ zDEJjuV|)>%u4qtSY`}S;!-WJ?O5x6Bbwx5|3j5A0((i1Y;7&)JXo7vZ!`^V?oc|FO zhEz={A@@jqx$c;IC}JWBrKB0Eda9u6{0LJr=`P^d=ciqDuGai5=T^>Cg|R6*_DYP< z9Fygcv;dMH&$kLtH?e=?be)ggMP@XDqv{Ob!NuqB!ANus7qL+#1(-@BL&n`27*LRS zNw**m$4?pQ((ncqO047n$J%rW9U&Bc166<-%;v3c?4A7_OQi>*bN)_a#KG zZ&V-Y?650mQW$hx%-iDxuMU}-A1R4?FabaE{EObkbN~9MYyM#p|mxWVX=QQ?B z#2-Go1J0A5`Z)Qib4I1_GdzR^xxD&x%XWadTkIhUBj@1jZau#`$Lx0}D>(XK^_*vb7w6+FE1-j7L8How1}vK3t|%tMqik{^{h;3(b3M?aq-LV& z1`nzN81aHbsRfq1=}Q&Z)@>n}|jPSj9+@ijJ#$s@bYCL<~!g;8hpw@+ z$O&7YaP<&>OhxFDC?jBH0TdCW(H(U=v0gEf|Am5Bz*`EUW&exSiiK2|3yA3ohREQg z=3vUhvP6f0hlYNbsD+g_7}dfYL1y~kjA67!UKQZ*Jq6a4!NVz3B9ZdQr&SsN+?fCk z%R@GSyXIkhnxi*GH!vDe%zSfE!rZLPwsAb6j$0qsTA3x_nIG~R&{s!pn$LEK`^8?<6W#9OFm>2$2YY7CqBw#M*`c$!7b;lT@2pZ|fMAF4I7MhV~t zQOx4psQ;&eO;i29e=Hp0DUNB>gPSjjdDm46-rie2?{5D7;K9`cH1k2#owkjP|Kr_F zXMA|@cgFl!Vy?oCV%{_6@0La_sjufq6EVoSLYxnk}IZIY72b>>S3(N8OlfzF>ss&0q zgxly8dX8iFba;Y<^H39oKvPbRASfq4EDr{FId!iEuc#Zy*wFT{#^VUS$QI1+^XC(q z@tLDa&pkeW`or5#UwuMbeua_g(~Gy*EOH=txSgSY+K1A?pT7EvBYSmQz*~WzmDlH6 zx7-IF==An0I!)<`B7K7b>xNKeg;JpnWO`c*Frc?AR*$0fcB8kqZ{5LHmKk{7n?2xL zB!uNxP4W(eOVriD+drtGGok$KscH8WM$22CRdb1E(I?QaWZIz!GW&#n1rKx7O#fWn z(sT!idm$|U{HI7h19{iT(km*e_6gO0^Y#rZ8mVd5xO|H;1*o#q5}+_z^6)&&lGd~b z_$!#Xn?WoWKX35YtvnSVM1HbU=r!Z{_nKuZ`uOvo9C?FuhXEn*=8#M>0H@5WTHtMz z698Da;9m>AL1uyxJOJ|$%gwHXv@E&~SscQmhU5$Q0SA_x1P}$l7mxU@SOsv1F2p*U z?7e`sTTyQ{c43cgVRteo12uN16_{?7nW}E-2OGqu<__1T7W1-%yM>vsuCO*)Va=gi z#&H-$oGr`@_O=4E2d)b}jg@zQ$4>R*6-aaqZ>)W9q8Ra{1#vBW6va z3EjPmGG#YcU#g9Wzo;RTbkc(lDhF<)jm z2h|?0aY^Q4`ozgLn(D{hOYyLW8noY^H3O}}lO+v1huKB9n~Pb+$_XC2;lmntT#%_= z^u$N;x_3RshQ`y&Fxv7P<@rdrR_%tB7|s61yBo_atTXHhlhIJncF<3TWZQ2zcdzk(7N(`0^Y}Eznxq^YY`}qP&JnOM#qg4>|gJz>2 z2Kdoh$pC0+#0=7E$^h`J%$C#iBVl>jWdK&oEgw+3;E^QPb8Pa;1Q@ie;L;hfHIsnxj~F5r~BsEz4{z zz*uUT;kMC@T9#5v0r%jzn$w-#5+z@&)gn!y^QpUGA6oE!16FjcyYVlO1>6i}g=y5n z94`EyySt=BfMEYsSdUZ@9gu38%*E&zuxNHTPy9bL#ABoSCJRW)vn;s2q-&xN@m~=D zn<$=!%woZ9i6#f80P+s3q7^h2#)ga{3$Q~cJq2=F05Gp!DyRknSm}R~3K9i{{bQRI z_E~+)jZ^SzaN+087OsNYCaaE`cMpcdBNHeRK3$b70bXc=0S+n~MGW@^M~c-htu`i7 z_%5-_vPo-*h{q_tFaqVOJFd1{L(hS$iZw;26E;}?d`sD7q!{Et*}TOHCOL2!Tdsr` z+FxZti(^1Za-f*6;o$aai$~A|b?>5I7-wk#lt%VR3+PFZidVW$Ezsa7r5=!Y`O@K^ z-aUnRg$w&#z##pPWZr9mrv+}cK!vs0Cwg`73{nG#7$@sQmmfv>>{oST%NBY3ob%7o z`XgAe2`ZF8sWl%LnSv*UK)5tR+u0PA)eq0da}rdRtq6#xHH`7^5dn>jQT)R3V^h`{ z8UUvL+gHsw^2gV7j+$*)3g|%&@9PHMFMQmAB{~rVA1ufTeglJm?1F>fhqY*U%fpLy z=-K(4vlrA^Hj42E`>f~~4o-q=qWtVI;rA1W+#4-7hyXOnz~1iwIN|ShOAWNWt4ASy zgJA%ojFKC+@yaM99~D0lc7Z_q(|jLIqdiX~&opn@K28u$6MH5Xo2A%yX8ZPgzRE5W zdSqT1eHW+^PN0M8uL{Sy>|**foY4CU(MvL6v6(4GvA#vFRj4Jozg99=CAb=+Ap47o@=zOCqqtb^M-+*7{V0?C~G+kTcQ zobz{@G%;^e=z`Ii7H*$l=k`;+eiv>(*8w!9|vTPE=jSWjmIH#gUo3b&Dd&Xe6V8M_dbp0y7VpU%gmwWF7oE@E z3(YV5oHQSGHTK`~bBDLsd?C?kW*<)G@Y5L#a$Ml6hADQn4~Cnp@1M<+*X) zu;JU7v_}@W1^eX+@LVc@j+-AQq`p#X(sNGQx5$?Q_r&0NcCssl0h}c48-!j2p7CXZ zJ{WpO*jK?7MQ-?e_*988=MNE}n&A6Q8_+LGDX(38UB6c4zMQ)=F%B2y(sQS70z2-gCLq0GI3y9}m=g4foU>9P|;a=WyZHR0HQt$Dc>pD~;|b`@mqq zpUC-bI{mV;>k1~yy#hya4$BiDvO1fvCmw&FjD1enNiA1=-B|oc%k8P_5ihL#e}S$y zw|Y@6m6U^%n^dw2*NBsBeU*TCOV%w1;e`jEBxU`N&YfFjak!;=>kFSW zFNHJ?pWw{Z0i#MjBVXwBUn(q4?o9YE*o5QO8RkzjorWv~;Q}O}(W~ATypj9OcNf+C0CeH6p(l5>;>Ez`xzdoI4 zub1QXW1DEl;VVp-e` z$lO_bYjy$<7@Us{ff4mUoh1`c7<@>>`|yc*^le%$R-agTN7B1sH_1+7IVL6B)M~Iz-2aklX72ziGyl}HDx{t zy2G#CiC{#TaYf5^8O<+8sb?tMibyNBAl(68q6nb7&E$DH?K+K(?aP6Q!$F?>Mp?nH z?*|MfUVJt)=X2Dj*$r)keKVz@Ih~ z>-%Xrsdiz|YB-w`-^6@Ne3Le?o@)OE)U{?2TU!cLDfRrslFfPnt7#9l)87q2w+Zsz zpbgjga+}&lY$Dqo?OqoXQ}~?zMI%O^TxoSXSypio0Gi{f21#jn^#Xoo*5noVx8l`w zoY-=2TZ#1vw!I?O%o+uLlgW0$ycyIEs95}L=foB~RmEgBo?Kk}_nh-azG8^MnG2P~ zeh??yeu9Fy4di+x+i>Qj^W>a(o-*gAl#WfG6b zAL+dKNf+B_Y%9M`UMnan@BF%D;)!(o{5AY$5RIEtb7#uS7BebI!psUOt<%dT7W>r; zb=6jLuqj?nTH`e_cqXxaG$qzwjyBYG$&t^Gs4Tg;gLQiC9M4%bnAyXd;K&?aozD|5 z-1!6!4m-?8)6WdYJI}~92m2Y0+f(z`y*;}v&TDVrK`pHZ%B@gl!R5smIO(ZRYXikc zz8!t{^oO22lW1d)$Y7f%uY*yObk_YO!JG$F#M#i`&9Jp1&_RHh6Q6_2(>_w^nSE}8fHZbV@`p>p5x$~cBfCh#DXxR_rf{xkyWJD23&{pV2;cDvBBUMi2-YL=Hyz7 zKEvLTDGI_#f+7!j5_$j+Zpqi5UWX<5DlPF36qxrsxKrh@b6Pxd-I$h+8bw7%OL*@y znL}U1oLL{Ztv(t6^<|BVL4=)jj+HnuCEc)Q(q4FGqvFE@{Q~DCIOWJpLfb-b7ok;I zm8Tq78Qhx(7@>s${IL?|2sBB>)A8bvzRU?Zl;(f^f{xRlEUIljI!TBZj)M(%`4J`? z4mh*n2@JY{4ldoZxS?NN8yzHP!@XSD@IAujI#Fcv?QrG73BCjAz0{8E%7w9)I?-4S z9T$&S`NRsn@+isw5iUHNmnLr}?Zu9fN_$C=2QEB-K41M_w*A}ZbKPkQpWijo zxvqO~-Nrm|;2sYSOnk;^&&Dmmgu9aKj@&qLU{b`)Q-cGq;B_N_*XT!F*yD9%7rS)G z^g8rjDe%S1!^I<;KCNt%WcLff(kqj9RX#R&1vxz*t7NyPO;}5SFDkPYPln-l6`GRW zE&iJ-rtOC=XbX2PL^;^5^WTpCUQEJ;;WDxWa6C*F17R5#Wx?3ojh{A~)WYNfSjP4F zUEB#`$lwhP=bf@!*pi-xt#eib!6Gu{y$p7nLGUtTxBTUY%x<0LEEq5EdeEZEf^$-N zS~L5-?6%7RxmB*}k2B5z?I9tH* zhBnB8GfwsY0`B*Ll6Y5w!t`KmGFC5JVaK@W}VlAHGY zx8tky*H7mDM!Ytv2Tj&g@8P3(z_AcmOd5m0MrGM}escV_m1?8JE+6AoZ zkTd)3sqd|Q`qp8ezb!;p{0;NBEzu3Moz<;eFLo-=$?L@zlh-k)r!Psi9%&_mofrBp zBYxKt+mX?NZw_`1^G)ovka5Ry(gJmpyouv(5n52mnBpxoby2O(&iUh3R{L#jO`tT&16+mQ5=CX7z{ReX(p?M z!B(;g1{jtVBsVH^wlh{cW2@uLE(hgV9nT}MBA=PB?gT)DPz^EM!okiBlRt>u=p^l= zuaT=!HMXS+u?#wCL_BTzT}>m@QQ2&WZ3!r*k=MSMCAl2C=h(-fY;uGjEB8cAPaalo z8-wIk zP3Go(B4Cou%e%owH$jUVA3d7sCTvMqI}%C{d(l6-t*D!IL%1Rqd==GGeWQ9-wBC4$ z8q<6oGo!|uJNkJ7Oq8@*o&?DiF;!*rvgD~O0>K{R2{vlpRMkvDW_)9CP41fsuvs`T zcrd#%*Gisx0xLWP*KlGivcPD9=$vvBEUX>AJ3*bym`<7KesDeuJ2FYI%T(9g zQw^kZMnq3yg>_bUXF`dtG9{l(tCO_cYcTT5MUCi-jDvuE%)BI16dkDa6Zh%};_`1mW1kLU5U-r_@XRb5uwoeT zom668RP(VU0c!|_GoDnf8mOzC==JqAA7InGYe|bl32~Y^B1R)gOJIUDHH=~|!!Re8 zDe?1g7mf1pbg^r^<~cBB&^@#kY2=X^=(8_G^J-Z_^bmYe9wR`$SYi;El#k6)OtSgL zF^ZsaoT8UCUPmlCxFEX5+(tf8&nU&)Uppj{!w1^T9js7Q8CSKPkgCa4ga*C5;LT?2 zQg@X`XQ0u4^LGvkxm>`tw{NTkgVheMUKdSJBr~8aYM(=x9$P&BGRoN(bX& zPg1!vScwxxa@pi!6aD?jW#*9sk#uqyj8TM>=Qpa#ntD}d26J+Gp?>Ya>-s#AG@urzX7M5xXd$*F|`Ux zxvDAW^U@~Rlk~B=yfc4%RM{d1ib@x`pie>>^l=&%z09@YSmIsK2A#}ovOCu5I~k-= z$e({+csEYMTbN@YGChzjwp1Wc+afm^E-`4gbdhw7pO~dfba4ifE{cngR|Yo>2%~yH zG%}vB0m-PfigaWPechf}jpHW}m zf|G?)MSj+$VG)%=WN&|n<|X5e0HSEtu*V5`v{MdKZ&7*QJZ1yAxpPBicmPf7911X`vwP}4xf!L6Wl|@*c|D#;Qg!BqXKBKySzuerr~>QQ$Bo$Y?V$D62S%soW-OVP*N&wN`t-~NmD za^kg|AH6~FZgHLq{MqqVbiH!@mq^jS7X6o--}8Ox<6F_=9qW@9rl)tQ_pdB}{eZ37 zyfpgFN38nKUvN%GsOIbi=Zc!A(tFmoqNcA!N2>QX^Ovb_$DDf0`nxmUm_BKn_KoS~ zz35l#H>Uf0(SPI{)AM`L4)M>tYV|u)e zJ-{^L^-HYAdjR!@5AeXW8D6 zZtq$4cd1W7zK}V+3jH$*h1XwCUYo8rwD+$~zdiNZ^z`2K{srl8&UnQ-+H~?V^*wJ$ ze_?M(Z(p$9y&)a7s&7cgJFoRFcfO;%d2Kr0U9C%uZxaV=IQzBf@!InCW$Fp#d&~W` z=^e_~fd8qy_Z$x5W$05d-f_-U#c~^jp;_{^>63~^YT7j}^J~-Z@QQSn&^+3k&iTr9 zgP6+>Z2FS)^?TFP`^Wj-^iRmFc}qGgcM^e#-j0rfj;~1{-kUDKUW~4qA&K5@USFI} z3p2ns4n10Ldspbw9}dmS%~f>R^5yC^L4@QCad1OVc>g-yl}`UUBBZyc*Egt_-l@Jj zUA@z^a~eUL(DdPu)vTA$P026IXt6W*Gi!QVY76Wdl~Ip!XYW=eho)qHxMsF&@C zVm4K~mNAQilG)Uem3Opd<;pz0ZwyYhkJBa)=4PhlnK`TQJ%B!cuG}s-;Z2`C2rMSr zeV)G7lohp_GI^PP_x&CX=2P((Zu!yggR*uq=?K^J9A6VV`jCXYIFPe=HBK&G-FJ&-?Rm z-KJ!(3+KH#0d0@u8Q4sQ@88`qwR}2p`TI=I=Ul9c&BT6%hN<{Ru^?bJc`lv}wG3nC z9x2*|hH+}1&$$&R+~*N<@pg!{2ppuT_}z2ymzs-b3Cp$t+%Y~wkE@>3?wA1P)T*Xi zHSMmscwrkL=C=9K)G_8n%e4Chs+04WExnhSc+R@g7b-BRSw9ocGPX1E1Xw|zP3By+ z&4Ft=cVnJ?9$%7kc9_ok8TDp@y(@H?g=aeXmxmI#Ye){VM>;A6jDTh1?J#cO*f$5C zoU~K$$WN~8Y`l(GjN#zftF~c~&~n$Q4)gfS)34g_8m?z+PW0b+|!b!*N&^Yql!40=#AEaMmfDc)h%@o>W z!5*&c5@T;<-O=3Essabxl7NKV>ET5KX!Iq4B6hIEr&{h{d*-q=oM7j0y?Gtccug*u z!}+d@6Oh%JOfP&HauO)4PWvw^$!mfNkl@wg_XJNY&VTLTDPAbyOpbql=4X)>Fh3lx zY-h!s%^35^f$UGENGT@X#1nIS3&CP5G({mAFM>mN@gfWX4O>F7ms9s>B8?PDYarueD@eSg%;fPJTvW^mOicO7T?;Yg!yOO_8Y@u99-D0ffwP&} z-RRlRJ>`n3;hxFoY(mdipn%bnOsv%>UilsD? z@;VE+Zt$cKmq&a$phs=zEAR0M6WbtSn(CMlu@yJFD-qKW>jS)I;xh1zH!wb_nygS^ zUE&bn1i`{0eAZkVGVEBG6(N|-B*Z9W>mLuLvLXU6M-UBeZDygy*N0^HLe-7Sm+x((LRPUz}kbF;(&$%4N4; ztavhW+?dEJajO#`nU(Il1>Hh&vN-rm9Bc}nOV?0YTNg$4DgnPLTM|*~+0q<5gF>N} z#i!M&5-^r0Fj6gi*r3ENgK8BBSYcmo1*lAivDFuJ1QE`Gz$26cNaF^?c`u#VYPB2k z`Lb|-F2z!gh3%ZGQ#Ise*fXUmG#CxZ9z6;&Rn|T$=gR!*XY?(*Ij&KTT!~X;8YXRIf0c%Q zr--|_Q^Td;uj58t&@v3xaWX_h@e^5kh&_p&3ZN%>ljWg0R#%PRA&`^w$1IJPsT?*1 zGR}k+6zj1pr-zu6G$XfM5hO-CT>@Lmbw_edY~rskeBzYWl|f%z-i5($XQzxLJ?x4P z!B7QBqBH1&IC}4ALsQF^BDv3&4ebY~WD4^}&OE?5^w74(TJj^5zEl+V@W_u~>C;Xm zEXHpd%-K=Kg7XMN1EDena>gUbSP)Kvksw-iP>`Urc~P#{^)*KZ-8>{vTK!|D+t6+j z)Ul_L1f4zgWcPWQQR3C_dbpgcRHud)bxBdnfdlP89zAku15%{=@D$D1&Ph_lX8yTN zxy6YZf0nkLt#WJx#ZQLhLXBY$G)ecqO*GMKn2xNc=R&l6*MUKWEUz-d6zACpR2=-hjio=0DJ|q`$ z%;A0+VpO&CP%R^U;Jks7xwpYQa`B`V+z#X<>9{*&so-ZiH-yrZXj-|YA1R^1hLFgJ z%k+O_)Uz9G^W($axVHR?Q^CY}&cbl@q|4CTv>6wH{k9AS5|HeO#cnCc6ePPCEDg*Q zBnZq5LyNVX=8Gv!M_0_wQRguYCm6{C5{2_e>=9j8tH074ePFp4xpjeN$;8a|Vt zyOI~Kn@#x4NoaE^%`W4P&I}dmtWd@C5v1r+v88%e&@ma9JnR_C9O;bg#Pnxoq&sxR zq&TaIgMdRUFxkiDqG9M-iG0JtPYm-6!0(cMQ*wt_ve68Y6L)w1mWF zWq{UM8QWcyCuLghL59*I+(#tjP=3{=Fb|vO;NX^-hlV)DHbDNCbo#DpMW6 z0)JTs{lTF{6U+3EuDH;J-T1tcNK=h27)G!_m3*9lIEBE^aIM~*XP{u{!^;GQ-4i`1 zp3#Av>bQQrALLLw;}kOtRqQ(cHm#O-I)5XUr%upjc}~Q621vm$Co+KsQi0RNgmY5y z|399=D~gL>m1YQunGfs?>&Jbx{ggH}>9l;+AcLdR0J4tJ&n8O<^ps;C!Dv%WYj8(; zfiiL9PG%eqS=o(nG*Fot$!w!C3sxFeStbz^=v~5m-UrFj%23dhXu=>1U@krM)4NAp zCb2vVrLdI~V-(I5Vk{@2013*pU=xaTm;te51!!0R-)F(gBIbTM0AsG;GBbq*abT57 zR0L4MSjBn7W1|cDF3U8N{9c1K;4{XUiegLkVsFIIm}t-mJ1ENW489N}@QN;M@(0m_ zI+L+r3#!qQEoeyNxX01YOVfsYM2?cyaTWO@i~u3h1N~np0fxys{NrMd>L^bz0w373l|RN;Aksv2yDcJ5-IeI$ zd0h^%p%Qll`7Vhs{#J~OA-ge+`fXZc0)`fKfiSkR zu2?DQf*9pB@_K9=W-^ZxC%(ogjp8 zO=KcBAoYyICCH1L^P(KKt1)Ve8-f5hEZG?dp8o7oS6@xy8SGjk@2H!w913w@x zzmrWM=ufP#3yF>vPY8!%M{piuB;J9%L|#KZ%r#FaWj)5%>L^CeIl}fRkHpaA3xKl* z-}XF2#_JJ#!ve#U*I0$W^j!(YkJu)4sI}@Ppc#9S$99-4Z`VI>2p_O~(`XqiXJM40 z#9J9^P*+I_rZKaQv@)R!$bL2z%%FT<**@(&W+K!>P zYSL(FDz}W49<+`c+K1}MC%-X2J_*L42vx<{2x3s|hD->xW{5m^NCQe=N=&Nz(6%)s zA`S(uW8nUP*H7tnT!%HwFcrwh#Ykd*B`gCfqn7zYfr>lN!Tv;c<~$F&u42*F8nrgY zx-hZ951tATDzpW7`t!ID*HIbN)W7r2-;3Z*yX2|%0kdezdxJ2d^}f;|mo}%c2ww1Z zgqx0M|3^nSoWkgw(g4Sk){J<+FVGJ=Hvg>QS%ZYe2wVm%e3WdFX2Sv_!=BNtHc%N# zw#Tx5tiyHEj%@&X4_1qkK^CRV|4|)m+h7UB7-psssxnanTn%zGJERe@8O(eLzPxA+ zF8Xn-4r}n{gBC6uX4QupF<8bkl%7J(g<=k9EHivEcu^4g$c52Wqm>3Lj#beylwqlK z6&b?I6{L(zY`WElJd|*G^^D5M|No*U{^1B#M9!EBMn4 zQ2m&Z29oyB4DlVKi$=S#5VDJr#YRLzmdbE`+fhyKqnZF4b#T#f9+S1~hM@>!%t6bS zXcB@hn16cNf7^fl28bv8CFfmlfHpQEV~l6CKVnGWuX%`aRZkuU+`*x5_1(X|3FNAUBL_dc)!K)Z3=zNmAu(|7TPq7x%p-O z28KJl0Al56`?V4CRNL={xZ4XLmM@KXZ!!1r0*D`fz&jiMp5NvWFM#;*9b%e@*=rv7 z9tOOK;qQ2t!awkag!BRk#oW^iAl5I2IL==7fOi(mV7>zaeYE%X_9mGye1g#h?_c2S z8vHE`XS@XBr|GL9>T4X5*9xBd5{UQje`*u^iy`m=2^O9ozCogSGZO3g_T!WH2c*qr zY@Y!cUjuRI(qtcK<3kQ3n9cJm9Qa}ge!P%vpegUxt02GtvX|u058Z&dq{H$q2;!v- zDhs<&e?>r$kI4vpYrnRRcQNp-3-*47@-B#TP`;v}c=*j;5HY^>fp3Ty(c3@V-hSe* z>#In*8RC@CKaUXJdte{EGGVA2n)}?BK_Ce{PG1J0Jov^19^jw;@~{A2oH@W0@iqq@ z^M8B^eT)7hpB8{9dTWAEBv)!rOjF|VHi$QPFGRc{LcS3Ozz&M%T@)N0lrmWLT6i5q z*J)A0LB!b{jnCw3ZENogA5vm!Cv?0f_(p`gOx--I>*-Q3axJDZF+f%^Mew zVtx)saagc&aDe4(stzDOaXaTpI9u|@UB`2-+Ymm|6=1VL72tdiK6|I-%vcjt)5W8h zd9$^nc?k1XTnmZ^FyO3C%Gs((J119Gm6&asp3Adyc5E|Znw_)BGS1VL$+mh_pg3Sc zT-)7bpr@4)zW6_nVQ2-^gc}Nh#oto?K8zjV;(n+_2;Fi?7Dbk(t9f-bJCo}_Nj zZkQ5PjbzU+eo9wd4gjiaZN6oTYcUN0*U@no_{@%1{V-ds>Whg~%hh{x!WGE-K&qOOCfHGN{$+0dBnxF{0YJwhH z`H-54&8QrqgA`;jfXk;iQN5U2PIIp~07zV}C2_GFgbVK%y(6^cW(3d?datNJIW9fl zGxd+^j_MJ5Mo)$V#n~6C*>qSn${iEvj8?&*ktlm2l^!xqi9qhkN+peNrUpqZsE%tZ z%zn%^>A=Bn+K=T@uvbDjpG8Ka2u&xJ!YP^?Tx(GM7hHK_-2U1t!$IC3Y2OJ4mE&nHeMzM?f`8eNZHW$Pf**_A;tMJFj(Zcz3@sY6PK>>v=^ zeM2bYW1SX{(1&Elf;z0Vh*0TNP%NZENs$>ZUdw!>po0{t9Y=Y91AsO zYgpj8E_ov9%dhc85DCf|j~h~^=qhkC*4rNQVCVwu8;88{vE{nCSQ}{Z{s))>jByzQl|Qpdm1(1T?pSf7U8X6e?zy%b?9E--=1yZ2)P*%m+t1%A!7$T z?MxRpHbWu{)+dXj6gM$Sr=-eEa2x~Y3_8KOS0uM_%Ur3cSL^|pmu&o@EYZ7DR?FG zK-w7R#xGG;laPJkK>x#(?Q;k#1?;khE5_tNz2M_ItbgMQ#wB`5I?T?M*B_7Rq+E;& zSQl*(F2oGC>(F_cig4C3%43P_GMpb%p0WDY9s98^KfAA*&esBpUJep^i^xaecs|98!tk zzYC3RJiSQj-}=MLf#9jXJVP)<&KpN8AWsaw$`Q+J*m#uBpK8G5F-WcseMDt+v}S5U z<;Jk0=U>bvJ}jJv517|PEfJHx81jFtmKigFDi~kP&;Fyc{J+w~m$t28FB>;=#a1@# zj2?EL#|F3qcgXQ$yQLR9*Y5=$%3xV?Z~A<@{0d(t7$PREv11$ka)14vh;~2xZGF_B zjXV)PrKy*c8hJ?tO(+}6udsVZNLlL*Nv0d){Z9z6yffFK@+uG z;UnW;kn=KXzJY$ZgZ}Wt9kV2?e*A0kB0?*xJmWl-t#NP z{NwBx-$O5NR}b(+!Xdon-St-wfNCfE)dSuP{blo>#@a)F@U8N0eDnVOjcf#a6{F4X&E|UBroP$~zs6&;d)cKQjw0gS%@b9_@Vbp2IJd{g{`fE7N59%J|4uu! zOGYYJ*glW!TNI-h+uXMN4?E;%yXH4A?F%=L^PTjbw%W~h;R73Y*XPahENu>RGkpVK zpw97m=llNM^uGJQ?v!tK&}W~+#`>ou2ZlbayXgac{#-`~o-7{OwtDokx4xm=hnQQ9 z#2vQUnEI!2XDUp;2#rnl&4$+i$L;vHU$Lm&9)0W*XYz`EW~mZdcFCiAhz<2o|M}MY zIeP2c^mlEux9#g_@Vfba&xU$zbjRl4+hZihPWKlt_JG_HuYK}Ne!JLb-t2NuTk381 zxjVq-=c6{-_vjg0>ajcBtlBjMI`+(YBAVO z1y{k3%>&D);8vX#>wryaEnp02GeI@zsX;t_>rYiLBT@@|#LbI)>tnV3*0VJ)S%M)( zaw^4Ci5D^UIC60}YOoYFXt1i>9^iwM36&YoDkG~c4F)A)k3K_6Fj0PtIcb)NT1%&S z4avr3Hx;PdIntiI?P5gFzU+yXlPOX7W(-T4`PJdF4iG znfhBxutPj(+)AhKO^)Qud#$xogjG+Y2&=b*|&RTrHP6{yyF9@0Jr`2XqH=PvA*&?9VC9`&ILK6Fny3lG)3bDzEti(jmR3dTa{r&|`9%^q4)NjVF=WS9Eoq zE3Y~FrFskxLpL((g=w!A+@rmq@dlN4TnjR%6!wU7l1RU$AlvigGe-GPX~!bnI*^Kr z;-#?^2+TkiEx1}RI+OW>Jg~0xIS8^XkRIie8M@&~%!Henu{>4G4VVL_S&&7mzFbyi zLH6b?>jueD>vmF*xl)pQ%TR~}jKq>;3u0XN&}Nqf?P+So_iU0N^Z1CyBlLL6xh;G9 zlygqds4&Y$jvOZvk}UqBC_Fr%V;Zw@srKpfQP%{^U8Gt8fxY?kD?5c&l=%-%wTn?c zrG>e+gL-Ezl+9((932$URO}Rq)jBC~=jB}dx@wR%gZmd`ZS#GBAUj{FVNK0{(nUW$ zRqL#Cy-Sc)RyhbVUN7H(i+`Wj2$)c(V?bRVhQWEQ&w)=3NEa8 zwHhj~h4nE>ZA9IRKctC2+3!ZNr5Y_( zqsE_y3dQxyxPTN<8)6JC+zXDiHCpcX(`lm^E5d3h#TfGdA2er)cpn9)i0Wdrx;DtG zZ7pUCNs$59w*KElSV0>s*2BJEYca~4#@B9XwbWe8h?T6{DaN>V^^Sah)LjdBZoEe6 zn(PU9oB-tguRL;Hn%j}pj zKCZtO7~}b&(h8j>K$zgSfBthTz>uQKG^vqMlfmQmA{kBybH&%6@sA%N1Q_E0Lw5z_ z#j~N$Hmsmx=bDW6OkoacNTsnAH`Rc}%T_AA<%UFl5JGL574B>kYg@XEu0H+y-~aq4 z|Nr;lU;pAi2nYoK1%MF#(}#cj(13&4{PW*6;$Qzf9R7Jg!X7;Qo2YVdhcx9U;D4I) zfHQ6dGV>*VA_BQ02j=~6&)Bp6*T0yhMNFE(b8U^`r+TrB%?)#y#=!s17`D>C<;~KR z0^p?J13zjhcpGE<1obiSHjjZb#;AQ;Z#m|lyfm2gpchxSN_u{VytBVjWM2iit$8>P+`MaM4%d~NOCrfD$VgPHUh16ZqN^yM>wf1BB(^c zT2Vkf7dW$2vnY=#QAiz7B#ONH2sTYA#wt;Vt597Vgh}x>WYokg+9tfPQ2IBkh(#1r zKVS-4PxGBbp!{m4)}E+4N*SC7P6{EXz{hIm?87Q@rXw{(0BRFLDx;$w%uvM7u<8p( zOxplUs53($JT0zE-1HdNe7t~%@K}uuDPdaZs>6Z(uS6_v{4f6sv4{~;o|du0UWF85 zUQB^+{KKaX*$!A_iT5m(ixnUac`iC)ZstvY*<}v|;@^jV|4YPzErx$g z!`8$2sG%gfPG<`HQH(k-uxs3UI&)dShXM?M*$%#XpbKvYd0o|V ztLaj?Yw7NE1m}JSN3Vju$}?o6IL(`4ye<9(WA-5N;T?eFqKWv6AirTnTl9uJAj}J> zSmC??XLbdok%2u-KdNfBW{(>%QksIZk~vJ*4IXE_$HQI;*teN3W_kNF$C>He2veBrX;b>mm&m^Ft}R%sT4OzCElf!eX2Ygo`wxl9WMP>q7W ztv9n)?NfvmNdB0xkD<97rWhshh$a_Buxw1i0NpOyMA5QCyXSmM1l{C_IO(b$&Q&TZ zAmz$znu-Q3es-xnU2@(|RGCRYVtqZeXK8RW!FD}O)P*k8<&2uhRR(*zXo@o?e zhoJ5;Y1p}_%!2`ZfI%Hfo=v?`U^(*%pp!q0L$!x^0rgEe_^!?B(TA-St47qNkctFj3F+V_iT}(@`NuMl9hq9_{7ncxL91R;=`?)*l>)X~`>~ zy_8cdQmQ#8jRKf??H3AOMzo@t3a62~#{@TGKxi4M+)RP?w*tDjQ@D@$*cnM(LFjWRz=VInPN-hI$f4f=oo3jnuRbF;ZbR_w(BcQLLd!wL zDf(`L6HJc`=hCVN8P26NNb>J-LLGj3m@Ix9+_Q>&c{~ky<8gpH#xbnm@=k5Ucl&+Z zeMnt-{)^E(a`^^#59@m7pBLB_{r=K<-p(XDsCINDOug_Zup9NKRk#B@jB9q}FzlO^+~j<&)t3who?2!IK-+la-$-jrECV^|5qv=TVEy-hBI zRyl#4c73?mB)<<+Wk@E-h7H8RZ8l^QkRju zW}xBU2;Mj~5N0`q`^No%bs2PJ?+213gwbUXj!ffR=+{*R1Q{Y2{qa|kjhTOZyHWe(vyX`qDfR0BUGP}81xe& z_WCmgYhB&qG`z;PFBqe-EFz$6FF+t3Vsg;KK2I5$k3jv;2clOnvif%1*_{m~75JaW-iK&U1_(*%S z19Fr#@wn+6H_g;kItJt9c$Nl~5B^%4UQ9Tav33-FeTgS7^m_JrhVsNEZ+&_k42L&X zP?&yxKpWI)Ex2FK&|R=YDcCo>#jCl3JVqv$OggJmRsW$@$m0GD?ozU?>4ryjiGEP( z7aq3{2Vb_vU9G8gp_ZXuwH^kAyVXg*eg0^1GP+bQ!nb$y=jtg?*XW1B zS0$>?P5X*%xve&GssF=-3w>E~aZ(URdRxl=BXZhoKx!0J3O(6}Aj%gCieVL>GAr_m>Fa zm_D!)^FG6@-{j?JrvR43CdyCL86a?R<(KeA$=w2jCa(>~|G^AMj&JIqPTmRR?rW|S zpl@8&c{_7c$V$Jt)#y@Ci@ebM+fn_Ayxz`Gpe)?_Z^|rN;QaNOJ8a@+()I?)fD$jJ zK`{=9dhG?umzo7QdUlv%z1o?N|TpN(1lDN!rq z$4?gau6kE9X+eMfWR9!`?a^(mQ$D12K*v5=)Ds4;1SGY9&Qq)V0pX0)bU)#W=xjwK zUrx56NFuFf8^$;0ESpES6;oq~=|aF%#=054@)`H_miD{{?>x> z?#`9hQKFOIF{pA`q&9;m>KV3_b$j@UOskyAlJfJ|9e%{%S*XhzEgeq``YmlTK(7p< zGYr}7;EtQnQuG!V?`#&C4p|x%_SUW|^pGYIhb5m8A(eyoRnwX5LgzE8_&R~JM!n&p z%k+S2tE2}V6J0k%lgR|N0u_%RkoxR$DxI@=wu*CsNRV9RRYU_9Mj)Y8mgfZ(El{$w zm;ur@k1lBNncYAU)J(&4k>?av3+w{@qv<*evP&({zL|?kL4u7->SXQ2Fu;=ZT`>S z@h{MCcz*{{7miNK?#>QS*Lsy|5`@EKYrfnUh=L_+7n^+qwC7_*dSs9MKY$kz|L{8^ zngz1uvdpoV15&6cbxmz8ewbNEZM$cFgXLBHj>=N5_Ws05IJ~rs#-CAb+|DUYs*6`n z3z15S>t?Atc$m3ja48y2Sd11rY9D~P#W<%k!>(0;PW2}cg13b|OytsZ_5iWi6!Bok zM2Vuo^FycSmSJ~zB74s0>z?i-PdtXTO+rp!%f#(~-11_>uxZ~jDcqlI>%k4LR6NP+ z4_WB5;5^@p_K79S3hk0`Chrd{FI-e4H+W_)G@<_j_Ptf?+%pbm_($>iP+er_p)2F@ zN8ArX+Nx1}XCAzRMu^%!_@8KXiL0=rf1w{QYg}sz9|g+rb+2z3x$gCSq7oaj6YkH7 zN}R_n2rzr;GI>`5U-KHNF>pgrNjt-t}m@lhI+HKfzIz1dn zVn%16^KG#qZusPBz~s9(i>@cxj*wIn$3hdEfYoFm7qgm^v)V} z7gQ-|J~fFi0rOunPI;>*mF)S^s01IV+@yze@^Gf7b_eY&!X%3o%wTBXNw`6s2L#g( z2QC}L1O`;aFDPo|VIj*0-~uDqDr6NQ>@s-e4MM=Wlq3U-QQt41vLFK{{x!mGN5?*m zX9bK?dvLZSO@xnDsJ{AHl0=BIWsB~w4spP*h{V9d`{zP2RA!6ooK~nxN++jc1t)H2 zqgbSXQzV&B220?Npq&uGx0wgz`-81l16-P}3}A7S911sz*{G>ZM_jWb;OiU_gVi&M zRoQk-dqp7K8qIuy74>2|75NmPnO=FjL2M=to7r1BjkMKtZB#R}821H=>s(gxO>>C$OgC9|eoUKs zVUBt6w5T8>cZ9g7drnjw57WA=ti#HS^ds5}5ulMP=NY2@EMwvsclC~ELkcWnnqbvrmY8rRtgPh0 z?aJAw4m^mZVS17`H+}loOPXnMmghIf1xut5`Cv$^9Fe$!G^h!RP8a3`jX8#SPb}Vl zKzr}IKMs?+qf7YImUbA`UT*YkNu>uG@&y;JDoz@zCG18g4JTZ0a0NEg58yPRgGqCX znu$Ulz!&!oXY)L3?r#<)U0tt$6j&bw5wAK3{Nt5PEz2i|Cf+NloU46sizR!KrzQq` z?E(*F`^&K3*w`$`Wh{tc7_m|_2Cf3-nvJ%0K-aCL>yE&~QdNg5x4?&`ub@qdl4{xu zrBvCGj)i0%k}%MGIMbEHNlK~2XY!y(PyIVP5p zOm~WltOlsniR(~?V`|;vikM_9F`!GS>uE4bgbmSQd>{yz<*ntO50oV0m=xqdOHhha zU84h}W#s1J2HIzrV;RLR%#tXxb_%s9gD;ZjGIt6!CC)WXUXhXk2mfS0$(lz`Wycen z%5QXSFNZ75v*`+rzU50*bIVDVxF;5C0Y)R|qt`n|P?+g~t1D`O5tV(at4IJ1GI96= z`r{kM$qI-1stH(J$IjK+sLk}dK3pBFB7*8S0yXJSl?d6KUcbONrDJ9q;81M@L^2~Y zM?YYjv>j=zmLyePS|0!M$?l1p%$We5kyYtD2&=lt zNiJ@69Xd^ce)28X5HGRuwGwJAW}u>uOBc{U=PM_ENT#l~P6Zg{U_0j|-)86SGNI(k zx31&$Rn7vpL=j1Ycr}v}TUrB#PF~&Nu$>%8Noy+;I?l$lu+bPk_XD8<xQGMzs!-aj&fcy-|#Y^)dW;jV&&{6jDf+(*1 zAHng@<16iTn!Z?Xl&dkSn%|*(;prssw|dl_0O*H3W@uZ-At;vZZ|oxf4Dp*@ZT9~_^SRDl6W zXVE(zlaS7~(v6C3v9JltWyH#9ObAy3mnjjRCg}j<6m%O{b1>AFEcTrg66OF8??(qZ40RNC>iCF=r_MSFDuli9=47j^r@_foUW(JVEk zKegL9J`W2GSUxSJbGODFu~=vP%i(lQjGdPj%V%3wKk->6eH0uYU7=;_X4b8Ka-Kqd z@sVu01cy%(3MbIu^x)vAY7GQx%SET?cF?qBbS^KXZoK(Y9ex@?B-kG~YJ62K{q#*0 zROp?(6rOMu0C{OUkdnX9D$-4pjD{<;BeiG(KLJ@8$%o;9p?NzVCLqrIm|1JU#sU$5 zi({Ih3ghw@CrV+!uguB?RYX`~#A*&|m^@6v*%YljF_k?G8VhrJL015S+wLiG?BJt_ zF(ruDaY;|ny1tHJj&nZ2;-yO-gO`p-i~~x>u>pk+V}e4xP%pnwv4+~=aIFwPBt0Yy zj0Z^i1!A(*mG+!4Y3$X2nA4QZ>9zeg=%~IE9H$NoJ@G3xDhp(uM%%Z(pZrU3$JYEL zx(o`UU7k?HuYSRukwtg1-~wyg{sy~>t?0`omyPk*U@WX@3*)jZp6Bx%2`^aF`~}z> zsodhuv53KS!!UVZf#Nb|SZ`p;Y06Boh zA{AAdPpu7#c|cPY5s>=qKHwMTCWYrKYv19Zh zm!aOV8b2P#kjp(%+du+s=+`xJLT(K8B4@A;AMH4G%nMd@Qe~nn)5uNrl<$6!0p_xd zlR|*%_m|1G6Drj5!0llwF)VMBQTvvcP8O7*gvaZ{M6&muksIJqO9Y89p|)?6%l{e?vxPAy3VzY6qcOXzi+I4_^x*|~zpNu8lP-5$dF*DXr|Xsu!Pr-i$3a(3b6 zc8SS<)HFK|ZtfWWYg~#G;6vZBH}0%gqDtf44Rv4=LcTk43kerM2MW3yb8yH*DqbFL;y}8?53d~MQmec|5 zTg431`8!USA>r3K8L6a_<;6k2lq>ZHSF6INo<_L>qha@@CM@76h^$tSJ+*yh5wGXd+KqFVzzt;gzB!5}S z8{B^bCc2N0gNmcVSCMP$o37%)ghN7kg&t5g%T`Hvc%k- zO@7!v3i557j4LlxS_vqOL1Nz8Hu)J6h0de5=cM`*;90X``oY#JzG!#q4nuj(v3sqI zQ7Nn}%Ivkh1o+~L)>|2|8hM~ikkWDQEb7P+e}fY;)Rd*UGC_V-83k2VDe$$$jJgw& zfRDJPlm%Mq6Gf$JZ$6Qw%f61HPn7>g;l8L`fqI)mATkCk>jgSKDh_p8;7`YHLMHUUH1VEzbt5=;>v`wOg-Slh;Oiox_R#&dUC=8RTdF!TS(aoKDR2tY zC{|r`fJm{SQl7~^#G-Ar3>GXGH8TGMl3aZ;IHefpcq8P^y%cS9Eu}nofKJuNK z@X?=Adt2(}U^~okHExVFJ48kA8};Gf2X}%zl%z1hqh=*4c^PE!&(uy5uHb?DIWdNP zRU{hyBtA<1GTa_wEJFoJqrMF80xbWMkcOl1nAl1QYTm}f){%0Gud@{+c5pQXQRFW^ zcp<_^CiE)dz>t$~Qk^8N%O|RHYIvhlQ9LUNMewK!?M*t9NU_OWXkA)?9w1^T++fM# zfMk(x3dZ0mW)3$pAr6OfAqA-qRuBMX>0HS+>m)=;?o@+16S4PjSb+Y?*>k65Zl&l; zmTcW4--Griwttl%S;ikX|KzeJY$EBXC3&TptgUHWCL}zfAE^amGpG(9szLA@C5*!z zZu}jn-GL+u^X3r=uoDciHgGNaw4N~ysnvFWU52UnFwNES&Z$COU}dwh&hQg^Bo@wQ zP7;3ue!43q`hNEwS+j51x@ZlSUY#nHS|#&lY)WI{d)2xzRphe#!06=hwLlI|9gFsa z4xVLmoc*yhmuINsrXeQBAVS-aL|NK&=w=Iad>yzj9~=wIN`yTU z4UxY@)6dTo zymB}$=gMhE_V+v`Y_%(g(ks(lQ*1a9?{{)#5u?>y+3~?*DE8Tgf^UOZBX&I42;0z6 z46}6@Lnn}dqqH4l8pGsEfoL2{^sKd;DMndj^3QVdHROQU*0O;lwn1qqm7o}QhjnQ` z31QXKuiV*D~QvCZRE_-rB={b-=fUG!xUm z+Q$T-nIHj?Yu^LnZbK0*Q6RfJOweLU^+L7sC`WiBDVO6+MSf;_k%yEG(@_yW;({OHf<^4wx zqbdq#A6qP`b_>kNv$34&0<}U2NhH`Hp0FL`DQo$@FmH|@HD2VpzfPb^qe9S?_eO@wT#Q5v({YFcHJ^nzGFOEjSmJPV}#)S?Tet$%JZetOqRd z57342w-WHNtFd}$>U;Ko{K@0j14U4j`lH9VpY7o|A=VcdAv^HY=YO@;5sV{LL;t58 ze}NPI&kp(jVRQ|2<3x~wE(rQh4E^zV3~6AR^=w2%eUm$%ZEs}Dj1Ac;R$RCfn{|V^dVwE3gMH?YsnX*%OI0hWXN@zLBS+yd9n)B<^tO#YeO;ov4 zgz4XM#a=<$57++vo+d3kFH|3;s%vVD z3o%tIWIs{CA6&7Gk~JAbfmMCzSg<~;1iF=>7NJ314!h}4XHs!mwg7@G5Qhd;tJ^`A zsZ`fQmGF#qZ+u{iB)TZYv{8)KHQ!vKTqyQ%!aaO5+m=Ni%fZGG*eYmxtph(vqf(45 zm}pcng>vy3^FcSx5puRxtXvYnl2HsU_>GxciM9LqH$cGpEeNTCjA(9%JgKe*OGdAW zPtRwwV)4f;C5ELFbNwlVlYf1LG|Xar0H(;ykIg>bfCCwVok<;MDOeCj^7g+;CAs=u z@9zzVVQL+WKIf#9F_T&!aRo{4`L3tE&hk8ev?2>~YEX9FY|c=(C3hyjBdVD8_b;&25HBY1@=`_kky_t2r~Oq z>z9Q-^RrTpTyLwFLF8xo@%ZK%mJlijNWx8Q@o_prtVJDZQO{z;H*$F#v6+;_%UT|W-)bQD0R0<*l zDmUoo%>pg(1uDeJ0G0z0%Yu&DDdaJnJn*ZimDw-ES)f~?aQv)G5u2uo!_=t~a4$5+ zl)ch(fWS84&f1-XgA+e#t98UMn2>}eg3hlqHW%Jh+1F}k`Ncaz^vI>=;_}Gty2dmtZ1#p@wj9IwKS^%Z|tjD|@ zC~Zn8yw;P>S)Unh;|bGwT}p0gc(L;&F)d5CdKzvcIlt4!$pb&%Z|#Mk<|g=IU*+&& zTo;w+mt~RmuRkL>_~{&Xl&cEKg#o6K-a@l?z)gljuvn6e>B)LKpx~?Gq>yGj8Nn+= zli z%{#FSN_6^(yFPwJqdP6;^xvSp1#e9H#444%2r>RPm9! zd|cAE++Uj>Z-B~T1pn5Q>linAPoL;HWi7#OA#Gx(jOn-2vEaEb{*0^UGp~?(QAsSkPKx<~fQ3FD-aZ`{+ zz_bEx9Hu4uG!YghKT%H+gYVr6zf|}*n-XsPlva4ce#1nss9OctPd!zX9weayJ1^G{ zHf*MasxsBjA=qRtLqeJ=mJ<%`evd_v#nXqU{#oBISk1Ok)KEkq|vMc86jSg ztgJadfqVjHsc12JPC|`4Uzpqc;1zb1B18;kIWe0iYz#D)o7v@5tIb|=r7O~-kVksq zy|hkRGn-_$V3PLJtBJXpI#;2jK$Zv6Ol{#eV@xRq;v%#25s#5sBg{SQ(Tl%C($9x- zVyxg*;KY`Rc`-CKFt=qJLdvQ%JH2YwQA|Ir3TN5dvuqQTwY-MubtP79^39&)A__U6 zW60%q(z+^@6eq1B!Hg*-bH^db%r_sO6xpeD_Cg2DJY#XL3OYxG{$Q{Rp*5TW2@suM zPGdmXf1NWC;y^~7mh;6ienp4_$8Y?CAz7yL$+=-Vyu~xGR+eE-zu+P4Nd>(iXZ~!2 z?=-ijGZ`P3Sm-%%1v~3T1#n$^DFrR;es%o|bhph~?!86ONjf^&Ti1rBeV`w(bKrA! z@tvu6*G3^!&?>(p5HR0rgES8N1D8zUpD$;51uKn2s@FDYse;GLS$)-77F_L46pdv< z&V|PEVe;QJ^JQU|&OG;<<@DZ`yh?7RcfHcd>z7H2e`k8Ww=J*B#8&EWojUKo3cO9C zt?X6j_FAUfOMUM_XZ(xn-6mV6wU-Yqydp)8zB0Wur|xVqcj1INCK|mH*p`Q(O-~OJ@%`YOR*-)%;Kbwf+e4)- z1G~sW9%JBRVsHs*rH%LO3Hzdy@ZAnKUKTEJm)AfId)^uwI1Qcoh%ae zczRu#Iv=MjH0e7CD?L9Pn!8oJpE+lndn;2G{%*N(G$Y_ymZt;hgN~@nK@U?PQt`;i z$ZrFd%Y#)AG?_D;8C<^N=~N-WO>z$4v70}B#m8&;S;o3Q;6xo2;yRQ0a-U^r`bNC9 z@nYnPd)j6#*3_mPKG0;IMjR4X$SNNmvV5Sc@(u{Jm=<(6(6)FQ^alO;M5N7R0~pv~ zY4T}I3`VQ^_38~*(I-FrsM-8eU(4_*-qI1*QMCR7P$7NNM%7@NF@DCEq3rrc(m42i zYPhzo_m%6#L&h%?&ief_A*X!X^nR^9IQqsoE+)lKVzx=E7RlEZS)j(oAeTP zOtN+6?^yb88GmG`e`EZoN!5LMQT{fqs}Z+(LTt6=MSDZ_2<>nxtasFr_)IWA-B`vXJm)!XL=z9KSxjV33u9STlt>>Ih zXUZV*w?RI-fv01nmBj3!p1-8*F}NBzg)q~vcC!2$b258Hy2-gco z??vJA_6&Ju+!0Jz#@)3A8L=++s9S#;3wX33;^DEBbN4oi8h>->!2*JLD!{=}Y8rMLWujmn0Qe?Y~$ znqbz|zpy%0OKA6!+>vL`0D$F(SQX#EaOH|4%o>F>9X|+IrPiby3Bj2c$ARrrF3AM3 z6zDY28=wPpn{hv6|ENUdz9#>l1QdFj20x%`6-(Db7;e z=IMg9lJ`Ra6)YN@V)ZO6yOjrXO-1wV?wS4ZLah`v%aB8!T6d7B_*r6if_Hc;Tj!Nm z60CsnkS<((n})3)kEGct{Vxb)11UW{YE!s!c?%R2-~N9%23 ztLEFu@*;EPwO(8f(t&=x!|^z{#I%af`;BX7cYEnSdHv<} zC(|F^)3)h)C)CbZv6hKzcuZ`PsVk<{B=^WoqJ5qEN;J>h}IBbTc_HQiT%|UO<>72=!0*+zI3FXu9(*vz94=&7Qa1Ubn^ zH}MY!b^PVwI&M7q6M8t5FF`|m7)W9qY)c0I@_=v;SICvU z_#(4sz?;ZfoUFIPpoE-J^9@0CUMRT%VRvT0xP4gkbhUytA59F2_j#T{(%G5ugkeK#4byp51^H zQV5|sAF|Ws(K^14L@?5XG{0Hn80T5zEHt9bqtxp$8;-7`B~aw>#}Vyf(UVFnM)!|o z6shVGUn~+QCyq>ehF&Z!to0ex{2d^e6C z3U@{eJpy-=#1^9hoTnU`U%1utz|x~^9*RWOPh|XA+;K&@;Ka4(W&;yXrw{lC`cc1< zX0v~Y2y9d{k9_s0cn!kqjF~eI)@Avu1~4uxkJEnVZ9K!(*V-7%qiX_Va4_U zE3ft}TB0X0L({R13DnYukT@2$V|-z1gMBxzT6sfU@gS9|gD624i0$wzm4ReQ7}K32 zWZfGEj^jY)WP4=O3CotB!BcoNpa7sa8c_UcIbiSElm8XD5<6Z%S|d!AyP6nS9LpA61w zrGVu0Ff^0Aab)1^Xs;JCMXfAeCV6AxOCG^Silqi&f8e%W{J2Z3P@Kp60mmZq{R($r zJ8Rizr$#nl8gnKOEqC(i!)7sG!}moiEL_tUYT%#I51ODaGzqf{_7DoNQT&bSu*inJ zr{9&^gI4UH>wx;k-QM|cigrgW?6CTrikA0&Cc2G3Rv+S*)KUF~VZ&btq zYXY*SM@zb$c^LGdsU*;z%>oOm0ZNeo}{S1>bLmSrDXodwh~YptIMp= zJb4r%m|7SQq)XvSnDPRI+vMwq!U7~_$*#%kw1>{d&x*u#2~1GYC~v`@Id`G(@Tp%W znU(=ZgSknX7@Ai)JXVI?eQusuAxX(9LT#O=fD9fvs}z#R7hg?%Yu3>s=x1*uiqt*i z^C^v&`s*m})&vza77BhCvb1|y%;w|7AeAw^;5nkNhR%_IWswyNuT(J|oa~AEg(L97 zg@7f>=s3LT*L&M9*t$CW;k`{_8aXB=OLk~uh)=IsY>zW9x`7;g*Sz7yIMWx+zz=l3 z&f+glxcW-8hTA98x_8a4P37E%o?fTo*OdU4%dwCObRLzcE=}stH4Wt@pIJN*VjkI!?TRuzK_+Kqt|1y+N*bv??nc?y2@VWhKBe zjUdj7-CvP;cB}?Usx9|RmAo#~<0b9eNkJzNoj8&HXz@E*(%g3rK0M|;c#KWn6t^;1 z78!n7x~covQI) z7_PP~sk>jxaP}Fu<2J0Mx(@Q%5jMVCxMW;A{Smk6?{?e{y@S|~8F*;kcTu6$*V=Df z)5BK4H=FuCVCVH@!<5_`CdLy@o;RwTli;&>YN4_(`k++_T{}4^Oh41@k6EMl99T6% z5J6IJ(ZZ`+G?)UAOt3d}<)M^&?Z+msXpe24(vlQ5IaG#x#yTY;rU!12lP`-I)Vzm2 zjq`rSw~bco3G$?0AK7k2agNFM$~-5Iv?mA7AQ^>M+Kmq1sT~Js6iJfgH%c}nIpaqN zj;tr;HWhr`hnFo2(>!&mnK|xQW|B}T$&}U$|L}xov1=G?E@RwX+XK@wx6F$0fpJ%5 z2Ol0?>#bD*3=w9A@|tXC(mB(AS0p-KDR>&$&V-eA^;i!Y;R zHV`D&R%~WE@Cc0{O)^6V-`zRM^QCHO%jfzkN(XdI4zb(@vMJ)38sg%=-^OSd?_8qz z$8qfmUSNktxDro66RDOE?G=NMhy<%|GNKTraFzanWB-Qx`%tOqUgBu4QkNK|Zl#q)dLCVD^CHyqmnf(zmDz8P-I+9+J;Md<`jwZG>~RPA8^c}V^7!|L|G!tUEX!yiM@^#yReZrq?F^@RWXE6T=`9N{r`9*^IKxL(R zmeJKo7H;+}-SmS(=QH0DT_Pe-$?OX&Nz?8q8>&Ylxsi*L305fzs{jr7;pDJF^6B{T zBIc`XS<+*hqvN^5P>XP4QiXrL&_N#)N`y(yynYGmKc$L5O?{S_BP7hWq=K?8k~xwk zt>env(5C^TMwgRd0!2>3xb~oCalueKkr*kU2=>^N;mRvCMpbKPv)-@#u z{czaqV$QXRj1{!2MkStHtu=geJ}`BG=oil}e+`|Oj<3tV(nv}}aXpc*AVW5X!wKxw z1JaV-5v4|!8*;zW;bFOviCIVGf{=d%X;R9z=TO?%GP1vV{Va!DrkQ#^XnNvTgfYjr z=sNvzEt$)%wL_&yOB1O#isbNMNdQqDr`eb$kGN$$M8vodbXgi$eO=Mhia))F6U4FX z6q^Oaf=DCmBs=}foX14X%h|of14c&9JX?feKQo;8ZYl#NAL_&ciSs4+A=rBi?~25U zdF?CLeN47wNquXmNB_}B1j%&}7|w|ImHgNt^2+QNj0`2zbG!YwZ^?Bv8&fkr@|pwj)6OB!zDEud!k(9+sBsbbtzw`wwY^% zEfTc;-f^hjrFrK%`Dr&nVD~qXWg1fImyOoip^W9lmhcz-3ua>k_U4^75x^pR2Y@l7 z4F0}w8O)HLzcnb`i?Pjl+iTonCOsUP8BLgLJIy+AL@DG2> z63QiaS*+q5_)u+=t2<6IwIzk?HcWOer_a|d%N@(g&azT_Wgvr!YQqj=i_(b8hedvH zbm)5A9+2^I+&YLgYS(879+$;+#Or<;NLT*BqJ8i~?Pr+(t)Ux}Guf(iKgPQ;lz)o+ zneVzVx5xj`aQ7;$b;_%UeSN!>T;6+osiL~-PyDkT+^n-twuiu0I&dUL|71yL>kc_v zO6=coWqH^LNU&=<+Eo3|XnOPHk^)H!*LC(Z!UBZy1h}O`KeZs(B7?dtb;ZW=Z)`~# zN`uQa4QZ$zcugZEL&=bhN^t29;B~ER?-#9L$&{+>inAv5Fw@B^NU~@6k=%2hEeu6u z#>vMLcOz!5N#zxYTxTS49|yxd_0u92Gs=(;t=o{4sA$*`Nv03nrKu(oKOQU``h}zE z4q2@uIFt@Bv zE`HB?h|yzanVc4Tt;31h$io|sO-M59Ux#qjM*|A}acH*y3l@h}Cl2odkOs(s)kz9i zWA~Z)b6qr_bg-IeOmjfwy_SqGP`M+Ea=WunUrTluT4JUXzFCa43?Wf@5YLjZkq#>z z0*sH3tm;OT9uCOr+}_f2p!J5OM}n;3I9w%XALocOgI%9!nL%XTgB!FSH%wW{iFQ~a zBAFW+3oDO`nI=`#wpEV$zd+LWHop=%fLf}w^m(e2!)ua9opW(c${{3kZ0AAvMDfqC zVv#u&EHOp;7q}vPI;g;ZG^C&oP3zSO#a2h1f!5M^G~?t?+ssb7tAg87ACYhI=2&ML?&$h7FezZk_Qenx^%QODQvlp^^9KHXwMNy_T6Xg+Fxw_IjC(xAfQ6rC(GTvfi*suf|zhi)>Sr;DI&%`Z5 z%w{kCH=39!f=~0@EGcyD%PyR9Y7pj8T%f>BL)!A3E>H{G8q|Wczra!mGdvv`4+!sq zDky?H2p*L+D=Op$QgOHn0DH;8BzqKf#b}}X453Ik&nllL^>kpNpS1zCtPGIjaX6k! zq^E7lP@0bQBgSs2JMNhynME&L3_m*P)7p1u5=MOsW-_!3E^i{o}3FUScp+5xL#wqK25K+ z9owIC{|<8-8*D6Lv+@>~zpG$J`-=O&B%l3y)GLxt@-OEpg=QRZhsnH;giiQI-fJwQ z??t#5_5sDdSnOW)oyutbe;nMm;*chn3o+VAQFI!zgZ~1;!di>~y>_Q!v%S`slgNhqXT@jiz<^^{RIo4G{Q&N1lyqzi3#;>e=1$+(Z57ZGTzFl{zezIPJDgd@K}>u09?(+Usd@ zPsY?@&L)WiNv!oWBe|{jc5);>wl0%pD)ly`I`rd1Z)+rnpBAN1KF>Jkud773)wrK4 zgNY-CD$*zUpA9@{){wgrw1^HzjBOxd#Qa6-a_TUDZP8`@;qzZvl=Nq6Y~BBj!M$|N z?4Q*b_U+Hc{D^+8%d27k+T%xTW%#4;gP$r~(eO9EYdd&uJ=*cn4H-Sw26n?<`syhP z&31PIBKoi33E3j(YW@^K{cq5e`s8ZOV}cmDOG0C;e(Q&M~c1mXdVBA{6Z4x znYjX7**lL=$>p9Mt_?c8k+ne}!I^@@4#L?3gr5eSOcs5_+&}VxVXw~n1t6TRMZ$sV zkU$d-RN0*jYh8}!5&ZXB88tsJ>bGAlON&G8s9zZ-4U?TsKS~7$qPjR?UG~pX;4?#f#@R)&!AQ_+G zI7GDJ?<`0h=9z(eEBY1OD^xw4=)6{KAEf%V>WT9$Kx0L#g^2uI{31Sa!v^&>h#Dgk zH3yLg-WT)(#`T7eWHN%`!R3t6Q5x*D+gMyDL^AkvmR^t8hHGx>VmAeqhUH(=PgJi@ zo0=y{UaFEZ4XWMo`fso97-!|Yu1@(JwVq^tJRN zndBc=!-UANVi-Cm8z<>tu_qO*{RJjxo>}_QA5u&3sP1FFe{7`ZimgAd4-;X!=-&pq z_2mGrM4YdVHIVP3Tw8F%WQ>iHJ=$4_D#G)?WMlmsRh|}jp%HHd$E(GCI+m7jN+AuT zk~2@Pq&(ej1JLD0Vn+d!{lb~!NuZ%ZmhT&(&G~JwK06KaYaqOX*l>9p@eMUT@je@> zk~ujsi%6(!n2OneGodL9fYeL)%oniY<9C?Wu5{wHl+63{+P6UKA-&?(veVQ>^4_c9 z>^e;O@g`)R{ZO2wjgEe+55D$nTI<$oMHVimjKX}($=G3|Saz|h3g2Zlmys`Gjyzcj ztYLZl*uc7PCBmJdigMP80&`-HHouPcRMC^-Z6SO4*y3rt6fTw)*=Hy3Z*2IQT3$4T zXC!Uh28x$MzA7z;;HZ4?JDtrpR;fMlw3>IB77Xzxu9*7LTWU_gHQW1!k?66eRbvnx z%TzP3$Y#S%&e#KXd^~SsCoW*?v@!gq$M(;Uqk@HLetQ6=mpHdbo^VAv*B ztc$@^xPPQXcrsw*D9oeII`P9QN_AYTe5ElCIu-ebV*cD_u%F#?gM?e;aY=W<_kMuu3tm;en1pc4mr7w zQpkym(wc41xuz%n1A$>q`-=GsiPj>9dvcp7R9BHBZssw2ru}z~e4POz1V1J50q_*S zIt=r-i-1caYp@F7Nmfp`EQRQz&Mfoxg z!l$$^_*Myi!_pN1-z6Nna?rmeEBrs#XN%x=uxZC0skR)o-#+`bXI@mObK0mmZCqkf zt1lwD4R;3QR+d*^knwn0WEjf?u_~+h^zAO7E$*?O7YJzBKpQK{tu=;vFqa4IERX#f zAuU@H{b|{)ktpyf06kszU|(s_PKc$~=q7HkcpG z-UE%)y;xrnaBKv3uc?)<0$DQ`MF&}HQ3;hNgbY~q1Lmny%}i#Zt)U6tS-t5}?2vt?xs%_j?$Kp{F2U~%-Jqn{u-Du=!=D%_P*QRNn|Iua~V z4<4igLd-l2=TTEC({xi)1qmktr{%(`ek=Vi0L^l<3IZ2Er2_nGhGxIlu`Gc@1+-_f z!4TEGW?pNT!n~MWXqHs!*^gy9QNHXNUk=H^=K|_nE8EoAC~=(GBy1E7UwPYQ^LDC* zoO)}U%=5#4L?hlkcd)=r*9=v1SqY)T@Y&*+wU(QiI{TlBI1&dQSDal6ZV}Kji)FEL z)M>WwkL_Wf;~(ZYnXVOia2f+^zE=G;E+xPJSmEd(;IC&FE_>lAt@**B{uNGg`o()k zanik2R&?ujC|vBvQ6ueUcG3;bF@t!Fipi{&oE~tK4abq=!1AqmT{CMe6|omNWeNg~ zocA3U{d_g{LUz!4&uK2(%4QB$XbXY@AdBniDi&P{jHDQen_+(BwhyIi+)p3{$hU=%vE-($baXlvKLTQ~sx0;W=Uhq~w2YyizWCf8ABIHVUq5l# zt=UUTtAuYC@60c)p9aTxdsMW4XP{g0SQSd?jnmeRPZhPDMraH!z=yc{%1nR08ge`y zRbX%e51c%$_}4gB=$)!7`)B;_(YFjzuWW=l{lsC_OFuZX`KEEO;Qu@LaUBV;TGOd_ zCzE2MFmd=>lxzOr%71R7RY}*`CHI;qMHhIUtWG+@b@K}O0-bN&%$BrV-`=i!Bh~MG z3qrpELVN9g8^!7s0y5S@6?+&>QnPlFF=Q$W8bxZYAcn zyTi=-7Ym_un9o5oV>mcz7_)<>$^h3JryI`eBxgSTasWDaWD1t;QrG2o;f~PkP@oE_^2vME2-NsyKvcddJ%JgTa8) z^2V$@zGW??<5F>iJ2svSobSMRQlSTne*h4OPPse*_i8h`i0~bO82skwGF_Gd*H)@4 zoA_{?bZee<^u|oj3>EveOu(^TOI;ja3t1hOEtgF|hQ7RgM^uzV&!W6^*DNnh6(T|< z)eB4FYIn(_U31d%<>#(uN`V7vCLTYA;su2vh)g`@%$>&2k)OQV;7342JbV@bx7fOuzc1hHkSe&O?Vr}-uF1Ly1RViFdIPgwZ`hf>+lslo9q98K^> zK5%~%o!H{r6%D4faP4%0Jmb-Xv;^G7PdM_(UF?Dh#E8F@tkSR+eDytSZyP8#ED2>u zltU)oK`}Cf>MIr#|6ji!l>E#HDN|BIgyXp)5KWh9p%#T+Xsem)4`pABiGqFLNyL+e zJ0}8)EoBY2>8M}}PFZKrv>5!sa}4BDkslV(D-Q@WjnFS2)11Il%@#7&y~-UqBlETl ztVx3Yv?NR}V7a->F6U)YOCqM+P8@v3vJGVX)8KL7b$kXp57kK_eI72 zgp12J@3@sfDDxPLlbg=>^MB6|PTqOo6 z_{ab-_;x6Y1zH5uZf5Yv$jV2S^1;PsTbUose48o9tes^0kxWZ)7RO&6J-30&)c5~T zlT9sg^B=q0$?k5Gi;9p#wU4{dnJIBi-Gq3C>M|SPJeLC%{q}{3&JD1-7+N_>TzzbU z&Ke0vjI2{+VuGZlFg4!!pX|iME1r<8w}r+U-C4q1^ak1CdW=XK57lkzddvMEPkeemk3hu4<`>j@n|r~`ap7w ztSEos#I$jCzn6e!VPk-XXp@Ttf>%rznMwt^N0BJZ?)H94^Q|&Mgkr$$Z}uZuu)p?t z9>onCY1pNnFUbWvC;u~Tch;eZCbO#IYjQf?D#nFoG%7d!QI&Y;R3(U%YZ6>AOGd%{ zw$U&+n*#sEHb0)RbzEO$^awd3Q9&Z_CKa*>O7wa)=^aI_8De~a>luOeAxoGY(0SV+ zyVg{*-qi4FlyE7j_~!awMO!c=K5N~OuEW8BKTu$tESj@}BBCzsK^V2@Wk??#_TfUE zGiF-)GJN}x%csRz691VR9=fI;C!}?j&t;*l9fqFIpqgXZEx;mTQoAapGrujLF3xL$ zGD*6YZ;u^a_DSo;iGC%8k|1~gHrVnf!&9o#r1a`<3U@Mz@-&nSr(BWtcp173KMtK* z5}%{9;o?N7`RRmMPgoE#Pgwjn4Anw6o7k(`4hhVI=Ar#yzcN6n8`W&<# z!l@wME=OvI6-07^(!KD#2MY+j2uacRlFUhj5~+gtr9LUj4aLA&uyL9ihdIi`E0%`@ zYv86|j+~A`GG!r(E!9~9;Nc@)|A0HC&CmOY_xRDF#V-Jy=yjO1Wg zD%dux44dDHgi|6;!*zA=6ikHJxD3J;Km<}aSP&e@+J3%#&c6|S998z>SS4W!p*Lib zB__0gA&2E$THQ{vwV9;WQmeTH&IzE&2S{G2)Qkd>RIYuXXlFCn>cH0;I)Am)rGnfdJ<)J(8oJ9^_B9&5R3hI(C+!cP|iLzob^=1_t z2`}qdlCE%YY#POVOl_)zi#|N3grhuLv6lLq2o?GP)2j=n(*A~iGH{{5xxg!uIEHg& za3*#WvUEm?!qdFoJOmU?X9nMq)93^aYPd799`A|vyk8}JEk!UoT|tDUmUKxfRoldOS4aTuR?L%LNTTaxgJC3N3{%kzGJaBSy zG6=I2`9_m9pS}x+Xph1z;e&Bi0^cdNiR=0rE+@)(yj+~pm&M82@bqzvr^RuW&&efQ z5&PSRTD~l)Ugf-esxO}ghyU_;bp@c}e#SUv<^1gO!$UgIbGck%9I|r+Kg$@yqjD=m z>JH%^eH#d^A*D1o$s!!Xjhi`^a*Vs%;as@FC=CKyTb6FuDq6hN*x9%q&1{7328!Z$Z`W|UYS5K zu_g`-1e3)c_;~o_?;7oE7c0p}-cBn z9Xcd3tDxn^3i{^Qv2@X9-xY_Y#x5|3>C-*Jp4{T$7){PrMv07cO3J?g3K<3(U%<%` z(U5$0kiB+AQ@6DbKiLp~Kpt(ZWio+h%I8wlZGA3N^`ub!L`jm|LK>vtAP(?j1rN48 z@al=rKG^*jic>sJXPS6qbI*M1!qdJjn<268=n;d0y?-^;oaar^E8?Ug8M3sN&Kk+9 z@OAAQ5389z6Xd`x;d969^QMVFvF}0F7h21ou%nMlv%M|5$GK&#da-F0Xq8YR?%;tt z9m5mHUXWZ!w*d7wA8Vt;s#2uYsHeUdzTB3TRX=_}vNgQK;LFT4(`CvMR8lApTFOSv zO5y0h&5dm=2#!f76w?l9lCd@u4iB$8ni#Avuw26|elRL==Ngr7ms%D0G?1K|@;3bR_y>dQt!rHJh!e;O=iW1`%v=qP+hB!l$i-4{CYQk$8Q5_j z24QW;JyFT|ifo4zCawf;ys~}c4(*ojz@(Y-h5qAVC}f+vpC)3OkynQ}Ig8}uL{$D?9%IsmaWcCr9O&QKVd)+5ug-b6aL zJSlVo3gwEHH_?xWf}oF`Gw#3En7{1!Zhy@!1sj*WIy`2l2W0(+1wN# zj@yv%Y01pf5hrC54jQrhh-T3d>?y&9>#3IYV;UHnNG?5N!mVnM34+%haYAGB(3zX* z2Q7PpAeEUyc<%d>`=apl1nWIWkGyJV76q?^M|_b=@{Pv{J^u74&m9KS2>N<9nRGJu zsa4!6oTB@J?Q}d_#4D0l`$*B3tt#}T1ptj1!d;?J{G+$P4&W|nQKp29lRXa)Qd-e!0&MQS zh1<^htVrVqPO~l)2}HX0;R57o!>8?FCihb<3A-^UlmMrv1Ny-C*S4zIhO#&jVyE59 zrz|pf_ORgLppJfteHf~o%6v)1P#0@UmEosH3@34Nq$g-Id&Q9PSqCl}KMRxv^OCA` zgvnsME3fztz6@1al#bAbqaPiqScv0a;<@ti>QY#dsw$It3t>nfe&b2M3X_9})Lyv5 zeBxL>=ynLcJ|UCO8}`HI2~*UFACZD(sjFGJRST|b<$4_SEspI*Tj)<{-=BO2p6nXk z`%C|vOevl;AF8rSA)p_tc{}Xy7bK%pDNJdubZ*r?fR7Vm`ARU>ztwC6JLoH>% zU}*<%9U-{yy{g5RLqHc>S7g7ltqx#Mwu?eBF^UCauhkTn&Dww6;mj7!gO@U{`h&YU$6@aR`yB*0Co zGzh~V4%$fGOK-{xPp|ZOPu#Hxds@^a1!TM}!E;%cn!;gmqNK_NnekT^LXF3zCiG!(damZ_6AS%KDtBAD zE5xa6#Z=ap%re=yvTBo(CHyljVQ&k8{)Yz1X+KcmM6Xhw@JSXrU+$Df?i#KTIbLF8N zFx945hFlmd9=8Ql`zjVL@7^zuq$DA&ZyBza=+(`RLG~KJcQ=pWh5M4kt!3OsH^O=c zmT+xJuGzsA6I{m!{DmrW!{Ym%p#zTZ%tus8(6}uQ<`6#2%~{P9$ga4kU^hrhDT2r~ zv~mbh73%_(g1G@hL=t}wrcPehqOtTyl8T`FQOhn6HH~;$n#lSwB3R4fJ(Sn^B z*OW0aWa7!`(EJO>8u~x)88H7jAD~Tj&&XR<(uXMJP6E|7{F_JLLzM*r~c|FX)A#8O2KH^39@(=$Z`HeO~N>Qi2vd7QnhHPjyhFl%{PV zMhlnnEHxP^WF$1UtlgKMh76-w_GU*7VCw+ND?r&R&t~7@ZDeEjRdM3x>QHAQ_DcCs z9694#QxozG2Y3BhHnE-rw-OHmAq=E)Hs~S^>aMOETN}bt+dS>1&auf~O>7DfZD8yJ3Pa@tJ~_=uD_}!=nPIrYAb$o|(%n z#VDwzzJ#b93vL=PHwv~aUU*u>F(s6KHjGQBGgPmp;mtuaWx5^Q z2AdzamJaa1lB7i@{N}c( zdXM+cNau$+Z#FkdTJ#&b1u{k0 zUIE=CQ@K!+fy;`QFq3SFXhbMz6vBy#P*nMrIOft2Fq(Xrkx0T(@Nc>D$h!5I7oP%&I zdr=B$oRslRD|+tU3|{g)PEc!lNA)= zs>BPdkBUqyfRu98JRnMA878+rBej)UbRLr|Sl2Hb*e7?ClP>EIG~64lKMK5y5Mwau z)RXm6W)q}>J^^Wtj=#V@mwj6wd842*Kf?XdV1Q2#)0{T-(H|*^o075r1%r8TkWVL2 zLh?$s5z+|^7X6Cw2SYdYmTKWo`&Q{YzFFDLEApnRUHR&49iMn#4`lzUEVO4^t?dz* zO=!?n*I$(zw*mHM+p|?QVoWc3(Alr1ezQ&B2fOqiFy!QUH2~?5d13v8eeM4shdy4a z>2LA#@Wf%dG_*Mzzo2e8A_{F52PLtS+Y7y^IOnxXW1DX(s$!D)0tPG3pU5}#V7ZYw zt?F(wZP9ZJylpATdwM+K`o5|FLs!N~A$*emLG?W^jB*_GJ*YZF0da>aN88nN7WdhN)yw0 z6!jC$@%Dg^SQ$-4elCuxm}1sV)Cq=*9~~ZA5%KXZ+zc?{J2Fj1m8A9+-iLd#Y|KdX z;f&f;gb1T0yJm!eSTQw;ccOL^G!&{swssAL889Rd^?uFD-F$gmpB z{R}v$uCoAj0KOuDmPz==N-wCj4yP3|B~@Su3ieVR-JTpFBc)t5jWbGk4mx?2{R8wN z`#^c?H_Hj?i@*=t4>(KT!{xQkUy3D0bRS{!BF7JOx&>OYDV}-`tSfaITv+~3e17Js zVf%u^7!V-q3HLS-p)o!@{>flo^VFCk1q=QVnv(7CmD8k^+cyuaH?DNs@fY~)(hvT& z$5-H`vrVu9!#2_Ma00*546WUqIJj?u+lMFln7)Z@TkvcuXD4;|gpP62-5>5Gm(%ctFl&@f(?VD*Ii4m|# z@nK|=5{rF-!lsbC(EXrmxNb3VGa|EPw)!ME)zk&w8*%<1WD%vPDWf8(J?qTQT5|Lx z>*hRchi~4@NX*XcjO7|(vc@ytO!AF2aTpN9shw$yk2$0=hK2*FjEcsr3J=@z&{v%> zN*Eb!f_$QpMEG!VjR2pjBWGed5?yn1iX%wD#?lUQgN<)aEwaf+By<`>FgvrM#K_D1 zmu{vaZDYcBg<7I;n$S@} zlVEnmq$i}j9`()GR4$y>SQ;+Xo#K;?eLU8-O&FR=QRRI0*ZG8(N(Og9c#w%0Pb znoOHqe=y#h7`>v&;w$RovfOIbjZ-n9nDn6WY(3%`;ScDB zf^OSOKCt1U(e!SZ6FSm2%whcse?h~0p;M&qE?UKCgXTSU3orR=79lS8emZ}E1bBg{ zri8Sk71P^K%&cwpp)GWIeu1J%Igpw?C4%hziMlbj^L@U^41Wd)-SZ+qrfLrX+3*sh z4pHx#WzgW5oMqZs?!fsrvCWLI0419>q%cKe$UI-HoI9g=YULtaLej*H=_?G&G!&B@ zroJQxiZ|be45r`*8mmlu7YZypRQDJG>sx<41~m6F6Nx^?(6AHdlv_(km?wx_!xMB4 zS?mKLL7yhW>XsG=HA}PV3>U>;%cj!T_JQ;`6zAGN{wqTndoUrWT)Qjbbz`XL@v>3)Y z88kvEF{f2UH-{4L!zrQ~>DmrlICSIW3<$&+T3m6aI3IHf=?@Xi2Z~4CQbNZU^$Y`( zvfrO@w}c~JGOmQKBZ;U{GcXy3eZfj1zHmtJD#AH08yII+nGsSIVX7_RhRK8sQ8;WQ zWP@LDuqOV34LQAd*x@Rp^i;1EjXs%%B(~9&ao**^;Gb~Uz}B>hU$9+I>JIC!u>b}= z*L;@0pd2v6#ld{C>Rtg{U~uXN*?kcSSX6E=ax)!)3q+A&Ow4TM48vd8iLOJnq}Cbr zQUu-W4{W5DSIQfl9aAYfP^~dVn&hc5ZeR?fP6_SCg~~`>!&U4z?sOsCTR3bZ02i+& zon}`2vXJ9f!zlgq_|;$xrPEROMn^<-~5K0+uz{h@~$ka3-^KRXUd?n zIY*zoYoe%!@9WfjKJc*R@9lJ9&&8EIjcKp0lH81ucmf7k#Vd_PH8nXD^$yLDmXdSu zz`obMuz72KgEwFsWzpH(n>!JC@|*cnHG6OF&4YTIUdT!5pU_4l_fp$0Y6E+F-xy(u z<93v0%7F+wo(o-K1HH9cRQ#Gcy__PZx+lVCY7k9g^5-}aE&&)!jCHR;M-gjR%ou!H z<;ou~PJ`Ixb1dUT>-b!v6-vm-EMLg$Q9Y=Q6S`VUh)ma?=yVrNGf*OPLw%qwQPyb7 zn04jN97!TZb8b4ZWu!|5$)S1FU1oIxEH*t5KNvdA04-FqTEs9Tyd)i}NuyaZCfeo; zGBGqijOTgVBcELz4uLcM0<1`7+bO)_G2SGj4kaWVtH5nCi%*kt-A5K3opM?GkM%=h z25)>^nuHg!UT0(NQV+et>z6|BeB+9Y5-N|!>1awKf-Fny96!R&cZ=^jbLod7YdZr< zlNvM082%Azp`h1Hsb*$kgh*N(4!?VurKdxWOZrsMw4=qjOW3IZMZhM)S;(@o3kPuj z;ZCT|1-&P-jr0mCnhZdN>3XnjG0ztDLCM4NUL<{i@LgQ7P<%+6YX zexl@#LCQlW&)`ATw=U`fta7WV8{VUF!x)ocuIT5RX-8+j!Gxory$xG1bC&r&d8iG7 zUQrc>M;SAfVJ6+`;u>vrOQ!U0WvJ0}kDAL$UbPUE031z8tQLFmj`PKPV>Pt=h#Ve$ zbc}EfW1hteTXx+pBEY*o)}Yzwh8W-82COL%Qk*%hC-PsTQxpnE5~;-|dfFT5R?q^>;w8JBVNlhnBU37`HX{cTJS z#)I=1p4b<>{HONw_|ObHvB@Nr$p)nhogH|i$=iUroOsMZjdr8kzL7;xXHv+-SU1Qf zfBIX>1L~h>eNLl*=7_>FB$<@xcIWaT5CR$uPDlO0gv6TrH;f`49=;?{HGl9ELbfb( z4a?;I33|@wywiDF1&Onz3gmLDxr+y2btO+15!#eqOO4q1nCr;Q50GFOxcOxgCl5C~ z*)^T5)3B%wjAH!ACt|?Uu7S0rJ)oFN7R3Y4(MsT{3KC>>$$&li1Ved)C?!xq3PWW< z)1pNMCmogvR36jNgvK?{WDz)6h)`^$%z7FaKa+$8(Qo4x+4?N2oVq_iGvUx47oz^% z+WfZSCP0hEdkaX4o|F)fZCTK+bu8~S(nHthFP9AfnLm%n(a40MiHN&&LaSpMocx;t z4o~y(ru8~Aop~nghB$X7NHNW~siDIZTfv<*k=ag^Vh>O1`1ka6fjNV$7~DJOambwG zMuaR>4r)MAD2>;9xk1S>;{!chd9iaE-DHyI2^&3^vN4;IYSA+vTo;b@D@^d4Y$fLY z1Dyh+iZDeFg*-a!#us#RgSBjekWZ(_~^@g3~<)}CIgVn93W*d{6(2`OgYZo8f=GXV}NvRq+YeqP_OgMoz zI{ATW)<&@9jyHH)g2J#5SyJ&i$?}`f;6qj?DP->$o3s2xZMfN4LXxfcPdempG9UO; z!JGeg_>@b=n_ZJU`TPNItF7&!=QUtxE=T=7g-tA^y3Z*O`jf5F3)V?^BjRYvpci*g z9B!0sOTPpP#$SwG%iO_x^#Mh# z7fN~~#w*zfK$G)snKkm2IVCrjFlEfda9r>qZH zX&_>g|HO8)_CbmjSToL(SopHJBU#vZVN;47L3^EEU~<#tZ@|b`Gd|%b9_j@nZ-xtE z;|mWlYA?hss+y@XA)fbEtv=r)D27$iY^MoeW+*4!| z-e}(j_%9mDydz#H=^M{XCY-5l?2`l;t=gsg3mPfOmP6GfOMmCFGPQ|uRGB<6HiMd% zbkH_Nm$)W=tab}~^O>&utiFwmY{zu%> zs{dqTVqcQK@n-VqZ&DxlNtP0(SY&vwoN-iIqttK~4)>hB&lV^XwVUmsFvbUb$S09x z-KWRR*nrkKojF--zOP_k!FooTDpwP?r1K3V%PaqJpOW=yr^wOWe`_jv(xm@oJ(A)-)8X@;A=)oIYX7Dnt z_k&5KwDWc+Gkx~G5i{v79vPfD=C7Xq^ zK50o(5Q2jSMQE4tnspQzbPTu7f3A&l*~@Kj@iZ}=CwV(>xsa^V5ay8d>(i8Kl~*#x zozYm5?WGfK2WFCQF7;v&%6m~L=yuoE;v@aeka-)Dne!&2>zP)EBN#HMQ*L@kmaWP> zU(TqS$(@7G95e=pz=Plpvw_`d@2pr3IZ7EoD8A9)zoDkuAXR{S^5q1PUc7mokZM-k zI>l@)H!<`w%Lokn#7&SVGY&i7y z8+W3T4!$G&+=~wUvQA*AB^i4(Ztz&0JcRn4t-wH&NEr||2f-#;!IPEd#UM)0L`)UX z8iuzG9`YE8&`}4XjcZ`1T>b%jGJAZ#VkaD$6ZnMBTnWj&lPCS!f5becn0*p!B65( zhU)w??ioy(Db!FCnx}}C47{i6Z{e2kVy8^^H^O)A#`8&_F(+hQWAsRVfr+sB;fJQ* z3tG+8zy9scsNSHyq_<$>lFWy@3q~J4KRTq`F5;E9ga*yAflT;S36DU*aLV@Y9;PMT? z6?5c~v@~X@R<{BT2dJBg7)P&)=j;+96a^>%K0;LU10?(#%!+&josQcLM&C5yksIQL z4E_mGq&y~$xF-U_(wBJ38iz$+!M8-z}le1*_1KI;iI_5^9*x)6O50Z7P5 zjBgpHgoULIoQxKGElm#C!27m zSnhh_F|9UJ+v5u{-eFwR)))Y_d~0PkJtbaT60^g*NnGJ;?Xh6%*k}IW*byk6Km8gf z7S#;z0fhtfch?4q6zAbwqJAF0?73fiM@Q58CxX zH?*v=ZG<6tBgblanGxg41Hw5ZC+_0` z`@i#tr8jEX<*2K{nl<*4ymBKgoHeQLU{%`Be}NO^H`w-hVGJzSX>$ko=Nu zy6K<`vg09ogP}i|a}ZJ*Ugd%za3!Qun3pLCX+T94WhyzIsO= zM=OrTpvd9ctXdl;3lEsA2ZV&*FJPQBCzWU?l5L6$e7=O3zM-a_6o$o&Y47ECLHsN2 zA&g&E2@rGi4X1(`kr#!Sm|mokrM%1{ygoov2ZWMZSLVdxdfLbG%etl48wP2aSE1Lk z{h9bQDIJ!8%ZZ6VlAa7$k&5+p_H$K|g)Z-2*ptztqDOn?L)h%o-ic@2dKgur=>Qso zo^Vnq#HpvjS$CmZ6=?spfZYPDO#ZgD;&nn zY>M=6|J#fF=b$sb*x9<3AAkh5R{d9P^7JkUGPonAVKU=8p z6v5#*KJ4D6q$XJ~=%BWTG>I-|140856pB+P+zwIpNj7z|3#7-cz#D7FBdHGnsp{ z@Z&b0F8YRIo(Bw_7~cFZK-=4+gu(v;wBoBjC~jLO8qdKK#d<(G#Nfu4pZ>9T<*h$M z;2|sjscdGx>;jTSKO@O7D0^{GxPzuXf40iiQmf#94%>W(s(7ebl1)+O%6_5-FR9|x z0=axrH`t&SQLBi_1`J1+f}S?ZF!}|y18xOqnjbEp#9T6r!3CpV+pEwV#WT$$7q0$U zsRz~HD8s1!gqaZWHR^*xRvAOT8n|H1nKLLoLy=sVXMBR?yqKir`$c|T=1>ITJO8#V9j}4JnfJ%kL%q@-5^rm-e zLm=km)eFH<*SqHeDZ{S1tR|p--3sUjfU@~0{R5bUe9_`_*6nu_X_8Zg3{ywh#7#_x z7D~VxEd7~>4^9ENy5ZH}!;b)S`d9yPjn8uyWLBuJ7X*pPb|yy1HXGiv$uxB~98wC& zka@$MV;nZxRjyPC|Az(I*0$*sR;{~@_V4xvkmQqA;AOs0*Uw)#QRNc3e>jE+(OG8UDKBF4GwAHn?EHG5k@tV z8FTV@!}j$DY}d`2=ja(>TOqG1J5%{bWMS}6kt7SOUMBsLr$1+M((|Pb55oh_sSUV= z5F$H0Qo~FRfqsIjVM-1S{14IG~ zWy(53s8*KHAgx)R9NJY&tCE4b+Obqh&vZp;>qK>3EIT!HP4w^;(cQHv8`338rK>i} z_;tAxIdwoYGruL(-Da?vu``y_Rh!hg!L8K`sw&T z(}X~+qo;TdYr!h1~sM31&0u9xv*A&d>sOVT18>5%bgu8 zV6$sH_E?_zGg|?yU(n+j0h=O^Yy(tY&Ns)y7%)_W8QwmXL!Ub(hn`Kv=HjL#+0*<~;-a6U0i zddaFlp+$Qt4Biad6iA_bLdpcB#45=8BsQN(rV&i?buvY41*)4*?i>%t6X84bgO=W9 z4?cWbiLG=cG!U`rDBklwg3;AGp z5%|HFBjwLE;tfQRT5y*|X{6i|$pzsTq&bR=_QFcDLc!vkF@FH!qg@XS zXwz6}!s$RTD=O`*TSNM=VQ-XXP|3iZa(Vd8g{`R+fjO zkJ?-u>#p{-Fc`_22v&knq4QiBGk1|WNDa*WssJkV&Tb83?4y&_;sjDWd}p+{h~#bo za`m|P(+uKdSi`wLfs;8Ylx*H`qTn82H2DvZdV2~(Et4JuB{lp7Rq}a2aZ17J(!0VW zT(riYprRNJvMeGoSDT3|E*IE^7#ZvY#>~$o>%p24c7U%SMn`((oqcJM+&0u0!%!uS zfxL=NX!aKn$R?W~j47UQVx2G@Yw-Y{jiB0c<50|kz2oZ#WEzKsIY>)mRsjQTJnmQs zive9BZj2vLtYyI~^{}uACAZ8WoU6)jNO>F>C(Ur0%0EEnH#IdbAzr>&T##`F>UK3W zWyEn4FdLA4&19N^(tJ+H7s~8|6_x2{CL<+;Jds3^oUN&NjRY}TFOc11p6+4Pz23io z->ElBtyU8Sr;{6VUnO}EnrIAm<{M15WLEMiw%9?ZzKd#=Q;u<<^xJ|Dl^oj-eu9}$ z0YiY!6SDDj1l0=KH~Q(PL{uno#$xIAAZ%ww!+ynZ58>f?a-niM^fd_;A@|$*k~5Bn zh*LusYFIe(6oZr14QO2;t!R(T$T=RSLRKcw6|ap+9Nut>Vrd+b9DhGj=Awknv!+uI zDmbJoVy+TZ5UN4RgA);olc_XwshA(AS_c|NeZw+X?(SvkX($B%@y?&nGSB^mkS9jJ z&CHGjsFz(Z)8VvEgJO$|T>MiZ2O$Fzv1VNnj;D6=?bj?DHv)Lava)9SEDMjlKHL9z zjVd!&RYI;d6HOy*&oUsDP^ISIzC+ zP}!3VZk-OW>s@*pX`O7YPK`iz#4BbRA><4A(_$@VHxvZRgIc#~A7K{5B~`e#4>b|B z-Vj`!uy7qlSk|Dp9cE>#C0Msy^}3iw#kv#Z&iqX339tDv$;KInc?-)OaEEDT!TID* zFGHEMN1}l~Fm+B8jb$8+=V_xbBbZRb8Mb(A`;;tW^1;4P?&c}fd7(taKmhjA%yz%K zn8+ojV%Wn!z6T;l32LfZ&BZ?;!b&JhYasd%p}lT~Z>ZUpg~OdhmerV4bA#Tzk)irV zt|-pf^?D#!Ck^KPGBF+0oW99zl<{g3=92D`}OA` zm-icMrha%0$s{Ai+Zt|)9)G%%7BT3Q|f z<5TOrb~yhFqN>2h4@7geKZumE&OAmg@E_h{Xl57$tWkonV;Ri^Lr86#7~2dsowPlh zVVgOH*#>k*Evd*mKs0Q@#6#0`bx5Du3av>&b4@=M$!VRYA~FQ3s7)ZB8N-G$d`|(b zGKJE%zcz6Wq&de->a&;0hQeTPIPP@m1@WOwS{EyM5EH1q;j2AS^=cpGd7l zoVLh{n?S77QvgsTm|5ejbjFnE%yr47%PNHLlDg3t@-9#Gku;&kG8~#_W&q}Gxu`Y+ z!-;fIQ)I;)r{>Rk8R)T?&4VY>`z~Z7p`9qs+j-{Dn|^IXa)4saXe1?AaH|UW2u<(rx9Q| zaI8+)2Q0UMg0WP2CxPAlIYZ5E-3WV?*EHl{188M(v6xo4Sl4sGjphA5^99nCr z*Ac9j3@p!*M?zQ;S4ch(^L!UJuJzE;U zl4D8wG2#<dNKEV)Km0$$6*nv3H$oQ{aBa#8HMshO%}yiICl*+Q*;F z<6(7loM1M~14eAEwr_|a(ZHn$ryR%~i-umh1kH~K3SF22};cN-o;#V%b#=#nSA?2 z(_2*m`yr+mi3ZA*jcNSZ&<7)EH$6mo^{6xHn`BN6Ax99+EPh}yn-BI<5*vwe{06G3 zA+BZWFcQClCF9`CGH%S4u+NR%2}KOX%`Yw_ar1yaeIW!3b6hi#XD?KN0>fsbXnF&s z)7q^F2H&P&D7K-^QfkPuCV*MyVq(~-MsoJeVBYIVCfHCVZ!VL85POZeY;wZGi#KZP z3ZWN-;Ev>!a(!T^n_vZ{dz(-h4r-9 z?+|{I{M4ruJ+;ax#u3s7q@$W%OfMAbX`d`iM?B9i=~p%JnAmZV#0sXn2#bO8$lMUw zU}EUd`vGFhh)ISiu^u36(@eHU0Dj<+lQA_7r33|=B|vSY@Ubv1ujEPL*qcIOuLLDs zqj8~Ebq_u!tu=8)ms>A2rkG5KgPm7Lt_ZllmJURTxj@M(EN<&0$c3Fk|)s26SKq3aT?1Knaq4kZA;QKTa3Alu@@ovgrQGqZ&hD9g~q@LNYAOCAg+j4w2H<6=2~BY5(aZ^*e|^ml*NdyHp&N(1LTeTgxFo(C#rNn7M?Tn}Inucn)Mm&BgG;LM^O;5XtvX zX!$$c5*++-FdmFb?MuVtCpyenLuuxEHaomVN(FURo4dK-#^5$I z*SaE9jnOJVApjD&4p2wSJP5pb5Y5mF{$bRB*QostkS~$;vCd3|4=5CM`|LxC<4**! zbhCLiKAwy%_(Xdld}2(O$R>jq(fDz~?beO7e?@$?;J@)J#*TxjqdasHsW1{UcMYJ^ z*mwQV6?9qc%5%kaGDo$#58YWVwTv^1lzCvRU3krsFO&^12P5r(_rQbGrGVZt>B5u< zVS-%Q9rECX&%^E9gxrH0Fg0B!F~n6OGgJT?=mm_)JR6x<*OF;R9~Vt9T2)0SYjY!O zH6qRE>aev@O1?-qe`?FhTq!F-W~EW0;+XVOfC~~GAM@!dMK!OBIwA3!;7Y(Fp%MR> zRc08Hwzpa2%YeR8)Ud8FVq;`CGHWz~#q6TU-H3z4**U$42lEkKBoLjl#9WwV43_}2 zih%J82<9RtX_E&lfXc%YGkGvLMpa^XVHY#cFc6T6GbxEZPy=YB7fkwHM_2SsoQo~X zjj4QOnP}vOrPi5gDQyuuXcmjaC7>I~6d_98o*S%4s!%LfBC(i&i|CE;nAviuY$Xtu z$WvA44ci8Qri-{p-HuLb#y}1Z-X%RqKslx43gChZyib6<>OTz5@f$D-!j{m$5$dt4 z7nIh=dTJ+=$p*V)0kewKlP}By6TR+H-E=6#Fan4RQNv^4FP~iK_Cs7I8;V+{}Fj8Esj(@;ADuhant5$3uu&inP6>WSm5<$Es`FN zn3S%nz7)!j-Yi4+Wo7mTq>M~@buueZ@Y-f|5y?z3GN6`>Apx=EC`#2C zx$;%L3v*wH5eqcNGuLwrY+fSq1)HEQ-#v#IA+x) z@o*f4u|+MX3N5VKpB3R;Q9%B9G(wj*=j1F#L6=|ViZsxKYcf$(6fHwlwAsWdlU(fJ zj~T`i%Yh3p7uz)%OXVE0c7xWrJv})@u`Wf@gQ)3bubQng;N&+(cRCmLEF%^Sphm!Q zRtJGOXo&zY@$$<3LdazhB4BzDa{oa3AYUBzToO?-LGVEub3$aHB{Z0XTjl|3{y7wD^0>li$s`~*X;V;<}eZC0FN8p6E0cdP01-Nb?JhI-vOtVIV z0qH$aGN|-hta(7?3m^V4^}7i0l*)}$|CSpC4mH8;8?OIYsv4~i( z2}Mv6h?P#?W>xCiu2owzK!hV^Zc~J1Btd1ydQ@Cz)$ZNNNDo|HZq5Wcqm7y==9;CW zDWn59-MO8m>QFbXX!GR>Jss>4PO%KvE;&wQTs1SCFs24G0O%t#cq)U zS07A2;oitLLt{!l-7|oeDE?Co9?e_j!Val?;XAT#*cNb`#HB$*H8;|%@ zNM(3x_L`MB=fc<4&Qzq`fI>OgbJM4VwPYp9fMk0&yGj5X)#Zc@@MAfNhf$f$P|fxG zTr6(zI5t1hcl6Vsi9rpR@6Q={u^%%GHC}hPzOm7(WH>y>j9y6yQVb8NkBoxHPdn_J zs0MK#bi4+M#od^HqpEYam-o;7YfdLKw~{(n^`mHvbU|^vUtQdRkvh)-WESEMvd3tX-70S zj1Sa?H&!>y-AG<1Ol;(pLA$?O2v44qGq<{o-=8ch&%eU5Wqb*p;QqM+JQ14sWje7x zEq^j>`EVYs<6&w)4b9@NZ#;e&7N?MW{RqJs$H3Tsd#B55D}K``o}=R)UD}fc7WJQ~ zvqKTb>52iRhsKxcr)f`Tz3#2j!l{g2&eKZxJgcQT6VIEgB~=N{hAR1 zaHR6bkC5#{YKZ*9qmvnI#04r^=Yv`|)nC-)MFEq;Qcb@Z#5|~JW{&p*2Gt#%5=Tto zgC%2%3E2IG8tBx>vq>)Vuky8BThQUrjTWi+It#^wIv7FbU;<#cfHo>X4f@b{n%G*P z&no##a2b!tPt@S=oH?@(Il0(G`1x~f=4eb_$c3AsczQykSAJY66ut#)`OPuq^e}}xFaqfTFJ8Ed-PpJMY;4@?KMhik!h0JMdM<5SJ z1qO_+3G1@F1+yk11HHKKvY@c|Bv`mismPv6*(DP8s}o37&5SL+*34NJ>tdF@9W68* z$S*n&Ho(;$9igC}x?r+%QGmsI=*()t29h`ro<0%!S*gOR;;nk`sP^c^Fk3V!$!k7w zFcFHvR46?-GS<|Afhl0tzt*C1*l`sb{v(C)~ zN`cjceqCFvK2liP7ZWzKwqOPF1B{LB8vWN=e{442=qi4$ZCK2tr@~~dKd|*^WpS3w z?`3ov%P1|E{PjRn_FmMhXl;5m=H%)&#UGZz_%VsI)RmioIt2`cZR|W(p5A(r1ZygZ z^Nx>M*yHXK&1EK?@iQPu{xGf~s(qINMV}de< z^$&|m9a1Mcf1S?5W}p*oOC^WoWLkLd9Dt?wSrpo z?Z6g&GebGE=^@1N)~B)!Kl9fD`i^V5F0?mbra>-{F0@OYbCjP!Q(yF4;v z%-1k_=ZeGFG_Tex)|>}0+Sdc6_qFywZ~}%^r`cM&9wc2qj2wW#JZ)whtCoU<`+W~iNRGc)UkqICPnJ$ZU?ST`ProI@J=g)j`>vUQ26JzTzDog91EXYPf*;VPB>pZo4H^L`2#)mV6$X?2; zSqZh(#|`2-eXefH!?|Y-(@?$6Rr5&9*gHvRJ$)@mtS3}Y_Xx%BUgC^j ztPMX6o-Bxkn3UYh872mElybt=;|_=02%<-qvX!W~R3&1hv3h@6!lX<})~i2RMKAqE zlnVoE8JP6SItU{Q$#Iw5yEHH1V^k?ppF? zRTp_aRl17vVFvhEY(7X*GC4W!#l$}b&hOB;b?$ZHE)I0)GihNHQ-wNmjZN0f2|%x> z7egqhMPz18UoBmrW#Ict;eMjd{09Y3#@ssh`Y&D(m^7wY1=?)W~jDqP-u>9kSvpmUL@vn5+lRclL`$Ec9U(a zE@R0Oiq?C{VYOVs%tMb5Xl|9P7pu_EQ~9x~IM?-SI*JikPn4<*CbM?8_z;zhh3a${q zc3Uj=X?`gm%L)-_SrAyvw~PP8r70rz)bugOfPL!wHur}JDB-Yk(q|4TGF$2@EJrvS z+S5@-_}S7C4C5@I&trvSzWR*8rHFBwfH@eAriE!({^@+C3gOM4a)vmxdPR)mR8kLr z=?yb;k7DDRj89$Gm_3b5=P=H6Dk!}2aM>2y<@B1R6 z!x;(zvln9|*!$q7fVskceE}Lqm^R>4sH^yOMRBmr(RF%1gB7H8b=Brv>&5^YFXHyP2 zZoCCE`d8#m^FjiSb^h91Mb5adtyrDvOSp#y16RO*fO%qg7aw|K1xgMBD|7zp{&&1( zyrWvl(Rs3LwZ9wMf$fILd?mlqyyZU)zx*?bslJu*TmDmVc4G}T{Inv~jYBtLle%%= zLhUG5|6ztmCnFMk4DG?R`m2R5u-lILtJUTI)G3<}(;am@*M&Fm8vYUgFLRiP(MwVL zBSXS3Zm}2sY0z!{-r2s~{j5kHg%9x&!JzGwJ8^lgu-u({Ovy7_clpGGV!Sln8F2P7 zaT?Ck6_|w!Moyv;yZ>_mHOdhYqrad0c-%JVo!$hSCPsaCF zvYR4fO1G{N5BQCFKMNL^%P$su@*VF>3@#{-Rj${7+CsYxET=cKZ9mAfW3SdIlWN$T z1o`kG0v3#C;F2UYee@;VhYkXKwTsfrZOCLAf#F|S;*ACcvy zxB5?-L8i+VF#<)6bw!&BsrN=X4;de5fQS`6c|l`=|H-zX;){_!(F;z!0o$=S$JkV} zx5Qv%YNh8>vE>fW)|GdB*-RlcnwDV^`vJ6MOxUR4AX%6P2Y>VkC+gab1zW+H^I7g{p-b z_|dI!H)}!-R91yJ$%qHCai{Br_Ba;4FY99KQog!2Fav zXlqjy+vd(JYfI0x5q1^0F=1geHKx|VBtx~n)?x^dZ+{}w@Y??2dj89L1Qx6y9~=aH z|ArP_Kd1NkF&y1&;udXi(il=B=y;R&d{h+lp=y9`F3L^<@o{C0L3GCk?W@$vAC9;ud^(kSoc zL31){z?Qj(g$agjoF61|=d$8AmifO0|4SMVnFLS3tuc3V-$YL0zZ+V1{aU=pUHqzK z>-z$FfW`XPwawD;2z_@~8EC?^TLYKmdH z`C&+aFaaHsrZd*@Qjx55$xlv~46ldBfZ!wqepxiICC0x!M(m40vSq(|Pc{iMhC4Seb^atywpZ(f@9fd&Yr=%rvc zNa)Fzgv=_J4K@|jk{TTJJ%WJT7c@z0pwwh?J_)Njyl6wpIKG)J78oAuesL?1aMB11 zG77RS>d7JAZn*p;}Cra4n@wY*D&$rwaKJS_e~fcgs+}=ZNI1Vb4^RqE@7x zbu!@~a7Cs_7XBD9v6}u13$Gsg;xQx_a|S&j#}xlTv2M2l{{*}Fv(+&#`!GMeUg9OK%c|cfinmL-Ur=~g z_W0mPOL`v_iwP#kC1XVUj$ySR-fakArZ~FSuGPXH36GtQx)-b#_8WA*&cyQx4obWl z!RkU#W@O?_wOYSpBN2bHs&Av=KeeNUZJ>8?sU=5P)1ffobku3Yl5r++{x^G=cl z(2mzzsSC&&CDO^Hw+r~uh@q7!{k1bqzDinqnH;*Pg9^FOlbO%V8`w_L5OjSZfN=_f zFi5$?0ihP>b}_TEn0Z{NBc8nYhiAAWYYFZHp*dl7Y{+aGjt|;nB>IL^vbj%QB$Tjz zmEDNNr&(yf_%B?CFZ>H{p`-talaE=bVJe$qgI2pczZx$Q(m4h!>+#oy^Bdcz$Nh`h z6(jvR702ZY{tM1~xSh7u?P#um8HVlc{BL*#k8}kDaae^HJUjd7DjuIJl zg#dTu-cZ^MsD*#sXWVX*yz$|tK>!j z1oLzDMhk|U33O=VSAncDKG1~tfhL!tw*aCrQO`AZL%qPD{#s?&hZvMpskw-`r5z zLrr=F9%2{HoC9Ul=FGJhf7$W9Z1#P4YexJ5)Af8YK0wj`$X}SL!X@8~Ai=U7 z4|H1OT9Zkc{ z*^ZpdnC!N_Kn=fHa@u~73w&7eFXjyX+evtnZ^_>+ob)U9J^$VGtIvO(;WwoZJPk=l zJ{mH+@+yp7+1>g}!*330*HOA!*Xde*7>>^u%dbvu_fLl;;4}Qg^0ghL`&)!RSpE?E zH?<%6{LcfwbmV_NvH!jd{vRv(f3lqXsBn$fs*5D|KLDkfz-c%?q|$f z;e~Eq=YECQP)+DCCQWZM|6rn#ag478=vpvbL->d759sClziw*3eiZY+F3o=D z_TQTKH}P*L_l4!%mwyJiPtP8e|M-vmU38)AJ>99-T>4X!A(z7sK>mGqHgtci;r<=X z6Znl>Uv+N{&&VeA-(mRC1wRuEOEKRQ`nN3G?H^U}PI|>@^trk@>+{p{Le^zB^wYTS z-%p(QFQ0rSZ&Isy`Mk-ukPjsec;zmP){RfSKh5lWxHE{Y52_{ zfsWD>bY^S$VK_ctEWbJZ`g=;mzWQA4xb45NlgS?}zXrdRe-`t<51}di50RGpzc0Q2 zo{at9gs-lL^9xn@ zhroXh-N5IL+*wRk-oArIzdUqb@V@K5m43c}=Rh>hc%1|HM)&N9ep1i6ljXkPzXxA# ze_XsY$t>R%{7a#4v6uInzJdSM*v9(f{R;Yr$}ZNZ{Y3s4=D*YUhrIvuVZVtlhWZ|X zZ&?3Y)xVC)bIr)#!{zYg@1OXaVE=CF`THoA{-q*+OU6HQv84WemDq#kuh;m?FwnfD z4;hhs7sj!Hi2MZ(c;F_odHn^hL;1W7SKiDgtT+bka?RuqTr;Pple>R&IhA7lcMS87 z$o!NV1Aj%B%rX_hZAtU-|*6Nf6;%#B_TU}RQr##*@!GoiT-Uj=5U>FD*t52VL<1}J&uig zEKiWNaOd%PS@7aXLheUm0ctosB0&nG2waoH$@!Q91^;Fu7HQ*}YkmQ!gts3;%YK#l zw7r>p^CEU){0pY=eF?q2nw@7i(@T#0{pS&KXZ8#&MDO3VKL*>?cE)(Mx;sZ(fHRbw z2a!$%`jDx1(4Ur(NubB4Nv_jnm|k|bZjX6mkPPWOQZ$J~$MZ@|gH1`scT2{g(rt(; z+COkk$ToppA(weShs!wWFrRis=mVy6=wEQ%8+73c8u02sP^u=m{JFSDkWsz|H4Z8< zJs^!r@S@YeIFjxUkTe`+WT^e>|c z7>X6xBy}+KGv@76+2}Usps_Xjn92nuFCG`nE09&j2MX~M#YglOc!4IC1hzkhG#0PQ zhHZLXS%Jxjx>Z{&>_nSyik+}hYZ=u)|AT({C;RXu7N`3CkN;7Rt)S1QL%@uH9Oq9k zmAOkW9_9m@FaDU9`2zKFze%RlTTEo(E_yIB$2Z>%9CRkb@>cYaO6fEIxIQR8SE8&& zbjz!%Tc7Q$Q4A)n>wEC=6P%Rco6#IL0_(Pc@+ggDZ>}axkan;tLqGy=Or09ASb)L$ z0)sc0x2E+gHo9p@^S~Dj-$`nYcJ0U;-IK$y&k-gDWPFFFe^*TXGpa1}f*h4zbMfYbGnPe#rNMn605evR@(!P}ZRfyS_P@jdq+fV0UzqHb#^a!l%t0 zPD>nck?Z3L+3^Mkwe_E`N3{0`vA8#b?g7tlU)ne{!{6~=9dv1yJyWan zHk?kju>IkOyiaUA#z01YV+gCUDLD=v@7a1KE|I>q2Zh@46|O2dR8{JLVlf^60$vYV z&%tsV@-Hxl&*4Ts^nb!B*R$dE)Gf62rK{EMxxK{K(sK@LvdpbM-z+Z^`F?TY>3vzo ziBl9j<8gZQYPynmOs(iPSySHP4JaV6c`xp+=KYh1b77mOD?AQz^sdhd~yp!q5a(bft`oHd(-1cX$HPZN8PzF4NxVZAZMk}4Gt zw%;7NjDuGAK~E-)2{~L)((Qee2w(#B0fp)Tx`XOp>YF+K1uYw@Uk$|;^ncqjwxT{; zpbhPnOxPvkH}nzsfi_1UJ5=ZYZIyrF1vT<8BKCozIp}Ezkpo)p3v^ef3`Jpv%i)O! zGp>%`K;2t@H%rg)>w%2FVym==dQoH0mImv!3p?KU^&W1@aqWSxyvc%INq6xZ7-CgM z8quV2Yi5mNo}}SG$l_{>85%fuPLsFU}A)PlAbBN$zR0PB0X!YaC^!;`4`4AgaNAh;D$wEi2!oP5sPMP zd!?TWo;>7u8G*}d8VHi<-rh3|?tKmjyUYXs>K0fVp$JdCyf0t{d}V}tzaK_?KWW+D z%BGMkxH6i;{%}IZUok#njCGhp3`lMRw=3m7Fl|T$E3s^0qfHf)>m^b{@j0NX3@&p| zqUZ13gWtGcaFS_;Z|(26yL0$@*!%9#cZ0`mBELL74bLxH3BEbu@fcpozTw^I>nw~$ z-}I5BZQW+xP{ng z!f&#=PE0X_;PLQz8TfOwN(bLx!cgbyGQ|0yZv(N-hX=+FhUeF_7*5fC$dqfEBhKre zJR@=^JIaIlR=b2k6pd9M^a1HI;{*j9>YMojRaLb9r7nsdqG?ATFw%PlvFTesV+kiU$%xkZ zPiP$FB48m=_&@pog$x?GgIbO!g!myreS@?0Z?HWeGjqk6Xu1@S?I zp;&LN`6r0LqT>;VMW+lYHeAXJZ7o+&C{y5*$pOtds~uqy?>vLz1%27DPBu|wq+_nh z&@t}2oLZ{sB9|MiR}tb?4fG*C*tGscE02?e@2Keb#%kJYc@xXU<4IhL%a!7~-AcNG zIWA7SF*iacAvtF98}RU!xNhUIjxm?<3GDhb#!25nlYTOs{+!Gc1xF;6^-&Du0y0}W zw@$fcXz1SOEe%4bJDd=zyj8Y^5N#Bff-&Qt)xI8@t|`Rk`)wf8lMMPV|B4ndLiwtR zJYPUJYScv^vhe8}Z6S{FnNNoUAA9A^#1YePhSTeadW5ad+V>Ubj2T zaj;>F+k`dbej>!sUu$yLz8(h`UWZTA7Zlaj*{cfs5izQb_wW2}76HCC;opA=YYNFg zk5k6{(w2?j&~6>R?#Q+q6yrEF-w{Dl}t z%*!I=IM9p!a@KZi+|hV(!+&6tSK$R4&$6D}X%(v=~n^_VnYDB&OO2i zWrS#vCXbY>P$0Q^rwqt=2H#oMgc3#!BVQSufG!_cZOj;8xp_MqEK4apeUk%<8IAVF zRErDGU4lX}BMZ1H7*(SDj+zz%WPzI8%o|>m{2FZ0<^>4BL~rIBv@6d`VnTgEzcwJD zNXws_azC*W!&eW;pqczY|Gy+DT7eXTpTDBPeW8{E*xH|8(dcl;_`|$GGO!yzz_>k3 z5}}x6MESX?%GKG$ETrXV4zJYsIzf=}7wCG_g=AEmEUHp4wu&Dx0p@uUBB0LsxwJs} z1&=o+epI@`KbXi^>M$Z$&4PE74Sq0*tE|vCpOOP+ZmwUk4SFjrhG1N4V)2NlI)@-z z8kWWL%QE2Q)s{7fIErRWYt^O01j}tsUg7)A%9@D(!Lbz`@iKhUB!g-o) zdiY^KGDY2&tI+20fg&4GawpRc#0E?_P@>LRs7DU};Tl|zFCfA)OF)*87e=U{QQ|| z{Pb2zIa`o_&CScbAkiAsSyp`c%$`yaYF>xQ;gp%%H}C`f6yP(+@sc&8;*abjk7fFE zLRQ2ClXHF5EYl-b&Wl;f6dJKe`#7F9T^;!in*uK1Fs~rh z-X?4dCmWkLu4Y43yx!Z1^URgEQlv6(ktqR04wD^U0L|~Rv-$1+Kz zV5|A1@b=bsm@P_KkH$~WQDmkDdbPSMR1}v0sN+f$+j%BU;GZG(y z`p_}HCKt`lUPj#?kkP5BBFhj*ByJQ@7SG^C9~p?bAvnND*c z98=5s-NY`mJ()ed{wl_6f<9ew%pldUrN5Y(N_?Nh*Gp4go8L|J@6)oI^tPZ=t@ymu zE=yd0sF6sdez9PRkJn6o>5j!1oj2_rqbX^R)UhOstR~~!p87v6Zx@|Vi}O~Z2Zj^t z>2fmfw!|9qW57Gj@k#J2n%>-8+{lcIX(snzQ##fl)2NFJyF(Dg{K=Q# z5Ec4{O}!^@bHeq4>L{QxsVu{D_yUr6)?whl-Bp&^^rxCkgHtD@=_L@s@|{T+GMtOo zj}ZOJO`f4L!{007kXSGKJ`9Oi`DXnYytxfn47P4q?$)^s?dS(;Dps06L|v4s7J zjvrj6w@_6F#1)-4I3XW!Sj$9}xvw|_Yz2nGjOy$2Pk>85M^|CELltdwr(^(w%kw*y z=;RzVlbg@Y<`KfS2+vgF^pWwj0B_qaYm5BcdJxN%=`&T-u`r-dO2zOto%|Px?xaa0`oA>93 z$v64GL?YhLfZ|hK#DDvx>~Cze)S})aSIc@(&W^Dh z)8AN3m^7LJR|YY!A2zbyAX{l_#Rrh zX6J|Ic#`Q1XXEm=K-wr+7~@Ar9BxU^CZ?zQ4~O*il2T=)ez)X-xP7YMEIuuxWrj~J zWGKIv%aCJzYS~8vEb)?PW4v&b#8Es#!B}I^o^1p=Kiz{LI-fxpKh%GPLq|`CWP5^w z6Bo-HC!E#`j~m4D@#vUD_d4>?U82<>MvB?g&nW9qz_03%PN z*R(~}9dW*Tp2EA{*uSBiql{r1B$Dp_3nf(#$p@OF5%&ar#k$dIl9u1O@WIeZSYY5z zVFMqqqS!DE6R-mEwHp3_>{@MiR--?k^@ge<+tW|0Q7|LX^Jy5%rg2koG`9h#4hw{D zuG5?Y)~Ov66yS0(@$Tr%=m(rhihP&)5T038=9s=TTlhe}7Bb%hEo>KmfUVLFuJb@Q zmIG7kNrfFenIBIc3deVzZ?l-%FE|gco1*hm-)D9_R5Q-(jTQb!tex3xD>>b|!L9KG zV_(lvJ(--kK(xnU`#r3zpSp^{_+b45FnOGGHrHQ)E#Sn~W8LikybO}bmAqvNh_6h& zyUWEE(Kp%K#F#R8Zh8A;Am@xrUo#sIBY9{cqMd6p^Fk_?g%gNKXTc*Wm>V&=93+c; zoEMJ|KgYtD1lLbiX#EZCM)goaw5n0LsRz8=O#?+fZwOCT5{sGAKVyg1Q$qoKE7!WK z$x9>hLQTi_NU(o^W^W#r`ng<=Kn>vAbIbS%4$)j5>V@`~kv88qow9@4qBN=Fqr|2l zy}tyRMH)e78OisFIDH3B^ZUwwWvyX;GpbOo>fo^kc8pe!VR3g2uf?z5%5Ydb8$Xs{ z8h%>P%J&Z+!MWIvp+N^eg{S`JRkF6`l}a!{9-+)WOdO@Lk4YynEt$QYmR`|I&ln-= z@%Fk+n5PXSI405Clswn8_x1W>vO{iF6R=0mPEhnlaPlvk%zz54a;l1ESFj;4%hMFgT-%~GKVLcfWLUe;kzXbxGY!r54D#iRUb7r>Z<48$K$y= z5xjMN!#D6WV_%}AV@lYM4u|hJe8uhN@Z(oiwca{(45gc&ew5FZ&*T%gTctHx4{WX5 zyM^OynyMdiRvA9nnQn$HGW!~cO(2EmEYt%p2mXBX@(k+*W{L3ug1>t9^7}`UU*LM0=_7W5n;}M|m+8H6O@nof<}P*Cr7DDf(E&x*wU()5L5Zfe z!HZ~r%PBoM&B5}6XC>~?&gu6Hg!vVm*t8wV@oK{E#REEfRL4`V^6~EM8|Y6w)p)MQ z@qtKz9hpKgjOvTr#aPz+E7&qPa%Uuh^ILJxz#FTJ*to3FgT-^4E-V$)*SIw-lt&`< z@tPXWUExqS*V}zD?ps3VS{^yfUm(NXtnkqPgyQ^y*bV}2w&QZAKt%TJaP(0B2Mu?# z>xDPooAU!!@0~K!}E(6Ddq#k z5s&Bhu%l2QLS;;^IDcu`BcFE=*`DF?`8-3kC-1L`^68QOe7Eolcq~_9 z9GAyJX*;qjrxr-T*ZA+_&*4Dq);Xo$xM@#9oRa;XT+Au$c!W56J%5&R0)SaU!af1w zry>zVm4;vsQO12{-t9m`% z2wMU{@FUsh#I5QshKVHLEK$;^+z!|l5REMlJJ&02X#GPhee(mmvR!iK@;l;v`;vjxo1N&;Q1%oFwf~uW+(-8 zmmiyQJDYp@W>rnDMY+|ByBWEs#22jhse0gBwdyc^Vw6CF)o?F(+A`KjDvl3KaYJoG z9>fbn#0Mv8-I%{ZC9||Gb7_^bsg@TT;c+I7@Sq+-Vx3!t^>hTV({wbnfJ}G~)JP&5 zSVcBt0n1^D8L_~Iya^G-ER{QNV5Qld_}4Ov+S{@~ZxS4Yx4X_?LO@|T!gj@DT% zIdtNPIx%kg-8jh_NkB+F;E_sY<6J=O(j?uHi~Il`!f3~`k%dq`=myy-%s=LAqydVy zxN*^S3N)y1YEPy?b#EslU)D%;0^C9PuID})i1jyM>QJWcQ>hW5q4mdGAk!Kv=+{^h zyuB;$1ryvgT~%u7k8@Q1`lni_hB$w!wYr3F&Quo*E@Z`62$>NeKz)QZK)MJKj%!jPFUGT`aBxbYxpBlpj-loV>la~mrzqau zFuc*0VrIIo@c3yTxib0&UYrw;t1;BR#LN z*pf8vj8#cCSb9^Ijbv2j%{VD0Os$?*xn>7jn}1^j+E{3;2ew)*CgZ(C#~GI-=WEMt zh}jwmx|U2%D7hSaXu{rBXH4)^YY7&9AX^!;9^-B;Vfdk-v!tk*+`fAD?RX@S2t`R` zLY}~0BD=>~B2o@p1nIc#*RslU6ewH7rKFrD$yatq&9$B+$H_%8B$IvhOnjCI@1Yg% zZ7U%vrnQ&+4`7J;7RYx_-rq`^oBppwWirHCgIlJ)>hi+*iojgoQ~?}+hCb*1K**qK12hRKSM=*6k1cA-|Ncn}#rh=wgGFM*LyAqDvA<29KHGre5c`GU z48I+|a7Uma<20muI{F@|jM5m&<`)LBeVF639l(S6x(uMxuwo2yP21;bX(xmZv4>$< zTWkTI@Q3=N9n}4xcM|#=yQWKNnR+72X>1qcHl21mw%5}{gZEF?&f`+za4kwuu?jIni{;Yeeh>u}{mEKr_bQsMwEe`jr4D_mXY+4I)Eg8?& zsth4s`8lCDmYqn>E$RBorL5HerGcnT+ z@q1D+{qtz4Xf<^@`v_WCQ&4H6iJji!2O?{{!*9VNz1|Fxa7QsEZOt%wEIv6YP7 zP1L1MWY7i|D&IPN)?IcIudyatuX(G&DNs7gJis<{e=-4S%%f0*#b1&T<9_D*2L2Ov`6)vx{6F&o&#M^SG^h4g2?0<2469;>| zt6ng@L-`wx9}oZKRS33%b2m??MmqYTTk`WY&c(({Fn`I-Vd)c+d7+2JC@*9Jr_vS7cYm5pGMaa0?vvvF(#*U>s0WQ@Z$*n>+M^kQ&?g~z}LN-PwdLAnHM z#CMie+;j-%F9rwUqj}d?c`3MlFcC8)K23H@LW#7!T2aoIseoZ@3lOaPTE++~p6QWZ{ISe8WUp zY;xJ{8!#Dmj~Y%E?$Fk#w3CFc(F?UIVo@5!u5G@+^lgJ8D>}QSsnB&620SdU*kg{kS_jdN2>{4K2>Nyj|Poa5)R$E{( zgUU}`zzcI6FG1L&a0hNLPNp#t^bfQW3>xE;TEUgZ6o_|Smq1G<0I&nmR+~cfRf*=G zfr(~^2}R;Ww1P=O6x?+=R)strd~#3e^|TD8g#2bsD`KdWI)=el>?Kj@L~6aMN3b)U zX3o$e?<(nEf*X$a$u35Nxxawjh&O$hHpB5j0lx)tAW@r?2JmgSKR*&A-!1%%XMuac9(J>F6}}%p7|1*6!0Vm z?u%N|sVF?Id!5<6-bHQ;Zq9AWkSllwZGKsnhK;)LhKwIxr0bg?>qK!ZrXesCgh)pt zABt%Di8WLg-r1TGNCO9Mx}T`QPgK_XEr7h`xMt*z2!EjIhksrjoAd3c6`vEGa3v0hsND1h+> zW-H^#wYJ^vM&nP@v+4PhtwgmOh}U0iKY@;~bZ4O~6+cH#&Z!C}=mk@SVLygDVOM8^ z{P`cbEE#Zrxp=_-*PQ-t@cJhf$FdAjlRyG%|CWg`9{+BV>RUHx;|?_cXcJ$dR-nI| zDm8A)t80o)g4^Q1=i2AXqLgo+ayc#9p8bdG4xZfRus$trZ zosY<0OuQHPnJHK<9~;}EQx|+C(LO&}K3&=nwGn?BsP>syN_}k&;Frf?K{>-c9zN+c zcKSTk&^GGO(`6cF#_@26q3lx1V7W7f_8gQrUmjz-)OykELauhgF^F{nR_OZFAcXll zC49a1KmzOv;4`>>%AeE?!FpH<9X5|~XvP7;RXO=g(9dy~yfgs=7q~+g7 zWn!RgJ1O&;X4__mk)8=?lTmkU8jnRw=ma553ZHzYd z5azD_*7t?iQodJ=6T9-oC>^Dp4Sz3jN|-%?R`?N1u9a~uYGps4C*@(RX=zSlp!I+h zOIe?oD>fFrOsS6Jvqmztv9{2W0L=A$L5Xh@1`UuR2Y5luD{F3vHz5$e4o{F|8_~pm zC*a4kp3pE&DsKChWw}1qwp1+5_e{*^TJ5t!UFoKIwdQfgZJSYSANJ!t>#!EjOCC2S zEx_xh;}N00A&bC=p>_8Q3&77RZ(%PGlU%N-mcal9XKn-h1v6m{az7YFjY9u;ZxI0fUjZG++?4{`5>W!xL{Oify9`a#mZ+sRPccCX7wv3O8Nz7+U zl%=8x;?5;#T^%r=>n9HTiAx7qx%8iO386nNg3D5W>%AEA_eW(_O47n-|7l`Bi(xwc zu5&@(CR+7u2fO+1BSWSAh;*9j54);}{FmTqlG~Tbsl`o{S#Yx6reFSVvMmEg$ioi5vCy`^`Y12|tZJ5z zVfnvX@Nyf@&H!!?ryl8gM^3?FxT zAR%|sd5T11`Iz%8ZkBiCBM+|o4TFT8|Gb1Ae>kxhaWJvnkx^=i8fCcYs~scfDFK_Ow4%xHRQyt?_TJF zSubRpDtsj+$SK)G2ubH^LwpPz251f$lU%awJQXbuz?fhYb@2C&UgB$dQlY~UCy(d?H;O{y_BD!?M?NKvjl5fOWHwyO!mx#3`^7;o_ z{tVke;MLcUm^!-eZQ`X|f4bpncZcC?mrVQ5IyZ5d9)H$(TOJLe5uZMt!^>ZZUnVv3 zcc$YHwA1S_DeWP6n|RfD=)U?y|JclTlh5Tex!rCprq?!s9&+19rWL793q~KXA55ZD z4#SBjiJ!k(#26WT_(Rk@Q&SV@!7BeJ6KSz)ZgV-hDy96ma=ji z3O|5EqOa*kh`J82oWWLm5;uuS0q3SUwSr7YF=5$Hj772+nd%2n7dr{hojqwJUz)R$ z2EMh3Q*wv`_KuUO*gO?wj*Y_^FyE4_$7XgG=#x{{+#VM7PXEY2J{op`rncO01a2I8 zgfDu-r$|%@a|FzCgC=+>S~Cl*B%N(CR)|-yaAy}fM9JnBsICtQ0jAloNyh597%e$= zdJSPv62DP)>xtRz@THpx-JTNqz}W0qI~Gz-DU+6XQ^2w9egB3IY8)q2jz?$Aa@D8& z2&*SGtfi$Tog!fm&ElP#&6^BS=H_Txh&yOUYolqLvP22G4 z^*upfbjCX@Ql>U&K9uqGe7cMa! zN%=L|9b{EP?Bj^MiU=NyLE7+s3bb>Go&sC%!odBKPfJc(L`dD<%4H$U z*fSiM^7B^N`B>t^4?bzY>%x+j^~~CuIVJ**#+`3EEV)~|Y^CbNzAgSl-L8t@+)&g` zEy;yj>1lx2SyXWmLP9`3q*1BCDbV!79To=1ub9lFp|O!Ho?w%1W(y=ov=9}_nX4zk zXmDNkQP2YJh1vhY+=Cm7R0`-2ii_&ZQY+W|hLu}x#gqSr!6-$LD=emN)M)0xhG%0- zIG0yw_;tn;LjF)914Cy6KcLZOoe>;oSAYO4=t`WgwFo@8G~o0u#Bj-yar-8*nSi_m=I{+3 zC?5}857>wD39r*7N4q5(u64luVN+5GO597Gj^T0OXv7hGq*H3;FbwrKNB^}pcP>6{ z@gHj?9*a(+!z+#i%fwBYjlV;d39s8!;cenTYKY{vfW_CPx}fJDUhB^_p6EkuSsqSD zdY9MCeEbEaM0hll#6$3>zt+D0kQ-hcot3;huwv1S%T!(B$aRzO(W>%0e4xCm#d*{e zfK77ahuX4eBICbVa9BR<(^5O)3=7qaIR9dKZw`99(3Qa>%&?H;av_--Zu+}Hq-kg$ zc4iw=ncL`Gm&XqV$MmkSwZkI@Wj1zS-8x{cbbwH=gfi1=3$6O>TDYIXRrRhxuPGw> z_vCNf$wud(^QLGx3ijD>_Bxq5;js4+s5+joKVAw(RD+^vjq0ZTmkPA^QgwQQerX{92d?8Um&y(PQqPUX6Z-}qlUF^$Suh8 z6q?nkq`gR8uHdg_(Z(WI)imcs`Za=tY-DwCoM$6))z3_f1i+T?0LoB$VgyixB+)=r zS0OSAVQ|wcgaXSq1_4el=U5mzx^E$T%XoKv#GMJmo@_E#WmeKj`0 z40{M97nyMMkL6N>+CUZxv%I%G=$ z9SrO+s{w42je zQVtol#&nMaAM*rQ-}5n;RfgeF)61|t^Ic>9MHUk$GCqFLhM+7qD1pF4az5dt ze>TQV;_FLgG5)G?wH=CQx~icC5_+)^Px9L`{(O?@>pvos)3-^fwcg_oykX)ztZtLW z_LBHE(SRRJ{im+mLOC27T~u$i=um_3o*hkEmf;h*Z;QH=@$gv|zg~$v9CkJPFR>%g^MoDYnbxn2f` zr?Y8DT_c-8GLqtH4aw@4t(}EwPGK2gB@2>CDK^Ws9OX_e1$c%Z_T$ci?;w4ZJDMWG z_1ua^vOZ0;9K`f;u?VW~cngwDVzjXC&Ss!MW?dmnwZIt~S1RWi;J6}yU<7}GZ!S!H zr+*RwJbO^Pv#9a4;>60!I1~mLx1?sMo5tt=9*P0`4Vy7SNlV&UEBj8~JEo&Vb{dtd zr^--g)|%n~2)QT}U_O8{uiADku6{750j$ z+^OJ+2rdp1a0a5~L_!!819D$J1F&i|J*CYs^-H12z5!t`b&$Isw)ol~4_+B)mR`~> zDO*U|YeY43JYrt!#0*n5pM;bgroHVZj)Q7paX*1pTn#3staTB~C&WUOK@?pmsAgKH z;Y^NUQl%ArJn?fHiL?zaZ#2ag$FfJ3_AZNM_?U?#R1MuKi?6@i_>;=%FNb)<6_W=y zD_MUAF89>&`~%JM2OK52oqx`4n9v^cj?N7$Awfs>X#>P&ms8)^#NUp038T~W00^GO zO&Xbgpd`h)8AIpj9Yq=$E|DW#iQ{#~tW(XW_#DyDakgRnMNpDYgT2wW$JAn82LBOO zwuq9XeK2)uZAyHlwoRdMjuYwAGDEe~S7+FdPNH+Drs?a;FjXVqs1a|b6s!DN@A!L4 z32e*wbIRM(7XFnE-kSap_N811lzl?N z;()NdduY?hO14BQGL_lB6)NU291 zPe@N2TKg-9R#pF2Z={vQS8#@qoo!`=O{(qA3f3F41rHA5_%Ek7za<*jr9n_UH9X_B@%TH< z(G}wPcN%3o@9sr}TvDWCtpJ4!d-CZ589U12la=vAzi?zoYQv78%WwaFBV@dLg0udi z*QA*xlnS$BO~5GU&(CcV4}qtqBb5stjA`0sVeqr|@UqV^)x!O=nV&T);WXx2L_HsjiY+DsK=LqgSJjaletg)OK9S=QwN@N zu5&w|rdqAY(?jzIo`^l%>*fr<@rxJ0-PfhE*opW39yO z$Xt#eh9~ZjPKj3ZV5;4yL|+UprrEkf>ADBZ)G*&8my9RAxE;ke&khPZFz{NhNg&fU z**-AobqTQYVo4UuJPzC#Z&;p{L6X`R<^#N_$?gaNq#a)rO`Pk>wkMUdc5=1N$@%gM zm1eU>lmtBki{|vs=b!I1>^mYEag_5^Wocrgdf1 zl1L<9=OHrk3`^r~J*#9c3f|@BS7;~ndxLZgkk`Fg3upZX~w+X_5lY~7KeexTZ^&S8C8%fT;%k&c`l`U2>`Hx8HsiW?vIr(xqVjP!%sA!oJ&`0j(wSBL$cOEpSSg)lA?d2N%` zQNoMIhD*GbDS>dag|8Kzy6;lcfCZ--sWn=kPS92)N5J2cW$_o7wsH5PmL#6-UVq%x zv%MVoi>p$Gs2S)*=wzgn~|4HlJz>!rS zm$QvkhK-sp{=`+_!Zlw*&oMFb=KaTwK!KN% zCk^jvVwaNDS3Y4Ey0nGD*sPiww&oT#&CLA~YhNp@X9(?5y|5fU?0ftclZOxQ_GETJ zN<`bgtASC!ur-|ixbmlGRPCcNtsK%R(n8Ci*mF6%wZ0&bm?Uil&z$p6IxmO5@9EROZ2_HHpsYNOI*FfE)Vx5a5Z38uqC@cI~`-CU|#yM&{G5Xu5yZ5IGSF>Ca z7fU=1)F9v6KxNv9SG>e?%ge&8IB+eJ4Y!9SU=fDZ>DFbymO^KEBx3*#2g9M85h^%n zRR^)jmD8WgRD|Kg4%^E?Ff6@oN)9pR+OeLNh@Hih=eJXr_>}v}XP2u0IyXKifc&I2 zoij2qFqvan?DVj>I}>H@q+Tq-34SRV9qoZWfE^g}^3^<6sOD5NJ>=m3>6Swr=AkTQOQ z1Wx$4nMtv4pab={C3Rnw@s5V>Ujx*nhA?7zpwuX%ZsxMc5(n9qTq$g{s2wcJ36jtd zsMI=O`Ed)8`vY`1GTU1UJEu+Tcsojjnf?e$OLhjt4*fF*x#ZiIJ6Nr~x}ef_n6e*` z3{=L$#xtl7WhAwQ70lUHkVJ1p4B_%k1wA1dQ!tqkE$1#iKj<+;IDp4GtmfNodHbyF zyor{t8=K)6)^MEGNzBq7=9>C;HYMWzPFJs`{a7p9^9|;|LiDasl0DUm_k!L-(&op* z1X}gL>no&QX(zvMWDgz5!9>`20{FUdUXT^{;Qs@T-$^H+RmpX$lMg=(mzgP7wm!zP zFWx(BPxm1j?Zpb|W7P8Z=(~c7w0djnL1C@8J2x&GDx=^meKkCswd>1)scX!?4rm>N zIs$5pWm`vm(&rEde=%)G!Y~GB6pv{0C|n*k_mjZ~@fbK!2!kV9CS*^OqhgH-mG-(! zJ3T%&aOvTd5nZ}3#CI${T%E<~L{5psM_EhdzD%mFE!m@G(F5O>G>diV-Co+?XC&*r zTCBVN^NJxRgG%CyjmK?K9-o$Ug$`uqJFE3C70wNmX{lWn4oAho;yTg_-Z=kg(P8-T zQDUE#|4T!6ru*W~Xxq=u>?%JK9y@dQIzHsf^;_q|p4-@aG zpV3m02E7;uWk(R$O8X(IjwZlyc#yhbclryiET;%(_zk9vTbsQ+(WL%FQ?PJtv?7FN zcP)4cOleX!r;CA3i%w8zn7Wrc$%73=L(&5ZjRc6Wp@rCh7BV4m8vrr_V&@~VzhT-z zw&a9%k`Y;xdYkE%DvaRmA*!Xc&}Tq~VjARb*pUg4K^_d<*_tuhA}GoFdJ=&0aWmM^ zzYLvJ7$qjq<&mIfJA@4`isao18)=CYmRGx6_!0_T9+qhsf9FbT8no(KFd4Sw_Qu|y zoy(>6sA9Yh3N0w3wpY;YEKg zv-^%4-hK+(FKVuv0+c;Bt|XwP<9ai^OW>yXjRx0201L*??}--S?bwQnu;$Y99KSff zq(=lfq0Un}W=3Mb*k)=oVtmsMA%eU?%=xDHdw&pu0-oFSPWjakWEP=;K0$ zlkQCUzA$jhk+p?$Maej2ntbEcaa+wS{=(1y{~B^fN!NjQ1cF^~*eWjCWD_=-c_7wv z-TUY$LfbQORHC0S&Q7W|%C04$nhwpspYSB3wc~6>?~_bavy?T}?%k*o4XS_LA{zuo z_aiF~Zf00K=fkfd-#M=w45Mwzxlo7;CMo~xEPVEGuCU=x1M$g}q1I0mo!+JnoE%S6 z+j1VMx&^6A zWm>4);}qiy%&@4Z{Y70bp*HBQ`8EtZh5+!F54wt(#Mrzs)R`9h2g}Or;q#wZz7qJ# zY}bE<|1!9hE_xlaTIoQmHyLMRcK7cfUnSD;52-K12mU2A@wL0EWNz*&J`a%3!Y`V! zg_y0v8dgsQ*J@3B|;9aYOa8yGj<~NGq z6GT>yJ6b-L{HDagDI}a&@CHPP1%>2~>0bX$FwldN6v*xung1DeWhocjhU6};8NAtjnY6C zUVf-+s!3>*;~12E@R)o3msV z6l4p*W_2QV=TalqJ8wcvKgjobVY_Nmba^7f8xkDXWD$_7;7~9yoL>9_Ex^2DknxHP z;{dEfOweltIt*y73`HOL*?+D9&b42yt%pgBUZOmf`9mj|brQB%ZM_}`5u?W=Yq@&a z^fDkE|3hp|lnJ+5_03OIgSBwZCZrTc)7qJvV{I$4T=_HAFvOE2nzZ~(>7lNtOE@nS zP+PR%4Fi!B9gMq&Q~$Kc`r;!;lCQC6;A~a?(@n36BLh18-DJY_LCS{HVTMS-rRC5i zaOmJKfS(Ib`}Ie{UvUU@RX6{q_!ZL$6wd#7$O-q#p~s>fMn3Z7r*4B=5p>(?Lr<_h z?V+jZTnB%{6U76IZ$rCL{W0YBgi?>?`S%nD$^QP!snu{R;fOCo^3pYK3+GspMs6EM zM@=ob5{dllpH32iEir$yZwCa8>!|rJCuiq~B>Iy{r(A=_yGFcFA<$tWrnG)CwTjTKYvLTfFba(Ng!%7;PbLrSZ7yu^s%icT%%GTWxSY)Qh` zH+putqLS81jklB%h>u?notyeBmk+V8zYlbp{sW&+mk$H)tk=Z83@Z2CLk)Xxiwaq7 z8Lg!8H184p)8JmedHig63Bks^3!S()YfS#cXKfo_GalJ-MpW{*!)ir)yFvSWb}lmW z6Z$@BfnN@wbGX^b>WM17H;FIw$bt-#GpXLC`y5<+-kGBoO|=Bny>esDihH1ZjS)?l z{xMk5G)HkmB7xY`9FAj45zK*zAfH})=|xiMznDzNn6P2!N`U%t_}B%z76N9BJ1-)# zl!1=FjC=~anq*Z8|k^6_gPgo6y{Wo+IB-sk3?j?s|;a0@DA)<%xn?rWX%a1I2YATXXLk!D27Z-8}(DpHqdLF9J^J_{pIeR)SLk-0-(sSF^QnoG!z)-ZRA>~KLk z9~6izf#};Wy|KRiuy-iU@$s93xgVpvNc>Xa!TQkAtKn%$#QG*> zPe;EsDy7`AC4Mam*iK0@|0TrJKpFbxkgo{C;YYlcZ$3EK#2fIJ(`1>-;F|CMuq&1T z<q=K=|FmV`*`_|qzyd5t^8X{ zw$fhhlK8il5`8x)kV8}qQV7Izv`HBJ{TM?q+>Aq{pkA7S1zO!F74i2@EX>4usoqYXqq}4Xq;ek6< zU6cmbs9Mw0yqX`lfSYb1`B2S@Dj^`yDQ#5AKT%3LQKyk2aT}v7RJ-uh;TN1AifsDMs5nIJ5wENV96cE)*D*!vB+y{K)OxbdNf1Biu)8}u z5;K05YMpR_tro^w!bfmxgj?$YJ2lp=)yIZ6+A zo~-Axx;t`KNdL4S4E^ZTpkn@5IdR+K#DT}58pNCho^wtv((+^GK=Ay2PXLzq)UP!h zz>i@3q(>xlk6)Ie?Sp=cIQ^FA-tvmq4@clwl1C8dmE;V-=PTDpxQLK!@p`*ZIe#`$ zw#H>JcH&U{tt55`-tZmXKEl1!`Y#;0r%-OPD(#mxEftIo-Ex{ri;lG2Hg3gd#K-W+X;euFUSxJv1_3d*G zPU;7&1JIwWhf4h+By7RN@_(#kY{5w952606I-$wMbp0Ryjg#Y)gY$nDh-FK)M_X1A zs#ru)Q6D~KJpPd~VV?Yk|0StN`t+QFpu zX{ZB%Piu8C(TRcfSH`DD9aVG5V>QaBG&R=uJaY&F=btTaE{E30>j(U`4rlfVKslm6 zll{RUlRZkYm|49^L3fmALqz}baFd@MjMxkAO?ty$ic31m>V@wBulNT&h0Cq0G~hQ& zPT4#fR{uBnc_b4mInEP70ymnS`Yp=a@Rj>WIl7cuPlv1bay^hd_%*PWMjlfEZdijR zNDAFxF<%gFIPz_P5fSA~A0~je*K9u@WLYyOW3Otlas(ZNW&L3A)DiLmL!2U9EXveVyGqo;K$!Kf~CUe-L1}= z(HU}2mM0ol7d*(d8Fn@t9nzAlU_~WG6Rhi|bE=G0r0q{K{soE~Pv2HBj%pppmd4F8 z`zE&_b%g;kt1Vreu0Lb@T zuRJlvq-&*4WD4N$sU6S+25sSqZFVylctfBMU9jxR%b9=PVtlEKOYrV>?B1 z)}>`hE}X_J2u5W+-vUBPX!T&+hp|7YC&VRt9 z$xiP_oNsLM+I+jFf(Kre>Feg^%bPY=k;ZF*p?mRgSp2xcMF*J6nilLV7tF+z8^$*x z1kTKoqI37&>}*8}Y;vacB*@!UE`q##g7cNW-BjTFK_EJT^U-_LbUsDn32q|oPWdkDX@=v>B=O@AkoY+?S)oU znK*iCrjI?^a;VwjFg&-14?*57gj|-&CAXCTYKKMjBmp+EhvO~Fc=BzKK%)HCuy*i&Qhej%@W5%_s^Dnrz`Ft>4)~<+1|7J45_ZZY{RmQwtFb)qrFZ_n!Oz2$ zD7SVx`72ae?k28kgs#VBls?h@cy*mVv|999ZC!Pr>}1x@Zn^YhYj-)4STIf9#)*Jg zzYc&G%n6TvI-{4G3h^3)3cwMwmz7~Hh{7mc5k0gdvVNF^tISw~i$x5-(^v&(g3=2v!@{j(O*^PejCNf*Z;2gk`2 zVpp6=k=Ps0{DCxqbTju=7g5{DLcb9$A~u{nScLHjV75jxZU8kf{jN-yuOLGAIxJn8 zFpdIVnWYp5=uuu}?xiVNlFyIJG^xnFe_G;%wC-y~1N&iVklY1BFQ^(lS3U%thBds1 zBhFf8ziOJ3w^8W(i1H*jxg(pBtcsDNw%mC?^o$27Z!DXg_VP^?b@00cN_2&OCAeel zK(sy9JfY?9!HR@gRDoqspof8PNryw+{HKPy?^JTpb$<~qeJt`14mNQ4KXIk0p9fC9 z;8Mu<&eD0{a0DHuE`CzhG9xAIulX^=fej}4_9CVmKk}!VTTTi*@{;m2mH1W z-~YyP#&IUg(s}8gvOX;dlie#XDkH)6*|O( zGa*)x8J%W+gy68guw-|+t?i2b3Lnkc><9d^D`M})?eeuH>o^=TuwP^>a@OCCd^9n= zzRAhsLMyuyrK^ZP(S7jz!gQc~=!ZMeWKk8_gpjk#^x0l-q;Cg53JCPS27qH=MAJfM;26#PcvY92(EM?tf4*uyXYi}C+!o+ zfyQK{;F1Ba!S_Nfj7)S9crnMo&YOE7h?dLYDknUU<63K zy-t}?fZ=th>UF%vveF9^uXt(_{VBAp-W4cCR*x*nQj>1rb4fF+V=_9ZPxDHQ6YCR! z5ejld0@k&>Xh0ESGp8?P6DLoY>X*3s$BJBf+UGA>+ahXKP7Yt#R*4qA8o7ntSGmM1 zAxKXn0jVIffTh@PfDynflW^G+lIRl_PF*2&)jk~w6l@s~x}^18Z3Z4}7H*J-Sw-)m@g}RaVjsk5IBJ8}Dih@4sIf zR_%-j92`k(i_IZo!C{=RK+0k3H|*o#El)a?pf=DOBtC6O-s0xeoZyJuOuHb>F{sR2 zgEg6?ArEwKo>M8VlL$8%Disa4bW0Fc_&ZufmZ~#_9JoaB#{xWd6qqQES!bd=C~=LI zV3FHJV#@@!#cqo`O=XV+93++*l5EkwEzSUob3WCG|7}q{uRc5z|7LMw&bu$k$|_1B zIUkK4iJiFj#SXzyQaWybgeHIDy_5Ek;ZgaLI?D^}Al=?bA>ioTQbWgh7&2#JXW(zruQR{Dr+LRW!$Z*+oP?V7z4X#aE~DeXw3+?0r^L zN>`nr{9asT_gj<##jPaPV<&WHyis8fs@kp9v>y<%xoP*Z;Hx&eP zijgZ8^8Dj7XFspi2;I|-2IOp@5~LP6WJQq*!2)@xp8Mj3Vi?YXv*UjNuOXX1l(m6o=drbxNw?O2LdaCwsSiassOC!|{5MSAo3mFYm(?VMrjVDS# z(y~J9b~UmnSPgukrkDXz)*6XsF7-ye6Dx@m#C7r8q4h*Px@M{_bvVf<-oEF9v-^N3 zRz9S|f)tF~2}3l!KYdMxzu~LWV;MRzD>>;L-_)kky9JMj#k#k}VO+`6LcZ94Z%JmV z=HYYZe3X+ezj-*X)s9R$qt_Usyd{h$y+2}TZTM-hCFkYzXv31?2rJ?PPkrn7+#&E! z^fRZ0qk49IeZ<;tyEA)9l>iCB;bb_~&K6VP3zs2y(=9~)Kf+6e?0n2Gb#PveS5=|^ zD~uO;)z#h2(|spNGGaYpmv_R^yUCzWiFz_F;S=@i25+UXpNK})32aGWg4(yv%Ngmi z_uh#7Qv7T*FYn-N{L%$feBUgam;4FY!(vsv`2JpiqP+}|$N);(1n^X<$TcO#ntK8T zBiHg?GyWBlQefn3MLwpoEX2NCq$YKZ>MYZYVS%8f^Fh2A+@;!hbifY$f=h;iqGPsN zzW&zOFc1M8#;`B-Z7Jx6tJ>5qpCpncW!G9^nW`9J3yiUQ>lMoVi_=L~!tBZtpnn!* zr*Pw1J=`Q|v`%qQCG6zbu|yD}RmEYc;Tqx?ATd`{6(ui;w)&osOb?<9L*K(GnlS(h zSq$(uVCRTNNU371Bbv=e(K%H*S1$VkQjqsVmGi5C!_PQj}Ad9(|2<;`fzm%m& zj455Z*lN{_Dj{m*DT;^hhLg};K=Cj#)$a;G_Y$gSd2EfnGe9NkpZjE!7;E*zgBh8TR|Z3Qi;3<3CIw zJ7vYQwf|fZWHg>BgN`NBTKfjLSWMO`RY$$|(gZkmSyUkg*{ZT7hT(|SduR)(ItW!iIh19LrNTIqrMm@U*Nf|WwTk3yqt|2uk} z7kR1?lyIhSfb}6F!L^fC$Jvt{w}Ja=jWzz-Fhd zRq$^(TKFi3W_Rxm^WCOSs~C)i-D;<96xe@3n2ns_*r1KJIL%dWB&!&86EdgA0=c`M zTDPq2%9TjX#f|ND4(1aILGMgx1=*_*+oQ9Qe5vcGQ2g2r0k|5q0Vu|~W&rG+lL)Du zV;3i4S&63U@TAfSP?yVHVh_L-?wrvSnQ)FW*3o#{;fCL_@$BE&@D*~>yfjYMo9H6G zmMOqi*P1jX%u2@sp)B&|dtZRIWuzK(wtp!mA3Lq!Rg&}2rMjOP8TpiwRwks*u@#|R z(#;Ahw3(bn9ReRw=+1G`7QPW?;yWq_Bz@?9v-1C5CI35+)^@V|S9FqE{xyZ7C&6A) zQT}^8@shP(dGF3rEeZ9@;1amtL|njvKxp0sY2Ly zeYHw)r76bclXreuuxWqRIDKx5*d=ybDm7WZEDF1#I0clVc3Wgvah`4IO+_sgF7gQQ zE0e>|wD8(_&02mk%zSqWU%CAXA1z5nyH)JC{;OXE_0U%L@P2pihJ?xr)djy9;?z1c zwzRoznAv(Z@i|R9$Yrsi5<6U`;Z3Z3PE$Lj8fDZSG0zv(q z;G{4bHQ5)8$)R_qfeiDZ(hrPdZEwfEGT5FYUCY}tPWC6LG;Hg1aKtYTg#u~-u0Wpm zCA?nQJy5^Vf&4h0)ulW-o1qB!iFzP&?c`*4iqJ`GhVe$V`&twUT`Sn7R@=YD{eOgq zhNJjC>Rpff5i})*0wIx;j5w<^QePHA(6{V`!yMDs@>%5b4 zzz7KU2b2_z_9sDq%~)t^sSmRs&>>JR-0Jhicdc_Y-@$nKXxWq0lW^_$l^g}M>- zg)%o>C{z_QiFfHx;=EuD%A{Ppqg4n`cNdCdJNF%6 zDZYX;^F>(T6IrG;S^5jvAwQtF497gKyE4|PY-O+~J>%|q3$Ac(I z4w@S=UH!9G+K_slW0IX6A%CLf!m7-=vuif2X@RWGgb@x8QNI|_vwFRkti3=c{az@L zSDl73tdR#mnro2036x<}9H1-~55)LDLcj6y{D3=3%d9w#*Fn{th$p83hn7?dc2Sj! zZmsAZEYFT-g1VSB=AADd;88DzFzv|$7E3#%Yr0ird`bO8(Vb|LGD@*~7bjJ)S zXaeDd$IR3$Yem0=QxZlc%+=O%WNsx-7PV1(nXg&&q;jL!kWMz-{nIE*1}M|?tx?}! zVE6;Jf-m|w*dh(n31AHuSXHw0F8MD#n{$DWI!{^o+0Zy%GAxrbS=nC}Np+^vLMnPt zrTbK?Y;OZO;PCJO{%3=;vKq5$iDoYCEk*@-$?935v%!JM^hi`0dYif@We`^$?r$1g ziOn4;LrP8vJ7nuAi9u0Q$H*&Q+hDK6se+L3UQ8j;mcxZ-7p}mOP2l8npPa7uz<1+L z_)R^1zLFuXUn`>tm5Y1*0h1{9y9IMkT80Q7`%#GLL|w|_t|w6lmT()@ht>;9GCCKd zLTkE?0ucb@WO2Ma`hy~1FuktBQ;D57LRBydo?dKGS~dcSQ;}2!W;kTPVBM-I(nu%= zuwBGQq$Da8RQyzs7a`oEiY1PRoiu~2pJL{YgNCk5c)l2TWDwI)8z@_)N^N1p#aHov zfT5QN;aH^teQVhw8`f;V&Qj}*VpK~hz-W`sj9+Tn+PXy9(SVtoX<6H_aFXJ{ZoEY)7-_kK0Bk~iEx@#TronP}-A=OS%_nrgN{iR|@bJ>`NgVN+y=R7UFhNUZ} zy@0gz1{E6I!OilzG`O*j!lAqsnUQ)^we|qigkNqBTytfR_qGXk?GR8yuUP{N)C5jz z<6R@5l~>qGKmqY8E~(kS(rABS1-E2da1MD}c+fs!EcCe|dgUCXlIH;->@;S`cyYj? zzu~sTK!Da!fMsdi=}!ZJ!y=>O%b>VU?lL6Z%5{1;#vL4mW$2kxM2Bn%LQ_vE_vDub zVj}XaJeFsSN*$OF`lF^}*5^VDWJ!=`17>?oGVMn+=Ty8%7WYD{ zfsOM1`IpZbOy&|<9V=3cvy_6KH8)No{Gc;$%_uQE)@rRb(Q7BwzjU~ruCYe9ed*w++0 zsecqMJC!p|*xx1wtY*QnUcK_gkUq!@#BZyfLY*t!*X@&@PiW-6yIP7!ifY07th(5g*R%-gw2iT z8;od+IGff^tgI3cgfRIBOXr;#IN&FWLBFw^8hUwv)JPyL%uN&HMh0m$YclG9<~~3K z12WU->;WUcK_{}ZAr!VeWetC!*5acP-4+&^^@Yk@XvlF;EWt^OefEqBwLd7f$Yvj~ z7jlo|Vs99~BR`och+*dfU1Q+&jjnW_2Giw`QcxP-0i{%0PeWDsZg3tP%$*R8AlSo! z7(W^4#hFy4dl(YP@qU4KUU}{B0G;ryBRnN>H9cJtT7esnVt==bQJcR7yv1~4N9_)v zHIqjig$0kT0ei)rX;K_?kwt&Npo%dnQaF@9U|uy(wse)sL&OQTnh4fKdsROZH2cLceJN`CKT5JI8v6e@#$wyMh8tAa^wWH!g>XlG3aSZTY;)G1tUTJ5{;37at zlJtl^m+^}cArIX?qXpxQOr=vcJQ4^GB%gvpzu z2;`e4x(D(zr8z4{Sk7qbSJb>B+8Jz_WIb?Vfn9J3^-VQ+ssKDaUf@(Pv#7Rqvj;A2 zr!3au3tpud56v(E$d7CJI%Fk-nFkK5?EDnFG)>sBBpeFTst94@%heMr=5aQoRZLC; zfL2K61CB;scXDAl*-Gu9qyTwB zxhuy6Wa8cQqtqhK!g)QViF2Z@b)qS(vU7f2V(wxq?Pcf|#bJC^!jSTY2Z<_=iu{iT z$KMZ#bh^_IAxuR2C&S?&_tcwxUP51Zw=H)%U0v!f=VTnX16=w%krlY}PgBL~(^7tm zC{vAH?sT@8()*xhvC~Fq8F|BXi*qE(dZCJ+M~jn2iXLxde6`qlTA;CKGix6qilauvG$lqR@Z|3bi>X z$!cF*s%8x)q2yl}**eqc2PK@;9NASkl4a@z7wHkdn}_-G^rWIn){!VM{#u>IY;dNv5oST*&I z0WEw!5y~X{?t>XlrR8w`Sg~iJQ1}?nUDw27(x@G~#tZFw#Fd6sie8psAOz|;t(O5? zy7(|q6Ta^To1n}+@chB=+WyIKL{>1ztLqZlIA(g3ZhYdMtrOjRdXNA<21{pt4pbx3 zZLza;wQcxE!8c#FiY!s*+OTsp%1M+}x zG};^{&j{VKI)z(e6;0kN-bPhG(E0vpurcISs|P(Ntdi3G#PkufFjoRk?P%V{eu5;L zM$55E-33;~D{$6+tU4E*$j-3^YC-M#ty(eXC$+P-y&J>|e%1sB(W<9ohN03#CJ8H) zM(EB>2HHSQP75usEX<6y!s22|2%vafu=5`<=|VGGT$a`Xg~zXBg1Wz7D6ViEQJ473 zWr3En-~uDY6>rW;LayjlFIhyg>eZr<3WUG+$*z?TgYo3r=Qnt#)q_E6KX8mu&g9;kXaH) zPWJ1JN!A$~m05<6d^WlR)>{iP$f07Sd%Zvq1w;mkWQyVtFtkn@G1lBPE~%HSJ{|izHvq`*eL=s!B=x$L2+!l+oHS$ z(~B-Xu=qsTu!|;rdy0iwG4ges1*AGb3U81_K>VlWspM6gucKP^2t`d4ylS~ z`Oe#HT`G3&6zGT>_WA>MTDEnD@kYcagXBF3)RQ2(;yKV-`erznklZ-{|5i*IR8uBd z=|SUlrGa|V+@OLFG=c+_awnd zG*D-Vo{$CQvtP`g5wgP%+^@pZrC3{Lb(&xk&~$pBLR~R z`koOC9WR(KIP!FYVc2OS3VWjlNaTS!Jg*kcTw@ov16&g<7Dw5Lt+|*F0pz!QPkG<)XxTEw?XeysXS0SyZm{S18HhN3Kme zJobXECq~hjdA{xvLkUSZmQKNeB1NcFLcdC)vq>|^$YkfXW0mllQ1f6qYiSN$w zJdp+|Cgz5LCh8Oj1RS==yauu$m4Z=xgXz?RZMixhuA}5gH?25}2kzQdCQhi*l7HuI zsiYM~iuAR^j0Mwzvjtgz6>LT=xf#wSC`eEgoDqooTHL6~$+yXW$E%5m>v!|3Gqs@$_q(&w*>s7NPS?Jg5Wt;i5wqPQ zVh*&<_lc%e&TpkhUr9KZR;aEE)yyC*Unn`;DDTG&5?Di}SEupTJRr|+huUBr)TCl( z0xH%D)m-vwf6Xm=V1a1Diem`1#@tAuexyS4n-o3U5`wyboZSh*sziz%N%;0D0VE3W+L;z1%P>*Ab}i?4Vx!4~(x$Ss%{>@|7? zi|-bJI+n>52d>GHxY1Bibj&CK;-p^^QI{pVK^+M|%7kGe&CxVhO$bX3piUp2e+^|G zDAK4ohCFd>XK|cN{DG55%PIO3X^OKGvy#sY`U1qT^#C4>!y_5R0jBaTy)MD`R1Hl_ zE|456Ydd941+J@>_TgghNW3f;lq~8@#fiECOJ?b1?j@>Z=2P7qF&zz)k}4_l5VR7< zZ_la4m0`W-(P|~E5w$xPkJ@+LkWfDQK}#2AKJF~F_lAk@!JUVITL3Xg)-u)xa8|BC z+ztuP3D|kUFQ`@J!6R1<(p@q2gCI(t=t@6@gK9kCIK_`{7h6pKAZL_N$iARR^AoNF z&DBb11$0-O1*DC3ZWBeTD}h|fx-*Q-vV~Q=*pNy#>0-GU_GQ4N(j&9lk)L5%L$DM9 zSn4+wP88!tlZ&XyA#V0n(eyqtH3?QE*DJuIFrB06#j zykI_L2x9BeH3Xa22ql-kw%3CP;G{g!RJy)&m*OE!6Y9m^!5hr_roNy&G|r{Csfjb& zSz}E@UQwPjkt+d{y~DvzgKIJW!cz=!Dr!~C68V!3=NEWa1bOm*hf{3D?WC-;@s%vN zj&<|BdmxNK=Oq|$z|thTxGcLqL2KZ&dLLwI^njs37xw{X#|E+?s(E3bIg(`@-hg!XA4QwF)%Z1`QP2T2)wwKCP{Q zw?>v5^voBwj!D@vZ{Puvvrmyc>}e+14}pnguB#_H7o$`a&{Qc%kxMs?2@!6rc`z?4 z%NNXvTFJi5l05hB-o%m%h@-DbCArN8l&5qPHz`wM=~l!hx}eUdKp+1dDJY+cj=NGO z1}C*5RZ<5U%q8iGDS;c@8zl=JHkHqnVmeO4c?m5II}_7C2zZYun1BkItnIKX`59k< z)5oRrSKMjPlhSuULX+OcCEHS*siq6=WDhuRpA@**;$}-DMu0H5BIDd!TLBkarU@_d z>K9}um{wML-k3^z2)MC#um&ZwLbWT}Curae9N1DERf1)^r)IU5a#x(jbl;dI#Txxs z(A*Uhnv~atAP#f14AJo?v>O;dg*VUj>*#tb~^>T25~Y0#j&0p zfE=fS_gb837yPoeZ#*h1B4$*QTDYXT8g<{?&ycv;rT|~4>cZp_qad#I(mhUfdMcVZsnr>4 zid9i6_Y|^VIWYItGqdaQF4>T!AaUkgGILTE=%?Mi32njN#h{_7%gSx$XObnF$vjZ6IZp z*EyBD3uvsW3RbUv`;%kh@VwQ+_l+jb3m7Q=dWf`1jR@16C+h?i!r&J={mrpT>GC{t z6iR{*!ZmE;!-I$uhd=O@A^H6yu4XR%twY~8><%j{Z^wj_({Esvl5JCpDc^dO+8r1n ztw=T$r0y_OSOOW*WuWOi+y<4I0}0w71ctY3==7oU_EGmedJcn>HJ|Q7Ai4P%1P{!qFh;3U4oJEsT*6600f@<$9P9jWnB}GQU znY`XIQ0ajnm8(jL^HQCbayGIv|N@z)owCAsr3X^ zsg-e~0d{9fID;E=(eT(yvT~iYtCQ7?(gQIw&*ef{d7B?6VDE?~j&DGiwVZr36S6`n zRcm)1@?=+W;>V3Hz!zX;i)mp=W?BtO0uh=rXkt8&Hq6?IWr9$<^ z(!?gmZBlpN`nK!xT!bb8^m?&M>g1wKC!i#lJ;uDW>7ep52Z|ptH(waeoV%pX)H8Qo zv+R+CYQ#-f^H@n3vv$oCc8~d9!_kk;=$)RpGlDu9FCM6KwNz8NllT2U;>n3uwDmto zo>T(vyp-m_BFQ|&Crl!K7f`{{bb1B<26vS$GabGSSlUC=3hjqwSx93Uyi3n^oGRH- zGFT!mtWU>n+2`gbi)Cpw%E{yi+cI2@vIRm2!Y$-eT; z4j1noKJJAw?B1ZHapo(dcni7jQq?;q6_t>V~&TYPOj5K-p(^+E7oN{(%&e)jLBtA0{gY6mCE#{aA-1wQ9BA7 zON~}6o*3p4qIhKO(*opYvfm1ojswUqI?yflKn9>zRNLGX9SXm1TET%VR2PX?7SviR zP45-4RjBkj*Z%NJuE|k$MW;^5Lavh0GlM)l&8pu~prO{xqBP1QA()avK1}|%{@11R zznk9w3)^vMQ~l46^1Evvd3< zBJvigX|H=D-0j$TagWHzup4=AaVQU9j@b$-P=O8^k3@HhYzP@pTV+>-I;hDt7L{SY z{AY8Y@wx z5pr~eb_sNVtlgM^RuNxcVxiQk_PB;c_b8`dl}=*k?gHY_BP}SCV+qQT z)zF9wxbu1XAqi169Q1^D?8GwIVdrJ2FAjc0OnX4!%RYAM z*#~L9LpfYRl4mr!O6~6k+YH{VyXF?y>0Cw;-i4*81fX=*WM<#J6LMfstE8?bzF`au zTA%qom*)l^`$XuABX!jshE%&0od#lDT+QP=&?h7!(VuYxMKZ0GUX4Pz$&ULFQ+2_wBu`TQj#Afm5{0B_> zF&m*dZiMf-;OFud&_#=bX)se2Q{=2K_X$Gnn2DHkXJDBN4r3j|qi}MOu$oyLmAurm zy2mBXBaK6`F~Z3Z0^SV~h2elSrL0uD0obD{OxoEiiehc!F(j~O;T$eovFp&}{cPlQ zmOf92t^&Os^g2N(#Zg%W!jVJ!t2UDpsuK?1p|@Qrzsxy&&+6*{>cR`DH38z)kFaPx@fRFsEfN>fh< zhJKCZ@L+5T>0`&mF^^-JF?g$uhj0RIL&4(Z%d4anjysXtg-9wo2m?oQ$n-!GS7W>F zqqx}QsAdt6s0=`38GQ`_{e_X!S5fWYb?rXR=HkkHOrgs^K%?$@8b&!c$jyNY31Zmv z)SQ4|6GpgDSpz5v+6zE1}=`efs?92HRJLyJW zN3M>Y;V@+773+bw)m82@>Uo~H%D@th7J}Cz7(l0Fz8C0H<>*3Oxt*XRa9}F5i(R-) zl|87pMF5=RhaiR|NE!BNkiEaV<0B(X<4lniAv=H%lMbg(tOg;1)t8A2)(4RTkw8!r zo5)}q1@rPx%;18t98N2Wec7quUq?CP2Uv4Wf>RymufEd}k}`_Wn9IGuOm|_#jBABZ z)Zs0tsw{|<4UJluN(LJgmx3VhdQ8p5E^Gr+alc+_!kOAH0qu~3H8N*{I+`OuRiVUB zp}t6k%CwuDYggkV#s;fk6+uE?D>VxOY;ebrU5h%|8rr1OHfVwr>>wr^W(v?*#Wfe1 z_*U!4#DhYJ!WweK_ z?kgZjc69X5t_eU2dWuD-zYS3glWkv*WYJitcUU=G&RQtxXawVE6IO@KUKwL=Or9ck z=@q*n!W%LhmSQt)a%fc&$52(^DPZ-4!h$v$A+c7YLnuMY;Xy^xzxJ{Y^oi> zIh$~+@);+nCv7`pC>+ZUSz}__jMd>zL)>DmoQxa|)D<&I{NA$!Ia(_R2OkcyrrFiL z9i#?cnp8bR8GtZz@6UZ)y{r&2NdT>%SIiDn4^N>9MK}A3%If4bC72^Z9QU9p zQieHtgx=fC3!C~^yU%j~mp~O2+cc(0Jui73wO2vTZKYy+jR$))AP~Y#m|7lC2ps)~Nt(qMC@C(R5jz8LKdMg>pEUr-o*R zTOOfzfFtPf*wE=*=>r7J-u($ufD3kfm_Awx!U;k_o+RaAAUcPauTe5sovs^Z9&1ln zk*3E;hDnjb)YeO9%oLnlU3eXb^hBH+T;|Xg#&z7`h21nGD^x|M0Mf#A#CJf@L~MUM zUiiwsg>xa>c}a<4t*2_LuD1@XGIBh^4V!@3?A0z{sQyJm`T~Sm4h6?R`%h)A(tdJ}o z^=M3_xDaI>D;5=05d`)~6!~~WkREBDxGtH2qmEG`Q=+1h$8B&t%7T@N!p1ipxNKa| zY2YO=%|*M|&?yzVqo>c1Xaw}eYwX1{fh?mr=NOem(UPZ}I%(}SOGnV2=B+lauM=u{ zD+_DFnXWb%G6`r#mpehCjX5~9=a6YKFB9BR5cb(*haF7cZXL~>1}SIMQ&ei4MNUw> z8uY$+N#d-EJ6MnhI66C60oOvE-SzbuS5%xxm3&WHP24|_kC=-q&=Njpp*(lq>0;_pb20mE?stOSXGfucJ_XL%r z{X#X8<4R~6QMAtPlgN-@udjv$iBM|}R_q5Dr<_T(s1E6p6AhSCj(|-~Q0Mz~V5Lev zs^pAURClXR`p%UrR8ZG)1=s1NASp7_rQ~juZg(a!1FP@VVtF`%bC3xSs_c%<;Z%o~ zvseslOHk5M#>^xSvR3qX8w-fR5y3<(>W&$#2{n(JRnGABxkj{sxrn%GtC1(lWIf^O zybwpYlp@Q5FlvMa%jAo~JjmWdmVu%D)jt?FrUp$TK!2-DH`{n~$+u#qT~l#kX%t0K z8KhD=K?OJ#BXxK|B`151CiOHFuR!ow16WS(*JiN|sWw?eaDcpMn7WS+i?BkTCsw}g z3F7nEW2!p>M0Zvh^iK>L{+=xZ!;n}6M}Vuo$M`cI!!Q(}U`+CJq3}Ek2?n;q$eh?< zGbBh@Aa?R0?kq)-QyjVz?^qoLGl3k#11DmVSiniteHK%rT4nc}V++KP4f}IzmqKn2?d8%u*MTuxA%MYBvihD0Sd1zj zl9QG(Lk+vGp|pxwcGC8M5bG!B$W$j-ecbOJlrRR zTAv2OuqLyKvLul$1*uPNLu?pjvp@n4EC8b0kq(~Tx~d+OVPz>VuBkS5AjkKfO^(K> zi#frN!C9GcVy?tO98>*#5$pmGh#*YBmJ~iAr7>hb)#0Fm z99{c@n8^b9C=!+v#jGR_LJa1Ttwsh?1!WmfCtogID^Gk(W|y;%8-1hNs6aCl#6$ct zRKR7~GDlM9O9{HGPL0Z(qO$6yDPrnyZ1J~I0x1SF2&14HB$9(0!5u~hvYMV0^fSXs zl95A{g_1Y|Wl6_uM^umUIJ@N4RpZ$jM3P?y*;VAVHU zI+Sz;feu#s0%`*II*pJ(*%Qdk5q&&3E9x|0U!Hb18%SV9bJ5u(L2V1NH*9?DXoJQH zvqDb1vkFR)a7i1V4#*VC!o)l|_@sbC1#IkhM3@!U=J<#df+b&BFk%?d&|VrjF{4z4 zUElFz8501=-=p%06sZUh-PkW)SqG2=V(<7M;}jFLX2`OD+0m|-7|BH2c#UN~PNOKM zu^bJni)URlOjEXm>&pcw73e^@=w)U)bZIin3X7LRVFXH|4oFB@wSjLMQC$}`m8WK@ zzMODhF$W}QGQcy@RAbKXjVy(z7o_h*0OBVDNybt1G>b+aXeKC>$Ekq8!d+hq?9r&q z&MqLls+l16_KpoipOUOzw{XgRmz=Ztt2g?!Gi1Gb8lJWk<0vyul1u=2%#F_Y4v!b~j8Nooot4>39bZ1r>a zVoem8JBW*epOBGC3yz4`U=FGgP!7w4 zsF)-^%r0MS(F}!8sd7j*_Wgxf%?<$r1cYBn3FvQ^T>T3$sqy0I#GT@+-=JsYB+YCB zpjzA$0B3rvTzbYKO$?L2*f?M6Er;P7S_P1k#^tIGFr_7wc21G?cc2B*0kqxI^xBgb zSC`35eMk~52HCxSuwX;Zq?#4~nx~E+3^JRkjZ2h^+&N3Z3t}!d2&1O<@DvYFVit>1 zAmh@I$3)&C%I`*x>QBg-W2CsAn(MB@6op7RTU|sDf-@!Oqw8061Kw)^xcH?a80Oo> z^aC&%E;@X2>w8Xvk-t4bVh<#<&EW+XBlCwbX_>=M`|qundLJkrwtXu!8EgltOIg z66E(WjT>?d2nFPjQ*G`N)FCx?lCfnjOpNI|a5!u;gylw7hi2q~BcyLz;do?h2b}cH zS=XtYPashOW2rj4$W>74!&a`8&WIdx2PXnKW1eT7|$b>t~R9_2mjk&m2wm z0+p4e_doXe>TzbLEsZ*P`}ZgtZMD{IuhYyy%Eh)U99;Rk1Qq0?=~&wEMSYY?B12TI z=FCUTeDlhsBG|FRB#MHiKtd7EQG+@D#GH$v(y3A#9ISc;C5~B|QfMcP**aNDcWRU3 zAhdjo7A|SqO@Y1GR9{x=u;&$&1Ff*IaPqVnaPGxptb~W!Fg+r|@>I3qb#@KaOVBZk zo`Rj5lP9^EgGNL##6++NIYG|5u~JZ=_{H~ta4shq9; z7QhQc*IAM((=np-%I#$~WzrZz*Ed@iT)}rBJkcn{nb`21!+{+~9YH4Q#qCoEpUuy% zX0{vNToxVCOA(|Yhu9z{WMW>a~%tzA{M7hN5xSiynIl` zk$HfaB?p%jw*?|L&DAaY4Z+L)Y}A$^hH}eV_b^P&Sq# zNXE!+z-r7A1K(pnp8>NaKqx|7nyqXK4%euoIM(dhU@TdNg;gpq)`u|c=p?av{Y7G% zpwMFtQe21d!E(6-fC0gM#_Bi1+JFNE*KQ!PvOp}Ezo0o1oqWsR0ec8;2v@VRaj0v^ z`F9q@BpKHpx-76e^cr%q7zSu2%V8e^oLE_S=XD(iK!J&p`cntAR7$a=arv%MBgSgrj2H%L0MH1l>;|CN;x%AD z4#-0wF$Q7Ap#qP{UN4U-NC;E#YXAyapm^KkU@pLR6Fg0A*c-?M82SZmI}2EN`a3OsLHDIT=+ZlovvjIm*fq7mpln zIt&-D!(c`*x5CYSMPPDnRZs?OIZHg!wt5U1#b@-e>MK?{9WS!A1$Gd^8xT@29`bPj)np-gf7>!fvfE^4p&LS;;`BLI5XBuJaSsr& z7HIIh<6I^x1f$;KJ&BdH8w8g(gn2!o{# zeZDiMn??c-UpI~A;Jl*JaDm~GHtwxMA&$TlV6kE6+Loc?4P%XJMLqHqJ{J4=wGUX!xxo%j_P_EK`_@OVaSKzwwcB2O&QtX=fEz+#I{irftZ0Y$D%SJ zc-lxw0C0h0wrBbnD~@fVriJO?uFfchIuwVGuxe1Q2Be!7xXr?G^k=siFsn<1VXt)p zDclFzv6?gkE5}TeFig`EsHmPc*P;Z>baKu)LjWIr`6(XxlG|&$4eN;ZpJV1|l1}Ha zT$cZ>ff132P`O!)jpf8FklN- z3kyRk~J4rpM-W=mWf1S1Mg=7 zM=~bteUipgLxG$}oPc%y+7wH|aeB@+ElxKLHI0PZqp1KAISEC$Apga4(z>oCjxQi8 z**5t+GR>o521f`l%>~~pFXjG6Oo_V$WDYLyY>7#0;rXsUZINkPY8E4}L`7ZKhS7O# zL{@axMs15<=Y>oQtgcH5{egvo78Au&?eSa!k%7snJCi(t+krq!e9IqRx(Gq|c{9LTq%&GUm1}S*Nx`(`4XnaCjRrdi$B8J*jnNsyO{k95 z;8Ai-k(Z?kpa46Ow17l0pnP08WN>4!k9$}gUNWvqWfG6R zPf1Up1_bGP#i0VVN%qQR?89T5#uS}(VCQd5_Rw`KY>8^Ku-+%je7ALGl~eyg0u?$! z&$$0D2xrFPu!V|S^odfJ8UQu=zkMK>QFa3qWrBkF%kBzT6NL!~*5JQ90!hc8dxuJG zPK#sGYkHg-LYZl2l9eW4Fd6Y*LBK53Hc$!)iiy}Rmv9DZ<61Fm4Ma%O0KC48<*af& z#trIh(}6oM4a%K-wZ+4et@>>O6l`YY@|rTqYKg1?RCC zu0b4(;5+>d+EA%DwQ(>h*)=+W;T)KL(WEq4#xW&a&d`+P)m6l$_)26@a2=vl(lfEM zGZHS|6Z4tvh!dMU&v5!OJD=z3aKPLsSv-;sC{Z*2fZ5KudCs|3U@CJ3oJdm0?i7So zQ?e@H1cV?4mQf-uJK#cKRD>}e0qCSEA4M zkQX15GtO3hgCpGx#Q)e5s+KZ64nmp$FB1Nc%97Cu!Dn!CN*Pg9OcNnbAykaXh~qQ; zBQiPkTqYbMI+C`g6fi4=op515;~4dq(Ex10ZPT^~q+72lri?c{qCw5NyGH<~Wi)%A zHYa)~b*?B=x94C;AHbDsWJlui6Pn?OyMYt^j%AJ_x6= z=j!yHb}}hh`KTl^EMwNQ$C)u)Fy|qG)}hCAHD+G6dx;i%O-zV06B+jm6Od%K)=*2S zRWZ*#i$h#}#A6~7-tsR8b{_d)WJHN%$Y?O_R~RRezdbc3f2J z`U*f^kJDkR6Tt?qiq&6W(6WyX_kTXOD5h(nLV>=pIJd^yyCzI@{T&1#hX@LRNP|zl zi9?oH*jH?aIu30UDN`%zI)s1`Q<{bqmGNm*tk|%-KJU`k875c;d_!YD`dAC6Hz=c5 zW_E=Q4@V{@^JJyc2%|+anoZ?5(+g8fXkK9=+bf3ddoNqjwTH%#Y0jaO1Is20hfarw zGmSyM+u)HcM%ci?rJWsL16*bIu6Q{G3QSOei78;D2)*;!+c8YpR?IF;JSr{0W3G|mXV40#n^5&>UeQ{z*=D3;kpo<(-LjT zAzp>7o{JI$$KUkh)}0!T(Bf$xgHx%pgOxKVE-Fp{CxqFWn;8IEP`f#50m%fC7ZSOMK~cE4 zSb|`1qlaDffx-r%*bOikpD>7}kj&8G=?YkuM+TQkK-@*R9^>yIU8|D|@zJ7YPuv_z zbv%5iQeXD>^e8!FymT&%v3;5^RO60=L%a-Q0Lt{~d&u6CH8R|>vXGiM4yssNa+jY& zLSi}9D5-7ug{G~e*b}oyip8w#Af}JH2D01HIXor|TsQjqrW&}~n1~A&Aot4X0~QRI zrD>F-X{>Dq{etG8_%%pa4}3&mFMjC<=vNzlj$Q|3@$kzJAGkUG-e*V{7`(_wkj#l$ z_5`R^eQo`%NCqkmt5jX$(WenpZT1d>g#&4GDCZ5F0UcL91a#2&JIDwVQcaUG7Sf@c z;fxB`quwTyN7~dVyH4ZLNYZOi3zng`xfHmVb?o^EP&W(elQruJP-m}!9E@?<(gb`E z8X%g1obU_-a0;ti$C{{&nLi<5xzV#NCHOReh=Zu!=HdxGLxNJI!uYkqkieWrrWCD) zlz{Pa*d93Q73;9XpH;>Y=l=F&C@$YMl(h;-m^iR}T~7Qx1}fMDasSmViiw0i>`COm_$jR?b5}Lsg2y|JAh{9E<+jp#Rgx#nTL0DGbEJ0&%+Ln zQIu^+L|X5+uJgql``i-6p~@8t$ZYl#FtAwJj&HoG&0LLmzc4gjM|N(Fla@mlsoW!2cR2(MlZv6`X1Tinz& z*=ONAbWZ1~$SOC;YQ$slbec1*WoQbR`vS-$=tc2i9w-Ce`rfty$Oa7GJ&zp4sJ+UuJsSIUd%Q{EuXL*G zgmMyK)oRhpc`e62PYEI`Ms0|m5{){KaNA^{<0iTj zX^_a3*7FblCuxXaSnUM3qWo_EEr3^No-9Aj4D5Tg_(QD4Q&xI&wsF{=9&ym@X8tl#0v}MOtX7A&SA?@Eo^dCMNSx#8GGxSFP5U0HtNM9` z*?@lsH=Pk3%nmoA=qxE45g8RN{E1HW_56zJcrqsX=>qkf0Dc zonZRjH7^)=+Q2epZdbgd=|B65J7KaNlb3Y(Tcc1OAB z!e>~{@`?o4!95w_ST@9OGVvR@M>H6R%=$X@~4JbZW=gdc(fNrAPuZ zIfn`fw}K>t!(z)ne2Zx${o1x|Q0a72-#B62*tzSDHW(7z{R~%9 z=-!qOi5`z-V9}bw0=;G5Bvdg?%mdS@#A3@5pY`H5A7QFU2>lv})>+w(xPLUO7midsfk~w})OQ{5qa|ugOd#@nG7_QN+F5IgGu}89?9Cdvc zYAX5o3YP4J)hf8~I$|7R8Wp*AJUJZ|qTY*gSPwi;voR9dn-~hF-6bHH(guOC*0w!x zVmh37sYOHnWu(G9_DDH|mx&6unl+!5%pgGsH;$(Ift@Acz{ui}FE-s}W*BiNxL1)~ zV$|znq)|QM&HEBqj$5}73ggJy5E7KF^VWVtagT$!H9Daz5pAc|LfNJ$zCTd(!Gu3R zSIUOz@?m*SbGzpA?-TW>Nqh3keLBV_uJMIwZi9+L74_24vUG?FlwpHppk1Le z^Kd0JWLGs%ITxy((s#hlUBqOeA~=9H#0pnHl_QIWP!FK+y4X48}+ znJi$*Nk^?xFBq8oD3TAdJ-+?WN{Du>zqNJ{C+?8~oOa82q^nYPP)C^ZZT+6&ELz_G8sp2?b;xGamlX@@XEWZ~WMXkjMtSS%?rPOHa;uS){e3D}+tS|iVT zHIS5&S>x4X#kKZh9`bc|_#6)Hxp+~!w#&Q(Ih%>YU$k2KHJf}1K**&A`Cp;V;~?_t zx^QNI{}FZc29w5g1&X^8`JjqO+yPP#&2~=qx=g3q3ts!G3Qq=KW9%qn1zZC_rtF@p zrwFEEvb~rRL&yfkuz<3{Bh#P~Zx&83{uN2F(t^{c$b!4;h;gj0lEBH{yUY6p>e*rlQv?zUwy`HiWU=E3$U^ zYFgaw<4WWZ2BGNMgqJ-7j2TS=<}qd2E22AW*4mkc#!XS8>3}Ux^xW6RUC$%EMrtpR z`y(z&re=vuWjgM7-MG&3eF8&r8S4d5b6PLcV&UzWuHG{yIqCPa&e)2oFZZH0nc5WT z1ZQ=Qa8@R4XY7<-v0skYayxwDR3ID3t=8+nC{q;Q^JjTEX0~*YIn~{;tjyUklp2Y< zemsU@&9NAA45o)_Nmb%&NvA>Q#4u1|m8%_-P|Nfl8Dj6d%4Uxs2~i6+C$PhHJ2Ens z^&N1tYF{beg)*CCV9k_O8Sp6NZ0JStD=A(N=iXs>+39Ph)nQ7R7nU+>&7+{!cM~ku=Jy|#&nz!)9N6{%I;ntZ`d@5D6-QbEm@1p@r8I~M`As8XG83ilq zz;(JUxcQKMD7Ei5C1IXiaW!%XGnW8{olOoMm)ta$(xBGj63v{eye8vJI7OQEI%KV2 zCu2x@_}a%`nd<0!KR}Yuuyc6&eJSO(vqqt@k1rD{Ogk{f`C@kfa4j+hqY|2-#B!Q#g^9+L?u#0uYLIaL;l3kktT2QaMbsaVW>`s^|h`QX$Ui z*d9?@t({}ye!@Cvh*U^OHYr)>nd@%Uj5_)74R=SCP0pC~1=okc)l3kh%YsR1FfTz( z^2mg0HkPQ zRnrtWnX3(u6Yd0-zwyBoSpCc-)&{rZDqgc!7h!28SD#(*xZqnX?|wDP;TTg|qXUi# zT(_tI-egbqfQ>r8ULXkxmFmT*17P=rwX@H=nU;(rqPkeTn_y!cq4d?O&gf}M!Bol; zCM!8Gc@f1aW(W${piH1J*8~BUd@0OxV#z3fH2W$#A-2!l= z8)xqw<^l+9e<3F#ZlueJ-?b|)wNJ{(lYDl9z;r!GpS1&08=a7V^zNu{9{`3~CVEgR z7@%p5voFvP5omAT++Mx9-CjM*@NWfWlL%=Stl$pjy9NJYem9R}oZZQ?dO*Lxr)! z7bDU{G2FiJ(NQ(0CJWfv^1B?-v*im6p4uVU7i7V34t8bIa_IEnm2_-hFb@UE*KRG7 zz*Ck_9oGXgHC(x1Qketz90iJ^PVs*)nrhPmNdRI9?FAc4S@g-2LR%Z}KTWPn#n&Q2% z<6GAQ=)U{v`&oV@jWIcuS&hBJ7*di(WYg#q0hHLI8r*C$jC~*7)Lz~` zArL`n{g&+)B0yz{eGERqfiK)i{Rv%1VQ7QV?owZ6anBqfwP#1;c#pS;|9-oDwt{8^ z6VKO**W+3Nv(fn$dW{lxoLNNhb@-j&hs{i?d9BK2Tp?BcYZ+ZsfiCLyniL2B+d$uF zrfywS1``bRLj_=pvA+4&OH@^nY~`x1ccC3N1yq0wa2BvIYyDK#1R11I_DTPtRsgUL zir23=*}dY}%ke{1-`;Y*Mm5dm(n)&-`jM7e5!U!YmH(Y!N7|SI6;y3?52#~u34SB< z!-*2=_7Qd7Zr^wwMxF+*h`$U|+&zi2T|iw0h186%pwD_{HjbBIs9C7R?Hs6(k0&Wk z3lP&gfdk>`;q9}}>WC11UOHwO|2sX$?$>7puY@^89%Fx3-oM}e^rv~w-){elT7LR( zuIQ&%k{!2aKk+WW6*Jda_6HVMo}WYjE>|g*0*~;x(GvTw%_f4rX#eY%H7QFelfm`d{9^tpYvJSVg4%^G>5TkPvJGCe_bytnIli^3l*5;q- z)Jo$q4DQ+>sdZ`iF&gabApQ`bzBE4{XwWWe(^4qGGs&4aV)g z?3#H1t$~WMGHb1*MaH_3>Jt97_!o&|#WBPGsjBeqhcRgK7rI?jFfczRNHo zsk8cM_`7Kv7TxsfZqb(S%S_HMzp&Q%U^8G>0kN3eE@d&st?}))S#DoEw>-G8D197k zs|}~ZvmY4jVRRo3`CJs#(GEVI;8jsCuPxF6wp3j+8Y)kgRs&_YD=&vOJ}zRl;6v3e z;KGnBtASWTEB$|SM7=J?nk-!I^u?{{VczcSF|f3mcUiJ4%ZA-_R-q*9vst7(F$8abKZyEi#uoJ@)tI`#p9;2q zlWD@YPc_J#wuEB)AQEPF&L7Yr0Srd!Q_Xh!EtcPWLnf z`Mdw^w;aa)v}50KtM}cX{6_TpJKr=t`_p$+{{*W0ot69IJ8|&<Xvvn z;x?+6bWnkx4e)h9{0I1Y0dFs$^46f6Tl*)~igMfg>`%JY&~!5|N^m3p3mh$&Zr}M1 z*;OrQZl#$I+{*R3Z?Ink)w`phMm0%u7t}wo1k4+tGD!*?z}qw4ZWfSq9|h#?H|`Tu ztOCJcDyG)rD@|2*Re8hIRJkEsnMz*Ft`qk{osLXi45KohQCt)^5#WZuqTHRoe!b{} z7m9lVnU=uUTD_$ioDdVMVqaTGF%X$n85AzM@TN(;$Ge(8O$E^9%mP-ov?2h zzMwbt#*Qh?$DO33Z5R=v0LA;5Fjb7N%e$hcJNg>9h6ZEjv2n^)iEC8zRzWH38qk64jJ|5&XD)qf!* zsCvdero()^619_8UASI-_xhFIBUr6@HPg!*(ASc|^lKiluaBhe>w_=aKV{7$$!;yl zxTJEuRx1``X|brUD-65vdgIbgubz4vGaY+b{kn>LGkW`}E?6E#kGY%EA{^;A^cd}L z!T(x@i5qxIkFgATIk~MlkD6}iu2rX1*>*nSKPIN)sH2O~?Qdt%^`i}lF4^0G?l53k z4GHyFjmi1iLk6`GN*!vd@sHM}KWyvp1AMnhT%_0q$i=zZdn3^bwpF={`oZlsh1hQ& z;j0u|9%}t|#WSB|C>ho1W?7|)Bye)K7|WQq9e!xHT|ueRQ=AJu)~btp>b`MKz$?*Z zOI2IaYST9gqNJ_566gC--xDTrzC?F5_xtJ9rnc=SO**>4GE2&1d1;(QnYE7tt*Y$q ztQH8eJ=Y{!$;HB*9;IRF?3=}P?-t9l9JI|K_26I>u^Cj_FzoqN7_!M_pDd+O6m6^7 z`eJI4cPz`!-j<%gBR2yYE9BspmJ(o7KG;PQzfxw8^w%SttNIjRM6MPArd6tST5#0X zg7;$hhuBl5aFi{~_5cE8`!?JwBNkyJ z(0rT1dI?<7H%WFBXs#e&6cz&QDUiTZwG~s?>jr;Dp#9v!`<14ol2plyZ)-_3D+Jmz z$~%J!1^AlUY3!}fZr`@3SkV`SN;kn4myL9*MZ~Vp0!oS%wVB$p(re?mjF6>$dE$72 zUkhDi{Qw8u)K~4C*V1RY0_TcKXX-QGxgGRbVSfGnWD&MS8}DST*>aFGGTp`eWH@z6 z;r*?8bxLFe9BsD2GBzGJzc%eMv|NR|ntz!N`mRtv5-UA8Aa|s}^4v~2N%&)%sliv3 zqXb(N*vgmdLuob*2alvcvuy?yLF%ey?pDxi@WZCd9L1YSd|H=@d`ZpB`3T+=VbWk% z@NL82Uo_jBMa#Ya-kUF~yhWQ4ec-LYtdGdm4)p!zG_=`1cgOwV$es%9v85_v=L|Ux z3#x8wFa(Y6`yzf!Y1y_|zK`$00@xO>Ax}JVTQ5UdKuVg`?PXi;zraFiRa@V5`^AMY zJH}DwVjnFfz7* znmL>m?LUu-^)dTsQCKxn-?i@y%W37J;G|)(a$+hk8`Y^al$jh3cxI*?3(f6@xw6(-Cz}SAlT5K|* zK9dMLT;!VK3XwOyU@{#wf9tj?}A+eFvLXN_|2^qo^h2U)Y}{j5<-!3LKnWuM7Q z>Q2lJd;)>SjF8@ovkvaYlye& zKsA0d3JS!wu=YT4wAya35LeeHJ(qP#^Tau!>&p~Uo39w0}Hb=@xzkh?94O1ua9?5Cd=DfXaS*d%4H!(I&#go%pKGHp8OYR$QK3tq#)c5^Yd)JTm_uu9VrvT$EW-+5G~Q2wTw< zCNj8v*la3HvTEF&ZU)J-D}w?}eRlip6mHKqfmZJ=7Xt05&-kI<-^UW9$dOnR;El^7 z$e(5W6_zpIwbS9aTW%FpId_x6?++%q=xaA|KvdtnV{ZKto*$qMrwVwt?P-%E;`r6= zb`T`??lc`MxBt%(ok!EuY8NJEv?(%2w^&ne7i-g`=|2DBK2zC}!-R-BS}fM^Ia&Oo zlp_1bEKYk^oo7EBv*(3h*@q~@Hx`y!K5bdfu{v;d*!+TMmvJjjoJVpkr>>(Y2RFvm z{JF$BnJP&vFO|Io+cd~?(Pb;n4R@V=1)r8<=G4fWD=G?lMGvC1rOk;SHCu|=HJd9` z!5sOayt-Re2Er>a8@Cne^dQ&6#^$>1v`~FAz#=p_Q z{f`(=$vF!*ar#kJxMx(9UAPgJ>u_l_e?ro^7BC9yO3i&cs6&itdgr-Hw?&KjJ=nS} zAB#Gon!qF7=0{MxtF`#h&7E4QxgA}^Ts_s1eS#l7%guj*d0%}14&jA+q~J#-yrO3M zu)b0;Dv2j`%@ne`Q?ObpAC+19T!Xu8D}}Z`XCsv|p#)1n zv?KyN$hNoR{H=+K6ZcYR8ZPcf+2%wgUs`Zg?F71%ZK8jJ%^!rMqc&t49-}4kZF$gk zlx?P#(qhitKD<4p+M4B=$t^n0D6t%K9eKb zzVc0Lzh)NIro46GR!>}h7tKE#whgc~)BP zFP!YRXN^)L?EwGzKaAF=Y$98_+kNaha23`K9%$zR5>_>E0w+r0& zqu^>Eye}PBH>h81O78tB*batO;tdyK$YXnQmg$m4Nh%>D_4S_NL&VM9n+FR~W!d2$ zRc+4VBzNMZb+Ee6y*IAC-^-*sR2*K5Ne|Wppv ziq((XL(_BJJLF9HA!kbeTxapPjYhENzq>TK^pnbxr|&)Gci~m0Yl~b?;c{3n<(x8Q zu4}Y$l>ITo)dD?@Io;>V0p{o!XK@@oQNM)Uz~_8?P-EUGJaNY^M5D(t1qo(m`vly7;7H(k4Y&0yEfP zsj&R{jumHiQ?*q9?Z7Ei%jxd4SSg_9R>=j&s*R6K&a6sUTJGJba#@1h@5!YX$~t$w z<@V1fWlv2gykg@6sBgfb(HazQf+9J<=FOY10z@|vPOYFBO^^e{AI2?_7eG(g?y+bq zvq$-1&EXcn_QY^nYca)}1AJK(^w|Br3ARPXnc~vIc1U|M0sYIJhF)KvpTvu9eEGrn zxMguoy0iIgy}QY8-V5CBc|f@x`|R5zH!?@2{G^-TT2pIuQ*RPVM6giP+etlkLzD5$ ziO)~+n_vmBfZS3aL0_Q(^)vP1zJi+SSLeCnK`Jt-Ie_zDpfJt#dq^r^35?d8j_=$J zTK^m*-M0}%q{>64Fv;wg(rIcubo=(j3Q7YEuoYdaFEb4)cfhlFBSB(1Pccba?jj6# zl}C0jNbRHDGVsGS8=HYM;*0TujR$EZVPtg{?^{qAlOWqb(*HW;_V$%rym~3VKlv!M zlp6`Aa$^)Jk=`t9{g|VcV9Yt{O7U#9)zJ8?97WW1QB~WZnw%z*y_Fh}zu!`B z;0kHURtdiNtvc}4#2=r4+!fpHy~S{A7sdQQhVn)PwuNBwSS<8Qv60G(?cxk@nbDJx zc)q{Y!@^A#@ zD1xKc;?OHc9oPk;#Ca0vuO0us)8=u#^Ex-`GL^!TALvyOu` zqic}gWNYWzs~Ug#kFYE@W-U{Fj&d=m`w2d~kmJ3}((It)3Z;-)3o?%2cWow09crL= z*_l0pZG98Oo)&H$;2Kl_DJ1X}q%UyOUB*5UHX3t)bfQTbjcB|--&dd_PX-E2`3I2E zaXLDX_4e)6v>4OMW}p((@t_QhuM}J7u|radX;g(qr?p=DSu2WMNvr+OKTE}(P~G~R zq#3t5SAtFGS+FU`jtVGgwu-w#zy0>x)@DPn-4<|uB=Q7Wo3%SL`;M3hHifj_TR_eE zVy>2JaU5J!BmpAHYR}K)gJPjMSx~e6&Ny!D7ZtZ?vqXS-3ASMCOuUQf1CeBNq8Ic6 z((^B&Ez*v+W5Cv?=G?4hbO4Xy469rXx| zf^DqN#H95$xrXymtr;6yVG0^2>Ir(F&3ww%$iPN=DWkR_Taf%d@lbjLk=; zBlg@-9@Rc9rM5y+9CBL`K8K^vMe4H0DoX0`dst$eXdp|AP}?w^h8yd<1GVClF#*RN zYu$`KMp#|4ffwY=kW0kWO`~`{_)Lh*GareS4?|UGGy}TswK{_#fG={iPNT!j05!{#Gy1^}dCQT)Mru&eSK6_U6_pSxq zs={V0pBk&_UX}Py%ln3Is4$yU&V|WNHqqlgcz9|21(S&4bHqEOG@F)N?z`acZMu({ zhk3TtEVw^xc@k=-UU%KKK)jCpBMiOopB?NchhJsUg@apO$~ffDz1zj-BkM*|>vQ%2 z%Q-fp^$d5*y=X?+j0)~G090F~nT+N?E%%cUi^mx!up*uKjk8bl0=ug``g4vAueh%KZQxYdm%|ReMU1PMeC0~& zlel9>iDfjyYdrYK@os>6=gAL7&NvEfLGh9Yg$iGq6&kh!JapTt#pI2Q@MkQyC`KLi z6Q<9P`hHj>8q+6xn#p$^fT^Eih{ju(hVS>C>9xKi(MCu=KohWLu@$XUtvJ)m-Z*s_ zef;r4qVZ0l5I-HJLsWd_F;$t)sSIR{EDKA4h5O4wmM#>1$3fjJZQVxLsESpsNK$AQ zwX-ZhFAds`3B#546zt3Ycd z1So4Bvt59=euq0ZDQ1GG6}U@O3Rlw(YB#9d5}!c7_Ex4nPN?tZ<3Gvc7QZ!{eBUY0 za6ig3=QQY^Kcbj48_4a}yzxA$7pS=z|Im-g5>#7(GA*J@qcdpj6ejj+{XA_|XKib> zqVbgy+p!mT9n@K{MIihf6_W(Z{7gKkvkPqf5Mv#iD`=Nt-zb~aNU};WcwYSV#n}#! zR#eIf!e9XjX=M`E4{vY3vb4$2y3D7hCDsU^h4h39JLobFYss(>&H%{LRvBh^08Kgr zSmsJ7pk6~f&+dtCVrrKN@_ezarL9cb0q*-Na37h{`SHwee`#e`RF(ArqTqg-W?5FE%0H!+(gNf0n3bA^Oo3B=kz;^I zD)H400gQd!5~{-(4^cYi0d7_Hg>f!)ofJ8wmb=Gk z=s1hPyn`qkB4Sl#Cf3DtlWV(S$hbX+rI(pqm6G^4MwH#M9NTl#XU~4_+uz!^%c{Mm zsl)XXwR`0&>4j56y}wXp2h;OhFdRyzqbf75O`8@~hUxp5?qfwYRzw;mwKjPgyQwp` z>F>WeE)q8^ypNxx6jt(M#7A1*DR9Sni_KAyKa;YoDhrkVhMN z?t->x<1>|5mS%0n#WBy`Tb!{xchTxNEDoxSSXUO!lj+6>PB-a>)sV{&#vHeDh|;P< ziS-eVO*i}}esFC^f*I`2CmkNl3?&l=#ss=)C1L6by}eDv(t+^lSdLw3QHd%BDwCi zxQ~^>LUvt%CEU#Q1~DetWjQAxcfp!)OEi-TRd`FZ$`yYOH@a;HOQJmqekac%(Hu-0 zKz-(NpQ@tMwEH$aZ$njg1#L-93sv15#PyyTaOY(D0cX%s22dxscs>N{ogBwWUqSWJ z;(VIbZO${}$vrDHF*eZ8$^i2|n4Mu zbl@xsAV{OJ6in3a)@Yr5#UQ&KKGq0!2}LYmmLU7atO;pZE+DkVlx?cwAjl>{SQ^ML zm1tzo>e^MCU!usS$60SJ!q*{B3@`eOVA2$58t&@Sb{&hBWY%IDc-=~$tpjjB5^H@H zBI?cmcy0zEO~-gS?)mK^&V%*3WX*O2^Ft;{``hjJ^QRZQMs(`D)eq1<|9XD<&vVdg z+uDrFzTXR8>Q{cP&uktWPCdgCce8`qV?BsmX@=473Zhw|8`-`4*m_o%Y_XKf~x% zpZNG={1eAGABE-Efw^57eAm1fn){CZs?(b4BW}abzxmB?K0htSP_6B-2)nv@?|b-! zLWwo^-W}gT>OZ1Bn+ImoBPZZJ0drrtZFncAAz|+1)4nlCHxYeXuNlUgQoj?ZVQ>3W61%i(|A{q@F-1 zo3>l-LfV3r=Gj=m{(k+b2lqZxqJ^5RCgR6uSCOU;R%o%54}NXBK!@u|5mW z_*7y<>vmTp{#!NYI2s~|9BP{6p~q)V;-WN6(_#Ztpc173=($(Cul^RY~8ko_~j?am3%GHpkF5=W+0Du;n; zm9~K@EYi)KY{Klq+b~}ljXc_mx$D$@#+#d?e!D@u4AnO9!(i(zto}=9^WKFK%fvk* zFl|c{=mcK6YdJm5^;7)V#L{02EeMUGCo0xWzo@$55JM5m`ByhVfU%_3%pmf1veoq=}PHJE-yY!hi8>(6I0iEbLx?Gk!e;VrpT z_kJWer=~_T=b+Id!`ND_LE*Q}EXBGv6+*ub;mvd9u9j zRqRorS+3|>!$s5bj7#p7iqp&Yp=!I-XV{AZg& zY~af+b9ld>^h58lWhZ`cdovcJEIGkju1>m7k`MGj&7pwW_Abk+(8|Xyw@si83pQCc z5oY7HJ{x{?c73*JJXxL>le;^$IgPe_FLW`n+?X!)Sv`2YaIueEhe`cvUBjixoC{0c zARjC*MX$QCEDg)z!)Q5iqUl;8t%k5F*f5|uJUC<#EW_o}=piggFC6^acP9g(Hr2T6 zGh($*S#(hJHENVyLQGp0M8Q@=NjHp(;f{we$6+Sj?wkR=f~x_-r9zX3{=!JkXXSs+ z>2mw}>7RHk4l^~k=~oI3Tndc>GQ;$Q`nKMNKCv(nMfdcK2si!Q<*gsDIXCfC13!$} z6`Bh>g0NMtn8VqSwP1S7Rx!_wL}-`KwbSI>dJ#1EcJ*KQI3VXqZpEvts}Ki}TXc!1mD!H~Gx^lG{mxk}M{f34 zsm%N3!pFtP)iKGIwrzLz7h5#+bC)0%>%B zyfhCq8qTBD-v;Ti{KlX72xbJ0rjxU$|J}Uu|A(#7-wbKY`=?Iyzt$CW`B_|XK9wqZ<+sMa;uFVDKYjIAz-Qw>fR^b^ zP%dw#LZ>~2E429IzoMQFF^oX}rvnj51+N*V;Xh+Yh}&%%gpi{R`$7bLNZ!J@)DDyV zn_1FGuv@rX@6A4{_QImSYNl271P?y9Pi6V?%k>A>X6Q5AZ8>-h%Lj)F_>)VY>c`Pv zez{uS=rGUoW5n9YQvryum_Fc-JS}aQBujp&Y4|+<;DhD=vrZl!PwCF;WY9b?oINfL zN-^+Nm1RK^XyR8ViYEO#hqx){q?1zmr7( zzl?KX;oMnoZ0-652b;N-_?hv(=rl)D8Ke#tA1#OeNKs!vW|W)R&$-iH*tY+!Ojtx% z+VAvN0Qv8Z1s^Q8-|2jg>FD%MlcOEI9Pd|JzMzKkS*li77Oy_D9hNK&SD691(`0cc zd00I8)AVQRJ(g4JIGz{mi!Zy_)kB|ntSPLh7M-=-s>eRadz~F(LmTY4d7bRD*Jl!0 zzeNZkq_U*#r~7t3y66cl0`w7xo5&F&4EV#4-289gCj)HaOF{f4d#HuU59s~?>UTN6 zV`g6Mm{@YmbEAswKuT~LDy2Rzm^4l*p~O3_8XHEA2ymC^QB=CJ&XEv}Fn!|qOkEN4 zMofvky%}5Vq$HGlTx+`etYSh$A(2`nM3dk5HFB3N0WG@RD_m^hY3tSTzc;pm`qLbr zea6+>e=hd5{)cM6IzAI@l*$1?1qJ=rN9gP34yu?9zBz&)DyqO23Hs`lZmwTHGpIiV zzk#eBe;uAb=C4vi)xCeAstr`Xr1j?YhoG9On4ru3lmi(2*FnY4|LLFD3Sd5t?~oxJs2=KwmQ!AaaOdE<<0oW(Qt@x(|1c3ZFs@igh9oALtDBoKhe z8el@m#=Hb;X1sz-;`EckAe6OE9*G%YUKr6oh!9ggQs*&XfhB?&$SVoaj|kBK5=tSQ znyn?(99p<~n2oj{&$>Ated)kp|L%BHgJVD3LfOsc>(a6)xdQRB<^{mZqOMB5C>c}{ z0rSl3__HJZoJCKr^xWw1Gdu|-Ut-xPd_DBqe#|F|VSZ6_ZNPr$wZl*huUz-HCuBa0 znb*dB3e|%qKMVVhH88(D7Bp}kW%;?+_BiORoOE*Ox!3xTERpg!?a^~DIEk9)UjA%i zgFNK=pMIF5CtozS1?th&g6Y|kh#RH{PW|L-`05dnpOEp~E1+(f-+ND=1$34ZJf8G0 zK{X!>=IM5PzEvM7++iMXh0eAfdl?vyy$p6>^n zj{dgexB>sA7T-$ihFWcRF?40%g2OGi+V1h|4am%jl)}%uflJVzs=51YZp+zUBqj9lRaR_{GyM=)UpqjO;mCfGbRyl9Qh{+;bR$ zKY+7dBKo;d&HwPQOY>K#Y8DZhem>6YEmSw^WHH^GGa?d=DBMR&C91E`rqLZ9NhnCJ=(W2# zwPzwP?=N5K$Labwie6nmS5|hqm(kDHWjQ|ux_DxRgXk9@Ks=*~%DJ<&+lWh0r7caL3LJ0511|8vWJUXSP}Qjq7G8n=`y$QNtv_w|jWc)Zqlw2Y*A>RS z@My&wV>(xuVB(z`^X94EACFJW+GTtL}1uE}Qh=P@F0HVZn!6~x0Z56d0Brw3G>okxm% z9P#tpv1~cLg=ay=YK#FwZ+XO}&mpP_gjo-^(m`K>u*Zi@&foQM7v8xbZBM~CcP_6} zMs+2={r>Rc&{dF6W`F(p@d|x=%i9(T?WL}q*NmTa@szk5_bt%@mS`x4e(%oyfpX(@ zxT!IX)%^0b#>zO`p#Ql-#r^#AUxwcP@|Mr+QQZ1&pnO{7&-FKt53QA6!9$Zp86D97 z>5mk)h?3Ew%wVZ999q6jS(PxQwpxM-Y+V+GCqOpIveIRfII)!}rT1Pwp?I9Fma#60 zh_cSk{=FZqZ-D>Zc&y4ebkB~9S#8A|V;_7)(tm^(v+oVhj_*SLZF>YX>c$O|_1bAg zwl~eqXaAt$KW5rX{@{`N;^T@B;fnr$yp5%stFL6OUHi zb}(b=oAU2>n@xeR+*d139v>|Jf2U%>d^g7%-RZRty*rz<#}+%xXzk$r0lu%sTAr_$ zOGsV#-cH);vC53PlP%&y3&&tK-TWdnfOgS!&!-Y0pA2@lVFqGlrW!lu=%w02=>v{7 zIQa~9SGQlI6!t*By=@Qf%ZGkz(EEvJiZSx;Q!;H6lsi5H1wRNf-TymGeOQq?(+bW& z@O_0zD`3Kep|!4)(QTr4&tD$}-*t}Ob~|*r@RadB@)h_ph_-9H-_OkQT$w6j4qZ{o zs+zNFxfWBglt42ZE~;WdJZBNmZ|8aO{!wXUrgh}|GDtXph&hp3uDY9}5V@8zEZ|&| zgj?NxAmCKQ?=Nm$tn5BN7<8SoC_@K0ci!!I)03|H;}O0hw^~zxB}AhK&|!Dxm0T+k zMll_eo9M{3Z>v!6461hqBUpb=h>DS&ugga|u~tks?A$-?8m=(7ht_wyE4*QP?nJyR zZ1q_*+^lBmx)oErl5htNrz$him3@|Qih61nSV1pN+#vw4T*WeY@PoIk0m5O2Bp!I0 zXW{m_PruW#oBnc7d25!#98e>Bb?-(ZY3wy4Q|nqhl|BJTiI`E^=WvYpq%h8ew5)Nb zk|A@BV`pFNRgHa<3XlA^&+5e7W6j6LPR9yJRE?pGlA6o#anaIfV%tHR{&*tNt3Q%B zbeb^YdR!Wfp#keQnR9S;oQGZuQgZE~;f9Cn=oomQRW7b?zTtR6vsrsd)a@F~Vo#r` ze}wP!{!raP$pQa8bw=1#&DotKTf--1h2bRH%)US)+Kvn1g*q!go}7LC^?x2q0Y&5O z?MFo0j<#wW+6a#mh!*o-$^TmZi;Cj+tt91+V5_#BI?*kH;7gg>!cD4c$~GsIE)iI5 z*oIn(*ed+?+XwhjCXUxXpxgFvW5&dN!nA+paP!(t zTYXQMez9)*!yfUGZsSJ(S|eJy<#FUu-f~N{hp*9dgu|6(k!P6ilM_uh#*4+b%rn|! z%W&@7#swH%-w>|RmUxssh+B0}SP)w$UHg_W+v==op3rA|0nuAETQa2I-1Zeq_Lh~=_A&zPeFNom3B(c3b++|~zQ zXT9Tz!M0?8fn|*XPw93y!F+t&M8Bsuw*po;Or+a=ffw|FbkiI89{N(ceJM`9ZcDoh zI#&kB_tNgO?}}<#5oNKRrJG1jut>KJVRaxN$@O1u9V96?C`nV1dz7NfltERXSRzTA znL2y*piRc9+irKWw0Wt!7dal)idB>zMLn<}Sxy=)5=(bTb$69Jr06{O8{RVQ5T#*- z&e|9e1Dw@cVKX-+ z8P{W7@FZCd+hJZsa{oF{E+2YJSY3;ZbC27ZujP3J8=jeSc8>5>C%&H|Uy z6T1t6CiVqdvds&Tcv%Rq_F17pxkNA29J&pSwX~9GJ1S5gf<)sj&BAR1t>%VsD=J_f zw{1dT!b?aiGP5tWs+XTAn@ThPjPe|AYB7?WmD`q@_tFP#8vZwwTYl944{0~FR;|d- z+fOa`&#BBZHIFCs*^{RA!QUa^YT8$7`|YzothVh={2`OBA=##M(*wZVg$(KT z|E_m1+^FbB(mad1MZ}~yPP!6Tb)Q6jHO#7+`=_&{^N+*1vD)Pr8T^RqfP3=9+320+ zkClxvx7U5Mvz!TwDyuaOlk;VfF^sAy+ib-2)5e;5EIoCNPVDLHo>T;>Hd-p(F=-N) zw7OsEx7IE%7GhzG*pM-TW?=zY_XwNi7(>7Du{I8SJaP}->!71UVk}2@Dwbq>hl<^>m}4Ort7ixaP`F`wJZ5%4YrPLW^SUCti7MOa;Bgnq{BF&1&_wd{PWwTJg5R^0=_KoXx$*_> zcmvM57H9Ldq3fQOo~!vIqwrs$==eLi(m)4Ar&Tn3l5W2Ori@?L>aP8;5Ps1h z)2@PDinoW{LxlGk?>bEo=t1aN(w(U9`+uQ6_#6Cs(*luFe1`G}>+_QR+$}$}%yIT5 zkoR8rXZd!JUPByi(U}_03uzYy;|~ps!%@RMGX39Mn)2e-@!xJTEo}+-Z^yr#F=_uM zk+jI#UKif?m$%rZn1hCUY}qGGp4oP#s?d>ePA2=uME`@BK5VwhzwmUk$)PRjm12U?vO z?bleY7l-$*t9CdDO94B3F|1`B3)`J)bkaBv_1i;(Z>E)4<+e0a@O~zz?)F=9yMEJx zh_Fjq-TbW!vO84SIvS1NH=TBGf*9-Ef=9p2gBK}Ls!pEeJz*6;;Yf-YIX1;^r2%kX z4?vg;Pob73_N9K+HD$S^l*4n_$0j6Sn?cYyCm`QyTUj+&v0bd$YlrUC5|8q`6TD+s znZ5L*H{BEb{MJb|BV8$%iG?+)Q3SOL>cNtLZp%0I?i%Z>Wn1rc6LOb&qt$aay>aU< zCk3=niHRHGgUR{KXCk+J0 zH{L~Nm)7XF4X`ipghj*E0>mL}Rs?udHf`iI~t+(ri zZMX26Vdl8p)_dN1ROOE|`4BHNWZk)GpL2AC{>)sM16o)Vv^$O(GFL389j( z2BL(l*uoeTgnaC@@;aA4$R)L3Fv8Z9L2(>7Rll z+2WkPG?KbwLyf8Dcv|nz|ELem?LQKbtN+V@W@T+9$Yh|7?qLVhhP}1`qcONnplfA4 zcROUZfuARxE>(E&etOG=j~-=n-F;fSNn2g&IyBco@A(H^1IMn$7#+y|{E+UyK+-9$ zR>`;Y&P{)5+kJBI+-k8B^9MOx&2npT;jo0;ySt!j+XMO6X75Y99VMOAo6HxI4s;I? z=31@PmBF)kljnPV`@K!O-g|zpFemZ0siexyGr6sd9z1~Cyq4J*`+r&ti`u$Li2ZsK z^6j6N=u5y|1udKiq`LV&zM7VMlEY$bPui9_JT2{3CAQVDDZ4+crd{-Hrj>SkEZ^=c z?|JT8|9{%vtyzvENz%+8*AG+sMgRm!Ku`?w5GBhZdZ!40Rk&1fR)jJl+buH?=_mf( z)lbdL-8?P_Ky{A;C&OLs;^rQa;c5|)nF;N)DoHOT=lRK1@-{u$VT*f_z!tOIm!d6E zf0B)ox22=m&$UETvnFp9nzr{!>YcTmPjo`*smcuCGP2g4tJ?%F#^Si9R%eLkka+Hp{Oa)yp?)#)W3Q;~Y*1UG~~ z&E?0*s>JZ*|YdBc5K z7%~ze6S+ZfsJJ#~xhrPNRwo?9Kq%G2!Mb*hoB`I{s+wdP&7OxMu&|4Q4A?8P~l ziOucz@7j8ww9tn?UQkDIbL_D z<~8CNM$*=ITaF6TGS8&#h?IgPGqH2r%D3DFlWVQY*;YV-w&`cC3^-3wG7^9skeN6| z!KpKr1~3f2mDXWs-;9)v>_(=fY<7k9xXTuuamhE7sO;ak3L}^5UTZ6`A};_QED|GSOuos64@)t1b8;R|_`> zII}Qke4@tLtnC#{h)tU&C1~e9SMzu!U6{A^Kl^3Ps>=L;l9uf=u(6y*a|+CD95|Vuq^2=T3t35<)4sXa!${e z^kgvYJ`zW<%~;mBE(Oz|%}N7ZU3@iHt4Rt_UdArgJxce~;320nvoFT?3G-pzo-TR^ zg>Or`Hv8JzZLe*@%cso-ECu=QY8J_dwg>nWx&W_WoSu!IW%GB*ma-{^Z9&uk&>tI0 z%*W<>yiSaEVsqMrRYO&kZPQESNhJvg;V2ufn&GCVuu(|XG&0&drqBl_CHWX`sJkIs z)u`)%%c!f0OM&_YfQ__l*`f?cN$#@dW8&&psTv|?n+!{xEfcT(P#M-aex9o-#j-2h zMZr&WaUyshm&`-6y$Zib;&x8b=H>0#w9drf{T{31YM0E&5D8t5a*~;eqI*g{QE#|% z#UZ_ax`3IPonedORR*`u*8E)__Qn9yZEla*8WD-E=V(Yc=%=M-WE=&hvlA#KAK!Qe z7}p$IhR|%?-&8>2QTdzrmQk@Q^5Md?pUGMREj25bGg&jDbi#<{A;LNU&YnCk~!JTMn3!HQ&KcJ{p7@r z6b@(gf?^yWlnp?72$sp}{5gcCd1_{)|00iBzu$^$p1<#%g!&F86_RiL zcZqowrs5ZE3!`0_s%rR$*iv4b_;EJN({z)SlO==pwnFJ^alUOrV`#g>JcbrQWk4CB z6R?q&DcACsi{Msu60%Vl8CnP=SA;fL@-u}%F%2{k%C>H$MQwHHW?0@1L5cS^Fgv|H+hP8i4s3TU*;mfqSE>@u zJ^BT^Cq3K6gY8)nr*9>q++lzxX&eJ@g*4`UFy%r8T@6oyb}OxNEX>nwL8K{J&LClt zua%N-MFGu%-6-MSu7u%yc3@AlDcKRp+|2&^E;F$B+vQzS7)bR|iJ03;BC2g?)ouZ% zgXqhz3^cttJ#pJLk`fKmxt6NRNN8RrWpX1wD9WSlG7D47iI%_712<%0(&oiZEB#R` zzMMY=d9Q4?WD%i~Be}oGrR+{lK_hRH&EX9Jgn1a#!omOz6`Nb3oS_?jb$*F&8=U(r zjP&|GYOBQWmy~GMX7TAVva#Flyd@=R?sBfm()PL76`na7IoCqf$k8I_+T%*|9YxBE;5qmzsaobRVju{FI<&x(XWS)Py{Y&~CRte8ZZZ%o- z;slFHUdlb_e?Qd?w9dtkB5&g^8jc5?chNHUYp{~Bam#cWjPH1iM58Qjk47@)zzgS7 zZtBna%ZTE+$MExMf|!m<#ugii9w@B}js5FuL?em#OV`pgX%vZP3oE~4RZR4%NBeDCh7AsIa;G9|0c#NkvuZX}dOgqU)MflCchY8h~20Nb}) zj7!6tODg9_#v5vz>>LR8)JUf!i@G0O+(6^L7$ND`**4T-8*Z?`W8EWL7HTt`3`AP= z?ULCUyr^eQDRmBV5D2|rpd4&5$}iS9$-x|!lAN>DEa6%{xluwPB~r4rhdqLhW>%nk zWKA_vsCeQ}z7DgX#}|tRxmq+CqrFP10>iT#4qbb zslmnG3dK|=BXWdKQ`cF56QD{kB;Yx}T~2g_{DWR=3T9`uv*>^yV78-*mi~912QWi& zg#S-4`MIxZIC9Zc>82ODpW-EzQW|k@Tb7I5Zso6%Q_>KX_G4pe<(D8Lh{q#G+(4}y z6eCIpMV7OsTG7I%dnE-@iqjraOgnX8kvmeL*5)j4$VBnV54KCs6J^hMDhNZLa3wRw-8))QK!Jv5%!(O2#3b~VqjmnUV+pohl<<=1@!Lh!pp}piFj@`SwR@sv z15N~NB;&h1P*hEue5FR2S3;xRUzeE-va@4A znr{9(o#~cF5pY#}EpQ#~Wl7LQBB#QgUaffO(}OayTKvSc?cIsgio3VK#~Lm53_@Q;I+O$%9a631IvMdS@jQWUAx?1sFAlxztlU?+`qM2bK(H3YStI8{IUwzNGDB~Q;~UeX~(xu;8^e-ZUTF>YZ+pYbh+%4wWB zRgzVS)KjH1mO1Hh@C+bmhN-J%OTq@Ej5~nQ6u-v6L z#>##@39N6Ieh!cX;rp=b5+RcOw-9`I{`~pv`E$;SD*w*Q2ep5D{u4^S=~p5-)#Z-8l9=;K5!x_wAky@WEbYM+H)82sVNlLRVsq0eD++OMwvfaF66 zydM0f$?$ZnlVST?*S4F0^?Hv4o$O(?v2jUEE{xR6{gR(n?CS#pn@9{%m->UPWb`!|V9YGBP&}$S!`u2xl=_{x^B=CBHv6@LQpzREj zH2q;F!%fZVp^vPzv_^)OoT5qYpoBtZ^wOF%c%qKaI!Q`HiZGj{41qZ2BT4(y-2*Pp{vk}XBctxzgj6{omMDO)~sWLQTCVpy) zEnC!!TSVVUF8(*cP-51Sas25|w?BdD>~XujctMo6o4`Q-)hKKCL_d5G`r?M_CHkj7 zJ;lcdb#*J%4-I?)__gdm4Vt9&R23b3sYp1|L-Fg| zUnhw1HCT#I6gCCxiDA0Ol&h@R?a7OQRmUAmcO4=iSfJ<)~2Ul=2QmqPtL_Q#t&uXx*{W6K!3& z-?74#cLEjdIZhos^(Xxn%17^5E$Ww#2<9_|KJ08kCt1EwmuG_Ye${w3534THot*CR zwY}Eyy^5Tg3{iF;o74MMV_L@hWxXS`q(`fWG$@d5=|@iimO$KSwYiqa#ZVoU@|(cE zACcuD={6$jWw{2>1PZN}FxcPC<88g1RJsXY19DYH|9gcU!*Ak$O496?>UIB%T5DWc z%qja$ht==HB3a;QFO@52bH6O=YJ9%M-b1#p9CdtawpVylQHD@utIF+UHhv$%GXn_= z-hA*)Uuh>;(R?19rhRj85W;hKk4F0h-6H6l%14g_sfPOS6C($m10-|7=>fCWm@U+- zqiwh$(Gk=$Li`$)=?1n!NLZ*N3~wL)?FRxU*4nAjTY`{&Bd2g^oyQ9_~kd+8rw9 zOdSks--l5jM7`}i|(PUbrdPG99H0tDB&avznbxXr0KNTf(UnIAs`ld+iZ8Sny zfFm0A{j8vU!|r(>!cUV(*Sj=qibA?Ia2CB@1~&qq%byySh_tp-s|^ePWPK_6Gg&+T z*w<&Z?3kBG6V7qc9>p`$r%;b<2yflXqcP%L9?7YY^hrTY1Eo0Xd6!lBr>+P{+D^9W zgc{no@B@Dh&iF52IeOMlvoY99^p|o>PcDn!`l`Z8he|usr!-l1x0eBb{U8%i1{w}2 zUS(BjsAPx-`}hh&&sKxKu<{OGU^hiLFYZS8%S$@5;pd7BB*F<8@+u>O0{?Pm1ZuiSBc5~>OO}w z(|`oV=BwB87G;?R%}je2l?vNt(c$|g>z$Ei_g^SzUaR`5D*LSkW8`~jo33?I;@bIM zUCx?Clinjxu5jM{*6e%r3cpUi&i$|YS6(Oa9vbWFp`&9tK28jXeualGM3l|Z=yfdu z&V$#iofYYSayi!(x(H}*1KJPWBR|(4C?l5#d0`ucjH0lFoMi;2Y^14?AWcL9JT(MY zAYr0cY67Y(O)Hn`ZZxqUB`hUZdBZKV79YxnL38#}N5^#&3ZkV|v7Ieo>&6@%56& z_EufP9UYJ7Az4bzz+92J<&^HqE2%+;z8U$De1)JxsrpSONV7j7kq}HsZH;6pi75Um zN5sj>VK1QX0m*h+TX!NR_w+e5lI~Cbe@F;)o5ZoSj6ncSo=!IbOuxJI2&cAqYFE4j zz;8qCi$^hglUSx{nAwZ71KT+pQmyn)+77?TY44iDJ!2*Jux|x<+C0fXd`2eYz?$1s zu8{VOma?H}A#w)-tXGfjVXhBkNsC0L>T0=;lq9Btj|?YJf%U%Z@6KE&FtP@2Rl$q@ zvdRY7N@0KEg)USUNjqEICke}Z9p{%Mc;k(>=R8BOV?#T9kmQf!KDS9fc*WEQpP5qP ze3Qa`cns(FIte%fLErcS-yWk1T4DsP!^q(m8I!s;|~lv8*lKhn-ji9iQT z$R|0Gpn?8Uaq!*iE>}6x*dNk*7@vU(gF)!sjFT@}PA>|O7yk$h;!4^3Drk4>rwUfR z(K_e0h%Y6Vg5h*jUR^o5M<7v~tM!!i*{RU0=4wQByS=*nDufHN-$`!s1Npt0j!(h2 z#n)^UYwqNFKNC|sXn3?YZTQ<4MYHWOwa?*;NjP&LA7}9Px#+>k^suH5`KH+GZvX>i z{?st2D#fXSoC;HxQJ5+RETFChA`iT{WZKaH!NaRG)!IUJ*Vv6E1|xM_#5Qst5Y>Eh zs3kvJCPR?}Ne8`N#yPr!K8Fs;r2mVKNdo8i&UDbQe&CBJ@W_8!Mics?$Wnf}$Mp>j ze7!`6=|D_9zC>aK+0dEkIGFc(UGg8JdfA~%{zKrycTKb6EG#Df0~tsUeHXi;i8 z5n;jZX95(+?VGoiBv`{r|6BqTR=E#y6Fxnn{_Pinl{|b(0#rfO)(qVZ$UGUyKn?(E zPH)PXnFk~7L8IR*xDtD^SG$iUIB_1m}2t5whgeUea z->E&Mp82HHOnKbWi~iYZbaCyXN!+Dg()pQ$x9d{I98Gjxz?#LSAZ({A^P$1BC+^Pg z!PgGN;|W&!XL#mAQb_-m4+&ZHNe4#WGu2hw=RG4(&(S_k5qt~^j?<64L=qy{NkPGA zTj;!JWHJxyd+`o%sq?LJ*;JSNcyaJTAc=ly-JPj>V0 z1QsYsW<$yz#te?c$4SM2Z{t?biofPoQ0_KoAsLw-!+IbFAN#Yg+&a1GlF`dSGAv(H zE1QM>FTV#YrQs~7?5U0K^@L=@aVs;0*&4c5*Nmj6N+W4W19^*J_a>5mM6h=GpGnlKIk#yy0pyKH*pPlfv-QMTM_lb`}1hE#|&NLkRtlwM%eka1t7ypA+uNqIKG z`g4&c5687e*8S9@>NOU>yQOFY;jV76(W^2ZKyLSJdY!j@X2v6V6b_E*wotY^i4PlS z5`Uv)UUR^`tczSpu-w;i@iTa?E#N2l2?9yBv}74GzwO`mG)D=1POa+NZ9Z zyeG5iH+)2ljUE%8X~(EWt0z2T_~PlI5GUS9coyU&-wD2t4@IRyzT=8s0#UxR&u(Di zL(AIS1TmtqRtcmhG4ZTj?z7{7x~e*mzct@kpVT`9aRh|O$xK#0Dc@ljPiqXCkJ*O8 zyPc_nkG{h`!4%CtMbS3smwmcJoA9gSk2Ld6Rz;cFPPtbz`s39}%x5G!8GC%($H|Q| zExDQvN9n{vXg<+KmsKYpQ47S?S+f?+v+PUnF5cG?+W=!%-O2_C&UK{w-Ms9Amd=FG zntt&KBTg6Vm$mq>JQ#i}IY^+VIESgIxSFx6^UluB+dU7uO&?p`eMxAhMW61i zB|p<~)|QbEsp#aTU??q>d1uG)JYMr1yC~Cmef0sMOTlf|$8>qY-NlyANxdwu1P9zH z&ZYiY`ms-O9Ou0ga%zDYf(*c3=8Nz{sA+MEi?m)$#^>UfQ#XU_wGoZqwPPB*ylh=D z>(c)`c;^G-lLTjz*Au_zsmwX5wBf86J%}a{XRA8z_+m&3D~X5^574k$0I718;8ZTd zxI#@igiLzEGtwJ;xeSK|Xe;||HvM3no6?8s&2RWTp- z-JM4DrQFh*;aSv~PaegcpamU`Wuy=v@&`h!w~#)xQ_2C|pVy==%s=gLC=N>&lO>QF zna-kP3O=-~TzO4TP3jXv<%X`B)nP>BhF$lN)>K?*LAA*t8BvMJuXfl#f{FMZw6~mkk>!*(*+(>HT$PZF4 z!(F_#RR`0DMwy94b|X=x6V6@S0@TEm>`C0pNkdTm#cNM)biBspg?&O2x>hBHA%Efj zsNk%b)=?j$i6qAf%Y0ih&JtjqWl3b>WGsnyU(AeJadN9Z1ee76AH%CB98oX*iAT2; zY7q6(9-Mc;@&dUn^$RzR#4qj}Zp#sk{MWEX->NVZHAlh2t7B4-urPiO5BIC~fR&j% zdSuhZ$%Gl}%viu%r5);-2U-xS=UwN&YHSKOq z&th(W|4XJTKMr^3#?%R#%E$+tyHlR^f55-2aHckzkhb?9h>VYb@tI}GZ5FzYWVq^* z*YsUw@fk-zL5oehNwcN}uP-y0-pyA1dAr(lktNx2w;|?{!I)x{)iRm_y}d`W=(R=< z*>E22@doGjh2-kIv>==MrfdZ(#=PSM*ry4im3q=Qp7Yx_a#ZIHsbncA@an~D&?kz`O&{}ZbW z1uV28TR;UZm&julAn+~Z?x5@i$GKq>(l&HBv5Epl0B1*ffv!fbq zAw`5F&lfaRCJ_l-gv+U4;q|!~m>!3e@piYR@L+MID{IlyWdiyaU0bfa1iV-^ULeW9 zVoSxg8{BWW6zp6GH=|R#ApO{(LxFy#It@aQb_GthQeUc*-8T1?BbsSX|AQUaedu{|2HYmjP+*TfGOwxm`pW% z%Ox80k$+70_eO~}eAS$zqLQ257?Q|S)T9E+UwkcrQDm%BW?|A(N{xA}I+y z(vwJ0*vjY`R_lAh%Y%>LhHF$FQoZ@>tpZ+Sm8W?c`JC)zyv3TQ<-CXID4FI|^HEho zy;>_g-qjayStZ}hH*b;nl3u}yFE<$`btN+yGZaiZ&K>&g(j_;kr^6ne!%h_9V}E!l zZ9N3G<|Y*l_^8jP#z;a0%Y}23(5NirFT2#lLyDMl5-KyTJynuX-Vq!&FdvAStY$ybsf(6<`|Pw zB5hy$Ud*YaWhKo3o7UV$9rQdaVM?yEk}es}@D()wBr{0%4{X4sea94r3=xg6@ITwiQE`nsQhjitxXzf11*#_a=r5Tq3f>m5oJ@);Wj&}A# zRpp+nq`3**(7sGoX0N4=|NWp+b`Jj^$x7=;)F4>47vXQU9b`RN+hc=+F( zXI`=-QqPG+6Pa%!XnTfCi~+P8A|FXcK9Y-ECKeg%F7;rnW%AA;d!Ia|TJ4%&h%e>3 zjfThUL&eJTfQe?jhT=qa?OhW+hm{7p^)Z@K?U{%zcf#t%s;!Y}39&j4;TQ3E;wQwRPd?$lmV)F_C{0Dc9TAtRdW*3m<|Xur`LXJ5&G!pq z@f-IHm&qvOoZ(^D*!Sn$`KF5VSej4u%maqos_b)*M3RlnZ-$YoF-xMh>+@J~*l0HfmB^&wH0xOIk;q**-rx2x`peW+kMR{|+MdHL zxoFA_T|UWDIE4Gbo{oBumkGz|b$AcmcFTcngg(Ah?Nstf=|;NIMnj9FBB5=F#N0!& zQfu2>NpQww?dZ6Do-ljk3IUTtFODrVmRDxRz=?%$`8pL4*CM#H6GgQX+-16P2&x1S z>h9?-1bQB9FX(wlx7ggZUUo!YxH~U#+wSgB#IEN(JKiHrIjZNbF4GXQnO!{i;V#Ih z!`ErZeykbpMMiKB9ERiAXEC`q-^h;c-(1a=X!F7Fa}tZxO((;bEBAw@?p<%b-F^7S zm~UB1yJZNGg2)3-EN2s`MZPT87q8 zV*)8T0LI_(ijUOVx6PU*E4ka6so541JAVy6{ml(}p=fN^6xOBt=03y7E~Q35(^4&- zyvnk)%=IlJ(74f|N@@Fe!r4gyPSIM8{Grl|mT2g{259`ZR;CXvxrmPFU&%tG9=6IP zBRh1kb81sPgS@~e?BS7%%u;ooMaSF4(mLdXmSCKZ8Grz_Q1LNh}&%r?^LA=#%8jssy_!C|G+Voe72Rcw8nF^AN#b^sLHG zf2(|Ahhwv38gn*$I`m5m$ogfl!y_>{$q;OGfNGwvjJdE-@98Bz+VUIk9}CH9$_M(n8g26QwO>5fnZl_=0Inn-wrLrddTUR zIT+e!4h(Nl{J1}{BEh1iGKy*S1ST(EG@62Dqo}x4Vm^m1y=Q^qOR)hm6!5eZV8VFX z1Fb!~dWRJ*(|X@F+~nr6lcfilj<${jmw71A%z=bmDAa3n1GI0J8r$&=#zDlVDt&at1kI<4t>dkRw}IER;(gqW;Nd4|zcEO^bzP}!zOZT610!}bOe2>)P9=T0C4d@{f^@3v%}msC`;?(YMFuN<(Ymr zq^)}G~27NB&2{)eleH*n#oJV#}=di%wgzhsVM*;hsFhkNQV@h<{9cOz97VdE` z7~yt-HOu5EAhcZ{VG*|?md+P}8oN0O@rebam+;uR2C^62*-MV3Tl54Wdx}j77Eq0P zkZJc3^bRlni|vascYM3XqsQdwZ8CUE#M!VrlJ>|0uJG`%xKDw)>I=fs^NHt0UX53(9CcT8$S?DQ;R970OLc&%xojJdUMS+wRViqI z;@Tn8>B4V+-mIJsmIW|9pj^&kt9FNQ8y>SqQUO#(Imr#k1tcG2on&CPof-_OM0uMb zh`;ozjKCegGJ&2M&{8Nux6*Ih-EyB2Qi41&8qpd-9}q^B$?`AYT945_$qDRZI1xde zzQss%f&QgfjA*^#p2Z)2&w657vZU*_*k(fe~v z2_l@90l4$lZW+Nlb)fN-!*_1aU}^S@L7erp9h6o3T)Tk%=qYkbFHl_>78byLO=+yk zqa`=(BN4r2LY{hS{avI61UCb$9^#;rfk#HDQT1#ExzDO(t%kb);-e7Q$HH+gaYL%rdSA-v!I&>V5v z8~(fCCn-2 zA^J)T`EkrdI7Q;&?g2yt(7`$3cA;BQ|oHX2h%_>va*(@^o=_jUgKbuoQ{XR{~Y7}mTsGy zb2N4kx~2MoRYph0a&UZWJ$fp&+wF&MztzCMazAs_pc=%Ao`W{}>RTqS%C|ZIS-QSu z8j5;rO%u2o++iDviG6$X61Z*5N9lpBR9`Cx1N3?Ow@}mTAF0W7a|QE1630N)C?H(! z05!SFA2|ZyR`ZVpKua&%?YtFTOYx5xhX5976&;UkSY&>?X(A#{k2D(cDgRk#!ylj8 zW4G@ws+?E~TQ?SBJMJbAZRc7OfTpML$*ufqOSLH_!}i^o3% z-WC+A6@4MyLZ9Bk!|ge!&PS+cPPZ=vJ~pvM^H1vkgRF%QP$3*a zG&v_*RmA%7?St%}{^^BO|JCulFgk}S-FPFUs$!8U_O}C`kYUkH&meZDd=Z`OcNooNl&T}R0DKtB!JsD){#E|sSF@? zHR%o-u7)R7u?UQYHRbhU*0$cM$k^1`g$0+6DXWOISDf*`skgMc1FF z_dWw%ecomX=Mgw}U+Qi~yM(Xa1nYIZ&jR}JfO4M&lo{!FU+U&bTpN^v%DnqB&K4mi^5a-)G6&1?Tr!eo4o*RnvE50`50o2svXN zurAbhIFCmmcY=(zygk`@&PDkpsmK{$&$FZoW&^>q6WpA3L_Z67qew~rqoAo$8H}o9ZQ@x3 zc6lhGGcRC>OxB7~aD1frc9T|p|_Y|AntDhIQrjH)!rU+6c>cmN{9R^~(g zxaF3HfmHWW=6YY?>Z)>#TZeq!6=_dl`I>Le%FFcusxetBJov->;SZe1N1XY_t(6St z?AD5OxeYUKt?=zoB8Q2Vt8v5S6#RdsnA_gh?- zXkrcFc8i`0TW+_U05Rxv0%eX;US%<&vShuLVhD|?FCt$ie4=1~&tfXl*Wz0M?WcCi zi^36b-hLNaxn9;^k`5or6*6`HC%OxUiz;Nj-e`4K7x8PSxXhWm_mE^WCli?|J-G9-COFo0ei+Y zA>#TADg&X(0&TArs=@WuC=jgz-Gg||Gp*{VXvCbLssBZPgVw7v$9@2XrzSNO(ai^p z63tN(%nx%U45;e*xeeP7bbK6~aap-sMzm-b!Zy({4O3=C>00Ca?={|#J$}ZzruyJ< z2ugdRHWa*nk|?#4OV9q&eWgU7C~yZ;ch(Th)>6xm7R-(N1rD(7cnvV={fQ^f@|dBi z;CX73G$ax2Vqvy+%y-0$Df?_7lYwy9{CqCAPvUJ#9?tx_MXv+q)D-UkPI{h*)~UW# zHN)H!b#PqmaXJyMeb(K$YcXgNkuf8tp2ZJWE%nRY+;(S56= zc0;I6bV~{IAvBuMT zoVZ5Cdzy0@2XLN%1~mW|4E~b;W1UL|Z~@zm4C>sv@C7}}l-txd&X&|BE zj&r=mCy+h>;)|lnsoKGj3kZ5%ExWmgz*p)-EU_{#mO`J}1uyhn?RT^regCAk?PVys zp8B}0W-(&|kl16ld{5qqnW8%nZ2Y3_6B@HXa#iR^#jS6ug-DIG@Mk960C<;zsxtvW zmL+5|(#r5VwS8Rc0|6Blu2mEkD%neD+w>{q+a9BaGM|fxPO{>S5K!vUsaKBalkR*x zTc+os#z7RJyqPRMSs0y8w!V9-zOgwMqk^5r3@2691@by!#QRa?TlyyJb&~ffBXLOv z7iu_{<<+ebzxBn+!w+CaDZh|)+{4N%cCm8i_XHMMx06L1vR*X1D%y&y51QRzlXZvn zomgD;X%<&b?h3u>`uk+xf<3KzQHO(+3m&Ar!O|*LZmwRY9hILu;lbg*pYLP@3Kv{0LPapPmrU zBjEh>L=06vC)kO)(ZT|zd8{(`fQ;vR7({40^?2OdUu?-kIy6S zmkgb7Hal!DBJL-JEBbTju9?8qn!1>vd~|F`0?OvssHvXK+}vJ?pf4bSo`Nx>X$Lnx z3<BWQ7x=?FW^4EaTGQE~2exk6&wj-Nb4GDJM$++bD*U5QnCmVMM z2RbgHd3T0Xt89f1EjJ&}p>V6jE24K68D$tk$ZMrEAy>RF`9Bu3Bn9z}@RC|Pf9sgu ztgX)<;7_pK#&1OI_;?TnNOi_+mR?L`pbiWaKEBZLH}3^pm_jp~II1Ww5)^r}+dlNznWEQyyug0(exa_s~sP!^iVBO7;2sUfFHz6%HrJX>gt zPK&p(gT^?jcW$JGfEL{yvSSf@95Y3yI|xAUlDCCi>FSb&gS3QQG?CDt^FNW zQqoSfnte3lJjL~lOZvgl)F02jSlVzgys+5O<}KV@kZ(=IG7nqzT2{F&^1Xzj z$gQpjdmu8A;Ai&X4wm5Jnz_Dr=;WHYwB=&k#e$O)RGJGKBhl;69^t$^l56L)?zoh< z#YJ@7`S^?li);C?`+|tdzU3uK(mdojbM&cA982 z5p!fCTXa}}TAMna40}^^wxt$rfkCJx%No%dlL~BmtJ7CSnCCN7?WljxG8~JLm*H59 z@jYtnvA+0q4tX5}Omp$^?W%?9Pla}-QOsKuvo z>k_o>lw}7ftsvBsBtyp=$<8Uc@gd9Fa|~D9g}#WDlJ?OkNO=GFZP3VI%r?3gs3Aym zF-cA>I5jRvc1fiO$IjS`n2CRlQDiK^$mo zR2366jM<;w4K;%r5pn4_C>3j{b9uzb9cB$ESafcJU`t1OR?Zh_@399CbhAO}Qrt0b zAN_BfI$++03)N0-*-cJul3bLMhra8&KprTvfl{k0tOunAhJugxf&g)3bf8lW=Bxd& z^`jO=`xinDjm41RJn}1b)7XUNLd^LNSQmnaw$O)h4S0dgoPEK#!=OC!p(1KYU)phu zXQXO$S>=j3tG~vaS3>PBnUf;acGj5lN~n=~uBbfFfBoS;QNDR6(Jzjukil|`JF}o4 zK+&1t!`YX-YSa+w7`Mai+_?g%E85e6B#}U=+V#Z3qbE98WDPUFa8zNUJeV`i z5=-8^*cO*wK^y61LFLpFDv*149;50-a?o{xfN_ELa84>Ob%QVu59vZ|nQ={+Zd6Qh zqq@Eh|0^6O2h707`E`3w@~+;|$8)dOXcC`;!aaD3d^`+0feoKi^lH50c2ieRklVx5 zmz!kK2pYZ(s_P?S&H|I5X)x-lUbd|C7{x1xi|JTB+=K2YbcVCP!d$Rtpt>Hx?926>iL)Trd1LQ90X>I);5pq2+BxQH}+6lqRBW2XusuUPz#JND5 zMSF-V<47nbscvp#?b)4SE}KdPE$pxD<_mS~wJC zc12o5#yMxVYL?qZnro%OqLXQGovu#Uv95<6P@iiG9h@1ouAzxJdAfpj7b-dxr4#A* zg__SnnIW?8;~I3R4<7wq7pC87?RYCZz+lG%69p;8T$tmSmqtAU8mW3fvmP>$N64`u zcM4w=(^YV2?Vy3|*l-$2T!T_It|%8Ut}q{fa%zi|Y7kl}i!X~Yh>Zb#WA^D$&^ABz z{Ds|)FHedKI}G*CpXsGBVp_fFAn-2Z13?boSm63y_8gNgV2L-_Y4(qa3T)Z^M?=CO z!bO(tR?HN~#e^{#^(VobQF)SApUrjEDB4RjeiKAA8k`U@4^%MocGTd?@5VbRv12dua6gy(ID?~_E1OZso_6qME$Z9L6! z9(ojN-$5S-4DD4L?p&~J#gQMeX+An?25J|9Cb_kTg0Yv!d`;8@xzKT0*!*~p-o=IG z0}MT`K0@eWnHt4Iu?4=m8AN}HVT*W<#C%}RiEEww%YgBR&-hWz41r&BuXi@oq`*l^Y7c2LJ{i4-6?ezg;XP#8eU{$b zo=qCkf!DsxVb64L`am7`W1NdLZxh={O6;vCZqyUbycFax%>xp^WZj#H-(MHac>%S}@#E3S?5YLK9psCx;o= zoo9fz@-^JdQ?$pcm*F+%9fo>_Rp+^FOm;|mi~<=)Bf(oi+7_QwrH@vU%^XsdSrTfB zAX07p-PL=rh85;vD#b0`3D`8gHbe-oq+5kl1lR`R#faV8sM;x*5C@XJIWOO391f$- zms)j&tK`a&Pcw+^-f5Lp;l0P7?gF#w#vkgpXI^zdQ&On3Q;|(aQ4G!{2QW2Mcc8jk zK|8>Y>81#}M?~QLs#x`9PIzRyZnNk`oy4pcsb`^j!1%eotV$V6ORXx_?ISYq1zx3r@C{B1CF6BBd zWo9?@2N6o@WIZ2KvTWGR7jk!)Z87g;VSuj0LlNr)Ut{hVhx$8?FW>a_@N+s%@?B3~ zWcR0Rm2pZw2*U_##E+;@kIK5kpr#~&1D>7F9^7A{eLJ0ZH2lXApV>(bTJPT0T^V^m zpMXz(p`kYYPs69d%2X=~QK4&QJC&N7BCyNufRYBUeVy<6IS2N_sdhLS+UryTTX{Js zWfcsTXOD*q=_RjKt)q4Tu({S=igY$e)qeSRaMe?=+oi8pKX0+e$Ei-kRAIEE#F(!5 z>o)XUF@L?rkl@e!HPi|cTC5NHHxCa4nvHk{!i|nJz0G=?5C)~wNscXtGX`Z}8+us; zY$^hRt!_b9Tl2PI6gx$lRRsnNVR*aKCa$-*%}hSZ2v)-CRS_Kst97kLm|}@qZ}Tsf zli-^sk_c8%x}OZ1GhyC1Y%r(rYO)t})q!X&^Kxb$`QBs|=IuX&G1yh)o+BBc8Cn%WHjP<$>V6yRI9*7rTP^0UKdH_YD%yA+YweDh#6UI*2}DV&)#L^e#^%7RG|aL@L*itHB*LJ;yY>@7LjRurs_Q zq1kt`&6Sz16m-?BU@G<*JY1yE$#)uM0X+PqsKInPb*H^V7jQ+QE$WO|J8!%-6yIoBAb7NdRF9( zdIDj}-?r(6QEs_Aiv1a~Y`j4CwlEu*kd;#6a+Bvfsrc+mC8%}rLAon7WNyo9Q!SaO zG}LrMHPwTAO2aivrJdzY+uoO&`XKjXR!qH#kQSxU;q%6DT|@Orgo6qq!6$5}nfaM{ z8a@Myd7@n$6%VwB`FolxDEeTXU_#avReTR66eI#HCM32dQWy{yNrB#e{dIh($KU-h zAydcqJU|Ogk=8ASSG>)Oo`x43^ZlTm^dMW=P_R{}Z%0&#+!i9Lxc00w+G*BVrUGId z_;qm)hjvMWFlwApDtI#*_KiCTs)kZD9ph}8$YGz#s@Wk_+OoJ-I$EU~vSF8#VJ89| z_tIb=SAYxQq#M-H7JXFxUDQ=AI_aw9s7#br^ke}qvVpL|Up2-(II|W-%&z|2H3n%rQvP+1dLWo`c_;#mvn~UCZ zI&S^0(jyTnTD2u88lN5jRvY13cWql_nSE~Ge^na6!r4ZgL~nI@*p;v3o5L7ltS&{oMkrZIhf4}Phgrkm!X61x65Z^GZ{BILxFfY^~fUUYCU%owV;cdCj- zRR7->mo1Vh^>}Ip>v^pSt}w-Y7j{1AJ#bN;LE(8GePUT3{76`a|eF?IQgUW%nXw&YCRUOeDjtl z8ziN-tk^(r#3;oZd}R>`Mp=;TRDhB`x~-Q^A4N%|C;A~hYB)m=M1w7oG};A(JeXu+ z(vP;>MBiTrlbr2}zk#jdSo9wHcX##IW^sC!A!r@}d2ySiRBNP`I6Cp)? zq$vxHs#{Wc2-e_&G)g0V+A`_{I!I$VgeMMv;jr54_Wc{tLy9Znc~2mW@3c$xX}U2t zMfZ?GLd_59R{Ri#C^N{SJl*jryo23J9z8#K^pETO3W2F%ALt;NJ4NljW3;niiKqoj zB#G-R5q-)E2bQnTingg8JD<}-p?npuv|wilv-5-sQDLDhVcizUAxZO>5@P{sH`d^QOvN`h{TV56HaO#K3seSpCS2Pix9 zL7xSg;!X$!N^vkh1z{h9MQPXaKlZsa#Ro+vr6lTS&A=Qyd@w_<&FhsT8ipfUKb1~+ z<0Mth;UHSVYAvpQ@YkqDz!#VX0Y`jE7#^LE=yU5mMWpBTdL>xZMUJMCF_T3JnkC#?B7cDRkh& zD-hmkxhf9mOC4J85?weTiY{6M&pDvU$(|S*)PiXSmLB5Fsm9Bsd#XxLEnihM0~Ey7 zLP3K8axG8XZa@m8F+lSUpdAAYC=ZAnnr{2XNnA$edv6_?Ou1IL5WtWi7IiQogwrI) zag5o1Ym#y#fM%xh5{|~T$e=XtHhc%U2*AQ`9JBXP&3#U$mBfHdz0<|g!d~Vo73ge% zx`jx^eMv94pCKL#5d7{ft@m%}i9br#lllb~VN`5Z)=Ts({lWZNH4PzZ6+!hN@)UhV z5aM&(i9PtS|u@ih4HdlQ*+IYs(32@5u#CgghSaNo(H=tplBDGL2h6 zLRXLfD~90T&UH1%|C@ftO}`Dw&seMcrpKT4PStN+@=Y1}%a*8HO+Wvv-^5lQYDED) z`B`hjx98e$+z##dDQY_-&Li%hT)%d0nSZ#_zjOKc-U00 z=Sy`zk3bupOX$dws~@%I*P;)uxau{v`Z-LKYuqziPezx$D|Lnxy5| zu9*r&sh<+Q=l8Ce!2R!C|A-a;^@G=6(x~DeycW(c3D?Eml-2KA%TBUxq=tTmm>)Fe z*OkB8iDWL3aQ&6Yds9DYsvY}p)pg&Zz3X0G#ICi-%KxKG@+YtP;e-5X=%WbP{IO&d z$FE_ZiZmJ(dvq;dWQ2qtb3T-m2wyvjxnb7&?=^RGjd%Q#v*w0hf0g(!H|#HFaw?}2 z{lv3lnsd_x{3i2RwE9(OV(PI{=hvd?WWi(1qFtYg+hF&b*WdP^!p;D-kylT4|BdRL zgv>remnDWm6oI(G&t3~$$N0nTC{ia^0xVvL+w$Ej87cB>a04%}2u^$fH_OqgH zBX!F^eVx!2KZKnIo3`{w!D$x1XITieKKd7~g-fh1`HgEDqpt3OiN_BecuYhk1hlHf z72>C_Z2)97_BJ(2>$?5)wMF{rYb{#wGqV5Sr?2%Jw*HOk$MqxEM*U;Fn&t)7FDU?P>lH?LZgsma6}d#nbpbY<~Ayt*`l!Y{2+EZ2chfuQ&hn^$MzQX-3hHTr(!# z<2)^(qTk--r>|?IIKAai|B-0^^!Eo;)d|0nEd&4Y)7ST-{Dkyv{2(^Y)#Nvd>!+`) zXVxbF%C+?QLF>{CP^|_MKRltN2)zXrAR&|_k_d=5}*2!Yokm*vG^CdMQAx-9(#IkPt~(^W4=48-ylZl zxyFHg_-C#IbYJ>qX#XBIz54gCRgq@=^tGsS{K|EKsO~`Qos0OVul@VbZJ>HTsb!b? zL2Li?wbbLFpdUckze>bu@K#uXPhUJKOx9M{$e)lp&}f)`=z3s(^^i3~!fc~J^*h%> znF?8BuHRNS1R^!(BK(52eq5Ss&h&vOe)>9hB5$|ne!E#uI<~YL-k*ZtUcX&!UpFK6 zE^ioFVFzUY^q0SV9RYPfztG|FJ(<6K9RY@g0`_lsSWNiNrQ)}*y<7MhY8^b=}^EbP^o0sBe7rI`qGTRrI*Tcir<@M8x?z+aD zeiZ{3YdOa=;#?==no;(wYvxfiTGt{A4b_JJg7Q#(VAF7L0Li`qf5AL8*Ng60>#{i@ z6Rxj1i_743(LFG!)Por)S}z{Vz!)F8Djhr51HH?E3X5y*uOE!->tMc$FE4e?U08Bu z9wyyii2pxu&D|aMYtrhUuDPh$OHO-Sb4S2#WOt(Js7pIE7s#U2%Htd8@R&yHmfI65 z^V+&wmlzSxE5L4r@hSLS8(Z`f7W!&^ewf$Amzy=Zt{V~ubp}g(0^_>DdHvO{+9VD5 z_3h^qZOuc$TSnoH;2a_FJ(t??FDF~>Rk3K*uZ+7U?*#zMufiSeX`VyIHKAY*{4 z=Ys_Y&4clJMcwxS7RcAa35Fgh+vZIH8O#_t?N{vrI@;UXR#RXS=5iN1=q^vez70Ncml}1@q?OibJp=IuaDA?VJIIGi z>cWJkEo=JMaih7eo+AcQ2S9J64-wD@x=tWnP}eUs*Had{44ltTyw~^FzO`LlP}f5i zZ^M(Gl<+&7cQ;|2TuzS;eYx8YSkV71AH{vI>bq$3%BAAGdLGYljJ8DRs#pD|Qcbtk z$9Mzc71zP!+)|TPO?6eqqKQT7pszwJYSD;RMQsrcgsJz|4MU!!2!inEm2}nV$5+*> zuBRAcCM&-5_r#s)a`(GiRJl_AZlH>=6x^r0a&cYWRoscXcF#|A`5atZB=2e)m(iu| zY3Y~It@DJApV;Bhv~y3Tj$Wa0P#e|!>hP5>E~<}-)>w3)tGNfm6R$c#{Wjs1_k@bi zE8}fd^U}{Yd~kns(jRn)!7Lu&XHmKbo^&{t@$`c|4A!2kd(jM=>!i&9*gX`<@-@Vdf5&NBJ?^w0s|nk83*|b6Y+${eq&ob~n~+Nt%y>HH_k9 zjQIrK1Xn>DdZe=HZ%{SWbH)nB5y3RL|H=X@*f=DT#%#l}D#7YLhpExCi>!}K4T;%X zBVf^s(1vxQw%MJ$IUbp--7&Hp>Qr|2KT>;6sTz9;L+*W~7NK-bl(yLx>exag)I=}CRz|gOkRN`a= z{SspIj>jK@lp#)1_69BnYcT%`P6oz8X|i>})8vhU<(`uzq%e-0OdT|urha&)wR19k z8x+W-WC6g)lB20tj%EIqA!*s?>DaeLX@ZeuTQOx@W-8S#Mn>CALoyhe+6yE5W@Tj6 z#FZyu7`qj)`3V!`Siy4A#(F3S4r3kUE*M0SC}@`{P;W>sp-M8&Y`g^$?b6pBVB$Ir zh|xRf7> zSylH%NiRf6fS9-WKwcuNZ=s~6EmOh56mUaLe+Y-^e zUTbP2fV>6k*}^;ngd$l?_K!&?fsxH|b-Ai-~P0I}Y501D0E(8OgW?CUJY*lzG>O z+~b^KVs(2wRB>ezBzd~ssBFQu)J62Ao&+jh_0=etgqzF>AQi(4m^joXUoPfQfyv7? z2EU;PJ50UQm%pqTM=v1Lsu~}%M^=cJaM*1Jg&KhjWJ^PbJww=}DH^3AB3HdJDou$+ zb)5>+UQY|5#s0>b8xat7pi&Um0s`;5An6EiHM(o2!J+8M=Ec% zvIlFTnt*gBjk1pXc`%PV4}=s1wF*18?L=);B6A5f^dDBiUr?lARBzo>@LaPJA9;&3 znrgjsL>9`>i+rle4Q)nkpGk$}F3ww;JO#>(vnR}FL=sGh+`xR=-rJD5Vv}e0L71-N zw(4)ic+_~OB3bYxSMwx#$G)OF2V)uqWx*&IlP**xxq=V{&3XqVl1J3U!MLuatt#4j z^Te+a7sN-M>5z#@(E(^$^iCI>4w5rY)UY^oM2Yy2nn&;^4<~4niZI~fLxK3K5y$(- zBu7fiTcv}I4k_x?G^jH~FdzD;0;r2d9tu8-`Sfc>1H7O4;BwEA+F~H$loyPo*T_%| zeJ&dh@Y60g(KO~4TG7iH=8Hvx3Hka{KVTXa2dl@vxur$-CWd2euNhjO^E#05L2yi6Cc$A9|Jq##=h0g03rIkwE>u|QrPS!n z5z!L+79F_DU^nqqH!S*En&m{V`ZSBl22Q^b-O zqe015ikfhO-$7oFioj%-Qq%w7G(gE#UYo!~7z45@80Xc&UwnWFguaBgw&wj6o55&2 zMW!!$_zC5rFJA1mRs!Yx_&ScP7swG98CrtmYgdXLHMoLUpl}a;;lO(&8E++xs7MIf zL2~v;BP!N8qb?HUdc`o*1($h1RAu<0nF3U|%%jkY0VAHGj@Zg*_{C2to$;3Hf6>d3 z&M>WUE)Kt`=#*`VM2tOmxxNpg?Rz%#n)O61Q~hj zWhD?TYtl)CI7CWxtwc+()_Pk7De-Ee6-riIaLLOl;g}071E?!5)iqzBBCl`;6)_#L z0X6jht@?T2m<@DY=NXwcu0j;LF$;l6AGb(jtK2<$;t_&lkbfrLt{qOL&AKo}J+#7n9M zU&uJ%BJ6Dw5MMVLd}CZWfrvOL6B6?e$Vh}qY+A9krs)uPqnns&QwFBGC&fqNBqS(u z+;O8xDtAGA6U!ws$cGthxYR)=n_8CD(q%(R#pbp{suGyAokr%vAGFZMF1^VJyJ#k_P!r4=$tqTe z7LhkCL9dEts7`ssUub#C%qI}!RV>v8^a@|0CD^P3^E?bYy~wz|jrk=N`Wo63{0^TH~(>Qg`lcyk;A%ZR9YNB+f6`TsIJcU(WknOdu>Xa|U{DE~o5dfJ5TVSn7D*%dsPzgXwJt!u@ z4Ghpx1LRsoE2fLcLU9EaBbwk9Jp&*_sq6pSw{KCQerP4vuneLA^RQuFxroqMe1j;Y z)6s%a2-DJ>R(dQX)@DmsvCRS`jU+L7jHY)gaKXc?uXUrYwYbAX9t1$!paNRJtES5o zfw=-Xc&qCPW*0)375%Ba@v|W6TS8rl;HeXKiE3^da!WRY0B8#G*zm&MNx_{E(*${8 zrpBcQ69*s=0#FB{5;3`I$V&T1Zt)xsSm-pIEB`~(1uF(&(Wq5kSe`ZJB0sQN<6~(= z@B6z`OgZ2%fzA;HZRpV}B$j4s|)Q%%nd$M?@NjIhkL zU8W1d9s|)y5<>9saFM|aJD&SeAAG>?PA@cA#jT0C&>z-$dKl|GmkAz#Fciw;To~Ir z&su=UFSUbc#fpl^#YcxXpb-g zjph7Ks#0E7^QB==xtLos4ZMl&c#VL`=VNL{-g76T2G>~$ILbGQD*B}1E+=;amj{B0<5RGLdSWXrevJw(f&qGX36!L)z`wYTu0P% zxt{B@7e9Ge*6vuXg=&qoEV6Uja{Sg8`7Mq0*;Or@Dr0@tGEO7b*0(Iyqdu1EJok>J zniZK4T9v&tYs0QZ`tt7a zn~~V(s>_PX3ad4CK{zzX!m+lF_LbLRfZaOG`;r5R^7t_;E@wgxl!u%yRlxk7XN8=Y z-{M|16GIa{V=S&N4kA|9hY6)1^oF8L&lcfW+;se>Lry+kHx0Qqpa%~HdJ%RP9+qBw zEkvOylvr_DgsqVXm*+v$1kovuTLQV_7LseQWQF*uzMASkKg1-Yy*?pUHy6lMqr*3Htq7C3)a}_q$kc%aE8r=~- zjsKb%!W3^US&BZ4x|V~x#cHd(U5_b(Ljy!`IK7uq~ERHG3->v1BsU4)$&=u(vxaw`W_9dm{x8`V;&MK7;`voI#YwF>EIVbm7h6Ccl!1MN*s-vrzlx>;gbl+s^w zRrI%^dYzmIx_Wbu6)~)9QDZS3h*t*IszS6fV6L^Wkh(No>z)^s)QLlyz5rw?MNfEFbIuYhg==nNA;p1i#xvi`9Xz60bOgbF4jGyz`>fTd3Vzpx42 zcsMZI3+Zo6@`_}N0>uJpac8P1I0uOlBR;Dz8O23EM=JMmGx{kwY~+6 zk5?>aUIuTQTX;}SEiFN#K|QYqvmEuhg46j4j>#hSNTv!*me|{qs<~KE@qbm!w>9`$ zs5r4`oiY;3ln?zlx_olr4zBFt2%t)BEK#wh_(rXN^CB0ZT%V9iIc~}3h0eIh`sPhE zS(jAYWLqs8x`C{<)Qfz20nkQkQ#QPaj+M!lO^T9K%ZzQ*{aRHeXA)Rl>3zx3| zYm+s9Au%nl;4F%)L{1Hzagn1bt@1`y%>3$2)O1#2jqw^r&z$PGIvIl~m_l71rmJ*t=XJQ3cTOQ>BMHedG+kYJk%Qa#P9a&+mQ>agHA6Zzu94!k=965cd zNknH``J`kXHWn743eXF{BOPz+7{a17+`Dskc- z1BeNTg@iL^Yo9f(pk*Y^<#vaKB3Ty^M(;knF!+geQWj#>^F?FPaOk;aI53Sv-SV@q zUR|{7S#5NJ@0wacf<;e^)>JG z8?&PCif5W?0JkpjA>JiY%-PMU^`P@*&in0uc05eZzvNwd|I^>Se)h%i*AkE1OMdGp zzFu5_4)g{q`}Z4QJW-3iVYG-~G<_ukQru`BEVH zqt#Vftb*!$|JCm!s3!O~$DnVT<;!N<_jNBm+E={Cu=sv;eIR*8^rZx*1PX` zZ=%CDxqCSAz2o#vk@s&- z2aMxhqUb5-#^3S&xE(WARR_{ScOTZ}cf8w#FL^(H@BHbPxjUk>FL_7cuPk4_kWD-9%&+}!4g~sZ-UYRJ`L@2$eL+9=HSgnF z+5KU1g~jOVljYj}k>zdMIA$!QDU5GT?-+Ig&XA;!CK>qV?JWk$`2iXcn5;?<%_#bI zbbqhAS_3jdi?35x2^D+~Aihd{vls<`yZi89qix#`U-q5@3(wKuJLAJm#q5=sW#2Hv zXKDzYZ*`B5TyJnx6~?y;F~0ZR_VK;%Y0Y=M<0rrD-NVe?{*QiA&-wcQ$$QtPOKK}i z`209WczV9sKowA7SGpT81_peg@!L^ugJBpV9G(g)raS8IzU#8Lw6{`u>M88$_RNUl zq_p-$TgptO){#y=1>@JH<7Mv*>yNijpcSum=i8L=T6DeEJy5;YeWI}7i*G`OHICm{S&;`9Wp(?c$6Mq%#T)$f4s`hRbCfSZudj9I zOWj%bL{a2x-G$qEeXTpRA*2n@0n!t^SDjr#be;f)?l-{4uNSFlkiG@d313AHYrWSU zFJ{Nv&<(!J*W~kM?*fTI^Ia$^OB7cs^7EW#ORb4`f`OE`y~A2yI8OdfPj4fQhLhv~avDKHK-MUh+hT@8S*Z>V<${45+s4{e0UyTlNHM z&@YyOpvre3co((dA9B>8|MI$dx4^G|mlrBf_X*#)PPYb7Bupm>$Y*$kPinwICal8_ zT+|I5=LK(G*WZwc*S*t%PKE(JpdeoohF|JEk&tmP2WcFTM-}zn7*W9B}dFXR5~EmGSE})eE!b3pWTpr&KDABW->nr-(Fc@(VX;+ljw$lVza5+3 zrp4EB>}puQ2?y-z_H`R0;~7>9-E*Vtuh$$O;a<{LYglAq;^*+4--{7JqjB>jGJ!mWz8-Z4Yh7Apjtuyx8yjEe;QOO@ILiKi z$f1Pquvl_pA@h=K3(%@~<_lc1(#=H*KC?Z*#J5whRqkJu@rUyKU{S{?7#g2+%JmB- z-E92!s(jVu@3z<(P~dl4yw=fdZVz*jUv=p@(7xM}5<1#ab_l;0gLU>R6yi*9z+{8s zOhAA^Z2~A0ib0h&ZBXTwn`KayUt4(u9YZrbYe*gAaQyxpUzU~;l{IK{;Rw8Wj=KBlH~7KcPWxW9v?xoKA6tg;b*L}=!iIY4^HL!6m!C35!_Za&_qXO%y zMW}c+Q&N_o{YAv-KzU;_PL5zmoD#cdIUsU?QAHZhToa{iGnGA4LxCk1bw{Dru;oq0 zIb{T`61C~rvMix2$CjZ46?G-N=gnm|YPn+I2THC&)@~>(iz1WTMZXiPv|v*Z(L!-!&d$;{QU-f`$TcM{qQiswOshP})y zeGVGz0F^m$mAT3}Ag~p5;aBA74#fR2i#a$@2T~Mq1O7x~gNnEcacg6!I0c&Xm@*P9 z0|Sb9Sq;aHpqab^IS}Ny5Jg4BQ3hoX1W;*3G`UD|iJ%Qo#=)Zo^l6JCx&ZCfU6=Q^ z#|2C2IED#^g;d4`7wO7z3?V(~>J{_hRjcB?K|x-0efxsu;KCXe9v?J#B%nTqz&#tj zgrv%L@y3KIGUpP{gze~aEyy9m)XkFjJ+3R<&Tz-uS^2oNRiKUUO6Uc_3nd0X3|A53 z&8%|1{o@9wi1+?_WFwHWo_Y7HFWeu$=e*QcJ3P#rowl>c>f=^NHM0aA-jwH5Rp6a^ zR#YMM_oioO4dEq*h}Bv&jrS)3T_rA#+jl9V)w-Dx3=MfT{Qmd2Ly*u|AS|j(sqTE; zjG+u$7u9>bSAP;>xypN$o-#z8dfq>u;~i1(%sO~?WsxNn<4%QV+(*3ZmOB+{@j&Oj z=mzLHHg8R-ti^Gs!d|znZ`8aIa80tIaa-!Vo=Uz)qtBqC_~NE?Nl$Jyjn{s8HnYUA zcme93oU*vjC52Ne@{$>WaX4n1b=N?|0!z~4x!V?yfp4xuQ z(%;P5yY5Q4;pxPU2v{L1DirX%ls6sY4)g&+hfB9Apq;#A6$fOxVp|ckHl@A?QDo;} zQ;@E8-b1Tz2EBnGA5EON|N9`W?Nqvn(FpT5ZaPkJ5^zzuH-21YkC&nFr0ED6wt z&wmd}&hVLhGI`F$(_u*UETEHdh3WEFkMpXvog}{aRiAGZxkK$=z=eF%>I=wRK>Jj{ zycO9jpb7>b!2}$`He4|rkKsJSxTjbN>d@t`^TbPm74n?1>9{VXLLJ5d%voP3&Plz#-IbJ%_^M!*fFa6u^anid{fgdVm6(p` z=DyBAqVs@AlwFVL5cy*(=+}q%ObOfA6ZDu6T00V9kEmDlkp(IzaLVW$$&t(BSQ7H}fDr}eaN+|R%r@wN>KGa78J}v_ z{6Jui5XbpXF*+~>7cnpK=W2R=8W8(#*{H~k3)s4$KRkc?_px&i8x{FX`9{wFee68E zg0au<(b(rVwOzTL+-?J3wj%*31evg1=b9y`lAP?A%bI zvKPj2K=94|D+JJ{#kX>lAG61I0&P(&0@%_(+YoPMgW|;bP(RlI15~~Wje$4$7!?&gnBZE)j{~`7n&LAV0+K^S+d(j@BB(4e@xi%sy2R^&ztGYpCai_g^%g)45v8Zo9tk#daD0c#~ zdPnwUYGY@jw!_VchbTHun%!U6Oi6mf@p@;XDmrZ_Ji-P$2a!}y+&*97!r^Saqyu?w zKrG53l?07@$~|~?QzY1cD9Phnb_Ikq;n{zR)3UR$fJ&LP2a&GEnG6X8=tXyJJN0!F zVii37$h~13J|LX1{Y?u41qIOF4Ms(m=o43y$2e0bDaNrKR*rI4#$pV};Q8VO+k3Q=dGqs;QWPE5Y zdT`GTh*k(&11y|@G>_*m6b6FO;3rIA%*avXX2M8^HZ8g+RoJS8jfJ`UF#`XN4`^Pl zmfvU^UbK(X1x^D3&pnF}1nT{f-m@5}Jy0&KXgIfMsv3yPwnSYbvS=Xp0AZhC>@>nQ zz}Tw@O{3`aiarsF!ME&S#MY;V!nW^Vq%S6IHVjqlSL-03iwlUr_^@o6*#D+&jo7cm zJxi{MyW+Hik%&>&H#@+0h#+puve!`c7Dl7ogJ%b~=h_yiCkU*)X>Qr9h`o7CV_%}5 zHlKVF&!({l(dI()QO+Z*A8kNvOr+PLi9EK=owqPz87;RkehQtri4Sfc@zf)!vCLp+ zBKGjv^F-AV6GcF3D!V7=Alz>lDE-8cOO$S@EG+yVPS8S1ef zC1c#jCk)lR8@AkI$R;x!Y(rix>b26p|iegy?xb~vlA0QU=aAj{N85%(PJGff zO$UcQakH?FPuiv^rv54zgv%YlN=HZh!yspYx=FY43$j-UzIFBSIXk;>FlIx*nu|pU zPV6i#$ii@vwiXPm2x1^5(k+?*jxq5@LOk#5-|f*?0{oGKaetPkemOvAG~;$1 zt4MXfmdO1xA}$Wy-K_Ezlx*72UD|$t1P`CjsK{ z*K#s^p^2yEcyf?`AsD7ZpZu6uLZ}%34ix!6QekOnFBK_FWSB(*&ZG>?i{vSs!aa;t zWQPR1%AdU5p=IK!u2%#sT1D4zLNZpes6oFaGa8lCj9TtueokI?^906G=3}>EoKU?! zFN4GTyi62qvb93N5?m-4X~l_`(F`MX@G@~d%niZ7fMU|yco}r@@SLdIoNdLkmX{7J zHMw+E^eh}xJrNlG1S}G?1BF+h)TA!i8g=!cZoDi|PVc3wACUp1Cg=Bjwy8O^H&|)e z04pscD6>k-PGF@fdaNcb8=7!Ksv%#T(kmfM0_JK!9rgwt&5tX{x=^&mMBUJE?*l7i zmvCC1Y=<*)pq-TkNZLSE>jo&u;HMCdX*thoVW1>nsY)k0Q>aB4VvYJ0lJy1y<4?g{ zGmgQ)gxL>lzmPxJo*5VNXG!K!vau1fXC5|6A;Xg>4HtlVu;6?0WSlD?*;tyvsRWva~5RDg- z`H5EYwMUDKs*E-d*Bw7g#?(5(stpe}ntQ@eMhMN1RDoxud&x(b6|(M<(kMiYcLgP* z2o{SdNa3X5Z4i9ohHnzfLIc&gSS;(tjs2<7O3kA}}s=H-m zsHMNNgC^t*m&;_wsEe9W!QslIxk1VF2&7=ruh0`lNlW&10OS9W03OJK^DL<$p*2)Q zwMdX69fg&l(~g5}l$5XOI8sd`S&*{tiaaZ)DJ)%Noz52~rY!meD&${KpfvmuqMX{~~2#;=))GFpOv$6wf}RRW-Sq z8CWc=VYV&Jpk+Y$gupcW2h%b#I6iyn1nX_gaS{u!6CJe7D5vdwvSSHM6J9*_$Toz+ za6##e=boy3fKks6wE?Q~SiUMC=C5V7jOOxn1#*)9T+{i%z|n;K_F|Yr$(m4SOXU;| zBKm4t1_L%8NfS#hdPI3aS2cY4|jDVTc1+jm$lwelC(@G z1q@8H|72hU3oUb10?iUo-K;08PFnUP-`XLwq-D@zgG6km?HQwzxd?K>JOZO_r)7!; z-q^UoY$M|Ta3k@3{w+mmXoNmf>?t>mg# zx^lgOt|fX5%v!6_wdm5_-%fDlX^ZbWq@$%}4-}MMDaI2;?h0>8|KWw+zW?y*{|+ns zf3QdXFKhCDPIE|DW@@EU_|J(=akZ-X)fc(zFizd7*!Zzam(9uX_iVJ4T>0tB^$NO{pjVn=4dGJsA0W7@VvDyX zj23jXwDN(1;mB9cEY4hUI>G<+TyNihdN_X{0sKGNDgT!>`Zb%=pO6nOT`3YDvXPlr zcZ+T#pPDoG^M_}LYP!xNT(qjU48rv^y;1cKMsxUU4(@>$qj#DxqRfX+7e@sh-L~{~ z9Ng8-V0#D+&A7%?Ul2{c4;&jyO~ z(`uy_8wTf(vvgzT*1dlp+wQ#NUGe|!hBbX|0W?nb2lwCTfL(eWzkSpDfleZnjc)ju z`wQWm+Ufju%Z-O80ZpU-Y4I(~PRQOBJbbGkz0%cruiv+R$gx(`UMk$ITG{Y{qE~P9bDiPtKxyps zYd^`7WV_Xm5*l4>^@}}v?OhnRue9}{b&V?E{xh`^r*tGqc3b_VK|$@#VTR247gpZt zr=MO`MdZ10kK5>!p2m@fvIns2jvIozKfP)Oc*ljeS7t5n!AqOJy}~wzuV$tDw_#lT z_UhLDGS_r+xz!IN9)+ivwk6%X9%M-NDcKKa4s{_w4SsQ7>b{_m8);3`R& zj){mw9cjU1?DQMZp`CtFluKCSh!3iUU_Oz#Ei>fxtr+q1E7wfR({HZs?esfs^h33E zqu=-KM+W=Ne{~y7-O`8cblh`h_BG#TOCe21wHG5gxc*_S-H^<=Gv>I=Syt#J8%Dk16{XN zrRM#Oe$<$nS9|*8lJ^3AvcN5Y+-vsy`%hWQO`U8iqHmfgN|SorT_t-ovuyN3kOPHA zZ2x2E>=bqhQGPH~l>M)vEQBVrNa`zq`u&w5f!M0G+vtZ5jm>Lg6CGYJk;eH}jO&el zV}sf-M?K^BSFYdTduQ9&=tq&akD&pvsgF$wZ2bNzF_@3vT5T~KvKHjDGtFBby@xJ0 z`i1}eVO2Sg=yUwTS;ha_=!avd!6Dl~r|@5Yf0c+TZ91$7K*w*TQbSo|-4dSq|^&O_`b z#{-8Kd;SJ1hj#oVYtiC8VVkt{TQFRLa)EIJ4cJvu3J1EcM9UJ^$&bRy$_gChOWIfD z>k2n1o1IXrJQKL6v=Tc;{mljbZQyW(97h%$X)pxHf-e#APuBJVm{fsX?U_CQqwi`Ye>GaJOITc_AGV4aUgy z%L|%zg-)mQrDM-nnpB?ObP49TEZT>|-#&`qYw$$%B+^^qLFh;O|J z$(J^ZIXy)P!tcrmkriYGGxpZ{D<={|q%-*e{VUX~`b&un%SFIT!qlT`i}%RCS`(r} z#-v0{?YB>dppP+}oLJebM4$o1s#HaO07^;X?ar&enI<+_onpIl&RaAfa@fBZA0Vnn`UGVo*`tcP-6=pQ*ff@;&FhYJ5ZN2#d z<|Phz!<#HgA{h5{%wJC#oem%+=Q#f+OLhks%s3NNm9t4}V2Sj@IhpOtkF6Z+0lbkg z=@A_XiU$ieUF6T!6N!W z{xSzi@UShDFAgC{tX~KJ25V5WMD;vq$`aB*IgKAgiW0q}?gG4` zX0)q6qC%WWCEmWh`1JPT)Ku!LSx^iL5!8&5u;x{vq|+CKBf$mxf{GPGF|e>eHG`Vr zw^~XY%;ETrh4KeQVeuQR;5i~bXq?>S56n;)KQI#`F+K8&Ht!9jlF$J{_C^FV?En@clSNb9n===D;rm0zI*&dwe zq-B$9O$PQ$)K6U`P>Q*xw&0=i`phg@m8)=LM_x5+ISbJ9Af?Glsr3~k^19852CZ8{DigFyc+ZX=UX(oP^7pXUZylsZXy0v7#yZGDKDo&vF(-BSeRQ zM!_ybCXZj~>oFMdx)3{C)#cDppGBN&lbx-Spjp=vc-`1&+T8>nCud_{R|~R&!putZ zMdrYE%!HZ^jmT7d;!1DQhBAHPBQ%f$`RBMRQO1gtFof6!j3O)%+VYK=MX}@B$ik^5`{`r1DvK|2TH)E0*ZvHR|rv_O7cEA z8I2FoJbOWRAfcXc%A#w{$M9?gwJb+2Xc=mng~`zbS_kfujWLUO5gcoqSplSHtU$ti z{?@^Bht+SK#m30d1m59jTz#rSl= z2~$|+Xz(S?!O;eBOY3-fhKbt=1aSdJL&WllQNqh-A3S-+Fp*GslEK>n`QfdZk_bPgyd$m|p)P06sTXf)TlCU|%SRqKC3D=}AZz!2=&m(cCzNW1^*?;ZEeG^G^=-I*pPUZD{DWgIh;1Pm%lX)*X$Dhwbjlaa9&T4otpmyrR(#J|BXG3ZhARTM4Bm~=Zu zgXcRGtyjm@x_pu4h@tYf0C~>A&~9^hP{lDXC)4`QQ!>8Xh^2Uip+;7Wt4O9}9^3T_ zBxt=FV{gRT8T7;2XE`1d3+%Y`X2i%^I|}4NPJ7s5p91Q2+IIU>eucH?XN}`UJtywh zagGnDbk<1mw=n>%{H(ArYf+i02;d3knTmjF36CncqSgdab!eu+g6e^|C1*|XHMm(- z)qrXimi7M<9a7-Yj0F!Bkh7})<{Qs3ZB#1DW-Jff%n7qd&o?JdKlEC;8CpNuF0a9Q zlwT-VR?N!i=z^S4Vpsjsb$bKivT~-jR>_w(O-2{X7z9%y1(OC(B9ms=vhuH#9JYyN zGZwl8U1oOhE0I=EkaA~`nRRtGQalDcZj}W4+Eg)l*)W2Z5=8XKZccv|Xwl4PvrEu^ zO!tB)tTu{HebXcn_NLP;VUvy}0-sNt;J27d$3Qzzi{hQyt3+aTT9xR!vh%3IdXt*5 zjhxqJn%oQqRgf!TYI_lkB{B)p4UU6^v5J=T(m|cy0+R=v2v>H8$=O$_-gKvPz?Ga$ zZMNj;hMeh7b}i(LO?zE-#+9Lkhmky-+1bzO9h~i$K?5L4f|qHu`pnPR@~KEOwT+l+ zq>IE*)1X<#C_SveEOQ4wry2mR4~hcufW#W9!Gp{AoMm`GnsrZpHlFnhL4!f%WVbBX zEvOjTn3M9TK}Qn0Tt!1Pj*~NooIN0;W+Gx7mq)N2CPib(-`O)q&f%FAGMK@jw5MpV zLh(%#h?^!OL3=h7Eh!f2f-aI&cWf**8%3j)h+Eo=_ia#h0Cy$^MSl$x|gPuCDp>`B?NY&&%)QTcHpqYmPYJT+&HMfF?*$EH}Jz Q9TRlKgb2nQgvbYb0~xB$iyFcr-<$p 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 + nlu=length(lu); + colors=hsv(nlu); + hp{i+1}=zeros(nlu,3); + areas{i+1}=zeros(nlu,1); + axes(ax(1).h) + + if any(l==0) + colors=hsv(nlu+1); + hp{i+1}=zeros(nlu+1,3); + hp{i+1}(nlu+1)=plot3d(atlas1.xyz(v(l==0),:), '.', 'Marker', '.', 'MarkerSize', 1, 'Color', colors(nlu+1,:)); + areas{i+1}=zeros(nlu,1); + areas{i+1}(nlu+1)=nl2+1; + end + for j=1:nlu + hp{i+1}(j)=plot3d(atlas1.xyz(v(atlas2.ref(l(l>0))==lu(j)),:), '.', 'Marker', '.', 'MarkerSize', 1, 'Color', colors(j,:)); + areas{i+1}(j)=lu(j); + end + + hp{i+1}(:,2)=copyobj(hp{i+1}(:,1),ax(2).h); + hp{i+1}(:,3)=copyobj(hp{i+1}(:,1),ax(3).h); + end + + % timebar(hbar,(i)/(nl)) + fprintf(sprintf('\b\b\b\b\b%02d/%02d', i,nl)) +end +disp('... done') +if 0 + v=find(not(ismember(ref,0:nl))); + if ~isempty(v) + hp(end+1,3)=plot3d(xyz(v,:), '.', 'Marker', '.', 'MarkerSize', 1); + else + hp(end+1,3)=plot3d([0 0 0], '.', 'Marker', '.', 'MarkerSize', 1); + set(hp(end,3), 'Visible', 'Off'); + end +end +% close(hbar); + +for j=1:3 + axes(ax(j).h) + axis image +end + +u.labels2=[ atlas2.labels(:) ; {'Not in Atlas2'}]; +u.areas=areas; + +u.hp=hp; +set(gcf, 'UserData', u); + +callback=[ 'u=get(gcbf, ''UserData'');' ... + 'set(cat(1,u.hp{:}), ''MarkerSize'', 1, ''Color'', ''k'');' ... + 'u.iref=get(gcbo,''value'');'... + 'set([u.hp{u.iref}(:)], ''MarkerSize'', 5);' ... + 'u.colors=hsv(size(u.hp{u.iref},1));'... + 'for i=1:size(u.hp{u.iref},1);'... + 'set(u.hp{u.iref}(i,:), ''Color'', u.colors(i,:));'... + 'end;'... + 'u.hleg=legend(u.hp{u.iref}(:,3), strrep(u.labels2(u.areas{u.iref}), ''_'', ''\_''),0);'... + 'set(u.hleg, ''Position'', get(u.hleg, ''Position'').*[0 0 1 1] + [ .4 .45 0 0]);'... + 'clear u;']; + +hui=uicontrol('Style', 'listbox', 'Tag', 'Labels', ... + 'String', [ {'Not Labeled'} ; atlas1.labels ; {'Missing labels'}], ... + 'callback', callback); +set(hui, 'units', 'normalized') +set(hui, 'Max', 2, 'Min', 0) +set(hui, 'position', get(ax(4).h, 'Position')); + +axes(ax(4).h) +set(ax(4).h, 'Units', 'centimeters') +ht=title(sprintf('%d labeled regions', nl)); +set(ht, 'Units', 'centimeters') +p=get(ht, 'Extent')+get(ax(4).h, 'Position').*[ 1 1 0 0] +htui=uicontrol('Style', 'text', 'Units', 'centimeters', 'position', p, 'String', get(ht, 'String')); +set(htui, 'units', 'normalized') +delete(ax(4).h) diff --git a/brodmann/atlasviewer.m b/brodmann/atlasviewer.m new file mode 100644 index 0000000..7c14460 --- /dev/null +++ b/brodmann/atlasviewer.m @@ -0,0 +1,97 @@ +function [h] = atlasviewer(xyz, ref, labels) +% ATLASVIEWER - Display labelled regions in 3 plans for a brain atlas +% [] = atlasviewer(AtlasXYZ, AtlasREF, AtlasLabels) +% +% - TemplateXYZ: [Na x 3] Atlas Coordinates of the Na vertices +% - TemplateREF: [Na x 1] Atlas Label ID +% - Labels: Cell of strings +% + + +figure +clf + +ax(1).h=subplot(2,2,1); +ax(1).name='coronal'; +view(0,0); % Left on left +hold on + + +ax(2).h=subplot(2,2,2); +ax(2).name='sagittal'; +view(-90,0); % anterior on left +hold on + +ax(3).h=subplot(2,2,3); +ax(3).name='axial'; +view(0,90); % anterior on top +hold on + + +ax(4).h=subplot(2,2,4); + +set([ax(:).h], 'units', 'normalized'); + +nl=length(labels); +hbar = timebar('Labeling clusters','Progress') + +v=find(ref==0); +axes(ax(3).h) +if ~isempty(v) + hp(1,3)=plot3d(xyz(v,:), '.', 'Marker', '.', 'MarkerSize', 1); +else + hp(1,3)=plot3d([0 0 0], '.', 'Marker', '.', 'MarkerSize', 1); + set(hp(1,3), 'Visible', 'Off'); +end +for i=2:nl+1 + v=find(ref==i-1); + if ~isempty(v) + axes(ax(3).h) + hp(i,3)=plot3d(xyz(v,:), '.', 'Marker', '.', 'MarkerSize', 1); + end + timebar(hbar,(i)/(nl)) +end +v=find(not(ismember(ref,0:nl))); +axes(ax(3).h) +if ~isempty(v) + hp(end+1,3)=plot3d(xyz(v,:), '.', 'Marker', '.', 'MarkerSize', 1); +else + hp(end+1,3)=plot3d([0 0 0], '.', 'Marker', '.', 'MarkerSize', 1); + set(hp(end,3), 'Visible', 'Off'); +end +close(hbar); +hp(hp(:,3)>0,1)=copyobj(hp(hp(:,3)>0,3),ax(1).h); +hp(hp(:,3)>0,2)=copyobj(hp(hp(:,3)>0,3),ax(2).h); +for j=1:3 + axes(ax(j).h) + axis image +end + +u.hp=hp; +set(gcf, 'UserData', u); + +callback=[ 'u=get(gcbf, ''UserData'');'... + 'set([u.hp(u.hp>0)], ''MarkerSize'', 1, ''Color'', ''b'');' ... + 'set([u.hp(get(gcbo,''value''),:)],''MarkerSize'',5, ''Color'', ''r''); ' ... + 'clear u;']; + +hui=uicontrol('Style', 'listbox', 'Tag', 'Labels', ... + 'String', [ {'Not Labeled'} ; labels ; {'Missing labels'}], ... + 'callback', callback); +set(hui, 'units', 'normalized') +set(hui, 'Max', 2, 'Min', 0) +set(hui, 'position', get(ax(4).h, 'Position')); + +axes(ax(4).h) +set(ax(4).h, 'Units', 'centimeters') +ht=title(sprintf('%d labeled regions', nl)); +set(ht, 'Units', 'centimeters') +p=get(ht, 'Extent')+get(ax(4).h, 'Position').*[ 1 1 0 0] +htui=uicontrol('Style', 'text', 'Units', 'centimeters', 'position', p, 'String', get(ht, 'String')); +set(htui, 'units', 'normalized') +delete(ax(4).h) + + + + + diff --git a/brodmann/ba_color.m b/brodmann/ba_color.m new file mode 100644 index 0000000..e7ced9e --- /dev/null +++ b/brodmann/ba_color.m @@ -0,0 +1,102 @@ +if 0 + a=readAPCmm('../../brainvisa/div/div11/anatomy/div11.APC') + [m,m2]=readMRItoCTF([ '../../brainvisa/div/div11/tri/'... + 'div11_MD_CTF.txt']) + [fv.vertices, fv.faces]=readmeshes({'../../brainvisa/div/div11/tri/div11_Lhemi.mesh' '../../brainvisa/div/div11/tri/div11_Rhemi.mesh'}); + fv.faces=fv.faces+1; + fv=reducepatch(fv,50000); + faces=fv.faces; + v_ctf=[fv.vertices ones(size(fv.vertices,1),1)]*m2; + apc=[a ones(3,1)]*m2; + vertices = ctf2tal(v_ctf, [], 'ACPCIH_ctf', apc, 'brainvert', v_ctf); + save ../../data/s11cortex_tal.mat faces vertices + + load ../../data/talairach/TTareas.mat + cmap= hsv(66); %[repmat(.6, 3,3) ; hsv(47-3) ; repmat(.6,69-47,3)]; + kcmap=0.3; + +xyz=xyz(1:10:end,:); +ref=ref(1:10:end); +ref=double(ref); + +% Bizarrely, this vertices are not exactly shaped to the talairach +% box... We deform them slightly +xyz=ctf2tal(xyz, [], 'brainvert', xyz, 'CA', [0 0 0], 'CP', [0 -23 0], 'IH' ,[0 0 50]); + +r=zeros(length(vertices),1)*NaN; +k=r; + +end + +figure +p=patch('vertices', vertices, 'faces',faces, ... + 'CData', r, 'CDataMapping', 'direct', ... + 'EdgeColor', 'none', 'FaceColor', 'flat'); +axis image +set(p, 'SpecularExponent', 2) +set(p, 'SpecularStrength', .3) + + +light('position' , [-1 1 0]) +light('position' , [ 0 0 1]) +light('position' , [ 1 -1 -1]) +light('position' , [ 1 1 0]) +l=findobj(gcf, 'type', 'light'); +set(l, 'color', .8*ones(1,3)) + +lighting gouraud +colormap(cmap) +h=colorbar +set(h, 'YDir', 'reverse'); +set(h, 'position', [0.8314 0.1100 0.02 0.8150]) +view(3) +rotate3d + +if all(isnan(r)) %not(exist('k')) & not(exist('r')) + r=labelize(vertices, xyz, ref, 'TimeBar', 'on'); + +% $$$ h=timebar('Labelling vertices...', 'TTGyri'); +% $$$ for i=1:length(vertices) +% $$$ d=sqrt(sum(power(xyz-repmat(vertices(i,:),size(xyz,1),1),2),2)); +% $$$ +% $$$ % local "smoothing" +% $$$ % r(i)=imax(hist(ref(d<5e-3), .5:length(labels))); +% $$$ +% $$$ k(i)=imax(-d); +% $$$ r(i)=ref(k(i)); +% $$$ timebar(h, i/length(vertices),1); +% $$$ %set(p, 'FaceVertexCData',r); drawnow; +% $$$ end +% $$$ close(h) +end + + +% jj=hsv;jj=jj(randperm(64),:);colormap(jj) + +hold on +for i=1:length(labels) + pp(i)=plot3d(xyz(ref==i,:),'w.', 'markerSize', 2); +end +set(pp, 'visible', 'off'); + +if not(exist('cmap')) + cmap=hsv(69); +end + + +hui=findobj(gcf,'Tag', 'Labels') +if isempty(hui) + callback=['set(pp, ''visible'', ''off'');' ... + 'i=get(hui,''value'');set(pp(i),''visible'',''on''); ' ... + 'c=cmap*kcmap; c(i,:)=cmap(i,:); colormap([c]);' ... + 'roi=find(r==i);']; + hui=uicontrol('Style', 'listbox', 'Tag', 'Labels', ... + 'callback', callback); + eval(callback); + set(hui, 'units', 'normalized') + set(hui, 'Max', 2, 'Min', 0) + set(hui, 'position', [0.8414 0.1100 .15 .8150]) + set(hui, 'String', labels) +end +rotate3d + diff --git a/brodmann/brodmann.m b/brodmann/brodmann.m new file mode 100644 index 0000000..6e7944f --- /dev/null +++ b/brodmann/brodmann.m @@ -0,0 +1,122 @@ +mypath + +addpath(fullfile(HOMEDIR, 'mtoolbox/brainstorm/Toolbox')) +addpath(fullfile(HOMEDIR, 'matlab/')) +addpath(fullfile(HOMEDIR, 'matlab/ctf2tal/')) +addpath(fullfile(HOMEDIR, 'matlab/stormvisa')) +addpath(fullfile(HOMEDIR, 'matlab/brodmann')) + +IRMDIR='/pclxserver2/home/datalinks/DAV/brainvisa/div' +TESSDIR='/pclxserver2/home/datalinks/DAV/brainvisa/div' +SCOUTDIR=fullfile(HOMEDIR, 'tmp') + +sujet='div07' +segdir='tri' +date=sujet +RESDIR=SCOUTDIR; + + +IRMdim=readdim(fullfile(IRMDIR,sujet,'anatomy', [sujet '.dim'])); +a=readAPCmm(fullfile(IRMDIR,sujet,'anatomy', [sujet '.APC']), IRMdim.voxelsize(1:3)) +tessfile=dir(fullfile(TESSDIR,sujet, segdir, [sujet '*_tess.mat'])); +tess=load(fullfile(TESSDIR,sujet, segdir, tessfile.name)); +fv.vertices=tess.Vertices{end}'; +fv.faces=tess.Faces{end}; +v1=readtri(fullfile(TESSDIR,sujet, segdir, [sujet '_Lhemi.tri'])); +v2=readtri(fullfile(TESSDIR,sujet, segdir, [sujet '_Lhemi-CTF.tri'])); +v2=v2/1000; + +if not(exist('left')) +% look for Left sided vertices +left=zeros(size(fv.vertices,1),1); +nverts=size(fv.vertices,1); +h=timebar('Finding side of scouts', 'Left or Right?') +for i=1:nverts +timebar(h, i/nverts) +d=sum((repmat(fv.vertices(i,:), size(v2,1),1)-v2).^2,2); + x=find(d==0); +if not(isempty(x)) + left(i)=1; +end +end +close(h) +end + +disp('Calcul des régions de Brodmann dans Talairach') + +m2=[v1 ones(size(v1,1),1)]\v2; + +%id est: +% v_ctf=[fv.vertices ones(size(fv.vertices,1),1)]*m2; + +v_ctf=fv.vertices; +apc=[a*1000 ones(3,1)]*m2; +verttal = ctf2tal(v_ctf, [], 'ACPCIH_ctf', apc, 'brainvert', v_ctf); +load(fullfile(HOMEDIR, 'matlab/ctf2tal/TTareas.mat')); +xyz=xyz(1:10:end,:); +ref=ref(1:10:end); +ref=double(ref); +xyz=ctf2tal(xyz, [], 'brainvert', xyz, 'CA', [0 0 0], 'CP', [0 -23 0], 'IH' ,[0 0 50]); +r=labelize(verttal, xyz, ref, 'TimeBar', 'on'); + +talairach.vertices=verttal; +talairch.faces=fv.faces; + +save(fullfile(SCOUTDIR,date,[date '_brodmann.mat']), 'r','talairach', 'xyz', 'ref', 'labels' ) + +disp('Sauvegarde dans un fichier CorticalScout') + +scoutfile=dir(fullfile(RESDIR,date, [date '*CorticalScout_2.mat'])); +scouts=load(fullfile(RESDIR,date, scoutfile.name)); + +BA=strmatch('Brodmann', labels); + +SIDES=['R', 'L']; +for side=0:1 +for i=1:length(BA) + vba=find(r==BA(i) & left==side); + scouts.CorticalScouts.CorticalMarkersLabels{i+42*side}=[labels{BA(i)} '-' SIDES(side+1)]; + if not(isempty(vba)) + scouts.CorticalScouts.CorticalSpots(i+42*side)=vba(1); + scouts.CorticalScouts.CorticalMarkers(i+42*side,:) = v_ctf(vba(1),:); +else + scouts.CorticalScouts.CorticalSpots(i+42*side)=NaN; + scouts.CorticalScouts.CorticalMarkers(i+42*side,:) = NaN; +end + scouts.CorticalScouts.CorticalProbePatches{i+42*side}=vba; + scouts.CorticalScouts.CorticalProbeDepth(i+42*side)=0; + scouts.CorticalScouts.CorticalProbePatchesXYZ{i+42*side}=v_ctf(scouts.CorticalScouts.CorticalProbePatches{i},:)'; +end +end +bascoutfile=strrep(scoutfile.name, '_2.mat', '_BA.mat') + CorticalScouts=scouts.CorticalScouts; + ResultFile=scouts.ResultFile; + ActiveTess=scouts.ActiveTess; + save(fullfile(SCOUTDIR,date,bascoutfile), 'ResultFile','ActiveTess','CorticalScouts') + + labelviewer(struct('vertices', verttal, 'faces', fv.faces), r, xyz, ref, labels) +return + + + +cmap=hsv(69); +hf=figure; +p=patch('vertices', fv.vertices, 'faces',fv.faces, ... + 'CData', r, 'CDataMapping', 'direct', ... + 'EdgeColor', 'none', 'FaceColor', 'flat'); +colormap(cmap) +h=colorbar; +axis image +light('position' , [-1 1 0]) +light('position' , [ 0 0 1]) +light('position' , [ 1 -1 -1]) +light('position' , [ 1 1 0]) +l=findobj(hf, 'type', 'light'); +set(l, 'color', .8*ones(1,3)) +set(h, 'YDir', 'reverse'); +set(h, 'position', [0.8314 0.1100 0.02 0.8150]) +view(3) +rotate3d +lighting gouraud + +return diff --git a/brodmann/correct_flip_tess.m b/brodmann/correct_flip_tess.m new file mode 100644 index 0000000..58c0c77 --- /dev/null +++ b/brodmann/correct_flip_tess.m @@ -0,0 +1,47 @@ +% Transpose, Flip, etc so that Left is on Left +% and all the rest is okay +% TessFile='D:\ndiaye\data\subjects\MNI_4_Yann\MNI_brain_NoCollosum_12000V_tess.mat'; +% fv=tess2patch(TessFile); +% v=(fv.vertices*diag([1 -1 -1])+repmat([-90e-3 217e-3-126e-3 181e-3-72e-3], size(fv.vertices,1),1))*diag([-1 1 1])*1000; +% view_cortex(fv.faces, v) +% image_spm(y,vol); view(2); hold on +% h=findTessellationHandles +% set(h, 'visible', 'off') + +TessFile='D:\ndiaye\data\subjects\MNI_4_Yann\MNI_brain_NoCollosum_12000V_tess_corrected.mat'; +fv=tess2patch(TessFile); + + +AtlasMRI='d:/ndiaye/data/subjects/colin27/brodmann.img'; +AtlasMRI='d:/ndiaye/data/subjects/colin27/aal.img'; + +% Note: the right occipital pole of the single subject MNI (colin27) goes a +% little on the left side +if not(exist('xyz')) +% y=spm_vol(fullfile(HOMEDIR, 'data', 'subjects', 'colin27','aal.img')) + y=spm_vol(AtlasMRI) + vol=spm_read_vols(y); + % Undo L-R flipping + if spm_flip_analyze_images + y.mat(1:5:11)=y.private.hdr.dime.pixdim(2:4); + y.mat(1,4)=-y.mat(1,4); + end + + ref=vol(vol>0); + [xyz(:,1),xyz(:,2),xyz(:,3)]=ind2sub(size(vol), find(vol>0)); + + try + [ign,labels,ign]=textread(strrep(y.fname,'img', 'txt'), '%f%s%f'); + catch + labels=cellstr(num2str([1:max(ref)]')); + end + +end +if not(exist('xyz2')) + DECIMATION=1; + xyz2=xyz(1:DECIMATION:end,:); + ref2=ref(1:DECIMATION:end); + % in MNI + xyz2=[xyz2 ones(size(xyz2,1),1)]*y.mat'; + xyz2=xyz2(:,1:3); +end diff --git a/brodmann/img2voxels.m b/brodmann/img2voxels.m new file mode 100644 index 0000000..7e05b71 --- /dev/null +++ b/brodmann/img2voxels.m @@ -0,0 +1,17 @@ +function [xyz,ref]=img2voxels(y,thd) +if ischar(y) + y=spm_vol(y); +end +if nargin<2 + thd=0; +end +vol=spm_read_vols(y); +% Undo L-R flipping +if spm_flip_analyze_images + y.mat(1:5:11)=y.private.hdr.dime.pixdim(2:4); + y.mat(1,4)=-y.mat(1,4); +end +ref=vol(vol>0); +[xyz(:,1),xyz(:,2),xyz(:,3)]=ind2sub(size(vol), find(vol>0)); +xyz=[xyz ones(size(xyz,1),1)]*y.mat'; +xyz=xyz(:,1:3); diff --git a/brodmann/labelbrainstorm.m b/brodmann/labelbrainstorm.m new file mode 100644 index 0000000..467f7b2 --- /dev/null +++ b/brodmann/labelbrainstorm.m @@ -0,0 +1,191 @@ +function [r]=labelbrainstorm(varargin) +% labelbrainstorm - display (and compute) labels on a brainstorm cortical surface +% +% [] = labelviewer([inputs]) +% +% Inputs should be given according to the following way: +% 'Option', OptionValue +% - BrainstormSubject='FileName': optional Brainstorm Subject file +% - BrainstormTess='FileName': a brainstorm tess file +% - PialSurface: vertices used for the Talairach Bounding Box +% (usually the Rhemi and Lhemi surfaces) +% +% - Atlas='FileName': an atlas file, e.g. TTareas.mat or TTgyri.mat (or any one you can design) +% Ouputs: +% - [none] : GUI version +% - or: [r, labels, xyz, ref]: +% r: label for each vertex +% +% Examples of use: +% > labelbrainstorm +% > [r]=labelbrainstorm('BrainstormSubject','d:/subjects/darwin/darw_brainstormsubject.mat') +% +% Version 1.0 - K. N'Diaye + +% What we need +% 1. The vertices of interest +% 2. The pial surface (R+L hemispheres) +% 3. AC, PC, IH in the same referential +% 4. The atlas + +options=struct(varargin{:}); +% if not(isfield(options, 'BrainstormTess')) & not(isfield(options, 'BrainstormSubjectImage')) +% uigetfile +% end +if isfield(options, 'BrainstormSubject') + bsj=load(options.BrainstormSubject); + if not(isempty(bsj.Tesselation)) + options.Tesselation=bsj.Tesselation; + end +end + +if isfield(options, 'PialSurface') + options.MRI2CTF=[diag([1 1 1]) zeros(3,1)]; +end +data_path=[]; + +if isfield(options, 'BrainstormTess') + tess=load(options.BrainstormTess); + if length(tess.Vertices)>1 + [s,v] = listdlg('PromptString','Choose a cortical surface:',... + 'SelectionMode','single',... + 'ListString',tess.Comment); + if not(v),return,end + options.Tesselation.vertices=tess.Vertices{s}'; + options.Tesselation.faces=tess.Faces{s}; + else + options.Tesselation.vertices=tess.Vertices{1}'; + options.Tesselation.faces=tess.Faces{1}; + end + clear tess +end + + +if not(isfield(options, 'Tesselation')) & not(isfield(options, 'BrainstormTess')) + import_tess=1; + tessfiles={}; + while import_tess + [f,p,fidx]=uigetfile({'*tess.mat', '(*tess.mat) Brainstorm Tess File'; '*.mat' 'Matlab file'; '*.mesh' 'AIMS mesh file'} , 'pick a tesselation/mesh file', data_path); + if isempty(f) | f==0, return, end + data_path=p; + tessfiles=[tessfiles {fullfile(p,f)}]; + if fidx<=2 + options.BrainstormTess=tessfiles{end}; + import_tess=0; + else + if not(isfield(options, 'Tesselation')) + options.Tesselation.faces=[]; + options.Tesselation.vertices=[]; + end + [fv.vertices,fv.faces]=readmesh(tessfiles{end}); + fv.faces=fv.faces+1+size(options.Tesselation.vertices,1); + options.Tesselation.faces=[options.Tesselation.faces ; fv.faces]; + options.Tesselation.vertices=[options.Tesselation.vertices ; fv.vertices]; + button = questdlg(['Add another file?' sprintf('%s\n', tessfiles{:})],'Importing tesselations','Yes','No','Cancel','Yes'); + if strmatch('No', button) + import_tess=0; + elseif strmatch('Cancel', button) + return; + end + end + end +end + +if not(isfield(options, 'Tesselation')) + error(sprintf('%s: No tesselation!', mfilename)); + return +end + +if not(isfield(options, 'PialSurface')) + [f,p,fidx]=uigetfile({'*tess.mat', '(*tess.mat) Brainstorm Tess File'; '*.mat' 'Matlab file'; '*.mesh' 'AIMS mesh file'} , 'pick a tesselation/mesh file for external cortical surface',data_path); + if isempty(f) | f==0, return, end + data_path=p; + if fidx<=2 + tess=load(options.BrainstormTess); + if length(tess.Vertices)>1 + [s,v] = listdlg('PromptString','Choose a cortical surface:',... + 'SelectionMode','single',... + 'ListString',tess.Comment); + if not(v),return,end + options.PialSurface=tess.Vertices{s}'; + else + options.PialSurface=tess.Vertices{1}'; + end + clear tess + else + [options.PialSurface]=readmesh(fullfile(p,f)); + end +end + +if not(isfield(options, 'APC')) + if not(isfield(options, 'VoxelSize')) + prompt={'Voxel size (in mm) : '}; + dlgTitle=['MR Slices']; + lineNo=[1]; + DefAns={'0.9375 0.9375 1.3'}; + options.VoxelSize=inputdlg(prompt,dlgTitle,lineNo, DefAns); + options.VoxelSize=str2num(char(options.VoxelSize{1})); + if isempty(options.VoxelSize) + return + end + end + [f,p]=uigetfile({'*.APC', 'Brainvisa APC file'} ,'Pick an APC file',data_path); + if f==0, return, end + data_path=p; + options.APCfile=fullfile(p,f); +end + +if not(isfield(options, 'APC')) + options.APCmm=readAPCmm(options.APCfile, options.VoxelSize); +end + +if not(isfield(options, 'APC')) + if not(isfield(options, 'MRItoCTF')) + button = questdlg('MRI to CTF registration file?','MRI to CTF registration','MegDraw','Compute it!','Cancel','MegDraw'); + if strmatch('MegDraw', button) + [f,p]=uigetfile({'*.txt', 'megDraw MRI to CTF'} ,'Pick a megDraw MRI to CTF file',data_path); + if f==0,return,end + data_path=p; + [ignore, options.MRItoCTF] =readMRItoCTF(fullfile(p,f)); + elseif strmatch('Compute it!', button, 'exact') + [f1,p1]=uigetfile({'*.mesh', 'original mesh file'} ,'Pick a mesh file',data_path); + if f1==0,return,end + data_path=p; + [f2,p2]=uigetfile({'*CTF.mesh', 'CTF registered mesh file'} ,'Pick a CTF mesh file',data_path); + if f2==0,return,end + data_path=p; + v1=readmesh(fullfile(p1,f1)); + v2=readmesh(fullfile(p2,f2)); + options.MRItoCTF=[v1 repmat(1,size(v1,1),1)]\v2; + else + return + end + + + + + end +end +if isfield(options, 'APCmm') + options.APC=[options.APCmm ones(3,1)]*options.MRItoCTF; +end +% options.PialSurface=[options.PialSurface ones(size(options.PialSurface,1),1)]*options.MRItoCTF; +end + + + + +fv.vertices = ctf2tal(options.Tesselation.vertices, [], 'ACPCIH_ctf', options.APC, 'brainvert', options.PialSurface); +fv.vertices = fv.vertices * 1000; +fv.faces=options.Tesselation.faces; +load TTdemo +%error('pb w/ CTF MRI ???') +fv=reducepatch(fv,500); +r=labelize(fv.vertices, xyz, ref,'TimeBar', 'on'); + +labelviewer(fv, r, xyz, ref, labels) + + +return + +function [vertices, faces]=imoprt_tess(curr_datadir) diff --git a/brodmann/labelize.m b/brodmann/labelize.m new file mode 100644 index 0000000..06184a3 --- /dev/null +++ b/brodmann/labelize.m @@ -0,0 +1,52 @@ +function [r]=labelize(vertices, t_xyz, t_ref,varargin) +% labelize - labelize vertrices according to a reference template +% +% [r]=labelize(vertices, t_xyz, t_ref,['TimeBar']) +% +% Each item of the vertices will be labeled with the t_ref value +% of the closest vertex in the t_xyz template (geometrical distance). +% +% Input: +% vertices: [Nvx3] coordinates of Nv vertices for which labels are computed +% t_xyz: [Ntx3] matrix, positions of the Nt template vertices +% t_ref: [Ntx1] vector, labels of the template vertices +% +% Output: +% r: [Nvx1] vector of the labels +% +% Options: +% TimeBar 1 or 'on' to show a progress bar +if nargin>3 +options=struct(varargin{:}); +end +if isfield(options, 'TimeBar') + if strmatch('on', lower(options.TimeBar)) + options.TimeBar=1; + else + options.TimeBar=0; + end +else + options.TimeBar=0; +end + +r=zeros(length(vertices),1)*NaN; +if options.TimeBar + h=timebar('Progres...','Labeling vertices'); +end + +for i=1:length(vertices) + d=sqrt(sum(power(t_xyz-repmat(vertices(i,:),size(t_xyz,1),1),2),2)); + + % local "smoothing" + % r(i)=imax(hist(ref(d<5e-3), .5:length(labels))); + + r(i)=t_ref(imax(-d)); + if options.TimeBar + timebar(h, i/length(vertices),1); + end + %set(p, 'FaceVertexCData',r); drawnow; +end +if options.TimeBar + close(h) +end + diff --git a/brodmann/labelviewer.m b/brodmann/labelviewer.m new file mode 100644 index 0000000..f393e6c --- /dev/null +++ b/brodmann/labelviewer.m @@ -0,0 +1,80 @@ +function [patchhandle, template] = labelviewer(fv, r, xyz, ref, labels, varargin) +% LABELVIEWER - Display labels on a cortex +% [] = labelviewer(Brain, CData, TemplateXYZ, TemplateREF, Labels, [ColorMap]) +% +% - Brain: a .vertices and .faces structure +% - CData: already computed labels on the brain mesh +% - TemplateXYZ: [Na x 3] Atlas Coordinates of the Na vertices +% - TemplateREF: [Na x 1] Atlas Label ID +% - Labels: Cell of strings +% - ColorMap=[Nlabels x 3] colormap (default: hsv) +% +% Ouputs: +% - [none] : GUI version + + +if nargin > 5 + cmap=varargin{1}; +else + cmap=hsv(length(labels)); +end + +kcmap=.4; +r=double(r); +hf=figure; +axes +set(gca, 'color', 'k'); +p=patch('vertices', fv.vertices, 'faces',fv.faces, ... + 'CData', r, 'CDataMapping', 'direct', ... + 'EdgeColor', 'none', 'FaceColor', 'flat'); +lighting gouraud +colormap(cmap) +h=colorbar; +axis image +light('position' , [-1 1 0]) +light('position' , [ 0 0 1]) +light('position' , [ 1 -1 -1]) +light('position' , [ 1 1 0]) +l=findobj(hf, 'type', 'light'); +set(l, 'color', .8*ones(1,3)) +set(h, 'YDir', 'reverse'); +set(h, 'position', [0.8314 0.1100 0.02 0.8150]) +view(3) +rotate3d + +hold on +if not(isempty(xyz)) & not(isempty(ref)) + for i=1:length(labels) + if any(ref==i) + pp(i)=plot3d(xyz(ref==i,:),'o', 'markerSize', 2, 'color', 1-cmap(i,:)); + else + pp(i)=plot3d([0 0 0],'Marker', 'none', 'markerSize', 2, 'color', 1-cmap(i,:)); + end + end + set(pp, 'visible', 'off'); +else + pp=[]; +end +u.pp=pp; +u.r=r; +u.cmap=cmap; +u.kcmap=kcmap; +u.ref=ref; +u.roi=[]; +set(hf, 'UserData', u); +callback=[ 'u=get(gcbf, ''UserData'');'... + 'set(u.pp, ''visible'', ''off'');' ... + 'i=get(gcbo,''value'');set(u.pp(i),''visible'',''on''); ' ... + 'c=u.cmap*u.kcmap; c(i,:)=u.cmap(i,:); colormap([c]);' ... + 'u.roi=find(ismember(u.r,i));' ... + 'set(gcbf, ''UserData'', u);']; +hui=uicontrol('Style', 'listbox', 'Tag', 'Labels', ... + 'String', labels , ... + 'callback', callback); +set(hui, 'units', 'normalized') +set(hui, 'Max', 2, 'Min', 0) +set(hui, 'position', [0.8414 0.1100 .15 .8150]) + +eval(strrep(strrep(callback, 'gcbf', 'hf'), 'gcbo', 'hui')); +rotate3d + diff --git a/brodmann/makeTTbasta.m b/brodmann/makeTTbasta.m new file mode 100644 index 0000000..fa8b41e --- /dev/null +++ b/brodmann/makeTTbasta.m @@ -0,0 +1,53 @@ +function [TTbasta]=makeTTbasta(ref,labels,xyz) +% Merge Brodmann regions into bigger ones + + +% Remove midline points +keep=find(xyz(:,1)~=0); +% Decimate +% keep=keep(1:10:end); +xyz=xyz(keep,:); +ref=ref(keep); + + +hbar=timebar('...', 'Merging BA clusters'); +ba=strmatch('Brodmann', labels); +baref=double(ref); +baref=0.*baref; +for i=1:length(ba) + timebar(hbar,i/length(ba)); + baref(find(ref==ba(i)))=str2num(labels{ba(i)}(15:end)); +end + +[a,labels2]=textread('mergeBA.txt','%s%s', 'delimiter', '|'); +ref2=0.*(baref); +for i=1:length(a) + timebar(hbar,i/length(a)); + ref2(find(ismember(baref, str2num(a{i}))))=i; +end +ref2=int16(ref2); +close(hbar) + +% add limbic structures to hippocampal region +limbix={'Hippocampus', 'Amygdala'}; +hipp=strmatch('hippocampal region', labels2); +for i=1:2 + ref2(find(ref==strmatch(limbix{i}, labels)))=hipp; +end +labels2{hipp}=[ labels2{hipp} ' + hippocampus + amygdala']; + +% Separate Left/Right hemisphere +nl=length(labels2); +ref3=zeros(size(ref2)); +for i=1:nl + l2{2*i-1}=['Left ' labels2{i}]; + l2{2*i}=['Right ' labels2{i}]; + ref3(find(ref2==i & xyz(:,1)<0))=2*i-1; + ref3(find(ref2==i & xyz(:,1)>0))=2*i; +end + +TTbasta.ref=int8(ref3); +TTbasta.xyz=int8(xyz); +TTbasta.labels=l2'; + +% Thalamic nuclei: 2 51 52 54 56 57 59 61 66 67 68 69 diff --git a/brodmann/make_aal_colormap.m b/brodmann/make_aal_colormap.m new file mode 100644 index 0000000..5ec1991 --- /dev/null +++ b/brodmann/make_aal_colormap.m @@ -0,0 +1,148 @@ +aal.labels={ + 'Precentral_L' + 'Precentral_R' + 'Frontal_Sup_L' + 'Frontal_Sup_R' + 'Frontal_Sup_Orb_L' + 'Frontal_Sup_Orb_R' + 'Frontal_Mid_L' + 'Frontal_Mid_R' + 'Frontal_Mid_Orb_L' + 'Frontal_Mid_Orb_R' + 'Frontal_Inf_Oper_L' + 'Frontal_Inf_Oper_R' + 'Frontal_Inf_Tri_L' + 'Frontal_Inf_Tri_R' + 'Frontal_Inf_Orb_L' + 'Frontal_Inf_Orb_R' + 'Rolandic_Oper_L' + 'Rolandic_Oper_R' + 'Supp_Motor_Area_L' + 'Supp_Motor_Area_R' + 'Olfactory_L' + 'Olfactory_R' + 'Frontal_Sup_Medial_L' + 'Frontal_Sup_Medial_R' + 'Frontal_Mid_Orb_L' + 'Frontal_Mid_Orb_R' + 'Rectus_L' + 'Rectus_R' + 'Insula_L' + 'Insula_R' + 'Cingulum_Ant_L' + 'Cingulum_Ant_R' + 'Cingulum_Mid_L' + 'Cingulum_Mid_R' + 'Cingulum_Post_L' + 'Cingulum_Post_R' + 'Hippocampus_L' + 'Hippocampus_R' + 'ParaHippocampal_L' + 'ParaHippocampal_R' + 'Amygdala_L' + 'Amygdala_R' + 'Calcarine_L' + 'Calcarine_R' + 'Cuneus_L' + 'Cuneus_R' + 'Lingual_L' + 'Lingual_R' + 'Occipital_Sup_L' + 'Occipital_Sup_R' + 'Occipital_Mid_L' + 'Occipital_Mid_R' + 'Occipital_Inf_L' + 'Occipital_Inf_R' + 'Fusiform_L' + 'Fusiform_R' + 'Postcentral_L' + 'Postcentral_R' + 'Parietal_Sup_L' + 'Parietal_Sup_R' + 'Parietal_Inf_L' + 'Parietal_Inf_R' + 'SupraMarginal_L' + 'SupraMarginal_R' + 'Angular_L' + 'Angular_R' + 'Precuneus_L' + 'Precuneus_R' + 'Paracentral_Lobule_L' + 'Paracentral_Lobule_R' + 'Caudate_L' + 'Caudate_R' + 'Putamen_L' + 'Putamen_R' + 'Pallidum_L' + 'Pallidum_R' + 'Thalamus_L' + 'Thalamus_R' + 'Heschl_L' + 'Heschl_R' + 'Temporal_Sup_L' + 'Temporal_Sup_R' + 'Temporal_Pole_Sup_L' + 'Temporal_Pole_Sup_R' + 'Temporal_Mid_L' + 'Temporal_Mid_R' + 'Temporal_Pole_Mid_L' + 'Temporal_Pole_Mid_R' + 'Temporal_Inf_L' + 'Temporal_Inf_R' + 'Cerebelum_Crus1_L' + 'Cerebelum_Crus1_R' + 'Cerebelum_Crus2_L' + 'Cerebelum_Crus2_R' + 'Cerebelum_3_L' + 'Cerebelum_3_R' + 'Cerebelum_4_5_L' + 'Cerebelum_4_5_R' + 'Cerebelum_6_L' + 'Cerebelum_6_R' + 'Cerebelum_7b_L' + 'Cerebelum_7b_R' + 'Cerebelum_8_L' + 'Cerebelum_8_R' + 'Cerebelum_9_L' + 'Cerebelum_9_R' + 'Cerebelum_10_L' + 'Cerebelum_10_R' + 'Vermis_1_2' + 'Vermis_3' + 'Vermis_4_5' + 'Vermis_6' + 'Vermis_7' + 'Vermis_8' + 'Vermis_9' + 'Vermis_10' + }; +l=aal.labels(1:2:end); + + + +if 1 + colors=subarray(colorcube(64),1:54); +else + +colors=zeros(54,3); +% FRONTAL: +% 'Frontal_Sup_L' +% 'Frontal_Sup_Orb_L' +% 'Frontal_Mid_L' +% 'Frontal_Mid_Orb_L' +% 'Frontal_Inf_Oper_L' +% 'Frontal_Inf_Tri_L' +% 'Frontal_Inf_Orb_L' +% 'Frontal_Sup_Medial_L' +% 'Frontal_Mid_Orb_L' + +i=strmatch('Frontal', l); +c=huemap([1 0 0],length(i)) +colors(i)=c; +end + +% Symmetrical colors +colors=reshape([ colors colors ]', 3,2*size(colors,1))'; +%Add gray colors for vermis: +colors=[colors; bone(8)]; + diff --git a/brodmann/mergeBA.m b/brodmann/mergeBA.m new file mode 100644 index 0000000..f5b40c5 --- /dev/null +++ b/brodmann/mergeBA.m @@ -0,0 +1,32 @@ +function [ref2,labels2]=mergeBA(ref,labels) +% Merge Brodmann regions into bigger ones + +hbar=timebar('...', 'Merging BA clusters'); +ba=strmatch('Brodmann', labels); +baref=double(ref); +baref=0.*baref; +for i=1:length(ba) + timebar(hbar,i/length(ba)); + baref(find(ref==ba(i)))=str2num(labels{ba(i)}(15:end)); +end + +[a,labels2]=textread('mergeBA.txt','%s%s', 'delimiter', '|') +ref2=0.*(baref); +for i=1:length(a) + timebar(hbar,i/length(a)); + ref2(find(ismember(baref, str2num(a{i}))))=i; +end +ref2=int16(ref2); +close(hbar) + +% add limbic structures to hippocampal region +limbix={'Hippocampus', 'Amygdala', 'Dentate'} +hipp=strmatch('hippocampal region', labels2); +for i=1:3 + ref2(find(ref==strmatch(limbix{i}, labels)))=hipp; +end + + + + +% Thalamic nuclei: 2 51 52 54 56 57 59 61 66 67 68 69 diff --git a/brodmann/mergeBA.txt b/brodmann/mergeBA.txt new file mode 100644 index 0000000..ae14e25 --- /dev/null +++ b/brodmann/mergeBA.txt @@ -0,0 +1,24 @@ +1 2 3 5 43 | intermediate postcentral area 1 + caudal postcentral area 2 + rostral postcentral area 3 + preparietal area 5 + subcentral area 43 +4 | precentral gigantopyramidal area 4 +6 | agranular frontal area 6 +7 | superior parietal area 7 +8 | intermediate frontal area 8 +9 | granular frontal area 9 +10 | frontopolar area 10 +11 12 25 47 | prefrontal area 11 + rostral area 12 + subgenual area 25 + orbital area 47 +13 | insular area 13 +17 | striate area 17 +18 | parastriate area 18 +19 | peristriate area 19; +20 | inferior temporal area 20 +21 | middle temporal area 21 +22 | superior temporal area 22 +23 31 26 29 30 | posterior cingulate & retrosplenial : ventral posterior cingulate area 23 + dorsal posterior cingulate area 31 + ectosplenial area 26 + granular retrolimbic area 29 + agranular retrolimbic area 30 +24 32 33 | anterior cingulate : ventral anterior cingulate area 24 + dorsal anterior cingulate area 32 + pregenual area 33 +27 28 34 35 36 | hippocampal region : presubiculum 27 + entorhinal area 28 + dorsal entorhinal area 34 + perirhinal area 35 + ectorhinal area 36 +37 | occipitotemporal area 37 +38 | temporopolar area 38 +39 | angular area 39 +40 | supramarginal area 40 +41 42 | anterior transverse temporal area 41 + posterior transverse temporal area 42 +44 45 46 | opercular area 44 + triangular area 45 + middle frontal area 46 diff --git a/brodmann/merge_cluster.m b/brodmann/merge_cluster.m new file mode 100644 index 0000000..1e4a8e0 --- /dev/null +++ b/brodmann/merge_cluster.m @@ -0,0 +1,15 @@ +function [pcl2, vc]=merge_cluster(pcl, parcelle) +% merge parcels (pcl) computed from Anael's geod_cluster +% [pcl2, vc]=merge_cluster(pcl, parcelle) +% +% pcl2: merged parcelles +% vc: vector of the parcel number for each source +% pcl: product of geod_cluster +% parcelle: list of {list of seeds} + +for i=1:length(parcelle) + seeds=[parcelle{i}]; + seeds(seeds>2027)=[]; + pcl2{i}= [pcl{seeds}]; + vc([pcl2{i}])=i; +end \ No newline at end of file diff --git a/brodmann/montreal_brodmann.m b/brodmann/montreal_brodmann.m new file mode 100644 index 0000000..dc33b61 --- /dev/null +++ b/brodmann/montreal_brodmann.m @@ -0,0 +1,162 @@ +mypath + +addpath(fullfile(HOMEDIR, 'mtoolbox/brainstorm/Toolbox')) +addpath(fullfile(HOMEDIR, 'mtoolbox/spm2')) +addpath(fullfile(MATLABDIR)) +addpath(fullfile(MATLABDIR, 'ctf2tal')) +addpath(fullfile(MATLABDIR, 'stormvisa')) +addpath(fullfile(MATLABDIR, 'brodmann')) + + +load(fullfile(HOMEDIR, 'data/subjects/montreal_subject/montreal_4layer_tess')) +fv.vertices=Vertices{4}'; +fv.vertconn=VertConn{4}; +clear Vertices; +fv.faces=Faces{4}; +clear Faces; +clear VertConn +clear tess2mri_interp +try + load(fullfile(USBDIR, 'data/subjects/montreal_subject/TalairachMRIscs.mat'), 'TAL') + S(1)=load(fullfile(HOMEDIR, 'data/subjects/montreal_subject/montreal_subjectimage.mat'), 'SCS') + S(2).SCS=S(1).SCS(1); + S(3)=load(fullfile(HOMEDIR, 'mtoolbox/brainstorm/Phantom/montreal_subject/montreal_subjectimage_info.mat'), 'SCS') + S(4).SCS=S(3).SCS(1); + +catch +end +% fv2.vertices=scs2mri(S(2),fv.vertices'*1000)'-repmat([92 128 74],[10001,1]); +fv2.faces=fv.faces; +fv2.vertconn=fv.vertconn; + +fv2.vertices=scs2mri(S(end),fv.vertices'*1000); +[ignore,fv2.vertices]=mri2scs(TAL,fv2.vertices); +fv2.vertices=fv2.vertices'; +v2=fv2.vertices; + +disp('Lecture des fichiers Analyze') +apc=[0 0 0; 0 -23 0; 0 0 50]; +verttal = ctf2tal(v2, [], 'ACPCIH_ctf', [apc], 'brainvert', v2); + +figure(1) +clf +view_cortex(fv2.faces,v2/1000,'FaceColor','none', 'EdgeColor', 'k' , 'FaceAlpha', .5) +hold on +view_cortex(fv2.faces,verttal, 'FaceColor','none', 'EdgeColor', 'r') +view_cortex(fv2.faces,mni2tal(v2)/1000, 'FaceColor','none', 'EdgeColor', 'g') +legend({'orig','tal', 'mni2tal'},0) + +view(90,0) +rotate3d +drawnow + + + +if not(exist('xyz2')) + DECIMATION=1000 +% y=spm_vol(fullfile(HOMEDIR, 'data', 'subjects', 'colin27','aal.img')) + y=spm_vol(fullfile(HOMEDIR, 'data', 'subjects', 'colin27','brodmann.img')) + v=spm_read_vols(y); + % Undo L-R flipping + if spm_flip_analyze_images + y.mat(1:5:11)=y.private.hdr.dime.pixdim(2:4); + y.mat(1,4)=-y.mat(1,4); + end + ref=v(v>0); + try + [ign,labels,ign]=textread(strrep(y.fname,'img', 'txt'), '%f%s%f'); + catch + labels=cellstr(num2str([1:max(ref)]')); + end + % in MRI + [xyz(:,1),xyz(:,2),xyz(:,3)]=ind2sub(size(v), find(v>0)); + xyz2=xyz(1:DECIMATION:end,:); + ref2=ref(1:DECIMATION:end); + % in MNI + xyz2=[xyz2 ones(size(xyz2,1),1)]*y.mat'; + xyz2=xyz2(:,1:3); + + % I dunno why but brodmann from MRIcro is not centered?!? + % xyz2=xyz2-repmat([-3 10 -10], [size(xyz2,1) 1]); + v2=fv2.vertices+repmat([0 10 -8],10001,1); +end +plot3d([xyz2]/1000) + +% r=labelize(v2, xyz2, ref2, 'TimeBar', 'on'); + +return +labelviewer(struct('vertices', v2, 'faces', fv.faces), r, xyz2, ref2, labels) + + +r=ref2(closest(v2,xyz2)); + +if 0 + load(fullfile(HOMEDIR, 'matlab/ctf2tal/TTareas.mat')); + xyz=xyz(1:10:end,:); + ref=ref(1:10:end); + ref=double(ref); + xyz=ctf2tal(xyz, [], 'brainvert', xyz, 'CA', [0 0 0], 'CP', [0 -23 0], 'IH' ,[0 0 50]); + r=labelize(verttal, xyz2, ref, 'TimeBar', 'on'); + talairach.vertices=verttal; + talairach.faces=fv.faces; +end + +return + +save(fullfile(SCOUTDIR,date,[date '_brodmann.mat']), 'r','talairach', 'xyz', 'ref', 'labels' ) + +disp('Sauvegarde dans un fichier CorticalScout') + +scoutfile=dir(fullfile(RESDIR,date, [date '*CorticalScout_2.mat'])); +scouts=load(fullfile(RESDIR,date, scoutfile.name)); + +BA=strmatch('Brodmann', labels); + +SIDES=['R', 'L']; +for side=0:1 +for i=1:length(BA) + vba=find(r==BA(i) & left==side); + scouts.CorticalScouts.CorticalMarkersLabels{i+42*side}=[labels{BA(i)} '-' SIDES(side+1)]; + if not(isempty(vba)) + scouts.CorticalScouts.CorticalSpots(i+42*side)=vba(1); + scouts.CorticalScouts.CorticalMarkers(i+42*side,:) = v_ctf(vba(1),:); +else + scouts.CorticalScouts.CorticalSpots(i+42*side)=NaN; + scouts.CorticalScouts.CorticalMarkers(i+42*side,:) = NaN; +end + scouts.CorticalScouts.CorticalProbePatches{i+42*side}=vba; + scouts.CorticalScouts.CorticalProbeDepth(i+42*side)=0; + scouts.CorticalScouts.CorticalProbePatchesXYZ{i+42*side}=v_ctf(scouts.CorticalScouts.CorticalProbePatches{i},:)'; +end +end +bascoutfile=strrep(scoutfile.name, '_2.mat', '_BA.mat') + CorticalScouts=scouts.CorticalScouts; + ResultFile=scouts.ResultFile; + ActiveTess=scouts.ActiveTess; + save(fullfile(SCOUTDIR,date,bascoutfile), 'ResultFile','ActiveTess','CorticalScouts') + +return + + + +cmap=hsv(69); +hf=figure; +p=patch('vertices', fv.vertices, 'faces',fv.faces, ... + 'CData', r, 'CDataMapping', 'direct', ... + 'EdgeColor', 'none', 'FaceColor', 'flat'); +colormap(cmap) +h=colorbar; +axis image +light('position' , [-1 1 0]) +light('position' , [ 0 0 1]) +light('position' , [ 1 -1 -1]) +light('position' , [ 1 1 0]) +l=findobj(hf, 'type', 'light'); +set(l, 'color', .8*ones(1,3)) +set(h, 'YDir', 'reverse'); +set(h, 'position', [0.8314 0.1100 0.02 0.8150]) +view(3) +rotate3d +lighting gouraud + +return diff --git a/bugs/emptiness.m b/bugs/emptiness.m new file mode 100644 index 0000000..ef943aa --- /dev/null +++ b/bugs/emptiness.m @@ -0,0 +1,163 @@ +disp('************************************************') +disp('* *') +disp('* BUGS in Matlab *') +disp('* *') +disp('************************************************') + +disp(['Version: ' version]) + +disp('------------------------------------------------') +disp('Matrix product is ill defined for "empty" arrays') +A=ones(2,0) +B=ones(0,3) +disp('A*B=') +A*B +disp('------------------------------------------------') + + + +disp('------------------------------------------------') +disp('Matrix product is ill defined for "empty" arrays') +A=ones(2,2) +disp('A(:,[])*A(:,[])'' =') +A(:,[])*A(:,[])' +disp('size(A(:,[]))') +size(A(:,[])) +disp('------------------------------------------------') + + +disp('------------------------------------------------') +disp(' Inconsistent logics due to If short circuit') +disp(' if 1 | [] ; ''T'' , else ''F'',end ') +if 1 | [] ; disp('T') , else disp('F'), end +disp(' if [] | 1 ; ''T'' , else ''F'', end ') +if [] | 1 ; disp('T') , else disp('F'), end + +disp('------------------------------------------------') +disp(' Inconsistent logics due to scalar expansion') +disp(' 1 == [] ') +1 == [] +disp(' [] == 1 ') +[] == 1 +disp(' [ 1 & [] ] ') +[ 1 & [] ] +disp(' [ [] & 1 ] ') +[ [] & 1 ] +disp(' [ 1 | [] ] ') +[ 1 | [] ] +disp(' [ [] | 1 ] ') +[ [] | 1 ] +% disp(' [ 1 || [] ] ') +% [ 1 || [] ] +% disp(' [ [] || 1 ] ') +% [ [] || 1 ] + + + +disp('------------------------------------------------') +disp(' Fields of empty arrays of structs') +a=dir +b=a([]); +disp('b=setfield(a([]),''test'', [])') +try +b=setfield(b,'test', []) +catch ME + disp('Error:') + disp(ME.message) +end + + + +% $$$ Groupes de discussion : comp.soft-sys.matlab +% $$$ De : Scott French - Rechercher les messages de cet auteur +% $$$ Date : 2000/09/13 +% $$$ Objet : Re: if (1 | []) : inconsistencies? +% $$$ +% $$$ Eric, +% $$$ +% $$$ There are a couple of issues interacting here. First, there's scalar +% $$$ expansion. In an expression of the form +% $$$ +% $$$ Scalar OPERATOR Matrix +% $$$ +% $$$ Scalar is replaced by a matrix with the same dimensions as Matrix, and +% $$$ whose every element is Scalar. Then the operator's operation is +% $$$ performed. Applying this to the expression +% $$$ +% $$$ 1 | [] +% $$$ +% $$$ You get the value 1 replaced by a matrix with the same dimensions as +% $$$ [], in other word, an empty matrix. So 1 | [] evaluates to [] | [], +% $$$ which is []. +% $$$ +% $$$ Second, IF treats the empty matrix conditional as false. It probably +% $$$ should be treated as true, since technically every element of the +% $$$ empty matrix (what few of them there are) is nonzero. It was +% $$$ implemented with empty treated as false a long time ago, and when we +% $$$ tried to change it a few years ago we found that we broke such a large +% $$$ amount of code (just among the M-code in MATLAB and our toolboxes) +% $$$ that we decided it would cause more problems for our customers if we +% $$$ fixed it than if we left it alone. +% $$$ +% $$$ Thirdly, in MATLAB 5.0, we added short circuited conditionals for IF +% $$$ and WHILE if the left hand side of the conditional is a +% $$$ scalar. Unfortunately, at the time, we didn't realize that empty +% $$$ matrices and scalar expansion violate a requirement for short +% $$$ circuiting. That is, in order to short circuit, +% $$$ +% $$$ 1 | X == 1 +% $$$ +% $$$ has to be true for all values of X. If X is the empty matrix, though, +% $$$ then the requirement fails. Its worth pointing out that even if IF +% $$$ treated [] as true, we would still have a problem because to short +% $$$ circuit logical AND, you need +% $$$ +% $$$ 0 & X == 0 +% $$$ +% $$$ to be true for all X, and if [] is treated as true, then this fails +% $$$ for X equal to [] as well. +% $$$ +% $$$ Since short circuited conditionals have been part of MATLAB since +% $$$ version 5.0, we have the same issue as before, that is, maintaining +% $$$ backward compatibility versus improving/fixing the MATLAB language. +% $$$ +% $$$ So now at least you know what is going on. You are the first person +% $$$ that I know of to state the issue as succinctly as you have, and as a +% $$$ result a bunch of us here are now in deep meditative trances trying to +% $$$ decide what the right thing to do is. In the meantime, if having [] be +% $$$ treated as false is a problem for you, you can pass conditional +% $$$ expressions that could potentially be empty through the ALL function +% $$$ first. If the short circuiting is a problem, you could call the +% $$$ functional form of OR, like this +% $$$ +% $$$ if (or(1,[])), disp('T'), else disp('F'), end % F +% $$$ +% $$$ I hope these suggestions are helpful. +% $$$ +% $$$ Sincerely, +% $$$ Scott French +% $$$ Software Engineer +% $$$ +% $$$ - Masquer le texte des messages pr?c?dents - +% $$$ - Afficher le texte des messages pr?c?dents - +% $$$ Eric Durant writes: +% $$$ > After reading 2-12 ff. and 2-398 ff. in the Matlab Function Reference, +% $$$ > Volume 1, Version 5, I expect all of the following to give the same +% $$$ > result ('T'), but they don't. Is this a bug or documentation error, or +% $$$ > have I missed something? +% $$$ +% $$$ > expr=(1|[]); if expr , disp('T'), else disp('F'), end % F +% $$$ > expr=([]|1); if expr , disp('T'), else disp('F'), end % F +% $$$ > if(1|[]), disp('T'), else disp('F'), end % T +% $$$ > if([]|1), disp('T'), else disp('F'), end % F +% $$$ > if(all(1|[])), disp('T'), else disp('F'), end % T +% $$$ > if(all([]|1)), disp('T'), else disp('F'), end % T + +% $$$ > I've tested this with R11.1 on PCWIN and SOL2. +% $$$ +% $$$ > -- Eric Durant +% $$$ > http://edurant.com/ +% $$$ +% $$$ -- +% $$$ Scott French E-mail - s...@mathworks.com +% $$$ The MathWorks, Inc. WWW - http://www.mathwor \ No newline at end of file diff --git a/cartool/J_segmentation.m b/cartool/J_segmentation.m new file mode 100644 index 0000000..39da59c --- /dev/null +++ b/cartool/J_segmentation.m @@ -0,0 +1,297 @@ +function [GFP,DE,indm,indM,DEm]=J_segmentation(flowc,graphe,Time,tstim,tend,GFP) + +if nargin<3 + Time=flowc.t; + [m,tstim]=min(abs(Time)); + + DE=sqrt(flowc.de); + nF=size(flowc.F,1); + GFP=sqrt(sum((flowc.F-repmat(mean(flowc.F),nF,1)).^2,1)); + % DE modifié par la moyenne des champs de vitesse + + nV=size(flowc.V,1); + for tt=1:size(flowc.V,3) + V=flowc.V(:,:,tt); + + mV=mean(V); + DEm(tt)=sqrt(sum((V(:,1)-mV(1)).^2) + sum((V(:,2)-mV(2)).^2) + sum((V(:,3)-mV(3)).^2)); + end + +else + DEm=sqrt(flowc); +end + + + +%%%%%%%%%%%%%%%%% +% Vizualisation % +%%%%%%%%%%%%%%%%% + +[mval,indm]=lmin(DEm,3); +[Mval,indM]=lmax(DEm,3); + +if graphe + figure + plot(Time,DEm,'Color',[0 0 0]) + hold on + if nargin==6 + figure(2) + plot(Time,GFP,'Color',[0 0 0]) + hold on + end +end + +goodm=find(indm>tstim & indmtstim & indM tstim + +% OUTPUTS +if nargin<3 + + +else + GFP=stable_state; + DE=trans_state; + DEm=0; +end +%%%%%%%%%%%%%%%% +% Subfunctions % +%%%%%%%%%%%%%%%% + +function [tm,tM]=intermediate_values(curve,ind,thres) +% Find the nearest indices before and after ind such that curve==thres +tm=ind; +tM=ind; + +if curve(ind)1 + tm=tm-1; + end + + while curve(tM)thres & tm>1 + tm=tm-1; + end + + while curve(tM)>thres & tM x(i-1) + if x(i) > x(i+1) % definite max + lmval =[lmval x(i)]; + indd = [ indd i]; + elseif x(i)==x(i+1)&x(i)==x(i+2) % 'long' flat spot + %lmval =[lmval x(i)]; %1 comment these two lines for strict case + %indd = [ indd i]; %2 when only definite max included + i = i + 2; % skip 2 points + elseif x(i)==x(i+1) % 'short' flat spot + %lmval =[lmval x(i)]; %1 comment these two lines for strict case + %indd = [ indd i]; %2 when only definite max included + i = i + 1; % skip one point + end + end + i = i + 1; +end +if filt>0 & ~isempty(indd), + if (indd(1)<= 3)|(indd(length(indd))+2>length(xx)), + rng=1; %check if index too close to the edge + else rng=2; + end + for ii=1:length(indd), % Find the real maximum value + [val(ii) iind(ii)] = max(xx(indd(ii) -rng:indd(ii) +rng)); + iind(ii)=indd(ii) + iind(ii) -rng-1; + end + indd=iind; lmval=val; +else +end + + + + +function [lmval,indd]=lmin(xx,filt) +%LMIN function [lmval,indd]=lmin(x,filt) +% Find local minima in vector X, where LMVAL is the output +% vector with minima values, INDD is the corresponding indeces +% FILT is the number of passes of the small running average filter +% in order to get rid of small peaks. Default value FILT =0 (no +% filtering). FILT in the range from 1 to 3 is usially sufficient to +% remove most of a small peaks +% Examples: +% xx=0:0.01:35; y=sin(xx) + cos(xx ./3); +% plot(xx,y); grid; hold on; +% [a b]=lmin(y,2) +% plot(xx(a),y(a),'r+') +% see also LMAX, MAX, MIN + +% +%**************************************************| +% Serge Koptenko, Guigne International Ltd., | +% phone (709)895-3819, fax (709)895-3822 | +%--------------06/03/97----------------------------| + +x=xx; +len_x = length(x); +fltr=[1 1 1]/3; +if nargin <2, filt=0; +else + x1=x(1); x2=x(len_x); + + for jj=1:filt, + c=conv(fltr,x); + x=c(2:len_x+1); + x(1)=x1; + x(len_x)=x2; + end +end + +lmval=[]; +indd=[]; +i=2; % start at second data point in time series + +while i < len_x-1, + if x(i) < x(i-1) + if x(i) < x(i+1) % definite min + lmval =[lmval x(i)]; + indd = [ indd i]; + + elseif x(i)==x(i+1)&x(i)==x(i+2) % 'long' flat spot + %lmval =[lmval x(i)]; %1 comment these two lines for strict case + %indd = [ indd i]; %2 when only definite min included + i = i + 2; % skip 2 points + + elseif x(i)==x(i+1) % 'short' flat spot + %lmval =[lmval x(i)]; %1 comment these two lines for strict case + %indd = [ indd i]; %2 when only definite min included + i = i + 1; % skip one point + end + end + i = i + 1; +end + +if filt>0 & ~isempty(indd), + if (indd(1)<= 3)|(indd(length(indd))+2>length(xx)), + rng=1; %check if index too close to the edge + else rng=2; + end + + for ii=1:length(indd), + [val(ii) iind(ii)] = min(xx(indd(ii) -rng:indd(ii) +rng)); + iind(ii)=indd(ii) + iind(ii) -rng-1; + end + indd=iind; lmval=val; +else +end \ No newline at end of file diff --git a/cartool/converts.m b/cartool/converts.m new file mode 100644 index 0000000..613cc98 --- /dev/null +++ b/cartool/converts.m @@ -0,0 +1,16 @@ +cd('D:\ndiayek\data\rings') +x=load('random02-Tr18_data_trial_1'); +c=load('random02-Tr18_channel.mat'); +[y,ic]=getMEGChannels(c.Channel); +f=x.F(i,:); + +fid = fopen('random02-Tr18_data_trial_1.ep','wt'); +for t=1:2125 + for i=1:151 + fprintf(fid,'%g ',f(i,t)); + end + fprintf(fid,'\n'); +end + +fclose(fid); + \ No newline at end of file diff --git a/cartool/ctf2cartool.m b/cartool/ctf2cartool.m new file mode 100644 index 0000000..61ba76e --- /dev/null +++ b/cartool/ctf2cartool.m @@ -0,0 +1,40 @@ +function [] = ctf2cartool(ctf) +%CTF2CARTOOL - Converts CTF M/EEG data to .ep text file and .xyz channel file +% [] = ctf2cartool(input) +% +% Example +% >> ctf=ctf_read_meg4('folder.ds',[],'meg'); +% >> ctf2cartool(ctf) +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2007 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2007-01-30 Creation +% +% ----------------------------- Script History --------------------------------- + +fid = fopen([ctf.folder(1:end-3) '.ep'],'wt'); +for t=1:size(ctf.data,1); + for i=1:size(ctf.data,2); + fprintf(fid,'%g ',ctf.data(t,i)); + end + fprintf(fid,'\n'); +end +fclose(fid); + +nsens=length(ctf.sensor.label); + +fid = fopen([ctf.folder(1:end-3) '_channels.xyz'],'wt'); +fprintf(fid,'%d 1', nsens); +for i=1:nsens + fprintf(fid,'%g %g %g ',ctf.sensor.location(:,i)); + fprintf(fid,'%s\n',ctf.sensor.label{i}); +end +fclose(fid); \ No newline at end of file diff --git a/cartool/event2mrkfile.m b/cartool/event2mrkfile.m new file mode 100644 index 0000000..b2030a2 --- /dev/null +++ b/cartool/event2mrkfile.m @@ -0,0 +1,29 @@ +function [] = event2mrkfile(mrkfile,event,srate,pnts) +%EVENT2MRKFILE - Writes EEGLAB events to .MRK marker (Cartool) +% [] = event2mrkfile(event,srate) +% +% Example +% >> event2mrkfile +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2007 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2007-04-04 Creation +% +% ----------------------------- Script History --------------------------------- + +fid=fopen(mrkfile, 'wt'); +fprintf(fid,'TL02\n') +for i=1:length(event) + fprintf(fid,'\t%d\t\t', round(event(i).latency)); + fprintf(fid,'%d\t\t', round(event(i).latency+event(i).duration)); + fprintf(fid,'"%02d"\n', event(i).type); +end +fclose(fid) \ No newline at end of file diff --git a/cartool/pierre/computeavgref.m b/cartool/pierre/computeavgref.m new file mode 100644 index 0000000..1cd031c --- /dev/null +++ b/cartool/pierre/computeavgref.m @@ -0,0 +1,23 @@ +function avgref=computeavgref(thedata) +% computeavgref: computes the average reference of a data set +% +% inputs: data as a 2-D numeric array where dimension 1 contains the +% timeframes, dimension 2 contains the channels +% +% outputs: average reference as a 1-D numeric array that contains the +% average reference at each timeframe +% +% Cartool: http://brainmapping.unige.ch/Cartool.htm +% +% author of this script: pierre.megevand@medecine.unige.ch + + +% define number of channels and time frames +numtimeframes=size(thedata,1); +numchannels=size(thedata,2); + +% compute average reference +avgref=zeros(numtimeframes,1); +for i=1:numtimeframes + avgref(i)=sum(thedata(i,:))/numchannels; +end \ No newline at end of file diff --git a/cartool/pierre/computedataagainstref.m b/cartool/pierre/computedataagainstref.m new file mode 100644 index 0000000..fc9c382 --- /dev/null +++ b/cartool/pierre/computedataagainstref.m @@ -0,0 +1,34 @@ +function thedata=computedataagainstref(thedata,ref) +% computedataagainstref: changes the reference of a data set +% +% inputs: data as a 2-D numeric array where dimension 1 contains the +% timeframes, dimension 2 contains the channels; ref as either a number +% (1-D numeric array) or the 'avgref' string for average reference +% +% outputs: data as a 2-D numeric array where dimension 1 contains the +% timeframes, dimension 2 contains the channels +% +% Cartool: http://brainmapping.unige.ch/Cartool.htm +% +% author of this script: pierre.megevand@medecine.unige.ch + + +% define number of channels and time frames +numtimeframes=size(thedata,1); +numchannels=size(thedata,2); + +% compute data against selected reference +if ischar(ref)==1&strcmp(ref,'avgref')==1 + avgref=computeavgref(thedata); + for i=1:numtimeframes + thedata(i,:)=thedata(i,:)-avgref(i); + end +elseif ischar(ref)==1&strcmp(ref,'avgref')==0 + error('Unknown code for reference'); +elseif isnumeric(ref)==1&ref>=1&ref<=numchannels + for i=1:numtimeframes + thedata(i,:)=thedata(i,:)-thedata(i,ref); + end +elseif isnumeric(ref)==1&(ref<1|ref>numchannels); + error('Reference specified does not exist'); +end \ No newline at end of file diff --git a/cartool/pierre/computedissandsc.m b/cartool/pierre/computedissandsc.m new file mode 100644 index 0000000..9ff17d2 --- /dev/null +++ b/cartool/pierre/computedissandsc.m @@ -0,0 +1,33 @@ +function [diss,sc]=computedissandsc(thedata1,thedata2) +% computediss: computes dissimilarity and spatial correlation between 2 +% data sets +% +% inputs: data as 2-D numeric arrays where dimension 1 contains the +% timeframes, dimension 2 contains the channels +% +% outputs: diss and sc as 1-D numeric arrays that contain the +% dissimilarity and spatial correlation at each timeframe +% +% Cartool: http://brainmapping.unige.ch/Cartool.htm +% +% author of this script: pierre.megevand@medecine.unige.ch + + +% verify equal number of channels and time frames +if size(thedata1,1)~=size(thedata2,1) + error('Number of timeframes is different'); +elseif size(thedata1,2)~=size(thedata2,2) + error('Number of channels is different'); +end + +% compute dissimilarity +diss=zeros(size(thedata1,1),1); +for i=1:size(thedata1,1) + diss(i)=sqrt(sum((thedata1(i,:)-thedata2(i,:)).^2)/size(thedata1,2)); +end + +% compute spatial correlation +sc=zeros(size(diss,1),1); +for i=1:size(diss,1) + sc(i)=(2-diss(i)^2)/2; +end diff --git a/cartool/pierre/computegfp.m b/cartool/pierre/computegfp.m new file mode 100644 index 0000000..e149384 --- /dev/null +++ b/cartool/pierre/computegfp.m @@ -0,0 +1,24 @@ +function gfp=computegfp(thedata) +% computegfp: calculates the global field power of a data set +% +% inputs: data as a 2-D numeric array where dimension 1 contains the +% timeframes, dimension 2 contains the channels +% +% outputs: global field power as a 1-D numeric array that contains the +% global field power at each timeframe +% +% Cartool: http://brainmapping.unige.ch/Cartool.htm +% +% author of this script: pierre.megevand@medecine.unige.ch + + +% define number of channels and time frames +numtimeframes=size(thedata,1); +numchannels=size(thedata,2); + +% compute global field power +gfp=zeros(numtimeframes,1); +avgref=computeavgref(thedata); +for i=1:numtimeframes + gfp(i)=sqrt(sum((thedata(i,:)-avgref(i)).^2)/numchannels); +end \ No newline at end of file diff --git a/cartool/pierre/openels.m b/cartool/pierre/openels.m new file mode 100644 index 0000000..ac5ff74 --- /dev/null +++ b/cartool/pierre/openels.m @@ -0,0 +1,38 @@ +% opens a Cartool electrode coordinates file (.els) +% +% Cartool: http://brainmapping.unige.ch/Cartool.htm +% +% author of this script: pierre.megevand@medecine.unige.ch + + +% check whether filename exists +if exist('filename')==0 + error('filename is not defined') +end + +% open filename for reading in text mode +fid=fopen(openfilename,'rt'); + +% read file +els=textscan(fid,'%s','delimiter','/n'); +els=els{1}; +magicnumber=els{1}; +totalnumelectrodes=str2num(els{2}); +numclusters=str2num(els{3}); +pointer=4; + +for i=1:numclusters + clusters(i).name=els{pointer}; + clusters(i).numelectrodes=str2num(els{pointer+1}); + clusters(i).dimension=str2num(els{pointer+2}); + pointer=pointer+3; + for j=1:clusters(i).numelectrodes + clusters(i).electrodes(j).name=sscanf(els{pointer},'%*f %*f %*f %s',1); + clusters(i).electrodes(j).x=sscanf(els{pointer},'%f',1); + clusters(i).electrodes(j).y=sscanf(els{pointer},'%*f %f',1); + clusters(i).electrodes(j).z=sscanf(els{pointer},'%*f %*f %f',1); + pointer=pointer+1; + end +end + +fclose(fid); \ No newline at end of file diff --git a/cartool/pierre/openeph.m b/cartool/pierre/openeph.m new file mode 100644 index 0000000..84258a5 --- /dev/null +++ b/cartool/pierre/openeph.m @@ -0,0 +1,59 @@ +function [numchannels,numtimeframes,samplingrate,thedata]=openeph(openfilename) +% openeph: opens a Cartool evoked potential data file (.ep(h)) +% +% inputs: full path and name of the file to open +% +% outputs: number of channels, number of timeframes (and sampling rate for +% .eph files) as 1-D numeric arrays; data as a 2-D numeric array where +% dimension 1 contains the timeframes, dimension 2 contains the channels +% +% Cartool: http://brainmapping.unige.ch/Cartool.htm +% +% author of this script: pierre.megevand@medecine.unige.ch + + +% open filename for reading in text mode +fid=fopen(openfilename,'rt'); + +% for .eph files +if strcmp(openfilename(end-3:end),'.eph')==1 + + % read header + eph=textscan(fid,'%s','delimiter','/n'); + eph=eph{1}; + numchannels=sscanf(eph{1},'%f',1); + numtimeframes=sscanf(eph{1},'%*f %f',1); + samplingrate=sscanf(eph{1},'%*f %*f %f',1); + + % prepare for reading data + formatstring='%f'; + if numchannels>1 + for i=1:numchannels-1 + formatstring=[formatstring ' %f']; + end + end + + % read data + thedata=zeros(numtimeframes,numchannels); + for i=1:numtimeframes + thedata(i,:)=sscanf(eph{i+1},formatstring); + end + +% for .ep files +elseif strcmp(openfilename(end-2:end),'.ep')==1 + thedata=''; + while ~feof(fid) + thedataline=fgetl(fid); + thedata=strvcat(thedata,thedataline); + end + thedata=str2num(thedata); + numtimeframes=size(thedata,1); + numchannels=size(thedata,2); + samplingrate=0; + +else + error('incorrect file type'); +end + +% close file +fclose(fid); \ No newline at end of file diff --git a/cartool/pierre/openfreq.m b/cartool/pierre/openfreq.m new file mode 100644 index 0000000..d35c2ca --- /dev/null +++ b/cartool/pierre/openfreq.m @@ -0,0 +1,53 @@ +function [numchannels,numfrequencies,numblocks,samplingrate,channels,frequencies,thedata]=openfreq(openfilename) +% openfreq: opens a Cartool frequency data file (.freq) +% +% inputs: full path and name of the file to open +% +% outputs: number of channels, number of frequencies, number of blocks and +% sampling rate as 1-D numeric arrays; frequencies as 1-D numeric array +% containing the frequencies; data as a 2-D numeric array where dimension 1 +% contains the frequencies, dimension 2 contains the channels, dimension 3 +% contains the blocks (=timeframes) +% +% ATTENTION: this function is unable to read FFT Complex files yet! +% +% Cartool: http://brainmapping.unige.ch/Cartool.htm +% +% author of this script: pierre.megevand@medecine.unige.ch + + +% open file for reading +fid=fopen(openfilename,'r'); + +% read fixed part of header +version=strcat(fread(fid,4,'int8=>char')'); +type=strcat(fread(fid,32,'int8=>char')'); +numchannels=fread(fid,1,'int32'); +numfrequencies=fread(fid,1,'int32'); +numblocks=fread(fid,1,'int32'); +samplingrate=fread(fid,1,'double'); +blockfrequency=fread(fid,1,'double'); +year=fread(fid,1,'int16'); +month=fread(fid,1,'int16'); +day=fread(fid,1,'int16'); +hour=fread(fid,1,'int16'); +minute=fread(fid,1,'int16'); +second=fread(fid,1,'int16'); +millisecond=fread(fid,1,'int16'); + +% read variable part of header +channels=strcat(fread(fid,[8,numchannels],'int8=>char')'); +frequencies=strcat(fread(fid,numfrequencies*16,'int8=>char')'); + +% read data +thedata=zeros(numfrequencies,numchannels,numblocks); +if strcmp(type,'FFT Norm')|strcmp(type,'FFT Norm2')|strcmp(type,'FFT Approximation')==1 + for i=1:numblocks + thedata(:,:,i)=fread(fid,[numfrequencies,numchannels],'float32'); + end +elseif strcmp(type,'FFT Complex')==1 + error('Sorry, I do not know how to read FFT Complex files yet! :)'); +end + +% close file +fclose(fid); \ No newline at end of file diff --git a/cartool/pierre/opensef.m b/cartool/pierre/opensef.m new file mode 100644 index 0000000..5626b15 --- /dev/null +++ b/cartool/pierre/opensef.m @@ -0,0 +1,39 @@ +function [numchannels,numtimeframes,samplingrate,thedata]=opensef(openfilename) +% opensef: opens a Cartool simple EEG data file (.sef) +% +% inputs: full path and name of the file to open +% +% outputs: number of channels, number of timeframes and sampling rate as +% 1-D numeric arrays; data as a 2-D numeric array where dimension 1 +% contains the timeframes, dimension 2 contains the channels +% +% Cartool: http://brainmapping.unige.ch/Cartool.htm +% +% author of this script: pierre.megevand@medecine.unige.ch + + +% open filename for reading +fid=fopen(openfilename,'r'); + +% read fixed part of header +version=strcat(fread(fid,4,'int8=>char')'); +numchannels=fread(fid,1,'int32'); +numauxchannels=fread(fid,1,'int32'); +numtimeframes=fread(fid,1,'int32'); +samplingrate=fread(fid,1,'float32'); +year=fread(fid,1,'int16'); +month=fread(fid,1,'int16'); +day=fread(fid,1,'int16'); +hour=fread(fid,1,'int16'); +minute=fread(fid,1,'int16'); +second=fread(fid,1,'int16'); +millisecond=fread(fid,1,'int16'); + +% read variable part of header +channels=strcat(fread(fid,[8,numchannels],'int8=>char')'); + +% read data +thedata=fread(fid,[numchannels,numtimeframes],'float32')'; + +% close file +fclose(fid); \ No newline at end of file diff --git a/cartool/pierre/saveeph.m b/cartool/pierre/saveeph.m new file mode 100644 index 0000000..f13573e --- /dev/null +++ b/cartool/pierre/saveeph.m @@ -0,0 +1,36 @@ +function saveeph(savefilename,thedata,varargin) +% saveeph: saves data as a Cartool evoked potential data file (.ep(h/sd/se)) +% +% inputs: full path and name of the file to save, data as a 2-D numeric +% array where dimension 1 contains the timeframes, dimension 2 contains the +% channels, (optional) samplingrate as 1-D numeric array passed as +% 3rd argument +% +% outputs: .ep(h/sd/se) file +% +% Cartool: http://brainmapping.unige.ch/Cartool.htm +% +% author of this script: pierre.megevand@medecine.unige.ch + + +if strcmp(savefilename(end-3:end),'.eph')==1 + numtimeframes=size(thedata,1); + numchannels=size(thedata,2); + samplingrate=varargin{1}; + theheader=[numchannels numtimeframes samplingrate]; + dlmwrite(savefilename,theheader,'\t'); + dlmwrite(savefilename,thedata,'delimiter','\t','-append'); +elseif strcmp(savefilename(end-2:end),'.ep')==1 + dlmwrite(savefilename,thedata,'delimiter','\t','-append'); +elseif strcmp(savefilename(end-4:end),'.epsd')==1|strcmp(savefilename(end-4:end),'.epse')==1 + if nargin==2 + dlmwrite(savefilename,thedata,'delimiter','\t','-append'); + elseif nargin==3 + numtimeframes=size(thedata,1); + numchannels=size(thedata,2); + samplingrate=varargin{1}; + theheader=[numchannels numtimeframes samplingrate]; + dlmwrite(savefilename,theheader,'\t'); + dlmwrite(savefilename,thedata,'delimiter','\t','-append'); + end +end \ No newline at end of file diff --git a/cartool/pierre/savesef.m b/cartool/pierre/savesef.m new file mode 100644 index 0000000..75c83d0 --- /dev/null +++ b/cartool/pierre/savesef.m @@ -0,0 +1,61 @@ +function savesef(savefilename,thedata,samplingrate) +% savesef: saves data as a Cartool simple EEG data file (.sef) +% +% inputs: full path and name of the file to save, data as a 2-D numeric +% array where dimension 1 contains the timeframes, dimension 2 contains the +% channels, samplingrate as 1-D numeric array +% +% outputs: .sef file +% +% Cartool: http://brainmapping.unige.ch/Cartool.htm +% +% author of this script: pierre.megevand@medecine.unige.ch + + +% define fixed part of header +version='SE01'; +numchannels=size(thedata,2); +numauxchannels=0; +numtimeframes=size(thedata,1); +year=0; +month=0; +day=0; +hour=0; +minute=0; +second=0; +millisecond=0; + +% open savefilename for writing +fid=fopen(savefilename,'w'); + +%write fixed part of header +fwrite(fid,version,'int8'); +fwrite(fid,numchannels,'int32'); +fwrite(fid,numauxchannels,'int32'); +fwrite(fid,numtimeframes,'int32'); +fwrite(fid,samplingrate,'float32'); +fwrite(fid,year,'int16'); +fwrite(fid,month,'int16'); +fwrite(fid,day,'int16'); +fwrite(fid,hour,'int16'); +fwrite(fid,minute,'int16'); +fwrite(fid,second,'int16'); +fwrite(fid,millisecond,'int16'); + +% define and write variable part of header +for i=1:numchannels + fwrite(fid,101,'int8'); + currentchannel=uint8(num2str(i)); + for j=1:size(currentchannel,2) + fwrite(fid,currentchannel(j),'int8'); + end + for k=j+2:8 + fwrite(fid,0,'int8'); + end +end + +% write data +count=fwrite(fid,thedata','float32'); + +% close file +fclose(fid); \ No newline at end of file diff --git a/cartool/read_eeg.m b/cartool/read_eeg.m new file mode 100644 index 0000000..de07ae9 --- /dev/null +++ b/cartool/read_eeg.m @@ -0,0 +1,47 @@ +%struct TSefHeader{ +% int Version; // magic number filled with the wide char 'SE01' +% int NumElectrodes; // total number of electrodes +% int NumAuxElectrodes; // out of which are auxiliaries +% int NumTimeFrames; // time length +% float SamplingFrequency; // frequency in Hertz +% short Year; // Date of the recording +% short Month; // (set to 00-00-0000 if unknown) +% short Day; +% short Hour; // Time of the recording +% short Minute; // (set to 00:00:00:0000 if unknown) +% short Second; +% short Millisecond; +% }; +% +% Variable part of the header: +% The names of the channels, as a matrix of NumElectrodes x 8 chars. +%typedef char TSefChannelName[8]; // 1 electrode name +% +% To allow an easy calculation of the data origin, be aware that names are +% always stored on 8 bytes, even if the string length is smaller than that. +% In this case, the remaining part is padded with bytes set to 0, f.ex. two +% consecutive names: +% +% binary values 70 112 122 00 00 00 00 00 65 70 55 00 00 00 00 00 ... +% string equivalence F P z \0 A F 7 \0 +% +% Data part: +% Starting at file position: +% sizeof ( TSefHeader ) + 8 * NumElectrodes +% data are stored as a float (Little Endian convention - PC) matrix written row by row: +% float data [ NumTimeFrames ][ NumElectrodes ]; + +frewind(fid) + +eeg.Version = fread(fid,1,'int') +eeg.NumElectrodes = fread(fid,1,'int') +eeg.NumAuxElectrodes = fread(fid,1,'int') +eeg.NumTimeFrames = fread(fid,1,'int'); +eeg.SamplingFrequency = fread(fid,1,'float'); +eeg.Year=fread(fid,1,'short'); +eeg.Month = fread(fid,1,'short'); +eeg.Day = fread(fid,1,'short'); +eeg.Hour = fread(fid,1,'short'); +eeg.Minute = fread(fid,1,'short'); +eeg.Second = fread(fid,1,'short'); +eeg.Millisecond = fread(fid,1,'short'); diff --git a/cartool/read_eph.m b/cartool/read_eph.m new file mode 100644 index 0000000..9082de9 --- /dev/null +++ b/cartool/read_eph.m @@ -0,0 +1,7 @@ +function [eeg] =read_eph(ephfile) +% read_eph() - Reads EPH file (Cartool) + +eeg.Filename = ephfile; +[eeg.NumElectrodes eeg.NumTimeFrames eeg.SamplingFrequency]=textread(ephfile,'%d %d %d',1); +eeg.data=textread(ephfile,'%f',eeg.NumElectrodes*eeg.NumTimeFrames , 'headerlines',1); +eeg.data=reshape(eeg.data,[eeg.NumElectrodes eeg.NumTimeFrames]); diff --git a/cartool/read_is.m b/cartool/read_is.m new file mode 100644 index 0000000..27765a1 --- /dev/null +++ b/cartool/read_is.m @@ -0,0 +1,28 @@ +function [K]=read_is(seffile) +%read_is() - reads IS inverse solution file (Cartool) +% [K]=read_is(isfile) +% K is the transposed inverse matrix [M electrodes]-by-[N sources] +% or possibly, if vectorial, [M electrodes]-by-[N sources]-by-3 + +fid=fopen(seffile, 'rb'); +tag=char(fread(fid,4,'char')'); +switch (tag) + case 'IS01' + datatype='float32'; + case 'IS02' + datatype='double'; + otherwise + error('Unknown tag: %s', tag) +end +ne=fread(fid,1,'int32'); +ns=fread(fid,1,'int32'); +scalar=double(fread(fid,1,'char')); +K=fread(fid,ne*ns*(3 - 2*scalar),datatype); +K=reshape(K,[ne ns (3 - 2*scalar) ]); + +% We keep the transposed inverse matrix. +% if scalar +% K=transpose(K); +% else +% K=permute(K, [2 3 1]); +% end diff --git a/cartool/read_mrk.m b/cartool/read_mrk.m new file mode 100644 index 0000000..f44a75f --- /dev/null +++ b/cartool/read_mrk.m @@ -0,0 +1,33 @@ +function [mrk]=read_mrk(mrkfile) +%read_mrk - reads .MRK marker file (Cartool) + +ascii=1; + +if ascii + mrk.Filename = mrkfile; + mrk.Name=textread(mrkfile, '%s', 1); + [mrk.Position mrk.To mrk.Type]=textread(mrkfile, '%d %d %s', 'headerlines',1); + +else + fid=fopen('Waridel\Alan_UG.EEG.MRK', 'r') + % struct mrkheader { + % char magic[4]; // always 'TL01' + % } mrkheader; // size 4 + magic=char(fread(fid,4,'char')'); + + % And the format for a single marker is: + % struct onemrk { + % long Position; // from this TF + % long To; // to this TF + % ushort Code; // trigger code + % ushort Type; // should always be 0x2 for a marker + % ushort Dummy; // not used, for the user + % char Name[6]; // name of the tag + % } onemrk; // size 20 + mrk.Position=fread(fid,1,'uint32'); + mrk.To=fread(fid,1,'uint32'); + mrk.Type=fread(fid,1,'uint16'); + mrk.Dummy=fread(fid,1,'uint16'); + mrk.Name=char(fread(fid,6,'char')); + fclose(fid) +end \ No newline at end of file diff --git a/cartool/read_ris.m b/cartool/read_ris.m new file mode 100644 index 0000000..64c8f6f --- /dev/null +++ b/cartool/read_ris.m @@ -0,0 +1,28 @@ +function [R,SF]=read_ris(risfile) +%read_ris() - reads RIS result of inverse solution file (Cartool) +% [R,SF]=read_ris(risfile) +% R is the values +% FS ssampling frequency +fid=fopen(risfile, 'rb'); +tag=char(fread(fid,4,'char')'); +switch (tag) + case 'RI01' + otherwise + warning('Unknown tag in file header: %s', tag) +end + +ns=fread(fid,1,'int32'); +nt=fread(fid,1,'int32'); +SF=fread(fid,1,'float32'); +isinversescalar=double(fread(fid,1,'char')); +K=fread(fid,ns*(3 - 2*isinversescalar)*nt,datatype); +K=reshape(K,[ns*(3 - 2*isinversescalar) nt]); +K=transpose(K); +K=reshape(K,[nt ns (3 - 2*isinversescalar) ]); + +% We keep the transposed inverse matrix. +% if scalar +% K=transpose(K); +% else +% K=permute(K, [2 3 1]); +% end diff --git a/cartool/read_sef.m b/cartool/read_sef.m new file mode 100644 index 0000000..7c0ca1c --- /dev/null +++ b/cartool/read_sef.m @@ -0,0 +1,55 @@ +function [eeg]=read_sef(seffile) +%read_sef() - reads SEF file (Cartool) +% [eeg]=read_sef(seffile) +fid=fopen(seffile, 'rb'); + +%struct TSefHeader{ +% int Version; // magic number filled with the wide char 'SE01' +% int NumElectrodes; // total number of electrodes +% int NumAuxElectrodes; // out of which are auxiliaries +% int NumTimeFrames; // time length +% float SamplingFrequency; // frequency in Hertz +% short Year; // Date of the recording +% short Month; // (set to 00-00-0000 if unknown) +% short Day; +% short Hour; // Time of the recording +% short Minute; // (set to 00:00:00:0000 if unknown) +% short Second; +% short Millisecond; +% }; +% +% Variable part of the header: +% The names of the channels, as a matrix of NumElectrodes x 8 chars. +%typedef char TSefChannelName[8]; // 1 electrode name +% +% To allow an easy calculation of the data origin, be aware that names are +% always stored on 8 bytes, even if the string length is smaller than that. +% In this case, the remaining part is padded with bytes set to 0, f.ex. two +% consecutive names: +% +% binary values 70 112 122 00 00 00 00 00 65 70 55 00 00 00 00 00 ... +% string equivalence F P z \0 A F 7 \0 +% +% Data part: +% Starting at file position: +% sizeof ( TSefHeader ) + 8 * NumElectrodes +% data are stored as a float (Little Endian convention - PC) matrix written row by row: +% float data [ NumTimeFrames ][ NumElectrodes ]; + +frewind(fid) + +eeg.Version = char(fread(fid,4,'uint8')'); % fread(fid,1,'int'); +eeg.NumElectrodes = fread(fid,1,'int'); +eeg.NumAuxElectrodes = fread(fid,1,'int'); +eeg.NumTimeFrames = fread(fid,1,'int'); +eeg.SamplingFrequency = fread(fid,1,'float'); +eeg.Year = fread(fid,1,'short'); +eeg.Month = fread(fid,1,'short'); +eeg.Day = fread(fid,1,'short'); +eeg.Hour = fread(fid,1,'short'); +eeg.Minute = fread(fid,1,'short'); +eeg.Second = fread(fid,1,'short'); +eeg.Millisecond = fread(fid,1,'short'); +eeg.TSefChannelName = reshape(char(fread(fid,8*eeg.NumElectrodes,'char')),8,eeg.NumElectrodes)' ; + +eeg.data = reshape(fread(fid,eeg.NumElectrodes*eeg.NumTimeFrames,'float'),eeg.NumElectrodes,eeg.NumTimeFrames); diff --git a/cartool/read_spi.m b/cartool/read_spi.m new file mode 100644 index 0000000..8fd77d4 --- /dev/null +++ b/cartool/read_spi.m @@ -0,0 +1,4 @@ +function [XYZ]=read_spi(spifile) +%read_ris() - reads SPI Solution Points Irregularly spaced file (Cartool) +% [XYZ]=read_spi(spifile) +[XYZ(:,1) XYZ(:,2) XYZ(:,3)]=textread(spifile,'%f %f %f'); \ No newline at end of file diff --git a/cartool/read_xyz.m b/cartool/read_xyz.m new file mode 100644 index 0000000..0645f1c --- /dev/null +++ b/cartool/read_xyz.m @@ -0,0 +1,6 @@ +function [XYZ,labels,R]=read_xyz(xyzfile) +%read_xyz() - reads XYZ electrode coordinates file (Cartool) +% [XYZ,labels,R]=read_xyz(xyzfile) +[ne,R]=textread(xyzfile,'%d %f',1); + +[XYZ(:,1) XYZ(:,2) XYZ(:,3), labels]=textread(xyzfile,'%f %f %f %s', ne, 'headerlines',1); \ No newline at end of file diff --git a/cartool/write_sef.m b/cartool/write_sef.m new file mode 100644 index 0000000..86dba3d --- /dev/null +++ b/cartool/write_sef.m @@ -0,0 +1,78 @@ +function [] = write_sef(seffile,eeg,varargin) +%write_sef - Writes a Simple EEG Format file (Cartool) +% [] = write_sef(EEG) +% +% Example +% >> write_sef +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2007 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2007-04-04 Creation +% +% ----------------------------- Script History --------------------------------- + +if isfield(eeg,'chanlocs') + % EEGLAB format + EEG=eeg; + eeg=[]; + eeg.data=EEG.data; + [eeg.NumElectrodes, eeg.NumTimeFrames] = size(EEG.data) + eeg.TSefChannelName = strvcat({EEG.chanlocs.labels}); + eeg.TSefChannelName =[eeg.TSefChannelName repmat(' ', size(eeg.TSefChannelName,1), max(0,8-size(eeg.TSefChannelName,2)))]; + eeg.TSefChannelName(eeg.TSefChannelName==32)=0; + if eeg.NumElectrodes ~= EEG.nbchan | eeg.NumElectrodes ~= numel(eeg.TSefChannelName)/8 + error('check electrode number, size of data and chanlocs') + end + eeg.NumAuxElectrodes = eeg.NumElectrodes-64; + eeg.SamplingFrequency = EEG.srate; + +elseif isnumeric(eeg) + data=eeg; + eeg=[]; + eeg.data=data; + eeg.NumElectrodes = size(eeg.data,1); + eeg.NumAuxElectrodes = 0; + eeg.TSefChannelName = sprintf('e%03d\0\0\0\0',1:eeg.NumAuxElectrodes); + + clear data; +else + error('Wrong type of input: Should be raw data OR EEGLAB structure.') +end + +fid=fopen(seffile, 'wb'); +eeg.Year = 0; +eeg.Month = 0; +eeg.Day = 0; +eeg.Hour = 0; +eeg.Minute = 0; +eeg.Second = 0; +eeg.Millisecond = 0; + +eeg.Version = 'SE01'; + +fwrite(fid,eeg.Version, 'uchar'); +fwrite(fid,eeg.NumElectrodes,'int32'); +fwrite(fid,eeg.NumAuxElectrodes,'int32'); +fwrite(fid,eeg.NumTimeFrames,'int32'); +fwrite(fid,eeg.SamplingFrequency,'float32'); + +fwrite(fid,eeg.Year,'int16'); +fwrite(fid,eeg.Month,'int16'); +fwrite(fid,eeg.Day,'int16'); +fwrite(fid,eeg.Hour,'int16'); +fwrite(fid,eeg.Minute,'int16'); +fwrite(fid,eeg.Second,'int16'); +fwrite(fid,eeg.Millisecond,'int16'); + +fwrite(fid,eeg.TSefChannelName(:,:)','uchar'); +fwrite(fid,eeg.data,'float32') + +fclose(fid) diff --git a/catstructs.m b/catstructs.m new file mode 100644 index 0000000..60c2eef --- /dev/null +++ b/catstructs.m @@ -0,0 +1,118 @@ +function [z]=catstructs(dim,x,y,keepfields) +% catstructs - Concatenate structures independently of field order/presence +% +% [Z]=catstructs(dim, X,Y) will concatenate structures X and Y into Z along +% dimension dim which MUST be 1 (ie. vertical concatenation) +% +% [Z]=catstructs(dim,X,Y,keepfields) +% keepfields: 1/0(default), keep non-matching fields. Missing values +% will be set to [] +if ~isequal(dim,1) + error('Concatenated dimension MUST be 1 (until further development!)') +end +if ~isvector(x) | ~isvector(y) + error('Structures MSUT be unidimensional') +end +if nargin<4 + keepfields=0; +end + +z=x(:); +y=y(:); + +fy=fieldnames(y); +if ~keepfields + f=fieldnames(z); + z=rmfield(z,f(~ismember(f,fy))); + y=rmfield(y,fy(~ismember(fy,f))); + fy=fy(ismember(fy,f)); +end +% if isempty(z) +% z=y; +% end +for j=1:length(y) + for i=1:length(fy) + if i==1 + z(end+1).(fy{i})= y(j).(fy{i}); + else + z(end).(fy{i})= y(j).(fy{i}); + end + end +end + + + +return + +% the following bugs when any argument is a struct array + + + +function A = catstruct(varargin) +% CATSTRUCT - concatenate structures +% +% X = CATSTRUCT(S1,S2,S3,...) concates the structures S1, S2, ... into one +% structure X. +% +% A.name = 'Me' ; +% B.income = 99999 ; +% X = CATSTRUCT(A,B) -> +% X.name = 'Me' ; +% X.income = 99999 ; +% +% CATSTRUCT(S1,S2,'sorted') will sort the fieldnames alphabetically. +% +% If a fieldname occurs more than once in the argument list, only the last +% occurence is used, and the fields are alphabetically sorted. +% +% To sort the fieldnames of a structure A use: +% A = CATSTRUCT(A,'sorted') ; +% +% See also CAT, STRUCT, FIELDNAMES, STRUCT2CELL + +% 2005 Jos van der Geest + +N = nargin ; + +error(nargchk(1,Inf,N)) ; + +if ~isstruct(varargin{end}), + if isequal(varargin{end},'sorted'), + sorted = 1 ; + N = N-1 ; + if N < 1, + A = [] ; + return + end + else + error('Last argument should be a structure, or the string "sorted".') ; + end +else + sorted = 0 ; +end + +for ii=1:N, + X = varargin{ii} ; + if ~isstruct(X), + error(['Argument #' num2str(ii) ' is not a structure.']) ; + end + FN{ii} = fieldnames(X) ; + VAL{ii} = struct2cell(X) ; +end + +FN = cat(1,FN{:}) +VAL = cat(1,VAL{:}) ; +[UFN,ind] = unique(FN) ; + +if length(UFN) ~= length(FN), + warning('Duplicate fieldnames found. Last value is used.') ; + sorted = 1 ; +end + +if sorted, + VAL = VAL(ind) ; + FN = FN(ind) +end + +VF = reshape([FN VAL].',1,[]) ; +A = struct(VF{:}) ; diff --git a/ccaxis.m b/ccaxis.m new file mode 100644 index 0000000..b09f5b2 --- /dev/null +++ b/ccaxis.m @@ -0,0 +1,46 @@ +function [varargout] = ccaxis(clim) +%CCAXIS - Contour compatible CAXIS +% [] = ccaxis(clim) +% Works ALSO when there are contours in the plot +% Example +% >> ccaxis([0 1]) +% +% See also: caxis + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2006 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2006-02-27 Creation +% +% ----------------------------- Script History --------------------------------- + +caxis(clim) +ha=gca; +ch=get(gca, 'Children'); +peers=findobj(get(gcf, 'Children'),'type','axes'); +hcont=findobj(ch, 'type', 'hggroup'); +if ~isempty(hcont) + cb=findobj(peers, 'tag', 'Colorbar'); + for i=length(cb):-1:1 + hcb=handle(cb); + if ~isequal(double(hcb.axes),ha) + cb(i)=[]; + end + end + if isempty(cb) + cb=colorbar; + end + for i=1:length(cb) + child=get(cb,'children'); + set(cb, 'YLim', clim); + set(child, 'ydata', clim); + end + axes(ha) + delete(cb(~ismember(cb,peers))) +end +varargout={hcont}; \ No newline at end of file diff --git a/cdf.m b/cdf.m new file mode 100644 index 0000000..d00f2fc --- /dev/null +++ b/cdf.m @@ -0,0 +1,77 @@ +function p = cdf(name,x,a1,a2,a3) +%CDF Computes a chosen cumulative distribution function. +% P = CDF(NAME,X,A1) returns the named cumulative distribution +% function, which uses parameter A, at the the values in X. +% P = CDF(NAME,X,A1,A2) returns the named cumulative distribution +% function, which uses parameters a and b, at the the values in X. +% Similarly for P = CDF(NAME,X,A1,A2,A3). +% +% The name can be: 'beta' or 'Beta', 'bino' or 'Binomial', +% 'chi2' or 'Chisquare','exp' or 'Exponential', 'f' or 'F', +% 'gam' or 'Gamma','geo' or 'Geometric','hyge' or 'Hypergeometric', +% 'logn' or 'Lognormal','nbin' or 'Negative Binomial', +% 'ncf' or 'Noncentral F','nct' or 'Noncentral t', +% 'ncx2' or 'Noncentral Chi-square' +% 'norm' or 'Normal','poiss' or 'Poisson','rayl' or 'Rayleigh', +% 't' or 'T','unif' or 'Uniform','unid' or 'Discrete Uniform', +% 'weib' or 'Weibull'. +% +% CDF calls many specialized routines that do the calculations. + +if nargin<2, error('Not enough input arguments'); end + +if ~isstr(name), error('First argument must be distribution name'); end + +if nargin<5, + a3=0; +end +if nargin<4, + a2=0; +end +if nargin<3, + a1=0; +end + +if strcmp(name,'beta') | strcmp(name,'Beta'), + p = betacdf(x,a1,a2); +elseif strcmp(name,'bino') | strcmp(name,'Binomial'), + p = binocdf(x,a1,a2); +elseif strcmp(name,'chi2') | strcmp(name,'Chisquare'), + p = chi2cdf(x,a1); +elseif strcmp(name,'exp') | strcmp(name,'Exponential'), + p = expcdf(x,a1); +elseif strcmp(name,'f') | strcmp(name,'F'), + p = fcdf(x,a1,a2); +elseif strcmp(name,'gam') | strcmp(name,'Gamma'), + p = gamcdf(x,a1,a2); +elseif strcmp(name,'geo') | strcmp(name,'Geometric'), + p = geocdf(x,a1); +elseif strcmp(name,'hyge') | strcmp(name,'Hypergeometric'), + p = hygecdf(x,a1,a2,a3); +elseif strcmp(name,'logn') | strcmp(name,'Lognormal'), + p = logncdf(x,a1,a2); +elseif strcmp(name,'nbin') | strcmp(name,'Negative Binomial'), + p = nbincdf(x,a1,a2); +elseif strcmp(name,'ncf') | strcmp(name,'Noncentral F'), + p = ncfcdf(x,a1,a2,a3); +elseif strcmp(name,'nct') | strcmp(name,'Noncentral T'), + p = nctcdf(x,a1,a2); +elseif strcmp(name,'ncx2') | strcmp(name,'Noncentral Chi-square'), + p = ncx2cdf(x,a1,a2); +elseif strcmp(name,'norm') | strcmp(name,'Normal'), + p = normcdf(x,a1,a2); +elseif strcmp(name,'poiss') | strcmp(name,'Poisson'), + p = poisscdf(x,a1); +elseif strcmp(name,'rayl') | strcmp(name,'Rayleigh'), + p = raylcdf(x,a1); +elseif strcmp(name,'t') | strcmp(name,'T'), + p = tcdf(x,a1); +elseif strcmp(name,'unid') | strcmp(name,'Discrete Uniform'), + p = unidcdf(x,a1); +elseif strcmp(name,'unif') | strcmp(name,'Uniform'), + p = unifcdf(x,a1,a2); +elseif strcmp(name,'weib') | strcmp(name,'Weibull'), + p = weibcdf(x,a1,a2); +else + error('Sorry, the statistics toolbox does not support this distribution.'); +end diff --git a/cell2str.m b/cell2str.m new file mode 100644 index 0000000..bea3e80 --- /dev/null +++ b/cell2str.m @@ -0,0 +1,68 @@ +function t = cell2str(c,sep,s) +%CELL2STR - Convert a cell array into text (i.e. string array) +% [S] = cell2str(C) +% +% Example +% >> celldisp2 +% +% See also: celldisp + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2010 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2010-04-01 Creation +% +% ----------------------------- Script History --------------------------------- + +%error(nargchk(1,2,nargin,'struct')); +if ~iscell(c), + error('MATLAB:celldisp:notCellArray', 'Must be a cell array.'); +end +if nargin<2, sep = {'-' '|' }; end +if nargin<3, s = inputname(1); end +if isempty(s), s = 'ans'; end + +for i=1:numel(c), + if iscell(c{i}) && ~isempty(c{i}), + t=sprintf( '%s\n%s\n%s',s,repmat(sep{1}, [1 size(s,2)]), cell2str(c{i},sep,[s subs(i,size(c))])); + else + t=sprintf('%s%s =\n',s,subs(i,size(c))); + + if ~isempty(c{i}), + t=sprintf('%s%s',t,); + else + if iscell(c{i}) + disp(' {}') + elseif ischar(c{i}) + disp(' ''''') + elseif isnumeric(c{i}) + disp(' []') + else + [m,n] = size(c{i}); + disp(sprintf('%0.f-by-%0.f %s',m,n,class(c{i}))) + end + end + end +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function s = subs(i,siz) +%SUBS Display subscripts + +if length(siz)==2 && any(any(siz==1)) + v = cell(1,1); +else + v = cell(size(siz)); +end +[v{1:end}] = ind2sub(siz,i); + +s = ['{' int2str(v{1})]; +for i=2:length(v), + s = [s ',' int2str(v{i})]; +end +s = [s '}']; \ No newline at end of file diff --git a/cellcat.m b/cellcat.m new file mode 100644 index 0000000..a497d11 --- /dev/null +++ b/cellcat.m @@ -0,0 +1,19 @@ +function X = cellcat(C) +%CELLCAT - Concatenate cell array content +% [X] = cellcat(C) is equivalent to X=[C{:}] +% +% See also: cell2mat() + +% Author: K. N'Diaye (kndiaye01gmail.com) +% Copyright (C) 2011 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2011-04-08 Creation +% +% ----------------------------- Script History --------------------------------- + +X = [C{:}]; \ No newline at end of file diff --git a/cellfun2.m b/cellfun2.m new file mode 100644 index 0000000..a035608 --- /dev/null +++ b/cellfun2.m @@ -0,0 +1,31 @@ +function [varargout] = cellfun2(varargin) +% cellfun2() - Cap funtion for cellfun which accepts any function call +% +% D = CELLFUN2(FUN, C) applies function FUN to the cells of C +% +% D = CELLFUN2(FUN, C, P,...) applies function FUN to the cells of C using +% parameters P in the call of FUN +% +%See also: cellfun + +try + if nargout == 0 + builtin('cellfun', varargin{:}); + else + [varargout{1:nargout}] = builtin('cellfun', varargin{:}); + end +catch + if nargout > 1 + error('cellfun2 cannot handle more than one output') + end + if iscell(varargin{1}) || ~iscell(varargin{2}) + error('cellfun2 input format is: cellfun2(function, cell array, ...)') + end + fname = varargin{1}; + x = varargin{2}; + y = cell(size(x)); + for i=1:numel(x) + y{i} = feval(fname, x{i},varargin{3:end}); + end + varargout{1} = y; +end diff --git a/circle3.m b/circle3.m new file mode 100644 index 0000000..2e2f8bb --- /dev/null +++ b/circle3.m @@ -0,0 +1,19 @@ +function [h]=circle3(X,R, varargin) +% CIRCLE3: draw an circle in 3D +% h = circle3(x,y,z,r) or circle3(xyz,r) +if prod(size(R))=prod(size(X))/3 + X(:,2)=varargin{1}; + X(:,3)=varargin{2}; + varargin=varargin{3:end}; +end + +h = circle(X,Y,R) +rotate( + + +function circle(C,r,npts); +if nargin<3 + npts = 50; +end +theta=0:2*pi/(npts-1):2*pi; +plot(C(1)+r*cos(theta),C(2)+r*sin(theta),'w'); \ No newline at end of file diff --git a/circpermarray.m b/circpermarray.m new file mode 100644 index 0000000..8ec0041 --- /dev/null +++ b/circpermarray.m @@ -0,0 +1,29 @@ +function [y]=circpermarray(x,pv,dim) +% circpermarray - circularly permute values in an array +% +% [y]=circpermarray(x,pv,dim) +% +% pv: length of the permutation. +% dim: dimension on which permutation is done. +% +% NB: The direction of permutation is top-down (dim==1) and +% left-right (dim==2). Of course! + +if nargin<3 + [dim]=min(find(size(x)>1)); + if isempty(dim) + dim=1; + end +end +sx=size(x); +pd=[dim, 1:dim-1 dim+1:ndims(x)]; +%permute dimensions +x=permute(x,pd); +pv=mod(pv, sx(dim)); +x=x([end-pv+1:end 1:end-pv],:); +x=reshape(x,sx(pd)); +y=ipermute(x,pd); + +return + + \ No newline at end of file diff --git a/clickableLegend.m b/clickableLegend.m new file mode 100644 index 0000000..5cf6a0b --- /dev/null +++ b/clickableLegend.m @@ -0,0 +1,56 @@ +function varargout = clickableLegend(varargin) +% clickableLegend is a wrapper around the LEGEND function that provides the +% added functionality to turn on and off (hide or show) a graphics object +% (line or patch) by clicking on its text label in the legend. Its usage is +% the same as the LEGEND function. For +% further information please see the LEGEND documentation. +% +% Notes: +% 1. If you save the figure and re-load it, the toggling functionality +% is not automatically re-enabled. To restore it, simply call clickableLegend +% with no arguments. +% +% 2. To prevent the axis from automatically scaling every time a line is +% turned on and off, issue the command: axis manual +% +% Example 1: +% z = peaks(100); +% plot(z(:,26:5:50)) +% grid on; +% axis manual; +% clickableLegend({'Line1','Line2','Line3','Line4','Line5'}, 'Location', 'NorthWest'); + +% Example 2: +% plot(1:10,1:10,'x', 1:10,10:-1:1,'r-', 1:10,rand(1,10)*5,'b:'); +% clickableLegend('Line1','Line2','Line3'); +% hgsave(gcf, 'testfig.fig'); +% hgload testfig.fig +% clickableLegend + + +% Create legend as if it was called directly +[varargout{1:nargout(@legend)}] = legend(varargin{:}); + +% Extract what is needed for the rest of the function and fix varargout +[leghan, objhan, plothan] = varargout{1:4}; %#ok +varargout = varargout(1:nargout); + +% At this point, we can quit and behavior would be equivalent to calling +% just legend. + +% Set the callbacks +for i = 1:length(plothan) + set(objhan(i), 'HitTest', 'on', 'ButtonDownFcn',... + @(varargin)togglevisibility(objhan(i),plothan(i)),... + 'UserData', true); +end + + +function togglevisibility(hObject, obj) +if get(hObject, 'UserData') % It is on, turn it off + set(hObject, 'Color', (get(hObject, 'Color') + 1)/1.5, 'UserData', false); + set(obj,'HitTest','off','Visible','off','handlevisibility','off'); +else + set(hObject, 'Color', get(hObject, 'Color')*1.5 - 1, 'UserData', true); + set(obj, 'HitTest','on','visible','on','handlevisibility','on'); +end diff --git a/closest.m b/closest.m new file mode 100644 index 0000000..55ee905 --- /dev/null +++ b/closest.m @@ -0,0 +1,15 @@ +function [I,D] = closest(A,B) % deprecated +% DEPRECATED. See: nearest + +% closest - Closest points between two sets +% (N-dimension euclidian distance) +% +% [I,D] = closest(A,B) +% +% INPUTS: +% A - MxD matrix (M=number of points, D=number of dimensions) +% B - NxD matrix (N=number of points, D=number of dimensions) +% OUTPUTS: +% I - Mx1 vector of indices of B's closest to each A's +% D - Mx1 vector of euclidian distances +error \ No newline at end of file diff --git a/cluster_threshold.m b/cluster_threshold.m new file mode 100644 index 0000000..44b00d7 --- /dev/null +++ b/cluster_threshold.m @@ -0,0 +1,68 @@ +function [Y,clusters] = cluster_threshold(X,thd,vc,dim,verbose) +%CLUSTER_THRESHOLD - Keep only data which are clustered +% [Y] = cluster_threshold(X,thd,vc/adj,dim,verbose) +% Finds clusters along dimension dim in matrix X whose mass/area is +% bigger than (or equal to) thd: +% for each cluster clu{i}=[ ... ], sum(X([clu{i}])) >= thd; +% Adjacency/connectivity must be specified in vc +% * use vc=1 for continous data (e.g. time samples) +% * use a conectivity matrix or cell array of neighbors otherwise +% By default [dim] is the first non singleton dimension of X +% Outputs Y (of the same size as X) such that Y=X where the clusters +% match the criterion, Y=0 elsewhere. +% +% See also: CLUSTERING + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2006 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2006-02-18 Creation +% +% ----------------------------- Script History --------------------------------- + +if nargin<4 + [dim,dim]=min(find(size(X)>1)); + if isempty(dim) + dim=1; + end +end +if nargin<5 + verbose=1; +end + +sX=size(X); +ndX=ndims(X); +Y=permute(X, [dim setdiff(1:ndX,dim)]); +if iscell(vc) + warning(sprintf('%s\n%s', '''vc'' is provided as a vertices_connectivity cell list.' , ... + 'It will be converted to an adjacency matrix for faster computation.')); + vc=vertconn2adjacency(vc); + fprintf('Adjacency matrix now computed\n'); +end + +if verbose + htime=timebar('Finding clusters'); +end +niter=(prod(sX)/sX(dim)); +for i=1:niter + [clu,sclu]=clustering(Y(:,i),vc); + y=Y(:,i); + Y(:,i)=0; + Y([clu{sclu>=thd}],i)=y([clu{sclu>=thd}],1); + if verbose + try,timebar(htime,i/niter);end + end + if nargout>1 + clusters{i} = clu(sclu>=thd); + end + +end +if verbose + try;close(htime);end +end +Y=ipermute(Y, [dim setdiff(1:ndX,dim)]); \ No newline at end of file diff --git a/clustering.m b/clustering.m new file mode 100644 index 0000000..447b25b --- /dev/null +++ b/clustering.m @@ -0,0 +1,117 @@ +function [clu,sz,mc,v2c] = clustering(X,vc,VERBOSE) +%CLUSTERING - find clusters of data (continuous or on a mesh) +% [clu,sz,mc] = clustering(X,vc, verbose) +% Find clusters of non-zeros values in data vector X according to the +% connectivity defined by 'vc' +% +%INPUTS: +% X: N-by-1 array of zeros and non-zeros data +% vc: Connectivity pattern, can be: +% [1] -> continuous data (e.g. time samples) +% vertice_connectivity (cell list) +% adjacency matrix +% +%OUPTUTS: +% clu: clusters found ss a cell list of indices in X, ordered in +% decreasing size +% sz: size of each cluster +% mc: mass of each cluster, mc(i)=sum(X(clu{i})) +% vtx2clu: N-by-1 array indicating the cluster to which belongs the +% vertex +% +%EXAMPLE +% >> [clu,sz]=clustering(ImageGridAmp(:,1),vertconn); + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2006 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2006-02-05 Creation +% KND 2007-04-16 Bug correction: unidimensional +% ----------------------------- Script History --------------------------------- + +if numel(X)>max(size(X)) + error('X should be a vector, not a matrix') +end +if nargin<3 + VERBOSE=0; +end + +X=X(:); +n=size(X,1); + +if isequal(vc,1) | isempty(vc) + % The '~~' avoids warnings MATLAB:conversionToLogical + p=logical(~~X); + dip=diff(cumsum(p),2); + dp1=find(dip==1)+2; + dp2=find(dip==-1)+2; + if isequal(p(1),1) + % check for siding values + dp1=[1; dp1]; + if isequal(p(2),0) + dp2=[2; dp2]; + end + elseif isequal(p(2),1) + dp1=[2; dp1]; + end + if isequal(p(end),1) + dp2=[dp2; n]; + end + clu=cell(1,length(dp1)); + sz=dp2-dp1; + mc=zeros(1,length(dp1)); + for i=1:length(dp1) + clu{i}=dp1(i):(dp2(i)-1); + if nargout>2 + mc(i)=sum(X(dp1(i):(dp2(i)-1))); + end + end + +else + if size(vc,1) ~= n + error('Connectivity and number of values in X don''t match') + end + + if iscell(vc) + A=vertconn2adjacency(vc,VERBOSE); + else + A=vc; + end + X=double(X); + % NaN values + X(isnan(X))=0; + + A=A|speye(size(A)); + % Remove OFF vertices from their neighbours + A(~X,:)=0; + A(:,~X)=0; + + % Compute clusters using matlab's dmperm + [p,ignore,r,s] = dmperm(A); + + sz=diff(r).*(diff(r)&diff(s)); + idx=find(sz); + [ign,idx2]=sort(-sz(idx)); + sz=sz(idx(idx2)); + clu=cell(1,length(idx2)); + if nargout>2 + mc=zeros(1,length(idx2)); + end + if nargout > 3 + v2c=zeros(n,1); + end + for i=1:length(idx2) + clu{i}=p(r(idx(idx2(i))):(r(idx(idx2(i))+1)-1)); + if nargout>2 + mc(i)=sum(X(clu{i})); + end + if nargout > 3 + v2c(p(r(idx(idx2(i))):(r(idx(idx2(i))+1)-1)))=i; + end + end +end diff --git a/clusters2faces.m b/clusters2faces.m new file mode 100644 index 0000000..dac3d8b --- /dev/null +++ b/clusters2faces.m @@ -0,0 +1,29 @@ +function [fclusters]=clusters2faces(vclusters, faces,verbose) +% clusters2faces - find faces encompassed in each cluster +% +% [fclusters]=clusters2faces(vclusters, faces,verbose) +% Retrieve faces fully included in each cluster + +% KND : 2005-07-01 : Initial release. Some vectorization should be possible +if nargin<3 + verbose=0; +end + +nclu=length(vclusters); +if verbose + h=waitbar(0,'Clusters of faces '); +end +for i=1:nclu + fclusters{i}=[]; + for j=1:size(faces,1) + if length(intersect(faces(j,:),vclusters{i}))==3 + fclusters{i}=[fclusters{i} j]; + end + end + if verbose + waitbar(i/nclu,h); + end +end +if verbose + close(h) +end \ No newline at end of file diff --git a/clusters2patches.m b/clusters2patches.m new file mode 100644 index 0000000..db5b922 --- /dev/null +++ b/clusters2patches.m @@ -0,0 +1,54 @@ +function [fv,vclusters]=clusters2patches(vclusters, faces, vertices,verbose) +% clusters2patches - computes patches of clusters +% +% [p,clusters]=clusters2patches(clusters, faces, vertices,verbose) +% +% INPUTS: +% clusters: cell list of vertex indices +% faces: from a FV structure, see PATCH +% vertices: idem. +% verbose: optional, default=1 +% +% OUPUTS: +% p: Nx1 array of FV structures +% clusters: newly assignated vertices in each clusters. +% Indeed, as some faces may lie on the border of two (or even +% three) clusters, the new patches may overlap. Therefore some +% faces and vertices are duplicated between patches and we keep +% track of these "extended" clusters in this output + +% KND : 2005-07-01 : Initial release. Some vectorization should be possible +if nargin<3 + verbose=0; +end + +nclu=length(vclusters); +if verbose + h=waitbar(0,'Clusters of patches'); +end +for i=1:nclu + fv(i).faces=[]; + for j=1:size(faces,1) + if length(intersect(faces(j,:),vclusters{i}))>1 + fv(i).faces=[fv(i).faces; 0 0 0]; + for k=1:3 + if ~ismember(faces(j,k),vclusters{i}) + vclusters{i}=[vclusters{i} faces(j,k)]; + end + fv(i).faces(end,k)=find(faces(j,k)==vclusters{i}); + end + end + end + fv(i).vertices=vertices(vclusters{i},:); + if verbose + waitbar(i/nclu,h); + end +end + +% Post process faces to remove duplicated faces +% f=unique(cat(1,fv(:).faces), 'rows'); +% [f,f]=setdiff(cat(1,fv(:).faces),f, 'rows'); + +if verbose + close(h) +end \ No newline at end of file diff --git a/cnrs_section27.m b/cnrs_section27.m new file mode 100644 index 0000000..91454b8 --- /dev/null +++ b/cnrs_section27.m @@ -0,0 +1,101 @@ +% Candidats 2009 +% ALAHYANE Nadia Admis à concourir +% AMIEZ Celine Admis à concourir +% ANTRI Myriam Admis à concourir +% BARAKAT Youssef Admis à concourir +% BARIK Jacques Admis à concourir +% BARRIERE Grégory Admis à concourir +% BEGUE Anne Admis à concourir +% BENCHENANE Karim Admis à concourir +% BENTURQUIA Nadia Admis à concourir +% BERANECK Mathieu Admis à concourir +% BIDET-ILDEI Christel Admis à concourir +% BLANGERO Annabelle Admis à concourir +% BOISTEL Renaud Admis à concourir +% BOMPAS Aline Admis à concourir +% BOUDIN Florian Admis à concourir +% BOULENGER Veronique Admis à concourir +% BRUEL JUNGERMAN Elodie Admis à concourir +% COENEN Olivier Admis à concourir +% COLLINS Thérèse Admis à concourir +% COPPOLA Vincent Admis à concourir +% COQUART Jérémy Admis à concourir +% DANGLOT Lydia Admis à concourir +% DE CHEVIGNY Antoine Admis à concourir +% DE MARGERIE Emmanuel Admis à concourir +% DELLEN Babette Admis à concourir +% DOLY Stephane Admis à concourir +% DUFOUR Valerie Admis à concourir +% DULIN David Admis à concourir +% DUPIERRIX Eve Admis à concourir +% DURAND Jean Baptiste Admis à concourir +% EGO-STENGEL Valérie Admis à concourir +% ELEORE Lyndell Admis à concourir +% FAURE Alexis Admis à concourir +% FERNANDEZ Julian Admis à concourir +% GENTY Emilie Admis à concourir +% GOMEZ Doris Admis à concourir +% GOUREVITCH Boris Admis à concourir +% GUILLEM Karine Admis à concourir +% HADJ-BOUZIANE Fadila Admis à concourir +% HANSARD Miles Edward Admis à concourir +% HERVE Pierre Yves Admis à concourir +% HICHEUR Halim Admis à concourir +% HOK Vincent Admis à concourir +% HOSY Eric Admis à concourir +% HUSKY Mathilde Admis à concourir +% HUYS Raoul Admis à concourir +% ISEL Frédéric Admis à concourir +% IZARD Véronique Admis à concourir +% KILAVIK Bjørg Elisabeth Admis à concourir +% LEBLOIS Arthur Admis à concourir +% LECOURTIER Lucas Admis à concourir +% LOPEZ Christophe Admis à concourir +% MADDEN Carol Admis à concourir +% MAIRESSE Jérome Admis à concourir +% MARQUES PEREIRA Patricia Admis à concourir +% MENDOZA Jorge Admis à concourir +% MICHELENA Pablo Admis à concourir +% MICHELET Thomas Admis à concourir +% MONTAGNINI Anna Admis à concourir +% MORICE Elise Admis à concourir +% MUNIER Claire Alice Admis à concourir +% NAVAILLES Sylvia Admis à concourir +% NAVARRO Jordan Admis à concourir +% PARRON Carole Admis à concourir +% PASCAL Frédéric Admis à concourir +% PASTORINI Chiara Admis à concourir +% PATINO VILCHIS Jose Luis Admis à concourir +% PIETROPAOLO Susanna Admis à concourir +% PLAILLY Jane Admis à concourir +% POIRIER Karine Admis à concourir +% REGUIGNE-KHAMASSI Mehdi Admis à concourir +% RIBOT Jérôme Admis à concourir +% ROUSTIT Christelle Admis à concourir +% SAGASPE Patricia Admis à concourir +% SARLEGNA Fabrice Admis à concourir +% SCHIRLIN Olivier Admis à concourir +% SELIMBEGOVIC Leila Admis à concourir +% SERGENT Claire Admis à concourir +% SMADJA Carole Admis à concourir +% SNOEREN Natalie Admis à concourir +% TAGLIABUE Michele Admis à concourir +% TRONEL Sophie Admis à concourir +% UPAL Roy Admis à concourir +% WARDAK Claire Admis à concourir +% WIRTH Sylvia Admis à concourir +% YALCIN CHRISTMANN Ipek Admis à concourir +clear +clc +filename=mfilename('fullpath'); +nc = 86; +[a ,nom(:,1),nom(:,2)]=textread(filename ,'%s%s%s%*[^\n]',nc, 'headerlines',1); +query = 'http://www.ncbi.nlm.nih.gov/pubmed/search?term=("2000"[Publication%20Date]%20:%20"3000"[Publication%20Date])%20AND%20("%s%%20%s"[au])'; +for i=1:nc +% nom={'Baillet', 'S'} + q =sprintf(query, nom{i,1},nom{i,2}(1)) + t=urlread(q); + pmid=findstr('pmid',t); + findstr('title',t(pmid(1)+[5:400])) + return +end \ No newline at end of file diff --git a/cogent/Textquestions.m b/cogent/Textquestions.m new file mode 100644 index 0000000..cd12d6f --- /dev/null +++ b/cogent/Textquestions.m @@ -0,0 +1,135 @@ +function [tx]=Textquestions(questions3,examples,keys) +if nargin ==0 + warning('no subject name - will use default name') + sname = 'test'; + listnum=1; +end +if nargin<1 || isempty(questions3) + questions3={'Est-ce que cette musique vous evoque des sensations corporelles ?' ... + 'Est-ce que cette musique vous evoque des images ?'}; + examples={'(par exemple des frissons ou besoin d''un mouvement)' '(par exemple des couleurs ou paysages)'}; +end +if nargin<2 + keys=[]; +end + +tx=[]; + +if length(dbstack)==1 + + global cogent + config_keyboard; + config_display(1, 2, [0 0 0], [1 1 1], 'Arial', 25, 4 ) + % config_log(['C:\Documents and Settings\SCHWARTZ\My Documents\MATLAB\Wiebke\myresults\' sname '.log']); + % config_results(['C:\Documents and Settings\SCHWARTZ\My Documents\MATLAB\Wiebke\myresults\' sname '.res']); + % datafilename= ['C:\Documents and Settings\SCHWARTZ\My Documents\MATLAB\Wiebke\mylists\datafile_' num2str(listnum) '.dat']; + % config_data(datafilename); + % config_sound; + + start_cogent + + fontName = 'Helvetica'; fontSize = 20; % font parameters (optional) + cgalign('c','c'); + keys=[]; + keys=getkeymap; + +end + +% thechar=char(fieldnames(themap)); +% thechar= {'A','B','C','D','E','F','G','H','I','J',... +% 'K','L','M','N','O','P','Q','R','S','T',... +% 'U','V','W','X','Y','Z','K0','K1','K2','K3',... +% 'K4','K5','K6','K7','K8','K9','F1','F2','F3','F4',... +% 'F5','F6','F7','F8','F9','F10','F11','F12','F13','F14',... +% 'F15','Escape','Minus','Equals','BackSpace','Tab','LBracket','RBracket','Return','LControl',... +% 'SemiColon','Apostrophe','Grave','LShift','BackSlash','Comma','Period','Slash','RShift','LAlt',... +% 'Space','CapsLock','NumLock','Scroll','Pad0','Pad1','Pad2','Pad3','Pad4','Pad5',... +% 'Pad6','Pad7','Pad8','Pad9','PadSubtrack','PadAdd','PadDivide','PadMultiply','PadPeriod','PadEnter',... +% 'RControl','RAlt','Pause','Home','Up','PageUp','Left','Right','End','Down',... +% 'PageDown','Insert','Delete'} +thechar= {'A','B','C','D','E','F','G','H','I','J',... + 'K','L','M','N','O','P','Q','R','S','T',... + 'U','V','W','X','Z','Y','0','1','2','3',... + '4','5','6','7','8','9','F1','F2','F3','F4',... + 'F5','F6','F7','F8','F9','F10','F11','F12','F13','F14',... + 'F15','Escape','-','=','BackSpace','Space','è','','Return','LControl',... + 'é','à','Grave','LShift','$',',','.','-','RShift','LAlt',... + 'Space','CapsLock','NumLock','Scroll','Pad0','1','2','3','4','5',... + '6','7','8','9','-','+','/','*','.','Return',... + 'RControl','RAlt','Pause','Home','Up','PageUp','Left','Right','End','Down',... + 'PageDown','Insert','Delete'}; +thechar=char( thechar'); +thenum=cell2mat(struct2cell(keys)); +clearkeys +NQ=length(questions3); + +for k = 1:NQ + theword={''}; + cgflip(0,0,0) + cgflip(0,0,0) + cgtext(questions3{k},0,200) + cgtext(examples{k},0,150) + cgflip + wait(2000); + cgflip(0,0,0) + cgflip(0,0,0) + + while 1 + theline=length(theword); + if length(theword{theline})>50 + lastblank= findstr(theword{theline},' '); + lastblank= lastblank(end); + theword{theline+1}=theword{theline}(lastblank+1:end); + theword{theline} = theword{theline}(1:lastblank) ; + theline=length(theword); + end + + [ keyout, time, n ] = waitkeydown( Inf ); + keyout= keyout(1); + if keys.Return == keyout + break + else + cgflip(0,0,0) + cgflip(0,0,0) + theletter = deblank(thechar(find(thenum==keyout),:)); + if strcmp(theletter,'Space') + theword{theline}=[theword{theline} ' ']; + elseif strcmp(theletter,'BackSpace') + if length(theword{theline})>0 + theword{theline}=[theword{theline}(1:end-1)]; + end + else + theword{theline}=[theword{theline} deblank(thechar(find(thenum==keyout),:))]; + end + posx= 0; + posy= 50; + theresp = ''; + for i=1:theline + theresp = [theresp theword{i}]; + cgtext(questions3{k},0,200) + cgtext(examples{k},0,150) + cgtext(theword{i},posx,posy) + posy=posy-50; + end + + cgflip + end + + tx{k}=theresp; + + end + if length(theword{1})==1 + tx{k}=['NO']; + end + cgflip(0,0,0) + cgflip(0,0,0) + wait(1000) +end + +if length(dbstack)==1 + stop_cogent; +end + +tx + +return \ No newline at end of file diff --git a/cogent/discrethopper.m b/cogent/discrethopper.m new file mode 100644 index 0000000..a1464db --- /dev/null +++ b/cogent/discrethopper.m @@ -0,0 +1,146 @@ +function [resp,rt]=discrethopper(trial,questions,map,buf) +resp=[]; % the answer +rt=[]; % the reactiontime between each key press +if nargin<1 + trial=''; +else + trial=sprintf('%02d/%02d',trial); % output of the current number of trial in realation to the total number of trials +end +if nargin<2 || isempty(questions) % in the case of testing the script alone without arguments + questions={'Enchantement (heureux, emerveillé)' 'Triomphe (energique, heroïque)' 'Nostalgie (sentimental, rêveur)'... + 'Transcendence (inspiré, spirituel)' 'Sérénité (calme, apaisé)' 'Tendresse (affectueux, doux)'... + 'Joie (euphorique, dansant)' 'Tristesse (deprimé, solonnel)' 'Agitation (tendu, neurveux)'}; +end +if nargin<3 + map=[]; +end +if nargin<4 + buf=1; +end + +scale=[]; + +Reckx = [-180 -140 -100 -60 -20 20 60 100 140 180]; +nbr = length(Reckx); % number of rects + +rectw=20; % width +recth=20; % hight +rectb=1; % thickness of rectangle +rectwi=rectw-rectb*2; %inside part width +recthi=recth-rectb*2; %inside part hight +vspace=51; % space between the lines + +fgcolor=[1 1 1]; +bgcolor=[0 0 0]; +fontSize=16; % textsize + +if length(dbstack)==1 % in case of calling the script without input variables + config_display(1,1,bgcolor,fgcolor,'Arial',20); + config_keyboard; + + start_cogent; + if ~isempty(scale) + cgScale(scale); + end + % the keyboardsetting + keys = getkeymap; +end + + +NQ=length(questions); +keyadjust=0; +polpos=5; % the default start position (5=in the middle) + + +for i=1:NQ % loop for the questions + + trial_start = time; % the onset time of the trial + hopper=polpos; + + while 1 + readkeys; + clearpict( buf ); + keyadjust; + + if keyadjust==29 + hopper = hopper + 1; + if hopper > 10 + hopper = hopper - 1; + end + end + if keyadjust==28 + hopper = hopper - 1; + if hopper < 1 + hopper = hopper + 1; + end + end + + for j=1:NQ + y=(5-j)*vspace; + if j0); +fullscreen = 1; +fullscreen = 0; + +clc; +t=textread(textfile,'%s', 'delimiter', '\n','whitespace',''); +scale=[]; +rectx=0; +recty=0; +rectw=400; +recth=rectw/40; +rectb=1; +rectwi=rectw-rectb*2; %inside part +recthi=recth-rectb*2; %inside part +vspace=5; + +fgcolor=[1 1 1]; +bgcolor=[0 0 0]; +fontSize=rectw/20; +linewidth=40; +textheight=20; + +if length(dbstack)==1 + config_display(fullscreen,1,bgcolor,fgcolor,'Arial',20,25); + if fullscreen==0 + config_keyboard( 100, 5, 'nonexclusive') + else + config_keyboard; + end + config_log; + config_results; + start_cogent; + cogprocess( 'getpriority' ) + cogstd('spriority','NORMAL') + cogprocess( 'getpriority' ) + keymap=getkeymap; +end +logstring( textfile); +addresults(textfile) +addresults(datestr(now,0)) +addresults('Subject', subject) +clearpict( 0 ); + +%% Instructions + +clearpict( 1 ); % Clear display buffer 1 +s=find(cellfun('isempty',t)); +i=1; +j=2; +while not(isempty(t{i}) & isempty(t{i+1}) ) + nlines=s(min(find(s>i)))-i; + for j=1:nlines + preparestring(t{i+j-1},1,0,(nlines/2-j)*textheight); + drawpict(1); + end + waitkeydown(5000+nlines*2000); + clearpict( 1 ); % Clear display buffer 1 + i=i+nlines; +end + +%% Response options + +i=i+2; +j=0; +while not(isempty(t{i})) + j =j+1; + if isempty(t{i+1}) + respoption(j).txt = t{i}; + respoption(j).key = getfield(keymap, sprintf('K%d',j)); + else + respoption(j).txt = [t{i+1} ' -> ' t{i}]; + if ismember( t{i+1} , '1234567890') + respoption(j).key = getfield(keymap,['K' t{i+1}]); + else + respoption(j).key = getfield(keymap,t{i+1}); + end + end + preparestring(respoption(j).txt,2,0,-50-textheight*j); + respoption(j).buf=2+j; + preparestring(respoption(j).txt,respoption(j).buf,0,-50-textheight*j); + respoption(j) + i=i+2; +end +preparestring('En cas d''erreur, appuyer sur la flèche gauche pour revenir en arrière',2,0,-50-textheight*(j+2)); + + +%% Questions + + +iq=1; +iline(iq)=i+1+iq; +if iline > length(t) + stop_cogent; + error; + return +elseif isempty(t{iline}) + stop_cogent; + return +end +clearpict( 0 ); +drawpict(0); + + +while (iline(iq))<=length(t) + nlines=1; + ok=(iline(iq)+nlines)<=length(t); + if ok, ok=ok & ~isempty(t{iline(iq)+nlines}); end + while ok + nlines=nlines+1; + ok=iline(iq)+nlines<=length(t); + if ok, ok=ok & ~isempty(t{iline(iq)+nlines}); end + end + cgsetsprite(0); + cgdrawsprite(2,0,0); + for j=1:nlines + preparestring(t{iline(iq)+j-1},0,0,(nlines-j)*textheight); + end + t0=drawpict(0); + wait(300); + clearkeys; + [k,rt] = waitkeydown(inf,[keymap.Delete keymap.Left keymap.Escape respoption.key] );%getkeydown; + rt=rt-t0+300; + if isempty(k) + elseif k(1)==keymap.Delete | k(1)==keymap.Left + iq=iq-1; + if iq<1, iq=1; end + clearpict( 0 ); + drawpict(0); + wait(100) + elseif k(1)==keymap.Escape + iline(iq)=inf; + else + r = find(k(1)==[respoption.key]); + r = r(1); + clearpict( 0 ); + cgsetsprite(0); + cgdrawsprite(respoption(r).buf,0,0); + for j=1:nlines + preparestring(t{iline(iq)+j-1},0,0,(nlines-j)*textheight); + end + drawpict(0); + wait(500) + addresults(iq, r, rt) + iq=iq+1; + iline(iq)=iline(iq-1)+nlines+1; + end + +end + +wait(100); +clearkeys; +stop_cogent; + +return + +loadpict( face, 1 ); % Copy face into buffer +clearkeys; % Clear all keyboard events +drawpict( 1 ); % Copy display buffer 1 to screen +wait( 1000 ); % Wait for 1000ms +drawpict( 2 ); % Clear screen +wait( 1000 ); % Wait for 1000ms +readkeys; \ No newline at end of file diff --git a/cogent/semislider.m b/cogent/semislider.m new file mode 100644 index 0000000..10fa7f8 --- /dev/null +++ b/cogent/semislider.m @@ -0,0 +1,166 @@ +function [resp,rt]=discretslider(trial,questions,map,buf) +resp=[]; % the answer +rt=[]; % the reactiontime between each key press +if nargin<1 + trial=''; +else + trial=sprintf('%02d/%02d',trial); +end +if nargin<2 || isempty(questions) + questions={'Enchantement (heureux, emerveillé)' 'Triomphe (energique, heroïque)' 'Nostalgie (sentimental, rêveur)'... + 'Transcendence (inspiré, spirituel)' 'Sérénité (calme, apaisé)' 'Tendresse (affectueux, doux)'... + 'Joie (euphorique, dansant)' 'Tristesse (deprimé, solonnel)' 'Agitation (tendu, neurveux)'}; +end +if nargin<3 + map=[]; +end +if nargin<4 + buf=1; +end + +scale=[]; +rectxa=-190; % x-axis +rectya=100; % y-axis 1st square +rectxb=-60; % x-axis 2nd square +rectxc=60; % x-axis 3rd square +rectxd=190; % x-axis 4th square + +rectw=20; % width +recth=20; % hight +rectb=1; % thickness of rectangle +rectwi=rectw-rectb*2; %inside part width +recthi=recth-rectb*2; %inside part hight +vspace=50; + +fgcolor=[1 1 1]; +bgcolor=[0 0 0]; +fontSize=20; % textsize + +if length(dbstack)==1 + config_display(1,1,bgcolor,fgcolor,'Arial',20); + config_keyboard; + start_cogent; + if ~isempty(scale) + cgScale(scale); + end + % the keyboardsetting + keys = getkeymap; +end + +NQ=length(questions); + +for i=1:NQ + mx=0; + trial_start = time; + keyout=[]; + while 1 + clearpict( buf ); + for j=1:NQ + y=(5-j)*vspace; + if j> coolhot +% +% See also: activmap, grayhot, greyish + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2006 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2006-02-22 Creation +% +% ----------------------------- Script History --------------------------------- + +if nargin < 1, n=size(get(gcf,'colormap'),1); end +n = ceil(n/2); +c=[flipud(fliplr(hot(n))); hot(n)]; \ No newline at end of file diff --git a/colon2.m b/colon2.m new file mode 100644 index 0000000..186e782 --- /dev/null +++ b/colon2.m @@ -0,0 +1,44 @@ +function [varargout] = colon2(varargin) +%: Colon2. (Adapted fomr COLON for 2-element input by KND) +% +% COLON2([J K]) is equivalent to COLON(J,K) +% +% See also: COLON + +% J:K is the same as [J, J+1, ..., K]. +% J:K is empty if J > K. +% J:D:K is the same as [J, J+D, ..., J+m*D] where m = fix((K-J)/D). +% J:D:K is empty if D == 0, if D > 0 and J > K, or if D < 0 and J < K. +% +% COLON(J,K) is the same as J:K and COLON(J,D,K) is the same as J:D:K. +% +% The colon notation can be used to pick out selected rows, columns +% and elements of vectors, matrices, and arrays. A(:) is all the +% elements of A, regarded as a single column. On the left side of an +% assignment statement, A(:) fills A, preserving its shape from before. +% A(:,J) is the J-th column of A. A(J:K) is [A(J),A(J+1),...,A(K)]. +% A(:,J:K) is [A(:,J),A(:,J+1),...,A(:,K)] and so on. +% +% The colon notation can be used with a cell array to produce a comma- +% separated list. C{:} is the same as C{1},C{2},...,C{end}. The comma +% separated list syntax is valid inside () for function calls, [] for +% concatenation and function return arguments, and inside {} to produce +% a cell array. Expressions such as S(:).name produce the comma separated +% list S(1).name,S(2).name,...,S(end).name for the structure S. +% +% For the use of the colon in the FOR statement, See FOR. +% For the use of the colon in a comma separated list, See VARARGIN. + +if nargout == 0 + if nargin==1 && length(varargin{1})==2 + builtin('colon',varargin{1}(1),varargin{1}(2)); + else + builtin('colon', varargin{:}); + end +else + if nargin==1 && length(varargin{1})==2 + [varargout{1:nargout}] = builtin('colon', varargin{1}(1),varargin{1}(2)); + else + [varargout{1:nargout}] = builtin('colon', varargin{:}); + end +end \ No newline at end of file diff --git a/colorbar_slider.m b/colorbar_slider.m new file mode 100644 index 0000000..cf433ac --- /dev/null +++ b/colorbar_slider.m @@ -0,0 +1,97 @@ +function varargout = colorbar_slider(action,varargin) +%COLORBAR_SLIDER - Add a GUI slider next to the colorbar +% [h] = colorbar_slider([ha]) +% [h] = colorbar_slider('init',ha) +% Adds a slider on the side of the specified colorbar +% (default: current colorbar, added if none) +% +% Example +% >> colorbar_slider +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2006 +% This program is free software; you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation; either version 2 of the License, or +% (at your option) any later version. +% +% ----------------------------- Script History --------------------------------- +% KND 2006-01-03 Creation +% +% ----------------------------- Script History --------------------------------- + +if nargin<1 + ha=colorbar; + varargin={ha}; + action='init'; +elseif nargin==1 || isnumeric(action) + ha=action; + action='init'; + if ishandle(ha) + if ~strmatch('Colorbar', get(ha, 'tag')) + ha=colorbar(ha); + end + varargin=[{ha} varargin ]; + end +end + +try + varargout={eval(sprintf('action_%s(varargin{:});',action))}; +catch + eval(sprintf('action_%s(varargin{:});',action)); +end + + +function [h]=action_init(ha,varargin) +pos=get(ha, 'Position'); +h=findobj(get(ha,'parent'), 'tag', mfilename); + +if ~isempty(h) + if all(all(getappdata(h, 'CurrentColormap')==colormap)) + colormap(ha,getappdata(h, 'BaseColormap') ) + delete(h) + end +end + +h=uicontrol('style', 'slider', 'units', get(ha,'units'),'tag', mfilename); +% Check orientation of colorbar +if isempty(get(ha, 'Xtick')) + % vertical colorbar + if strmatch('right',get(ha, 'YAxisLocation')) + set(h, 'Position',pos*[1 0 -0.5 0;0 1 0 0; 0 0 .5 0; 0 0 0 1]') + else + set(h, 'Position',pos*[1 0 +1.5 0;0 1 0 0; 0 0 .5 0; 0 0 0 1]') + end + set(h, 'Min',min(get(ha, 'YLim')), 'max',max(get(ha, 'YLim')),'value',min(get(ha, 'YLim'))) +else + % horizontal colorbar + if strmatch('top',get(ha, 'XAxisLocation')) + set(h, 'Position',pos*[1 0 0 0;0 1 0 -0.5; 0 0 1 0; 0 0 0 .5]') + else + set(h, 'Position',pos*[1 0 0 0;0 1 0 +1.5; 0 0 1 0; 0 0 0 .5]') + end + set(h, 'Min',min(get(ha, 'XLim')), 'max',max(get(ha, 'XLim')),'value',min(get(ha, 'XLim'))) +end +% set(h, 'SliderStep', (get(h,'Max')-get(h,'Min')).*[1/100 1/10]); +set(h, 'UserData', ha); +setappdata(h, 'Colorbar', ha); +setappdata(h, 'Figure', get(ha,'Parent')); +setappdata(h, 'BaseColormap', colormap); +setappdata(h, 'BaseColor', [.6 .6 .6]); +setappdata(h, 'CurrentColormap', colormap); +set(h, 'Callback', sprintf('%s(''slide'', gcbo);', mfilename)); +return + + +function [h]=action_slide(h,varargin) +x=get(h, 'Value'); +h=action_value(h,x); + +function [h]=action_value(h,x) +cmap=getappdata(h, 'BaseColormap'); +n=round((x-get(h,'Min'))/(get(h,'Max')-get(h,'Min'))*size(cmap,1)); +cmap(1:n,:)=repmat(getappdata(h,'BaseColor'), n,1); +colormap(getappdata(h,'Colorbar'),cmap); +setappdata(h, 'CurrentColormap', colormap); \ No newline at end of file diff --git a/colormap_threshold.m b/colormap_threshold.m new file mode 100644 index 0000000..e797369 --- /dev/null +++ b/colormap_threshold.m @@ -0,0 +1,85 @@ +function c = colormap_threshold(x,cmap,beta) +%COLORMAP_THRESHOLD - Cut colormap at a given threshold +% +% [cmap] = colormap_threshold(x) +% Replace colormap in current axes (gca) with a thresholded one at +% the value x (or [-x x] if both positive and negatice values are +% used in the current graphics +% +% [cmap] = colormap_threshold(x,rgb) if RGB has only one line, the new +% colormap will be based on the colormap of the current axes +% +% [cmap] = colormap_threshold(x,handle) uses the colormap of the +% specified axes. If empty, uses the current axes. +% +% [cmap] = colormap_threshold(x,cmap,beta) +% beta specifies where the colormap should be cut and replace by the +% given RGB color (col). In percent (if beta<1) or actual number of colors (if >=1). +% Default: beta=0 (the whole previous colormap will be +% used to display values beyond the threshold x) +% +% [cmap] = colormap_threshold(x,beta,cmap,clim,col) + +% +% Example +% To cut p-value map displayed in log10 at a p<0.05 threshold +% >> colormap_threshold(-log10(0.05)) +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2005 +% This program is free software; you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation; either version 2 of the License, or +% (at your option) any later version. +% +% ----------------------------- Script History --------------------------------- +% KND 2005-12-18 Creation +% +% ----------------------------- Script History --------------------------------- +h=gca; +beta=0; +if nargin>1 + h=varargin{1}; +end +if nargin>2 + beta=varargin{2}; +end +if nargin>3 + col=varargin{3}; + if length(col)==3 + cmap=colormap(h); + clim=get(h, 'CLim'); + else + beta=varargin{1}; + cmap=varargin{2}; + clim=varargin{3}; + col=[]; + if nargin>4 + col=varargin{4}; + end + + end +else + cmap=colormap(h); + clim=get(h, 'CLim'); + col=[]; +end + +if prod(clim)>=0 + % all values have same sign + d=clim(2)-clim(1); + + if (clim(2)>0) + % All positive + + elseif clim(1)<0 + % All negative + else + % All zeros! + error('Wrong CLim') + end +else + +end diff --git a/colorname2rgb.m b/colorname2rgb.m new file mode 100644 index 0000000..04e0b5d --- /dev/null +++ b/colorname2rgb.m @@ -0,0 +1,64 @@ +function y = colorname2rgb(x) +%COLORNAME2RGB - Converts colornames to +% [] = colorname(x) +% +% Example +% >> colorname('r') +% [ 1 0 0 ] +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2007 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2007-10-02 Creation +% +% ----------------------------- Script History --------------------------------- +if isnumeric(x) + y=x; + return; +end +if iscell(x) + y=cell(size(x)); + for i=1:numel(x) + y{i}=colorname2rgb(x{i}); + end + return +end +if ischar(x) + if size(x,1) >1 + y=NaN*zeros(size(x,1),3); + for i=1:size(x,1) + y(i,:)=colorname2rgb(x(i,:)); + end + else + switch lower(x) + case { 'k' 'black'} + y=[0 0 0]; + case { 'w' 'white'} + y=[1 1 1]; + case { 'r' 'red'} + y=[1 0 0]; + case { 'g' 'green'} + y=[0 1 0]; + case { 'b' 'blue'} + y=[0 0 1]; + case { 'y' 'yellow'} + y=[1 1 0]; + case { 'c' 'cyan'} + y=[0 1 1]; + case { 'm' 'magenta'} + y=[1 0 1]; + + otherwise + y=str2num(x) + end + end +end +if isempty(y) + y=NaN; +end diff --git a/columnlegend.m b/columnlegend.m new file mode 100755 index 0000000..f8a5907 --- /dev/null +++ b/columnlegend.m @@ -0,0 +1,132 @@ +function [legend_h,object_h,plot_h,text_strings] = columnlegend(numcolumns, str, varargin) +% +% columnlegend creates a legend with a specified number of columns. +% +% columnlegend(numcolumns, str, varargin) +% numcolumns - number of columns in the legend +% str - cell array of strings for the legend +% +% columnlegend(..., 'Location', loc) +% loc - location variable for legend, default is 'NorthEast' +% possible values: 'NorthWest', 'NorthEast', 'SouthEast', 'SouthWest' +% +% columnlegend(..., 'boxon') +% columnlegend(..., 'boxoff') +% set legend bounding box on/off +% +% example: +% legend_str = []; +% for i=1:10, +% x = 1:i:(10*i); +% plot(x); hold on; +% legend_str = [legend_str; {num2str(i)}]; +% end +% columnlegend(3, legend_str, 'Location', 'NorthWest'); +% +% +% Author: Simon Henin +% +% 4/09/2013 - Fixed bug with 3 entries / 3 columns +% 4/09/2013 - Added bounding box option as per @Durga Lal Shrestha (fileexchage) + + + +location = 'NorthEast'; +boxon = false; +for i=1:length(varargin), + switch lower(varargin{i}) + case 'location' + location = varargin{i+1}; + i=i+2; + case 'boxon' + boxon = true; + case 'boxoff' + boxon = false; + end +end + +%create the legend +[legend_h,object_h,plot_h,text_strings] = legend(str); + +%some variables +numlines = length(str); +numpercolumn = ceil(numlines/numcolumns); + +%get old width, new width and scale factor +pos = get(legend_h, 'position'); +width = numcolumns*pos(3); +rescale = pos(3)/width; + +%get some old values so we can scale everything later +xdata = get(object_h(numlines+1), 'xdata'); +ydata1 = get(object_h(numlines+1), 'ydata'); +ydata2 = get(object_h(numlines+3), 'ydata'); + +%we'll use these later to align things appropriately +sheight = ydata1(1)-ydata2(1); % height between data lines +height = ydata1(1); % height of the box. Used to top margin offset +line_width = (xdata(2)-xdata(1))*rescale; % rescaled linewidth to match original +spacer = xdata(1)*rescale; % rescaled spacer used for margins + + +%put the legend on the upper left corner to make initial adjustments easier +loci = get(gca, 'position'); +set(legend_h, 'position', [loci(1) pos(2) width pos(4)]); + + +col = -1; +for i=1:numlines, + if (mod(i,numpercolumn)==1 || (numpercolumn == 1)), + col = col+1; + end + + if i==1 + linenum = i+numlines; + else + linenum = linenum+2; + end + labelnum = i; + + position = mod(i,numpercolumn); + if position == 0, + position = numpercolumn; + end + + %realign the labels + set(object_h(linenum), 'ydata', [(height-(position-1)*sheight) (height-(position-1)*sheight)]); + set(object_h(linenum), 'xdata', [col/numcolumns+spacer col/numcolumns+spacer+line_width]); + + set(object_h(linenum+1), 'ydata', [height-(position-1)*sheight height-(position-1)*sheight]); + set(object_h(linenum+1), 'xdata', [col/numcolumns+spacer*3.5 col/numcolumns+spacer*3.5]); + + set(object_h(labelnum), 'position', [col/numcolumns+spacer*2+line_width height-(position-1)*sheight]); + + +end + +%unfortunately, it is not possible to force the box to be smaller than the +%original height, therefore, turn it off and set background color to none +%so that it no longer appears +set(legend_h, 'Color', 'None', 'Box', 'off'); + +%let's put it where you want it +pos = get(legend_h, 'position'); +fig_pos = get(gca, 'position'); +switch lower(location), + case {'northeast'} + set(legend_h, 'position', [pos(1)+fig_pos(3)-pos(3) pos(2) pos(3) pos(4)]); + case {'southeast'} + set(legend_h, 'position', [pos(1)+fig_pos(3)-pos(3) fig_pos(2)-pos(4)/2+pos(4)/4 pos(3) pos(4)]); + case {'southwest'} + set(legend_h, 'position', [fig_pos(1) fig_pos(2)-pos(4)/2+pos(4)/4 pos(3) pos(4)]); +end + +% display box around legend +if boxon, + pos = get(legend_h, 'position'); + orgHeight = pos(4); + pos(4) = orgHeight/numlines*numpercolumn; + pos(2)=pos(2) + orgHeight-pos(4) - pos(4)*0.05; + pos(1) = pos(1)+pos(1)*0.05; + annotation('rectangle',pos) +end \ No newline at end of file diff --git a/compute_forward.m b/compute_forward.m new file mode 100644 index 0000000..dc25a43 --- /dev/null +++ b/compute_forward.m @@ -0,0 +1,2660 @@ +function [varargout] = bst_headmodeler(varargin); +%BST_HEADMODELER - Solution to the MEG/EEG forward problem +% function [varargout] = bst_headmodeler(varargin); +% Authorized syntax: +% [G, OPTIONS] = bst_headmodeler(StudyFile, OPTIONS); +% [G, OPTIONS] = bst_headmodeler(OPTIONS); +% [OPTIONS] = bst_headmodeler; +% +% --------------------------------- INPUTS ------------------------------------- +% INPUTS +% +% [OPTIONS] = bst_headmodeler; Returns the default values for the OPTIONS +% parameter structure +% +% StudyFile: the name of a BrainStorm study file. We suppose the +% corresponding BrainStorm channel file is available in the same folder +% with the conventional file name (e.g., for a study file calles +% meg_brainstormstudy.mat, BST_HEADMODELER expects to find the +% corresponding channel file under the name meg_channel.mat). If the +% channel file were not sitting in the same folder as the study file, +% OPTIONS.ChannelFile enforces computation of the forward model with the +% channel information contained in OPTIONS.ChannelFile. +% +% If no further input arguments are specified, forward modeling is +% completed with the default parameters specified below +% +% OPTIONS a structure where optional parameters may be specified using the +% following fields Note: if no options are specified, BST_HEADMODELER will +% proceed to the computation of the foward problem on a 3D grid of source +% locations that cover the entire head volume See +% OPTIONS.VolumeSourceGridSpacing for default settings +% +% Important notice: there is no need to define all the following fields +% when using the OPTIONS argument. The undefined field(s) will be assigned +% default values. +% +% * +% * Fields Related to forward approach +% * +% +% .Method: is either a character string or a cell array of two strings that +% specifies the kind of approach to be applied to the compuation +% of the foward model In case Method is a cell array, it should +% contain 2 strings, one to specifiy the method to be used for MEG +% and the other for . If only a single string is specified, the +% foward computation will be completed on the set of corresponding +% CHANndx only (i.e. MEG or MEG) Available forward modeling +% methods and corresponding authorized strings for Method: +% - MEG +% 'meg_sphere' (DEFAULT) : Spherical head model designed +% following the Sarvas analytical formulation (i.e. +% considering the true orientation +% of the magnetic field sensors) (see OPTIONS.HeadCenter) +% 'meg_os' : MEG overlapping sphere forward model +% 'meg_bem' : Apply BEM computation (see OPTIONS.BEM for details) +% - +% 'eeg_sphere' : Single-sphere forward modeling (see +% OPTIONS.HeadCenter, OPTIONS.Radii, OPTIONS.Conductivity) +% 'eeg_3sphere' : EEG forward modeling with a set of 3 +% concentric spheres (Scalp, Skull, Brain/CSF) (see +% OPTIONS.HeadCenter, OPTIONS.Radii, OPTIONS.Conductivity) +% 'eeg_3sphereBerg' (DEFAULT) : Same as eeg_3sphere with +% correction for possible dipoles outside the sphere +% 'eeg_os' : EEG overlapping sphere head model (see +% OPTIONS.HeadCenter, OPTIONS.Radii, OPTIONS.Conductivity) +% 'eeg_bem' : Apply BEM computation (see OPIONS.BEM for details) +% +% Default is {'meg_sphere','eeg_3sphereBerg'}; +% +% .HeadModelName : a character string that specifies the name of the +% headmodel represented by this file, e.g "Spherical", +% "Overlapping Spheres", "Constant Collocation BEM", etc. +% Default is "Default", meaning it will include the the +% name(s) of the method(s) used in the MEG and/or EEG +% forward models +% +% * +% * Fields Related to function's I/O +% * +% +% .HeadModelFile : Specifies the name of the head model file where to store +% the forward model. If set to 'default', the default +% nomenclature for BrainStorm's head model file name is +% used and BST_HEADMODELER creates a file in StudyFile's +% folder. +% Default is empty. +% .ImageGridFile : Specifies the name of the file where to store the full +% cortical gain matrix file If set to 'default', the +% default nomenclature for BrainStorm's head model file +% name is used and BST_HEADMODELER creates a file in +% StudyFile's folder. +% Default is empty. +% .ImageGridBlockSize : Number of sources for which to compute the forward +% model at a time in a block computation routines +% (saves memory space). This option is relevant only +% when some forward modeling on cortical surface is +% requested (i.e. when .Cortex is specified) +% Default is 2000 +% .FileNamePrefix : A string that specifies the prefix for all file names +% (Channel, HeadModel, Gain Matrices) when .HeadModelFile +% is set to 'default' and .ChannelFile is empty. +% Default is 'bst_' +% .Verbose : Toggles verbose mode on/off; +% Default is 1 (on) +% +% * +% * Fields Related to Head Geometry * +% * +% +% .Scalp : A structure specifying the Scalp surface envelope to serve +% for parameter adjustment of best-fitting sphere, with +% following fields: +% .FileName : A string specifying the name of the BrainStorm +% tessellation file containing the Scalp +% tessellation (default is 1); +% .iGrid : An integer for the index of the Scalp surface in +% the Faces, Vertices and Comments cell arrays in +% the tessellation file +% Default is empty (Best-fitting sphere is +% computed from the sensor array). +% .HeadCenter: a 3-element vector specifying the coordinates, in the +% sensors coordinate system, of the center of the spheres that +% might be used in the head model. +% Default is estimated from the center of the best-fitting +% sphere to the sensor locations +% .Radii : a 3-element vector containing the radii of the single or 3 +% concentric spheres, when needed; +% Order must be the following : [Rcsf, Routerskull, Rscalp]; +% Default is estimated from the best-fitting sphere to the +% sensor locations and OPTIONS.Radii is set to: Rscalp [.88 +% .93 1]. Rscalp is estimated from the radius of the +% best-fitting sphere; +% .Conductivity : a 3-element vector containing the values for the +% conductivity of the tissues in the following order: +% [Ccsf, Cskull, Cscalp]; +% Default is set to [.33 .0042 .33]; +% .EEGRef : the NAME (not index of the channel file) of the electrode +% that acts as the reference channel for the EEG. If data is +% referenced to instantaneous average (i.e. so called +% average-reference recording) value is 'AVERAGE REF'; +% IMPORTANT NOTICE: When user calls bst_headmodeler with the +% .ChannelLoc option and .ChannelType = 'EEG'and wants the EEG +% reference to be e.g. channel 26, then .EEGRef should be set +% to 'EEG 26' +% Default is 'AVERAGE REF'. +% +% .OS_ComputeParam : if 1, force computation of all sphere parameters when +% choosing a method based on either the MEG or EEG +% overlapping-sphere technique, if 0 and when +% .HeadModelFile is specified, sphere parameters are +% loaded from the pre-computed HeadModel file. +% Default is 1. +% +% .BEM : Structure that specifies the necessary BEM parameters +% .Interpolative : Flag indicating whether exact or +% interpolative approach is used to compute +% the forward solution using BEM. +% if set to 1, exact computation is run on a +% set of points distributed wihtin the inner +% head volume and any subsequent request for +% a forward gain vector (e.g. during a +% volumic source scan using RAP-MUSIC) is +% computed using an interpolation of the +% forward gain vectors of the closest volumic +% grid points. This allows faster computation +% of the BEM solution during source search. +% if set to 0, exact computation is required +% at every source location. +% We recommend to set it to 0 (default) when +% sources have fixed location, e.g. +% constrained on the cortical surface. +% .EnvelopeNames : a cell array of strutures that specifies +% the ORDERED tessellated surfaces to be +% included in the BEM computation. +% .EnvelopeNames{k}.TessFile : A string for +% the name of the tessellation file +% containing the kth surface +% .EnvelopeNames{k}.TessName : A string for +% the name of the surface within the +% tessellation file This string should match +% one of the Comment strings in the +% tessellation file. The chosen surfaces must +% be ordered starting by the the innermost +% surface (e.g. brain or inner skull +% surface) and finishing with the outermost +% layer (e.g. the scalp) +% .Basis : set to either 'constant' or 'linear' (default) +% .Test : set to either 'Galerkin' or 'Collocation' (default) +% .ISA : Isolated-skull approach set to 0 or 1 (default is 1) +% .NVertMax : Maximum number of vertices per envelope, +% therefore leading to decimation of orginal +% surfaces if necessary +% (default is 1000) +% .ForceXferComputation: if set to 1, force recomputation of +% existing transfer matrices in current +% study folder (replace existing +% files); +% Default is 1 +% +% * +% * Fields Related to Sensor Information * +% * +% +% .ChannelFile : Specifies the name of the file containing the channel +% information (needs to be a BrainStorm channel file). If +% file does not exists and sensor information is provided in +% .ChannelLoc, a BrainStorm Channl file with name +% .ChannelFile is created +% Default is left blank as this information is extracted +% from the channel file associated to the chosen BrainStorm +% studyfile. +% .Channel : A full BrainStorm channel structure if no channel file is +% specified; +% Default is empty +% .ChannelType : A string specifying the type of channel in ChannelLoc. Can +% be either 'MEG' or 'EEG'. Note that the same channel type +% is assumed for every channel. +% Default is empty +% .ChannelLoc : Specifies the location of the channels where to compute +% the forward model Can be either a 3xNsens (for EEG or +% MEG-magnetometer) or 6xNsens matrix (for the +% MEG-gradiometer case). (for magnetometer or gradiometer +% MEG - channel weights are set to -1 and 1 for each +% magnetometer in the gradiometer respectively) Note that +% in the MEG-gradiometer case, the 3 first (res. last) rows +% of .ChannelLoc stand for each of the magnetometers of the +% gradiometer set. In the case of a mixture of MEG magneto +% and MEG gradio-meters, .ChannelLoc needs to be a 6xNsens +% matrix where the last 3 rows are filled with NaN for +% MEG-magnetometers If ones wants to handle both EEG and MEG +% sensors, please create a full ChannelFile and use the +% .ChannelFile option. +% Default is empty (Information extracted from the ChannelFile). +% .ChannelOrient : Specifies the orientation of the channels where to +% compute the EEG or MEG forward model Can be either a +% 3xNsens (for EEG and magnetometer MEG) or 6xNsens (for +% gradiometer MEG) matrix or a cell array of such matrices +% (one cell per type of method selected) +% Default is empty (Information extracted from the +% ChannelFile or assume radial orientation when +% .ChannelLoc is filled). +% +% * +% * Fields Related to Source Models * +% * +% .SourceModel : A vector indicating the type of source models to be +% computed; The following code is enfoced: +% -1 : Compute the forward fields of Current Dipole sources +% (available for all forward approaches) +% 1 : 1st-order Current Multipole Sources +% (available for sphere-based MEG approaches only) +% User can set OPTIONS.SourceModel to e.g., [-1 1] to +% compute forward models from both source models. +% Default is -1 +% +% +% * +% * Fields Related to Source Localization * +% * +% .Cortex : A structure specifying the Cortex surface +% envelope to serve as an image support with +% following fields. +% .FileName : A string specifying the name of +% the BrainStorm tessellation file +% containing the Cortex +% tessellation; +% .iGrid : An integer for the index of the +% Cortex surface in the Faces, +% Vertices and Comments cell arrays +% in the tessellation file (default +% is 1) +% Default is empty. +% .GridLoc : A 3xNsources matrix that contains the +% locations of the sources at which the forward +% model will be computed +% Default is empty (Information taken from +% OPTIONS.Cortex or OPTIONS.VolumeSourceGrid); +% .GridOrient : a 3xNsources matrix that forces the source +% orientation at every vertex of the .ImageGrid +% cortical surface; +% Defaults is empty; this information being +% extracted from the corresponding tessellated +% surface. +% .ApplyGridOrient : if set to 1, force computation of the forward +% fields by considering the local orientation of +% the cortical surface; +% If set to 0, a set of 3 orthogonal dipoles are +% considered at each vertex location on the +% tessellated surface. +% Default is 1. +% .VolumeSourceGrid : if set to 1, a 3D source grid is designed to +% fit inside the head volume and will serve as a +% source space for scannig techniques such as +% RAP-MUSIC; +% if set to 0, this grid will be computed at the +% first call of e.g. RAP-MUSIC); +% Default is 1 +% .VolumeSourceGridSpacing : Spacing in centimeters between two consecutive +% sources in the 3D source grid described above; +% Default is 2 cm. +% .VolumeSourceGridLoc : a 3xN matrix specifying the locations of the +% grid points that will be used to design the +% volumic search grid (see .VolumicSourceGrid) +% Default is empty (locations are estimated +% automatically to cover the estimated inner +% head volume) +% .SourceLoc : a 3xNsources matrix that contains the +% locations of the sources at which the forward +% model will be computed +% Default is empty (Information taken from +% OPTIONS.ImageGrid or +% OPTIONS.VolumeSourceGrid); +% .SourceOrient : a 3xNsources matrix that contains the +% orientations of the sources at which the +% forward model will be computed +% Default is empty. +% +% +% --------------------------------- OUTPUTS ------------------------------------ +% OUTPUT +% G if the number of sources (Nsources) if less than +% .ImageGridBlockSize then G is a gain matrix of dimension +% Nsensors x Nsources: Each column of G is the forward field +% created by a dipolar source of unit amplitude. Otherwise, G is +% the name of the binary file containing the gain matrix. This +% file can be read using the READ_GAIN function. +% OPTIONS Returns the OPTIONS structure with updated fields following the +% call to BST_HEADMODELER. Can be useful to obtain a full +% BrainStorm Channel structure when only the .ChannelLoc and +% possibly .ChannelOrient fields were provided. + +% ---------------------- 27-Jun-2005 10:43:31 ----------------------- +% ------ Automatically Generated Comments Block Using AUTO_COMMENTS_PRE7 ------- +% +% CATEGORY: Forward Modeling +% +% Alphabetical list of external functions (non-Matlab): +% toolbox\bem_gain.m +% toolbox\bem_xfer.m +% toolbox\berg.m +% toolbox\bst_message_window.m +% toolbox\colnorm.m +% toolbox\get_channel.m +% toolbox\get_user_directory.m +% toolbox\good_channel.m +% toolbox\gridmaker.m +% toolbox\inorcol.m +% toolbox\norlig.m +% toolbox\overlapping_sphere.m +% toolbox\rownorm.m +% toolbox\save_fieldnames.m +% toolbox\source_grids.m +% toolbox\view_surface.m +% +% Subfunctions in this file, in order of occurrence in file: +% BEMGaingridFname = bem_GainGrid(DataType,OPTIONS,BEMChanNdx) +% g = gterm_constant(r,rq) +% +% At Check-in: $Author: Mosher $ $Revision: 66 $ $Date: 6/27/05 8:59a $ +% +% This software is part of BrainStorm Toolbox Version 27-June-2005 +% +% Principal Investigators and Developers: +% ** Richard M. Leahy, PhD, Signal & Image Processing Institute, +% University of Southern California, Los Angeles, CA +% ** John C. Mosher, PhD, Biophysics Group, +% Los Alamos National Laboratory, Los Alamos, NM +% ** Sylvain Baillet, PhD, Cognitive Neuroscience & Brain Imaging Laboratory, +% CNRS, Hopital de la Salpetriere, Paris, France +% +% See BrainStorm website at http://neuroimage.usc.edu for further information. +% +% Copyright (c) 2005 BrainStorm by the University of Southern California +% This software distributed under the terms of the GNU General Public License +% as published by the Free Software Foundation. Further details on the GPL +% license can be found at http://www.gnu.org/copyleft/gpl.html . +% +% FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE +% UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY +% WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF +% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY +% LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE. +% ------------------------ 27-Jun-2005 10:43:31 ----------------------- + + + +% /---Script Author--------------------------------------\ +% | | +% | *** Sylvain Baillet Ph.D. | +% | Cognitive Neuroscience & Brain Imaging Laboratory | +% | CNRS UPR640 - LENA | +% | Hopital de la Salpetriere, Paris, France | +% | sylvain.baillet@chups.jussieu.fr | +% | | +% \------------------------------------------------------/ +% +% Date of creation: March 2002 + + +% ----------------------------- Script History --------------------------------- +% +% SB 07-Aug-2002 Fixed GridLoc saving issues - now GridLoc +% is a cell array of length one which indicates +% the name of the tessellation file that was +% used as an imaging support. +% SB 03-Sep-2002 Fixed minor bugs when function is called from command line +% Now bst_headmodeler can be called even when Brainstorm is not running +% SB 05-Sep-2002 BEM can now be called from command line as well. +% Updated script header +% SB 06-Sep-2002 Updated EEG BEM : added option for arbitrary reference of the +% potentials (takes now OPTIONS.EEGRef into account). +% SB 18-Nov-2002 Updated EEG reference management. +% SB 21-Nov-2002 Minor fixes for command line calls. +% SB 21-Nov-2002bis Fixed bug from the get(*,'VertexNormals') command that +% sometimes returns [0 0 0] at some vertex +% SB 23-Dec-2002 Correct HeadModelFile name is passed to OPTIONS output argument +% JCM 27-May-2004 Cleaning comments +% Found Matlab bug and temp solution. +% Matlab 6.5.0 bug, Technical Solution Number: 1-19NOK +% http://www.mathworks.com/support/solutions/data/1-19NOK.html?solution=1-19NOK +% Must REWIND first before fseek. +% Apparently fixed in 6.5.1 +% Example: +% status = fseek(fdest,0,'bof'); +% status = fseek(fdest,offset,'bof'); +% SB 08-Mar-05 Fixed bug in BEM computation +% ----------------------------- Script History --------------------------------- + +% Default options settings-------------------------------------------------------------------------------------------------- +DefaultMethod = {'meg_sphere','eeg_3sphereBerg'}; + +ReducePatchScalpNVerts = 500; % Number of vertices the scalp tessellation will be reduced to in OS computations +BEM_defaults = struct(... + 'Basis','linear',... + 'Test','galerkin',... + 'Interpolative',0,... + 'ISA',1,... + 'NVertMax',1000,... + 'ForceXferComputation', 1, ... + 'checksurf',0); + +Def_OPTIONS = struct(... + 'ApplyGridOrient',1,... + 'BEM', BEM_defaults,... + 'Channel', [],... + 'ChannelFile', '',... + 'ChannelLoc', '',... + 'ChannelOrient', '',... + 'ChannelType', '',... + 'Conductivity', [.33 .0042 .33],... + 'Cortex',[],... + 'EEGRef','',... + 'HeadCenter',[],... + 'HeadModelFile', '',... + 'HeadModelName','Default',... + 'ImageGridBlockSize', 2000,... + 'ImageGridFile', '',... + 'GridOrient',[],... + 'Method', {DefaultMethod},... + 'OS_ComputeParam', 1,... + 'PrefixFileName','bst_',... + 'Radii', [],... + 'Scalp',[],... + 'SourceLoc',[],... + 'SourceModel', [-1],... + 'SourceOrient',[],... + 'StudyFile','',... + 'TessellationFile','',... + 'VolumeSourceGrid',1,... + 'VolumeSourceGridSpacing', 2,... + 'VolumeSourceGridLoc', [],... + 'Verbose', 1 ... + ); + +SourceOrderString = {'Current Dipole',... + 'Unvalid Order (was Magnetic Dipole Moment)',... + 'Current Multipole Expansion',... + 'Current Dipole Pairs',... + 'Unvalid Order (was Magnetic Dipole Moment PAIRS)',... + 'Current Multipole Expansion Pairs'}; + +if nargin == 0 + if nargout > 1 + varargout{1} = Def_OPTIONS; + varargout{2} = Def_OPTIONS; + else + varargout{1} = Def_OPTIONS; + end + return +elseif nargin == 1 + if ischar(varargin{1}) % User enters only the studyfile name + StudyFile = varargin{1}; + OPTIONS = Def_OPTIONS; + elseif isstruct(varargin{1}) % User enters only the OPTION field + OPTIONS = varargin{1}; + else + errordlg('Uncorrect input parameter type, please check the help file'), varargout = cell(nargout,1); + return, + end +elseif nargin == 2 % No options were specified, apply default + StudyFile = varargin{1}; + OPTIONS = varargin{2}; +else + errordlg('Wrong number of arguments when calling head modeler') + varargout = cell(nargout,1); + return +end + +% Check field names of passed OPTIONS and fill missing ones with default values +DefFieldNames = fieldnames(Def_OPTIONS); +for k = 1:length(DefFieldNames) + if ~isfield(OPTIONS,DefFieldNames{k}) | strcmp(DefFieldNames{k},'BEM') + if ~isfield(OPTIONS,DefFieldNames{k}) + + OPTIONS = setfield(OPTIONS,DefFieldNames{k},getfield(Def_OPTIONS,DefFieldNames{k})); + + elseif strcmp(DefFieldNames{k},'BEM') + + BEM_DefFieldNames = fieldnames(BEM_defaults); + for kk = 1:length(BEM_DefFieldNames) + if ~isfield(OPTIONS.BEM,BEM_DefFieldNames{kk}) + OPTIONS.BEM = setfield(OPTIONS.BEM,BEM_DefFieldNames{kk},getfield(BEM_defaults,BEM_DefFieldNames{kk})); + end + end + end + end +end + +if isempty(OPTIONS.Conductivity) + OPTIONS.Conductivity = Def_OPTIONS.Conductivity; +end + +clear Def_OPTIONS + + +if isempty(OPTIONS.HeadModelFile) & ~isempty(OPTIONS.ImageGridFile) + % Force creation of a headmodel file + OPTIONS.HeadModelFile = 'default'; +end + +OPTIONS.HeadModelFileOld = OPTIONS.HeadModelFile; + +% What type of forward model (MEG and/or EEG) ? +DataType.MEG = strmatch('meg',OPTIONS.Method); % Rank of the respective forward method in cell array Method (ie could be Method = {'meg_bem','eeg_sphere'} or vice-versa) +DataType.EEG = strmatch('eeg',OPTIONS.Method); + +if ~iscell(OPTIONS.Method) + OPTIONS.Method = {OPTIONS.Method}; +end + +MegMethod = []; +if ~isempty(DataType.MEG) + MegMethod = OPTIONS.Method{DataType.MEG}; % String indicating the forward method selected for MEG (res. EEG) +end +EegMethod = []; +if ~isempty(DataType.EEG) + EegMethod = OPTIONS.Method{DataType.EEG}; +end + + +% Check inputs integrity + +%Source models +if ~isempty(find(OPTIONS.SourceModel == 0)) | ~isempty(find(abs(OPTIONS.SourceModel) > 1)) % unValid source models + if ~isempty(DataType.MEG) + errordlg('Valid source model orders for MEG are: -1 (Current Dipole) and 1 (fist-order Current Multipole Expansion)') + end + if ~isempty(DataType.EEG) + errordlg('Valid source model order for EEG is: -1 (Current Dipole) ') + end + varargout = cell(nargout,1); + return +end + +% Source locations +if isempty(OPTIONS.SourceLoc) & ~OPTIONS.VolumeSourceGrid & isempty(OPTIONS.Cortex) % No source locations were specified + errordlg('No source locations are specified. Please fill either one of the following fields of OPTIONS: .SourceLoc / .VolumeSourceGrid / .Cortex') + varargout = cell(nargout,1); + return +end + + +%-------------------------------------------------------------------------------------------------------------------------------------------- +% +% HEAD MODELING BEGINS +% +%-------------------------------------------------------------------------------------------------------------------------------------------- + +if OPTIONS.Verbose, + + clockk = fix(clock); + time0 = clock; + + bst_message_window({... + ' ',... + '__________________________________________',... + sprintf(... + 'Head modeling begins (%s - %dH %dmin %ds)',date,clockk(4:6))... + }) + +end + +User = get_user_directory; +if isempty(User) % Function is called from command line: BrainStorm is not running + % Use default folders + User.SUBJECTS = pwd; + User.STUDIES = pwd; +end +try +cd(User.STUDIES) +catch +end + +% Get all Subject and Study information------------------------------------------------------------------------------------------------------ +if exist('StudyFile','var') + if OPTIONS.Verbose, bst_message_window('Loading Study and Subject Information...'), end + try + load(StudyFile) % Load study information + [StudyPath,tmp,ext] = fileparts(StudyFile); clear tmp + catch + errordlg(['Could not find ',StudyFile,' in current Matlab path']) + varargout = cell(nargout,1); + return + end + + if isempty(OPTIONS.StudyFile) + OPTIONS.StudyFile = StudyFile; + end + + SubjectFile = BrainStormSubject; clear BrainStormSubject + + OPTIONS.rooot = findstr(StudyFile,'brainstormstudy.mat'); + if isempty(OPTIONS.rooot) + errordlg('Study file name should be of the form ''*brainstormstudy.mat''') + varargout = cell(nargout,1); + return + end + OPTIONS.rooot = strrep(StudyFile,'brainstormstudy.mat',''); + + Study = load(StudyFile); + %User = get_user_directory; + try + cd(User.SUBJECTS) + Subject = load(Study.BrainStormSubject); + catch + errordlg(sprintf('Please make sure the subject''s information in %s is available on this plateform',Study.BrainStormSubject)); return + end + + if isempty(OPTIONS.TessellationFile) % No Tessellation file was passed to the headmodeler + if isfield(Subject,'Tesselation') + OPTIONS.TessellationFile = Subject.Tesselation; % Take default (OBSOLETE as for BsT MMII because we now consider all tessellation files in Subject's folder) + else + OPTIONS.TessellationFile = ''; + %[OPTIONS.TessellationFile,DataPopup, Leader] = find_brainstorm_files('tess',fullfile(Users.SUBJECTS,OPTIONS.subjectpath)); + end + end +end + +% Get Channel Information ------------------------------------------------------------------------------------------------------------------ +if ~isempty(OPTIONS.Channel) + Channel = OPTIONS.Channel; +else + if isempty(OPTIONS.ChannelFile) & isempty(OPTIONS.ChannelLoc) % Load Channel file in current folder + + [Channel, ChannelFile] = get_channel(fullfile(User.STUDIES,OPTIONS.StudyFile)); + OPTIONS.ChannelFile = fullfile(fileparts(fullfile(User.STUDIES,OPTIONS.StudyFile)),ChannelFile); + + if isempty(ChannelFile) + errordlg(sprintf('Channel file %s is missing. Please have it available it the current study folder.',OPTIONS.ChannelFile), 'Missing Channel File') + return + end + + elseif isempty(OPTIONS.ChannelLoc) & exist(OPTIONS.ChannelFile,'file') % If no specific channel locations are given and channel file exists, load the proper channel file + + OPTIONS.rooot = strrep(lower(OPTIONS.ChannelFile),'channel.mat',''); + try + load(OPTIONS.ChannelFile) + catch + cd(User.STUDIES) + load(OPTIONS.ChannelFile) + end + + else % Create a dummy Channel structure with Channel Locations (res. Orientations) specified in OPTIONS.ChannelLoc (res .ChannelOrient) + + if OPTIONS.Verbose, bst_message_window('Creating the channel structure from information in the OPTIONS fields. . .'), end + + % Get Channel Locations + nchan = size(OPTIONS.ChannelLoc,2); % Number of channels + Channel = struct('Loc',[],'Orient',[],'Comment','','Weight',[],'Type','','Name',''); + Channel(1:nchan) = deal(Channel); + + ChanType = upper(OPTIONS.ChannelType); + if isempty(ChanType) + errordlg('Please specify a channel type (i.e. MEG or EEG) in the ChannelType field of OPTIONS'), + bst_message_window('Please specify a channel type (i.e. MEG or EEG) in the ChannelType field of OPTIONS'), + varargout = cell(nargout,1); + return + end + [Channel(:).Type] = deal(ChanType); + if size(OPTIONS.ChannelLoc,1) == 6 % MEG Gradiometers or mixture gradio/magneto meters + OPTIONS.ChannelLoc = reshape(OPTIONS.ChannelLoc,3,nchan*2); + iGradFlag = 1; % Flag - gradiometer-type sensor set + else + iGradFlag = 0; % Flag - EEG or magnetometer-type sensor set + end + ichan = 1; + for k = 1:nchan + if iGradFlag + Channel(k).Loc = OPTIONS.ChannelLoc(:,ichan:ichan+1); + ichan = ichan+2; + else + if strcmp(ChanType,'MEG') + Channel(k).Loc = OPTIONS.ChannelLoc(:,ichan); + elseif strcmp(ChanType,'EEG') % Artificially add a dummy column full of zeros to each Channel(k).Loc + Channel(k).Loc = [OPTIONS.ChannelLoc(:,ichan) [0 0 0]'];; + end + ichan = ichan+1; + end + Channel(k).Name = sprintf('%s %d',ChanType,k); + Channel(k).Comment = int2str(k); + end + clear ichan k + + % Get Channel Orientations + if isempty(OPTIONS.ChannelOrient) & strcmp(ChanType,'MEG') % No channel orientation were specified: use radial sensors + if OPTIONS.Verbose, bst_message_window('Assign radial orientation to all Channels. . .'), end + if isempty(OPTIONS.HeadCenter) + if iGradFlag + Vertices = OPTIONS.ChannelLoc(:,1:2:end)'; + else + Vertices = OPTIONS.ChannelLoc'; + end + + nscalp = size(Vertices,1); + if nscalp > 500 % 500 points is more than enough to compute scalp's best fitting sphere + Vertices = Vertices(unique(round(linspace(1,nscalp,500))),:); + nscalp = size(Vertices,1); + end + nmes = size(Vertices,1); + + % Run parameters fit -------------------------------------------------------------------------------------------------------------- + mass = mean(Vertices); % center of mass of the scalp vertex locations + R0 = mean(norlig(Vertices - ones(nscalp,1)*mass)); % Average distance between the center of mass and the scalp points + vec0 = [mass,R0]; + + [minn,brp] = fminsearch('dist_sph',vec0,[],Vertices); + OPTIONS.HeadCenter = minn(1:end-1); + if isempty(OPTIONS.Radii) + OPTIONS.Radii = minn(end); + OPTIONS.Radii = minn(end)*[1/1.14 1/1.08 1]; + end + + if OPTIONS.Verbose, bst_message_window({... + sprintf('Center of the Sphere : %3.1f %3.1f %3.1f (cm)',100*OPTIONS.HeadCenter),... + sprintf('Radius : %3.1f (cm)',100*OPTIONS.Radii(3)),... + '-> DONE',' '}) + end + + clear minn brp mass R0 Vertices vec0 + end + + tmp = [Channel.Loc] - repmat(OPTIONS.HeadCenter',1,nchan*(1+(iGradFlag==1))); + OPTIONS.ChannelOrient = tmp*inorcol(tmp); % Radial orientation for every channel + clear tmp + elseif ~isempty(OPTIONS.ChannelOrient) & strcmp(ChanType,'MEG') & iGradFlag + OPTIONS.ChannelOrient = reshape(OPTIONS.ChannelOrient,3,nchan*2); + end + + if strcmp(ChanType,'MEG') + for k=1:nchan + Channel(k).Orient = OPTIONS.ChannelOrient(:,((1+(iGradFlag==1))*k-1):(1+(iGradFlag==1))*k); + end + end + + clear k + + % Define Weights + if iGradFlag + [Channel(:).Weight] = deal([1 -1]); + else + [Channel(:).Weight] = deal([1]); + end + + if strcmp(ChanType,'MEG') % Force no reference channels + Channel(1).irefsens = []; + end + + % New Channel stbemructure completed: save as a new channel file + if ~isempty(OPTIONS.ChannelFile) + %OPTIONS.ChannelFile = [OPTIONS.rooot,'channel.mat']; + save(OPTIONS.ChannelFile,'Channel') + if OPTIONS.Verbose, bst_message_window({... + sprintf('Channel file created in : %s',OPTIONS.ChannelFile),... + '-> DONE',' '}), end + end + + end + + %load(OPTIONS.ChannelFile) + OPTIONS.Channel = Channel; +end + +% Find MEG and EEG channel indices +MEGndx = good_channel(Channel,[],'MEG'); +EEGndx = good_channel(Channel,[],'EEG'); +EEGREFndx = good_channel(Channel,[],'EEG REF'); + +MEG = ~isempty(MEGndx) & ~isempty(DataType.MEG); % == 1 if MEG is requested and is available +EEG = ~isempty(EEGndx) & ~isempty(DataType.EEG); + +if ~EEG & ~MEG + errordlg('Please check that data (MEG or EEG) and channel types are compatible.') + return +end + +% Test whether CME models are requested for EEG +if ~isempty(find(OPTIONS.SourceModel == 1)) & EEG + if MEG + if OPTIONS.Verbose + bst_message_window('wrap',... + {'',... + 'CME model not available for EEG - Skipping. . .',... + 'Keeping it for MEG forward modeling only',... + ''... + }) + end + else % EEG only is requested + errordlg(... + {'CME model not available for EEG - Skipping. . .',... + 'Keeping it for MEG forward modeling only'... + }) + return + end + +end + +% Detect EEG reference - if none is specified in the .Comment or Type fields, +% and if none was passed through OPTIONS.EEGRef +% -> Apply average-referencing of the potentials by default. + +if EEG + if isempty(OPTIONS.EEGRef) + + % EEG Reference Channel + EEGREFndx = good_channel(Channel,[],'EEG REF'); + if isempty(EEGREFndx) % Average EEG reference anyway + [Channel(EEGndx).Comment] = deal('AVERAGE REF'); + end + + else % EEG Reference is specified + + switch(OPTIONS.EEGRef) + case 'AVERAGE REF' + [Channel(:).Comment] = deal('AVERAGE REF'); + + otherwise + + EEGREFndx = strmatch(OPTIONS.EEGRef,char(Channel(:).Name)); + if isempty(EEGREFndx) + errordlg(sprintf(... + 'No channel named ''%s'' was found amongst available EEG channels. Cannot use it as a reference for EEG.',OPTIONS.EEGRef... + )) + return + end + + end + + end + + % if isempty(EEGREFndx) & ~MEG % No reference electrode was defined for the EEG stop if no MEG to compute... + % errordlg('Please make sure you have defined a reference for the EEG') + % return + % elseif isempty(EEGREFndx) & MEG % Skip EEG forward modeling and proceed to MEG + % EEG = 0; + % if OPTIONS.Verbose + % bst_message_window({... + % 'No reference was defined for the EEG',... + % 'Skipping EEG modeling and completing MEG only...',... + % }) + % end + % end + +end + +if OPTIONS.Verbose, + if EEG & MEG + if ~isempty(EEGREFndx) + bst_message_window({'Channel count for current computation:',[int2str(length(MEGndx)), ' MEG Channels'],... + sprintf('%d EEG Channels with Reference: %s',length(EEGndx),Channel(EEGREFndx).Name),' '}) + else + bst_message_window({'Channel count for current computation:',[int2str(length(MEGndx)), ' MEG Channels'],... + sprintf('%d EEG Channels with Average Reference',length(EEGndx)),' '}) + end + + elseif MEG + bst_message_window({'Channel count for current computation:',[int2str(length(MEGndx)), ' MEG Channels'],... + ' '}) + elseif EEG + if ~isempty(EEGREFndx) + bst_message_window({'Channel count for current computation:',... + sprintf('%d EEG Channels with Reference: %s',length(EEGndx),Channel(EEGREFndx).Name),' '}) + else + bst_message_window({'Channel count for current computation:',... + sprintf('%d EEG Channels with Average Reference',length(EEGndx)),' '}) + end + + end +end + +if MEG & isempty(MEGndx) % User wants to compute MEG but no MEG data is available + errordlg('Sorry - No MEG data is available'), return +end + +if EEG & isempty(EEGndx) % User wants to compute EEG but no EEG data is available + errordlg('Sorry - No EEG data is available'), return +end + + +% Computation of parameters of the best-fitting sphere -------------------------------------------------------------------------------------------------------------- +if length(findstr('bem',[OPTIONS.Method{:}])) ~= length(OPTIONS.Method)% Only if sphere-based head model is requested in any modality (MEG or EEG) + + % Best-fitting sphere parameters -------------------------------------------------------------------------------------------------------------- + if isempty(OPTIONS.HeadCenter) | isempty(OPTIONS.Radii) + + if OPTIONS.Verbose, bst_message_window('Estimating Center of the Head. . .'), end + + if isempty(OPTIONS.Scalp) % Best-fitting sphere is derived from the sensor array + % ans = questdlg('No scalp surface was selected - Do you want to use a spherical approximation of the head derived from the sensor locations instead ?',... + % '','Yes','No','Yes'); + if EEG & length(EEGndx) > 9 % If EEG is available but a reasonable number of electrodes, use it to compute the sphere parameters + ndx = [EEGndx]; % Compute Sphere parameters from EEG sensors only + else + ndx = [MEGndx]; + end + + Vertices = zeros(length(ndx),3); + for k=1:length(ndx) + Vertices(k,:) = Channel(ndx(k)).Loc(:,1)'; + end + + else % Best-fitting sphere is computed from the set of vertices of the scalp surface enveloppe + + try + load(fullfile(User.SUBJECTS,OPTIONS.Scalp.FileName),'Vertices') + catch + load(OPTIONS.Scalp.FileName,'Vertices') + end + + if ~isfield(OPTIONS.Scalp,'iGrid') % Apply Default + OPTIONS.Scalp.iGrid = 1; + end + + try + Vertices = Vertices{OPTIONS.Scalp.iGrid}'; + catch + errordlg(sprintf(... + 'Tessellation file %s does not contain %d enveloppes',... + OPTIONS.Scalp.FileName,OPTIONS.Scalp.iGrid)) + end + end + + nscalp = size(Vertices,1); + nmes = size(Vertices,1); + + % Run parameters fit -------------------------------------------------------------------------------------------------------------- + mass = mean(Vertices); % center of mass of the scalp vertex locations + R0 = mean(norlig(Vertices - ones(nscalp,1)*mass)); % Average distance between the center of mass and the scalp points + vec0 = [mass,R0]; + + [SphereParams,brp] = fminsearch('dist_sph',vec0,[],Vertices); + + + if OPTIONS.Verbose + bst_message_window({... + sprintf('Center of the Sphere : %3.1f %3.1f %3.1f (cm)',100*SphereParams(1:end-1)'),... + sprintf('Radius : %3.1f (cm)',100*SphereParams(end)),... + '-> DONE',' '}) + + end + + end % no head center and sphere radii were specified by user + + % Assign default values for sphere parameters + % if none were specified before + if isempty(OPTIONS.Radii) + OPTIONS.Radii = SphereParams(end)*[1/1.14 1/1.08 1]; + end + + if isempty(OPTIONS.HeadCenter) + OPTIONS.HeadCenter = SphereParams(1:end-1)'; + end + + if isempty(OPTIONS.Conductivity) + OPTIONS.Conductivity = [.33 .0042 .33]; + end + +end % Use BEM Approach, so proceed to the selection of the envelopes + +% --------------------------------------------------------------------------------------------------------- +%Create HeadModel Param structure +Param(1:length(Channel)) = deal(struct('Center',[],'Radii',[],'Conductivity',[],'Berg',[])); + +if ~isempty(findstr('os',[OPTIONS.Method{:}])) % Overlapping-Sphere EEG or MEG is requested: load the scalp's tessellation + + if isempty(OPTIONS.Scalp) + errordlg('Please specify a subject tessellation file for Scalp enveloppe in OPTIONS.Scalp when using Overlapping-Sphere forward approach'); + return + end + + % load(fullfile(User.SUBJECTS,BrainStorm.SubjectTess),'Faces'); + load(fullfile(User.SUBJECTS,OPTIONS.Scalp.FileName),'Faces','Vertices'); + + Faces = Faces{OPTIONS.Scalp.iGrid}; + Vertices = Vertices{OPTIONS.Scalp.iGrid}'; + + if size(Vertices,1) > ReducePatchScalpNVerts % Reducepatch the scalp tessellation for fastest OS computation + nfv = reducepatch(Faces,Vertices,2*ReducePatchScalpNVerts); + if OPTIONS.Verbose, bst_message_window({... + sprintf('Decimated scalp tessellation from %d to %d vertices.',size(Vertices,1),size(nfv.vertices,1)),... + ' '}); + end + clear Faces Vertices + SubjectFV.vertices = nfv.vertices'; + SubjectFV.faces = nfv.faces; + clear nfv + else + SubjectFV.vertices = Vertices; % Available from the computation of the head center above + clear Vertices + SubjectFV.faces = Faces; clear Faces + end + + if length(findstr('os',[OPTIONS.Method{:}])) == 2 % OS approach requested fro both MEG and EEG + ndx = [MEGndx,EEGndx]; + + else + if MEG + if strcmpi('meg_os',OPTIONS.Method{DataType.MEG}) % OS for MEG only + ndx = MEGndx; + end + end + + if EEG + if strcmpi('eeg_os',OPTIONS.Method{DataType.EEG}) % OS for EEG only + ndx = [EEGndx, EEGREFndx]; + end + end + end + if OPTIONS.Verbose + bst_message_window({... + ' ', 'Computing Overlapping-sphere model. . .'}) + end + + if isempty(OPTIONS.HeadModelFile) | OPTIONS.OS_ComputeParam + + Sphere = overlapping_sphere(Channel(ndx),SubjectFV,OPTIONS.Verbose,OPTIONS.Verbose); % Compute all spheres parameters + if OPTIONS.Verbose + bst_message_window({... + 'Computing Overlapping-sphere model -> DONE',' '}) + end + [Param(ndx).Center] = deal(Sphere.Center); + [Param(ndx).Radii] = deal(Sphere.Radius); + [Param(ndx).Conductivity] = deal(OPTIONS.Conductivity); + + elseif exist(OPTIONS.HeadModelFile,'file') + + load(OPTIONS.HeadModelFile,'Param') % or use precomputed + if OPTIONS.Verbose + bst_message_window({... + sprintf('Sphere parameters loaded from : %s -> DONE',OPTIONS.HeadModelFile),' '}) + end + + else + + errordlg(sprintf('Headmodel file %s does not exist in Matlab''s search path',OPTIONS.HeadModelFile)) + return + + end + +end + +% Saving HeadModel's Param structure in the headmodel file +if strcmp(lower(OPTIONS.HeadModelFile),'default') + if ~isfield(OPTIONS,'rooot') + OPTIONS.rooot = OPTIONS.PrefixFileName; + end + OPTIONS.HeadModelFile = [OPTIONS.rooot,'headmodel.mat']; + ifile = 0; + while exist(OPTIONS.HeadModelFile,'file') % Do not overwrite headmodel files + ifile = ifile + 1; + OPTIONS.HeadModelFile = [OPTIONS.rooot,'headmodel_',int2str(ifile),'.mat']; + end + +elseif ~isfield(OPTIONS,'rooot') & ~isempty(OPTIONS.HeadModelFile) + OPTIONS.rooot = strrep(OPTIONS.HeadModelFile,'headmodel.mat',''); +end +if ~isfield(OPTIONS,'rooot') % Last Chance + OPTIONS.rooot = 'bst_'; +end + +if ~isempty(findstr('sphere',[OPTIONS.Method{:}])) % Single or nested-sphere approaches + + [Param([MEGndx, EEGndx, EEGREFndx]).Center] = deal(OPTIONS.HeadCenter); + [Param([MEGndx, EEGndx, EEGREFndx]).Radii] = deal(OPTIONS.Radii); + [Param([MEGndx, EEGndx, EEGREFndx]).Conductivity] = deal(OPTIONS.Conductivity); + + if EEG & strcmpi('eeg_3sphereberg',lower(OPTIONS.Method{DataType.EEG})) % BERG APPROACH + + if ~isempty(OPTIONS.HeadModelFile) & exist(OPTIONS.HeadModelFile,'file') + if OPTIONS.Verbose, bst_message_window('Checking for previous EEG "BERG" parameters'), end + ParamOld = load(OPTIONS.HeadModelFile,'Param'); + iFlag = 0; % Flag + if isfield(ParamOld.Param,'Berg') & ~isempty(ParamOld.Param(1).Radii) + % Check if these older parameters were computed with the same as current radii and conductivity values + if ParamOld.Param(1).Radii ~= Param(1).Radii; + iFlag = 1; + end + if ParamOld.Param(1).Conductivity ~= Param(1).Conductivity; + iFlag = 1; + end + else + iFlag = 1; + end + else + iFlag = 1; + end + + if iFlag == 1 + if OPTIONS.Verbose , bst_message_window('Computing EEG "BERG" Parameters. . .'), end + [mu_berg_tmp,lam_berg_tmp] = berg(OPTIONS.Radii,OPTIONS.Conductivity); + Param(EEGndx(1)).Berg.mu = mu_berg_tmp; clear mu_berg_tmp + Param(EEGndx(1)).Berg.lam = lam_berg_tmp; clear lam_berg_tmp + if OPTIONS.Verbose, bst_message_window({'Computing EEG "BERG" Parameters -> DONE',' '}), end + else + if OPTIONS.Verbose , bst_message_window('Using Previous EEG "BERG" Parameters'), end + Param(EEGndx(1)).Berg.mu = ParamOld.Param(1).Berg.mu; + Param(EEGndx(1)).Berg.lam = ParamOld.Param(1).Berg.lam; clear ParamOld + end + [Param.Berg]= deal(Param(EEGndx(1)).Berg); + + end + +end + +if ~isempty(findstr('bem',[OPTIONS.Method{:}])) % BEM approaches - Compute transfer matrices + + if OPTIONS.BEM.Interpolative==0 & OPTIONS.VolumeSourceGrid %& isempty(OPTIONS.Cortex) % User wants volumic grid : force Interpolative approach + OPTIONS.BEM.Interpolative = 1; % CBB (SB, 07-May-2004)| Should work also for volumic grid + hwarn = warndlg('Volumic Source Grid BEM is only available for interpolative BEM. BEM computation will be now forced to interpolative. If you want a non-interpolative BEM on a cortical image grid, first uncheck the Volumic Grid box from headmodeler gui.','Limitation from current BrainStorm version'); + drawnow + waitfor(hwarn) + end + + if MEG + if ~isempty(findstr('bem',OPTIONS.Method{DataType.MEG})) % BEM is requested for MEG + BEMChanNdx{DataType.MEG} = MEGndx; + end + end + + if EEG + if ~isempty(findstr('bem',OPTIONS.Method{DataType.EEG})) % BEM is requested for EEG + BEMChanNdx{DataType.EEG} = sort([EEGndx,EEGREFndx]); % EEGREFndx = [] is average ref + end + end + + OPTIONS.Param = Param; + + if OPTIONS.BEM.Interpolative % Computation of gain matrix over 3D interpolative grid + if OPTIONS.BEM.ForceXferComputation + BEMGaingridFileName = bem_GainGrid(DataType, OPTIONS, BEMChanNdx); % Computation of the BEM gain matrix on the 3D interpolative grid for MEG and/or EEG data + else + try + BEMGaingridFileName = OPTIONS.BEM.GaingridFileName; + catch + cd(User.STUDIES) + BEMGaingridFileName = bem_GainGrid(DataType, OPTIONS, BEMChanNdx); % Computation of the BEM gain matrix on the 3D interpolative grid for MEG and/or EEG data + end + end + + if MEG & EEG + [Param(MEGndx).bem_gaingrid_mfname] = deal(BEMGaingridFileName.MEG); + [Param([EEGndx]).bem_gaingrid_mfname] = deal(BEMGaingridFileName.EEG); + else + [Param(:).bem_gaingrid_mfname] = deal(BEMGaingridFileName); + end + + else % BEM gain matrix computation over cortical surface + + + BEMGaingridFileName = bem_GainGrid(DataType, OPTIONS, BEMChanNdx); + + [Param(:).bem_gaingrid_mfname] = deal(''); + + end + + + OPTIONS = rmfield(OPTIONS,'Param'); + + ndx = sort([BEMChanNdx{:}]); + + + [Param(ndx).Center] = deal([]); + test=0; % this test is nowhere defined ? + + if OPTIONS.VolumeSourceGrid + % Now define the outer scalp envelope for the source volume gridding if requested + + if OPTIONS.BEM.ForceXferComputation | ~test + global nfv + SubjectFV.vertices = nfv(end).vertices'; + SubjectFV.faces = nfv(end).faces; clear Faces + else + load(fullfile(User.SUBJECTS,fileparts(OPTIONS.Subject),OPTIONS.BEM.EnvelopeNames{end}.TessFile)) + + idScalp = find(strcmpi(OPTIONS.BEM.EnvelopeNames{end}.TessName,Comment)); clear Comment + if isempty(idScalp) + errodlg(sprintf(... + 'Scalp tessellation %s was not found in %s.',OPTIONS.BEM.EnvelopeNames{end}.TessName, OPTIONS.BEM.EnvelopeNames{end}.TessFile),... + 'Error during BEM computation') + return + end + + SubjectFV.vertices = Vertices{idScalp}'; clear Vertices + SubjectFV.faces = Faces{idScalp}; clear Faces + end + + end + +end % if BEM + +if EEG + + switch(lower(OPTIONS.Method{DataType.EEG})) + case 'eeg_sphere' + [Param(EEGndx).EEGType] = deal('EEG_SINGLE'); + case 'eeg_3sphere' + [Param(EEGndx).EEGType] = deal('EEG_3SHELL'); + case 'eeg_3sphereberg' + [Param(EEGndx).EEGType] = deal('EEG_BERG'); + case 'eeg_os' + [Param(EEGndx).EEGType] = deal('EEG_OS'); + case 'eeg_bem' + [Param(EEGndx).EEGType] = deal('BEM'); + if EEG & ~MEG + [Param(EEGndx).bem_gaingrid_mfname] = deal(BEMGaingridFileName); + end + end + +else + + tmp = []; + [Param.Conductivity] = deal(tmp); + [Param.Berg] = deal(tmp); + [Param.EEGType] = deal(tmp); + +end + + +%_________________________________________________________________________________________________________________________________________________________________________ +% +% The following part is an adaptation of the former HEADMODEL_MAKE script +% +% ________________________________________________________________________________________________________________________________________________________________________ + + +% Allocate function names depending on the selected forward approaches specified in the Method argument +Function = cell(1,length(Channel)); %allocate function names + +if MEG + if ~isempty(findstr('bem',OPTIONS.Method{DataType.MEG})) % MEG BEM + Function(MEGndx) = deal({'meg_bem'}); + if isfield(Channel(MEGndx(1)),'irefsens') + Function(Channel(MEGndx(1)).irefsens) = deal({'meg_bem'}); + end + + else + Function(MEGndx) = deal({'os_meg'}); + if isfield(Channel(MEGndx(1)),'irefsens') + Function(Channel(MEGndx(1)).irefsens) = deal({'os_meg'}); + end + if isfield(OPTIONS,'BEM') + OPTIONS = rmfield(OPTIONS,'BEM'); + end + + end +end + + +if EEG + if ~isempty(findstr('bem',OPTIONS.Method{DataType.EEG})) % MEG BEM + Function(EEGndx) = deal({'eeg_bem'}); + else + Function(EEGndx) = deal({'eeg_sph'}); + if isfield(OPTIONS,'BEM') + OPTIONS = rmfield(OPTIONS,'BEM'); + end + end + +end +% -------------------------------------------------------------------------------------------------------- + +DIMS = [3 12]; % number of columns for each parametric source model: Current Dipole / Current Multipole +HeadModel.Param = Param; +HeadModel.Function = Function; + +if OPTIONS.VolumeSourceGrid % if SearchGain matrices are requested + + SearchGain = cell(1,6); % One cell for each source order + pairs of sync. sources - keep 2 'phantom' cells for former Magnetic Dipole model + + %------------------------------ Computing SearchGridLoc and SearchGain------------------------------------------- + + if ~isempty(findstr(MegMethod,'bem')) | (EEG & ~MEG) + if OPTIONS.Verbose, bst_message_window(... + {'Forward modeling not available for EEG''s Current Mulipole models or the BEM approach','Computing forward fields for Current Dipole models. . .'... + ,' '}), end + %return + OPTIONS.SourceModel = [-1]; % Force current dipole source model when MEG BEM or any EEG is requested + end + + if OPTIONS.Verbose, bst_message_window(... + 'Computing the Gain Matrices from the Volumic Search Grid. . .'), end + + % Prepare call to source_grids + + GUI.VALIDORDER = OPTIONS.SourceModel; + GUI.SPACING = 10*OPTIONS.VolumeSourceGridSpacing; % Pass it to source_grids in millimeters + GUI.VERBOSE = OPTIONS.Verbose; + GUI.MEG = MEG; + GUI.EEG = EEG; + + if ~exist('SubjectFV','var') + % Create a spherical tessellation of the INNER SKULL surface boundary for source gridding + + [x,y,z] = sphere(30); + %Scale to Radius BrainStorm.R + [TH,PHI,R] = cart2sph(x,y,z); + if OPTIONS.Radii(1) == 0 + error('The sphere radius you have specified is ZERO - please change it to a non null positive value') + end + R = OPTIONS.Radii(1) * ones(size(R)); + [x,y,z] = sph2cart(TH,PHI,R); + + % Tessellate both hemispheres + Im = find(z>=0); + tri = delaunay(x(Im),y(Im)); + + %Gather everything into the same patch + SubjectFV.vertices = [x(Im),y(Im),z(Im);... + x(Im),y(Im),-z(Im)]; + + % Translate about the head center + SubjectFV.vertices = SubjectFV.vertices + repmat(OPTIONS.HeadCenter',size(SubjectFV.vertices,1),1); + + SubjectFV.faces = [tri;tri(:,[3 2 1])+max(tri(:))]; + + clear x y z R TH PHI Im tri + + end + + HeadModel = source_grids(HeadModel,Channel,SubjectFV,GUI); + clear SubjectFV + + if OPTIONS.Verbose, bst_message_window({... + '-> DONE',... + 'Now saving HeadModel file. . .'... + }) + end + + % Now save VolumeSourceGrid headmodel(s) + SaveHeadModel.Param = HeadModel.Param; + SaveHeadModel.Function = HeadModel.Function; + + if strcmpi(OPTIONS.HeadModelName,'Default') % Specify default HeadModelName + if MEG & EEG + SaveHeadModel.HeadModelName = sprintf('%s | %s', OPTIONS.Method{DataType.MEG},OPTIONS.Method{DataType.EEG}); + elseif MEG + SaveHeadModel.HeadModelName = sprintf('%s', OPTIONS.Method{DataType.MEG}); + elseif EEG + SaveHeadModel.HeadModelName = sprintf('%s', OPTIONS.Method{DataType.EEG}); + end + else + SaveHeadModel.HeadModelName = OPTIONS.HeadModelName; + end + + if ~isempty(OPTIONS.HeadModelFile), + [HeadModelFilePath,HeadModelFile,ext] = fileparts(OPTIONS.HeadModelFile); + end + + try + cd(User.STUDIES) + catch + end + + for k = 1:length(GUI.VALIDORDER) % One file per source order + + % Specify Source Model Name and Other Fields + if GUI.VALIDORDER(k) == -1 + SourceOrderString = 'CD'; % Current Dipole + elseif GUI.VALIDORDER(k) == 0 + SourceOrderString = 'MD'; % Magnetic Dipole + elseif GUI.VALIDORDER(k) == 1 + SourceOrderString = 'CME'; % Current Multipole + end + + [SaveHeadModel.Param.Order] = deal(GUI.VALIDORDER(k)); + SaveHeadModel.SourceOrder = GUI.VALIDORDER(k); % Alternative to previous line + SaveHeadModel.HeadModelType = 'SearchGrid'; + if MEG + SaveHeadModel.MEGMethod = OPTIONS.Method{DataType.MEG}; + end + if EEG + SaveHeadModel.EEGMethod = OPTIONS.Method{DataType.EEG}; + end + + SaveHeadModel.GridName = {sprintf('%s Volumic Grid : %3.1f cm spacing',SourceOrderString, OPTIONS.VolumeSourceGridSpacing)}; + SaveHeadModel.GridLoc = HeadModel.GridLoc(GUI.VALIDORDER(k)+2); + SaveHeadModel.Gain = {single(HeadModel.Gain{GUI.VALIDORDER(k)+2})}; % Convert to single precision gain matrix as in BST MMII convention + + % now collect together and save + if ~isempty(OPTIONS.HeadModelFile), % User has provided a headmodel file name + SaveHeadModelFile = fullfile(HeadModelFilePath,[HeadModelFile,... + sprintf('VolGrid_%s',SourceOrderString),... + ext]); + ifile = 0; + while exist(SaveHeadModelFile,'file') % Do not overwrite headmodel files + ifile = ifile + 1; + SaveHeadModelFile = fullfile(HeadModelFilePath,[HeadModelFile,... + sprintf('VolGrid_%s',SourceOrderString),... + '_',int2str(ifile),ext]); + end + + + if OPTIONS.Verbose, bst_message_window({... + sprintf('Writing HeadModel file:'),... + sprintf('%s', SaveHeadModelFile)... + }) + end + + if strcmp(lower(OPTIONS.HeadModelFileOld),'default') + OPTIONS.HeadModelFile = SaveHeadModelFile; + end + + save_fieldnames(SaveHeadModel, SaveHeadModelFile); + + if OPTIONS.Verbose, bst_message_window(... + {'-> DONE',' '}), end + end + + end + + OPTIONS.HeadModelName = SaveHeadModel.HeadModelName; + + if OPTIONS.Verbose, bst_message_window(... + {'Grid gain matrices are saved','RAP-MUSIC & Least-Squares Fits Approaches are now available',' '}) + end + +end + + +%% ------------------------------------------------------------------------- +% +% Now proceed to forward modeling of cortical grids or at some other specific source locations +% +%% ------------------------------------------------------------------------- + + +if ~isempty(OPTIONS.Cortex), % subject has cortical vertices as source supports + % First make room in memory + HeadModel.SearchGain = []; + clear SearchGridLoc SearchGain G + + if OPTIONS.Verbose, bst_message_window({... + 'Computing the Image Gain Matrices (this may take a while). . .'}) + end + + % Find the cortical grid where to compute the forward model in the tessellation file + try + ImageGrid = load(fullfile(User.SUBJECTS,OPTIONS.Cortex.FileName),'Comment','Vertices'); + catch + ImageGrid = load(OPTIONS.Cortex.FileName,'Comment','Vertices'); + end + + if ~isfield(OPTIONS.Cortex,'iGrid') + OPTIONS.Cortex.iGrid = 1; + end + + if OPTIONS.Cortex.iGrid > length(ImageGrid.Comment) % Corresponding cortical surface not found + errordlg(sprintf('Cortical Tessellation file "%s" does not contain %d surfaces',... + OPTIONS.Cortex.FileName,OPTIONS.Cortex.iGrid)) + return + end + + GridLoc = ImageGrid.Vertices{OPTIONS.Cortex.iGrid}; % keep only the desired cell + ImageGrid = rmfield(ImageGrid,'Vertices'); + +elseif ~isempty(OPTIONS.SourceLoc) % Specific source locations are provided + if size(OPTIONS.SourceLoc,1) ~= 3 + OPTIONS.SourceLoc = OPTIONS.SourceLoc'; + end + GridLoc = OPTIONS.SourceLoc; + +else + rmfield(OPTIONS,'rooot'); + if nargout == 1 + varargout{1} = OPTIONS; + else + varargout{1} = HeadModel; + varargout{2} = OPTIONS; + end + return % No ImageGrid requested + +end + +if ~isempty(OPTIONS.Cortex) + GridName{OPTIONS.Cortex.iGrid} = ImageGrid.Comment{OPTIONS.Cortex.iGrid}; + OPTIONS.Cortex.Name = ImageGrid.Comment{OPTIONS.Cortex.iGrid}; +end + +%for i = 1 % CHEAT - Dipoles only here +for Order = OPTIONS.SourceModel % Compute gain matrices for each requested source models (-1 0 1) + + i = 1; % Index to cell in headmodel cell arrays (MMII convention) + + switch(Order) + case -1 + SourceOrderString = 'CD'; % Current Dipole + Dims = DIMS(1);% number of columns per source + case 0 + errordlg(sprintf('Unauthorized Source Model Order %d.',iSrcModel),'Wrong HeadModel parameter assignment') + return + %SourceOrderString = 'MD'; % Magnetic Dipole - OBSOLETE + case 1 + SourceOrderString = 'CME'; % Current Multipole + Dims = DIMS(2);% number of columns per source + otherwise + errordlg(sprintf('Unauthorized Source Model Order %d.',iSrcModel),'Wrong HeadModel parameter assignment') + return + end + + if OPTIONS.Verbose, + bst_message_window(... + 'wrap',... + sprintf('Computing Gain Matrix for the %s Model',SourceOrderString)) + h = waitbar(0,sprintf('Computing Gain Matrix for the %s Model',SourceOrderString)); + pos = get(h,'position'); + end + + if ~isempty(OPTIONS.Cortex) & OPTIONS.ApplyGridOrient % Use cortical grid + try + load(OPTIONS.Cortex.FileName,'Faces'); + catch + Users = get_user_directory; + load(fullfile(Users.SUBJECTS,OPTIONS.Cortex.FileName),'Faces'); + end + + Faces = Faces{OPTIONS.Cortex.iGrid}; + + ptch = patch('Vertices',GridLoc','Faces',Faces,'Visible','off'); + set(get(ptch,'Parent'),'Visible','off') + clear Faces + GridOrient{i} = get(ptch,'VertexNormals')'; % == {1} as of MMII conventions. Most data in HeadModel files are single-cell cell arrays. + % Cell array structure kept for backward compatibility with BsT2000. + delete(ptch); + + end + + if OPTIONS.ApplyGridOrient % Take cortex normals into account + if isempty(OPTIONS.GridOrient) & isempty(OPTIONS.SourceOrient) % Consider the cortical patch's normals + if isempty(OPTIONS.Cortex) % Specific source locations in .SourceLoc but nohing in. SourceOrient + OPTIONS.ApplyGridOrient = 0; % No source orientation specified: Force computation of full gain matrix + else + [nrm,GridOrient{i}] = colnorm(GridOrient{i}); + % Now because some orientations may be ill-set to [0 0 0] with the get(*,'VertexNormals' command) + % set these orientation to arbitrary [1 1 1]: + izero = find(nrm == 0);clear nrm + if ~isempty(izero) + GridOrient{i}(:,izero) = repmat([1 1 1]'/norm([1 1 1]),1,length(izero)); + end + clear izero + + end + + elseif ~isempty(OPTIONS.GridOrient) % Apply user-defined cortical source orientations + + if size(OPTIONS.GridOrient,2) == size(GridLoc,2) % Check size integrity + GridOrient{i} = OPTIONS.GridOrient; + [nrm,GridOrient{i}] = colnorm(GridOrient{i}); + clear nrm + else + errordlg(sprintf('The source orientations you have provided are for %0.f sources. Considered cortical surface has %0.f sources. Computation aborted',... + size(OPTIONS.GridOrient,2),size(GridOrient{i},2))); + return + end + + elseif ~isempty(OPTIONS.SourceOrient) % Apply user-defined specific source orientations + + if size(OPTIONS.SourceOrient,2) == size(GridLoc,2) % Check size integrity + GridOrient{i} = OPTIONS.SourceOrient; + [nrm,GridOrient{i}] = colnorm(GridOrient{i}); + clear nrm + else + errordlg(sprintf('The source orientations you have provided are for %0.f sources. Computation aborted',... + size(OPTIONS.SourceOrient,2))); + return + end + + end + end + + nv = size(GridLoc,2); % number of grid points + + if ~isempty(OPTIONS.ImageGridFile) % Save cortical gain matrix in a binary file + + [PATH,NAME,EXT,VER] = fileparts(OPTIONS.HeadModelFile); + + if strcmp(lower(OPTIONS.ImageGridFile),'default') + if exist('GridName','var') % Cortical support was specified + destname = [NAME,'_Gain_',strrep(ImageGrid.Comment{OPTIONS.Cortex.iGrid},' ',''),'_',SourceOrderString,'.bin']; % New naming (March, 19 - 2002) + k = 1; + try + while exist(destname,'file') % Don't write over existing .bin gain matrix file + destname = [NAME,'_Gain_',strrep(ImageGrid.Comment{OPTIONS.Cortex.iGrid},' ',''),'_',SourceOrderString,'_',int2str(k),'.bin']; % New naming (March, 19 - 2002) + k = k+1; + end + catch + while exist(destname,'file') % Don't write over existing .bin gain matrix file + destname = [NAME,'_Gain_',strrep(ImageGrid.Comment{OPTIONS.Cortex.iGrid},' ',''),'_',SourceOrderString,'_',int2str(k),'.bin']; % New naming (March, 19 - 2002) + k = k+1; + end + end + + clear k + + else % Specific location was provided in .SourceLoc + destname = [NAME,'_Gain_SpecLoc_',SourceOrderString,'.bin']; % Specific location was provided in .SourceLoc + k = 1; + while exist(destname,'file') % Don't write over existing .bin gain matrix file + destname = [NAME,'_Gain_SpecLoc_',SourceOrderString,'_',int2str(k),'.bin']; + k = k+1; + end + clear k + end + OPTIONS.ImageGridFile = destname; + else + [PATH,destname,ext,ver] = fileparts(OPTIONS.ImageGridFile); + destname = [destname, ext]; + end + + % Check whether this name exists - if yes, don't overwrite + try + cd(fullfile(User.STUDIES,PATH)) + catch + cd(PATH) + end + + hdml = 1; + while exist(destname,'file') + if hdml == 1 + destname = strrep(destname,'.bin',['_',int2str(hdml),'.bin']); + else + destname = strrep(destname,[int2str(hdml-1),'.bin'],[int2str(hdml),'.bin']); + end + + hdml = hdml+1; + end + clear hdml + + fdest = fopen(destname,'w','ieee-be'); + if fdest < 0 + errordlg('Error creating the file for the cortical image forward model; Please check for disk space availability') + return + end + frewind(fdest); + + fwrite(fdest,length([OPTIONS.Channel]),'uint32'); + + % % BEM and non-interpolative, store gain matrix already computed + % if isfield(OPTIONS,'BEM') % BEM computation + % if ~OPTIONS.BEM.Interpolative + % global GBEM_grid + % end + % end + + if fdest < 0 , errordlg('Please Check Write Permissions', ['Cannot create ',destname]), return, end + else % If no ImageGridFile was specified + %OPTIONS.ImageGridBlockSize = nv; % Force a one-time computation of all source forward fields + end + + src_ind = 0; + jj = 0; % Number of OPTIONS.ImageGridBlockSize + + + for j = 1:(OPTIONS.ImageGridBlockSize):nv, + jj = jj+1; + if 1%OPTIONS.ApplyGridOrient + ndx = [0:OPTIONS.ImageGridBlockSize-1]+j; + else + ndx = [0:3*OPTIONS.ImageGridBlockSize-1]+j; + end + + if 1%OPTIONS.ApplyGridOrient + if(ndx(end) > nv), % last OPTIONS.ImageGridOPTIONS.ImageGridBlockSizeSize too long + ndx = [ndx(1):nv]; + end + else + if(ndx(end) > 3*nv), + ndx = [ndx(1):3*nv]; + end + end + + % Compute MEG + if MEG & ~isempty(MEGndx) + Gmeg = NaN*zeros(length(MEGndx),Dims*length(ndx)); + if ~isempty(Function{MEGndx(1)}) + + if MEG & EEG + clear('gain_bem_interp2'); % Free persistent variables to avoid confusion + end + + if isfield(OPTIONS,'BEM') % BEM computation + if ~OPTIONS.BEM.Interpolative % ~interpolative : retrieve stored gain matrix + global GBEM_grid + tmpndx = [3*(ndx(1)-1)+1:min([3*nv,3*ndx(end)])];%[0:3*OPTIONS.ImageGridBlockSize-1]+j; + Gmeg = GBEM_grid(MEGndx,tmpndx); + end + else + Gmeg = feval(Function{MEGndx(1)},GridLoc(:,ndx),Channel,Param,Order,OPTIONS.Verbose); + end + + end + else + Gmeg = []; + end + + if EEG & ~isempty(EEGndx) & i==1 % % Order -1 only for now in EEG + + Geeg = NaN*zeros(length(EEGndx),Dims*length(ndx)); + if ~isempty(Function{EEGndx(1)}) + if MEG & EEG + clear('gain_bem_interp2'); % Free persistent variables to avoid confusion + end + + if isfield(OPTIONS,'BEM') % BEM computation + if ~OPTIONS.BEM.Interpolative % ~interpolative : retrieve stored gain matrix + global GBEM_grid + %Geeg = GBEM_grid(EEGndx,[j:min([3*nv,j+3*OPTIONS.ImageGridBlockSize-1])]); + tmpndx = [3*(ndx(1)-1)+1:min([3*nv,3*ndx(end)])];%[0:3*OPTIONS.ImageGridBlockSize-1]+j; + %min(tmpndx ), max(tmpndx) + Geeg = GBEM_grid(EEGndx,tmpndx); + end + else + Geeg = feval(Function{EEGndx(1)},GridLoc(:,ndx),Channel,Param,Order,OPTIONS.Verbose); + end + + end + else + Geeg = []; + end + + if OPTIONS.ApplyGridOrient + G = NaN*zeros(length(OPTIONS.Channel),length(ndx)); + else + G = NaN*zeros(length(OPTIONS.Channel),Dims*length(ndx)); + end + + if OPTIONS.Verbose, bst_message_window(... + ['Computing Cortical Gain Vectors. . . Block # ',int2str(jj),... + ' of ',int2str(length(1:OPTIONS.ImageGridBlockSize:nv))]) + + hh = waitbar(0,['Computing Cortical Gain Vectors. . . Block # ',int2str(jj),... + ' of ',int2str(length(1:OPTIONS.ImageGridBlockSize:nv))]); + set(hh,'Position',[pos(1), pos(2)+pos(4),pos(3),pos(4)]) + drawnow + end + + src = 0; + % Options on cortical source orientation + if OPTIONS.ApplyGridOrient % Apply source orientation + + for k = 1:Dims:Dims*length(ndx)-2 + src = src+1; + src_ind = src_ind+1; + if ~isempty(Gmeg) + G(MEGndx,src) = Gmeg(:,k:k+2) * GridOrient{1}(:,src_ind); + end + + if ~isempty(Geeg)&(i==1)% % Order -1 only + G(EEGndx,src) = Geeg(:,k:k+2) * GridOrient{1}(:,src_ind); + end + if OPTIONS.Verbose, + if ~rem(src,5000) + waitbar(src/length(1:Dims:Dims*length(ndx)-2),hh) + end + end + + end + + else % Do not apply cortical orientation. Keep full gain matrix at each cortical location + + if MEG + G(MEGndx,:) = Gmeg; + end + if EEG + G(EEGndx,:) = Geeg; + end + + end + + if OPTIONS.Verbose, + if ~rem(src_ind,1000) + waitbar(src_ind/nv,h) + end + + delete(hh) + end + + + if ~isempty(OPTIONS.ImageGridFile) + % 4 bytes per element, find starting point + if OPTIONS.ApplyGridOrient % Apply source orientation + offset = 4 + (ndx(1)-1)*length(Channel)*4; + else + offset = 4 + 3*(ndx(1)-1)*length(Channel)*4; + end + + % Matlab 6.5.0 bug, Technical Solution Number: 1-19NOK + % http://www.mathworks.com/support/solutions/data/1-19NOK.html?solution=1-19NOK + % must REWIND first before fseek. + % Apparently fixed in 6.5.1 + % JCM 27-May-2004 + status = fseek(fdest,0,'bof'); + status = fseek(fdest,offset,'bof'); + if(status == -1), + errordlg('Error writing Image Gain Matrix file'); return + end + + fwrite(fdest,G,'float32'); + elseif nargout>1 + if ~exist('GG', 'var') + if OPTIONS.ApplyGridOrient + GG = NaN*zeros(length(OPTIONS.Channel),nv); + else + GG = NaN*zeros(length(OPTIONS.Channel),Dims*nv); + end + end + if MEG + GG(MEGndx,ndx) = G(MEGndx,:); + end + if EEG + GG(EEGndx,ndx) = G(EEGndx,:); + end + + end + + clear Gmeg Geeg + + end + if exist('destname','var') & ~isempty(OPTIONS.Cortex) + Gain{OPTIONS.Cortex.iGrid}{i} = destname; + OPTIONS.ImageGridFile = destname; + end + + if OPTIONS.Verbose + close(h) + end + + if ~isempty(OPTIONS.ImageGridFile) + fclose(fdest); + if OPTIONS.Verbose, bst_message_window({... + sprintf('Computing Gain Matrix for the %s Model -> DONE',SourceOrderString),... + sprintf('Saved in:'),... + sprintf('%s',destname)... + }), + end + end + + + % Now save ImageGrid headmodel(s)----------------------------------------------------------------------------------- + OPTIONS.HeadModelFile = OPTIONS.HeadModelFileOld; % Use original HeadModelFile entry + if strcmp(lower(OPTIONS.HeadModelFile),'default') + if ~isfield(OPTIONS,'rooot') + OPTIONS.rooot = OPTIONS.PrefixFileName; + end + OPTIONS.HeadModelFile = [OPTIONS.rooot,'headmodel.mat']; + ifile = 0; + while exist(OPTIONS.HeadModelFile,'file') % Do not overwrite headmodel files + ifile = ifile + 1; + OPTIONS.HeadModelFile = [OPTIONS.rooot,'headmodel_',int2str(ifile),'.mat']; + end + + elseif ~isfield(OPTIONS,'rooot') & ~isempty(OPTIONS.HeadModelFile) + OPTIONS.rooot = strrep(OPTIONS.HeadModelFile,'headmodel.mat',''); + end + + SaveHeadModel.Param = HeadModel.Param; + SaveHeadModel.Function = HeadModel.Function; + + if MEG + SaveHeadModel.MEGMethod = OPTIONS.Method{DataType.MEG}; + end + if EEG + SaveHeadModel.EEGMethod = OPTIONS.Method{DataType.EEG}; + end + + if strcmpi(OPTIONS.HeadModelName,'Default') % Specify default HeadModelName + if MEG & EEG + SaveHeadModel.HeadModelName = sprintf('%s | %s', OPTIONS.Method{DataType.MEG},OPTIONS.Method{DataType.EEG}); + elseif MEG + SaveHeadModel.HeadModelName = sprintf('%s', OPTIONS.Method{DataType.MEG}); + elseif EEG + SaveHeadModel.HeadModelName = sprintf('%s', OPTIONS.Method{DataType.EEG}); + end + else + SaveHeadModel.HeadModelName = OPTIONS.HeadModelName; + end + + if ~isempty(OPTIONS.HeadModelFile), % and is not empty + [HeadModelFilePath,HeadModelFile,ext] = fileparts(OPTIONS.HeadModelFile); + end + + % Assign proper GridName + switch(Order) + case -1 + SourceOrderString = 'CD'; % Current Dipole + case 0 + SourceOrderString = 'MD'; % Magnetic Dipole + case 1 + SourceOrderString = 'CME'; % Current Multipole + end + + [SaveHeadModel.Param.Order] = deal(Order); + SaveHeadModel.SourceOrder = Order; % Alternative to previous line + SaveHeadModel.HeadModelType = 'ImageGrid'; + + + if ~isempty(OPTIONS.Cortex) + SaveHeadModel.GridName = {sprintf('%s Surface Grid : %s',SourceOrderString, OPTIONS.Cortex.Name)}; + if ~isempty(OPTIONS.ImageGridFile) + SaveHeadModel.Gain = {OPTIONS.ImageGridFile}; % Store Image Grid File name in the headmodel.mat file + end + + SaveHeadModel.GainCovar{1} = cell(1); % May compute it later within headmodel_make + SaveHeadModel.GainCovarName =''; + SaveHeadModel.GridLoc{1} = OPTIONS.Cortex.FileName; + if 1%OPTIONS.Cortex.iGrid > 1 % Meaning that cortical support is not the fisrt surface in tessellation file (default in MMII) + % Add yet another field to headmodel file to specify this information. + SaveHeadModel.iGrid = OPTIONS.Cortex.iGrid; + end + else + SaveHeadModel.GainCovar = []; + SaveHeadModel.GainCovarName = []; + SaveHeadModel.GridName = []; + SaveHeadModel.GridLoc = {OPTIONS.SourceLoc}; + SaveHeadModel.Gain = {OPTIONS.ImageGridFile}; + end + if ~OPTIONS.ApplyGridOrient + SaveHeadModel.GridOrient = []; + else + SaveHeadModel.GridOrient = {OPTIONS.SourceOrient}; + end + + + % now collect together and save + if ~isempty(OPTIONS.HeadModelFile) & exist('destname','var') + % HeadModel file name + SaveHeadModelFile = fullfile(HeadModelFilePath,[HeadModelFile,... + sprintf('SurfGrid_%s',SourceOrderString),... + ext]); + ifile = 0; + while exist(SaveHeadModelFile,'file') % Do not overwrite headmodel files + ifile = ifile + 1; + SaveHeadModelFile = fullfile(HeadModelFilePath,[HeadModelFile,... + sprintf('SurfGrid_%s',SourceOrderString),... + '_',int2str(ifile),ext]); + end + if OPTIONS.Verbose, bst_message_window({... + sprintf('Writing Cortical Image Support HeadModel file:'),... + sprintf('%s', SaveHeadModelFile)... + }) + end + + if strcmp(lower(OPTIONS.HeadModelFileOld),'default') + OPTIONS.HeadModelFile = SaveHeadModelFile; + end + + try + save_fieldnames(SaveHeadModel, SaveHeadModelFile); + catch + cd(User.STUDIES) + save_fieldnames(SaveHeadModel, SaveHeadModelFile); + end + + + if OPTIONS.Verbose, bst_message_window(... + {'-> DONE',' '}), end + end + + % Save completed ----------------------------------------------------------------------------------- + +end % Cortical gain matrix for each source model + + +%% ------------------------------------------------------------------------- +if isfield(OPTIONS,'rooot') + OPTIONS = rmfield(OPTIONS,'rooot'); +end + +if nargout == 0 + clear G SearchGain +elseif nargout == 1 + varargout{1} = OPTIONS; +else + if OPTIONS.ImageGridBlockSize < nv & ~isempty(OPTIONS.HeadModelFile) + varargout{1} = OPTIONS.HeadModelFile; + else + varargout{1} = GG; + end + + varargout{2} = OPTIONS; +end + +fclose('all'); + +if OPTIONS.Verbose, bst_message_window(... + {'The head model has been properly designed and written to disk'}) +end + +if OPTIONS.Verbose, bst_message_window({... + sprintf(... + 'Head modeling ends (%s - %dH %dmin %ds (took %3.2f seconds))',date,clockk(4:6), etime(clock,time0)),... + '__________________________________________'... + }) +end + +%------------------------------------------------------------------------------------------------------------------------------ +% +% SUB-FUNCTIONS +% +%------------------------------------------------------------------------------------------------------------------------------ + +function BEMGaingridFname = bem_GainGrid(DataType,OPTIONS,BEMChanNdx) + +% Computation of the BEM gain matrix on the 3D interpolative grid for MEG and/or EEG data +% DataType : a structure with fields MEG and EEG. DataType.MEG (res. DataType.EEG) is set to 1 if MEG (res. EEG) data is available +% OPTIONS : the OPTIONS structure, input argument from bst_headmodeler +% BEMChanNdx : a cell array of channel indices such that : BEMChanNdx{DataType.EEG} = EEGndx (res. MEG). +% +% BEMGaingridFname: a structure with fields: +% .EEG : string with the name of the file containing the gain matrix of the 3D BEM interpolative grid in EEG +% .MEG : same as .EEG respectively to MEG BEM model. + + +% Detect the requested BEM computations: MEG and/or EEG___________________________________ + +User = get_user_directory; +if isempty(User) + User.STUDIES = pwd; + User.SUBJECTS = pwd; +end + +MEG = ~isempty(BEMChanNdx(DataType.MEG)); % == 1 if MEG is requested +EEG = ~isempty(BEMChanNdx(DataType.EEG)); % == 1 if EEG is requested +if MEG + MEGndx = BEMChanNdx{DataType.MEG}; +end +if EEG + %EEGndx = BEMChanNdx{DataType.EEG}; + EEGndx = OPTIONS.EEGndx; % EEG sensors (not including EEG reference channel, if any) + EEGREFndx = good_channel(OPTIONS.Channel,[],'EEG REF'); +end + +if MEG & EEG + [Param(:).mode] = deal(3); +elseif ~MEG & EEG + [Param(:).mode] = deal(1); +elseif MEG & ~EEG + [Param(:).mode] = deal(2); +else + errordlg('Please check that the method requested for forward modeling has an authorized name'); + return +end + +% BEM parameters ________________________________________________________________________ +% Determine what basis functions to use +constant = ~isempty(strmatch('constant',lower(OPTIONS.BEM.Basis))); +if constant == 0 + [Param(:).basis_opt] = deal(1); +else + [Param(:).basis_opt] = deal(0); +end + +% Determine what Test to operate +collocation = ~isempty(strmatch('collocation',lower(OPTIONS.BEM.Test))); +if collocation == 0 + [Param(:).test_opt] = deal(1); +else + [Param(:).test_opt] = deal(0); +end + +% Insulated-skull approach +isa = OPTIONS.BEM.ISA; +if isa == 1 + [Param(:).ISA] = deal(1); +else + [Param(:).ISA] = deal(0); +end + +Param.Ntess_max = OPTIONS.BEM.NVertMax; +Param.Conductivity = deal(OPTIONS.Conductivity); + +%_________________________________________________________________________________________ + +% Load surface envelopes information______________________________________________________ + +% Find the indices of the enveloppes selected for BEM computation +if ~isfield(OPTIONS.BEM,'EnvelopeNames') + errordlg('Please specify the ordered set of head-tissue envelopes by filling the OPTIONS.BEM.EnvelopeNames field') + return +end +if isempty(OPTIONS.BEM.EnvelopeNames) + errordlg('Please specify the ordered set of head-tissue envelopes by filling the OPTIONS.BEM.EnvelopeNames field') + return +end +for k = 1:length(OPTIONS.BEM.EnvelopeNames) + + try + load(fullfile(User.SUBJECTS,OPTIONS.subjectpath,OPTIONS.BEM.EnvelopeNames{k}.TessFile),'Comment'); + catch % Maybe user is using command line call to function with absolute-referenced files OPTIONS.*.TessFile + try + OPTIONS.BEM.EnvelopeNames{k}.TessFile = [OPTIONS.BEM.EnvelopeNames{k}.TessFile,'.mat']; + load(fullfile(User.SUBJECTS,OPTIONS.subjectpath,OPTIONS.BEM.EnvelopeNames{k}.TessFile),'Comment'); + catch + cd(User.SUBJECTS) + load(OPTIONS.BEM.EnvelopeNames{k}.TessFile,'Comment'); + end + end + + Comment = strrep(Comment,' ',''); + % find surface in current tessellation file + OPTIONS.BEM.EnvelopeNames{k}.SurfId = find(strcmpi(OPTIONS.BEM.EnvelopeNames{k}.TessName,Comment)); + IDs(k) = OPTIONS.BEM.EnvelopeNames{k}.SurfId; + if isempty(OPTIONS.BEM.EnvelopeNames{k}.SurfId) + errordlg(... + sprintf('Surface %s was not found in file %s',... + OPTIONS.BEM.EnvelopeNames{k}.TessName, OPTIONS.BEM.EnvelopeNames{k}.TessFile)) + return + end + + % Load vertex locations + try + tmp = load(fullfile(User.SUBJECTS,OPTIONS.subjectpath,OPTIONS.BEM.EnvelopeNames{k}.TessFile),'Vertices'); + catch % Maybe user is using command line call to function with absolute-referenced files OPTIONS.*.TessFile + tmp = load(OPTIONS.BEM.EnvelopeNames{k}.TessFile,'Vertices'); + end + Vertices{k} = tmp.Vertices{OPTIONS.BEM.EnvelopeNames{k}.SurfId}'; + + % Load faces + try + tmp = load(fullfile(User.SUBJECTS,OPTIONS.subjectpath,OPTIONS.BEM.EnvelopeNames{k}.TessFile),'Faces'); + catch% Maybe user is using command line call to function with absolute-referenced files OPTIONS.*.TessFile + tmp = load(OPTIONS.BEM.EnvelopeNames{k}.TessFile,'Faces'); + end + + Faces(k) = tmp.Faces(OPTIONS.BEM.EnvelopeNames{k}.SurfId); +end +clear Comment tmp + +cd(User.STUDIES) + + +%_________________________________________________________________________________________ + + +% Channel Parameters______________________________________________________________________ +if MEG + R_meg1 = zeros(length(MEGndx),3); + O_meg1 = R_meg1; + R_meg2 = R_meg1; + O_meg2 = O_meg1; + flaggrad = zeros(length(MEGndx),1);% if = 1 - Flag to indicate there are some gradiometers here + + i = 0; + for k = MEGndx + i = i+1; + R_meg1(i,:) = OPTIONS.Channel(k).Loc(:,1)'; + O_meg1(i,:) = OPTIONS.Channel(k).Orient(:,1)'; + + if size(OPTIONS.Channel(k).Loc,2) == 2 + if sum(OPTIONS.Channel(k).Loc(:,1)-OPTIONS.Channel(k).Loc(:,2))~=0 % Gradiometer + R_meg2(i,:) = OPTIONS.Channel(k).Loc(:,2)'; + O_meg2(i,:) = OPTIONS.Channel(k).Orient(:,2)'; + flaggrad(k-min(MEGndx)+1) = 1; + end + end + + end + O_meg1 = (O_meg1' * inorcol(O_meg1'))'; + if exist('O_meg2','var') + O_meg2 = (O_meg2' * inorcol(O_meg2'))'; + end + + % Handle MEG reference channels if necessary + % if isfield(OPTIONS.Channel(MEGndx(1)),'irefsens') + % irefsens = OPTIONS.Channel(MEGndx(1)).irefsens; + % else + % irefsens = []; + % end + irefsens = good_channel(OPTIONS.Channel,[],'MEG REF'); + + if ~isempty(irefsens) + flaggrad_REF = zeros(length(irefsens),1);% if = 1 - Flag to indicate there are some gradiometers here + R_meg_REF = zeros(length(irefsens),3); + O_meg_REF = R_meg_REF; + R_meg_REF = R_meg_REF; + O_meg_REF = R_meg_REF; + + if ~isempty(irefsens) & ~isempty(OPTIONS.Channel(MEGndx(1)).Comment) % Reference Channels are present + if OPTIONS.Verbose, bst_message_window('Reference Channels have been detected.'), end + end + i = 0; + for k = irefsens + i = i+1; + R_meg_REF(i,:) = OPTIONS.Channel(k).Loc(:,1)'; + O_meg_REF(i,:) = OPTIONS.Channel(k).Orient(:,1)'; + + if size(OPTIONS.Channel(k).Loc,2) == 2 + if sum(OPTIONS.Channel(k).Loc(:,1)-OPTIONS.Channel(k).Loc(:,2))~=0 % Reference Gradiometer + R_meg_REF2(i,:) = OPTIONS.Channel(k).Loc(:,2)'; + O_meg_REF2(i,:) = OPTIONS.Channel(k).Orient(:,2)'; + flaggrad_REF(k-min(irefsens)+1) = 1; + end + end + + end + + else + R_meg_REF = []; + O_meg_REF = R_meg_REF; + R_meg_REF = R_meg_REF; + O_meg_REF = R_meg_REF; + end + + MEGndx_orig = MEGndx; +else + R_meg1 = zeros(length(EEGndx),3); % Use dummy channel locations + O_meg1 = R_meg1; + R_meg2 = R_meg1; + O_meg2 = O_meg1; +end + +if EEG + R_eeg = zeros(length([EEGREFndx,EEGndx]),3); + i = 0; + for k = [EEGREFndx,EEGndx] + i = i+1; + R_eeg(i,:) = OPTIONS.Channel(k).Loc(:,1)'; + end + if ~MEG + flaggrad = []; + irefsens = []; + end + +else + R_eeg = NaN * zeros(size(R_meg1)); % Dummy coordinates +end + +%_________________________________________________________________________________________ + + +%Compute Transfer Matrices__________________________________________________________________ + +BrainStorm.iscalp = IDs(end); % Index of the outermost surface (scalp, supposedly) + +eeg_answ = ''; +meg_answ = ''; + +% Check whether some transfer-matrix files already exist for current study + +fn_eeg = sprintf('%s_eegxfer_%s_%s.mat',OPTIONS.rooot, OPTIONS.BEM.Basis,OPTIONS.BEM.Test); +fn_meg = sprintf('%s_megxfer_%s_%s.mat',OPTIONS.rooot, OPTIONS.BEM.Basis,OPTIONS.BEM.Test); +if MEG + test = exist(fn_meg,'file'); +elseif EEG + test = exist(fn_eeg,'file'); +else MEG& EEG + test = exist(fn_eeg,'file') & exist(fn_meg,'file'); +end + +if (OPTIONS.BEM.ForceXferComputation | ~test) | OPTIONS.BEM.Interpolative + + % Force (re)computation of transfer matrices, even if files exist in current study folder + + % if OPTIONS.Verbose, bst_message_window('Computing the BEM Transfer Matrix (this may take a while)....'), end + + global nfv + nfv = bem_xfer(R_eeg,R_meg1,O_meg1,Vertices,Faces,Param(1).Conductivity,Param(1).mode, ... + Param(1).basis_opt,Param(1).test_opt,Param(1).ISA,fn_eeg,fn_meg,Param.Ntess_max,OPTIONS.Verbose,OPTIONS.BEM.checksurf); + + if ~isempty(find(flaggrad)) + if OPTIONS.Verbose, bst_message_window({'Gradiometers detected',... + 'Computing corresponding Gain Matrix. . .'}), end + + %fn_meg_2 = fullfile(pwd,[OPTIONS.rooot,'_megxfer_2.mat']); + fn_meg_2 = sprintf('%s_megxfer2_%s_%s.mat',OPTIONS.rooot, OPTIONS.BEM.Basis,OPTIONS.BEM.Test); + bem_xfer(R_eeg,R_meg2,O_meg2,Vertices,Faces,Param(1).Conductivity,Param(1).mode, ... + Param(1).basis_opt,Param(1).test_opt,Param(1).ISA,fn_eeg,fn_meg_2,Param.Ntess_max,0,OPTIONS.BEM.checksurf); % Verbose = 0 + if OPTIONS.Verbose, bst_message_window('Gradiometer Channel Gain Matrix is Completed.'), end + end + + if ~isempty(irefsens) % Do the same for reference channels + %fn_meg_REF = fullfile(pwd,[OPTIONS.rooot,'_megxfer_REF.mat']); + fn_meg_REF = sprintf('%s_megREFxfer_%s_%s.mat',OPTIONS.rooot, OPTIONS.BEM.Basis,OPTIONS.BEM.Test); + bem_xfer(R_eeg,R_meg_REF,O_meg_REF,Vertices,Faces,Param(1).Conductivity,Param(1).mode, ... + Param(1).basis_opt,Param(1).test_opt,Param(1).ISA,fn_eeg,fn_meg_REF,Param.Ntess_max,0,OPTIONS.BEM.checksurf);% Verbose = 0 + + if ~isempty(find(flaggrad_REF)) + + if OPTIONS.Verbose, bst_message_window({'MEG Reference Channels detected',... + 'Computing corresponding Gain Matrix. . .'}), end + + %fn_meg_REF2 = fullfile(pwd,[OPTIONS.rooot,'_megxfer_REF2.mat']); + fn_meg_REF2 = sprintf('%s_megREFxfer2_%s_%s.mat',OPTIONS.rooot, OPTIONS.BEM.Basis,OPTIONS.BEM.Test); + + bem_xfer(R_eeg,R_meg_REF2,O_meg_REF2,Vertices,Faces,Param(1).Conductivity,Param(1).mode, ... + Param(1).basis_opt,Param(1).test_opt,Param(1).ISA,fn_eeg,fn_meg_REF2,Param.Ntess_max,0,OPTIONS.BEM.checksurf);% Verbose = 0 + if OPTIONS.Verbose, bst_message_window('MEG Reference Channel Gain Matrix is Completed.'), end + end + end +end + +% Computation of TRANSFER MATRIX Completed ___________________________________________________ + + +%%%% THIS PART SPECIFIES PARAMETERS USED TO GENERATE THE 3-D GRID %%%%%%%%%% + + +if OPTIONS.BEM.Interpolative%1%~exist(BEMGridFileName,'file') + + if OPTIONS.Verbose, bst_message_window('Computing BEM Interpolative Grid. . .'), end + + BEMGridFileName = [OPTIONS.rooot,'grid.mat']; + + % update tessellated envelope with surfaces possibly modified by + % BEM_XFER (downsampling, alignment, embedding etc.) + Vertices = {nfv(:).vertices}; + Faces = {nfv(:).faces}; + gridmaker(Vertices,Faces,BEMGridFileName,OPTIONS.Verbose); + + if OPTIONS.Verbose, + bst_message_window('Computing BEM Interpolative Grid -> DONE'), + + % Visualization of surfaces + grid points + for k = 1:length(Vertices) + [hf,hs(k),hl] = view_surface('Head envelopes & a subset of BEM interpolative grid points',Faces{k},Vertices{k}); + view(90,0) + delete(hl) + end + camlight + rotate3d on + + set(hs(1),'FaceAlpha',.3,'edgealpha',.3,'edgecolor','none','facecolor','r') + set(hs(2),'FaceAlpha',.2,'edgealpha',.2,'edgecolor','none','facecolor','g') + set(hs(3),'FaceAlpha',.1,'edgealpha',.1,'edgecolor','none','facecolor','b') + + hold on + + load(BEMGridFileName) + hgrid = scatter3(Rq_bemgrid(1:10:end,1),Rq_bemgrid(1:10:end,2),Rq_bemgrid(1:10:end,3),'.','filled'); + + end + +else + + global GBEM_grid + + % Grid points are the locations of the distributed sources + if isempty(OPTIONS.Cortex) % CBB (SB, 07-May-2004)| Should work also for volumic grid + errordlg('Please select a cortical grid for computation of BEM vector fields') + return + end + + try + load(OPTIONS.Cortex.FileName); % Load tessellation supporting the source locations and orientations + catch + Users = get_user_directory; + load(fullfile(Users.SUBJECTS,OPTIONS.Cortex.FileName)); % Load tessellation supporting the source locations and orientations + end + + BEMGridFileName.Loc = Vertices{OPTIONS.Cortex.iGrid}; clear Vertices + + if OPTIONS.ApplyGridOrient % Take cortex normals into account + ptch = patch('Vertices',BEMGridFileName.Loc','Faces',Faces{OPTIONS.Cortex.iGrid},'Visible','off'); + set(get(ptch,'Parent'),'Visible','off') + clear Faces + BEMGridFileName.Orient = get(ptch,'VertexNormals')'; + delete(ptch); + [nrm,BEMGridFileName.Orient] = colnorm(BEMGridFileName.Orient); + % Now because some orientations may be ill-set to [0 0 0] with the get(*,'VertexNormals' command) + % set these orientation to arbitrary [1 1 1]: + izero = find(nrm == 0);clear nrm + if ~isempty(izero) + BEMGridFileName.Orient(:,izero) = repmat([1 1 1]'/norm([1 1 1]),1,length(izero)); + end + clear izero + else + BEMGridFileName.Orient = []; + end + + %if OPTIONS.Verbose, bst_message_window(sprintf('Loading BEM interpolative grid points from %s', BEMGridFileName)), end + +end + + + +% This part computes the gain matrices defined on precomputed grid------------------------------------------------------------ + +% Assign file names where to store the gain matrices +if MEG & ~EEG + bem_xfer_mfname = {fn_meg}; + %BEMGaingridFname = [OPTIONS.rooot,'_meggain_grid.mat']; + BEMGaingridFname = sprintf('%s_MEGGainGrid_%s_%s.mat',OPTIONS.rooot, OPTIONS.BEM.Basis,OPTIONS.BEM.Test); + test = exist(BEMGaingridFname,'file'); +elseif EEG & ~ MEG + bem_xfer_mfname = {fn_eeg}; + %BEMGaingridFname = [OPTIONS.rooot,'_eeggain_grid.mat']; + BEMGaingridFname = sprintf('%s_EEGGainGrid_%s_%s.mat',OPTIONS.rooot, OPTIONS.BEM.Basis,OPTIONS.BEM.Test); + test = exist(BEMGaingridFname,'file'); +elseif EEG & MEG + bem_xfer_mfname = {fn_meg,fn_eeg}; + % BEMGaingridFname.MEG = [OPTIONS.rooot,'_meggain_grid.mat']; + % BEMGaingridFname.EEG = [OPTIONS.rooot,'_eeggain_grid.mat']; + BEMGaingridFname.MEG = sprintf('%s_MEGGainGrid_%s_%s.mat',OPTIONS.rooot, OPTIONS.BEM.Basis,OPTIONS.BEM.Test); + BEMGaingridFname.EEG = sprintf('%s_EEGGainGrid_%s_%s.mat',OPTIONS.rooot, OPTIONS.BEM.Basis,OPTIONS.BEM.Test); + test = exist(BEMGaingridFname.MEG,'file') & exist(BEMGaingridFname.EEG,'file'); +end + +if 1%OPTIONS.BEM.ForceXferComputation | ~test% Recompute gain matrices when transfer matrices have been recomputed just before + + if OPTIONS.Verbose + if OPTIONS.BEM.Interpolative + bst_message_window('Computing the BEM gain matrix for interpolative grid. . .'), + else + bst_message_window('Computing the BEM gain matrix for source grid. . .'), + end + end + + t0 = clock; + + if OPTIONS.Verbose, + if MEG & EEG + bst_message_window('for MEG and EEG channels. . .') + elseif EEG + bst_message_window('for EEG channels. . .') + elseif MEG + bst_message_window('for MEG channels. . .') + end + end + + + if length(bem_xfer_mfname) == 1 % xor(MEG,EEG) + bem_gain(BEMGridFileName,bem_xfer_mfname{1},Param(1).ISA,BEMGaingridFname, OPTIONS.Verbose); + else + % MEG gaingrid matrix + bem_gain(BEMGridFileName,bem_xfer_mfname{1},Param(1).ISA,BEMGaingridFname.MEG, OPTIONS.Verbose); + % EEG gaingrid matrix + bem_gain(BEMGridFileName,bem_xfer_mfname{2},Param(1).ISA,BEMGaingridFname.EEG, OPTIONS.Verbose); + end + + if OPTIONS.Verbose, bst_message_window('-> DONE'), end + + if ~isempty(find(flaggrad)) % Compute forward model on the second set of magnetometers from the gradiometers array + + bem_xfer_mfname = sprintf('%s_megxfer2_%s_%s.mat',OPTIONS.rooot, OPTIONS.BEM.Basis,OPTIONS.BEM.Test); + bem_gaingrid_mfname_2 = sprintf('%s_MEGGainGrid2_%s_%s.mat',OPTIONS.rooot, OPTIONS.BEM.Basis,OPTIONS.BEM.Test); + + if OPTIONS.Verbose, bst_message_window('MEG - completing gradiometers. . .'), end + + bem_gain(BEMGridFileName,bem_xfer_mfname,Param(1).ISA,bem_gaingrid_mfname_2,OPTIONS.Verbose); + + if OPTIONS.Verbose, bst_message_window('-> DONE'), end + + if MEG & EEG + G1 = load(BEMGaingridFname.MEG); + else + G1 = load(BEMGaingridFname); + end + G2 = load(bem_gaingrid_mfname_2); + % Apply respective weights within the gradiodmeters + meg_chans = [OPTIONS.Channel(MEGndx(find(flaggrad))).Weight]; + w1 = meg_chans(1:2:end); + w2 = meg_chans(2:2:end); + + G1.GBEM_grid(find(flaggrad),:) = w1'*ones(1,size(G1.GBEM_grid,2)).*G1.GBEM_grid(find(flaggrad),:)... + + w2' * ones(1,size(G1.GBEM_grid,2)).*G2.GBEM_grid(find(flaggrad),:); + clear G2 + + % is there special reference channel considerations? + if ~isempty(irefsens) + + % Forward model on all reference sensors + bem_xfer_mfname_REF = sprintf('%s_megREFxfer_%s_%s.mat',OPTIONS.rooot, OPTIONS.BEM.Basis,OPTIONS.BEM.Test); + bem_gaingrid_mfname_REF = sprintf('%s_MEGGainGrid_REF_%s_%s.mat',OPTIONS.rooot, OPTIONS.BEM.Basis,OPTIONS.BEM.Test); + + if OPTIONS.Verbose, bst_message_window('MEG reference channels. . .'), end + bem_gain(BEMGridFileName,bem_xfer_mfname_REF,Param(1).ISA,bem_gaingrid_mfname_REF,OPTIONS.Verbose); + if OPTIONS.Verbose, bst_message_window('-> DONE'), end + + if ~isempty(find(flaggrad_REF)) % Gradiometers are present in reference channels + + bem_xfer_mfname_REF2 = sprintf('%s_megREFxfer2_%s_%s.mat',OPTIONS.rooot, OPTIONS.BEM.Basis,OPTIONS.BEM.Test); + bem_gaingrid_mfname_REF2 = sprintf('%s_MEGGainGrid2_REF_%s_%s.mat',OPTIONS.rooot, OPTIONS.BEM.Basis,OPTIONS.BEM.Test); + + if OPTIONS.Verbose, bst_message_window('MEG reference channels / completing gradiometers. . .'), end + + bem_gain(BEMGridFileName,bem_xfer_mfname_REF2,Param(1).ISA,bem_gaingrid_mfname_REF2,OPTIONS.Verbose); + + if OPTIONS.Verbose, bst_message_window('-> DONE'), end + + GR = load(bem_gaingrid_mfname_REF); + GR2 = load(bem_gaingrid_mfname_REF2); + + meg_chans = [OPTIONS.Channel(irefsens(find(flaggrad_REF))).Weight]; + w1 = meg_chans(1:2:end); + w2 = meg_chans(2:2:end); + + GR.GBEM_grid(find(flaggrad_REF),:) = w1'*ones(1,size(GR.GBEM_grid,2)).*GR.GBEM_grid(find(flaggrad_REF),:)... + + w2' * ones(1,size(GR.GBEM_grid,2)).*GR2.GBEM_grid(find(flaggrad_REF),:); + + %GR.GBEM_grid(find(flaggrad_REF),:) = GR.GBEM_grid(find(flaggrad_REF),:) - GR2.GBEM_grid(find(flaggrad_REF),:); + clear GR2; + end + + % Apply nth-order gradient correction on good channels only + %Weight by the current nth-order correction coefficients + %G1.GBEM_grid = G1.GBEM_grid - Channel(MEGndx(1)).Gcoef(find(ChannelFlag(irefsens)>0),:)*GR.GBEM_grid; + + G1.GBEM_grid = G1.GBEM_grid - OPTIONS.Channel(MEGndx(1)).Comment*GR.GBEM_grid; + + end + + GBEM_grid = 1e-7*G1.GBEM_grid; + + if MEG & EEG + save(BEMGaingridFname.MEG,'GBEM_grid','-append'); + else + save(BEMGaingridFname,'GBEM_grid','-append'); + end + + end + + % Detect EEG reference - if none is specified in the .Comment or Type fields, + % and if none was passed through OPTIONS.EEGRef + % -> Apply average-referencing of the potentials by default. + + if EEG + % EEG Reference Channel + EEGREFndx = good_channel(OPTIONS.Channel,[],'EEG REF'); + + if MEG & EEG + load(BEMGaingridFname.EEG,'GBEM_grid') + else + load(BEMGaingridFname,'GBEM_grid') + end + + + if isempty(EEGREFndx)% AVERAGE REF + GBEM_grid = GBEM_grid - repmat(mean(GBEM_grid),size(GBEM_grid,1),1); + else + % GBEM_grid = GBEM_grid(setdiff(EEGndx,EEGREFndx)-EEGndx(1)+1,:) - repmat(GBEM_grid(EEGREFndx-EEGndx(1)+1,:),size(GBEM_grid(setdiff(EEGndx,EEGREFndx)-EEGndx(1)+1,:),1),1); + GBEM_grid = GBEM_grid(2:end,:) - repmat(GBEM_grid(1,:),length(EEGndx),1); % SB : EEG REF is stored as first sensor in GBEM_grid; see line 2226 + end + + if MEG + save(BEMGaingridFname.EEG,'GBEM_grid','-append') + else + save(BEMGaingridFname,'GBEM_grid','-append') + end + + end + + + if MEG & EEG + meg = load(BEMGaingridFname.MEG,'GBEM_grid'); + eeg = load(BEMGaingridFname.EEG,'GBEM_grid'); + GBEM_grid = zeros(length(OPTIONS.Channel),size(GBEM_grid,2)); + GBEM_grid(MEGndx,:)= meg.GBEM_grid; clear meg + GBEM_grid(EEGndx,:)= eeg.GBEM_grid; clear eeg + + elseif MEG + + meg = load(BEMGaingridFname,'GBEM_grid'); + GBEM_grid = zeros(length(OPTIONS.Channel),size(GBEM_grid,2)); + GBEM_grid(MEGndx,:)= meg.GBEM_grid; clear meg + + elseif EEG + + eeg = load(BEMGaingridFname,'GBEM_grid'); + GBEM_grid = zeros(length(OPTIONS.Channel),size(GBEM_grid,2)); + EEGndx = OPTIONS.EEGndx; + GBEM_grid(EEGndx,:)= eeg.GBEM_grid; clear eeg + + %clear GBEM_grid + % % Now save the combined MEG/EEG gaingrid matrix in a single file + % MEGEEG_BEMGaingridFname = strrep(BEMGaingridFname.EEG,'eeg','meg_eeg'); + % Gmeg = load(BEMGaingridFname.MEG,'GBEM_grid'); + % GBEM_grid = NaN * zeros(length(OPTIONS.Channel),size(Gmeg.GBEM_grid,2)); + % GBEM_grid(MEGndx,:) = Gmeg.GBEM_grid; clear Gmeg + % eeg = load(BEMGaingridFname.EEG); + % + % save_fieldnames(eeg,MEGEEG_BEMGaingridFname); + % + % GBEM_grid(setdiff(EEGndx,EEGREFndx),:) = eeg.GBEM_grid; clear eeg + % + % save(MEGEEG_BEMGaingridFname,'GBEM_grid','-append') + % + % BEMGaingridFname = MEGEEG_BEMGaingridFname; + else + save(BEMGaingridFname,'GBEM_grid','-append') + end + + telap_meg_interp = etime(clock,t0); + + if OPTIONS.Verbose, bst_message_window(sprintf('Completed in %3.1f seconds', telap_meg_interp),... + 'Computing the Gain Matrix for the Interpolative Grid Points -> DONE'), end +else + % if MEG & EEG + % if OPTIONS.Verbose, bst_message_window(sprintf('Loading 3D grid gain matrix from %s', BEMGaingridFname.EEG)),end + % else + % if OPTIONS.Verbose, bst_message_window(sprintf('Loading 3D grid gain matrix from %s', BEMGaingridFname)),end + % end +end + + + +function g = gterm_constant(r,rq) +%gterm_constant +% function g = gterm_constant(r,rq) + +% -------- 20-Nov-2002 14:06:02 ------------------------------ +% ---- Automatically Generated Comments Block using auto_comments ----------- +% +% Alphabetical list of external functions (non-Matlab): +% toolbox\rownorm.m +% ---------- 20-Nov-2002 14:06:02 ------------------------------ + + +if size(rq,1) == 1 % Just one dipole + r_rq= [r(:,1)-rq(1),r(:,2)-rq(2),r(:,3)-rq(3)]; + n = rownorm(r_rq).^3; + g = r_rq./[n,n,n]; +else + g = zeros(size(r,1),3*size(rq,1)); + isrc = 1; + for k = 1:size(rq,1) + r_rq= [r(:,1)-rq(k,1),r(:,2)-rq(k,2),r(:,3)-rq(k,3)]; + n = rownorm(r_rq).^3; + g(:,3*(isrc-1)+1: 3*isrc) = r_rq./[n,n,n]; + isrc = isrc + 1; + end + +end diff --git a/compute_inverse.m b/compute_inverse.m new file mode 100644 index 0000000..b293ef1 --- /dev/null +++ b/compute_inverse.m @@ -0,0 +1,862 @@ +function Results = minnorm(OPTIONS); +%MINNORM - Mininum norm solution as called from command line or BST_SOURCEIMAGING.M +% function Results = minnorm(OPTIONS); +% ------------------------------------------------------------------------------------------------- +% NOTICE: +% This function is not optimized for stand-alone command calls. +% Please use the generic BST_SOURCEIMAGING function to access Min.Norm. source map computations. +% ------------------------------------------------------------------------------------------------- +% +% +% INPUTS +% OPTIONS - a structure of parameters +% OUTPUTS +% Results - a regular BrainStorm Results structure (see documentation for details) +% +% Details of the OPTIONS structure +% +% Data Information: +% .DataFile : A string actual data file requested for localization. If isempty, look for data array(s) in .Data +% .Data : Array of nChannel x nTime array(s) of data. +% .SegmentIndices: Vector of all time samples (in sample values) +% of the latencies specifying the time window for analysis. +% .Trial : A scalar specifying the trial from which to extract the data segment +% (not empty only when data in .DataFile is stored in native format) +% .ChannelFlag: a vector of flags (1 for good, -1 for bad) of channels in the data file to process +% .DataTypes : A scalar or a vector of codes to specidy which is the type of data to use: +% 1 is MEG, 2 is EEG, 3 is Fusion +% (Default is all data type available) +% .FusionType : Code for the type of fusion to apply between MEG and EEG data +% 1 is linear, 2 is non-linear +% (Defaut is 1) +% .Data : Specify some data to be passed independently of any data file +% .F : Cell array of nChannel x nTime array(s) of data. +% If not empty: uses this array as input for inverse process. +% .ChannelFile : Channel file corresponding to data in .F +% .Time : Vector specifying the time instants of data samples in .Data.F +% (Default for .Data is empty) +% +% Source Support Information: +% .GridLoc : a string or a 3xN matrix +% - if a string : specifies the name of the BrainStorm tessellation file where cortical support is stored +% - if a matrix : contains the coordinates of the cortical locations where to compute the cortical current density +% .iGrid : when .GridLoc is a file name, specify which imaging grid to use (Default is 1). +% +% Forward Field Information : +% .HeadModelFile: Cell array of strings for filename of the BrainStorm headmodel file(s) to use for imaging. If is empty, look for.GainFile field. +% There are only 2 options at this point: +% - .HeadModelFile is of length 1, a single headmodel file is used for all data sets passed to the bst_sourceimaging. +% - .HeadModelFile is of length the number of data sets passed to bst_sourceimaging (i.e. the length of .DataFile). +% .GainFile : Cell array of filenames of image gain matrix (.bin) for the selected cortical support. If is empty, look for .Gain field. +% Remarks about the length of .HeadModelFile hold for .GainFile too. +% .Gain : Cell array containing the gain matrix of the cortical support (either a single cell or a cell array the length the number of data sets passed to function) +% Remarks about the length of .HeadModelFile hold for .GainFile too. +% (Default is empty). +% +% Processing Parameters : +% .Method : A string that specifies the imaging method +% 'Minimum-Norm Imaging': MN estimate of current density using MN priors on source amplitudes (Default) +% 'Recursive Min. Norm.': Recursive MN estimate based on adaptation of FOCUSS (NOT AVAILABLE) +% 'SPTF' : Scanning technique constrained on cortical surface (see Denis Schwartz) (UNDER DEVELOPMENT) +% 'ST-MAP' : Non-linear imaging technique based on sparse-focal image models (NOT AVAILABLE) +% 'RAP-MUSIC' : link to Mosher's GUI (UNDER DEVELOPMENT) +% (Default is 'Minimum-Norm Imaging') +% .Tikhonov : Hyperparameter used in Tykhonov regularization +% (Default is 10) +% .FFNormalization : Apply forward field normalization of the gain matrix +% (Default is 1) +% .Rank : Specify a rank for signal subspace following SVD of the spatio-temporal data matrix +% (Default is full rank) +% .ComputeKernel : If 1, compute MN kernel to be applied subsequently to data instead of full ImageGridAmp array +% (Default is 0: compute full ImageGridAmp array) +% +% Output Information: +% .ResultFile : optional string for filename where to write results +% if 'default', filename as used in GUI environment is used +% (Default is '' (no Result file created)) +% .Verbose : Turn ON/OFF verbose mode +% (Default is 1 (ON)) +% .GUIHandles : A vector of GUI handles to the SOURCEIMAGING GUI +% when operating in graphic mode: passed automatically to bst_sourceimaging by the source imaging switchyard +% in command line mode: leave this field empty (default). +% .NNormalization : Apply noise normalization +% (Default is 0) + +% ---------------------- 27-Jun-2005 10:45:06 ----------------------- +% ------ Automatically Generated Comments Block Using AUTO_COMMENTS_PRE7 ------- +% +% CATEGORY: Inverse Modeling +% +% Alphabetical list of external functions (non-Matlab): +% toolbox\bst_message_window.m +% toolbox\colnorm.m +% toolbox\good_channel.m +% toolbox\inorcol.m +% toolbox\meg4read.m +% toolbox\norcol.m +% toolbox\read_gain.m +% toolbox\regcheck.m +% toolbox\regsubspace.m +% +% Subfunctions in this file, in order of occurrence in file: +% varargout = gainmat_covar(varargin) +% +% At Check-in: $Author: Mosher $ $Revision: 38 $ $Date: 6/27/05 9:00a $ +% +% This software is part of BrainStorm Toolbox Version 27-June-2005 +% +% Principal Investigators and Developers: +% ** Richard M. Leahy, PhD, Signal & Image Processing Institute, +% University of Southern California, Los Angeles, CA +% ** John C. Mosher, PhD, Biophysics Group, +% Los Alamos National Laboratory, Los Alamos, NM +% ** Sylvain Baillet, PhD, Cognitive Neuroscience & Brain Imaging Laboratory, +% CNRS, Hopital de la Salpetriere, Paris, France +% +% See BrainStorm website at http://neuroimage.usc.edu for further information. +% +% Copyright (c) 2005 BrainStorm by the University of Southern California +% This software distributed under the terms of the GNU General Public License +% as published by the Free Software Foundation. Further details on the GPL +% license can be found at http://www.gnu.org/copyleft/gpl.html . +% +% FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE +% UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY +% WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF +% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY +% LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE. +% ------------------------ 27-Jun-2005 10:45:06 ----------------------- + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% /---Script Author--------------------------------------\ +% | *** John C. Mosher, Ph.D. | +% | Biophysics Group | +% | | +% | *** Sylvain Baillet, Ph.D. | +% | Cognitive Neuroscience & Brain Imaging Laboratory | +% | CNRS UPR640 - LENA | +% | Hopital de la Salpetriere, Paris, France | +% | sylvain.baillet@chups.jussieu.fr | +% \------------------------------------------------------/ +% +% Date of creation: October 2002 (adapted from MINNORM_GUI.M, now obsolete and moved to ARCHIVE) +% +% Script History ----------------------------------------------------------------------------------- +% SB 23-Dec-2002 Added .ComputeKernel option +% Added .Data option +% SB 19-Oct-2003 Added .TimeSeries field to Results structure (as per BrainStorm general convention) +% EK 02-Oct-2004 Changed .Tikhonov to be defined as the percentage of the +% maximum sigular value. +% EK 04-Oct-2004 Added noise normalization for both BST and Kernel +% format +% SB 02-May-2005 Added deprecated code +% SB 13-May-2005 More efficient computation of noise normalization +% SB 16-May-2005 Warning off when trying to load Projector from data file +% SB 19-May-2005 Added DP bug fix when number of sources > 10,000 (default block +% size) +% SB 22-May-2005 Fixed bug for AAt EEG computation when length(sources) = +% block size + 1 +% SB 23-May-2005 Fixed bug: ChannelFlag are now properly read from file +%--------------------------------------------------------------------------------------------------- + +% General hard-wired parameter values:-------------------------------------------------------------- +DataType = {'MEG','EEG','Fusion'}; % String labels corresponding to OPTIONS.DataTypes flags +OPTIONS.BLOCK = 10000; % process by blocks +OPTIONS.ExpoiG = .4; % Exponential weighting for column normalization +%--------------------------------------------------------------------------------------------------- + +% Load Headmodel information ----------------------------------------------------------------------- + +% From file, if passed to function +if ~isempty(OPTIONS.HeadModelFile) + HeadModel = load(OPTIONS.HeadModelFile,'Function','Param',... + 'GridLoc','Gain'); % Load Basics + + if ~isempty(OPTIONS.GridLoc) + HeadModel.GridLoc = {OPTIONS.GridLoc}; + end + if ~isempty(OPTIONS.Gain) + HeadModel.Gain = OPTIONS.Gain; + end + + HeadModelFileWhos = whos('-file',OPTIONS.HeadModelFile); + % if OPTIONS.DataTypes == 3 + % % If Fusion, force gain column normalization + % OPTIONS.FFNormalization = 1 ; + % end + + if OPTIONS.FFNormalization == 1 % Forward-field normalization requested + if OPTIONS.DataType == 3 % Fusion: load specific items + if ~isempty(find(strcmp(cellstr(char(HeadModelFileWhos(:).name)),'GainCovar_ColNormFus'))) + tmp = load(OPTIONS.HeadModelFile,'GainCovar_ColNormFus'); + AAt = tmp.GainCovar_ColNormFus{1}; % Generic name for gain covariance entry (see below) + else + AAt = []; + end + if ~isempty(find(strcmp(cellstr(char(HeadModelFileWhos(:).name)),'Gain_RowNorm'))) + tmp = load(OPTIONS.HeadModelFile,'Gain_RowNorm'); + Gain_RowNorm = tmp.Gain_RowNorm; + else + Gain_RowNorm = cell(size(HeadModel.Gain)); + end + + else + if ~isempty(find(strcmp(cellstr(char(HeadModelFileWhos(:).name)),'GainCovar_ColNorm'))) + tmp = load(OPTIONS.HeadModelFile,'GainCovar_ColNorm'); + AAt = tmp.GainCovar_ColNorm{1}; % Generic name for gain covariance entry (see below) + else + AAt = []; + end + end + + else % FF is set to 0 + if ~isempty(find(strcmp(cellstr(char(HeadModelFileWhos(:).name)),'GainCovar'))) + tmp = load(OPTIONS.HeadModelFile,'GainCovar'); + AAt = tmp.GainCovar{1}; % Generic name for gain covariance entry (see below) + if iscell(AAt) + AAt = AAt{1}; + end + else + AAt = []; + end + end + + clear tmp + + % Gain matrix full filename + gainfile = fullfile(fileparts(OPTIONS.HeadModelFile),HeadModel.Gain{1}); + +end + +% Load Data Information ----------------------------------------------------------------------- + +if(~isempty(findstr('_results',deblank(lower(OPTIONS.DataFile))))), + % CHEAT + % The DataName has the string '_results' in it + % We are trying to treat independent topographies as min norm data. Eventually this + % capability will be moved to a new routine. + % name has the string '_results' in it + Results = load(OPTIONS.DataFile); + % now synthesize a Data structure from this + [ignorm,Anrm] = colnorm(Results.IndepTopo); + + Data = struct('F',Anrm,'ChannelFlag',Results.ChannelFlag,... + 'Time',[1:size(Results.IndepTopo,2)],... % time is now simply an indexer + 'NoiseCov',Results.NoiseCov,'SourceCov',Results.SourceCov,'Projector',Results.Projector,... + 'Comment',sprintf('Synthesized Data Set from Results: %s',Results.Comment)); +else + if isempty(OPTIONS.Data) + % assume it is a standard data file + DataArray = 0; % Correponsing flag + warning off + Data = load(OPTIONS.DataFile,'F','ChannelFlag','Time','Projector','Comment'); + warning on + else + DataArray = 1; + Data = OPTIONS.Data; + Data.Projector = []; + Data.Comment = ''; + Data.ChannelFlag = ones(size(Data.F,1),1); + end + +end + +% Data & Channel information ------------------------------------------------------------------ +if isfield(OPTIONS,'ChannelFile') + Channel = load(OPTIONS.ChannelFile); + OPTIONS.Channel = Channel.Channel; clear Channel +end + +% now alter the data according to the bad channels +OPTIONS.GoodChannel = good_channel(OPTIONS.Channel,Data.ChannelFlag,DataType{OPTIONS.DataType}); %good channels for selected modality +OPTIONS.EEGChannel = good_channel(OPTIONS.Channel,[],'EEG'); +OPTIONS.MEGChannel = good_channel(OPTIONS.Channel,[],'MEG'); + +if ischar(Data.F) % Data is in native file format + VERBOSE = 1; + Data.F = meg4read(Data.F,OPTIONS.Trial,OPTIONS.SegmentIndices,OPTIONS.Verbose); + Data.FBaseline = meg4read(Data.F,OPTIONS.Trial,OPTIONS.NoiseSegmentIndices,OPTIONS.Verbose); +else + % Trim the data to the requested segments + if OPTIONS.NNormalization + Data.FBaseline = Data.F(:,OPTIONS.NoiseSegmentIndices); + end + Data.F = Data.F(:,OPTIONS.SegmentIndices); + + +end + + +try + Data.F = Data.F(OPTIONS.GoodChannel,:); % good channels only + if OPTIONS.NNormalization + Data.FBaseline = Data.FBaseline(OPTIONS.GoodChannel,:); % good channels for noise segment too + end + +catch + errordlg(sprintf('Data array has %d rows while there are %d %s channels. Possible problem is wrong type assignement of reference vs. measurement channels in channel file %s',... + size(Data.F,1),length(OPTIONS.GoodChannel), DataType{OPTIONS.DataType}, OPTIONS.ChannelFile ),'Inconsistent channel and data array sizes') + return +end + + +Time = Data.Time(OPTIONS.SegmentIndices); + + +% Is there any projector for the data ? +if ~isfield(Data,'Projector') + Data.Projector = []; +end +if(~isempty(Data.Projector)), + Data.Projector = Data.Projector(OPTIONS.GoodChannel,:); +end + +% Get HeadModel parmaters for GoodChannels +if 0 % Deprecated code (thanks to Nicolas Lefebvre) | SB - 02-May-2005 + HeadModel.Param = HeadModel.Param(OPTIONS.GoodChannel); +end + + + +% ------------------------ If not available : Create & Save Gain Matrix Covariance ------------------------------------- + +if isempty(AAt) % Need to compute the proper gain covariance matrix (depending on DataType and and Forward Fiels normalization option) + % NOTE : This is a one-time computation + % Result will be saved to HeadModelFile + if OPTIONS.Verbose + bst_message_window('Starting one-time (long) computation . . .') + if ~isempty(OPTIONS.GUIHandles) + OPTIONS.hwaitbar = waitbar(0,'Creating Column-Normalized Gain Matrix Covariance...'); + else + bst_message_window('Creating Column-Normalized Gain Matrix Covariance...'); + end + end + + % Compute gain matrix covariance + [AAt, Gain_RowNorm] = gainmat_covar(gainfile, OPTIONS); + + if OPTIONS.Verbose & ~isempty(OPTIONS.GUIHandles) + close(OPTIONS.hwaitbar); + bst_message_window('Saving the gain covar back into your head model'); + end + + % Now save back to headmodel file + + switch OPTIONS.FFNormalization + case 0 + GainCovar{1} = AAt; + save(OPTIONS.HeadModelFile,'GainCovar','-append'); + case 1 + if OPTIONS.DataType == 3 + GainCovar_ColNormFus{1} = AAt; + Gain_RowNorm = {Gain_RowNorm}; % Save it as a cell like other Gain* entries in headmodel file + save(OPTIONS.HeadModelFile,'GainCovar_ColNormFus','Gain_RowNorm','-append'); + else + GainCovar_ColNorm{1} = AAt; + save(OPTIONS.HeadModelFile,'GainCovar_ColNorm','-append'); + end + end + +end % gain covariance matrix is created and available + + +% Keep only the rows and columns of AAt corresponding to GoodChannel +AAt = AAt(OPTIONS.GoodChannel,OPTIONS.GoodChannel); +if exist('Gain_RowNorm','var') + if iscell(Gain_RowNorm) + Gain_RowNorm = Gain_RowNorm{1}; + end + if ~isempty(Gain_RowNorm) + Gain_RowNorm = Gain_RowNorm(OPTIONS.GoodChannel,OPTIONS.GoodChannel); + else + clear Gain_RowNorm + end +end + +% if OPTIONS.DataType == 3 % Fusion: Apply gain matrix row-norm weighting. +% Data.F = Gain_RowNorm*Data.F; +% end + +% Update MEG and EEG channel indices by removing bad channels. +OPTIONS.EEGChannel = intersect(OPTIONS.GoodChannel,OPTIONS.EEGChannel); +OPTIONS.MEGChannel = intersect(OPTIONS.GoodChannel,OPTIONS.MEGChannel); + +if OPTIONS.DataType == 2 & ... + length(OPTIONS.GoodChannel) < length(OPTIONS.EEGChannel) % EEG data with bad channels + + % Adjust average reference contribution from covariance matrix + if isempty(find(mean(Data.F >eps))) + % Average Reference (don't use the channel.comment field that may not have been altered following manipulations in the data viewer tool) + % Load gain matrix + G = read_gain(gainfile,[]); + if strcmp(OPTIONS.Col_norm,'y'); + G = G*inorcol(G); + end + + Gm = mean(G,1)'; + + I = ones(length(OPTIONS.GoodChannel),1); + AAt = AAt - G*Gm*I' - I*Gm'*G' + Gm'*Gm*ones(size(AAt)) ; + clear G + % Reopen the Gain file that was closed during call to read_gain + fid = fopen(... + fullfile(fileparts(OPTIONS.HeadModelFile),HeadModel.Gain{1}),... + 'r','ieee-be'); % HeadModel.Gain{1} - CHEAT Dipoles only + else + % Do nothing + end +end + +OPTIONS = regcheck(OPTIONS); % returns OPTIONS.REG set to the appropriate field + +% Did the user provide an existing control? +if(isfield(Data,'Projector')), + %Decompose it: + if(isempty(Data.Projector)), + A = []; + Ua = []; + else + A = Data.Projector; % initialize + Ua = orth(A); + end +else % user did not give a projector + A = []; + Ua = []; +end + +if isempty(OPTIONS.Rank) + OPTIONS.Rank = min(size(Data.F)); +end + +if(OPTIONS.Rank < min(size(Data.F))), % reduced rank + + % User has asked for an rank RANK subspace search. + % Form the signal subspace + + if(size(Data.F,1) >= size(Data.F,2)), % skinny matrix + [Uf,Sf,Vf] = svd(Data.F,0); % economy + else % wide matrix + [Vf,Sf,Uf] = svd(Data.F',0); + end + + % form the reduced rank data + Data.F = Uf(:,1:OPTIONS.Rank)*(Sf(1:OPTIONS.Rank,1:OPTIONS.Rank)*Vf(:,1:OPTIONS.Rank)'); + + % project away if needed + if(~isempty(Ua)), + Data.F = Data.F - Ua*(Ua'*Data.F); + end + + clear Ua Sa Va Uf Sf Vf +end + + +% -------------------------------------------------------------------------------------- + +% Begin minimum norm estimate -------------------------------------------------------------------------------------- + +% -------------------------------------------------------------------------------------- + +% recall the y = Ax, let x = A'c -> y = AA'c, solve for c, the 'coefs' of x. +% c = pinv(AA')*y, and we will regularize the inverse of AA' based on user selection +% Then form x = A'c. + +[U,S,V] = svd(AAt); +U = regsubspace(OPTIONS,U,sqrt(S)); % condition based on svd of gain matrix +reg_rank = size(U,2); % reduced rank based on regularization +S = diag(S); +switch deblank(lower(OPTIONS.REG)) + case 'tikhonov' + Lambda = sqrt(S(1))*OPTIONS.Tikhonov/100 ; % sing values already squared --ESEN Tikhonov percentage + Si = 1../(S(1:reg_rank)+Lambda^2); % filtered inverse + otherwise + % do nothing, truncation only + Si = 1../S(1:reg_rank); +end + +% the min norm coefs +coefs = V(:,1:reg_rank)*... + ((spdiags(Si,0,reg_rank,reg_rank)*U(:,1:reg_rank)') ); % * Data.F removed to allow optional computation of MN imaging kernel alone + +clear U S V + +% Contribution of MN estimate to data (Fsynth) +% let rAAt = regularized(AAt), as found by the above +% So Data.F = AAt *c, c = inv(rAAt), so Fsynth = AAt*c, let's calculate and stick into results +Fsynth = AAt * coefs * Data.F; %Estimated Data + +clear AAt + +% Calculate source amplitudes explicitly --------------------------------------------------------------- +% How many sources ? +tmp = read_gain(gainfile,OPTIONS.GoodChannel(1),[]); +nsrc = length(tmp); clear tmp % Number of sources + +if OPTIONS.ComputeKernel == 0 + + ImageGridAmp = single(zeros(nsrc,size(Data.F,2))); % one column per time slice / 07-Oct-2002 : SB - now stored as 'singles' (space saver) + ImagingKernel = []; + + if OPTIONS.Verbose + if ~isempty(OPTIONS.GUIHandles) & nsrc > OPTIONS.BLOCK + hwaitbar = waitbar(0,'Inverse transformation running. . .'); + else + bst_message_window('Inverse transformation running. . .'); + end + + bst_message_window({... + sprintf('Image support: %.0f grid points . . .',nsrc)... + }) + + end + + % Iterative block computation of source amplitudes + + for i = 1:OPTIONS.BLOCK:nsrc + + if ~isempty(OPTIONS.GUIHandles) + if nsrc > OPTIONS.BLOCK + waitbar(i/nsrc,hwaitbar); + end + end + + % Indices of next sources for which amplitude needs to be estimated + ndx = [0:(OPTIONS.BLOCK-1)]+i; + if(ndx(end) > nsrc), % last block too long + ndx = [ndx(1):nsrc]; + end + + % Load and possibly normalize forward fields (Kernel) for running block of sources + if OPTIONS.FFNormalization == 1 + clear Kernel + if OPTIONS.DataType == 1 + Chans = OPTIONS.MEGChannel; + elseif OPTIONS.DataType == 2 + Chans = OPTIONS.EEGChannel; + elseif OPTIONS.DataType == 3 + Chans = OPTIONS.GoodChannel; + end + + Kernel = read_gain(gainfile,Chans,ndx); % Chunk of gain matrix for this block of sources + if OPTIONS.DataType == 3 + Kernel = Gain_RowNorm * Kernel; + end + + OPTIONS.iG = sqrt(norcol(Kernel)'); % NORCOL computes the column norms to the SQUARE + OPTIONS.iG(OPTIONS.iG == 0) = min(OPTIONS.iG(OPTIONS.iG ~= 0)); + iKernel = spdiags((1./OPTIONS.iG).^OPTIONS.ExpoiG, 0, length(OPTIONS.iG), length(OPTIONS.iG)) ; + Kernel = (Kernel*iKernel)*iKernel; + + elseif OPTIONS.FFNormalization == 0 + + Kernel = read_gain(gainfile,OPTIONS.GoodChannel,ndx); % Chunk of gain matrix for this block of sources + + end + + % next chunk of source amplitudes + if i == 1 + if OPTIONS.NNormalization + coefsBaseline = coefs*Data.FBaseline; + end + coefs = coefs * Data.F; + + end + % ESEN - Add Noise normalization in BST Mode + if isempty(find(Kernel==NaN)) & isreal(Kernel) % Check Kernel integrity + if OPTIONS.NNormalization + % Compute source baseline stats + tmp = (Kernel'*coefsBaseline); + mean_Baseline= mean(tmp,2); + std_Baseline = std(tmp,0,2); + + % Plain source ampltiudes outside baseline + ImageGridAmp(ndx, :) = (Kernel'*coefs); + + % Proceed to Z-score normalization + iStd = spdiags(1./std_Baseline, 0, length(std_Baseline), length(std_Baseline)) ; % variance normalization + ImageGridAmp(ndx, :) = single(full(iStd * (double(ImageGridAmp(ndx, :)) - double(repmat(mean_Baseline, 1, size(ImageGridAmp, 2)))))); + + + clear tmp mean_Baseline std_Baseline + else + ImageGridAmp(ndx,:) = single(Kernel'*coefs); % 07-Oct-2002 SB : now stores under 'single' format (space saver) + end + else + ImageGridAmp(ndx,:) = NaN; + if OPTIONS.Verbose + bst_message_window('wrap',{... + 'XXXX Imaging Kernel is deficient, filling source amplitudes with NaNs.'}) + end + end + + clear Kernel + + end + + if nsrc > OPTIONS.BLOCK & ~isempty(OPTIONS.GUIHandles) + close(hwaitbar); + end + +else % Compute imaging Kernel only + + ImageGridAmp = []; + + if OPTIONS.Verbose + bst_message_window({... + 'Computing Minimum-Norm Imaging Kernel. . .',... + sprintf('Image support: %.0f grid points . . .',nsrc)... + }) + end + + % Load and possibly normalize forward fields (Kernel) + if OPTIONS.FFNormalization == 1 + clear Kernel + if OPTIONS.DataType == 1 + Chans = OPTIONS.MEGChannel; + elseif OPTIONS.DataType == 2 + Chans = OPTIONS.EEGChannel; + elseif OPTIONS.DataType == 3 + Chans = OPTIONS.GoodChannel; + end + + Kernel = read_gain(gainfile,Chans,1:nsrc); % Chunk of gain matrix for this block of sources + + if OPTIONS.DataType == 3 + Kernel = Gain_RowNorm * Kernel; + end + + OPTIONS.iG = sqrt(norcol(Kernel)'); % NORCOL computes the column norms to the SQUARE + OPTIONS.iG(OPTIONS.iG == 0) = min(OPTIONS.iG(OPTIONS.iG ~= 0)); + iKernel = spdiags((1./OPTIONS.iG).^OPTIONS.ExpoiG, 0, length(OPTIONS.iG), length(OPTIONS.iG)) ; + Kernel = (Kernel*iKernel)*iKernel; + + elseif OPTIONS.FFNormalization == 0 + + Kernel = read_gain(gainfile,OPTIONS.GoodChannel,1:nsrc); % Chunk of gain matrix for this block of sources + + end + + + % ESEN - Add Noise normalization to Kernel only format too + + if OPTIONS.NNormalization + tmp = (Kernel'*coefs*Data.FBaseline); + mean_Baseline= mean(tmp,2); + std_Baseline = std(tmp,0,2); + tmp = (Kernel'*coefs); + +% ImagingKernel = (tmp - repmat(mean_Baseline, 1, size(tmp, 2))) ./ repmat(std_Baseline, 1, size(tmp, 2)); + ImagingKernel = tmp ./ repmat(std_Baseline, 1, size(tmp, 2)); + ImagingKernel = single(ImagingKernel); + clear tmp mean_Baseline std_Baseline + else + ImagingKernel = single(Kernel'*coefs); + end + + clear Kernel + +end + + + +if OPTIONS.Verbose + bst_message_window({... + 'Inverse Transformation -> DONE.'... + }) +end + +% +% MN estimate - DONE -------------------------------------------------------------------------------------------------------------------------- + + +% Save Results in Result file ----------------------------------------------------------------------------------------------------------------- +% +% Fields from Results structure +Channel = OPTIONS.Channel; +ChannelFlag = Data.ChannelFlag; +Comment = sprintf('MIN NORM, rank %.0f',OPTIONS.Rank); +DataFile = OPTIONS.DataFile; +Date = datestr(datenum(now),0); +Time = [OPTIONS.SegmentIndices]; +ImageGridTime = Data.Time(Time); +ImageGridAmp = ImageGridAmp; % monster matrix when ImagingKernel is not computed +Function = mfilename; % name of this calling routine +HeadModelFile = OPTIONS.HeadModelFile; +ModelGain = HeadModel.Gain{1}; +Projector = Data.Projector; +SourceLoc = HeadModel.GridLoc{1}; +SourceOrder = -1; + +NoiseCov = []; +SourceCov = []; +PatchNdx = []; +PatchAmp = []; + + +% Reduced Results structure for call from bst_sourceimaging. +Results = struct('Comment',Comment,'Function',mfilename,... + 'OPTIONS',OPTIONS,'Date','','Time',Time,'HeadModelFile',HeadModelFile,... + 'Channel',Channel,'ChannelFlag',Data.ChannelFlag,'DataFile',OPTIONS.DataFile,... + 'NoiseCov',NoiseCov,'SourceCov',SourceCov,... + 'Projector',Projector,'SourceLoc',SourceLoc,'SourceOrder',SourceOrder,... + 'SourceOrientation',[],'TimeSeries',[],'ImagingKernel',ImagingKernel,'ModelGain',ModelGain,... + 'PatchNdx',PatchNdx,'PatchAmp',PatchAmp, ... + 'ImageGridAmp',ImageGridAmp,'ImageGridTime',ImageGridTime,'Fsynth',Fsynth); + +return + +% ----------------------------------------------------------------------------------------------------- +% +% +% SubFunctions +% +% +% ----------------------------------------------------------------------------------------------------- + +function varargout = gainmat_covar(varargin) + +% GAINMAT_COVAR Computation of gain matrix covariance +% function [GainCovar] = gainmat_covar(gainfile,OPTIONS) +% Computation of gain matrix covariance +% +% INPUTS +% +% gainfile : a string - filename of the binary gain matrix file (imaging grid) +% OPTIONS : a structure specifying the options for the computation +% .Verbose : turn on/off verbose mode (1/0) +% .DataType: scalar specifying the type of data +% 1 is MEG +% 2 is EEG +% 3 is MEG/EEG fusion +% .FFNormalization : flag specifying whether forward field normalization is requested +% 0 off +% 1 on +% .GoodChannel : Indices of good channels for current DataType +% .MEGChannel : Indices of all MEG channels +% .EEGChannel : Indcies of all EEG channels +% +% OUTPUTS +% GainCovar : a structure with the proper gain covar matrix outputs +% +% /---Script Author--------------------------------------\ +% | *** John C. Mosher, Ph.D. | +% | Biophysics Group | +% | | +% | *** Sylvain Baillet, Ph.D. | +% | Cognitive Neuroscience & Brain Imaging Laboratory | +% | CNRS UPR640 - LENA | +% | Hopital de la Salpetriere, Paris, France | +% | sylvain.baillet@chups.jussieu.fr | +% \------------------------------------------------------/ +% +% Date of creation: October 2002 +% Script History ----------------------------------------------------------------------------------- +%--------------------------------------------------------------------------------------------------- + +% Map entries +gainfile = varargin{1}; +OPTIONS = varargin{2}; + +% Read number of sources and total number of channels +tmp = read_gain(gainfile,OPTIONS.GoodChannel(1),[]); +nsrc = length(tmp); clear tmp % Number of sources +tmp = read_gain(gainfile,[],1); +nchan = length(tmp); clear tmp % Number of channels + +% if OPTIONS.Verbose +% bst_message_window(sprintf('Image support: %.0f grid points . . .',nsrc)); +% end + +AAt = zeros(nchan); % Initialize GainCovar matrix +Gain_RowNorm = []; % Initialize matrix containing norms of gain matrix rows (useless and therefore empty if DataType ~= 3) + +for i = 1:OPTIONS.BLOCK:nsrc, + +% if OPTIONS.Verbose & ~isempty(OPTIONS.GUIHandles) +% waitbar(i/nsrc,OPTIONS.hwaitbar); +% end + + ndx = [0:(OPTIONS.BLOCK-1)]+i; + if(ndx(end) > nsrc), % last block too long + ndx = [ndx(1):nsrc]; + end + + % Normalize EEG and MEG forward fields separately + % Fusion Case + if OPTIONS.DataType == 3 % Compute gain matrix row norm on first block only + if i == 1 + Gain_RowNorm = zeros(length(OPTIONS.Channel)); + if ~isempty(OPTIONS.GUIHandles) + hkk = waitbar(0,'Processing Lead-Field Normalization...'); + end + + tmp = inorcol((read_gain(gainfile,OPTIONS.GoodChannel,1:nsrc))'); + Gain_RowNorm(OPTIONS.GoodChannel,OPTIONS.GoodChannel) = diag(diag([tmp])); + if 0 + for kk = OPTIONS.GoodChannel + Gain_RowNorm(kk,kk) = 1/norm(read_gain(gainfile,kk,1:nsrc)); + if ~isempty(OPTIONS.GUIHandles) + waitbar((kk-OPTIONS.GoodChannel(1)+1)/length(OPTIONS.GoodChannel)) + end + end + end + + if ~isempty(OPTIONS.GUIHandles) + close(hkk) + end + end + + temp = Gain_RowNorm([OPTIONS.GoodChannel],[OPTIONS.GoodChannel])*read_gain(gainfile,[OPTIONS.GoodChannel],ndx); + OPTIONS.iG = sqrt(norcol(temp)'); % NORCOL computes the column norms to the SQUARE + OPTIONS.iG(OPTIONS.iG == 0)= min(OPTIONS.iG(OPTIONS.iG ~= 0)); + OPTIONS.iG = spdiags((1./OPTIONS.iG).^OPTIONS.ExpoiG, 0, length(OPTIONS.iG), length(OPTIONS.iG)) ; + AAt(OPTIONS.GoodChannel,OPTIONS.GoodChannel) = AAt(OPTIONS.GoodChannel,OPTIONS.GoodChannel) + temp*(OPTIONS.iG*OPTIONS.iG)*temp'; % next chunk of correlations + + else % MEG | EEG + temp = read_gain(gainfile,[OPTIONS.MEGChannel],ndx); + if ~isempty(OPTIONS.MEGChannel) & ~isempty(temp) + temp(find(isnan(temp))) = 0; + if OPTIONS.FFNormalization % Forward field normalization is requested + OPTIONS.iG = sqrt(norcol(temp)'); % NORCOL computes the column norms to the SQUARE + OPTIONS.iG(OPTIONS.iG == 0)= min(OPTIONS.iG(OPTIONS.iG ~= 0)); + OPTIONS.iG = spdiags((1./OPTIONS.iG).^OPTIONS.ExpoiG, 0, length(OPTIONS.iG), length(OPTIONS.iG)) ; + AAt(OPTIONS.MEGChannel,OPTIONS.MEGChannel) = AAt(OPTIONS.MEGChannel,OPTIONS.MEGChannel) + temp*(OPTIONS.iG*OPTIONS.iG)*temp'; % next chunk of correlations + else % no FF normalization + AAt(OPTIONS.MEGChannel,OPTIONS.MEGChannel) = AAt(OPTIONS.MEGChannel,OPTIONS.MEGChannel) + temp*temp'; % next chunk of correlations + end + end + + clear temp OPTIONS.iG + + temp = read_gain(gainfile,OPTIONS.EEGChannel,ndx); + if ~isempty(OPTIONS.EEGChannel) & ~isempty(temp) + temp(find(isnan(temp))) = 0; + if OPTIONS.FFNormalization + OPTIONS.iG = sqrt(norcol(temp)'); % NORCOL computes the column norms to the SQUARE + if (min(OPTIONS.iG(:)) == max(OPTIONS.iG(:))) & length(OPTIONS.iG) > 1 % as it happens when EEG channels stand for ECoG or EMG + OPTIONS.iG = eps; + end + + OPTIONS.iG(OPTIONS.iG == 0) = min(OPTIONS.iG(OPTIONS.iG ~= 0)); + + OPTIONS.iG = spdiags((1./OPTIONS.iG).^OPTIONS.ExpoiG, 0, length(OPTIONS.iG), length(OPTIONS.iG)) ; + AAt(OPTIONS.EEGChannel,OPTIONS.EEGChannel) = AAt(OPTIONS.EEGChannel,OPTIONS.EEGChannel) + temp*(OPTIONS.iG*OPTIONS.iG)*temp'; % next chunk of correlations + else + AAt(OPTIONS.EEGChannel,OPTIONS.EEGChannel) = AAt(OPTIONS.EEGChannel,OPTIONS.EEGChannel) + temp*temp'; % next chunk of correlations + end + end + + clear temp OPTIONS.iG + + end % datatype + +end % for each block of sources + +varargout{1} = AAt; +varargout{2} = Gain_RowNorm; + + + +% ----------------------------------------------------------------------------------------------------------------------- diff --git a/concatenatepatch.m b/concatenatepatch.m new file mode 100644 index 0000000..d93988f --- /dev/null +++ b/concatenatepatch.m @@ -0,0 +1,35 @@ +function NFV = concatenatepatch(FV1,varargin) +%CONCATENATEPATCH - Concatenate FV patch structures +% [NFV] = concatenatepatch(FV1,FV2,FV3,...) +% will concatenate FV's .vertices fields and re-index .faces ones +% Additional field will be concatenated also (if possible) +% See also: patch + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2005 +% This program is free software; you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation; either version 2 of the License, or +% (at your option) any later version. +% +% ----------------------------- Script History --------------------------------- +% KND 2005-12-11 Creation +% +% ----------------------------- Script History --------------------------------- + +NFV=FV1; +fn=fieldnames(NFV); +nfn=length(fn); +for i=2:nargin + nv=size(NFV.vertices,1); + NFV.vertices=[NFV.vertices; getfield(varargin{i-1}, 'vertices')]; + NFV.faces=[NFV.faces; getfield(varargin{i-1}, 'faces')+nv]; + for j=1:nfn + try + NFV=setfield(NFV, fn{j}, [ getfield(NFV, fn{j}) ; getfield(varargin{i-1}, fn{j}) ]); + catch + warning(sprintf('Can''t concatenate field: %s', fn{j})); + end + end + +end \ No newline at end of file diff --git a/continuity.m b/continuity.m new file mode 100644 index 0000000..75df912 --- /dev/null +++ b/continuity.m @@ -0,0 +1,41 @@ +function [y]=continuity(x,l,dim,lm) +%continuity - Assess minimal continuity in data +% [y]=continuity(x,w,dim,wm) +% x: ND-array of logical values (0/1) +% w: length of consecutive values explored +% dim: dimension of x which is explored. Default: 1 +% wm: minimal true values in that window. Default: wm=w +% +% NB: This function is especially useful for assessing a minimal number of +% consecutive samples below a given significance threshold. +% E.g: p is a Nc-channels x Nt-samples array of p values +% To assess a minimal length of 10 consecutive samples: +% continuity(p,10,2) + +% KND, 29-Aug-2005 + +if nargin<3 + dim=1; +end +if nargin<4 + lm=l; +end + +sx=size(x); +x=logical(x); +x=nd2array(x,dim); + +y=logical(zeros(size(x))); + +mx=sx(dim); +for i=1:sx(dim) + t=[-l+1:0]; + if i=lm; + t=t+1; + end +end +y=nd2array(y,-dim,sx); diff --git a/corrcoef2.m b/corrcoef2.m new file mode 100644 index 0000000..a379fe7 --- /dev/null +++ b/corrcoef2.m @@ -0,0 +1,147 @@ +function [r,p,t,z,p_yy,z_yy,r_yy]=corrcoef2(X,Y,sp) +%CORRCOEF2 - Iterative cap function for CORRCOEF +% [r,p,t,z]=corrcoef2(X,Y) computes the Pearson's correlation +% coefficients between the columns of X (a M-by-N matrix) and columns of +% Y (a M-by-P one) leading to a N-by-P matrix. +% r and p have the same size as the rest of Y. +% T-value t is computed as: t = r.*sqrt((n-2)./(1-r.^2)); +% Z-score z is : z = log(abs((r+1)./(r-1)))/2 +% [ref: http://faculty.chass.ncsu.edu/garson/PA765/correl.htm#faq] +% +% [r,p,t,z,p_yy,z_yy,r_yy]=corrcoef2(X,Y) also reports significant +% difference between correlations coefficients derived from Y columns. +% This requires that X is a M-by-1 vector. +% Ref: Comparing correlated correlation coefficients, Meng, 1992 +% +% [...] = corrcoef2(X,Y,sp) draws scatterplots (if sp==true) +%See also: corrcoef + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2010 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2010-02-04 Creation +% +% ----------------------------- Script History +% --------------------------------- + +sX=size(X); +sY=size(Y); +Y=Y(:,:); +if isvector(X) + X=X(:); + sX=size(X); + if numel(X)~=sY(1) + error('KND:corrcoef2:XYmismatch', 'The length of X and the number of lines in Y must match.'); + end +else + if ~isequal(sX(3:end),sY(3:end)) + error('KND:corrcoef2:XYmismatch', 'The remaining dimensions of X and Y must match.'); + end + X=X(:,:); +end +r=NaN*zeros([sX(2) sY(2:end)]); +p=r; +for ix=1:sX(2) + for iy=1:sY(2) + [r2,p2]=corrcoef(X(:,ix),Y(:,iy)); + r(ix,iy)=r2(2); + p(ix,iy)=p2(2); + end +end + +if nargout>2 + n=sX(1); + t = r.*sqrt((n-2)./(1-r.^2)); + if nargout>3 + z = log(abs((r+1)./(r-1)))/2; + end +end + +if nargout > 4 + if sX(2) > 1 + error('KND:corrcoef2:Xvectorfor', 'X must be a vector of observations for comparing regressors in Y.'); + end + nY=prod(sY(2:end)); + + % pairs of Y's + yy = nchoosek(1:nY,2); + % indices of the yy + idx = find(tril(ones(nY),-1)); + % cross-correlations in Y + r_yy = corrcoef(Y); + r_yy = r_yy(idx); + + r2 = sum(r(yy).^2')'/2; + f = (1-r_yy)./2./(1-r2); + f(f>1)=1; + h = 1 + r2./(1-r2).*(1-f); + + z_yy=diag(Inf*ones(nY,1)); + + z_yy(idx) = diff(z(yy)')'.*sqrt((n-3)./2./(1-r_yy)./h); + z_yy = z_yy + -z_yy'; + + %z_yy(z_yy<0)=-Inf; + p_yy = 0.5 * erfc(z_yy ./ sqrt(2)); + +end +if nargin<3 + return +end +% Scatterplots +nx=size(X,2); +ny=size(Y,2); +for i=1:nx + for j=1:ny + subplot(nx,ny,(j-1)*nx+i) + plot(Y(:,j),X(:,i),'x') + axis square + end +end + + +return + +%% avoid using corrcoef + +% ToDO: Implement directly corrcoef code down here. + +t = isnan(x); +% Compute correlation for each pair +r = eye(m,class(x)); +n = diag(sum(t,1)); +jk = 1:2; +for j = 2:m + jk(1) = j; + for k=1:j-1 + jk(2) = k; + tjk = ~any(t(:,jk),2); + njk = sum(tjk); + if njk<=1 + rjk = NaN; + else + rjk = correl(x(tjk,jk)); + rjk = rjk(1,2); + end + r(j,k) = rjk; + n(j,k) = njk; + end +end +r = r + tril(r,-1)'; +n = n + tril(n,-1)'; + +% ------------------------------------------------ +function [r,n] = correl(x) +%CORREL Compute correlation matrix without error checking. + +[n,m] = size(x); +c = cov(x); +d = sqrt(diag(c)); % sqrt first to avoid under/overflow +dd = d*d'; dd(1:m+1:end) = diag(c); % remove roundoff on diag +r = c ./ dd; + diff --git a/cov2.m b/cov2.m new file mode 100644 index 0000000..cf2c288 --- /dev/null +++ b/cov2.m @@ -0,0 +1,28 @@ +function V = cov2(X,varargin) +%COV2 - Multidimensional Covariance +% [V] = cov2(X) +% Computes the M-by-M-by-[...] covariance matrix of data X, which are +% given as N-by-M-by-[...] matrix , N observations and M variables. +% Example +% >> V=cov2(X(:,:,:)) +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2006 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2006-02-14 Creation +% +% ----------------------------- Script History --------------------------------- + +sX=[size(X) 1]; +X=reshape(X,sX(1),sX(2),prod(sX(3:end))); +V=zeros(sX(2), sX(2), prod(sX(3:end))); +for i=1:size(X,3); + V(:,:,i)=cov(X(:,:,i),varargin{:}); +end \ No newline at end of file diff --git a/curvature_cortex.m b/curvature_cortex.m new file mode 100644 index 0000000..af75c2f --- /dev/null +++ b/curvature_cortex.m @@ -0,0 +1,134 @@ +function [curvature_sigmoid,curvature]=curvature_cortex(arg1,arg2,arg3,arg4) +%CURVATURE_CORTEX - calculates cortex curvature +% function [curvature_sigmoid,curvature]=curvature_cortex(arg1,arg2,arg3,arg4) +% +% function [curvature_sigmoid,curvature]=curvature_cortex(FV,VertConn,sigmoid_const,show_sigmoid) +% FV is the Faces/Vertices structure used to calculate curvature +% VertConn is the vertices connectivity of FV +% sigmoid_const is the sigmoid coefficient (small values give smooth transition of convex to concave areas +% and vice versa). Use close to zero for linear transition. +% show_sigmoid chooses whether to show the sigmoid (1) or not (0) +% +% curvature returned is the surface curvature +% curvature_sigmoid returned is the curvature weighted by a sigmoid +% +% function [curvature_sigmoid]=curvature_cortex(curvature,sigmoid_const,show_sigmoid) +% Used to change the transition of positive and negative curvatures +% +% Remarks: Curvature_cortex uses an approximation of mean curvature. It calculates the mean angle between +% the surface normal of a vertex and the edges formed by the vertex and the neighbouring ones. +% +% See also VERTICES_CONNECTIVITY + +% ---------------------- 26-May-2004 11:29:55 ----------------------- +% --------- Automatically Generated Comments Block Using AUTO_COMMENTS --------- +% +% CATEGORY: Visualization +% +% Alphabetical list of external functions (non-Matlab): +% toolbox\colnorm.m +% +% At Check-in: $Author: Mosher $ $Revision: 8 $ $Date: 5/26/04 9:59a $ +% +% This software is part of BrainStorm Toolbox Version 2.0 (Alpha) 24-May-2004 +% +% Principal Investigators and Developers: +% ** Richard M. Leahy, PhD, Signal & Image Processing Institute, +% University of Southern California, Los Angeles, CA +% ** John C. Mosher, PhD, Biophysics Group, +% Los Alamos National Laboratory, Los Alamos, NM +% ** Sylvain Baillet, PhD, Cognitive Neuroscience & Brain Imaging Laboratory, +% CNRS, Hopital de la Salpetriere, Paris, France +% +% See BrainStorm website at http://neuroimage.usc.edu for further information. +% +% Copyright (c) 2004 BrainStorm by the University of Southern California +% This software distributed under the terms of the GNU General Public License +% as published by the Free Software Foundation. Further details on the GPL +% license can be found at http://www.gnu.org/copyleft/gpl.html . +% +% FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE +% UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY +% WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF +% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY +% LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE. +% ------------------------ 26-May-2004 11:29:55 ----------------------- + +% Author: Dimitrios Pantazis, Ph.D. + +% ----------------------------- Script History --------------------------------- +% DP 05-May-2002 Creation +% JCM 19-May-2004 Commenting +% ----------------------------- Script History --------------------------------- + + +%choose whether to display bars +if(~exist('VERBOSE','var')), + VERBOSE = 1; % default non-silent running of waitbars +end + +narg = nargin; +if narg==4 + %assign inputs + FV=arg1; + VertConn=arg2; + sigmoid_const=arg3; + show_sigmoid=arg4; + + %ititialization + nVertices=size(FV.vertices,1); + + %calculate normals and curvature of FV + hf=figure('Visible','off'); + hp=patch('faces',FV.faces, 'vertices', FV.vertices); + normals=get(hp,'VertexNormals'); + close(hf); + %Make the normals unit norm + [nrm,normalsnrm]=colnorm(normals'); + + if(VERBOSE) + hwait = waitbar(0,sprintf('Calculating curvature...')); + drawnow %flush the display + step=round(nVertices/10); + end + %compute average angle on each vertex + curvature=zeros(nVertices,1); + curvature_sigmoid=zeros(nVertices,1); + for i=1:nVertices %for all vertices + if(VERBOSE) + if(~rem(i,step)) % ten updates + waitbar(i/nVertices,hwait,sprintf('Calculating curvature...')); + drawnow %flush the display + end + end + nNeighbours=length(VertConn{i}); %number of neighbours + edgevector=FV.vertices(VertConn{i},:)-repmat(FV.vertices(i,:),nNeighbours,1); %vectors joining vertex with neighbours + [nrm,edgevector]=colnorm(edgevector'); + curvature(i)=mean(acos(normalsnrm(:,i)'*edgevector))-pi/2; + curvature_sigmoid(i)= 1./(1+exp(-curvature(i).*sigmoid_const))-0.5; + end + close(hwait); +end + +if narg==3 + %assign inputs + curvature=arg1; + sigmoid_const=arg2; + show_sigmoid=arg3; + + curvature_sigmoid=zeros(length(curvature),1); + for i=1:length(curvature) + curvature_sigmoid(i)= 1./(1+exp(-curvature(i).*sigmoid_const))-0.5; + end +end + +if(exist('show_sigmoid')) + if(show_sigmoid) + x=-pi/2:0.01:pi/2; + y=1./(1+exp(-x*sigmoid_const))-0.5; + figure; + plot(x,y) + grid on; + title('Transition between negative and positive curvature'); + end +end diff --git a/data/COLORS.mat b/data/COLORS.mat new file mode 100644 index 0000000000000000000000000000000000000000..1e330cd8a2cda6024270782846a32e39ec646248 GIT binary patch literal 488 zcmeZu4DoSvQZUssQ1EpO(M`+DN!3vZ$Vn_o%P-2cQV4Jk_w>_Ia4t$sEJ;mK$j`G< z2u>_fa4bz%Ffvv!G`BJ_wK6nNFfuSORv;NLFnap(F)%PBFfuT(0cj2(1~S2b6-dJP zAT~%Gq{i9b$3G~TfdQn?04fGj#{$G4fUFOs2WB1%0~13wkWY@jA~N+QLG^h6X_)^& lGzcKOlNrc|xswyfmH=X7Ft}_#YbFT1w1@G@aepq*JOHV>D)|5a literal 0 HcmV?d00001 diff --git a/data/PSC.mat b/data/PSC.mat new file mode 100644 index 0000000000000000000000000000000000000000..14d18c4c2c477e1bac51ee6a10a24a166da10b85 GIT binary patch literal 416 zcmb76%Wi^D5IxwcUG*F01JGQhHsp4NxG^b)R1()zE-49*1~C4TpXH}GW5I=qi_YZa zJnoqrc+sb~q{UQG;6;-(%Z*K89=B;#7n(wUx8B&~*CuXFLRG0L`Z>_b>`A?mde_R) zj!Sc;T>AIGS_iKH6l27A>6i(_&I`*}ctW`KG5E8PyECJ|d+zf7&SD=igvX4;H2XUH zh4fC0FH|_9fd;{6T_Qn-mLa;q2j{Z#mg9ipno4`i?i;hKGTan3xw!>w)_rS!%kNuL K$o9R*zg+;>5IU^@ literal 0 HcmV?d00001 diff --git a/data/SUJETS.mat b/data/SUJETS.mat new file mode 100644 index 0000000000000000000000000000000000000000..080255be2f5626b8fe71d579ed8ade7f98c26d23 GIT binary patch literal 208 zcmeZu4DoSvQZUssQ1EpO(M`+DN!3vZ$Vn_o%P-2cQV4Jk_w>_Ia4t$sEJ;mK$j`G< za4X7Ga7#^6Ff>*$Ft#!^w=%I*FfuSOQ6L#GFnap(F)%QA05Ka7b3kbz#lQ;XGXiOD jAO_JOHNl}?t|7q;3`|gQb`DN1ZeBiq0U=?a2m=EEA8;O9 literal 0 HcmV?d00001 diff --git a/data/nicehead-CTF.tri b/data/nicehead-CTF.tri new file mode 100644 index 0000000..ada28e3 --- /dev/null +++ b/data/nicehead-CTF.tri @@ -0,0 +1,3514 @@ +- 1172 +-1.2359 -0.827349 -1.20053 -0.364768 -0.371413 -0.853812 +-1.27233 -0.83377 -1.1463 0.0958058 -0.977372 0.188589 +-1.3548 -0.825012 -1.10122 -0.0797913 -0.950972 0.298808 +-1.15337 -0.813712 -1.175 0.330552 -0.938551 0.0992835 +-1.32702 -0.805088 -1.0607 0.116169 -0.818284 0.562954 +-0.285288 -0.905168 0.288119 -0.0942633 -0.990741 -0.0977043 +-1.41675 -0.789656 -1.04704 -0.109142 -0.766753 0.632597 +-1.32273 -0.754917 -1.14699 -0.48247 0.046785 -0.874662 +-0.109435 -0.880441 0.277909 0.423928 -0.897906 0.118536 +-0.213645 -0.905775 0.308438 0.127182 -0.965149 0.228719 +-1.42261 -0.799278 -1.07031 -0.345797 -0.910439 0.22699 +-0.321233 -0.891907 0.301329 -0.398785 -0.883543 0.245607 +-0.310574 -0.895299 0.256954 0.0415369 -0.89525 -0.443624 +-0.2685 -0.887275 0.134708 0.302073 -0.929519 -0.211533 +-0.393783 -0.870347 0.211847 -0.524914 -0.85112 -0.0076477 +-1.0302 -0.745341 -1.2769 -0.24781 -0.129184 -0.960157 +-0.395854 -0.863132 0.157732 -0.109895 -0.951309 -0.287984 +-0.272878 -0.896002 0.323357 -0.163068 -0.719297 0.675293 +-0.196627 -0.879444 0.238004 0.246605 -0.957095 -0.152168 +-0.259758 -0.878544 0.257024 -0.0129917 -0.970645 -0.240166 +-0.292114 -0.883474 0.133216 -0.214258 -0.972925 0.0866636 +-0.296613 -0.880651 0.100526 -0.431136 -0.888666 -0.156184 +-0.155995 -0.889004 0.306886 0.345667 -0.612302 0.711056 +-0.388285 -0.843405 0.263553 -0.770845 -0.016509 0.636807 +-1.49241 -0.756913 -1.04178 -0.429776 -0.794222 0.429539 +-0.333465 -0.85873 0.177266 -0.245426 -0.965447 -0.0876288 +-0.381519 -0.869408 0.257346 -0.673229 -0.606439 0.423077 +-1.13468 -0.799517 -1.24052 -0.329415 -0.0540767 -0.942635 +-1.27362 -0.6669 -0.895239 -0.0508519 -0.768149 0.638249 +-0.356382 -0.836381 0.0436771 -0.258373 -0.953319 -0.156287 +-0.322252 -0.876857 0.31564 -0.524366 -0.286358 0.801897 +-0.22582 -0.85774 0.116125 0.647207 -0.613852 -0.452005 +-1.10725 -0.781242 -1.1451 0.453256 -0.788786 0.415182 +-0.294142 -0.865171 0.0498924 0.310103 -0.878156 -0.364252 +-0.231064 -0.878181 0.337062 0.125558 -0.333 0.93453 +-0.100295 -0.870436 0.279115 0.662982 -0.335506 0.669246 +-1.47202 -0.777331 -1.08189 -0.650771 -0.7351 -0.190067 +-0.173244 -0.858716 0.204286 0.59134 -0.678262 -0.436207 +-0.262572 -0.860215 0.338956 -0.165971 -0.00530734 0.986116 +-0.169588 -0.87883 0.250664 0.262362 -0.841497 -0.47228 +-0.426216 -0.819012 0.0532324 -0.701124 -0.690497 -0.177877 +-0.0854229 -0.867267 0.234511 0.230063 -0.876283 -0.423318 +-0.292272 -0.821039 -0.0339925 0.615762 -0.704666 -0.35254 +-1.38559 -0.818795 -1.12327 -0.447657 -0.100045 -0.888591 +-0.143496 -0.8896 0.267255 -0.0272134 -0.851994 -0.522844 +-0.411407 -0.846263 0.220795 -0.88666 -0.435616 0.155151 +-1.02868 -0.755182 -1.23984 0.529554 -0.843185 -0.0927938 +-1.28674 -0.702375 -1.17157 -0.401319 0.0849897 -0.911987 +-0.0565295 -0.841226 0.214383 0.792299 -0.609383 0.0302523 +-1.55966 -0.688178 -1.00162 -0.657958 -0.692548 0.295753 +-0.12937 -0.795517 0.166933 -0.0976969 -0.850734 -0.516436 +-0.178455 -0.864581 0.328767 0.421597 -0.239044 0.874708 +-0.328994 -0.837397 0.315287 -0.494607 0.0483226 0.867773 +-0.334915 -0.825126 -0.0253208 -0.190961 -0.929027 -0.316927 +-0.0592657 -0.802121 0.267736 0.690275 -0.518792 0.504357 +-0.395518 -0.801951 -0.0386922 -0.481473 -0.779308 -0.401078 +-0.296161 -0.780015 0.322405 -0.607592 -0.0638641 0.791678 +-0.442344 -0.810542 0.11042 -0.996815 -0.0796201 -0.00441821 +-1.36371 -0.575791 -1.11657 -0.438707 0.0414482 -0.897673 +-0.269328 -0.779383 -0.1646 0.148488 -0.952519 -0.265817 +-1.02949 -0.751814 -1.19866 0.570893 -0.785179 0.239947 +-0.222196 -0.77259 0.0659991 0.74937 -0.388432 -0.53625 +-0.270614 -0.803423 -0.0682701 0.605791 -0.629069 0.487124 +-0.296388 -0.772025 -0.192291 -0.376679 -0.804951 -0.458439 +-1.19411 -0.801441 -1.10661 0.273628 -0.824176 0.495844 +-0.181442 -0.797359 0.333337 0.144338 -0.0614898 0.987616 +-0.0335159 -0.790274 0.149853 0.563022 -0.771829 -0.295442 +-0.429385 -0.784644 0.14725 -0.802774 0.56613 0.187214 +-0.41524 -0.782141 -0.0188495 -0.930736 -0.0956803 -0.352952 +-0.367722 -0.823333 0.28086 -0.691586 0.368424 0.621266 +-0.360244 -0.744734 0.232488 -0.867765 0.181528 0.462635 +-0.0821984 -0.805888 0.135413 -0.273243 -0.808399 -0.521373 +-0.330218 -0.735226 -0.176565 -0.777711 -0.241042 -0.580574 +-0.165506 -0.792374 0.337425 0.0245186 -0.999038 -0.0363747 +-0.423156 -0.758263 0.0443737 -0.825621 0.560077 -0.0682991 +-0.155628 -0.807592 0.624051 -0.147662 -0.988578 -0.0301418 +-0.184772 -0.801194 0.75434 -0.075895 -0.996153 0.0437981 +-0.21149 -0.780275 -0.118017 0.344451 -0.926684 -0.150367 +-0.243933 -0.795506 0.332936 -0.113911 -0.306432 0.945052 +-0.0983164 -0.795096 0.309668 0.435619 -0.3699 0.820616 +-0.163972 -0.790848 0.375573 -0.146024 -0.988143 -0.0474334 +-0.0536123 -0.785463 0.111641 0.235585 -0.854666 -0.462651 +0.073629 -0.811028 0.743787 0.0063691 -0.999865 0.0152027 +-0.419229 -0.800511 0.191837 -0.844159 0.305884 0.440262 +-0.165182 -0.771795 0.111561 0.613778 -0.546305 -0.569936 +-0.290289 -0.774916 0.357872 -0.165531 -0.98611 -0.0136465 +-0.378991 -0.712485 0.0503074 -0.763073 0.646313 -0.000303211 +-0.409453 -0.76095 -0.0262836 -0.846504 0.441417 -0.297627 +0.148068 -0.802515 0.664402 0.0961896 -0.992107 -0.0804394 +-0.377018 -0.719282 0.153902 -0.987957 0.148907 0.0420303 +-0.0239991 -0.786371 0.170447 0.794544 -0.604909 0.0527869 +-0.235934 -0.736411 -0.0289695 0.765274 -0.61558 0.188195 +-0.0175752 -0.78206 0.270895 0.221866 0.952948 0.206558 +-0.255335 -0.761867 -0.230771 -0.296066 -0.720432 -0.627154 +-0.102098 -0.764737 0.0519576 -0.175893 -0.942036 -0.285709 +-1.11972 -0.70987 -1.0206 0.335363 -0.800003 0.497521 +-0.33858 -0.773902 0.607923 -0.247632 -0.968854 -0.00107743 +-0.234034 -0.74251 0.0210507 0.758518 -0.561615 -0.330514 +-0.152537 -0.743596 -0.113964 0.417146 -0.831742 0.366327 +-0.160476 -0.749378 -0.150201 0.453519 -0.875787 -0.165284 +-1.55829 -0.694437 -1.03557 -0.445669 0.0282841 -0.894751 +-0.313315 -0.766983 0.363419 -0.396033 -0.91582 -0.0665687 +-0.906507 -0.646887 -1.31995 0.228615 -0.540893 -0.809425 +-0.223254 -0.749656 -0.245359 0.159795 -0.565925 -0.808823 +-0.0497205 -0.794635 0.869837 -0.0373419 -0.958177 0.283731 +-1.20708 -0.684529 -0.93491 0.230646 -0.789361 0.568956 +-0.181715 -0.724463 -0.0255991 -0.248262 -0.963496 -0.100203 +-0.138183 -0.737589 -0.089144 0.108896 -0.976847 -0.184151 +-0.140274 -0.748919 -0.0276796 -0.143545 -0.977986 -0.151458 +-0.199844 -0.746121 -0.234172 0.37606 -0.902376 -0.210467 +-0.211099 -0.761216 -0.174436 0.39949 -0.881417 -0.252014 +-0.281479 -0.720077 -0.22856 -0.653256 0.0469955 -0.755676 +-0.231405 -0.730494 -0.257429 -0.379664 -0.239942 -0.893467 +0.292361 -0.7868 0.772007 0.146344 -0.988623 0.0347469 +-0.424382 -0.740473 0.686991 -0.388732 -0.921344 0.00337697 +-0.330003 -0.722418 -0.172392 -0.796017 0.449788 -0.405028 +-0.193862 -0.744053 0.0624071 -0.0422035 -0.936345 -0.348535 +-0.200695 -0.723789 -0.0462006 0.423734 -0.753229 0.503086 +-1.48711 -0.711671 -0.975251 -0.183654 -0.736056 0.651531 +-0.350274 -0.758836 0.77347 -0.313913 -0.932888 0.176578 +-0.113387 -0.724112 -0.139157 0.532552 -0.814657 -0.229612 +-0.88085 -0.622379 -1.16187 0.585384 -0.787634 0.192247 +-0.224417 -0.72571 -0.0215472 0.190865 -0.965675 -0.176189 +0.0712941 -0.790961 0.878396 0.0395164 -0.975737 0.215347 +0.224013 -0.701322 -0.148306 0.0814829 -0.97298 -0.216037 +-0.227545 -0.695829 -0.260314 -0.489434 -0.044127 -0.870923 +0.253944 -0.725798 0.00617885 0.130987 -0.983636 -0.123705 +-1.32618 -0.534135 -1.13708 -0.409588 0.0893186 -0.907887 +-0.196349 -0.730566 1.01631 -0.109485 -0.912749 0.393578 +-0.166129 -0.788803 0.858001 -0.187254 -0.952178 0.24144 +-0.383939 -0.717913 0.207685 -0.459395 -0.87588 -0.147618 +-0.0213035 -0.764547 0.0811933 0.0995572 -0.986747 -0.128138 +-1.23804 -0.60006 -0.80346 -0.0319543 -0.853267 0.520495 +-1.54087 -0.67178 -0.961717 -0.380859 -0.633395 0.673615 +-0.115472 -0.691422 -0.251445 0.120316 -0.955388 -0.269738 +-0.864536 -0.627994 -1.25402 0.643069 -0.76562 -0.0169153 +-0.185011 -0.688243 -0.274724 -0.0338855 -0.476558 -0.87849 +0.267223 -0.774629 0.864144 0.14048 -0.957236 0.252916 +-0.268726 -0.670011 -0.215829 -0.7621 0.342787 -0.549272 +-0.498543 -0.694909 0.756858 -0.506962 -0.848314 0.152821 +-0.829798 -0.576045 -1.08277 0.404892 -0.910012 0.0891112 +-0.993178 -0.667185 -1.0687 0.447814 -0.836537 0.315703 +0.49968 -0.747135 0.745255 0.242711 -0.964625 0.10291 +0.00263944 -0.725939 1.05904 -0.00814693 -0.907193 0.420636 +0.435509 -0.69547 0.0156324 0.195151 -0.969335 -0.14935 +0.160195 -0.751753 0.978193 0.111003 -0.920547 0.374529 +-0.152533 -0.677387 -0.290568 -0.0120037 -0.960674 -0.277417 +-1.10442 -0.640282 -0.902022 0.162041 -0.915926 0.367184 +-1.14084 -0.570079 -0.7318 -0.0316163 -0.917185 0.397205 +0.606929 -0.726806 0.592891 0.263317 -0.964436 -0.0229556 +-0.360559 -0.686893 0.0332631 -0.92038 -0.263299 -0.289094 +0.414729 -0.754505 0.826007 0.248011 -0.939903 0.234675 +0.530642 -0.738815 0.474495 0.144418 -0.985552 -0.0884941 +-0.315948 -0.72183 0.963194 -0.277686 -0.914192 0.2952 +-1.42477 -0.549645 -0.794606 -0.395025 -0.620915 0.677066 +0.372486 -0.733819 0.927452 0.221034 -0.897271 0.382164 +-0.505286 -0.682629 0.347068 -0.448653 -0.885544 -0.120506 +-0.555091 -0.671887 0.671744 -0.576619 -0.813788 0.0725289 +-0.958992 -0.595298 -0.865137 0.331081 -0.918244 0.217287 +-0.317274 -0.666894 -0.118796 -0.832291 0.4882 -0.26259 +0.343245 -0.680939 -0.159774 0.194989 -0.950958 -0.240122 +0.122607 -0.673575 -0.30251 0.043283 -0.96158 -0.271092 +-1.69645 -0.504019 -0.963207 -0.801522 -0.328941 -0.49936 +-0.793469 -0.395315 -1.34897 -0.383907 0.0728476 -0.920494 +-0.297302 -0.641751 -0.181851 -0.57343 -0.767095 -0.287651 +0.63846 -0.700254 0.367547 0.318646 -0.935152 -0.154778 +-0.136778 -0.661388 -0.331954 -0.293544 -0.905989 -0.304984 +-0.15892 -0.636773 1.19423 -0.113389 -0.82929 0.547194 +-0.410532 -0.678671 0.123544 -0.446966 -0.855704 -0.260753 +-0.511243 -0.70082 0.527881 -0.496211 -0.867127 -0.0431893 +0.568208 -0.677789 0.913018 0.323189 -0.879485 0.349361 +-0.920037 -0.578057 -0.807844 0.190441 -0.979511 0.0655084 +-0.252181 -0.6862 1.08384 -0.278013 -0.841421 0.463379 +0.668144 -0.705575 0.655221 0.327093 -0.944685 0.0241248 +0.436985 -0.675531 -0.0790195 0.360164 -0.898409 -0.251286 +-0.403525 -0.680474 0.977762 -0.394266 -0.857007 0.331801 +-0.47584 -0.682172 0.864437 -0.435077 -0.866764 0.243777 +-0.371296 -0.614191 -0.138623 -0.334927 -0.916757 -0.217669 +-1.59975 -0.618084 -0.953745 -0.527389 -0.578169 0.622561 +0.366918 -0.624296 -0.325909 0.28153 -0.887518 -0.364763 +-0.981014 -0.578129 -0.759241 0.0540465 -0.968008 0.24503 +0.25319 -0.640213 -0.357651 0.141562 -0.931839 -0.334119 +-0.101383 -0.602558 -0.550581 -0.0112101 -0.950855 -0.309433 +-0.837461 -0.562177 -0.729408 0.0301202 -0.997498 0.0639676 +-0.534061 -0.622494 0.930231 -0.572509 -0.751869 0.326996 +0.733685 -0.593024 0.0548392 0.392824 -0.875263 -0.282142 +-0.290184 -0.609492 -0.318563 -0.426018 -0.891817 -0.152221 +-1.09403 -0.551971 -0.67192 -0.206487 -0.924842 0.319422 +-0.243354 -0.654812 -0.258709 -0.375035 -0.864707 -0.334111 +-0.837392 -0.550973 -0.546001 -0.127987 -0.987826 0.0884252 +0.0191014 -0.666716 1.16761 0.0469305 -0.850425 0.523998 +-0.57825 -0.598457 0.0890663 -0.342288 -0.916269 -0.208068 +-0.756231 -0.539718 -1.00343 0.486495 -0.870789 -0.0710601 +-0.074533 -0.529274 -0.749165 -0.0385015 -0.904124 -0.425532 +-0.377943 -0.578085 -0.329164 -0.22436 -0.951275 -0.211517 +-0.712765 -0.45114 -1.38328 0.247326 -0.422351 -0.872037 +-1.45473 -0.448371 -1.06732 -0.404032 0.0921852 -0.910088 +-0.570304 -0.585092 -0.0173333 -0.201459 -0.977141 -0.0678972 +0.585848 -0.565601 -0.162477 0.58529 -0.753953 -0.298313 +0.43081 -0.65833 1.04478 0.273897 -0.799946 0.533916 +0.753739 -0.673863 0.552241 0.399675 -0.916294 -0.0258099 +-0.707783 -0.47322 -1.31678 0.677826 -0.716098 -0.166598 +-0.735997 -0.514389 -1.19178 0.628415 -0.776387 -0.0481434 +-0.679806 -0.562881 0.235205 -0.567783 -0.821701 -0.0492892 +-0.742407 -0.546693 -0.839793 0.261173 -0.959602 -0.104654 +-0.746529 -0.556422 -0.66131 0.0452462 -0.998262 -0.0377658 +-0.567385 -0.564613 -0.32281 -0.0496839 -0.996872 -0.0614585 +-1.1066 -0.433392 -1.21619 -0.397519 0.113905 -0.910497 +-0.42991 -0.54254 -0.461555 0.046441 -0.983212 -0.176457 +-0.637276 -0.590083 0.768384 -0.647141 -0.740972 0.179358 +0.500397 -0.607378 -0.2219 0.429193 -0.851594 -0.300968 +0.694547 -0.683944 0.7465 0.387046 -0.902069 0.190962 +-0.658518 -0.592892 0.629667 -0.668351 -0.743454 0.0241414 +-0.971458 -0.538821 -0.564231 -0.169078 -0.96742 0.188445 +0.223477 -0.687619 1.09034 0.159282 -0.860914 0.483173 +-0.45714 -0.621882 1.04165 -0.414508 -0.803414 0.427445 +-0.319466 -0.603912 1.17924 -0.26479 -0.769853 0.580699 +0.821919 -0.632605 0.458228 0.518057 -0.851436 -0.0816999 +0.0593572 -0.626851 1.22037 0.0464126 -0.773631 0.631935 +0.55911 -0.550894 -0.276654 0.506108 -0.78517 -0.356878 +0.774062 -0.641347 0.344482 0.42812 -0.88773 -0.169263 +-1.23332 -0.525416 -0.677626 -0.262528 -0.863747 0.43014 +-1.64422 -0.403427 -0.977508 -0.383223 0.0458231 -0.922519 +-0.794933 -0.523858 -0.239779 -0.297344 -0.947655 0.116351 +-0.139607 -0.487659 -0.796958 -0.444744 -0.734496 -0.512562 +-0.727596 -0.54563 -0.287325 -0.126582 -0.991743 0.0205909 +-0.480563 -0.533469 -0.524901 0.173311 -0.973945 -0.14627 +-0.531839 -0.465379 -0.868355 0.429009 -0.868048 -0.249887 +-0.426285 -0.510442 -0.572471 0.311669 -0.9149 -0.256553 +-0.403071 -0.600048 1.12937 -0.466298 -0.728844 0.50135 +-0.569637 -0.620845 0.188727 -0.454682 -0.880537 -0.133864 +-0.31306 -0.518935 -0.56104 -0.33905 -0.847893 -0.407582 +-1.00733 -0.47504 -0.364403 -0.399201 -0.863375 0.308581 +0.193791 -0.61444 1.2062 0.219467 -0.769764 0.599415 +-0.164823 -0.546854 -0.666502 -0.321852 -0.886908 -0.331367 +-0.694297 -0.543873 0.0201696 -0.40572 -0.913954 0.0089443 +-1.44763 -0.484379 -0.743776 -0.456822 -0.571923 0.681335 +-0.0313143 -0.570851 -0.64345 0.0984603 -0.932784 -0.346728 +-0.068518 -0.592813 1.26219 -0.0418808 -0.725462 0.686987 +-0.641995 -0.553792 0.867195 -0.71859 -0.639982 0.272126 +0.798552 -0.628726 0.745515 0.572329 -0.788051 0.226749 +0.539605 -0.613686 1.04313 0.412228 -0.711956 0.568495 +-0.691108 -0.499395 -1.07255 0.496649 -0.848543 -0.182525 +0.707281 -0.639494 0.856836 0.463725 -0.767915 0.441889 +-1.34948 -0.465904 -0.66125 -0.346783 -0.732611 0.585681 +-1.53004 -0.184907 -1.01366 -0.387359 0.0541812 -0.920335 +-0.211601 -0.551981 1.27834 -0.19983 -0.714822 0.670148 +0.204605 -0.524822 -0.629653 0.342136 -0.820903 -0.457232 +0.387279 -0.565528 -0.432185 0.368367 -0.815399 -0.446577 +0.808108 -0.56049 0.0755044 0.542015 -0.808509 -0.229202 +0.842098 -0.623054 0.58526 0.5703 -0.820247 0.0441981 +-0.563156 -0.515976 1.07606 -0.576731 -0.669458 0.468196 +0.613856 -0.585596 -0.0616774 0.65233 -0.454587 -0.606479 +-0.616193 -0.505535 1.01321 -0.694913 -0.605835 0.387374 +0.0410968 -0.505398 -0.771505 0.203886 -0.885577 -0.417352 +-0.360766 -0.502963 -0.570704 0.0420798 -0.926117 -0.374884 +-1.68023 -0.47855 -0.898502 -0.571984 -0.537617 0.619517 +0.622757 -0.537073 -0.0829898 0.804573 -0.459102 -0.37668 +-0.111731 -0.494943 -0.8032 -0.178599 -0.793075 -0.582353 +-0.815706 -0.486531 -0.0456312 -0.486086 -0.864348 0.128927 +0.161252 -0.566376 1.26786 0.2128 -0.673019 0.708351 +-0.764433 -0.499491 0.297434 -0.642977 -0.76587 0.00497657 +-0.346381 -0.544271 1.2315 -0.419486 -0.622253 0.660934 +0.880053 -0.577631 0.350318 0.630285 -0.766486 -0.123451 +-1.11459 -0.432794 -0.396068 -0.405422 -0.81344 0.41707 +-1.73648 -0.38909 -0.890378 -0.753806 -0.394613 0.52541 +0.0730888 -0.542604 1.30772 0.109569 -0.634946 0.764747 +0.870794 -0.486268 0.0235905 0.504895 -0.604095 -0.616564 +0.682639 -0.566102 -0.0284066 0.38302 -0.220787 -0.896967 +0.645994 -0.503669 -0.0957057 0.643261 -0.699403 -0.31153 +-1.18607 -0.357709 -0.369285 -0.589336 -0.632214 0.50298 +-0.891576 -0.472639 -0.177269 -0.410637 -0.881063 0.234746 +0.388098 -0.534656 1.21266 0.316383 -0.673152 0.668407 +-0.874171 -0.227795 -1.29612 -0.407693 0.081027 -0.909517 +0.858768 -0.552867 0.808261 0.636906 -0.663303 0.392911 +0.779517 -0.556477 0.91141 0.578702 -0.64959 0.493088 +0.624059 -0.521849 -0.113699 0.77064 -0.613211 -0.173456 +-0.966199 -0.397863 -0.10412 -0.584742 -0.770484 0.253833 +-0.130359 -0.53376 1.31135 -0.126348 -0.594718 0.793944 +-1.1431 -0.233864 -1.17651 -0.398723 0.0777519 -0.91377 +0.531108 -0.503025 -0.403689 0.542876 -0.681156 -0.491236 +-0.325792 -0.429026 -0.732823 -0.0945464 -0.865514 -0.491882 +0.122826 -0.443883 -0.848468 0.326902 -0.821493 -0.467209 +-0.0112365 -0.490366 1.35438 -0.0142688 -0.586069 0.810136 +-1.36705 -0.261368 -1.08391 -0.381554 0.0801628 -0.920864 +-0.742381 -0.486843 0.703792 -0.7866 -0.601584 0.139132 +-0.774802 -0.498808 0.0849351 -0.581551 -0.810876 0.0654157 +-0.84012 -0.42837 0.223121 -0.776143 -0.624748 0.0853948 +0.668471 -0.465954 -0.149703 0.7768 -0.575631 -0.255401 +0.916213 -0.556449 0.641944 0.698616 -0.712704 0.0631514 +0.638688 -0.539227 -0.0624873 0.42252 -0.538879 -0.728756 +0.915399 -0.476876 0.0911055 0.728913 -0.652079 -0.208518 +-0.511947 -0.491285 1.159 -0.529828 -0.593521 0.605819 +0.576213 -0.494656 1.14238 0.44084 -0.595985 0.671165 +0.951614 -0.505221 0.33349 0.713821 -0.680118 -0.167027 +0.81382 -0.456582 -0.0323555 0.478979 -0.572472 -0.665473 +-0.00101593 -0.433601 -0.913079 0.0513247 -0.829739 -0.555786 +-0.589323 -0.363293 -1.26127 0.637304 -0.724561 -0.262403 +0.608173 -0.48906 -0.310809 0.773156 -0.562543 -0.292874 +-0.27312 -0.511853 1.29316 -0.24737 -0.550381 0.797427 +-1.63535 -0.35066 -0.985573 -0.394674 0.0610726 -0.916789 +-1.68817 -0.406593 -0.852779 -0.555472 -0.450531 0.698908 +0.331709 -0.415644 -0.715189 0.464917 -0.749557 -0.471187 +-0.605702 -0.293175 -1.41927 0.739042 -0.487606 -0.464819 +-0.739872 -0.416706 0.890979 -0.780547 -0.560414 0.276916 +-0.39446 -0.434507 -0.749945 0.367287 -0.835388 -0.408935 +-1.43272 -0.345866 -0.61268 -0.564185 -0.494337 0.661307 +-1.26759 -0.383109 -0.488339 -0.598005 -0.572796 0.560619 +0.738469 -0.397725 -0.155913 0.673182 -0.615511 -0.409843 +0.750314 -0.503609 1.00044 0.536143 -0.590265 0.603438 +-0.448518 -0.421088 -0.843294 0.65294 -0.678048 -0.337519 +0.788849 -0.443591 -0.0700327 0.410701 -0.567358 -0.713743 +0.0319352 -0.360085 -0.997632 -0.253192 -0.562275 -0.787237 +-0.695575 -0.414768 0.993345 -0.766109 -0.481545 0.425666 +0.673959 -0.491713 -0.0801672 0.484609 -0.617414 -0.619641 +-0.426789 -0.300966 -1.01689 0.680697 -0.626049 -0.380413 +-0.902103 -0.386252 0.0952833 -0.730083 -0.647918 0.217215 +-0.805681 -0.396765 0.681202 -0.872678 -0.469814 0.133072 +-0.21801 -0.410623 -0.821481 -0.404714 -0.724016 -0.558577 +0.99139 -0.478356 0.421898 0.746198 -0.662745 -0.0629027 +-0.599814 -0.409705 1.13911 -0.672531 -0.486191 0.557961 +0.258227 -0.474189 1.31421 0.272926 -0.557417 0.78409 +-0.385011 -0.435737 1.29888 -0.413702 -0.524662 0.74403 +-1.77275 -0.346906 -0.924 -0.745645 -0.123435 -0.65481 +-1.03468 -0.377009 -0.184715 -0.601638 -0.695372 0.393052 +0.955044 -0.399407 0.0363087 0.724263 -0.536652 -0.432952 +-0.0722692 -0.392059 -0.936725 -0.34992 -0.612785 -0.708556 +-0.265222 -0.362262 -0.851883 -0.229695 -0.769678 -0.59568 +0.857844 -0.404936 -0.0274397 0.518655 -0.349463 -0.780303 +-0.962327 -0.360727 -0.0147914 -0.707236 -0.613383 0.351538 +0.829833 -0.395468 -0.0644373 0.641969 -0.333941 -0.690188 +0.934767 -0.502512 0.74045 0.772176 -0.559014 0.302073 +0.7581 -0.38983 -0.118937 0.669296 -0.532646 -0.518007 +0.14997 -0.419212 1.38271 0.240662 -0.443498 0.863361 +-0.312908 -0.392238 1.35632 -0.258734 -0.440132 0.859849 +0.682074 -0.418316 1.12444 0.509216 -0.434924 0.742658 +0.783658 -0.423061 -0.0777358 0.488252 -0.346297 -0.801055 +1.02249 -0.438826 0.573954 0.805928 -0.578059 0.127777 +-0.320932 -0.324125 -0.893558 0.0132093 -0.761926 -0.647529 +1.05639 -0.345524 0.652916 0.88441 -0.409391 0.224094 +-0.797686 -0.337317 0.857536 -0.860488 -0.442661 0.252211 +0.755155 -0.366856 -0.155015 0.823153 -0.246452 -0.511547 +-0.44927 -0.365643 1.30378 -0.524027 -0.400982 0.751405 +0.68601 -0.415668 -0.200753 0.841068 -0.419948 -0.340952 +0.81712 -0.358899 -0.0704398 0.611915 0.00892986 -0.790873 +0.88491 -0.432508 0.921046 0.707366 -0.480899 0.518043 +0.140699 -0.235154 -1.11715 -0.245529 -0.518663 -0.818966 +0.865055 -0.360655 -0.0288653 0.394812 -0.0635629 -0.91656 +-0.896551 -0.329983 0.267007 -0.872573 -0.458505 0.16849 +1.0288 -0.435125 0.48164 0.833135 -0.534589 -0.141777 +-0.213596 -0.326552 -0.908492 -0.278651 -0.623623 -0.730374 +0.282887 -0.287994 -0.987681 0.474656 -0.751851 -0.457626 +-1.15923 -0.285006 -0.257845 -0.742308 -0.442605 0.503071 +0.4921 -0.322787 -0.663524 0.584649 -0.648718 -0.487186 +-0.510116 -0.400563 1.23641 -0.549471 -0.502094 0.66782 +1.06402 -0.372927 0.520605 0.91548 -0.395786 0.0724506 +0.84547 -0.361097 -0.058665 0.576725 -0.0206326 -0.816678 +-1.5705 -0.162787 -1.00179 -0.375131 0.0711174 -0.924239 +-0.32604 -0.220453 -1.00429 0.433533 -0.574184 -0.694523 +0.951692 -0.349453 4.49484E-05 0.529355 -0.28272 -0.799909 +0.983614 -0.390753 0.10303 0.841766 -0.507862 -0.183049 +0.330468 -0.350611 1.35727 0.372582 -0.366368 0.852617 +1.0305 -0.337534 0.745107 0.805768 -0.467015 0.364189 +0.124702 -0.32686 -1.03272 0.22866 -0.783971 -0.577152 +0.712492 -0.358785 -0.200709 0.805839 -0.257933 -0.533004 +0.513448 -0.231254 -0.784467 0.551031 -0.68961 -0.469897 +0.767313 -0.309866 -0.151973 0.853954 -0.00922989 -0.520266 +-0.352588 -0.308432 -0.922034 0.419994 -0.723685 -0.547617 +-1.21739 -0.116929 -1.14091 -0.403457 0.0663291 -0.912591 +0.98718 -0.334702 0.0278856 0.839354 -0.343937 -0.42094 +-0.555257 -0.17295 -1.43803 0.156692 -0.117543 -0.980628 +0.777905 -0.325709 -0.118166 0.858239 -0.0542554 -0.510375 +1.07226 -0.339633 0.424169 0.905644 -0.39631 -0.150825 +-0.11395 -0.40422 1.40057 -0.131167 -0.454242 0.881169 +1.01863 -0.310677 0.0955722 0.925774 -0.312854 -0.212284 +-1.7861 -0.179987 -0.827737 -0.758188 -0.257813 0.598903 +-1.74369 -0.263468 -0.8205 -0.62792 -0.33154 0.704129 +-0.736556 -0.306701 1.02806 -0.829023 -0.333066 0.44921 +0.542627 -0.27223 -0.661913 0.699912 -0.59972 -0.387889 +0.27896 -0.204648 -1.11951 0.353169 -0.750209 -0.55898 +0.429153 -0.452956 1.25582 0.424843 -0.443319 0.789289 +-0.882153 -0.311451 0.409196 -0.944344 -0.296956 0.141537 +-0.188194 -0.257368 1.44694 -0.239067 -0.292175 0.926003 +-0.96422 -0.293816 0.0945973 -0.867845 -0.364069 0.338082 +-0.806578 -0.165617 -1.32756 -0.411721 0.0711135 -0.908531 +-1.30093 -0.306732 -0.451059 -0.690405 -0.381941 0.614379 +-0.840412 -0.261524 0.826112 -0.913672 -0.262991 0.309902 +-0.162582 -0.249027 -0.985145 -0.319826 -0.470657 -0.82231 +-1.46694 -0.155403 -0.534037 -0.616562 -0.357919 0.701245 +-0.994926 -0.306756 0.00121908 -0.812759 -0.37665 0.444474 +0.422184 -0.185497 -0.965885 0.601677 -0.660403 -0.44928 +-0.249725 -0.187616 -1.011 -0.0219401 -0.559729 -0.828386 +-0.480985 -0.268821 1.32778 -0.543439 -0.266233 0.796111 +0.840672 -0.293486 -0.0564422 0.648569 0.158406 -0.74449 +-0.379347 -0.319423 1.36457 -0.362527 -0.294698 0.884153 +-0.407993 -0.232168 -1.07438 0.749699 -0.469334 -0.466558 +-0.651722 -0.350085 1.12155 -0.744073 -0.355135 0.565893 +0.63545 -0.350456 -0.453778 0.723998 -0.530177 -0.441292 +-0.142804 -0.165073 -1.02998 -0.257009 -0.318192 -0.912524 +0.956558 -0.235346 -0.0231431 0.0480395 -0.158781 -0.986144 +0.73389 -0.272998 -0.199621 0.930692 -0.0605817 -0.360753 +0.681154 -0.2906 1.19166 0.540541 -0.354584 0.762946 +-1.70585 -0.182609 -0.93724 -0.439826 0.098895 -0.892621 +0.23706 -0.184126 -1.15637 0.069322 -0.610912 -0.788657 +0.065043 -0.406895 1.40279 0.0470555 -0.443726 0.894926 +0.809666 -0.306455 1.08276 0.632365 -0.386754 0.671219 +0.128526 -0.178917 -1.14005 -0.33687 -0.285226 -0.89731 +-1.19878 -0.104305 -0.212048 -0.779286 -0.281925 0.559671 +0.764739 -0.182412 -0.338967 0.839288 -0.542435 -0.036884 +0.737481 -0.194314 -0.433737 0.775387 -0.517642 -0.361694 +-0.705962 -0.293195 -1.38111 -0.388581 0.052931 -0.919893 +0.730507 -0.247819 -0.261248 0.945309 -0.278333 -0.170063 +0.77911 -0.226645 -0.0865388 0.823815 0.121692 -0.553641 +0.820283 -0.190962 -0.0330465 0.502432 0.0912103 -0.859793 +0.913876 -0.260172 0.9944 0.724467 -0.301467 0.619892 +0.695313 -0.323242 -0.32416 0.869892 -0.452769 -0.195675 +0.812099 -0.205372 -0.0410129 0.758303 0.284642 -0.586478 +1.09798 -0.283899 0.521244 0.972637 -0.230715 -0.0273414 +1.04281 -0.209309 0.0915364 0.961491 -0.153518 -0.227964 +-0.879863 -0.21786 0.695926 -0.930233 -0.33959 0.139086 +-1.81125 -0.21563 -0.892437 -0.917452 -0.251284 0.308443 +-0.646502 -0.165815 1.21707 -0.692003 -0.186156 0.697479 +0.769746 -0.223834 -0.111244 0.907263 0.147351 -0.393907 +0.957045 -0.342843 0.88944 0.807634 -0.316856 0.497322 +-0.58859 -0.287487 1.23049 -0.652919 -0.337925 0.677867 +0.60579 -0.152585 -0.800382 0.597167 -0.695623 -0.399374 +0.038867 -0.222381 1.47196 0.062535 -0.159274 0.985252 +-0.431087 -0.14998 -1.20223 NAN NAN NAN +0.772686 -0.144412 -0.158524 0.85963 -0.510884 -0.00585139 +0.865258 -0.25365 -0.0208298 0.231409 0.0249864 -0.972535 +0.353744 -0.150544 -1.12553 0.56854 -0.683766 -0.457412 +1.02967 -0.303422 0.780701 0.901174 -0.270823 0.338438 +0.127993 -0.312518 1.43595 0.216313 -0.275446 0.936663 +1.10467 -0.224497 0.609943 0.94231 -0.266165 0.202997 +0.521212 -0.178363 -0.818033 0.613672 -0.630808 -0.474855 +-1.24957 -0.0163132 -0.259999 -0.782726 -0.093112 0.615362 +0.637666 -0.100365 -0.825334 0.454428 -0.521065 -0.722486 +0.765233 -0.159645 -0.445223 0.646403 -0.757254 -0.0934214 +-0.319622 -0.0663932 -1.12563 0.646702 -0.409656 -0.643396 +0.317319 -0.130709 -1.17485 0.352367 -0.574453 -0.738811 +0.75865 -0.181029 -0.122922 0.966121 -0.188875 -0.175885 +0.49481 -0.193954 -0.858104 0.614457 -0.713265 -0.337188 +-0.497289 -0.165472 1.34755 -0.561083 -0.147297 0.814548 +1.0169 -0.217687 0.0192547 0.832029 -0.152482 -0.533364 +-0.70716 -0.168619 1.14257 -0.800758 -0.179828 0.571356 +-1.66022 -0.0972307 -0.684113 -0.651258 -0.243715 0.718655 +0.783318 -0.189652 -0.0802057 0.648365 0.153006 -0.745797 +0.602603 -0.104277 -0.837929 0.58268 -0.70923 -0.396835 +0.617618 -0.0819589 -0.897756 0.591538 -0.686294 -0.423184 +-0.257151 -0.0725612 -1.07932 0.414629 -0.429114 -0.802461 +1.13195 -0.0741916 0.499113 0.993129 -0.0338097 -0.112036 +0.686439 -0.082783 -0.795331 0.690594 -0.662118 -0.290996 +0.385415 -0.0874847 -1.1626 0.658352 -0.572574 -0.488601 +0.193742 -0.0172337 -1.19858 -0.395296 -0.0651244 -0.916242 +-0.900287 -0.141207 0.656034 -0.98636 -0.111989 0.120627 +0.709908 -0.173959 -0.53407 0.545886 -0.798431 -0.254003 +-0.423462 -0.0609163 -1.25556 0.813768 -0.335737 -0.474408 +0.78642 -0.151887 -0.263465 0.543235 -0.824925 0.156186 +0.734245 -0.155838 -0.5685 -0.0740299 -0.853741 -0.515408 +-1.58069 0.0154786 -0.979683 -0.412723 0.126931 -0.901969 +-1.36707 -0.122428 -0.426393 -0.703649 -0.173909 0.688937 +0.271925 -0.108799 -1.19875 0.0196604 -0.398715 -0.916864 +0.781325 -0.138386 -0.603251 -0.0916895 -0.201346 -0.97522 +1.0958 -0.242931 0.377973 0.96337 -0.223883 -0.147631 +0.94459 -0.156122 0.992129 0.813731 -0.174743 0.554352 +-1.8331 -0.0528819 -0.863982 -0.663136 0.0658106 -0.7456 +0.967904 -0.0665272 -0.0710806 0.660148 -0.216463 -0.719269 +-1.33105 0.115563 -1.06896 -0.392165 0.07157 -0.917107 +0.647808 -0.165732 -0.649416 0.679876 -0.668413 -0.30165 +0.514136 -0.0674974 -0.990482 0.675077 -0.498171 -0.544147 +0.440385 -0.169295 1.36438 0.433603 -0.155287 0.887623 +0.501404 -0.133218 -0.943061 0.517043 -0.54297 -0.661703 +0.768445 -0.193674 -0.0883227 0.851524 -0.331283 -0.406398 +0.847695 -0.17867 -0.0336957 -0.0151887 -0.306744 -0.951671 +0.775907 -0.0548235 -0.611081 0.576178 -0.642792 -0.504815 +0.725748 -0.137842 -0.598927 0.409265 -0.646285 -0.644064 +0.809021 -0.169164 -0.0453051 0.558415 -0.364792 -0.74505 +-0.967235 -0.187164 0.189734 -0.932191 -0.23893 0.271903 +-0.945197 -0.111372 0.323143 -0.968837 -0.170678 0.179507 +-0.0450539 -0.100841 -1.08176 -0.367675 -0.119788 -0.922208 +1.04982 -0.0419248 0.0545412 0.948383 -0.0615262 -0.311102 +-0.72453 -0.0516342 -1.35426 -0.404437 0.0782907 -0.911208 +-1.78921 -0.0608484 -0.797432 -0.694513 -0.0509748 0.717672 +-0.813008 -0.155534 0.976529 -0.887143 -0.214765 0.408475 +-0.884296 -0.145309 0.762944 -0.973896 -0.0962075 0.205597 +1.03934 -0.20609 0.820284 0.869254 -0.177882 0.461254 +0.255869 -0.0568979 -1.21352 -0.228411 -0.212857 -0.950011 +0.840766 -0.142033 -0.053466 -0.0323425 -0.651617 -0.757859 +-0.520128 -0.0282484 -1.43951 0.156958 -0.00643269 -0.987584 +-1.02723 -0.126706 0.074242 -0.89208 -0.164988 0.420681 +0.826781 -0.191858 1.10921 0.686167 -0.18153 0.70443 +-0.467664 0.00885957 -1.35618 0.849271 -0.211323 -0.483819 +0.935642 -0.0878129 -0.595691 0.501062 -0.840807 -0.204891 +0.628804 -0.0673474 -0.846352 0.619542 -0.450012 -0.643162 +0.604635 -0.237359 1.26013 0.465689 -0.306431 0.830201 +-0.973531 -0.0311093 0.238992 -0.943557 -0.0643139 0.324906 +0.830668 -0.124408 -0.417565 0.366412 -0.928559 0.0593346 +0.805169 -0.0729959 -0.586642 -0.253317 -0.180007 -0.950488 +0.8076 -0.123695 -0.0807599 0.714622 -0.547393 -0.435518 +0.764524 -0.11894 -0.602335 0.240736 -0.0297933 -0.970133 +0.802265 -0.149673 -0.59032 0.3228 -0.940066 -0.109892 +-0.194481 -0.16042 1.46729 -0.211274 -0.127735 0.969045 +0.787532 -0.0627556 -0.589707 0.315993 -0.540151 -0.77999 +0.939013 -0.174166 -0.0445224 0.120126 -0.491417 -0.8626 +0.877875 -0.0961992 -0.614299 -0.2568 0.324837 -0.910239 +0.621962 -0.0342662 -0.933878 0.55186 -0.327552 -0.766916 +0.850825 -0.0995644 -0.193431 0.449426 -0.891966 0.049125 +0.42669 -0.0392151 -1.14999 0.775778 -0.313535 -0.547598 +0.830509 -0.108092 -0.0945351 0.10407 -0.886067 -0.451724 +-0.195023 -0.0502443 -1.06911 0.0987422 -0.375464 -0.921562 +0.900013 -0.0998299 -0.106014 0.0838987 -0.749816 -0.656306 +0.981458 0.0166146 -0.0661218 0.815246 -0.010323 -0.579022 +0.918758 -0.103415 -0.549594 0.412995 -0.909049 0.0553639 +-0.196785 0.0193673 -1.08687 0.338027 -0.143314 -0.93016 +-0.0186182 -0.2563 1.46569 -0.050947 -0.272671 0.960757 +-0.641519 -0.0469662 1.23584 -0.729065 0.0203058 0.684143 +-0.509766 0.144003 -1.43247 -0.0478599 0.0699453 -0.996402 +-1.37301 0.0286557 -0.401876 -0.735099 0.0116433 0.677859 +0.640635 -0.0595224 -0.87897 0.874015 -0.483755 -0.0455946 +-0.928849 -0.103905 0.418916 -0.991089 -0.00918116 0.132884 +0.317835 -0.206777 1.40995 0.326207 -0.251819 0.911139 +0.809073 -0.110474 -0.113217 0.477918 -0.877108 0.0476967 +0.545031 -0.0899265 1.31976 0.516942 -0.0770851 0.852542 +0.901976 -0.0884998 -0.619486 -0.113656 -0.500305 -0.858357 +-0.532013 -0.0830306 1.33352 -0.606278 -0.0552056 0.793334 +0.713433 -0.00727915 -0.828377 0.537373 -0.268708 -0.799392 +0.373412 -0.0453718 -1.19685 0.458066 -0.324383 -0.827618 +0.902952 -0.0835108 -0.146816 0.292114 -0.942826 -0.160461 +-1.12997 -0.0993238 -0.103515 -0.845835 -0.197436 0.495563 +0.7495 -0.238375 1.1603 0.571049 -0.17842 0.801293 +-0.80511 -0.0814162 1.01105 -0.867971 -0.0410942 0.494912 +0.138088 -0.0799998 1.4823 0.195748 -0.142677 0.97022 +0.77981 -0.11524 1.1628 0.597745 -0.0870154 0.79695 +-1.04668 0.0210823 0.0613365 -0.90404 0.0172105 0.4271 +0.930751 -0.0842467 -0.100574 0.669981 -0.410408 -0.61862 +-1.12099 0.00892211 -0.0710063 -0.849391 -0.0255525 0.527145 +0.269423 0.0235582 -1.22487 -0.19388 0.000288564 -0.981025 +0.983256 -0.0584183 -0.522185 0.698033 -0.715606 0.0256459 +-0.139938 -0.0533793 -1.06538 -0.172206 -0.167651 -0.97069 +-0.86667 -0.0531242 0.860177 -0.957055 0.00346522 0.289887 +1.12355 -0.150739 0.563724 0.987226 -0.0546963 0.149646 +1.11088 -0.145082 0.365474 0.980233 -0.0650645 -0.186847 +0.930534 -0.0653557 -0.136281 0.541238 -0.839746 0.0434473 +0.961112 -0.0408458 -0.633544 0.525036 -0.61615 -0.587108 +-0.711384 -0.0121365 1.15187 -0.816818 0.0288867 0.576173 +0.95984 -0.0645588 -0.60858 0.680889 -0.623616 -0.384047 +1.00786 -0.00077452 -0.550118 0.982406 -0.161626 -0.093572 +0.322515 -0.0166528 -1.22277 0.158124 -0.164278 -0.973658 +0.437508 0.0251217 -1.15682 0.790153 -0.0278213 -0.612278 +0.645769 -0.0223367 -0.915388 0.761478 -0.237158 -0.603246 +-0.404367 -0.111137 1.40631 -0.367716 -0.163838 0.915391 +0.939763 -0.0990356 1.00912 0.761042 -0.0637516 0.645563 +0.382303 0.0218652 -1.20614 0.494255 -0.0433014 -0.868238 +1.04121 -0.0279407 0.860044 0.878644 -0.0525971 0.474571 +-0.400237 0.099149 -1.28471 0.762687 -0.152437 -0.628548 +0.999385 -0.0291456 -0.452925 0.91922 -0.382368 0.0939676 +0.783832 -0.0157816 -0.68296 0.71486 -0.61089 -0.340278 +0.72432 -0.026108 -0.803352 0.830009 -0.372721 -0.414927 +0.779473 0.0296155 -0.711513 0.870391 0.0739491 -0.486776 +0.995993 -0.00087373 -0.606028 0.934995 0.0207282 -0.354056 +-0.0538418 0.0715961 -1.09304 -0.208473 0.0634211 -0.97597 +0.654479 -0.0115039 -0.854687 0.8365 -0.262845 -0.480812 +0.942222 -0.0540851 -0.200201 0.851522 -0.512364 0.111325 +0.654145 0.0127709 -0.907751 0.920635 0.0366239 -0.388704 +-0.166125 0.0208231 -1.0806 0.0658639 -0.0864817 -0.994074 +0.838613 -0.0120095 -0.64637 -0.00711015 -0.534163 -0.845351 +0.943639 -0.021052 -0.651286 0.0432013 -0.375498 -0.925816 +-0.969061 0.0255445 -1.23441 -0.408834 0.0867818 -0.908473 +-0.936542 0.11947 -1.24256 -0.395845 0.0261919 -0.917943 +-1.58609 0.125913 -0.95443 -0.460321 0.173999 -0.870534 +0.978782 -0.0321138 -0.3152 0.978821 -0.124189 0.16275 +0.553043 -0.0110544 -0.978568 0.551278 -0.0612938 -0.832067 +-0.333769 0.0261697 1.44225 -0.288829 -0.0106019 0.957322 +-1.81107 0.204914 -0.816854 -0.680807 0.05186 0.730624 +-1.83214 0.122069 -0.84378 -0.774196 0.0969311 -0.625479 +0.729747 0.0156405 -0.814795 0.872404 -0.0139711 -0.488586 +0.656419 -0.00976532 -0.894494 0.985764 -0.151831 -0.0722301 +0.330041 0.098089 -1.22461 0.213212 0.0363252 -0.97633 +-0.940412 0.0111346 0.367301 -0.987572 0.0187575 0.156041 +0.669418 0.0283314 -0.854596 0.447297 0.0159704 -0.894243 +-1.51349 0.080313 -0.538986 -0.676606 -0.00834845 0.736297 +0.97171 -0.00497264 -0.640562 0.619194 0.0620807 -0.78278 +0.926581 0.00445274 -0.658512 0.154612 0.016319 -0.987841 +0.956273 -0.00672347 -0.172982 0.986504 -0.121094 0.110209 +-1.13375 0.122968 -0.105423 -0.832811 0.175233 0.525089 +-0.455227 0.165174 -1.35224 0.797493 0.112536 -0.59274 +-1.72724 0.0485458 -0.895578 -0.426038 0.127065 -0.895737 +0.0231545 0.0888636 -1.11822 -0.379571 0.0135084 -0.925064 +-0.955654 0.0777454 0.277402 -0.944369 0.0948311 0.314922 +-0.24379 0.158763 -1.10472 0.492273 0.222008 -0.841653 +0.53232 0.00916394 -1.00481 0.821134 -0.128047 -0.556186 +1.00188 0.035377 -0.46686 0.967586 0.215074 0.13236 +0.316536 -0.0566653 1.43387 0.325457 -0.107052 0.939477 +-0.693915 0.199494 -1.34652 -0.408382 0.0814847 -0.909167 +0.86422 0.0168785 -0.653867 -0.0166781 0.149525 -0.988617 +0.958588 -0.00682357 -0.104132 0.945188 -0.102036 -0.310173 +-0.248109 0.0488172 -1.11573 0.643343 -0.174982 -0.745314 +1.09437 -0.162024 0.699964 0.951464 -0.101037 0.290704 +-1.2573 0.0833839 -0.266475 -0.803672 0.0584381 0.592195 +0.208328 0.12491 -1.19985 -0.372192 0.129828 -0.91903 +0.831959 0.0349633 -0.660516 0.560663 0.0612408 -0.825776 +0.957832 0.0650008 -0.630067 0.516329 0.46309 -0.720384 +-1.37618 0.165307 -0.416437 -0.700629 0.167664 0.693547 +0.929921 0.048515 -0.650216 -0.000475202 0.450041 -0.893008 +-0.283892 0.139647 -1.14737 0.751742 0.114036 -0.649523 +-0.420576 0.0322834 1.40351 -0.496092 0.0218217 0.867995 +0.537201 0.116787 -0.996631 0.831267 0.00606925 -0.55584 +0.305508 0.121792 -1.22423 -0.122217 0.196188 -0.972919 +-0.822598 0.0151779 0.970869 -0.881227 0.0854067 0.464913 +1.02821 0.0489071 0.000450697 0.8814 -0.0147517 -0.472141 +-1.41169 0.168869 -1.01948 -0.456205 0.049179 -0.888515 +0.853245 0.0486285 -0.637931 -0.0544012 0.647093 -0.760468 +0.715485 0.0880931 -0.822393 0.755381 0.319844 -0.571926 +0.976686 0.07771 -0.492889 0.598129 0.790502 0.131714 +0.378942 0.131354 -1.20249 0.570779 0.120156 -0.812265 +0.0782879 0.0894472 1.49523 0.0524228 0.0274759 0.998247 +0.80244 0.0921273 -0.576762 -0.205574 0.250485 -0.946043 +-0.867591 0.155404 0.728022 -0.940512 0.287329 0.181323 +0.664372 0.0933842 -0.851119 0.893477 0.115173 -0.43409 +0.991115 0.0660081 -0.536124 0.912973 0.400358 -0.0786986 +0.818751 0.061843 -0.642048 0.6998 0.663147 -0.265548 +0.944987 0.0492921 -0.124104 0.895205 0.356191 -0.267834 +-0.547399 0.00506866 1.32112 -0.616463 0.0807702 0.783231 +0.343158 0.179371 -1.20586 0.196291 0.282889 -0.938852 +-0.889817 0.0526662 0.731092 -0.98489 0.0749769 0.156111 +0.979741 0.0704115 -0.591093 0.830963 0.476034 -0.287909 +-1.70823 0.19473 -0.885385 -0.396226 -0.00669604 -0.918128 +0.945234 0.0638129 -0.307993 0.579311 0.802123 0.144903 +0.630542 0.0975173 -0.93668 0.579952 0.0275862 -0.814183 +-1.23427 0.224251 -0.277964 -0.746053 0.230508 0.624717 +0.774693 0.0892502 -0.589698 0.650641 0.73823 -0.177995 +0.931176 0.0594664 -0.175181 0.593801 0.804449 0.0161905 +0.956982 0.0202991 -0.184634 0.915519 0.3735 0.149409 +0.749822 -0.0126198 1.18971 0.594562 0.0321019 0.803409 +-0.738376 0.108317 1.08957 -0.837867 0.199869 0.507969 +1.12759 0.0806779 0.551268 0.994616 0.0785793 0.0675627 +0.430807 0.129633 -1.15531 0.77316 0.178255 -0.608644 +0.716677 0.157092 -0.680228 0.737533 0.670615 -0.0795048 +1.08005 0.0296646 0.775293 0.93421 0.0147291 0.35642 +-0.979413 0.141693 0.175424 -0.88472 0.246266 0.395759 +0.367192 -0.0163991 1.41508 0.431781 0.0084332 0.901939 +-0.684372 0.0824441 1.16959 -0.749783 0.16968 0.639558 +0.885034 0.0918837 -0.619894 -0.418751 0.316757 -0.851066 +0.865569 -0.0194035 1.09481 0.695651 0.0465453 0.716871 +0.922241 0.0778048 -0.1084 0.360114 0.849358 -0.385886 +-0.0916933 -0.039296 1.49545 -0.0808895 -0.0588291 0.994986 +0.0305883 0.190253 -1.10655 -0.380564 0.176842 -0.907688 +-0.592443 0.126765 1.26103 -0.668391 0.160239 0.726345 +0.560259 0.12648 -0.972143 0.689236 0.207984 -0.694044 +0.971597 0.118214 -0.0436478 0.43809 0.448798 -0.778882 +-0.138041 0.118326 -1.07896 0.0663552 0.234332 -0.969889 +-0.69147 0.230745 -1.34221 -0.408988 0.0513751 -0.911093 +-1.45399 0.285533 -0.992698 -0.456663 0.0679805 -0.887038 +0.174446 0.0202077 1.48054 0.268139 -0.00019695 0.96338 +0.945379 0.100354 -0.597977 0.538736 0.778062 -0.323084 +0.756074 0.0932178 -0.717169 0.823804 0.455749 -0.337105 +0.640526 0.158086 -0.911689 0.634488 0.392943 -0.665598 +-0.831512 0.168863 0.854695 -0.897438 0.364057 0.249135 +0.838773 0.143756 -0.595405 -0.143009 0.0860192 -0.985976 +-0.825019 0.0942934 0.935616 -0.92179 0.194501 0.335369 +0.861424 0.156125 -0.581965 0.28147 0.746458 -0.602973 +0.870151 0.157153 1.05645 0.696651 0.243698 0.674751 +1.05019 0.167476 0.057479 0.970416 0.0850506 -0.225961 +0.961718 0.095951 -0.549052 0.553944 0.831639 0.0390119 +0.827805 0.125267 -0.0983673 0.609676 0.792647 -0.00268317 +0.860226 0.112954 -0.296787 0.573552 0.814371 0.0885333 +-0.442182 0.23967 -1.30035 0.788317 0.348827 -0.50683 +0.621457 0.0351771 1.27328 0.508296 0.0645503 0.858759 +-1.49231 0.31712 -0.973458 -0.431808 -0.0938567 -0.897069 +0.653825 0.147256 -0.884016 0.957134 0.233522 -0.171356 +1.05912 0.075648 0.0832599 0.983211 0.00650026 -0.182357 +1.02285 0.11731 0.875882 0.861292 0.18429 0.473511 +0.693277 0.172134 -0.769814 0.750712 0.614518 -0.242486 +0.85136 0.122826 -0.0561617 0.241497 0.788494 -0.565647 +0.237033 0.208341 -1.19165 -0.211987 0.353713 -0.911015 +0.308436 0.237651 -1.18423 0.0897058 0.587302 -0.804381 +1.03298 0.167402 0.0165532 0.763514 0.137432 -0.630999 +0.745418 0.153154 -0.586458 0.204083 0.110079 -0.972745 +0.647176 0.165587 -0.830983 0.809318 0.448823 -0.378896 +-0.709829 0.29428 -1.3341 -0.397769 0.039631 -0.91663 +0.815523 0.143891 -0.0706684 0.760477 0.545217 -0.352722 +0.790029 0.18443 -0.583403 0.0412567 0.456397 -0.888819 +-1.80962 0.288078 -0.83381 -0.868168 0.104699 -0.4851 +0.798781 0.172157 -0.36038 0.740796 0.671285 0.0244759 +0.575031 0.14495 1.28827 0.526265 0.223441 0.820438 +0.721042 0.155013 -0.620437 0.754055 0.619432 -0.218415 +0.963026 0.0397664 0.979616 0.788462 0.129822 0.601227 +0.468707 0.26273 -0.952555 0.576631 0.690505 -0.436691 +0.357991 0.197272 -1.19076 0.553061 0.42205 -0.71833 +0.87431 0.144531 -0.0333356 0.0841107 0.423347 -0.902055 +0.617801 0.204503 -0.818967 0.641528 0.631242 -0.435861 +-0.931218 0.10717 0.360082 -0.964288 0.20698 0.16525 +-0.220195 0.249917 -1.04676 0.346905 0.523224 -0.778392 +-1.53277 0.214774 -0.577227 -0.651111 0.152506 0.743503 +0.738837 0.181979 -0.578716 0.196588 0.844205 -0.498668 +-0.424318 0.32084 -1.19092 0.664837 0.654511 -0.360012 +0.319877 0.10356 1.43006 0.423086 0.113145 0.898997 +-0.964337 0.207237 -1.23738 -0.411634 0.0583291 -0.909481 +-1.35906 0.242479 -1.05883 -0.394956 -0.0052046 -0.918685 +-0.172282 0.0956895 1.48351 -0.203126 0.0352343 0.978518 +0.796554 0.160548 -0.130086 0.883274 0.468726 0.0110924 +0.834712 0.160683 -0.0389261 0.587565 0.264476 -0.764735 +0.798526 0.189712 -0.569502 0.43869 0.89658 0.0607948 +0.834376 0.156386 -0.42693 0.510624 0.85145 0.119564 +-0.0927653 0.149188 1.48785 -0.0582368 0.13326 0.989368 +0.761914 0.197861 -0.515965 0.454653 0.889053 -0.0536208 +0.180738 0.120317 1.47355 0.212978 0.173605 0.961511 +0.635228 0.185678 -0.880465 0.70921 0.69473 -0.119882 +0.3506 0.248844 -1.15456 0.494312 0.62566 -0.603494 +0.60153 0.21072 -0.894011 0.5246 0.740151 -0.420678 +0.405525 0.19873 -1.14505 0.69961 0.51036 -0.500079 +1.09978 0.101769 0.701154 0.963956 0.0620138 0.258731 +0.760373 0.206409 -0.447727 0.81528 0.565317 -0.125435 +0.791252 0.180134 -0.114982 0.944356 -0.0345371 -0.327107 +1.11473 0.115009 0.40079 0.983244 0.115911 -0.1407 +-0.30629 0.252626 -1.10558 0.584602 0.624347 -0.518103 +-1.57259 0.322356 -0.643234 -0.570324 0.301678 0.764016 +-0.325793 0.202689 -1.16938 0.730626 0.391593 -0.559321 +-0.063657 0.207844 -1.06659 -0.26854 0.291799 -0.918008 +-0.243692 0.155424 1.45689 -0.283112 0.229359 0.931258 +-0.178376 0.281503 -1.01984 -0.0695309 0.47991 -0.874558 +0.769856 0.105476 1.16134 0.608527 0.149152 0.77939 +-1.37345 0.302394 -0.468496 -0.610458 0.364987 0.70294 +0.139831 0.27154 -1.13181 -0.351119 0.357636 -0.865339 +-0.788676 0.210509 0.931767 -0.860694 0.378885 0.340075 +-1.30723 0.321254 -0.416681 -0.642884 0.49022 0.588545 +0.535876 0.182743 -0.970177 0.647073 0.564329 -0.51267 +0.820831 0.195832 -0.05361 0.632105 -0.251676 -0.732873 +-0.0855928 0.326157 -1.00731 -0.298838 0.479972 -0.824817 +-1.28412 0.398511 -1.07731 -0.319818 0.0347196 -0.946843 +-0.382586 0.146534 1.40855 -0.454985 0.161022 0.87582 +-0.999608 0.244814 0.0554833 -0.767107 0.428125 0.47776 +0.56705 0.335846 -0.669804 0.720291 0.655196 -0.227812 +1.02368 0.249424 0.0246104 0.627854 0.256686 -0.734788 +0.196026 0.325993 -1.11644 -0.0623983 0.669899 -0.739826 +0.320655 0.31068 -1.10376 0.547578 0.691476 -0.471189 +-1.40388 0.349602 -1.0601 -0.428338 -0.10785 -0.897159 +-1.46617 0.349964 -1.0191 -0.699699 -0.000744026 -0.714438 +0.71642 0.233973 -0.499469 0.697546 0.623808 -0.352552 +0.77727 0.224469 -0.180675 0.927515 0.357276 -0.109858 +0.97987 0.270931 0.857195 0.785649 0.337723 0.518361 +0.536685 0.28679 -0.792442 0.65744 0.719762 -0.222971 +-0.9116 0.199779 0.342491 -0.896011 0.350258 0.272919 +-0.662385 0.224854 1.14617 -0.749645 0.290813 0.594525 +-1.1031 0.274741 -0.142396 -0.751205 0.4158 0.512642 +-0.43797 0.209162 1.36181 -0.507372 0.261209 0.821184 +-0.718212 0.232818 1.05905 -0.815042 0.362384 0.452089 +-0.63227 0.335058 -1.36815 -0.40947 0.0369739 -0.911574 +-0.463947 0.332989 -1.25031 0.804491 0.460053 -0.375693 +0.863839 0.196811 -0.0167825 0.205439 0.130298 -0.969957 +0.627077 0.294162 -0.592883 0.754396 0.523734 -0.395715 +0.00764502 0.245876 1.46604 0.0523127 0.332851 0.941527 +-0.566983 0.227932 1.25203 -0.662001 0.321367 0.67711 +-0.272709 0.306432 -1.0161 0.343682 0.762254 -0.5485 +0.993197 0.211569 -0.00198026 0.227799 0.241644 -0.943248 +-0.518903 0.273968 -1.42068 0.393459 0.270689 -0.878588 +0.713565 0.30071 -0.401775 0.872718 0.39992 -0.280048 +0.77968 0.29239 -0.164313 0.927407 -0.0180611 -0.373618 +0.808305 0.215991 -0.0661837 0.800018 -0.135217 -0.58454 +0.824485 0.306862 -0.0923741 0.853359 -0.192997 -0.484284 +1.10157 0.175625 0.643917 0.963786 0.224908 0.143292 +-0.882309 0.243286 0.412974 -0.910698 0.383832 0.152653 +0.864782 0.222225 -0.0207486 0.701957 -0.381486 -0.601436 +0.213576 0.397357 -1.04286 0.110324 0.758694 -0.642038 +-0.387785 0.421635 -0.934442 0.535284 0.751732 -0.385188 +-0.0913069 0.225472 1.46823 -0.156675 0.336094 0.928706 +-0.810441 0.384275 0.40705 -0.824213 0.548841 0.139453 +0.500553 0.348401 -0.752284 0.537717 0.729704 -0.422365 +-1.74116 0.348213 -0.773614 -0.628548 0.190808 0.754002 +0.599299 0.266851 -0.771267 0.596138 0.692246 -0.406714 +-0.956724 0.329098 0.0439186 -0.787909 0.494391 0.367119 +0.286284 0.198914 1.42299 0.355516 0.242896 0.902557 +-0.884939 0.265864 -1.26116 -0.356063 0.0288618 -0.934016 +-1.76635 0.414988 -0.82258 -0.767361 0.293931 0.569879 +0.15533 0.250415 1.44871 0.151198 0.355845 0.922232 +0.747296 0.286997 -0.29086 0.962173 0.0597854 -0.265798 +0.288802 0.37799 -1.02486 0.567975 0.742643 -0.354805 +-1.55556 0.448028 -0.690596 -0.466797 0.434803 0.770096 +0.885606 0.261998 -0.0327242 0.611933 -0.224089 -0.7585 +0.468791 0.221853 1.32773 0.46714 0.239746 0.851059 +-0.793542 0.341094 0.688352 -0.862195 0.474691 0.176886 +1.09991 0.23045 0.463829 0.967903 0.251327 0.000285037 +0.0682769 0.382766 -1.03869 -0.296556 0.587528 -0.752905 +0.717493 0.216286 1.17028 0.605952 0.307083 0.73384 +-0.601686 0.503777 -1.37146 0.177694 0.342141 -0.922694 +-0.278709 0.279123 1.40738 -0.271964 0.362794 0.8913 +-0.25685 0.358925 -0.950496 0.0279754 0.758344 -0.651254 +-1.25279 0.385438 -0.418088 -0.521157 0.656574 0.545258 +-0.193105 0.36201 -0.951636 -0.211563 0.69491 -0.687271 +-1.32028 0.427046 -0.515602 -0.500306 0.574189 0.648074 +0.936278 0.238939 0.939215 0.763314 0.281885 0.581284 +-0.350757 0.459588 -0.823319 0.188721 0.853647 -0.485459 +-0.102442 0.41854 -0.932534 -0.322216 0.638179 -0.699217 +-0.491806 0.407797 -1.18014 0.756432 0.613805 -0.225951 +0.856109 0.282426 -0.0494451 0.627861 -0.128292 -0.767679 +1.00206 0.33465 0.0382807 0.625701 0.440449 -0.64382 +-0.808368 0.505973 -1.27907 -0.393274 0.0429822 -0.918417 +-1.20003 0.501644 -1.09908 -0.329628 0.048465 -0.942866 +-1.33466 0.411533 -1.08789 -0.28469 -0.0517355 -0.957222 +-0.619303 0.350005 1.11885 -0.720405 0.447116 0.530192 +0.259254 0.395342 -1.02224 0.343765 0.827657 -0.443633 +0.927561 0.308751 -0.000664683 0.463922 0.0271537 -0.885459 +-1.26268 0.504655 -1.11042 0.0719208 -0.228066 -0.970986 +-1.38857 0.496347 -1.05699 -0.458173 0.0237238 -0.888546 +-1.74978 0.385792 -0.882423 -0.459425 -0.0226826 -0.887927 +1.06695 0.309412 0.383828 0.93935 0.327135 -0.102978 +0.772393 0.342945 -0.17168 0.894826 0.273634 -0.352718 +0.858001 0.311694 0.985661 0.658405 0.421222 0.623758 +1.02675 0.383301 0.340502 0.870174 0.481587 -0.104269 +-0.529039 0.386236 1.19315 -0.595628 0.528926 0.604536 +0.80904 0.34447 -0.124846 0.813206 0.0586834 -0.579009 +0.940033 0.328401 0.00738849 0.401163 0.221118 -0.888918 +1.07286 0.266598 0.646437 0.911484 0.379019 0.159815 +-0.932032 0.428567 -0.0879638 -0.627928 0.710947 0.316639 +-1.45914 0.45608 -0.63653 -0.442365 0.540887 0.71537 +1.03269 0.278938 0.0583564 0.953259 0.260908 -0.152394 +0.860447 0.367091 -0.0506931 0.617146 0.237759 -0.750067 +-0.873195 0.351198 0.220987 -0.777774 0.579862 0.242544 +-0.10212 0.373614 1.40409 -0.133182 0.457634 0.87911 +-0.723895 0.370223 0.885806 -0.825245 0.508177 0.24643 +-0.85128 0.44394 -1.26961 -0.377568 0.0562327 -0.924273 +0.503968 0.309565 -0.815466 0.564804 0.747261 -0.350139 +1.05729 0.244071 0.729759 0.870061 0.347385 0.349738 +-1.05455 0.394012 -0.212378 -0.580728 0.673096 0.457927 +0.900999 0.368757 -0.0355438 0.648928 0.0775776 -0.756884 +0.306944 0.385178 1.32764 0.393919 0.47601 0.786284 +-0.676884 0.378845 0.986646 -0.784504 0.491137 0.378598 +-1.10042 0.48272 -0.406317 -0.462592 0.763708 0.450287 +0.797659 0.395135 -0.123246 0.762928 0.43699 -0.476424 +0.533341 0.286941 1.26273 0.447471 0.429516 0.784402 +0.0644678 0.484482 -0.944409 -0.108446 0.759391 -0.641534 +-0.354782 0.334993 1.34966 -0.406709 0.449321 0.795424 +-1.71029 0.491475 -0.802923 -0.520567 0.378634 0.765276 +0.910705 0.412178 0.0100243 0.674792 0.271824 -0.686125 +0.685473 0.290953 1.1589 0.560754 0.382876 0.73414 +-1.72229 0.550024 -0.853944 -0.768181 0.456775 0.448615 +-1.77483 0.446021 -0.867162 -0.917169 0.24218 -0.316464 +0.817477 0.394167 -0.0855272 0.752389 0.456863 -0.474538 +-0.50108 0.503814 -0.94458 0.634168 0.724241 -0.270749 +1.00092 0.365482 0.0715953 0.875816 0.454333 -0.162875 +0.105905 0.540557 -0.8563 0.174989 0.831864 -0.526669 +0.53286 0.417059 -0.58464 0.648348 0.621792 -0.439341 +-0.246644 0.474749 -0.797003 -0.120296 0.83538 -0.536349 +-1.18475 0.465088 -0.454575 -0.393809 0.739311 0.546199 +0.254039 0.27709 1.40976 0.359998 0.404324 0.840787 +-0.448862 0.336445 1.2941 -0.529431 0.440671 0.724922 +0.684188 0.400854 -0.37193 0.824141 0.451105 -0.342485 +-1.54721 0.413922 -0.970922 -0.394501 -0.0600917 -0.916928 +0.236121 0.511882 -0.803459 0.517118 0.770034 -0.373681 +-0.763872 0.462158 0.284941 -0.712304 0.69471 0.100007 +0.733465 0.440938 -0.179267 0.839751 0.393517 -0.374116 +-0.282973 0.400884 1.34073 -0.283079 0.521208 0.805114 +-0.432885 0.553839 -0.685377 0.417934 0.853995 -0.30988 +-1.25249 0.516974 -0.566974 -0.368915 0.65189 0.662527 +1.06471 0.324973 0.522257 0.89863 0.436197 0.0468634 +-0.538406 0.449627 -1.24648 0.807227 0.571041 -0.149322 +0.867737 0.424874 -0.0377443 0.437027 0.374678 -0.817694 +-0.7936 0.477494 0.0849604 -0.563878 0.8011 0.200699 +0.497108 0.413952 1.20412 0.441603 0.539671 0.716758 +0.153481 0.378198 1.38798 0.171021 0.472731 0.864452 +0.982338 0.380624 0.738447 0.791353 0.542787 0.281324 +-1.58958 0.668508 -0.826151 -0.348261 0.487339 0.800759 +-0.908012 0.531389 -0.297876 -0.443621 0.840457 0.311179 +0.614496 0.494679 -0.388374 0.652913 0.597931 -0.464954 +-1.04541 0.539317 -0.466421 -0.315114 0.848284 0.425577 +0.951163 0.366148 0.81721 0.750478 0.466832 0.467815 +-0.314988 0.549819 -0.631073 0.00889787 0.935512 -0.353184 +-0.591969 0.454265 1.03702 -0.661766 0.622245 0.418183 +0.769526 0.47491 -0.0460399 0.489855 0.561329 -0.667047 +0.202428 0.431907 1.34015 0.226433 0.56669 0.792206 +0.996254 0.44309 0.456251 0.832456 0.553089 -0.033311 +-1.62594 0.49997 -0.945318 -0.398032 -0.019242 -0.91717 +-0.689411 0.653528 -1.28267 0.78179 0.615318 -0.100943 +-0.0850719 0.523628 -0.840018 -0.311446 0.708751 -0.632988 +0.169801 0.551685 -0.782465 0.32425 0.877766 -0.352687 +0.868113 0.401026 0.897232 0.640585 0.556367 0.529251 +-0.707756 0.49171 0.564047 -0.726453 0.681707 0.0868468 +-0.378609 0.464746 1.24291 -0.429387 0.596181 0.678377 +0.0102288 0.448088 1.36448 0.043243 0.535876 0.843188 +0.95026 0.446918 0.0954439 0.758267 0.63722 -0.137772 +-0.594643 0.584892 -0.908303 0.536527 0.820999 -0.195188 +0.647378 0.367424 1.1383 0.496114 0.498378 0.710979 +-0.189418 0.501275 -0.794963 -0.381471 0.729662 -0.567514 +-0.646333 0.564525 0.155013 -0.506524 0.86222 -0.00322421 +-0.589141 0.535294 -1.14343 0.734462 0.671094 -0.100987 +0.835381 0.458523 -0.0302406 0.543615 0.614901 -0.571297 +1.00526 0.414875 0.579785 0.824374 0.534169 0.187274 +-0.145876 0.473513 1.33055 -0.162016 0.579907 0.798411 +-1.18145 0.586261 -0.618891 -0.252596 0.783306 0.568004 +-0.662448 0.465651 0.861446 -0.720806 0.63561 0.276473 +-0.751531 0.717931 -1.29776 -0.186639 0.234538 -0.954022 +0.457622 0.46575 -0.609863 0.513242 0.716508 -0.472439 +0.778715 0.452172 -0.0692359 0.708299 0.615829 -0.34506 +0.914275 0.442292 0.776292 0.645245 0.676684 0.354622 +-0.930458 0.587482 -0.498994 -0.249876 0.929284 0.272016 +-0.431947 0.515967 1.14851 -0.493049 0.685465 0.535761 +-0.11512 0.591544 -0.722823 -0.395443 0.792395 -0.464473 +0.730759 0.480088 -0.120878 0.759234 0.578747 -0.297683 +0.917997 0.469784 0.0702479 0.604661 0.629217 -0.488336 +-1.45543 0.619058 -0.747837 -0.309842 0.529624 0.789619 +0.294103 0.471234 1.27227 0.354613 0.612388 0.706563 +-1.62145 0.71259 -0.876964 -0.564407 0.516718 0.643775 +0.48012 0.508845 1.13365 0.41226 0.640713 0.64771 +-1.25105 0.556891 -1.11207 -0.343106 0.061902 -0.937255 +-0.245099 0.552978 -0.656345 -0.37771 0.849389 -0.368609 +-0.670744 0.50811 0.70323 -0.770016 0.618851 0.155235 +-0.574477 0.605612 0.0356721 -0.385053 0.917091 0.103339 +0.352145 0.557854 -0.562475 0.45174 0.793384 -0.40801 +-1.28548 0.636936 -1.08909 -0.289639 0.173896 -0.941206 +-0.665851 0.625727 -0.995542 0.569651 0.82182 -0.0104029 +0.960188 0.480118 0.524887 0.670791 0.739967 0.0498768 +-0.635256 0.619985 -0.839524 0.35478 0.928643 -0.108414 +0.597158 0.558449 -0.316082 0.691046 0.647425 -0.3214 +-0.242262 0.500258 1.27992 -0.287438 0.636395 0.715808 +-1.69831 0.611605 -0.905458 -0.818959 0.347544 -0.456638 +-0.0321413 0.612319 -0.739908 -0.153459 0.87757 -0.454227 +-0.777035 0.55227 -0.161157 -0.400825 0.893618 0.201957 +-0.366498 0.583186 -0.533521 0.208557 0.946894 -0.244738 +0.701711 0.538647 -0.0574279 0.715635 0.6421 -0.274909 +0.0338592 0.584407 -0.797806 0.134972 0.898499 -0.417712 +0.855871 0.511277 0.0584345 0.504671 0.777276 -0.375698 +0.899324 0.533898 0.333737 0.608939 0.790587 -0.0645461 +-0.579911 0.60686 0.58819 -0.623489 0.780579 0.0442412 +-0.477227 0.599176 -0.578363 0.337563 0.921098 -0.193984 +-1.05251 0.613031 -0.617462 -0.110905 0.893525 0.435101 +-0.118365 0.628906 -0.634472 -0.394889 0.87626 -0.276101 +-0.647196 0.608882 -0.205834 -0.265753 0.952791 0.146849 +-0.597693 0.564823 0.770142 -0.641523 0.746634 0.176029 +0.689751 0.582548 -0.00863103 0.552192 0.659619 -0.509889 +0.930801 0.494704 0.600212 0.646747 0.727935 0.227658 +-0.655395 0.613943 -1.10195 0.733685 0.677759 0.0484713 +-0.334237 0.604191 -0.406498 0.0480474 0.99318 -0.106238 +0.160031 0.521581 1.27956 0.179688 0.707919 0.683055 +-0.534344 0.617766 -0.555123 0.203879 0.975501 -0.0826476 +0.961977 0.480225 0.374113 0.72746 0.682612 -0.0695907 +0.772308 0.490672 0.90437 0.557571 0.67525 0.482858 +-0.128152 0.533899 1.28174 -0.129714 0.707215 0.694997 +-0.84023 0.643116 -0.66474 -0.0142096 0.964006 0.265504 +-0.677433 0.637983 -0.742461 0.27161 0.962405 0.00216705 +0.715254 0.420109 1.04245 0.531913 0.593445 0.604062 +-0.718714 0.671127 -1.04238 0.651961 0.738215 0.17316 +-0.83185 0.607229 -0.495803 -0.110001 0.989263 0.0962206 +-1.15129 0.521815 -1.11947 -0.395783 0.0452757 -0.917227 +-0.880584 0.685186 -0.780477 0.206436 0.910357 0.358657 +0.525424 0.576888 -0.370076 0.387366 0.834013 -0.3929 +0.874211 0.517094 0.12174 0.546813 0.82144 -0.161963 +-1.35837 0.613834 -0.709222 -0.174854 0.686245 0.706041 +-0.601031 0.625317 -0.54405 0.0264796 0.99948 0.0184297 +-0.517387 0.629546 -0.219139 -0.112001 0.993161 0.0329733 +-1.15257 0.654022 -0.697406 -0.117159 0.813725 0.56932 +-0.664423 0.546215 0.321692 -0.586006 0.809501 0.0361193 +-0.731919 0.641187 -0.686329 0.111282 0.985403 0.128827 +-0.803353 0.676353 -0.810057 0.221284 0.940697 0.257143 +-0.16277 0.642863 -0.479306 -0.36368 0.921323 -0.137483 +-0.766443 0.682101 -0.893068 0.4253 0.898962 0.104823 +-0.0590567 0.569258 1.2544 -0.0371138 0.754239 0.65555 +0.0392055 0.642216 -0.636638 0.219249 0.92826 -0.300436 +-1.00244 0.761342 -1.18174 -0.436184 0.0733493 -0.896863 +0.244121 0.560374 1.20626 0.301068 0.749924 0.589044 +0.0280721 0.535013 1.28999 0.0745578 0.681647 0.727872 +0.82798 0.554589 0.662555 0.518366 0.827495 0.215751 +0.774582 0.524443 0.00311462 0.497251 0.709161 -0.499831 +-0.269887 0.631492 -0.180581 -0.176957 0.967149 -0.182506 +0.500992 0.545443 1.07525 0.399971 0.73827 0.54312 +0.612787 0.600494 -0.178127 0.666297 0.702451 -0.250223 +-0.390757 0.651678 -0.0263053 -0.164131 0.961617 -0.219895 +-0.246138 0.606061 -0.475971 -0.317029 0.934668 -0.1609 +-0.849508 0.825838 -1.24563 -0.355404 0.12622 -0.926152 +-0.979593 0.678104 -0.73238 0.0400803 0.871192 0.489304 +0.778711 0.535182 0.813463 0.548715 0.772047 0.320709 +-0.90178 0.73083 -0.876545 0.3099 0.847195 0.431536 +-0.533469 0.577715 0.900234 -0.622736 0.724155 0.296311 +-0.216601 0.633948 -0.254666 -0.379756 0.919925 -0.0975909 +-0.463707 0.634672 -0.0719206 -0.0567597 0.997024 -0.0521521 +-0.508613 0.653296 0.633684 -0.530405 0.844179 0.0776624 +-0.512413 0.527924 1.04096 -0.545616 0.736923 0.399059 +-1.11032 0.692862 -0.743254 -0.0374019 0.719241 0.693754 +0.257723 0.615115 -0.522161 0.31119 0.888883 -0.336227 +-0.508927 0.634453 0.152472 -0.419196 0.904693 -0.076184 +-0.824729 0.810532 -1.22152 0.730957 0.6824 0.00561611 +-0.836994 0.747022 -0.985552 0.521523 0.798267 0.301302 +-0.261256 0.561231 1.20805 -0.298212 0.757764 0.580398 +0.817562 0.582273 0.291984 0.498097 0.858754 -0.120167 +-1.04325 0.744399 -0.825656 0.164285 0.831892 0.530062 +-0.0849279 0.654208 -0.579767 -0.197162 0.967404 -0.158923 +0.644365 0.634532 0.0249108 0.599273 0.767259 -0.228444 +-1.35851 0.759899 -0.819954 -0.0921457 0.585911 0.80512 +0.384713 0.712556 -0.143705 0.245578 0.928615 -0.278148 +-0.203982 0.653561 -0.192261 -0.538444 0.727559 -0.425131 +0.843285 0.574771 0.496686 0.526039 0.847759 0.0677292 +-1.18164 0.691282 -1.11639 -0.257951 0.0688345 -0.963703 +0.739041 0.606467 0.642347 0.396843 0.904571 0.155778 +0.649972 0.564807 0.915715 0.452895 0.761257 0.464083 +-0.122469 0.685781 -0.242477 -0.337199 0.929103 -0.151868 +-1.17837 0.748401 -0.804575 0.0826842 0.713367 0.695896 +0.770091 0.613572 0.375745 0.419372 0.907585 -0.0204012 +-0.272514 0.688245 -0.0599229 -0.706079 0.59992 -0.37623 +0.527924 0.649623 -0.184717 0.416189 0.868156 -0.270355 +-1.5945 0.779397 -0.947006 -0.459151 0.0777367 -0.884951 +-0.351601 0.703527 0.145509 -0.409928 0.887236 -0.211593 +-0.140043 0.697949 -0.215815 -0.234793 0.405474 -0.883439 +-0.481059 0.646499 0.799106 -0.507321 0.842755 0.17997 +-0.423787 0.655396 0.0388284 -0.32854 0.935914 -0.126986 +-1.06082 0.799926 -0.89195 0.254517 0.687772 0.679846 +0.721556 0.632494 0.485254 0.350424 0.934207 0.0667935 +-0.36231 0.62775 1.03778 -0.434957 0.785051 0.441031 +0.134692 0.617206 1.17118 0.177626 0.819022 0.545576 +0.453356 0.59104 1.04035 0.345081 0.824473 0.448512 +0.537279 0.611247 0.926366 0.307715 0.899216 0.311 +0.106262 0.69956 -0.332873 -0.056955 0.987692 -0.145676 +-0.0709241 0.630135 1.17495 -0.138915 0.830226 0.539841 +0.602936 0.666994 0.055035 0.49318 0.846586 -0.200162 +-0.359627 0.726084 0.308693 -0.488429 0.870361 -0.0625119 +-0.296435 0.674923 0.993254 -0.27946 0.893862 0.350588 +0.00445829 0.629167 1.17964 0.0755701 0.817924 0.57034 +-0.022336 0.658429 -0.590629 0.0410284 0.978671 -0.201294 +-1.53825 0.853204 -0.96382 -0.719313 0.553406 -0.419916 +-0.277485 0.636449 1.08796 -0.252827 0.825754 0.504192 +0.636098 0.663238 0.173615 0.379888 0.924132 -0.040811 +-0.403093 0.714258 0.541348 -0.388876 0.920805 0.0299109 +-0.036244 0.694637 -0.288039 -0.112945 0.983173 -0.143579 +-0.230906 0.69341 -0.133001 -0.877225 -0.294894 -0.378833 +0.6897 0.593029 0.79018 0.351942 0.905894 0.235569 +-1.22671 0.691637 -1.08857 -0.387725 0.111485 -0.915008 +-0.316667 0.702806 0.88897 -0.3271 0.922993 0.202706 +0.519494 0.69778 -0.00427462 0.365285 0.916237 -0.164553 +-0.397762 0.705513 0.704926 -0.389006 0.913197 0.121431 +0.276706 0.635565 1.07283 0.248709 0.883797 0.396293 +-0.455611 0.686114 0.456345 -0.508079 0.860913 -0.0261662 +-0.429463 0.649032 0.897926 -0.473573 0.822949 0.313821 +-0.836323 0.810318 -1.14065 0.645059 0.731155 0.222063 +-0.077343 0.713003 -0.199112 0.0233055 0.983156 -0.181275 +-0.183607 0.691715 -0.191639 -0.618484 0.0986708 -0.779577 +-0.162801 0.67408 1.07377 -0.138423 0.902431 0.407991 +0.604575 0.655772 0.636797 0.225176 0.966952 0.119581 +-0.926601 0.777065 -0.94572 0.41646 0.746618 0.518771 +0.578795 0.685658 0.353763 0.299074 0.953783 0.0292191 +-1.29006 0.841893 -0.873475 0.11482 0.602377 0.78991 +-0.318808 0.723088 0.115722 -0.833814 0.5123 -0.205676 +-0.178261 0.747702 -0.199229 -0.159139 0.330437 -0.930315 +-0.112004 0.720091 -0.197645 0.153837 0.678146 -0.718647 +-0.169966 0.718706 0.101554 0.483537 0.759407 -0.43531 +-0.160562 0.738887 0.0317863 -0.482582 0.868315 0.114652 +0.351542 0.674953 0.899361 0.247766 0.943387 0.220527 +0.0958182 0.669776 1.08456 0.156572 0.917189 0.366401 +-0.191972 0.73347 0.0263578 0.505205 0.860352 0.0675399 +-0.227496 0.710843 0.946927 -0.162685 0.949209 0.269322 +-1.08782 0.906068 -0.989009 0.462664 0.673655 0.576308 +-0.145981 0.729984 -0.0235778 -0.310262 0.945313 -0.100603 +0.292785 0.745339 -0.0729179 0.0200817 0.985 -0.171385 +0.000954631 0.691273 1.05737 0.0348291 0.936108 0.349984 +-0.1312 0.727149 0.0949298 -0.511461 0.741795 0.433761 +-0.302019 0.725895 0.0390265 -0.900297 -0.353863 -0.25347 +-0.12705 0.75276 -0.0587028 0.57987 0.547895 0.602961 +-0.318519 0.733247 0.11673 -0.866069 -0.497182 -0.0523011 +-0.324072 0.765021 0.276043 -0.97511 0.0709353 0.21007 +-0.0466534 0.727821 -0.129078 0.175058 0.958779 -0.223825 +0.422061 0.730908 0.243003 0.17624 0.980945 0.0817719 +0.441333 0.730901 0.0411906 0.286279 0.957154 -0.0435993 +-0.094325 0.750623 -0.0614936 0.207084 0.961282 -0.181809 +-0.24356 0.768168 -0.159154 -0.642127 -0.0870651 -0.761638 +-0.214531 0.814707 -0.0113716 0.730899 0.513835 0.449178 +-1.48427 0.897161 -0.943239 -0.509253 0.709978 0.486407 +-0.208756 0.702251 -0.170485 -0.798014 -0.27916 -0.534081 +-0.28312 0.776026 0.372068 -0.652716 0.469978 0.594207 +-0.18686 0.737852 0.00502987 0.749493 0.438479 0.495981 +-1.45877 0.835212 -0.998317 -0.346416 0.116653 -0.9308 +-0.128825 0.766611 -0.0796837 0.0982321 0.24462 0.96463 +-0.115405 0.749583 0.166509 0.344316 0.789959 -0.507357 +-0.105633 0.774413 0.0220431 -0.510495 0.837947 -0.192974 +-0.98275 0.898614 -1.07152 0.501891 0.718098 0.482121 +-0.160345 0.715664 0.0908442 -0.393383 0.919373 0.00150765 +-0.263578 0.750408 0.69855 -0.252644 0.962556 0.098267 +-0.0768517 0.7564 0.206268 -0.47845 0.869968 -0.119334 +0.324236 0.755363 0.0161118 0.185766 0.98064 -0.0619373 +-0.0954391 0.776567 0.0231252 -0.0564702 0.980871 -0.186286 +0.14294 0.713664 0.910819 0.170633 0.97453 0.145515 +-0.0837378 0.766308 0.233712 0.449342 0.721548 -0.526744 +-0.184428 0.747617 0.828051 -0.13234 0.98025 0.146959 +-0.370877 0.819472 0.124372 -0.925424 -0.378837 -0.00852571 +-0.157027 0.795401 -0.0664636 0.576086 0.815969 -0.0481658 +-0.162021 0.773049 -0.16197 0.397102 0.831934 -0.387551 +0.263962 0.763122 0.0757943 0.102035 0.994241 0.0327773 +-0.101669 0.726338 0.953809 -0.0233068 0.959089 0.282145 +-0.108283 0.771463 0.0568074 -0.464576 0.816108 0.34371 +-0.328369 0.752144 0.200791 -0.881381 -0.465517 0.0803731 +-0.212128 0.781208 -0.177785 0.0511464 0.820658 -0.569126 +-0.0569369 0.771266 0.0986003 -0.337278 0.940079 0.0499577 +-0.275609 0.767187 0.412443 -0.325645 0.943147 0.0665384 +-0.0212076 0.755264 0.790723 0.13218 0.987797 0.0823712 +0.101538 0.765463 0.107189 -0.0057519 0.990296 -0.138857 +-0.215485 0.759075 0.0693505 0.872718 0.457538 0.170358 +-0.0734489 0.797633 0.270301 -0.0460985 0.700395 -0.712264 +-0.0842491 0.751818 0.838264 -0.00202323 0.986399 0.164356 +-0.261772 0.798358 -0.135706 -0.820382 0.0767474 -0.566641 +-1.07145 0.845853 -1.13315 -0.353674 0.0911834 -0.930914 +-0.212211 0.764674 0.10487 0.879259 0.375526 -0.293061 +0.0605512 0.774143 0.231178 0.188907 0.981207 0.0393127 +-0.950411 0.914667 -1.18251 -0.126679 0.495144 -0.859526 +-0.349706 0.790443 0.0576596 -0.842647 -0.499703 -0.200605 +-0.313556 0.789582 -0.0566849 -0.856631 -0.294662 -0.423508 +-1.14658 0.889029 -1.0967 -0.272454 0.149886 -0.950422 +-0.169126 0.783579 0.480135 -0.0942962 0.989987 0.105038 +-0.183662 0.809976 -0.0786047 0.330058 0.929421 -0.165045 +-0.0228587 0.795679 0.182418 -0.486097 0.869242 -0.0901536 +-0.021023 0.773912 0.105846 0.149919 0.960528 -0.234332 +-0.226352 0.878825 0.159191 0.78278 0.455601 -0.42389 +-0.232839 0.81094 -0.127359 0.127196 0.94307 -0.30731 +0.0162244 0.785682 0.350707 0.325023 0.938466 0.116798 +-0.0808098 0.796673 0.434365 0.0567101 0.977002 0.205556 +-0.222528 0.788428 0.415207 -0.261081 0.830934 0.49131 +0.0362839 0.779216 0.163137 0.186354 0.960839 -0.205084 +-0.0633896 0.767098 0.232144 -0.497346 0.741197 -0.450859 +-1.45218 0.877968 -0.907295 -0.243649 0.548765 0.799682 +-0.0194785 0.797519 0.38006 0.512266 0.706159 0.488797 +-0.356481 0.8219 0.039159 -0.907889 0.167333 -0.384365 +-0.199775 0.761767 0.129098 0.682007 0.512928 -0.521317 +-0.287136 0.842375 -0.0211229 0.145749 0.968853 -0.200205 +-0.0026568 0.82425 0.24413 0.155217 0.92317 -0.35166 +-1.05036 0.977025 -1.13368 0.514449 0.85723 0.0223567 +-0.240208 0.831584 0.0155933 0.549555 0.835208 0.0204387 +-0.368656 0.833199 0.188341 -0.952138 -0.221753 0.21038 +-0.0244671 0.819854 0.252798 -0.562706 0.689926 -0.45537 +-0.135024 0.859036 0.296455 0.563683 0.746202 -0.354179 +0.00866362 0.82344 0.261405 0.706018 0.708094 -0.0119124 +-1.24065 0.90216 -1.07577 -0.361939 0.0676439 -0.929744 +-0.13577 0.782385 0.195484 0.587847 0.610261 -0.531054 +-0.0793492 0.817758 0.404531 0.381084 0.570545 0.727499 +-0.285538 0.822543 -0.0822071 -0.482238 0.759755 -0.43614 +0.00804448 0.803832 0.330901 0.757511 0.523948 0.38943 +-1.11639 0.925582 -1.1077 -0.340555 0.00584734 -0.940207 +-0.0519504 0.847051 0.382657 0.627513 0.300932 0.718099 +-0.369778 0.848531 0.1096 -0.921108 0.358137 -0.152635 +-1.35501 0.970734 -0.964125 -0.0972657 0.650971 0.752846 +-0.298746 0.870784 0.0863294 0.00538987 0.968632 -0.248445 +-0.359911 0.855634 0.0902518 -0.625713 0.709178 -0.324883 +-0.295728 0.793174 0.343747 -0.827253 -0.0140948 0.561653 +-0.335532 0.866114 0.082155 -0.260499 0.928376 -0.265062 +-0.255125 0.878324 0.380437 -0.571576 0.161727 0.804454 +-0.172382 0.833111 0.199461 0.628606 0.324431 -0.706823 +-0.255572 0.849796 0.0518479 0.81546 0.506925 -0.279379 +-0.189553 0.829245 0.402926 -0.216404 0.30562 0.927236 +-0.0663694 0.864765 0.322597 -0.196862 0.688161 -0.698342 +-1.17771 1.00651 -1.01779 0.271651 0.624969 0.73186 +-0.19988 0.867836 0.400713 -0.0962003 0.149852 0.984017 +-0.369657 0.862553 0.171312 -0.947216 0.314716 0.0611173 +-0.339472 0.861652 0.291217 -0.869395 -0.0613596 0.490294 +-1.09896 0.9902 -1.06721 0.514365 0.725836 0.456716 +-0.303385 0.87808 0.144016 -0.0686925 0.991672 -0.108946 +-0.347379 0.884398 0.193855 -0.270081 0.955447 -0.119065 +-0.0130932 0.849887 0.326119 0.638615 0.763379 0.097077 +-1.31493 0.888814 -1.03584 -0.342919 0.112198 -0.93264 +-0.343451 0.884491 0.264587 -0.811352 0.54778 0.204076 +-0.269691 0.886444 0.109064 0.359479 0.893351 -0.269627 +-0.0972903 0.834841 0.401516 0.125866 0.210631 0.969429 +-0.305353 0.892635 0.330094 -0.634392 0.529108 0.563552 +-1.39254 0.973315 -1.00426 -0.487901 0.528668 -0.694596 +-0.272948 0.893689 0.14878 -0.284634 0.958334 -0.024051 +-0.268323 0.887248 0.265147 -0.0234328 0.999608 -0.015304 +-0.0735852 0.879245 0.378914 0.419765 0.605361 0.676266 +-0.12736 0.867881 0.321831 0.146744 0.828054 -0.541104 +-0.233957 0.897577 0.226481 0.253558 0.965488 -0.0595084 +-1.18995 1.01497 -1.09075 -0.329955 0.229443 -0.915688 +-1.25725 1.00618 -0.99548 0.0309791 0.766161 0.641902 +-0.18975 0.881806 0.324396 0.201791 0.973983 -0.103141 +-0.0941589 0.890048 0.365348 0.181996 0.963638 -0.195649 +-0.150408 0.884506 0.397537 0.132298 0.490817 0.86116 +-0.290945 0.903126 0.319547 0.0313172 0.9933 -0.111244 +-0.234967 0.902194 0.362415 0.0195827 0.997582 -0.0666904 +-0.193269 0.893993 0.38955 -0.0464214 0.837152 0.544997 +-1.26791 1.01746 -1.04811 -0.410526 0.568818 -0.712682 +-1.176 1.01633 -1.053 0.176175 0.981003 0.0812151 +- 2340 2340 2340 +900 869 825 +779 771 696 +844 771 779 +522 667 640 +667 522 532 +126 152 131 +99 77 110 +110 77 59 +99 107 98 +77 99 98 +120 107 99 +99 110 120 +93 63 72 +93 72 111 +112 111 125 +136 125 188 +221 148 187 +154 244 236 +1075 1084 1087 +1084 1075 1069 +1053 1061 1065 +1117 1120 1103 +1103 1107 1117 +1117 1107 1128 +1126 1041 1116 +386 484 419 +624 454 484 +454 419 484 +539 608 659 +617 624 539 +659 617 539 +539 624 484 +539 484 386 +937 924 894 +941 937 894 +578 519 454 +454 624 578 +419 340 386 +608 634 659 +634 724 659 +111 112 93 +103 93 112 +93 103 109 +608 539 530 +657 617 659 +724 657 659 +444 544 530 +483 444 530 +515 544 444 +530 544 608 +691 578 624 +444 421 515 +657 724 780 +780 724 819 +780 617 657 +624 617 691 +136 103 125 +188 146 136 +146 134 136 +103 136 134 +112 125 103 +1021 1063 1032 +1032 988 1021 +962 972 966 +962 966 931 +973 947 948 +752 743 641 +342 392 424 +501 550 382 +550 501 572 +533 584 535 +535 489 533 +578 691 588 +578 588 495 +519 578 478 +478 578 495 +287 317 348 +317 381 348 +419 381 317 +287 285 317 +454 478 381 +377 396 444 +483 377 444 +421 444 396 +421 396 424 +634 641 743 +746 724 634 +304 340 285 +340 317 285 +419 317 340 +313 340 304 +313 377 340 +377 313 396 +826 724 746 +819 724 826 +746 634 743 +313 304 253 +320 424 396 +641 634 544 +608 544 634 +515 641 544 +761 742 691 +489 477 495 +495 477 478 +489 495 533 +495 588 533 +213 187 180 +477 381 478 +927 889 971 +867 971 889 +826 889 819 +826 867 889 +421 524 622 +524 605 622 +622 515 421 +421 442 524 +239 253 304 +285 209 239 +285 239 304 +239 209 184 +905 876 766 +905 766 780 +819 905 780 +752 799 743 +799 746 743 +752 809 799 +691 742 639 +691 639 588 +442 550 605 +572 605 550 +442 605 524 +261 212 287 +212 285 287 +396 313 320 +313 253 320 +348 381 477 +819 889 905 +927 905 889 +239 184 253 +251 253 184 +253 251 320 +320 251 292 +392 394 550 +392 550 442 +394 392 342 +550 394 382 +974 922 927 +905 927 922 +905 922 876 +922 950 849 +922 849 876 +766 876 849 +746 799 826 +799 867 826 +867 799 809 +139 209 157 +184 209 139 +212 157 209 +974 927 1001 +971 1001 927 +974 1001 1026 +1001 1024 1026 +742 770 639 +742 817 770 +639 770 731 +316 287 348 +742 761 817 +761 766 817 +639 533 588 +533 639 584 +639 731 744 +639 744 584 +139 176 184 +176 175 184 +215 184 175 +251 229 292 +316 383 389 +348 383 316 +389 329 316 +752 745 845 +745 831 845 +809 752 845 +101 169 156 +130 101 156 +114 169 101 +114 101 96 +169 114 139 +212 261 203 +212 203 169 +169 203 156 +320 292 354 +262 322 354 +262 354 292 +322 342 354 +389 383 528 +528 383 489 +389 528 407 +971 867 975 +572 730 605 +719 730 572 +215 229 184 +251 184 229 +215 175 172 +215 172 229 +262 292 229 +352 329 389 +407 352 389 +895 1005 975 +1005 895 981 +730 719 745 +785 745 719 +286 261 287 +287 316 286 +352 324 329 +1072 1019 1026 +1019 1012 1028 +974 1028 922 +974 1019 1028 +877 895 809 +895 877 981 +119 139 114 +119 176 139 +176 119 175 +322 299 334 +262 299 322 +322 334 394 +382 394 334 +322 394 342 +1072 1102 1088 +1110 1088 1102 +1088 1019 1072 +883 1028 978 +76 119 96 +119 114 96 +119 76 129 +230 130 156 +172 175 153 +172 153 128 +286 230 203 +286 203 261 +277 316 329 +324 277 329 +373 382 334 +1072 1026 1024 +1072 1024 1078 +1046 1078 1024 +883 950 1028 +877 913 981 +936 981 913 +831 785 851 +818 851 785 +877 831 851 +770 813 823 +813 770 817 +85 80 75 +96 85 75 +73 75 80 +128 153 129 +153 119 129 +153 175 119 +259 286 277 +286 316 277 +259 277 271 +246 299 262 +385 352 407 +460 385 407 +460 407 435 +813 817 857 +813 857 862 +598 629 517 +602 517 629 +85 78 73 +78 65 73 +85 73 80 +56 78 85 +56 52 38 +262 229 216 +229 172 216 +246 262 216 +307 270 352 +517 580 460 +831 745 785 +787 725 823 +744 823 725 +823 827 787 +629 598 584 +615 645 426 +426 645 514 +79 73 65 +78 56 38 +65 34 51 +34 65 38 +78 38 65 +17 38 30 +52 30 38 +11 17 30 +69 23 52 +101 70 56 +101 130 70 +172 128 167 +167 216 172 +246 216 167 +460 435 517 +1046 1024 1013 +1033 1013 1017 +1017 1013 1005 +887 913 851 +887 851 818 +936 913 887 +851 913 877 +719 699 704 +719 704 765 +645 704 699 +744 725 629 +517 602 693 +517 693 580 +65 51 79 +14 5 11 +14 12 5 +11 26 14 +30 52 23 +26 30 23 +26 11 30 +56 69 52 +232 277 324 +232 271 277 +264 232 324 +1110 1064 1088 +883 978 906 +5 17 11 +9 17 5 +12 14 16 +19 16 25 +19 12 16 +70 83 69 +70 69 56 +128 129 104 +143 104 123 +168 89 130 +283 404 373 +1144 1138 1141 +999 1012 1038 +1055 1038 1012 +906 978 973 +878 936 887 +878 887 818 +765 785 719 +704 751 765 +751 818 765 +818 785 765 +704 645 615 +615 751 704 +629 725 722 +22 8 35 +22 35 51 +9 5 44 +5 12 44 +8 9 44 +18 39 19 +39 44 19 +23 83 45 +23 69 83 +23 45 26 +70 67 83 +89 86 67 +57 67 74 +67 57 83 +74 67 86 +86 89 150 +1141 1110 1109 +1109 1127 1141 +1154 1141 1127 +1144 1141 1154 +1141 1064 1110 +1141 1138 1064 +1136 1064 1138 +1055 1064 1136 +1085 1055 1121 +1121 1079 1085 +1078 1093 1102 +1078 1102 1072 +1093 1078 1046 +1089 1102 1093 +1083 1013 1033 +1083 1046 1013 +1038 1055 1085 +1085 1054 1038 +706 751 615 +706 774 751 +48 35 8 +48 8 41 +41 8 44 +35 48 54 +12 19 44 +13 18 20 +29 21 25 +21 20 25 +45 14 26 +74 68 57 +68 74 87 +190 218 167 +238 167 218 +264 270 244 +244 270 307 +264 244 221 +388 385 460 +432 333 520 +531 432 520 +1129 1127 1114 +1109 1114 1127 +1129 1131 1127 +1127 1131 1154 +1159 1131 1150 +1159 1154 1131 +1144 1154 1166 +1154 1159 1166 +1159 1165 1166 +1168 1169 1165 +1165 1169 1166 +1017 981 1010 +1005 981 1017 +1010 981 936 +1010 1033 1017 +955 1010 936 +1010 955 1014 +936 959 955 +936 878 959 +864 862 894 +725 789 722 +725 787 789 +470 592 520 +520 592 531 +470 640 592 +66 90 48 +66 48 81 +44 81 41 +41 81 48 +44 71 81 +50 71 44 +29 25 16 +16 53 29 +53 21 29 +55 40 68 +40 57 68 +238 266 283 +385 306 307 +333 361 520 +1150 1129 1124 +1131 1129 1150 +1114 1108 1129 +1165 1160 1168 +1164 1168 1160 +1164 1167 1168 +1164 1158 1167 +1164 1161 1158 +1146 1055 1136 +1136 1138 1146 +1146 1121 1055 +1050 1010 1014 +1050 1033 1010 +1014 1044 1050 +973 926 906 +843 787 827 +827 864 843 +90 92 54 +90 54 48 +50 44 39 +37 50 39 +66 131 90 +66 81 131 +37 18 31 +37 39 18 +37 31 84 +33 21 53 +159 150 164 +86 150 159 +168 164 150 +223 225 197 +206 197 225 +266 218 260 +238 218 266 +233 260 218 +376 388 445 +1142 1122 1092 +1142 1160 1165 +1160 1123 1164 +1145 1121 1152 +1079 1121 1145 +1121 1146 1152 +1149 1145 1152 +859 869 878 +878 774 859 +878 751 774 +696 771 706 +771 774 706 +716 722 777 +653 640 696 +653 696 706 +615 653 706 +159 115 87 +123 82 113 +82 88 113 +123 113 137 +177 186 164 +164 168 177 +197 206 194 +197 177 191 +194 177 197 +191 177 168 +266 260 321 +266 321 333 +260 272 321 +404 333 432 +333 321 361 +1092 1160 1142 +1092 1123 1160 +1112 1092 1122 +1158 1161 1157 +1148 1167 1158 +1148 1149 1167 +1132 1149 1135 +1132 1145 1149 +1079 1145 1132 +1149 1137 1135 +1115 1132 1135 +1115 1079 1132 +1076 1083 1050 +1033 1050 1083 +1093 1083 1076 +1093 1076 1089 +1099 1052 1054 +965 999 962 +996 962 999 +999 965 1002 +973 1002 965 +926 916 906 +899 777 814 +722 814 777 +722 789 814 +71 94 81 +94 131 81 +55 72 63 +68 72 55 +145 123 137 +145 143 123 +214 143 145 +167 143 190 +214 190 143 +260 233 272 +264 187 232 +264 221 187 +1082 1089 1057 +1124 1111 1118 +1104 1118 1105 +1111 1105 1118 +1126 1106 1123 +1126 1139 1106 +1077 1126 1123 +1077 1123 1092 +1077 1092 1073 +1112 1073 1092 +1077 1073 1068 +1153 1161 1123 +1158 1157 1148 +1148 1134 1149 +1137 1149 1134 +1063 1021 1100 +996 1052 1021 +996 1038 1052 +999 1038 996 +1038 1054 1052 +962 973 965 +827 862 864 +932 959 869 +878 869 959 +693 722 716 +640 470 522 +106 108 94 +53 42 33 +164 186 188 +159 164 188 +388 306 385 +306 388 301 +1076 1057 1089 +1097 1082 1090 +1122 1118 1104 +1077 1068 1126 +1139 1116 1106 +1134 1153 1140 +1135 1137 1128 +1135 1128 1115 +1099 1100 1021 +1099 1021 1052 +1099 1115 1100 +948 941 926 +888 946 853 +853 946 899 +92 90 131 +116 84 61 +106 94 116 +107 108 106 +107 106 98 +117 98 106 +97 116 61 +306 301 236 +1105 1087 1104 +1116 1139 1126 +1087 1084 1051 +1106 1096 1140 +1091 1140 1096 +1106 1116 1096 +1140 1153 1106 +1050 1044 1076 +1117 1137 1134 +1128 1137 1117 +1094 1100 1115 +1094 1115 1128 +1027 1076 1044 +1027 1043 1076 +844 869 859 +844 825 869 +844 859 774 +774 771 844 +667 779 696 +667 684 779 +640 667 696 +134 107 120 +122 117 106 +122 106 116 +91 122 97 +91 117 122 +116 97 122 +77 98 62 +111 72 115 +214 145 155 +137 155 145 +214 155 199 +233 214 199 +233 199 272 +232 187 213 +361 321 380 +321 272 380 +375 376 445 +375 445 482 +1041 1126 1068 +1041 1068 1071 +1091 1120 1140 +1065 1061 1091 +1065 1091 1045 +1120 1091 1061 +1134 1120 1117 +1140 1120 1134 +1063 1094 1060 +1100 1094 1063 +1021 988 996 +988 962 996 +958 1027 1006 +1027 1044 1006 +1006 932 958 +869 900 932 +1034 1057 1076 +1034 1076 1043 +1057 1034 1036 +1056 1031 1059 +1053 1045 1048 +1053 1065 1045 +1061 1053 1080 +1061 1103 1120 +1061 1080 1103 +1063 1060 1032 +1060 1086 1039 +1081 1039 1086 +1060 1039 1032 +1086 1060 1094 +993 1032 1000 +1032 1039 1000 +1032 993 988 +932 900 958 +947 941 948 +103 134 109 +134 110 109 +177 194 186 +241 272 199 +335 494 380 +335 401 494 +1053 1059 1067 +1053 1048 1059 +1080 1053 1067 +1031 1040 1081 +1059 1031 1080 +1081 1080 1031 +1059 1080 1067 +1040 1000 1039 +1039 1081 1040 +1000 1040 993 +899 814 853 +814 789 853 +134 120 110 +208 205 226 +213 183 189 +272 293 380 +272 241 293 +154 236 133 +178 133 236 +256 236 301 +376 301 388 +482 574 465 +573 574 482 +490 532 529 +987 1074 1058 +987 1049 1074 +1058 1074 1057 +1086 1103 1081 +1103 1080 1081 +1086 1107 1103 +993 1040 1031 +858 902 900 +858 900 825 +825 829 858 +825 844 829 +716 832 768 +716 768 693 +716 777 832 +768 832 773 +573 693 768 +633 721 667 +684 667 721 +633 667 532 +166 182 146 +166 146 188 +188 186 194 +188 194 234 +171 183 180 +158 171 180 +213 180 183 +154 133 118 +178 236 256 +375 482 465 +465 420 375 +1020 993 1031 +1008 1027 1007 +1008 1043 1027 +992 1008 1007 +1043 1008 1022 +966 993 953 +953 993 984 +933 931 923 +941 947 937 +951 947 938 +992 1007 963 +773 835 836 +682 768 773 +682 573 768 +682 773 836 +174 144 160 +165 152 144 +170 155 151 +151 155 137 +151 142 170 +155 170 199 +140 204 171 +204 183 171 +180 148 147 +147 148 132 +148 180 187 +309 335 293 +380 293 335 +335 309 405 +265 256 301 +376 375 265 +376 265 301 +256 265 323 +375 420 323 +375 323 265 +982 1011 1018 +1018 1011 1025 +1018 1025 1058 +1025 987 1058 +1034 1022 991 +1022 1034 1043 +994 1037 986 +994 986 976 +901 832 777 +832 835 773 +832 901 835 +165 200 173 +149 152 165 +188 234 166 +182 166 234 +281 231 255 +226 205 204 +158 180 147 +963 939 992 +963 902 939 +983 994 976 +976 968 983 +983 1003 994 +994 1003 1037 +998 914 901 +835 901 914 +998 901 1016 +914 836 835 +185 220 165 +185 165 174 +144 126 124 +144 124 160 +181 161 182 +194 231 234 +255 305 281 +141 158 147 +140 171 158 +49 133 178 +118 49 24 +118 133 49 +162 256 323 +529 405 490 +643 633 532 +643 532 551 +532 490 551 +1011 961 985 +928 985 961 +1011 920 961 +1004 989 995 +1004 991 989 +1004 1036 1034 +1034 991 1004 +1015 1049 987 +987 956 1015 +1049 1015 1009 +931 866 917 +923 911 933 +661 783 721 +783 684 721 +721 643 661 +633 643 721 +686 661 643 +249 220 185 +249 263 220 +200 220 217 +200 165 220 +263 217 220 +179 160 181 +181 124 161 +181 160 124 +160 179 174 +182 237 181 +255 228 305 +305 228 227 +305 227 310 +305 338 281 +226 204 228 +204 205 183 +241 199 170 +309 241 243 +243 241 170 +241 309 293 +309 243 275 +256 162 178 +162 100 49 +162 49 178 +551 686 643 +985 928 918 +1020 984 993 +911 938 933 +938 947 933 +875 935 939 +992 939 935 +834 783 807 +661 807 783 +939 881 834 +939 834 807 +1062 901 1113 +861 1113 901 +1062 1016 901 +790 661 686 +686 740 790 +250 240 211 +250 211 200 +289 240 250 +240 243 211 +142 211 170 +211 243 170 +228 204 227 +490 414 464 +490 405 414 +490 464 551 +992 1022 1008 +953 984 925 +925 966 953 +984 915 925 +984 1015 915 +896 925 915 +983 968 943 +943 970 983 +970 1003 983 +684 783 834 +224 258 193 +224 193 234 +224 234 231 +318 224 231 +318 281 327 +318 231 281 +140 192 204 +227 204 242 +242 204 192 +274 345 275 +405 309 345 +345 309 275 +275 243 274 +274 331 345 +961 868 928 +961 885 868 +1036 1004 995 +875 939 807 +740 807 790 +790 807 661 +740 865 807 +807 865 875 +1113 1133 1062 +274 243 240 +158 141 140 +141 147 95 +95 105 64 +553 551 464 +551 553 686 +918 892 897 +868 918 928 +892 918 868 +982 921 945 +977 956 987 +987 944 977 +987 997 944 +866 791 852 +911 880 909 +911 909 938 +970 1035 1003 +1035 970 980 +1156 1062 1133 +1156 1016 1062 +290 311 295 +336 311 314 +314 311 290 +263 249 291 +269 276 288 +269 314 257 +276 269 257 +276 198 298 +257 198 276 +257 252 198 +179 210 174 +210 179 248 +254 237 193 +362 431 423 +423 431 485 +464 423 485 +345 362 423 +485 553 464 +856 868 885 +885 961 898 +920 898 961 +879 795 898 +885 898 795 +918 897 964 +985 918 964 +964 897 912 +921 982 995 +921 995 989 +960 1022 969 +1022 992 969 +960 991 1022 +966 904 866 +966 866 931 +954 938 909 +954 909 980 +267 295 328 +295 311 330 +328 295 330 +330 356 347 +347 328 330 +254 248 179 +181 254 179 +248 254 247 +242 202 297 +227 242 297 +141 121 140 +2 4 6 +2 6 10 +10 43 2 +4 2 1 +4 1 64 +824 856 833 +816 868 856 +828 897 892 +828 850 897 +912 997 964 +910 989 929 +929 886 910 +870 910 886 +934 910 870 +842 866 904 +904 882 842 +842 791 866 +1003 1035 1047 +1156 1133 1163 +399 347 429 +336 314 332 +314 308 332 +269 308 314 +288 343 308 +288 308 269 +276 298 288 +240 331 274 +289 331 240 +824 833 801 +801 778 824 +824 816 856 +892 868 816 +828 892 837 +991 960 989 +874 956 977 +919 915 956 +1015 956 915 +874 919 956 +896 915 873 +896 882 904 +873 882 896 +980 909 940 +393 347 356 +332 371 336 +344 336 371 +364 308 343 +267 291 249 +298 219 280 +248 280 219 +280 397 298 +250 217 263 +250 263 289 +250 200 217 +248 247 302 +247 254 282 +282 302 247 +3 64 1 +0 27 3 +0 3 1 +0 1 43 +2 43 1 +485 597 553 +485 431 597 +833 795 811 +801 833 811 +794 816 824 +778 794 824 +879 839 795 +833 885 795 +816 837 892 +960 929 989 +960 969 929 +940 909 930 +909 884 930 +412 344 371 +422 412 371 +341 308 364 +263 291 294 +319 289 263 +319 263 294 +302 280 248 +302 353 280 +280 353 397 +331 337 339 +331 289 337 +339 362 331 +331 362 345 +326 258 224 +326 296 258 +327 281 338 +305 367 338 +227 297 310 +839 733 795 +839 815 733 +811 795 733 +810 816 794 +810 794 759 +816 810 837 +879 808 839 +815 839 808 +810 806 828 +806 850 828 +977 944 907 +944 891 907 +880 884 909 +884 880 838 +1047 1035 1070 +1035 980 1070 +1147 1143 1047 +1147 1047 1070 +1163 1171 1170 +1171 1162 1170 +1170 1156 1163 +1163 1143 1171 +399 473 503 +347 399 359 +369 359 443 +476 446 472 +476 413 446 +422 446 412 +422 472 446 +366 422 371 +366 400 422 +202 121 135 +140 121 202 +202 135 201 +60 121 141 +135 121 60 +815 808 805 +828 837 810 +759 713 757 +713 739 757 +759 757 810 +810 757 806 +893 929 969 +893 860 929 +875 865 893 +860 893 865 +860 865 822 +838 793 884 +1070 1119 1147 +635 760 711 +476 473 413 +325 360 291 +325 291 267 +319 294 291 +319 291 349 +360 349 291 +476 472 498 +440 422 400 +422 440 472 +366 364 400 +366 341 364 +202 201 297 +46 27 15 +46 3 27 +60 3 46 +597 540 711 +553 597 638 +597 711 638 +597 433 540 +635 711 540 +801 811 754 +778 762 727 +758 778 727 +806 846 850 +929 860 886 +767 891 841 +840 874 800 +840 800 763 +967 979 890 +979 872 890 +979 1030 930 +872 979 884 +930 884 979 +980 1030 1119 +1098 1119 1030 +980 1119 1070 +822 740 671 +711 760 822 +711 822 671 +740 822 865 +487 473 476 +476 498 487 +510 487 508 +498 508 487 +472 428 498 +428 521 498 +472 440 428 +400 411 440 +411 400 364 +411 364 343 +193 296 254 +312 363 296 +282 254 296 +193 258 296 +326 312 296 +431 362 339 +431 339 433 +431 433 597 +635 540 450 +649 754 676 +673 701 689 +673 680 701 +749 701 727 +749 727 762 +689 701 749 +775 757 739 +757 775 806 +756 846 806 +756 806 775 +750 732 841 +841 732 767 +840 830 919 +782 792 873 +782 728 792 +728 788 792 +788 882 792 +882 873 792 +842 788 786 +842 882 788 +842 786 791 +822 760 812 +508 498 521 +428 440 411 +409 397 378 +397 353 378 +680 700 713 +680 713 758 +664 700 680 +664 683 700 +664 665 683 +886 854 870 +776 848 821 +872 884 855 +511 443 466 +480 511 609 +480 443 511 +418 443 480 +508 527 510 +508 521 527 +521 506 527 +457 521 428 +355 349 372 +372 417 355 +195 297 201 +907 891 767 +848 776 800 +848 800 874 +720 786 788 +753 764 791 +753 791 786 +764 753 715 +609 511 676 +595 534 542 +595 466 534 +510 527 542 +510 542 534 +372 349 360 +408 415 409 +409 415 397 +415 411 343 +415 298 397 +644 631 664 +664 631 665 +912 846 863 +890 872 784 +542 562 595 +583 595 562 +621 595 583 +649 511 595 +466 595 511 +649 595 621 +428 408 457 +428 415 408 +428 411 415 +296 363 282 +387 398 406 +387 406 346 +297 195 303 +644 621 631 +631 621 632 +841 863 750 +841 891 863 +863 846 750 +621 583 632 +527 506 542 +537 542 512 +537 562 542 +570 583 562 +496 506 437 +457 408 506 +437 506 408 +846 756 750 +714 781 635 +463 418 541 +408 409 437 +378 353 425 +425 353 365 +398 538 479 +538 398 509 +391 509 398 +387 391 398 +358 449 509 +358 509 391 +513 509 449 +513 449 596 +427 395 456 +303 456 395 +712 738 756 +705 712 683 +734 840 763 +734 830 840 +830 734 782 +425 451 378 +451 425 436 +312 346 363 +670 541 480 +821 767 741 +788 728 720 +692 720 650 +695 715 717 +717 748 695 +855 793 748 +455 437 409 +447 436 365 +425 365 436 +447 365 434 +351 430 390 +538 509 564 +632 627 631 +613 632 591 +627 703 665 +665 703 683 +703 705 683 +702 705 703 +738 712 705 +705 694 738 +741 767 690 +800 735 763 +734 723 782 +728 782 723 +632 583 570 +591 632 570 +496 437 455 +455 500 496 +458 500 455 +455 475 458 +499 475 474 +462 475 499 +499 474 502 +448 441 390 +441 448 447 +513 564 509 +694 705 702 +707 741 690 +735 687 726 +735 726 710 +718 720 728 +720 718 650 +497 462 499 +497 504 462 +502 497 499 +523 462 504 +523 500 462 +557 556 451 +525 557 451 +525 451 436 +554 438 456 +596 438 554 +681 694 702 +677 694 681 +637 685 630 +685 677 630 +620 655 637 +650 718 560 +564 650 560 +650 564 589 +513 596 589 +513 589 564 +604 589 596 +604 717 589 +596 554 604 +523 504 497 +565 523 497 +525 436 493 +561 525 493 +518 448 549 +576 518 549 +447 448 518 +554 456 491 +491 488 554 +303 370 456 +456 370 488 +613 627 632 +660 702 663 +658 681 660 +637 630 620 +735 708 675 +735 675 763 +735 710 708 +763 675 734 +585 604 554 +755 748 666 +600 556 558 +565 556 600 +558 620 600 +558 655 620 +565 497 502 +505 549 448 +439 430 379 +439 379 403 +461 439 403 +479 538 587 +560 587 538 +538 564 560 +591 546 619 +546 559 619 +613 591 619 +663 625 654 +619 663 613 +677 658 616 +681 658 677 +660 681 702 +655 612 672 +656 563 628 +656 669 563 +726 656 648 +656 628 648 +648 710 726 +555 546 591 +555 537 546 +611 594 600 +600 620 611 +565 600 594 +566 523 565 +618 678 612 +612 690 672 +612 678 690 +618 561 576 +618 576 563 +403 379 363 +403 363 346 +587 599 453 +718 646 560 +587 560 646 +619 625 663 +619 559 625 +654 660 663 +658 642 616 +642 611 616 +707 656 709 +656 707 669 +674 723 734 +723 674 599 +728 723 718 +646 718 723 +646 723 599 +545 559 546 +545 546 537 +559 545 543 +492 545 537 +492 537 512 +512 500 492 +630 611 620 +611 630 616 +582 566 565 +582 565 594 +594 603 582 +594 611 603 +618 612 579 +618 669 678 +648 571 590 +648 590 606 +628 571 648 +590 469 507 +452 430 439 +526 507 452 +403 406 461 +406 453 486 +406 486 461 +403 346 406 +599 587 646 +658 660 642 +654 601 660 +603 660 601 +603 611 660 +611 642 660 +688 623 675 +688 675 708 +675 623 607 +675 607 674 +607 599 674 +674 734 675 +603 601 582 +669 618 563 +452 469 390 +452 390 430 +469 452 507 +486 547 461 +547 486 536 +581 559 543 +581 543 566 +566 582 581 +625 559 601 +601 559 581 +582 601 581 +601 654 625 +590 548 606 +507 548 590 +636 606 548 +439 461 526 +547 526 461 +439 526 452 +552 526 547 +536 486 453 +599 536 453 +614 623 688 +708 710 688 +614 688 636 +710 636 688 +648 636 710 +648 606 636 +1119 1171 1147 +1119 1162 1171 +1147 1171 1143 +453 479 587 +350 327 338 +350 318 327 +338 391 350 +350 391 387 +358 391 367 +338 367 391 +89 70 130 +70 89 67 +602 629 722 +602 722 693 +462 458 475 +500 458 462 +561 618 579 +714 450 670 +541 670 450 +635 450 714 +531 615 426 +702 613 663 +703 613 702 +703 627 613 +940 930 1030 +1030 980 940 +97 42 91 +91 62 117 +98 117 62 +1155 1138 1168 +529 522 494 +494 522 470 +401 529 494 +529 532 522 +31 61 84 +840 919 874 +37 84 50 +50 84 116 +283 278 238 +278 246 238 +351 363 379 +379 430 351 +299 373 334 +939 902 858 +939 858 881 +751 878 818 +695 748 793 +1001 1029 1024 +1029 1001 971 +971 975 1029 +1005 1029 975 +805 808 870 +805 870 854 +781 805 854 +955 959 1014 +959 932 1014 +20 19 25 +19 20 18 +1128 1107 1086 +1128 1086 1094 +1138 1144 1169 +1144 1166 1169 +1138 1169 1168 +449 438 596 +438 449 358 +426 404 432 +432 531 426 +426 514 404 +404 514 373 +552 577 614 +577 623 614 +577 552 547 +548 526 552 +548 507 526 +552 614 548 +548 614 636 +864 894 924 +904 966 925 +925 896 904 +745 622 730 +605 730 622 +647 745 752 +647 622 745 +519 478 454 +726 687 656 +873 830 782 +830 873 915 +830 915 919 +950 922 1028 +159 87 74 +159 74 86 +521 457 506 +1155 1152 1146 +1138 1155 1146 +1152 1155 1167 +1152 1167 1149 +209 285 212 +182 161 146 +135 102 201 +195 201 102 +469 471 390 +414 345 423 +464 414 423 +405 345 414 +337 289 319 +319 349 337 +339 337 355 +349 355 337 +833 856 885 +823 744 770 +731 770 744 +665 631 627 +1019 974 1026 +662 714 670 +662 815 714 +670 480 662 +707 821 741 +549 563 576 +563 549 628 +505 628 549 +607 623 577 +577 536 607 +577 547 536 +536 599 607 +966 972 993 +884 793 855 +421 392 442 +424 392 421 +6 28 118 +157 169 139 +212 169 157 +441 447 434 +776 687 735 +776 821 687 +401 335 405 +401 405 529 +72 68 87 +72 87 115 +54 51 35 +54 79 51 +879 898 945 +898 920 945 +947 931 933 +931 947 973 +831 877 845 +877 809 845 +271 213 223 +232 213 271 +1153 1157 1161 +1134 1157 1153 +1134 1148 1157 +463 372 374 +374 372 360 +463 374 418 +306 236 244 +306 244 307 +926 973 948 +418 480 541 +677 616 630 +310 315 305 +315 367 305 +1082 1074 1049 +1082 1057 1074 +167 238 246 +456 395 427 +456 438 395 +438 358 395 +676 511 649 +906 916 857 +916 862 857 +883 906 857 +584 744 629 +298 415 288 +415 343 288 +132 95 147 +132 105 95 +105 132 28 +676 662 609 +480 609 662 +815 676 733 +815 662 676 +1122 1073 1112 +1104 1073 1122 +1019 1088 1012 +1012 1088 1064 +1055 1012 1064 +76 104 129 +123 104 76 +123 76 82 +714 815 805 +805 781 714 +1124 1097 1111 +1129 1108 1097 +1129 1097 1124 +299 246 278 +299 278 373 +283 373 278 +1099 1079 1115 +1054 1085 1079 +1054 1079 1099 +848 767 821 +848 907 767 +369 443 374 +418 374 443 +333 404 266 +266 404 283 +686 553 671 +671 553 638 +686 671 740 +754 649 689 +649 673 689 +159 138 115 +111 115 138 +188 125 138 +125 111 138 +159 188 138 +855 784 872 +531 592 653 +653 592 640 +531 653 615 +1045 1042 1048 +1069 1048 1042 +1069 1042 1084 +671 638 711 +8 22 9 +34 9 22 +22 51 34 +190 214 233 +233 218 190 +979 967 1098 +1030 979 1098 +517 435 598 +1168 1167 1155 +185 174 198 +210 198 174 +252 185 198 +468 474 475 +475 455 468 +949 976 946 +986 946 976 +468 451 556 +468 378 451 +393 429 347 +429 393 413 +416 413 393 +270 324 352 +270 264 324 +15 102 46 +135 46 102 +1048 1069 1075 +1059 1048 1075 +951 937 947 +952 937 951 +800 776 735 +493 436 447 +493 518 561 +576 561 518 +493 447 518 +307 352 385 +1047 1143 1003 +1143 1163 1037 +1133 1037 1163 +1037 1003 1143 +762 778 801 +794 758 759 +34 17 9 +17 34 38 +32 60 141 +32 141 95 +64 3 32 +3 60 32 +32 95 64 +165 144 174 +1013 1024 1029 +1029 1005 1013 +230 235 191 +286 235 230 +191 235 197 +494 361 380 +470 520 494 +494 520 361 +191 168 230 +781 760 635 +812 760 781 +812 781 854 +82 73 88 +886 860 822 +886 822 812 +812 854 886 +192 202 242 +140 202 192 +932 1006 1014 +1006 1044 1014 +1046 1083 1093 +424 320 354 +342 424 354 +617 761 691 +399 429 473 +413 473 429 +738 694 685 +677 685 694 +685 750 738 +975 867 895 +867 809 895 +910 934 921 +910 921 989 +713 700 739 +700 683 739 +756 739 712 +756 775 739 +712 739 683 +206 208 194 +208 231 194 +230 156 203 +864 888 843 +924 888 864 +853 843 888 +645 699 572 +699 719 572 +514 645 501 +501 645 572 +142 151 113 +151 137 113 +152 149 142 +142 113 152 +152 113 88 +168 130 230 +823 813 862 +827 823 862 +968 937 943 +968 924 937 +496 500 512 +707 690 669 +669 690 678 +45 57 40 +83 57 45 +14 45 40 +14 40 16 +55 16 40 +987 1025 997 +997 985 964 +1011 997 1025 +1011 985 997 +968 976 949 +949 924 968 +945 1011 982 +945 920 1011 +382 373 514 +514 501 382 +328 359 267 +325 267 359 +325 359 369 +347 359 328 +56 85 101 +101 85 96 +525 575 557 +525 612 575 +558 612 655 +558 557 575 +558 556 557 +612 558 575 +969 935 893 +969 992 935 +875 893 935 +503 487 510 +487 503 473 +128 143 167 +128 104 143 +617 780 761 +780 766 761 +446 413 416 +446 416 393 +59 63 93 +59 93 109 +109 110 59 +690 767 769 +769 672 690 +912 863 944 +891 944 863 +944 997 912 +963 958 902 +900 902 958 +1027 963 1007 +1027 958 963 +506 496 512 +506 512 542 +488 491 456 +580 573 482 +92 131 73 +647 752 641 +647 641 515 +647 515 622 +226 228 255 +226 255 208 +231 208 255 +897 850 846 +912 897 846 +926 862 916 +926 894 862 +941 894 926 +135 60 46 +318 350 326 +318 326 224 +1123 1161 1164 +1153 1123 1106 +1045 1091 1096 +1045 1096 1116 +1071 1045 1041 +1045 1116 1041 +61 31 42 +31 33 42 +97 61 42 +42 62 91 +341 332 308 +332 341 371 +341 366 371 +693 573 580 +510 534 503 +466 503 534 +748 717 666 +585 666 717 +585 717 604 +146 124 134 +146 161 124 +808 879 870 +453 406 479 +398 479 406 +355 417 433 +355 433 339 +417 450 540 +450 463 541 +417 540 433 +463 450 417 +463 417 372 +680 644 664 +680 673 644 +257 314 290 +257 290 252 +10 24 36 +24 49 36 +100 36 49 +118 24 6 +10 6 24 +10 36 43 +302 365 353 +351 365 302 +206 205 208 +144 152 126 +561 579 525 +525 579 612 +1012 999 978 +1012 978 1028 +1002 973 978 +978 999 1002 +358 367 395 +315 395 367 +378 468 455 +378 455 409 +350 387 326 +387 312 326 +387 346 312 +79 92 73 +79 54 92 +759 758 713 +325 369 360 +374 360 369 +1071 1073 1104 +1071 1068 1073 +1087 1051 1071 +1071 1104 1087 +53 16 55 +63 53 55 +168 150 89 +149 165 173 +142 149 173 +211 142 173 +200 211 173 +94 50 116 +71 50 94 +535 584 598 +528 435 407 +528 489 535 +535 598 528 +598 435 528 +118 244 154 +28 244 118 +132 221 244 +148 221 132 +244 28 132 +738 750 756 +923 852 880 +911 923 880 +838 880 852 +852 791 764 +838 852 764 +193 182 234 +182 193 237 +181 237 254 +673 649 644 +621 644 649 +390 471 448 +505 448 471 +505 471 469 +1020 1009 1015 +1020 1015 984 +33 18 13 +33 31 18 +13 21 33 +20 21 13 +363 351 282 +282 351 302 +290 295 267 +695 793 838 +838 764 695 +695 764 715 +96 75 76 +73 76 75 +82 76 73 +821 709 687 +707 709 821 +687 709 656 +310 297 303 +315 303 395 +315 310 303 +474 468 556 +556 565 474 +502 474 565 +124 131 134 +126 131 124 +107 131 108 +134 131 107 +94 108 131 +545 500 543 +523 543 500 +545 492 500 +543 523 566 +748 784 855 +784 748 755 +1105 1059 1075 +1111 1056 1105 +1105 1056 1059 +1087 1105 1075 +754 811 733 +676 754 733 +758 794 778 +874 977 907 +848 874 907 +330 336 356 +330 311 336 +356 336 344 +393 356 344 +223 235 259 +286 259 235 +197 235 223 +223 259 271 +779 684 834 +834 829 779 +779 829 844 +858 829 881 +834 881 829 +537 555 562 +570 562 555 +570 555 591 +754 749 801 +801 749 762 +754 689 749 +843 853 787 +789 787 853 +460 580 482 +482 445 460 +388 460 445 +750 685 732 +732 685 672 +732 769 767 +769 732 672 +210 248 219 +219 198 210 +198 219 298 +152 88 131 +73 131 88 +962 988 972 +993 972 988 +1150 1165 1159 +1150 1118 1165 +1124 1118 1150 +1165 1118 1142 +1122 1142 1118 +1084 1042 1051 +1071 1051 1042 +1045 1071 1042 +105 28 4 +6 4 28 +64 105 4 +1102 1109 1110 +1109 1102 1089 +1031 1111 1090 +1031 1056 1111 +1090 1049 1031 +1009 1031 1049 +1009 1020 1031 +483 530 539 +386 483 539 +483 386 377 +340 377 386 +213 189 223 +189 183 225 +225 223 189 +205 206 225 +205 225 183 +949 946 888 +949 888 924 +692 786 720 +692 753 786 +692 715 753 +715 692 589 +715 589 717 +650 589 692 +923 931 917 +917 852 923 +917 866 852 +883 857 950 +849 950 857 +849 857 817 +817 766 849 +879 921 934 +879 945 921 +870 879 934 +637 672 685 +655 672 637 +466 399 503 +443 359 466 +466 359 399 +1109 1108 1114 +1109 1089 1108 +1089 1097 1108 +1082 1097 1089 +393 344 446 +344 412 446 +1049 1090 1082 +1097 1090 1111 +441 365 390 +441 434 365 +365 351 390 +42 77 62 +628 505 571 +469 571 505 +590 571 469 +53 59 42 +53 63 59 +59 77 42 +1036 1058 1057 +1018 1058 1036 +982 1018 995 +1018 1036 995 +943 937 952 +952 980 943 +980 970 943 +938 954 951 +954 952 951 +952 954 980 +381 419 454 +973 962 931 +986 1113 899 +899 946 986 +1113 986 1133 +1037 1133 986 +185 252 267 +268 267 252 +185 267 249 +267 268 290 +290 268 252 +899 861 777 +861 901 777 +1113 861 899 +758 701 680 +758 727 701 +489 383 477 +383 348 477 +488 585 554 +516 585 488 +585 755 666 +682 574 573 +755 585 516 +323 420 465 +0 43 7 +195 102 163 +303 195 410 +370 303 410 +755 516 651 +784 755 747 +1098 967 957 +1119 1098 1130 +1162 1119 1130 +1170 1162 1151 +682 836 804 +574 682 626 +465 574 586 +162 323 222 +43 36 100 +102 15 163 +410 195 163 +651 516 593 +747 755 651 +890 784 796 +967 890 796 +1130 1098 1095 +1151 1162 1125 +1125 1162 1130 +1156 1170 1151 +1095 1098 957 +804 836 914 +626 682 804 +586 574 626 +300 323 465 +222 323 300 +100 162 222 +163 15 27 +222 245 100 +465 402 300 +402 465 586 +998 804 914 +1016 1066 998 +1066 1016 1156 +1066 1156 1151 +593 516 488 +47 27 0 +47 0 7 +7 43 58 +47 273 163 +27 47 163 +47 7 58 +47 207 273 +58 127 47 +127 207 47 +58 43 100 +481 370 410 +747 651 679 +784 747 679 +410 163 384 +488 481 593 +163 273 384 +481 410 384 +488 370 481 +796 784 679 +1125 1130 1101 +804 998 871 +626 804 871 +100 127 58 +245 196 100 +127 100 196 +847 626 871 +1023 1151 1125 +1125 1101 1095 +1130 1095 1101 +957 967 820 +820 967 796 +772 796 679 +207 279 273 +1066 903 998 +908 903 1066 +1151 908 1066 +1023 908 1151 +990 1023 1125 +1095 990 1125 +942 990 1095 +957 942 1095 +196 207 127 +300 245 222 +402 357 300 +586 459 402 +569 586 652 +652 586 626 +847 871 998 +847 998 803 +803 998 903 +820 796 772 +567 593 481 +384 567 481 +384 273 368 +368 273 279 +357 245 300 +459 357 402 +586 569 459 +668 652 626 +847 668 626 +697 957 820 +651 568 679 +567 651 593 +772 679 568 +908 1023 990 +903 990 942 +697 820 772 +908 990 903 +284 368 279 +284 196 245 +207 196 284 +207 284 279 +697 772 568 +567 568 651 +368 567 384 +942 957 697 +459 569 652 +668 847 803 +942 697 797 +942 797 903 +903 797 802 +903 802 798 +803 737 668 +368 284 467 +368 568 567 +697 729 797 +797 729 802 +903 798 803 +803 798 736 +803 736 737 +368 467 568 +697 568 698 +697 698 729 +802 729 798 +652 610 459 +357 284 245 +652 668 737 +652 737 736 +798 729 736 +357 467 284 +736 729 698 +357 459 467 +459 610 467 +467 610 698 +610 652 736 +610 736 698 +467 698 568 diff --git a/data/nicehead.mat b/data/nicehead.mat new file mode 100644 index 0000000000000000000000000000000000000000..b19dcf6d3f0faaa8bbfa20f30947d7e8e430ad36 GIT binary patch literal 43712 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

  • olhf9Q<5)zo`X&q5`>RHLDX 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 bdS65Qyyahoo.fr) +% Copyright (C) 2006 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2006-01-06 Creation +% +% ----------------------------- Script History --------------------------------- +[HOMEDIR,USBDIR]=mypath; +if isnan(USBDIR) + MATLABDIR=fullfile(HOMEDIR, 'matlab'); +else + MATLABDIR=fullfile(USBDIR, 'matlab'); +end + +% Brainstorm tools +addpath(fullfile(HOMEDIR, 'mtoolbox', 'brainstorm','Toolbox')); +addpath(genpath(fullfile(HOMEDIR, 'mtoolbox', 'brainstorm', 'PublicToolbox'))); + +% SPM2 Tools +addpath(fullfile(HOMEDIR, 'mtoolbox','spm2')); + +% EEGLAB +addpath(fullfile(HOMEDIR, 'mtoolbox', 'eeglab4.5b')); +addpath(fullfile(HOMEDIR, 'mtoolbox', 'eeglab4.5b', 'functions')); +% Native mat2cell & cell2mat are better: +addpath(fullfile(matlabroot, 'toolbox', 'matlab', 'datatypes')) + +% CTF/MRI plugin for EEGLAB +addpath(fullfile(HOMEDIR, 'mtoolbox', 'ctf')); + +addpath(MATLABDIR) +addpath(fullfile(MATLABDIR, 'behavioral')) +addpath(fullfile(MATLABDIR, 'ctf')) +addpath(fullfile(MATLABDIR, 'ctfimport')) +addpath(fullfile(MATLABDIR, 'brainstorm2', 'Developer')) +addpath(fullfile(MATLABDIR, 'stormvisa')) + +addpath(fullfile(HOMEDIR, 'data', 'studies', 'repromn' ,'matlab')) +if not(isnan(USBDIR)) + addpath(fullfile(USBDIR, 'data', 'studies', 'repromn' ,'matlab')) +end + +cd(fullfile(HOMEDIR, 'data', 'studies', 'repromn')) + +if (isnan(USBDIR)) + clear USBDIR +end diff --git a/data/test2.wrl b/data/test2.wrl new file mode 100644 index 0000000..9f59835 --- /dev/null +++ b/data/test2.wrl @@ -0,0 +1,30 @@ +#VRML V2.0 utf8 +WorldInfo {title "Matlab-VRML"} +NavigationInfo { + headlight FALSE + type "EXAMINE" +} +Shape { + geometry IndexedFaceSet { + appearance Appearance { + material Material { + ambientIntensity 0.4 + diffuseColor 0.2 0.7 0.2 + shininess 1.0 + specularColor 0.3 0.3 0.3 + } + } + coord Coordinate{ + point [ + 0 0 0 + 1 1 0 + -1 0 0 + 1 1 1 + -1 1 1 + -1 -1 1 ]} + color Color { color [1 0 0, 0 1 0, 0 0 1]} + colorIndex [0 1 2 -1 0 0 1] + colorPerVertex TRUE + coordIndex [0 1 2 -1, 3 4 5] +} +} diff --git a/dec2basen.m b/dec2basen.m new file mode 100644 index 0000000..aefd621 --- /dev/null +++ b/dec2basen.m @@ -0,0 +1,33 @@ +function s = dec2basen(d,b) +%DEC2BASEN Convert decimal integer to base B *number*. +% DEC2BASEN(D,B) returns the representation of D as a string in +% base B. D must be a non-negative integer array smaller than 2^52 +% and B must be an integer > 1. +% +% Examples +% dec2basen(23,3) returns [2 1 2] +% +% See also DEC2BASE + +% Original by Douglas M. Schwarz, Eastman Kodak Company, 1996. +% Modified (actually cut through !) by Karim N'Diaye, 2005 + +d = d(:); +if any(d ~= floor(d)) || any(d < 0) || any(d > 1/eps) + error('KND:dec2basen:FirstArg', 'D must be an array of integers, 0 <= D <= 2^52.'); +end +if numel(b)~=1 || b ~= floor(b) || b < 2 + error('KND:dec2basen:SecondArg', 'B must be an integer > 1'); +end +d = double(d); +b = double(b); +n = max(1,round(log2(max(d)+1)/log2(b))); +while any(b.^n <= d) + n = n + 1; +end +s(:,n) = rem(d,b); +while n > 1 & any(d) + n = n - 1; + d = floor(d/b); + s(:,n) = rem(d,b); +end diff --git a/decours.m b/decours.m new file mode 100644 index 0000000..c5d646b --- /dev/null +++ b/decours.m @@ -0,0 +1,256 @@ +function [capt]=decours(chnames, data, imod, allcond, PlotLat, Filtering, Ncond) +% decours(chnames, data[, imod,allcond,PlotLat, Filtre]) +% Affiche les décours du signal sur une liste de capteurs +% en fonction de la durée de stimulation +% data est au format data.G, data.Channel +% ex. decours({'mrt14'} , data) +% Si chnames est une 'cell' sur plusieurs lignes, 'decours' affiche les MOYENNES +% des capteurs sur chaque ligne +% +% > allcond = 1 pour avoir les plots sur la meme figure +% > imod = 1 pour l'auditif seul, 2 pour visuel, 3:les deux [bugge !], 0:demande +% > PlotLat = 1 pour qu'il place un ppoint sur la latence sur max +% > Filtre = 1 : slidingwindows, 2: lpass, 0:none + +COLORS={[0 0 0], [ .2 .7 .2], [0 0 1], [1 0 1] , [1 0 0]}; +COL2={[1 0 0], [0 1 0], [0 0 1]}; +global CONDS +CONDS={'TC', 'C','S','L','TL'} ; +CONDS={'C', 'IC','S','IL','L'} ; +MODAL={'Auditory' 'Visual'}; + +if nargin < 2 + return +end +if nargin < 4 + % Graphs on the same figure + allcond=1; +end + +if nargin < 5 + PlotLat=0; +end +if nargin < 6 + Filtering=0; +end + +if nargin < 7 + Ncond=5; +end +disp(fprintf('%d conditions', Ncond)); + +if isfield(data,'Time') + Time=data.Time; +else + global Time +end + +if ~iscell(chnames) & ischar(chnames) + chnames=cellstr(chnames); + if size(chnames,1)>1 + chnames=chnames'; + end +end + +if isfield(data,'G') & iscell(data.G) + if nargin<3 + ButtonName=questdlg('Which modality?', 'Question', 'Auditory','Visual', 'Both', 'cancel'); + switch ButtonName, + case 'Auditory', + F=data.G{1}; + imod=1; + case 'Visual', + F=data.G{2}; + imod=2; + case 'Both', + imod=3; + otherwise + return + end % switch + else + F=data.G{imod}; + end +elseif isfield(data,'F') + F=data.F; + data.G=F; +end + +if isfield(data,'F') + data=rmfield(data,'F'); +end + +if ischar(chnames) + chnames=cellstr(chnames); +end +if isnumeric(chnames) + chnames={data.Channel(chnames).Name}; +end +if size(chnames,1)>1 + % Dans le cas où l'on veut avoir des groupes d'électrodes on les + % indique en colonnes + + num=1:size(chnames,1); + for j=num + id=whichChannel(chnames(j,:), data.Channel, 'exact'); + disp(sprintf('Averaging %d electrodes', length(id))) + + if ~allcond & chnames{j,1}(1)=='M' + rev=1-2*(chnames{j,:}(2)=='L') + % reverse signal for meg ! + switch ndims(F) + case 3 + data.F(j,:,:)=rev*mean(F(id,:,:),1); + case 2 + data.F(j,:)=rev*mean(F(id,:),1); + end + else + switch ndims(F) + case 3 + data.F(j,:,:)=mean(F(id,:,:),1); + case 2 + data.F(j,:)=mean(F(id,:),1); + end + end + + chnames2{j}=sprintf('Group.%d',j); + end + + chnames2{1}='Right electrodes'; + chnames2{2}='Left electrodes'; + if size(chnames,2)>1 + chnames=chnames2; + end +else + num=1:length(chnames); + for j=num + id=whichChannel(chnames(j), data.Channel, 'exact'); + + disp(sprintf('Averaging %d electrodes', length(id))) + switch ndims(F) + case 3 + data.F(j,:,:)=mean(F(id,:,:),1); + case 2 + data.F(j,:)=mean(F(id,:),1); + end + end + +end + +if Filtering + disp('Filtering data') + switch Filtering + case 1, + disp('Sliding windows') + data.F=slidingwindows(data.F); + case 2, + disp('Low Pass') + data.F=lpass(data.F); + case 3 + filter=20; + disp(sprintf('Low Pass %fHz', filter)); + data.F=lpass(data.F, filter); + otherwise + filter=Filtering; + disp(sprintf('Low Pass %fHz', filter)); + data.F=lpass(data.F, filter); + end +end + + +if ~allcond + for j=1:size(data.F,3) + f(j)=figure(j); + clf + hold on + end +else + for j=num + f(j)=figure; + clf + hold on + end +end + +set(f,'Color', [1 1 1]) + +for j=num + p=[]; + if allcond + % Une figure par channel, plusieurs cond sur la meme + figure(f(j)); + set(f(j), 'Name', chnames{j}); + ax(j)=gca; + set(gca, 'Fontsize', 15) + if Ncond==3 + p=plot3cond(squeeze(data.F(j,:,:))); + else + p=plot5cond(squeeze(data.F(j,:,:))); + end + if PlotLat + addPlotLat(data.F(j,:,:)); + end + + hold off + if ~isfield(data, 'Title') + titletxt{j}=[MODAL{imod} ' - ' chnames{j}]; + else + titletxt{j}=[data.Title chnames{c}]; + + end + legtxt=CONDS; + + else + % Same figure for different channels, one figure by condition + for d=1:size(data.F,3) + figure(f(d)); + set(f(d), 'Name', ['Condition: ' CONDS{d}]); + F=data.F(j,:,d); %ipermute(lpass(permute(data.F(j,:,d),[1 3 2]),4),[1 3 2]); + p=[p plot(Time, F)]; + ax(d)=gca; + if ~isfield(data, 'Title') + titletxt{d}=[MODAL{imod} ' - ' CONDS{d}]; + else + titletxt{d}=[data.Title CONDS{d}] + end + + end + legtxt=chnames; + set(p,'Color', COL2{j}) + set(p,'LineWidth', 2) + + end +end +if size(data.F,2)==938 + set(ax, 'XLim', [-0.1 1.4]) +end +for a=1:length(ax) + if max(abs(get(ax(a), 'YLim')))<1e-10 + axes(ax(a)) + ylabel('Magnetic Field (T)') + elseif max(abs(get(ax(1), 'YLim')))<1e-4 + axes(ax(a)) + ylabel('Electric Potential (V)') + set(ax(a),'YDir', 'reverse') + end + + settitle(ax(a), titletxt{a}); + h=legend(legtxt); + set(h,'Position', [0.83 0.70 0.1643 0.2869]) +end + + + +function []=settitle(h,t) +global CONDS +axes(h) +ttl=title([t],'FontSize', 14); +posttl=get(ttl, 'Position'); +set(ttl, 'Position', [0.7 posttl(2) posttl(3)]) +xlabel('Time') +xlim=[-0.1 1.4]; +set(gca, 'xlim', xlim) +set(gca, 'xgrid', 'on') +set(gca, 'Box', 'on') +hold on +plot(xlim, xlim*0,':','Color', [.5 .5 .5]) +hold off \ No newline at end of file diff --git a/deg2rad.m b/deg2rad.m new file mode 100644 index 0000000..1c0a6ad --- /dev/null +++ b/deg2rad.m @@ -0,0 +1,10 @@ +% ----------------------------------------------------- +% deg2rad.m +% This M-function converts degree to radian +% Programmed by Tomonari Furukawa for MTRN9224 +% March 11, 2004 +% +% ----------------------------------------------------- +function rad = deg2rad(deg) + +rad = deg / 180 * pi; diff --git a/depfuntoolbox.m b/depfuntoolbox.m new file mode 100644 index 0000000..f41997f --- /dev/null +++ b/depfuntoolbox.m @@ -0,0 +1,62 @@ +function Files = depfuntoolbox(input) +%DEPFUNTOOLBOX Locate dependent functions of an M/P-file by toolbox +% +% Files = depfuntoolbox(filename) +% Files = depfuntoolbox(traceList) +% +% This function uses the depfun function to determine the dependent +% functions of any m or p file, and then separates the files based on the +% Matlab toolbox to which they belong. +% +% Input variables: +% +% filename: string with m or p filename +% +% traceList: cell array output of depfun function +% +% Output variables: +% +% Files: 1 x 1 structure. Each field is the name of a toolbox +% directory (for example, matlab = base Matlab, stats = +% Statistics Toolbox, map = Mapping Toolbox, etc) and holds a +% cell array of dependent function names associated with that +% toolbox. Dependent functions that are not part of a Matlab +% toolbox are listed under the fieldname 'other'. + +% Copyright 2006 Kelly Kearney + +%---------------------------- +% Check input, run depfun if +% necessary +%---------------------------- + +if ischar(input) + allFiles = depfun(input, '-quiet'); +elseif iscell(input) + allFiles = input; +else + error('Input must be a filename or cell array output of depfun'); +end + +%---------------------------- +% Separate by toolbox +%---------------------------- + +matlabToolbox = fullfile(matlabroot, 'toolbox'); +nchar = length(matlabToolbox); + +isNotMatlabProper = cellfun(@isempty, strfind(allFiles, matlabToolbox)); + +matlabFiles = allFiles(~isNotMatlabProper); +matlabFiles2 = cellfun(@(a) a(nchar+2:end), matlabFiles, 'UniformOutput', false); + +toolboxName = cellfun(@(a,b) a(1:b(1)-1), matlabFiles2, strfind(matlabFiles2, filesep), 'UniformOutput', false); +uniqueToolbox = unique(toolboxName); +filesInToolbox = cellfun(@(a) matlabFiles(strcmp(a, toolboxName)), uniqueToolbox, 'UniformOutput', false); + +Files = cell2struct(filesInToolbox, uniqueToolbox, 1); +if any(isNotMatlabProper) + Files.other = allFiles(isNotMatlabProper); +end + + diff --git a/detrend2.m b/detrend2.m new file mode 100644 index 0000000..d718f4c --- /dev/null +++ b/detrend2.m @@ -0,0 +1,65 @@ +function [X]=detrend2(X,o,i,d) +% detrend2 - polynomial detrending +% Y = detrend2(X,o,i,d) removes trends in data +% +% X: input data array +% o: order of the fitted polynom (default 1, i.e. linear detrending) +% i: indices of the control points (default: [], i.e. all points) +% d: dimension to work on (default: 1) + +% See also: detrend + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2010 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2010-04-21 Creation +% +% ----------------------------- Script History --------------------------------- + +if nargin < 2 + o = 1; +end +if nargin < 3 + i = []; +end +if nargin < 2 + d = 1; +end +nd = ndims(X); +if d>nd + error(sprintf('X has only %d dimensions', nd)); +end +sx = size(X); + +if o==0 + r = ones(1,nd); + r(d) = sx(d); + + S.type='()'; + S.subs = repmat({':'},1,nd); + S.subs{d} = i; + X = X - repmat(mean(subsref(X,S),d),r); + return +end + +error + + +% polynomial adjustment +%--------------------------------------------------------------------------- +G = []; +for i = 0:p + d = [1:m].^i; + G = [G d(:)]; +end +y = x - G*(pinv(G)*x); + + +% detrend - remove linear trend +% addpath('/pclxserver2/home/ndiaye/mtoolbox/spm2') +% F=spm_detrend(F',1)'; diff --git a/dgrouping.m b/dgrouping.m new file mode 100644 index 0000000..b0d218d --- /dev/null +++ b/dgrouping.m @@ -0,0 +1,67 @@ +function [I,nl,F] = dgrouping(G,S) +% dgrouping() - Get indices of data elements using grouping variable in multidimension (myanova) +% +% [I,NL,F] = dgrouping(G) +% +% G is a grouping variable with as many colunms (N) as there are +% factors in the design and as many unique elements per column as +% they are levels in the corresponding factor. +% G must represent BALANCED data across levels in each factor. +% I are the indices of the data in a N-dimensional array (one +% dimension per factor) +% NL : number of levels in each factor (size of reshaped data) +% F are the factors +% +% See also: myanova + +if prod(G)==max(size(G)),G=G(:);end +% number of factors +nf=size(G,2); +% total number of elements (inc. replications) +ne=size(G,1); +% output indices +I=zeros(ne,1); +% number of cells in the design +nc=1; + +% if length(unique(G, 'rows'))','name') +% Lists all MEX-files in the c:\matlab6p5\toolbox directory in the cell +% array of strings Names (including path). +% Note the regexp syntax of the filter string. +% Bytes is a structure with fields "total" and "dir". total is the +% total size of the directory, dir is a recursive substructure with +% the same fields as bytes for the subdirectories. +% +% [Files,Bytes,Names] = DIRR('c:\toto'... +% ,'name','bytes','>50000','isdir','0') +% Lists all files larger than 50000 bytes NOT recursively. +% +% [Files,Bytes,Dates] = DIRR('c:\matlab6p5\work','date','2005') +% Lists all dates of files from year 2005. (With path in front of +% date in the cell array of strings Dates) +% +% +% +% v1.02 +% Maximilien Chaumon +% maximilien.chaumon@chups.jussieu.fr +% 2006 06 16 + + +verbose = 0; +% set to 1 to get folders list in command window + +if nargin == 0 + chemin = cd; +end +if nargout == 0 + dum = varargin; + varargin{1} = 'name'; + varargin = [varargin(1) dum]; +end + +fields = {'name' 'date' 'bytes' 'isdir'}; + +if regexp(chemin,'[\*\?]') % if chemin contains any ? or * + filt = regexprep(chemin,'.*[\\/](.*\>)','$1');% get filter + filt = regexprep(filt,'\.','\.');% in regexp format + filt = regexprep(filt,'\*','.*'); + filt = regexprep(filt,'\?','.'); + filt = regexprep(filt,'(.*)','\\<$1'); + chemin = regexprep(chemin,'(.*)[\\/].*\>','$1');% and chemin +end + +if not(isempty(varargin)) % if additional fields were provided after chemin + for i = 1:length(fields) + if strcmp(varargin{1},fields{i})% if first varargin matches a fieldname, + % assume no filter was provided, + + if not(exist('filt','var'))% or it was in chemin and was set just before + filt = '.*';% set it to wildcard + break + + end + end + end + if not(exist('filt','var'))% else + filt = varargin{1};% first varargin is the filter + varargin(1) = []; + end +else% if no additional fields were provided and filter was not in chemin + if not(exist('filt','var')) + filt = '.*'; + end +end +% determine which varargin are fieldnames +whicharefields = zeros(1,length(varargin)); +for i = 1:length(varargin) + for j = 1:length(fields) + if strcmp(varargin{i},fields{j}) + whicharefields(i) = 1; + break + end + end +end +% set f2out and f2outfilt +f2out = {}; f2outfilt = {}; +idx = 0; +if not(isempty(varargin)) + for i = 1:length(varargin) + if whicharefields(i) + idx = idx + 1; + f2out{idx} = varargin{i}; + f2outfilt{idx} = ''; + else % if nargin{i} is not a fieldname, assume it's a filter + f2outfilt{idx} = varargin{i}; + end + end +end + +%%%%%%%%%%%%%%%%%%%% START +if verbose + disp(chemin); +end + +list = dir(chemin); +if isempty(list) + disp([chemin ' not found']); + if nargout == 0 + clear list + else + for i = 1:nargout - 2 + varargout{i} = []; + end + sumbytes = 0; + end + return +end +% remove . and .. +i_file = 1; +while i_file <= length(list) + if strcmp(list(i_file).name,'.')|strcmp(list(i_file).name,'..') + list(i_file) = []; + else + i_file = i_file + 1; + end +end + +% set sumbytes +sumbytes = struct('total',0,'dir',{}); +sumbytes(1).total = 0; +i_dir = 0; +% and all output fields +for i = 1:size(f2out,2) + f2out{2,i} = {}; +end +filenames = {}; +todel = 0; +r = 1; +for i_out = 1:size(f2out,2) + if strcmp(f2out{1,i_out},'isdir') + if strcmp(f2outfilt{i_out},'0') % check if no recursion is wanted + r = 0; + end + end +end + +% for each item in list +for i_file = 1:length(list) + for i_out = 1:size(f2out,2) % for every output field + if not(isempty(f2outfilt{i_out}))% if there is a filter + if strcmp(f2out{1,i_out},'bytes') % if field is 'bytes' + line = [num2str(list(i_file).(f2out{1,i_out})) f2outfilt{i_out} ';']; % compare with filter numerically + if eval(line)% if passes the filter + continue % continue to next field + else + todel(end+1) = i_file; % else set to be deleted + end + elseif not(strcmp(f2out{1,i_out},'isdir'))% if field is 'name' or 'date' + if regexpi(list(i_file).(f2out{1,i_out}),f2outfilt{i_out}) % apply filter + continue % continue to next field + else + todel(end+1) = i_file; % else set to be deleted + end + end + end + end + % once checked for every field's filter + if todel(end) == i_file % if one didn't pass, + if not(list(i_file).isdir) % and it's not a directory + continue % skip this file and continue + end + else + if regexpi(list(i_file).name,filt) % else, check for general filter on filename + sumbytes(1).total = sumbytes(1).total + list(i_file).bytes; % sum bytes of that level + for i_out = 1:size(f2out,2)% and assign all output fields with the values of that file + f2out{2,i_out}{end+1} = [chemin filesep num2str(list(i_file).(f2out{1,i_out}))]; + end + else + todel(end+1) = i_file; % else the file will be removed from the list structure + end + end + if list(i_file).isdir % if it's a directory + if not(r) + continue + end + i_dir = i_dir + 1; + cheminext = strcat(chemin,filesep,list(i_file).name); + % get it's content by recursion + % write the line to enter eval + line = '[list(i_file).isdir,sumbytes.dir(i_dir)'; + for i_out = 1:size(f2out,2)% with all the requested fields as temporary variables + line = [line ',f2outtemp{' num2str(i_out) '}']; + end + line = [line '] = dirr(cheminext,filt']; + for i_out = 1:size(f2out,2) + line = [line ',f2out{1,' num2str(i_out) '}']; + if f2outfilt{i_out} + line = [line ',f2outfilt{' num2str(i_out) '}']; + end + end + line = [line ');']; + eval(line); + + for i_out = 1:size(f2out,2) + f2out{2,i_out} = [f2out{2,i_out} f2outtemp{i_out}]; % catenate temporary variables with f2out + end + % sum bytes + sumbytes(1).total = sumbytes(1).total + sumbytes(1).dir(i_dir).total; % that level + the next one + list(i_file).bytes = sumbytes(1).dir(i_dir).total; % and set list(i_file).bytes to that value + if list(i_file).bytes & todel(end) == i_file + todel(end) = []; + end + end + + +end +todel(1) = []; +list(todel) = []; + + +for i_out = 1:size(f2out,2) + varargout{i_out} = f2out{2,i_out}; +end +if nargout == 0 + clear list + disp(char(f2out{2,1})); +end diff --git a/drag.m b/drag.m new file mode 100644 index 0000000..87af713 --- /dev/null +++ b/drag.m @@ -0,0 +1,289 @@ +function drag(varargin) +%DRAG Move around on a 2-D or 3-D plot. +% DRAG with no arguments toggles the plot dragging state. +% DRAG ON turns plot dragging on for the current figure. +% DRAG XON, YON, or ZON turns plot dragging on for the x, y, or z axis only. +% DRAG OFF turns plot dragging off in the current figure. +% DRAG RESET resets the restore point to the current axis settings. +% +% When plot dragging is on, you can click and drag the axes around while +% maintaining the current level of zoom. If DRAG RESET has not been +% called the restore point is the original non-explored plot. If DRAG +% RESET has been called the restore point is the retore point that existed +% when it was called. Double clicking retores the axes to the current +% restore point - the point at which plot dragging was first turned on for +% this figure (or to the state to which the restore point was set by DRAG +% RESET). +% +% In a figure with multiple subplots, pressing and holding the left mouse +% button drags around the currently active subplot, while pressing and +% holding the right mouse button drags around all subplots simultaneously. +% Double clicking with the left mouse button restores the currently active +% plot to its restore point, while double clicking with the right mouse +% button restores all subplots to their defined restore point. +% +% DRAG(FIG,OPTION) applies the drag command to the figure specified by +% FIG. OPTION can be any of the above arguments. +% +% Example: +% x = 0:0.1:5; y = exp(x); +% figure; subplot(211); plot(x,y,'r.'); subplot(212); plot(y,x,'g.'); +% drag xon; +% +% Ryan M. Eustice 08-15-2003 +% Woods Hole Oceanographic Institution, MS 7 +% Woods Hole, MA 02543 +% 508.289.3269 ryan@whoi.edu + +% History +% DATE WHO WHAT +%---------- ------------------ ------------------------------ +%2003-07-23 Ryan Eustice Created & written. +%2003-08-15 Boyko Stoimenov Modified the private function +% dragreset to correctly count the +% number of axes. Also, switched +% short-circuit && to & for matlab +% backwards compatability. +%2004-01-29 Ryan Eustice Fixed bug in private function +% dragreset to properly count +% number of axes and not count +% legend or colorbar objects. + +% Note: drag uses the figure buttondown and buttonmotion functions + +% +% PARSE ARGS - set fig and drag command +% +fignum = get(0,'CurrentFigure'); +if isempty(fignum) + return; % no figure +end + +switch nargin +case 0 % no arg in + switch get(fignum,'Tag') + case 'dragon' % toggle state + dragoff(fignum); + otherwise % turn on + figstate.motion = 'all'; + set(fignum,'UserData',figstate); + dragon(fignum); + end % switch get(fignum,'Tag') +case 1 % one arg in + switch lower(varargin{1}) + case 'on' + figstate.motion = 'all'; + set(fignum,'UserData',figstate); + dragon(fignum); + case 'off' + dragoff(fignum); + case 'reset' + dragreset(fignum); + case 'xon' + figstate.motion = 'xon'; + set(fignum,'UserData',figstate); + dragon(fignum); + case 'yon' + figstate.motion = 'yon'; + set(fignum,'UserData',figstate); + dragon(fignum); + case 'zon' + figstate.motion = 'zon'; + set(fignum,'UserData',figstate); + dragon(fignum); + otherwise + error(sprintf('Unrecognized argument ''%s''',varargin{1})); + end % switch varargin{1} +case 2 % two args in + fignum = varargin{1}; + switch lower(varargin{2}) + case 'on' + figstate.motion = 'all'; + set(fignum,'UserData',figstate); + dragon(fignum); + case 'off' + dragoff(fignum); + case 'reset' + dragreset(fignum); + case 'xon' + figstate.motion = 'xon'; + set(fignum,'UserData',figstate); + dragon(fignum); + case 'yon' + figstate.motion = 'yon'; + set(fignum,'UserData',figstate); + dragon(fignum); + case 'zon' + figstate.motion = 'zon'; + set(fignum,'UserData',figstate); + dragon(fignum); + otherwise + error(sprintf('Unrecognized argument ''%s''',varargin{1})); + end % switch varargin{2} +otherwise % too many args + error(nargchk(0,2,nargin)); +end % switch nargin + +%==================================================================== +% drag: private +%==================================================================== +function dragon(fignum) +figstate = get(fignum,'UserData'); +dragreset(fignum); +set(fignum,'Interruptible','on'); +set(fignum,'DoubleBuffer','on'); +set(fignum,'Tag','dragon'); +set(fignum,'WindowButtonDownFcn',@startexploring); +set(fignum,'WindowButtonUpFcn',@stopexploring); + + +function dragoff(fignum) +set(fignum,'DoubleBuffer','off'); +set(fignum,'Tag',''); +set(fignum,'WindowButtonDownFcn',''); +set(fignum,'WindowButtonUpFcn',''); + + +function dragreset(fignum) +figstate = get(fignum,'UserData'); +kids = get(fignum,'Children'); +cc = 1; +for ii=1:length(kids) + % check for regular axes handles by keying off the Tag field which should be + % empty. note that objects like legend and colorbar fill in the Tag field. + if strcmp(get(kids(ii),'Type'),'axes') & strcmp(get(kids(ii),'Tag'),'') + figstate.axishandle(cc) = kids(ii); + figstate.origlimits(cc,:) = axis(kids(ii)); + cc = cc+1; + end +end +figstate.numaxes = length(figstate.axishandle); +set(fignum,'UserData',figstate); + +%==================================================================== +% callbacks +%==================================================================== +function startexploring(obj,eventdata) +fignum = gcbf; +figstate = get(fignum,'UserData'); +switch get(fignum,'SelectionType') +case 'normal' %mouse left single-click + figstate.whichaxes = 'current'; % only drag current axis + axishandle = gca; + initaxis(axishandle); +case 'open' %mouse left or right double-click + axishandle = gca; + for ii=1:figstate.numaxes + if strcmp(figstate.whichaxes,'current') & figstate.axishandle(ii) == axishandle + % restore currently active axis limits to orig state + axis(axishandle,figstate.origlimits(ii,:)); + break; + elseif strcmp(figstate.whichaxes,'all') + % restore other subplot axis limits to orig state + axis(figstate.axishandle(ii),figstate.origlimits(ii,:)); + end + end +case 'alt' %mouse right single-click + figstate.whichaxes = 'all'; % drag all axes simultaneously + for ii=1:figstate.numaxes + axishandle = figstate.axishandle(ii); + initaxis(axishandle); + end +otherwise + error('Mouse SelectionType unknown'); +end +set(fignum,'WindowButtonMotionFcn',@setnewview); +set(fignum,'Pointer','Fleur'); +set(fignum,'UserData',figstate); % store figure state + + +function setnewview(obj,eventdata) +persistent cc; +fignum = gcbf; +figstate = get(fignum,'UserData'); +switch figstate.whichaxes +case 'current' + axishandle = gca; + updateaxes(axishandle,figstate.motion); +case 'all' + for ii=1:figstate.numaxes + axishandle = figstate.axishandle(ii); + updateaxes(axishandle,figstate.motion); + end +otherwise + %do nothing +end % switch figstate.whichaxes +cc = cc+1; +if mod(cc,2) + drawnow; % force screen update every other motion cycle (lowers cpu usage) +end + + +function stopexploring(obj,eventdata) +fignum = gcbf; +set(fignum,'WindowButtonMotionFcn',''); +set(fignum,'Pointer','arrow'); + +%==================================================================== +% callbacks: private +%==================================================================== +function pos = getcurrentposition(axishandle) +pos = get(axishandle,'CurrentPoint'); +pos = mean(pos); +pos = round(pos*1000)/1000; + + +function initaxis(axishandle) +state.currentaxislimits = axis(axishandle); +state.startpoint = getcurrentposition(axishandle); +set(axishandle,'UserData',state); % store axis state + + +function updateaxes(axishandle,motion) +state = get(axishandle,'UserData'); +state.endpoint = getcurrentposition(axishandle); +motionvector = state.startpoint - state.endpoint; + +if length(state.currentaxislimits) == 4 % 2D plot + switch motion + case 'all' + state.currentaxislimits = ... + [state.currentaxislimits(1:2) + motionvector(1), ... + state.currentaxislimits(3:4) + motionvector(2)]; + case 'xon' + state.currentaxislimits = ... + [state.currentaxislimits(1:2) + motionvector(1), ... + state.currentaxislimits(3:4)]; + case 'yon' + state.currentaxislimits = ... + [state.currentaxislimits(1:2), ... + state.currentaxislimits(3:4) + motionvector(2)]; + otherwise %do nothing + end % switch motion +elseif length(state.currentaxislimits) == 6 % 3D plot + switch motion + case 'all' + state.currentaxislimits = ... + [state.currentaxislimits(1:2) + motionvector(1), ... + state.currentaxislimits(3:4) + motionvector(2), ... + state.currentaxislimits(5:6) + motionvector(3)]; + case 'xon' + state.currentaxislimits = ... + [state.currentaxislimits(1:2) + motionvector(1), ... + state.currentaxislimits(3:4), ... + state.currentaxislimits(5:6)]; + case 'yon' + state.currentaxislimits = ... + [state.currentaxislimits(1:2), ... + state.currentaxislimits(3:4) + motionvector(2), ... + state.currentaxislimits(5:6)]; + case 'zon' + state.currentaxislimits = ... + [state.currentaxislimits(1:2), ... + state.currentaxislimits(3:4), ... + state.currentaxislimits(5:6) + motionvector(3)]; + otherwise %do nothing + end % switch motion +end +axis(axishandle,state.currentaxislimits); +set(axishandle,'UserData',state); diff --git a/dumpmemmex.dll b/dumpmemmex.dll new file mode 100644 index 0000000000000000000000000000000000000000..c230c630e2af2b5801a548185c2a6268613c5934 GIT binary patch literal 36864 zcmeIb4O~>!`Y*nR+29riXH-Zm)G@Iv&4w9JhT&xp2JrkFKca^~3H^Q)Mj*_0B#QzjyQ1F(N ztS%-revMx5+Cq=(PD~9NBYJ>ii%V4e&yvvk%Pp41?QK*t0?NZUxNE!Gg-$*7U5gx z;6?be`+Rr zw%q)|u$B4uh+pM3Z&Mz5>XXPJjYX0)5r&H$wVMz<2sxekqPcmGCXCXx`bVJD#t=%k zLythK|1`Wugp$wCjs<v-S!?Ym#*CVA=wd{ZfJKjxLX^~f{!{kJ2EFbQ$B`( z1_6nYWx3AH(V(-4#692>c?6q=(*Qm@$DS#jN88tR6EbSnzuMHof=k+OALYq)%9h7{*84-Ahnh|9L(Ro!0RnZrQSH!THv)D!G|U_KFQw#T%^goc5ZNLd!D#md*rofm z5JRscmz|Ny$DaaQs?Ay?l+t$TjKY$KM8)9qNNS*y_;wn#`>-MSEQtryk4kMARUgpM z)G#Ww_7Ha|zgbL)pc_J!RHpV2tBF0#G7V`eD^f2vF!0zr8%RR1^NNFIoL6kD+UogE zY)f;-7}%>ZI!=|=Fxr|_d!k;)bt}|9rOr-}Py3u>PeW=-G)7hob7(TuvoxmI-xt;OPk#3Wj&E78N=^CF##B@mP zsym~7wkgAOh>y8TyS+)BuE~(T!Klm4^~8uaCL`lh3IkWl4*|VAo!FHvW@%i>D!a+e zCm@5{bVwnfKBhjLAvK{?{$L1FP&P-mPx+2V?ba50)l1A28X z~o)AN(@OY^Brq-o70qZ`EW=bM*xD4>hhX%#Ht8Qoy^`$)Pp8lO1udcWP7%{CtJF#G^H+oWpkqrO#|)XCm$=;k=c(S zv)B>Tu$U%MSeJFu-@O%XE~QFIEj7vn24E)&fLC%{is2mFD=%5C%6Ic#Ir6 z&8xB^;9-{1P?_jmAwoQv6zV5*62!R1554;RRx$V(LfNhd`Yc@)W5ob3SNsLq5=~Gl zzsoNUBP@jwmhME6xw*x90thF7@d}{s%sm`w1V&KUkVEw-d1bSrAT8j$GI)?~3+?$X zAKIssA`@3JF_B};L%$l3rOv(MAj)JzC08h!M>(w=O#pJ}45UJ35zUopI%A%&r<&4b ziFd^Syg8+DFJkDa@llAn)@h0uChMBtPO?LXs^Cp{k=R z^W|tlb?*Lnf|= z38tm1@@8idP4T1TmQ?@1MuNk)F#aUX^MxxVf=n!FV7ds+GzTllcaasEA*If(t$mAx z4X$E(4~4F8>2ih7#CYv8N0TzDm+U_XVqSj!6s{~f*cjehzA(a7uN2=@8RlGt;u*Qc z>K`a-c{fwjq!fOhZ>7DIs^0Mghzd;;rtQtCqMGlO*;x(gRCkk#o!Q;Qa6#ZQ1ze(Q zOcYzi51M-Y`OhbbP(fh9MYoAT0t5W|GBB%Rd1Wlcqo9ToR%` zBm;>l9VyN2K@Sk)35A$RtT`)3nva~KlxuS)#oZ%Sa*mJm6w~Oc?7Y_L2$SW$UwNLgSOuInmaO3EoB5+ofWxpT#aLn=O~XAtSBt z28RRq*hRSLhwa%p(l{j-_7ov$Li7=kUYWn64c zCH#>R_8e6rwDUG9l@Lg^dXg20zmnXbvubyL(#;Pg;@=QfKQR@h=-H7dWu)`!NbX4O z2~M5nVNeF`ERreS5k@&L5`V%~?rY_xjzncv&EJmZ2*DI8Q+dr98I64pQo2m&(PbDv*bAQ_yJ0L^O!7x! zm*oFY7jsl(s=W0YY!k^WS%It zu%oQjB6Apb#q0ery+w?tK<;>NW~MbW)1IA~DWpV1$sdc7#lsr2q>s?q$dG}KWT#|i zX4x^lL;y?vm~0FX(Mh+3k#Vvjf-Jan*c8)*y=G4)Q5p*^*TBUf$8228B(aqZw8n&S z*T}1TdaG5GvZOK;162;5X;Zf+C@UvhI)~whX%)(7AtBh}@iV!JZo$UFyX70p$CJ$w zS_y2>a_9=6d3R$!+P&O4uzQ(Y+y^m8;FfBneuE{MZ{n8;l*munvD9?F06B|USjX;3 z!|K4IV}qctla&2L)x~V_V$;`Z%kN2&wk{l~1BGd8CD4FWf_(*QYd;2{<|-*wTYC|2 zSKSJ%SQG_e#}wx?A8=!R@MEj7!;y-x)6C8Ff^zJxzmqIdO)yq3xqNTj`a8TScBu>? zXcL7bFIh^}>fYibppYXKP9&1kx$0J-yA|(JsOmt~3&^ocV+SL}uPUj20LZ%+qE22M zyVQk!YOzkQ@?|QnRq(c8#^n#rSn0XVWn)}QhEmE775X{0gSiNlUiQu;m{3-sIr0^a zl#%_1)75=4*C)k72x1pj?LEh)z+RsUGd3ckwY4H!78P(*RWc0I8AHv|=(~AdFHy zS?UvV3b7CnbH)8Nr*u*-FsC0r*>0C-OG~h1fQDPNF&t^&FcdoofGs{S|2>V};y2(= zY2FMB8&Gy_luPxwyrYkDtyH0y=6uiw&u!k#vW>|vRSD_6SWjR;P5)uJc=Y5Kbvkx( zGIno_6hdST!x%RzheQ{Vs9@{M1#Q%l;FhX$C+n~#v09p{HBf`o+gx!EncT3Q+WH1X zB`%DhvrE;}+m?$4AxZ@)%;)}@uk5)nKwo*~m6eLMgKSADE;xxu>55lMK7$9}NsLWi zK~*#N@JHpR#P?cleHflp&lSOT8L6lj@6b@<;gera|D@+1;s~*A%28Ae04d0(5k`=e z(|cIWoNSk2A%R`utXO@y)bAd#r-1?LiiXk5Ewy_zrqg zyHXvmkqU4yqkS_iP&|$C=AO<_i#km@*7iYUd zSa0g?s6uI$NmbL7tl3HijWfN+sl?YJE^Y`xPgKt?Rdp-@t_=OLA;jtE*+MYXm!x^X z$<4)faZ>K|#Ts#o?&Q~EI@5FdmP154QO=-?LEL3twsOH@I(kj7@Bl50_5|{>p7RGp zh*V@<+{I!OiH#_6(nAV+n&x7L8v!+=KDRXBeYobm@F z6itL4^>gIMh9EyNfP7@&AtnOGQ|fvl0#%?yne^(Mo+i=F3Ul@nptdt|Fd?ksa2g~{#_))dp?Cm{R&Y`3+ z#8--H6~+-wwX(U$8)%t}iE1FSvbo-u3XzIgIvAZbFgi2hd`*sPv#KS)PSq01vDYAPdwbz5!%!q6x0TUAWS4*1i?Zp=<1GQ z7SyQ^iJvriCIy{#X~qa7e@!d7-YUN5`3z$r=z}65jZwzKKu7fX;?f8W>4Vsk%r&_i z(4slw75*w+aaYf2{&O|UOg~;fV(vWds@SF+R}fF9ZW0gkU#Ocp{Mdj?uOMpxaOML? z=@59+%>%`t{d351gP5*^HBoF5kE>fO{p%S^ulAvhXt17E()GCz&;mvN3R7{%rJZub z6MJT05^jB)BtmaiX!v%ux=oI#J2oBTHp11032IEb~)i<1hBxg=%lU<&fTj`E&{ z+YYxCt`@Eej)hwTC#F1dUPyWLJbVeh1iur0C;Tq>nDHOE4F59xUiiK6``}~C?LxIF zb(gw#KoT;k@G-cva9xBpCf${XnzxbK4h?&2?t_5GA4vnE7{GP0k$tWzbDvNC5jW%? zeErhSK9p;piBjaA(~vTSAZQJ=#}#jTOegKJ>@KA}2=caKgzMpU!8N3+ryQ@Jt6l`c zi;3iR4`5&>R-rR{xjrCg-usnj1-iOXE~5wJuG)rKvImT8Ex)*N|gx9aS-c zcZYZ+@2M2u83LV(c|J2+Ow!@l`~i))ypKQ7m(z4XD;^ae>?=7$V<)vhsP1!ZRi%J4 zA@8z~-X&_r!f1=S!43SF8wZ>5l~v3#%|CKxEVv9x93ScXf3F66K1E5rALHRs9i zUAQ9O^YP@WT`Bz|x#=i%ve^^CH?{kcf$hWCyXzow04abVZK6Su5v#`1f)b<=WYl@R z!X^gHh}W8AdND62u2+{Pu7sV9{~UaEYU`c@iRoE=fG8Z|0Daza%VDs&-d{kS538Z% zuYmx`Zn?VNPrPdRfW05babgB-$r!@@+aTmT`K`j+iOEZEa6ek%-j_VU{fWujf5d$T zt&n8Id9*O!8>Yp1j4+?UB8so>q_~mhYK^v5Kq+E+mrHYH0F#7)CV;|xTFAh3PFv

    eIFTP4x&@PIXpmu~m78@F{+9(gUH596uOyrR+)>8Roe|LDIPvw<{@L ztXvxLf!3*2_#PP}La4Sb;bvjQ*gl!j)}h-_jilQVTlr^%&EQuN^Bw{;eXdofY2G38 zuyPoXnE}g^lCEU&31_68QKG(_RvrIBJ!xVuUD2xRPfPY9r0JZSk|7g*72U5u_>GLr zt;xiISf`OcRzUK)8^;HsF&H08rx!!X7;(|grglHEO+m!YX5r}03|Cr6hFxm!#UdD+ zgM%75m<$q&KdIAtH7|vT!6d5?Oa_yOsF58cNr)!Zys?D{8B$DUHVVqYz6r==Y^06j zKkvf0Kv$cGE?+$g`<;^)`1D>`;4XV~=;VBxZ0bHKAu8B<8%asxDa&VAl2wiu7to?* zN_y{<{-6)IAig=olGlfXif`a^o5W8nrwCf;kI?*q=K0!kipvF3g8zF;Ly!M_0fQBn*Bw0H_@qdbNU$&@}pG*?`p6I5cdj*pSK5hizs7C>Qw!ez2f z)bUqkA*+uK5w~t5%_dfDOBvvIw&(?|OPP|@I|W+=vTsdG3Hpb~UB+FP$7+Eu+xn1* zWviHew4w_b7H4-eOAiC}d^bp(_95pU?I5;5v-B^a?Eo zPKW-vNcLy_^di1LaWe*fzNJT78w~a28zco7Al1_c#{edp2(r&Lcw)yReEU8$;spUwaCT=`H&aT=px960sJ7%SNBpxD`8|m$Uq@! zYqt~mfYYjrv`ceQco3uhXG6HKVPOEQ3K+sc)pztaIje)HdO)}!Whi2g{{2YHU=X~I ztg27(+&u&_*tKY2*pB=SluDm#ufi?B%If#zg^{zNcIpnXK|-LgaC9G7hGOHo)W=JGU!se}aXX3GT9<+(ASWl@GNH^Mj903tB1IkYkmyh9+E%r~%N%a$h2 z;7Sqf7;<*+xgM-C-gCq?7?+k-wt)9ePRa!4%S{Gb@xdg_atub1H-jsxMpouJORJEo zWA7}biz&VDY!L?HVuhNJs=;~jdfi|lWbosL%s74#BMS+_W?J?qLt9MM;UrFMS()!O zo00h~M|?mhCSe0q=SF{O$5{eyAtaW#$@I3|Rclb@Hd~)yWdth?bMSnK=UOS<^o>@% z3Fn-cU6tcaA1|<9F&NPuze8)g{tbu7l;!G%IM-YqpVlk$^_^ODdRKiPyjE*p7d(it zzVCaO64K~arvv~2`V+FQv@Xlx^{wRaXR*e=7kwC~%w(^T#JJ{bu#pgwXuj~76iV3p z3>kb4smTp({Y|xQsGj;o$((xRMp{v`;(I5*?Kv^o&WO!QTglNZwoZz9l`QPNR&~04 z!>s_(QaC`wOmq`D9cBp{ExoLq1?O_-usZM_DM#zEbKek@EnU5;m_4{~oW;Tpn$Ndr z`LDZRksD}lvQEkO=E8AEM7C5x4&UnB*(}ZmA0y%1d>pV3pp%wUS_k)s&^W|YRZoU5 zP6(o_z_!3)ER_O+o!TXBZ8iy=*kb@UW&>NnyA5;V_3N<#xJc|JIFeWgddk5FY(G?~ z)#T3I{~Qjw_-aWdCl z1}v@m8Xlhp1ZEo{W3{xd|7TK?(@b$6LL>6c3}4+xS2Up=Tm849&fuJ~?;y38S_osi zqmXyM6Y-rpb9WHlcY3lwV3SN~>(a0gho#ZWA!Ch~k)r~4Lbk&9BHtkD3BVyI$e7#; zHCXRF!R84ufUu*aN034K3tkvVYV>KP?~q+3Kb2s>&fCvniAlnQL3!#%Mby`S3}h8x zAE#|x1H>EXik0xL7tjIOTX~6Q$MI4yt4U+k=wf?lnxdNbj8Wb+4D`)=Mw4((sTGDO zrTx%qKBg79>>#yJB%xR!vE8ld48h9WW$Vm_0wSUt(iLtZoyNV7*re-g{X1cHvRYay zE;rbIXsIJHJ?uKOGjm*7m$bVBwY%MxrVSIYWZ0{qChgJV5l>?W1k31q*qvYc^PD*i zb7WkeIkxg1n4;@CF(%|_(k|Yj?ErynwjK6#H@H%8V6XG0W)CK3doxL2;RHs(!Ht%m z;uMpE10UYsfyW0*eL!i>pbq#IKCVhRqLTM7;TTn5U?|pE?r~z_q z&JMk&eSjE9?So;4k0&SO6gQ^yx-14`~DBvBhuob#DBpau__L-j&LnQ zGOBep7xoB+<`BI9wmrf}dpnAF8tw(SSK;1(+Y5IP?qfK#%l`s< z1Sw3EyYTLXV-nE^;0D{}f5sl6+&YQFc*Tj^oZ_%TqSLCNUd3L^HTFI+It<}r4Zu0_ zKVTUkn5@0Hj~ook|0tG-zrzZk{Erw`GXM^8JiUrNM3aM6Dtp^1W70 z#cn>2ecfw14;V0=--J6oUNikUOuaK;I-lq>ool?NbFM`g=LxI%f>>t}8if#wLWnk< zP-vRm^VmR>TNnYm{5-A~40Ck@JVJYw%Ebmqx_I3pN>|v3tdzY_m`Z%_xb zVVU-CTmgV`4H9p1KN837Bu~e)q-b&hgzPiSd0`x*Ot^wA3^z7#H4wI6n_ju|)y`OP ztP&=NI*|`o`z_7vYAjn^aTv+GTOH9Sy%r2nI3)41z@5#>%$D*ylq;B?Agn8zMTbOg zsqpE2GVZ6`A#?57Uivt}9H0u4gh{NwINXIKwIx?LT#h3!WE2XZPEpEg!YfGYuL&|7 z3qbIJ8$I!MRGUl9!7=GQnTiyZ*&FM_&~1vuaJy*&!SURhgYP92X(DZ_lBTV_6_ZY@ zKS{^rNtvrdHWT*1w)6~Aa&ZnC(nwN?q$kK+_`tnVA~vN8giyyhr_Hcv_%XNvED0U5xeyBmd~`cPa_+#3DCEA#XJ|+5{Q@&3bwOEoSv}*@|A^Z1tR91 zH&>r!i_n5+y(JJK`{<$rrK7S+l9x*&3K8GVoU-(=#%^0M&05XX;o4fVh_i6vTPAv| z_R}md-_$({6w*R%KWQNx%#sP)MbGl`{JS78&u4|ac7^9LgFKtg5P*o%KV#oOU*^vL zgf%eyH2BHM;pm(kPk@)1?95_4S<#SMQ40TL$Nm5oK4MG98saUDfa#YUjbyv#%edGT zgZmm*@!~veI4&WQ&-qU$M;+pUW@67&?);)A8l&ZK1#LN6NiJs~L6<@!* zHtw#x2l}x4fU$9|R2oB6Hb`#{!qjpqL%{L|8t`X-0>{YPzySVRFC1G+!X>to25}=T z9#wY>23J&KQ^%yBi((ohek`n`o4%cN{PUp0*u*h|*w~e&(c%x{m8P#J9q$M_iVdE~ zG<}2c@o$4ZSSi>U0mv9Ad?&h>21j^06Sg7QXqZJ-w8dB(dk<)eDZqtdC z=1hj0Mr=z>O_EMXW<<5v)pSAE^tmqRA~^xK^>q>DSxF0O&&hq16Whj0hSOzAmq7}t zM78F6JVn^R<`)Ucf!;fceCG1?VKf_Mu=q zCfLryCMMWAg=$@c)_FybDpF z=VLK+EwW1Eyk_1qqEy{60r1!ftgE>$h}jPl+0+1-o}TgVL>b|UtV<15i!E@M0C z^cZB@<@zv>TDJ8;P%?>OP8m1DACc_m8d_ps~{xyh}eWaQm5GFECsw zaP-guL8Ly66!#WHq^%89>U5X3R)s*vM<~`y9i?ve)yVYg1!!x@#Ujj>Ij+V1q%G2Q z-nK~bwIhpmEL7T23z?NN9*2Je$RwSyfBv*qA7NxPfLH%W6pA;R=%hc=z^O%fZf-S?i0-tA(Oj@9(r zLnvyCQiXl{G7uKH^FO6@{q(O7L?_lVm?b6Ueh?V;?=@Ht8cLEc~{V*Tnjtxor!DVyf82<%a=d(+t_!ttOBI2IU zL5(?Gs`l$SrMC5&)7v?<+7@O`Kg+47+rr~X4K^EEZ66XjkdKYXB&oxt0v?}iva3vI z_KP|vfE6~+!Io65lv|+symd^ zVmp7N#r754*Kps!NpKx--@zL0WHaXcX?>M^|-rSRCj`# zSSNEgo2wZvw1}TVk%X3HwW=kVHgxo(UPZXX_@MI1XHlzKMOk{e0OjVgwx<=cuyP}c z*I}XMNd;SMXbqGzMJ}x3ZZkKtxR|79k!6Ykm{7b509P`4>ItG}P>#QH@vNf^1r;wF zNc@{GF(B9Tx%SNi;s=-^I>Jiax^&#WxWE+gjxHE2U7AHjLe|y$52Xv$-L5xOn(y{@ zVis!Z$LjM3?f#aYQ)t{SyYw(jBWTc~n(C`^Vg)9M^Qqp1bnGp1bGvO}sv^O}juxv! zkPBa0$y=C?i%=y1@Ny{3{h=$hZ*o^_F7zit9K7Qy24FbMM=%3{d>+nu- z_FjY$Q-NMXpuu)cOb>TDuAwR+G*oZ6oE}oI;&x*J-;gG!r8%!eyB5$@21`?gmRzO@ zz-5{=&qbWa($*qb-A~=wJVRZdtk&*xI}5{TYT)u6@NskBKyJ@p2J-12^#PT^x!ND1 zUgkO3xDg9kOafYBxh3C?=CT8y8y-1={)*W*i+sFPye*yVBp7KchDsimC`SWI`Sw0Y(TmF%ah-n&Jn@oG;~JC&ctvl}c*s9zu~=WMBwG3F3nbA-PYi)QG=fNDT^Hn)Q6uqcp|Z zva?+G>*S@6(s}uy5aeb}8{>eLsa(_J57J1*h1PRqKE(&$n)RhB5qB1(0`xhNepE=- zATNUqND`wS^)!-!N_oPSns`D9@lq_rFVN;+y(1>~f^^>7{|yegFm)`S`@v>&LIX>?fy7TpMre^OJ2|GOi|o z@Rp}*Y>c0qs1az$%~QK=-#EE2m+h=w3cXC0xA+blN=ZrG@`cwk`=Cm!db)N$y4fj} zjS)|E+s>+3tiL)H0)8N{AeZ}=9P+cYGySXzD(@`i+@KY0XK_DGMa5H2T=$OVtIx^Y z4Bl}L-_L#_a}#*S7Z7>%*Rsq7L10C#wy!CtXH=1F``W|EwlByM?;JWeX{O3sip2DD zL9Io?C^5ZVSmY~=F!8cmMX7BAZWw>ZE z-*IDjVTSjtGN)_U)zxGJ@{WEZrRzC`Yjp}(hpQMUP+$l~5_ALL%}a5ASk1SOur#r^ zyP(x-;&{sGisCCYuIkG>ckYzayKx7Q7zd;Ztd?-Q^boRh-qWcg@>Dzaf#7TS8J57X z1coIrEP-JO3`<~G0>cs*mcXzCh9xj8f&c3z@OBaYITG9&2Sq&u_c+`OaDRb22zLtZ zYq+a$qktC5U9`0ed zr{P|MbHg2jBhONlcjMWW;0<@doJhGhtL#beow7VPXk`*ut>7z_yFCkfb+wGO?bqN2P)Pf@{vD0AWB%uFwYM5)Yq zi{=iAQYnkmljbeD0a-H3V2lY4yi3oO72LbVp=ZnSD+eWPNx_A{1^G6NxpRuhkkuY z1Jdkey0HJ1_u`0TUK7N76KUfT?*Dwv9_e_$8t6(_4&p2ag;%VC^Q~d>wRQ$-NZ?6`M%!4Me9L0NY5vog5g5-cNpPBNknlnmKbO6*o}J zvI6vOuI&D@@(pDeJ#!uG!h%XR30+{l!(P6QTc^Oh@tl&f!txDNI)+ifeU3Th86**C5 zrPMfS4f$Dc-cROfcyQbg~{m!>%8&;hI?on>391rVb^4pZNLa0oDY9G zst0}($5>4MxycPT4e=Rp1~@X>lZa0ha%cvcze@t=E&uJ)Uj0Jsaj-tr7pyNRS)*)wu^1??*rdN#*{0$5|E9(i>hHx`tWrO1 zYSc_)kv@IS9b^xORb-*Q06D$uR{c~%%tlH-^{!clO7bDsz(c?!O#DDR;AnVwf zaKyiW`^LmUl0W;KMh*P^JniQYTr~Wbf3?)l|9+gKhPv^mqW*J#Ln%B&-T3o^)c-So z1Nrrk#sBjUjXr@J@#L}`zM!D2a5C=GIpNo!FOz;q`mi1jcdn>ja@P)z%8z{h3fm~+ zs~+0)&}0(9^WtY-zj3G6uX(nebVbU#*{S$11HWm`;Jb(WAo2jp!m!7}p*XeI=RB~kaQ&h$$`Jd>im7kP;@*~rzlDFpMsdMdDuv^{i zec0@b89%Q7vi&9MK=- zgK540?}%#n_2)T1>tJG=|BdifW-x4hS~D27H){vO5tqjdgq^>C@}_~Xe(%2V-Y~uY z&@GQdk#OU8M~>cd;N&8!zU!I4t(*SxzrXv(66>+N>r=nF`H{urJ-qdS-+%l>@+g1T z9j`xQy|Vwgl<#WBJX-V6Th{$?AHDd2Ja5v*11GKJE$v%7oQLMV{kxZw_SW5>b-UTs zv#z~r&fZTSId|oc*F2m5oRQr4_4+T5{BXzJwKcb;Ow*0~bMI@qFW)_QHO2GNhnIRh zpU;2ri4Sa!Bdw2(dhxaA#(%jZHP|su!__>@tv{5OzHH9zb-BpPfg7euAI5=&rkGTx_$riZF!$M%N8E`!@ubp z{FbabIF&wm+bu8OnOOa5cvc-y;F+iWaIUYP8`FgUPVK z@RH$x;X}h|L%-qf$nwZdk=r7lhVbF?|hoN8WSzSsPO`DydN zoBwKVHXk-0H=j0Bv6|SMVkgGl7CR$0EB2n)O|g%}?vHJWJrg_9GS(7hnP(}oxGaCL z9I>3Rv|FxN0^&mB=Emj3{g=2G<95Xzj5`tcS={-!Z{uw73*)!PpN`*~@KHiw;+Vu4 zi7|<3iN8(!IPqI@GeM6#6Y!=P<`@HA!s3LWdOCpORYa!ED zBby-4bCD}eWu_KWzeyK0C2D2V53`vVzu57yQ(~vZ8e*ekEwPEQDX}kG+?H0$2bK>l zrz~eJ{&CaeY;g~Txumc^}zTNPIrR~+}3xIJ-uf4z~{BQ?e`##@bd85505jCsb@#$scc@i%DUXN-R`zHWTWxZn5z+IhTb zzv3AQpNKvceIfdv(buB=X9dmD&AMsUZL{v4HE-7PS*vDM&iZK9me~hpe>K}P zJ0#}jm{~FQn7o+MnCh6{#%zl@5OXZ%ix{B+Cq51BYmZ+V|91Sl z@$K>7_@p*E!IH2xflb($P?g|Js7M2Mvb|Um1E0vSD=O_{cjVVrO0<8Cz37TDvAn$7Znu~l@gT}Wsh1KMgC!08ekfO&KP10Glm=W%9x*F#GMi2 zLgP~73S*&hjgiHeuQhHpZZ|${e8Kpt@eSi%<3ZyQyahoo.fr) +% Copyright (C) 2007 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2007-11-05 Creation +% +% ----------------------------- Script History --------------------------------- + +if numel(n)==1 & n>=1 + alpha=1-(1-alpha).^(1./n); + sig=alpha; + return +end +p=n; +n=numel(p); +alpha=dunnsidak(alpha,n); +sig=p<=alpha; \ No newline at end of file diff --git a/e2pie.m b/e2pie.m new file mode 100644 index 0000000..1c21019 --- /dev/null +++ b/e2pie.m @@ -0,0 +1,183 @@ +function [ hh th ] = e2pie(varargin) +%PIE Pie chart. +% E2PIE(X) draws a pie plot of the data in the vector X. The values in X +% are normalized via X/SUM(X) to determine the area of each slice of pie. +% If SUM(X) <= 1.0, the values in X directly specify the area of the pie +% slices. Only a partial pie will be drawn if SUM(X) < 1. +% +% E2PIE(X,EXPLODE) is used to specify slices that should be pulled out from +% the pie. The vector EXPLODE must be the same size as X. The slices +% where EXPLODE is non-zero will be pulled out. +% +% E2PIE(...,LABELS) is used to label each pie slice with cell array LABELS. +% LABELS must be the same size as X and can only contain strings. +% +% E2PIE(AX,...) plots into AX instead of GCA. +% +% H = E2PIE(...) returns a vector containing patch and text handles. +% +% E2PIE is different from the MATLAB version in that labels have +% line segments indicating which slice they belong to, and are +% spaced more rationally. +% +% Example +% e2pie([2 4 3 5],{'North','South','East','West'}) +% +% See also PIE. + +% Clay M. Thompson 3-3-94 +% Updated Eric M. Ludlam 2005 +% Copyright 1984-2006 The MathWorks, Inc. + +% Parse possible Axes input +[cax,args,nargs] = axescheck(varargin{:}); + +if nargs==0, error('Not enough input arguments.'); end %#ok + +x = args{1}(:); % Make sure it is a vector +args = args(2:end); + +if nargs>3, error('Too many input arguments.'); end %#ok + +nonpositive = (x <= 0); +if any(nonpositive) + warning('MATLAB:pie:NonPositiveData',... + 'Ignoring non-positive data in pie chart.'); + x(nonpositive) = []; +end +xsum = sum(x); +if xsum > 1+sqrt(eps), x = x/xsum; end + +% Look for labels +if nargs>1 && iscell(args{end}) + txtlabels = args{end}; + if any(nonpositive) + txtlabels(nonpositive) = []; + end + args(end) = []; +else + for i=1:length(x) + if x(i)<.01, + txtlabels{i} = '< 1%'; %#ok + else + txtlabels{i} = sprintf('%d%%',round(x(i)*100)); %#ok + end + end +end + +% Look for explode +if isempty(args), + explode = zeros(size(x)); +else + explode = args{1}; + if any(nonpositive) + explode(nonpositive) = []; + end +end + +explode = explode(:); % Make sure it is a vector + +if ~isempty(txtlabels) && length(x)~=length(txtlabels), + error('Cell array of strings must be the same length as X.'); %#ok +end + +if length(x) ~= length(explode), + error('X and EXPLODE must be the same length.'); %#ok +end + +cax = newplot(cax); +next = lower(get(cax,'NextPlot')); +hold_state = ishold(cax); + +theta0 = pi/2; +maxpts = 100; +inside = 0; + +h = []; +ytextmidlast = nan; +xtextlast = nan; +for i=1:length(x) + n = max(1,ceil(maxpts*x(i))); + r = [0;ones(n+1,1);0]; + theta = theta0 + [0;x(i)*(0:n)'/n;0]*2*pi; + txttheta = theta0 + x(i)*pi; + + if inside, + [xtext,ytext] = pol2cart(txttheta,.5); + else + [xtext,ytext] = pol2cart(txttheta,1.05); + end + + [xx,yy] = pol2cart(theta,r); + if explode(i), + [xexplode,yexplode] = pol2cart(theta0 + x(i)*pi,.1); + xtext = xtext + xexplode; + ytext = ytext + yexplode; + xx = xx + xexplode; + yy = yy + yexplode; + end + theta0 = max(theta); + h(i) = patch('XData',xx,'YData',yy,'CData',i*ones(size(xx)), ... + 'FaceColor','Flat','parent',cax); + + %% ERIC HACK + if ~isempty(txtlabels) + if ytext > .7 + ytextmid = ytext+.1; + elseif ytext < -.7 + ytextmid = ytext-.1; + else + ytextmid = ytext; + end + + if xtext > 0 + % We are slices on the right side of the chart + ha = 'left'; + xtextend = xtext+.2; + xtextmid = xtext+.1; + + if ~isnan(ytextmidlast) && xtextlast > 0 + if ytextmidlast >= ytextmid-.05 + ytextmid = ytextmidlast + .05; + end + else + %% First label. Use a flat line if > .7 + if ytext > -.7 + ytextmid = ytext; + end + end + + else + ha = 'right'; + xtextend = xtext-.2; + xtextmid = xtext-.1; + + if ~isnan(ytextmidlast) && xtextlast > 0 && ytextmidlast >= ytextmid+.05 + ytextmid = ytextmidlast - .05; + end + end + + if ~isempty(txtlabels{i}) + + xdata = [ xtext xtextmid xtextend ]; + ydata = [ ytext ytextmid ytextmid ]; + line(xdata, ydata, [ 0 0 0 ], 'color','k',... + 'clipping','off'); + + t(i) = text(xtextend,ytextmid,txtlabels{i},... + 'parent',cax,... + 'horizontalalign',ha); %#ok + ytextmidlast = ytextmid; + xtextlast = xtext; + end + end + %% END ERIC HACK +end + +if ~hold_state, + view(cax,2); set(cax,'NextPlot',next); + axis(cax,'equal','off',[-1.2 1.2 -1.2 1.2]) +end + +if nargout>0, hh = h; end +if nargout>1, th = t; end diff --git a/edist.c b/edist.c new file mode 100644 index 0000000..fecafb5 --- /dev/null +++ b/edist.c @@ -0,0 +1,68 @@ +/*-------------------------------------------------------------- +// file: edist.c - compute euclidian distance matrix +// D = edist(A,B) +// INPUTS: +// A - mxd matrix (m=number of points, d=number of dimensions) +// B - kxd matrix (k=number of points, d=number of dimensions) +// OUTPUTS: +// D - mxk matrix of euclidian distances +//--------------------------------------------------------------*/ +#include +#include "mex.h" + +/*-------------------------------------------------------------- +// function: mexFunction - Entry point from Matlab environment +// INPUTS: +// nlhs - number of left hand side arguments (outputs) +// plhs[] - pointer to table where created matrix pointers are +// to be placed +// nrhs - number of right hand side arguments (inputs) +// prhs[] - pointer to table of input matrices +//--------------------------------------------------------------*/ +void mexFunction( int nlhs, mxArray *plhs[], int nrhs, const mxArray +*prhs[] ) +{ + int M,N,K; /* Sizes */ + int m,n,k; /* Loop variables */ + double *pA, *pB, *pD; + + if (nrhs < 2) + mexErrMsgTxt("Not enough input arguments."); + if (nrhs > 2) + mexErrMsgTxt("Too many input arguments."); + if (!mxIsDouble(prhs[0]) || !mxIsDouble(prhs[1])) + mexErrMsgTxt("Arguments must be type double."); + if (mxGetN(prhs[0]) != mxGetN(prhs[1])) + mexErrMsgTxt("matrix dimensions (number of columns) must agree."); + if (mxIsComplex(prhs[0]) || mxIsComplex(prhs[1])) + mexWarnMsgTxt("Complex parts ignored."); + + /* edist(MxK,NxK) => MxN */ + M = mxGetM(prhs[0]); /* Number of rows in output */ + N = mxGetM(prhs[1]); /* Number of columns in output */ + K = mxGetN(prhs[0]); /* Number of dimensions */ + + plhs[0] = mxCreateDoubleMatrix(M,N,mxREAL); + + pA = mxGetPr(prhs[0]); + pB = mxGetPr(prhs[1]); + pD = mxGetPr(plhs[0]); + + /* Now the distance computation */ + pA = mxGetPr(prhs[0]); + pB = mxGetPr(prhs[1]); + for (m=0; m<4Cpz<*-B#2{qp*^;p(M?YCJ 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 bBoOJhowS{@y=oh_UALhjo3aS}1`zmPQo_qk literal 0 HcmV?d00001 diff --git a/edist.dll.old b/edist.dll.old new file mode 100644 index 0000000000000000000000000000000000000000..fe9838d0cdd54c9cd356e442832b2ee9e96553b6 GIT binary patch literal 18432 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 bBoOJhowS{@y=oh_UALhjo3aS}1`zmPQo_qk literal 0 HcmV?d00001 diff --git a/edist.mexglx b/edist.mexglx new file mode 100644 index 0000000000000000000000000000000000000000..88ad2e156ebd40b77901789b4605f11d8d50acb4 GIT binary patch literal 6117 zcmb_geT-Dq6~Aw0kZt)|6e|dpr`hOIb+X`!OH1r7yR!>s7gpE>E#h-_<}ovNKGvBx zW*|k_JgrV&FeX^*KQ$(5YSP5oY8sk0cF7vRgjz~y3khqt2I;K3CD@XdTFUnKyYJrF z0W>B}&*tp8zkAO8KIgtWXLqE#(=ZI7DJX)1sCR}C&5&cWeND44#SGCXZW1?2y|$;E z{F2XTB1FBAdRey!_24)YCRIZ{IRSosmg10D4Nvy|V zwFzFzf+7=gPL08Q@yXP~N{8)|?Yk9bU*wk+PAFVbxJ%*JAiFX7uSh&g+y!Z3LzZEI z*J2gl1SV0kzug3&C71^>_lxu`;9DRcm1x>tSXLsF%UT5|ns+P0bhJ>g3nF8eI*ZwulgedTj^y(_g+yP;5t-7uLPxGRkhW3mvYqv^ zrahM#Oxq=zZi(izRa+SJz@R;EM;+U5))RH|sgiY1Qk>{#J`^vL09vXQXwrINNm#u7EZ1LQSY&YyI z_FT@Z9BQZ(Q8O{i9S)V<)u@^}=1J+*KXt4)e$Z`2o#DP4+PZCI@7uQmaKATI!YB-l z+~)W1?VG$Lr*plx?}9(ES$kd9VSuS)ers<@gFi{M-?AAiT3s23?a;`F*mkd2?_M%? z&)w_3`B&Mh@(xWHfPF{-@&1zv#D&H_9+>|D!=H6hkKBx{OjAkO-I?5u4t z2cUe=+qJ+w6q;`I&;LwKtvcwSk*OL*Y8zdE`pMzqTyG{v_*fzupHWj!{crI(c?tzt?lx>!0N40`U88try)3Q_j5dAEu6aofqA+A;99_ zw?4dWgg#soQ;(ooXF=(4{`MH8cMt*wCcnV|iLsKvtv&GDS zoi}sa%~&p7%w!A8{I=0V-nQ9kPg`Gi+ZuD@@@6v|byAPps52G^zp)UKUy0YT7TzYu zu@ile2r)^<&W*zOPMxuMb|~}=?n>Thz)d%nKPr)o$=k!(?RyfTcL!0 z_ztM*Sq341*Bbx(Y1IJO!*5XN0j6@h4p__@{E%F7V(B2#I=oUUc8Dx;SEmT z!zQ@26#tyB-jjW6o)J>U`^0CTxq7#-YFygy}x9)Xd#vzuS z8$>;vC&#u}X6|)htViN=kiEd4LPGpc#5bv?moS$%!0&+H2Y&!Q3%&%Ni$#7F90uPD z=6Cf1A8%dl?O!(=yVmzFGn?@^SYfVcYHDe^r>WU&+-S$mwNb~fzq7e{*%j6+!YlqS zHD5StxUeJRLEuN?ku8QFS1+qtV8t)fSuoApX*Tx@ zgK0;EQ`r>$J2qZ^VLTX9Q03xLCn~~r(%PPnW^5}NM@x-FIOgQ?1&r>CM`KX3f$Te< zlhcZ3QZbC3L!bB;mIL{n4iwNdmdo%G&?ouD62~X~mVQMaCij7fNjP%JH$&2oSx?_4 zGeGEXEYglXjd%(0qgo$C;kbjP{ zCfZT|jFMbm?e9-ONxHumfM`#<=dnR#?u#aE_cAJI$EowTFn#_l5QF2YXt!A^Dgtr|PN{drhgS8F#6z%%3gGFrD;JBJ- z_o@bvTE}mTUK`ctVK>2oB5g;>v(=PD!(I$V-&zUgxb#76|7HE{$0PgBnufYRm|aD@ zV@Q6+McpM5`yKGkATcLH;@v_h_Oxz$`kct+Q;~z24d`zf@-H3qdw*; z{8;ocodDzc-NGK51Y&HFg6ialtC%kUKVB$%b#spfB2Qcz&iLG(9LP-a1Vzw)tv6VZQsywESG|3R@x`iEX`0PFQ< z;fJ!=2h-&SCbPk%7PLYZITZ*Y=KQK!)0rx3;>hA`w zwO9OSdPLAcL)nd%t zsrM!3uV0!>FQ$am)xBX&Ter2LvvX6V&+2Pi(;Z>{FlBoX$+4kc- literal 0 HcmV?d00001 diff --git a/edist.mexw32 b/edist.mexw32 new file mode 100644 index 0000000000000000000000000000000000000000..83bb1ffdac3e6d8913a5b7f1065ffbf5d0422268 GIT binary patch literal 6656 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 literal 0 HcmV?d00001 diff --git a/eeglab/chanlocs2bstchannel.m b/eeglab/chanlocs2bstchannel.m new file mode 100644 index 0000000..5eb2b46 --- /dev/null +++ b/eeglab/chanlocs2bstchannel.m @@ -0,0 +1,18 @@ +function [ Channel ] = chanlocs2bstchannel( chanlocs ) +%bstchannel2chanlocs - Converts EEGLAB chanlocs structure to BrainStorm Channel +% [ Channel ] = chanlocs2bstchannel( chanlocs ) + +% First converts to Cartesian coordinates +chanlocs = convertlocs(chanlocs, 'topo2all'); + + + +return + +for i=1:length(Channel) + eloc(i).X=Channel(i).Loc(1); + eloc(i).Y=Channel(i).Loc(2); + eloc(i).Z=Channel(i).Loc(3); + eloc(i).labels=Channel(i).Name; +end +chanlocs=convertlocs(eloc, 'cart2all') \ No newline at end of file diff --git a/eeglab/eeglab2brainstorm.m b/eeglab/eeglab2brainstorm.m new file mode 100644 index 0000000..e69de29 diff --git a/eeglab/icadefs.m b/eeglab/icadefs.m new file mode 100644 index 0000000..f7f1ba3 --- /dev/null +++ b/eeglab/icadefs.m @@ -0,0 +1,97 @@ +% icadefs() - function to read in a set of EEGLAB system-wide (i.e. lab-wide) +% or working directory-wide constants and preferences. Change the +% way these are defined in the master icadefs.m file (usually +% in dir eeglab/functions/sigprocfunc) or make a custom copy of +% the icadefs.m file in a project directory. Then, calling functions +% that call icadefs from an EEGLAB session in that working directory +% will read the local copy, which may set preferences different from +% the system-wide copy. +% +% Author: Arnaud Delorme, Scott Makeig, SCCN/INC/UCSD, La Jolla, 05-20-97 + +% Copyright (C) 05-20-97 Scott Makeig, SCCN/INC/UCSD, scott@sccn.ucsd.edu +% +% This program is free software; you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation; either version 2 of the License, or +% (at your option) any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with this program; if not, write to the Free Software +% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +% ---------------------------------------------------------------------- +% ------ EEGLAB DEFINITION - YOU MAY CHANGE THE TEXT BELOW ------------- +% ---------------------------------------------------------------------- + +% INSERT location of ica executable for binica.m below +ICABINARY = '~/bin/ica_linux'; + +YDIR = 1; % positive potential up = 1; negative up = -1 + % for most ERP plots + +HZDIR = 'up'; % ascending freqs = 'up'; descending = 'down' + % (e.g., timef/newtimef frequency direction) + +% the eeg_options.m file also countains additional options + +% ---------------------------------------------------------------------- +% ------------------------ END OF DEFINITIONS -------------------------- +% ---------------------------------------------------------------------- + +TUTORIAL_URL = 'http://sccn.ucsd.edu/wiki/EEGLAB'; % online version +DEFAULT_SRATE = 256.0175; % default local sampling rate (rarely used) +DEFAULT_TIMLIM = [-1000 2000]; % default local epoch limits (ms) + +% Set EEGLAB figure and GUI colors +% -------------------------------- +lowscreendepth = 0; +if ~exist('OCTAVE_VERSION') + if get(0, 'screendepth') <=8 % if mono or 8-bit color + lowscreendepth = 1; + end; +end; +if lowscreendepth + fprintf('icadefs(): Setting display parameters for mono or 8-bit color\n'); + BACKCOLOR = [1 1 1]; % Background figure color + BACKEEGLABCOLOR = [1 1 1]; % EEGLAB main window background + GUIBUTTONCOLOR = [1 1 1]; % Buttons colors in figures + GUIPOPBUTTONCOLOR = [1 1 1]; % Buttons colors in GUI windows + GUIBACKCOLOR = [1 1 1]; % GUI background color + GUITEXTCOLOR = [0 0 0]; % GUI foreground color for text + PLUGINMENUCOLOR = [.5 0 .5]; % plugin menu color + +else % if full color screen + BACKCOLOR = [.93 .96 1]; % EEGLAB Background figure color + BACKEEGLABCOLOR = [.66 .76 1]; % EEGLAB main window background + GUIBUTTONCOLOR = BACKEEGLABCOLOR;% Buttons colors in figures + GUIPOPBUTTONCOLOR = BACKCOLOR; % Buttons colors in GUI windows + GUIBACKCOLOR = BACKEEGLABCOLOR;% EEGLAB GUI background color <--------- + GUITEXTCOLOR = [0 0 0.4]; % GUI foreground color for text + PLUGINMENUCOLOR = [.5 0 .5]; % plugin menu color +end; + + +% THE FOLLOWING PARAMETERS WILL BE DEPRECATED IN LATER VERSIONS +% ------------------------------------------------------------- + +SHRINKWARNING = 1; % Warn user about the shrink factor in topoplot() (1/0) + +MAXENVPLOTCHANS = 264; % maximum number of channels to plot in envproj.m +MAXPLOTDATACHANS = 264; % maximum number of channels to plot in dataplot.m +MAXPLOTDATAEPOCHS = 264; % maximum number of epochs to plot in dataplot.m +MAXEEGPLOTCHANS = 264; % maximum number of channels to plot in eegplot.m +MAXTOPOPLOTCHANS = 264; % maximum number of channels to plot in topoplot.m + +DEFAULT_ELOC = 'chan.locs'; % default electrode location file for topoplot.m +DEFAULT_EPOCH = 10; % default epoch width to plot in eegplot(s) (in sec) + +SC = ['binica.sc']; % Master .sc script file for binica.m + % MATLAB will use first such file found + % in its path of script directories. + % Copy to pwd to alter ICA defaults diff --git a/eeglab/plugins/LENA/eegplugin_lena.m b/eeglab/plugins/LENA/eegplugin_lena.m new file mode 100644 index 0000000..2ee3e95 --- /dev/null +++ b/eeglab/plugins/LENA/eegplugin_lena.m @@ -0,0 +1,33 @@ +function eegplugin_lena( fig, try_strings, catch_strings); +% Plugs in the LENA format importation +% K. N'Diaye, 2009/04/05 +if ~exist('pop_readlena', 'file') + addpath(fileparts(mfilename('fullpath'))); +end +if ~exist('fastReadLENAHeadBin', 'file') + addpath(fullfile(fileparts(mfilename('fullpath')),'ReadWriteLena')) + addpath(fullfile(fileparts(mfilename('fullpath')),'ReadWriteLena', 'Input')) + addpath(fullfile(fileparts(mfilename('fullpath')),'ReadWriteLena', 'Output')) + addpath(fullfile(fileparts(mfilename('fullpath')),'ReadWriteLena', '@xmltree')) +end; + +% e_try = 'try, if exist(''h'') == 1, clear h; disp(''EEGLAB note: variable h cleared''); end;'; +% e_catch = 'catch, errordlg2(lasterr, ''EEGLAB error''); LASTCOM= ''''; clear EEGTMP; end;'; +% nocheck = e_try; +% e_catch = 'catch, errordlg2(lasterr, ''EEGLAB error''); LASTCOM= ''''; clear EEGTMP; end;'; +% storenewcall = '[ALLEEG EEG CURRENTSET LASTCOM] = pop_newset(ALLEEG, EEG, CURRENTSET); h(LASTCOM);'; +% e_newnonempty = [e_catch 'eeg_h(EEG,LASTCOM); ... +% if ~isempty(LASTCOM) & ~isempty(EEGTMP), ... +% EEG = EEGTMP;' storenewcall 'disp(''Done.''); ... +% end; clear EEGTMP; eeglab(''redraw'');']; + +nocheck =[]; +e_newnonempty = 'EEG = EEGTMP; eeglab(''redraw'');' ; + +filemenu = findobj(0, 'label', 'File'); +if isempty(filemenu) + error('No EEGLAB window found: try starting EEGLAB first'); +end +neuromenu = findobj(filemenu, 'tag', 'import data'); +uimenu(neuromenu, 'Tag', 'lenaCmd', 'Label', 'From LENA format', 'CallBack', ... + [ nocheck '[EEGTMP LASTCOM]= pop_readlena;' e_newnonempty ], 'Separator', 'on'); diff --git a/eeglab/plugins/LENA/lenaformat_files/image001.gif b/eeglab/plugins/LENA/lenaformat_files/image001.gif new file mode 100644 index 0000000000000000000000000000000000000000..6209b5c1507a89e282812f83c65c204f56871ee1 GIT binary patch literal 3798 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 literal 0 HcmV?d00001 diff --git a/eeglab/plugins/LENA/pop_readLENA.m b/eeglab/plugins/LENA/pop_readLENA.m new file mode 100644 index 0000000..9778e64 --- /dev/null +++ b/eeglab/plugins/LENA/pop_readLENA.m @@ -0,0 +1,175 @@ +% pop_readLENA() - load LENA Data files (pop out window if no arguments). +% +% Usage: +% >> EEG = pop_readLENA; % a window pops up +% >> EEG = pop_readLENA( lenafile ); +% +% Inputs: +% datafile - A LENA header file +% +% Outputs: +% EEG - EEGLAB data structure +% +% Author: Karim N'Diaye, CNRS-UPR640, 01 Jan 2004 +% +% See also: eeglab(), readLENA() + +%123456789012345678901234567890123456789012345678901234567890123456789012 + +% Copyright (C) 2009, Karim N'Diaye (karim.ndiaye@upmc.fr) +% +% This program is free software; you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation; either version 2 of the License, or +% (at your option) any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with this program; if not, write to the Free Software +% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +function [EEG, command] = pop_readLENA(datafile) + +EEG = []; +command = ''; +if nargin < 1 + % ask user + try + cd('\data\confinum\RAWDATA\EEG5330'); + catch + try + cd('ndiayek\data\confinum\RAWDATA\EEG5330'); + catch + cd('d:\ndiayek\data\confinum\RAWDATA\EEG5330'); + end + end + [filename, filepath] = uigetfile('*.lena', 'Choose a datafile in a DS folder -- pop_readLENA()'); + drawnow; + if filename == 0 return; end; + datafile = fullfile(filepath,filename); +end; + +% load data +% --------- +EEG = eeg_emptyset; + [header data] = read_lena(datafile); +%[header data] = fastReadLENAHeadBin(datafile); + +EEG.setname = 'LENA data'; +[EEG.filepath,filename ,ext] = fileparts(datafile); +EEG.filename = [filename ext]; +EEG.data_filename = a%data_filename; +EEG.pnts = header.timeSamples; +EEG.nbchan = length(header.sensors.name); +if isempty(strmatch('datablock_dim', header.dimensions_names)) + EEG.trials = 1; +else + error ('Don''t know how to deal with multi trial data!') +end +EEG.srate = header.sampleRate; +EEG.xmin = header.preTrigger/header.sampleRate; +EEG.xmax = (header.timeSamples-1-header.preTrigger)/header.sampleRate; +EEG.data = data; +EEG.times = EEG.xmin:1./EEG.srate:EEG.xmax; + +try; + EEG.comments = header.history; +end +EEG.ref = ['nose']; + + + +%% Channel info +for chan = 1:EEG.nbchan + EEG.chanlocs(chan).labels = header.sensors.name{chan}; + fn=fieldnames(header.sensorSamples); + i=strmatch('list_sensors',fn, 'exact'); + if ~isempty(i) +% for i_fn=setdiff(1:numel(fn), i) +% EEG.chanlocs(chan) = setfield(fn{i_fn}, +% getfield(header.sensorSamples, fn{i_fn}) +% labels = header.sensors.name{chan}; +% end + end + +end + +% if isempty([chanlocs.scale]) +% chanlocs = rmfield(chanlocs, 'scale'); +% end + +EEG = eeg_checkset(EEG); +command = sprintf('EEG = pop_readLENA(''%s'');', datafile); + +% This +ptxfile = [ data_filename(1:end-5) '.ptx']; +if ~exist(ptxfile) + return +end + + +return +% importing the events +% -------------------- +if ~isempty(Eventdata) + orinbchans = EEG.nbchan; + for index = size(Eventdata,1):-1:1 + EEG = pop_chanevent( EEG, orinbchans-size(Eventdata,1)+index, 'edge', 'leading', ... + 'delevent', 'off', 'typename', Head.eventcode(index,:), ... + 'nbtype', 1, 'delchan', 'on'); + end; +end; +return + +%EEG = +% +% setname: 'Continuous EEG Data epochs' +% filename: 'eeg_demo_squareepochs.set' +% filepath: '' +% pnts: 384 +% nbchan: 32 +% trials: 80 +% srate: 128 +% xmin: -1 +% xmax: 1.9922 +% data: [32x384x80 single] +% icawinv: [] +% icasphere: [] +% icaweights: [] +% icaact: [] +% event: [1x157 struct] +% epoch: [1x80 struct] +% chanlocs: [1x32 struct] +% comments: [9x769 char] +% averef: 'no' +% rt: [] +% eventdescription: {[2x29 char] [2x63 char] [2x36 char] ''} +% epochdescription: {} +% specdata: [] +% specicaact: [] +% reject: [1x1 struct] +% stats: [1x1 struct] +% splinefile: [] +% ref: 'averef' +% history: [11x108 char] +% times: [1x384 double] + +% EEG.event +% 1x157 struct array with fields: +% type: 'square' or 'rt' +% position: [1] or [2] +% latency: in ms from the beginning of the continuous recording +% epoch: the epoch in which the event falls (indexed from 1) + +% EEG.epoch +% 1x80 struct array with fields: +% event: the index of the events that fall in that one epoch +% eventlatency: the latency of each event +% eventposition: the position of each event +% eventtype: the type of each event + + diff --git a/eeglab/plugins/LENA/readLENA.m b/eeglab/plugins/LENA/readLENA.m new file mode 100644 index 0000000..aa968b2 --- /dev/null +++ b/eeglab/plugins/LENA/readLENA.m @@ -0,0 +1,143 @@ +% read_lena() - read LENA header & data +% +% Usage: +% >> [header data lena_tree] = read_lena(lenafile) +% +% Required Input: +% lenafile = LENA header file +% Available options: +% device -> 'ALL' (default)|'MEG'|'EEG'|'EEG+MEG'|'DC' +% trials -> Trials to import as list of indices (default: [] = 'all') +% timewindow -> Default: [] = 'all' +% +% Outputs: +% header = header info (from reading the xml heder file) +% data = cell array of data (one cell by trial) +% +% Author: Karim N'Diaye, CNRS-UPR640, 01 Jan 2004 +% +% See also: +% POP_READLENA, EEGLAB + +%123456789012345678901234567890123456789012345678901234567890123456789012 + +% Copyright (C) 2004, CNRS - UPR640, N'Diaye Karim, +% karim.ndiaye@chups.jussieu.Fr +% +% This program is free software; you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation; either version 2 of the License, or +% (at your option) any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with this program; if not, write to the Free Software +% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +% $Log: readegi.m,v $ +% Revision 0.1 2004/01/01 +% First alpha version for EEGLAB release 4.301 + +function [header data lena_tree] = readLENA(lenafile,varargin) + +if nargin < 1 + help(mfilename); + return; +end; +binary_extensions = {'.bin' '.data'}; + + +lena_tree = xmltree(lenafile); +header = convert(lena_tree); + +if nargout<2 + return +end + +if nargin==2 + data_filename = varargin{1}; + if isfield(header, 'data_filename') + warning('Overloading binary data filname from header (%s) to: %s', header.data_filename, data_filename); + end + header.data_filename = data_filename; +end + + +if ~isfield(header, 'data_filename') + header.data_filename =''; + for i=1:length(binary_extensions) + data_filename = [lenafile(1:end-5) binary_extensions{i} ]; + if exist(data_filename, 'file') + header.data_filename = data_filename; + warning('Assumed binary data file not specified in header... Found possible match: %s', data_filename); + break; + end + end +end +if isempty(header.data_filename) + error('No datafile') +end + +fprintf('Importing data from binary file...\n') + +if strcmp(header.data_format,'LittleEndian') + data_fid = fopen(header.data_filename,'r','l'); +elseif strcmp(header.data_format,'BigEndian') + data_fid = fopen(header.data_filename,'r','b'); +else + warning('No data_format provided'); + data_fid = fopen(header.data_filename,'r'); +end + +% Read the offset because this info is not provided in header! +header.data_offset = getData_offset(lena_tree); +fseek(data_fid,data_offset); + +% Get data type : +switch(data_type) + case 'unsigned fixed' + data_type='uint'; + case 'fixed' + data_type='int'; + case 'floating' + data_type='float'; + otherwise + error('Error : data_type wasn t found, which is required.') + return +end +% Get the data size : +header.data_size = getData_size(lena_tree); +data = fread(fid,length(time_dim),data_type); +% first sensor 1, from sample 1 to last (537264) + + +% +% fprintf('Importing channel location information') +% Channel=getfield(load([studyname '_channel']), 'Channel'); +% chanlocs=LENAchannels2chanlocs( Channel ); +% +% i=1; +% %datafilename=sprintf('%s%d.mat', database , i); +% f=dir(sprintf('%s*.mat', database)); +% f=strvcat({f.name}); +% f=f(:,length(database)+1:end); +% trials=str2num(char(strrep(cellstr(f), '.mat', ''))); +% trials=sort(trials); +% +% h = waitbar(0,'Importing Datafiles. Please wait...'); +% for i=1:length(trials) +% waitbar(i/length(trials),h) +% datafilename=sprintf('%s%d.mat', database , trials(i)); +% fprintf('Importing %s\n', datafilename) +% Data(i)=load(datafilename); +% %channelflag=channelflags & Data(i).ChannelFlag; +% channelflag=channelflags; +% Data(i).F=Data(i).F(find(channelflag),:); +% end +%close(h) + +return diff --git a/eeglab/plugins/bstorm/WS_FTP.LOG b/eeglab/plugins/bstorm/WS_FTP.LOG new file mode 100644 index 0000000..3066cf0 --- /dev/null +++ b/eeglab/plugins/bstorm/WS_FTP.LOG @@ -0,0 +1,3 @@ +2004.01.07 17:05 A d:\ndiaye\matlab\eeglab4.301\plugins\bstorm\pop_readbstorm.m --> pclxserver2.meg.chups.jussieu.fr /meglx13/users/ndiaye/analyse/matlab/eeglab4.301/plugins/bstorm pop_readbstorm.m +2004.01.07 17:05 A d:\ndiaye\matlab\eeglab4.301\plugins\bstorm\readbstorm.m --> pclxserver2.meg.chups.jussieu.fr /meglx13/users/ndiaye/analyse/matlab/eeglab4.301/plugins/bstorm readbstorm.m +2004.01.07 17:05 A d:\ndiaye\matlab\eeglab4.301\plugins\bstorm\readbstorm.m~ --> pclxserver2.meg.chups.jussieu.fr /meglx13/users/ndiaye/analyse/matlab/eeglab4.301/plugins/bstorm readbstorm.m~ diff --git a/eeglab/plugins/bstorm/bstchannel2chanlocs.m b/eeglab/plugins/bstorm/bstchannel2chanlocs.m new file mode 100644 index 0000000..38079ce --- /dev/null +++ b/eeglab/plugins/bstorm/bstchannel2chanlocs.m @@ -0,0 +1,12 @@ +function [ chanlocs ] = bstchannel2chanlocs( Channel ) +%bstchannel2chanlocs - Converts BrainStorm Channel struct to EEGLAB chanlocs structure +% [ chanlocs ] = bstchannels2chanlocs( Channel ) +% +%NB: All Channel shoud be of the same type, of course... +for i=1:length(Channel) + eloc(i).X=Channel(i).Loc(1); + eloc(i).Y=Channel(i).Loc(2); + eloc(i).Z=Channel(i).Loc(3); + eloc(i).labels=Channel(i).Name; +end +chanlocs=convertlocs(eloc, 'cart2all') \ No newline at end of file diff --git a/eeglab/plugins/bstorm/bstormchannels2chanlocs.m b/eeglab/plugins/bstorm/bstormchannels2chanlocs.m new file mode 100644 index 0000000..f3ada0a --- /dev/null +++ b/eeglab/plugins/bstorm/bstormchannels2chanlocs.m @@ -0,0 +1,7 @@ +function [ ] = bstormchannels2chanlocs( Channel ) +%bstormchannels2chanlocs - Deprecated: use bstchannel2chanlocs + +%bstormchannels2chanlocs - Converts BrainStorm channels to EEGLAB chanlocs structure +% [ ] = bstormchannels2chanlocs( Channel ) +% +warning('Deprecated use: bstchannel2chanlocs') diff --git a/eeglab/plugins/bstorm/bstormchannels2chanlocs_gui.m b/eeglab/plugins/bstorm/bstormchannels2chanlocs_gui.m new file mode 100644 index 0000000..a275544 --- /dev/null +++ b/eeglab/plugins/bstorm/bstormchannels2chanlocs_gui.m @@ -0,0 +1,47 @@ +function [ chanlocs ] = bstormchannels2chanlocs( Channel , device ) +%bstormchannels2chanlocs - Converts BrainStorm channels to EEGLAB chanlocs structure +% [ chanlocs ] = bstormchannels2chanlocs( Channel , device ) +% +%INPUTS: +% Channel : a BrainStorm Channel structure +%Importing Electrodes to EEGLAB format + +error('NOT IMPLEMENTED YET') +return + +channelflags=zeros(length(Channel),1); +ChannelTypes = unique({Channel.Type}); +if nargin <2 + if length(ChannelTypes)>1 + DEVICES = ChannelTypes; + [s,v] = listdlg('PromptString','Select a device:',... + 'SelectionMode','multiple',... + 'ListString',DEVICES); + device={}; + if all(v) + for strread(DEVICES(s), '%s', 'delimiter', ',') + end + else + + end + else % There is only one type of channels + device=ChannelTypes; + end +if not(iscell(device)) + device={device}; +end +for i=1:length(device) + channelflags=channelflags + strcmp(device{i},{Channel.Type}'); +end +%channelflags(strmatch('EEG', {Channel.Type}))=1; +%channelflags(strmatch('MEG', {Channel.Type}))=1; + +Channel=Channel(find(channelflags)); + +for i=1:length(Channel) + eloc(i).X=Channel(i).Loc(1); + eloc(i).Y=Channel(i).Loc(2); + eloc(i).Z=Channel(i).Loc(3); + eloc(i).labels=Channel(i).Name; +end +chanlocs=convertlocs(eloc, 'cart2all') \ No newline at end of file diff --git a/eeglab/plugins/bstorm/eegplugin_bstorm.m b/eeglab/plugins/bstorm/eegplugin_bstorm.m new file mode 100644 index 0000000..f3a75f1 --- /dev/null +++ b/eeglab/plugins/bstorm/eegplugin_bstorm.m @@ -0,0 +1,28 @@ +function eegplugin_bstorm( fig, try_strings, catch_strings); +% Plugs in the BStorm importation +% K. N'Diaye, 2005/02/07 +if ~exist('pop_readbstorm') + try + p = which('eeglab'); + p=fileparts(p); + p=fileparts(p); + p=fileparts(p); + p=fullfile(p, 'matlab', 'eeglab', 'plugins', 'bstorm') + addpath(p) + catch + end + p = which('eegplugin_bstorm'); + p = p(1:findstr(p,'eegplugin_bstorm.m')-1); + addpath([ p 'bstorm' ] ); +end; + +e_try = 'try, if exist(''h'') == 1, clear h; disp(''EEGLAB note: variable h cleared''); end;'; +e_catch = 'catch, errordlg2(lasterr, ''EEGLAB error''); LASTCOM= ''''; clear EEGTMP; end;'; +nocheck = e_try; +e_catch = 'catch, errordlg2(lasterr, ''EEGLAB error''); LASTCOM= ''''; clear EEGTMP; end;'; +storenewcall = '[ALLEEG EEG CURRENTSET LASTCOM] = pop_newset(ALLEEG, EEG, CURRENTSET); h(LASTCOM);'; +e_newnonempty = [e_catch 'h(LASTCOM, EEG); if ~isempty(LASTCOM) & ~isempty(EEGTMP), EEG = EEGTMP;' storenewcall 'disp(''Done.''); end; clear EEGTMP; eeglab(''redraw'');']; + +filemenu = findobj(fig, 'label', 'File'); +neuromenu = findobj(filemenu, 'tag', 'import data'); +uimenu(neuromenu, 'Tag', 'bstormCmd', 'Label', 'From BrainStorm DS folder', 'CallBack', [ nocheck '[EEGTMP LASTCOM]= pop_readbstorm;' e_newnonempty ], 'Separator', 'on'); diff --git a/eeglab/plugins/bstorm/pop_readbstorm.m b/eeglab/plugins/bstorm/pop_readbstorm.m new file mode 100644 index 0000000..81cfb53 --- /dev/null +++ b/eeglab/plugins/bstorm/pop_readbstorm.m @@ -0,0 +1,147 @@ +% pop_readbstorm() - load BrainStorm Data files (pop out window if no arguments). +% +% Usage: +% >> EEG = pop_readbstorm; % a window pops up +% >> EEG = pop_readbstorm( dsfolder ); +% +% Inputs: +% datafile - A datafile within a DS folder containing BrainStorm datafiles +% +% Outputs: +% EEG - EEGLAB data structure +% +% Author: Karim N'Diaye, CNRS-UPR640, 01 Jan 2004 +% +% See also: eeglab(), readbstorm() + +%123456789012345678901234567890123456789012345678901234567890123456789012 + +% Copyright (C) 2004, CNRS - UPR640, N'Diaye Karim, +% karim.ndiaye@chups.jussieu.Fr +% +% This program is free software; you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation; either version 2 of the License, or +% (at your option) any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with this program; if not, write to the Free Software +% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +% $Log: pop_readbstorm.m,v $ +% Revision 0.1 2004/01/01 +% First alpha version for EEGLAB release 4.301 +% + +function [EEG, command] = pop_readbstorm(datafile); + +EEG = []; +command = ''; +if nargin < 1 + % ask user + try + %cd('..\..\data\studies\div\T01\div-d1-f.ds'); + catch + end + [filename, filepath] = uigetfile('*_data_*.mat', 'Choose a datafile in a DS folder -- pop_readbstorm()'); + drawnow; + if filename == 0 return; end; + datafile = [filepath filename]; +end; + +% load data +% --------- +EEG = eeg_emptyset; +[study EEG.chanlocs data dsfolder] = readbstorm(datafile); + +EEG.filename = study.Name +EEG.filepath = dsfolder; +EEG.setname = 'BrainStorm data'; +EEG.nbchan = length(channel); +EEG.srate = 1/mean(diff(data(1).Time)); +EEG.trials = length(data); +EEG.pnts = length(data(1).Time); +EEG.times = data(1).Time; +EEG.xmin = EEG.times(1); % huh?? +EEG.xmax = EEG.times(end); % huh?? +%Caution! CELL2MAT is overloaded in EEGLAB environment +fprintf('Importing data...(may take a while)') +EEG.data=zeros([size(data(1).F,1) size(data(1).F,2) EEG.trials]); +for i=1:EEG.trials + EEG.data(:,:,i)=data(i).F; +end +EEG.comments = study.Session; +EEG.ref = ['common']; + +EEG = eeg_checkset(EEG); +command = sprintf('EEG = pop_readbstorm(''%s'');', dsfolder); + +return; + + + +% importing the events +% -------------------- +if ~isempty(Eventdata) + orinbchans = EEG.nbchan; + for index = size(Eventdata,1):-1:1 + EEG = pop_chanevent( EEG, orinbchans-size(Eventdata,1)+index, 'edge', 'leading', ... + 'delevent', 'off', 'typename', Head.eventcode(index,:), ... + 'nbtype', 1, 'delchan', 'on'); + end; +end; +return + +%EEG = +% +% setname: 'Continuous EEG Data epochs' +% filename: 'eeg_demo_squareepochs.set' +% filepath: '' +% pnts: 384 +% nbchan: 32 +% trials: 80 +% srate: 128 +% xmin: -1 +% xmax: 1.9922 +% data: [32x384x80 single] +% icawinv: [] +% icasphere: [] +% icaweights: [] +% icaact: [] +% event: [1x157 struct] +% epoch: [1x80 struct] +% chanlocs: [1x32 struct] +% comments: [9x769 char] +% averef: 'no' +% rt: [] +% eventdescription: {[2x29 char] [2x63 char] [2x36 char] ''} +% epochdescription: {} +% specdata: [] +% specicaact: [] +% reject: [1x1 struct] +% stats: [1x1 struct] +% splinefile: [] +% ref: 'averef' +% history: [11x108 char] +% times: [1x384 double] + +% EEG.event +% 1x157 struct array with fields: +% type: 'square' or 'rt' +% position: [1] or [2] +% latency: in ms from the beginning of the continuous recording +% epoch: the epoch in which the event falls (indexed from 1) + +% EEG.epoch +% 1x80 struct array with fields: +% event: the index of the events that fall in that one epoch +% eventlatency: the latency of each event +% eventposition: the position of each event +% eventtype: the type of each event + + diff --git a/eeglab/plugins/bstorm/readbstorm.m b/eeglab/plugins/bstorm/readbstorm.m new file mode 100644 index 0000000..a961523 --- /dev/null +++ b/eeglab/plugins/bstorm/readbstorm.m @@ -0,0 +1,85 @@ +% readbstorm() - read BrainStorm datafiles +% +% Usage: +% >> [study channel data dsfolder] = readbstorm(datafile,device) +% +% Required Input: +% datafile = Data file within a DS folder +% (all data_*_[1..N] will be imported) +% +% Optional Input: +% device = ['MEG'|'EEG'|'EEG+MEG'|'ALL'] +% +% Outputs: +% study = study info +% channel = sensor positions +% data = cell array of data (one cell by trial) +% +% Author: Karim N'Diaye, CNRS-UPR640, 01 Jan 2004 +% +% See also: +% POP_READBSTORM, EEGLAB + +%123456789012345678901234567890123456789012345678901234567890123456789012 + +% Copyright (C) 2004, CNRS - UPR640, N'Diaye Karim, +% karim.ndiaye@chups.jussieu.Fr +% +% This program is free software; you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation; either version 2 of the License, or +% (at your option) any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with this program; if not, write to the Free Software +% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +% $Log: readegi.m,v $ +% Revision 0.1 2004/01/01 +% First alpha version for EEGLAB release 4.301 + +function [Study,chanlocs,Data,dsfolder] = readbstorm(datafile,device) + +if nargin < 1 + help(mfilename); + return; +end; + +[dsfolder, filename]=fileparts(datafile) +studyname=strtok(filename,'_') +p=findstr(filename, '_') +database=filename(1:p(3)) +prev_dir=cd; +cd(dsfolder) +Study=load([studyname '_brainstormstudy']); + +fprintf('Importing channel location information') +Channel=getfield(load([studyname '_channel']), 'Channel'); +chanlocs=bstormchannels2chanlocs( Channel ); + +i=1; +%datafilename=sprintf('%s%d.mat', database , i); +f=dir(sprintf('%s*.mat', database)); +f=strvcat({f.name}); +f=f(:,length(database)+1:end); +trials=str2num(char(strrep(cellstr(f), '.mat', ''))); +trials=sort(trials); + +h = waitbar(0,'Importing Datafiles. Please wait...'); +for i=1:length(trials) + waitbar(i/length(trials),h) + datafilename=sprintf('%s%d.mat', database , trials(i)); + fprintf('Importing %s\n', datafilename) + Data(i)=load(datafilename); + %channelflag=channelflags & Data(i).ChannelFlag; + channelflag=channelflags; + Data(i).F=Data(i).F(find(channelflag),:); +end +close(h) + +return diff --git a/eeglab/plugins/bstorm/v1/pop_readbstorm.m b/eeglab/plugins/bstorm/v1/pop_readbstorm.m new file mode 100644 index 0000000..3d752e1 --- /dev/null +++ b/eeglab/plugins/bstorm/v1/pop_readbstorm.m @@ -0,0 +1,156 @@ +% pop_readbstorm() - load BrainStorm Data files (pop out window if no arguments). +% +% Usage: +% >> EEG = pop_readbstorm; % a window pops up +% >> EEG = pop_readbstorm( dsfolder ); +% +% Inputs: +% datafile - A datafile within a DS folder containing BrainStorm datafiles +% +% Outputs: +% EEG - EEGLAB data structure +% +% Author: Karim N'Diaye, CNRS-UPR640, 01 Jan 2004 +% +% See also: eeglab(), readbstorm() + +%123456789012345678901234567890123456789012345678901234567890123456789012 + +% Copyright (C) 2004, CNRS - UPR640, N'Diaye Karim, +% karim.ndiaye@chups.jussieu.Fr +% +% This program is free software; you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation; either version 2 of the License, or +% (at your option) any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with this program; if not, write to the Free Software +% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +% $Log: pop_readbstorm.m,v $ +% Revision 0.1 2004/01/01 +% First alpha version for EEGLAB release 4.301 +% + +function [EEG, command] = pop_readbstorm(datafile); + +EEG = []; +command = ''; +if nargin < 1 + % ask user + try + cd('D:\ndiaye\data\studies\'); + catch + end + [filename, filepath] = uigetfile('*_data_*.mat', 'Choose a datafile in a DS folder -- pop_readbstorm()'); + drawnow; + if filename == 0 return; end; + datafile = [filepath filename]; +end; + +% load data +% --------- +EEG = eeg_emptyset; +[study channel data dsfolder] = readbstorm(datafile); + +EEG.filename = study.Name +EEG.filepath = dsfolder; +EEG.setname = 'BrainStorm data'; +EEG.nbchan = length(channel); +EEG.srate = 1/mean(diff(data(1).Time)); +EEG.trials = length(data); +EEG.pnts = length(data(1).Time); +EEG.times = data(1).Time; +EEG.xmin = EEG.times(1); % huh?? +EEG.xmax = EEG.times(end); % huh?? +%Caution! CELL2MAT is overloaded in EEGLAB environment +fprintf('Importing data...(may take a while)') +EEG.data=zeros([size(data(1).F,1) size(data(1).F,2) EEG.trials]); +for i=1:EEG.trials + EEG.data(:,:,i)=data(i).F; +end +EEG.comments = study.Session; +EEG.ref = ['common']; + +fprintf('Importing channel location information') +for i=1:length(channel) + eloc(i).X=channel(i).Loc(1); + eloc(i).Y=channel(i).Loc(2); + eloc(i).Z=channel(i).Loc(3); + eloc(i).labels=channel(i).Name; +end +EEG.chanlocs=convertlocs(eloc, 'cart2all') + +EEG = eeg_checkset(EEG); +command = sprintf('EEG = pop_readbstorm(''%s'');', dsfolder); + +return; + + + +% importing the events +% -------------------- +if ~isempty(Eventdata) + orinbchans = EEG.nbchan; + for index = size(Eventdata,1):-1:1 + EEG = pop_chanevent( EEG, orinbchans-size(Eventdata,1)+index, 'edge', 'leading', ... + 'delevent', 'off', 'typename', Head.eventcode(index,:), ... + 'nbtype', 1, 'delchan', 'on'); + end; +end; +return + +%EEG = +% +% setname: 'Continuous EEG Data epochs' +% filename: 'eeg_demo_squareepochs.set' +% filepath: '' +% pnts: 384 +% nbchan: 32 +% trials: 80 +% srate: 128 +% xmin: -1 +% xmax: 1.9922 +% data: [32x384x80 single] +% icawinv: [] +% icasphere: [] +% icaweights: [] +% icaact: [] +% event: [1x157 struct] +% epoch: [1x80 struct] +% chanlocs: [1x32 struct] +% comments: [9x769 char] +% averef: 'no' +% rt: [] +% eventdescription: {[2x29 char] [2x63 char] [2x36 char] ''} +% epochdescription: {} +% specdata: [] +% specicaact: [] +% reject: [1x1 struct] +% stats: [1x1 struct] +% splinefile: [] +% ref: 'averef' +% history: [11x108 char] +% times: [1x384 double] + +% EEG.event +% 1x157 struct array with fields: +% type: 'square' or 'rt' +% position: [1] or [2] +% latency: in ms from the beginning of the continuous recording +% epoch: the epoch in which the event falls (indexed from 1) + +% EEG.epoch +% 1x80 struct array with fields: +% event: the index of the events that fall in that one epoch +% eventlatency: the latency of each event +% eventposition: the position of each event +% eventtype: the type of each event + + diff --git a/eeglab/plugins/bstorm/v1/readbstorm.m b/eeglab/plugins/bstorm/v1/readbstorm.m new file mode 100644 index 0000000..2f45468 --- /dev/null +++ b/eeglab/plugins/bstorm/v1/readbstorm.m @@ -0,0 +1,106 @@ +% readbstorm() - read BrainStorm datafiles +% +% Usage: +% >> [study channel data dsfolder] = readbstorm(datafile,device) +% +% Required Input: +% datafile = Data file within a DS folder +% (all data_*_[1..N] will be imported) +% +% Optional Input: +% device = ['MEG'|'EEG'|'EEG+MEG'|'ALL'] +% +% Outputs: +% study = study info +% channel = sensor positions +% data = cell array of data (one cell by trial) +% +% Author: Karim N'Diaye, CNRS-UPR640, 01 Jan 2004 +% +% See also: +% POP_READBSTORM, EEGLAB + +%123456789012345678901234567890123456789012345678901234567890123456789012 + +% Copyright (C) 2004, CNRS - UPR640, N'Diaye Karim, +% karim.ndiaye@chups.jussieu.Fr +% +% This program is free software; you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation; either version 2 of the License, or +% (at your option) any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with this program; if not, write to the Free Software +% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +% $Log: readegi.m,v $ +% Revision 0.1 2004/01/01 +% First alpha version for EEGLAB release 4.301 + +function [Study,Channel,Data,dsfolder] = readbstorm(datafile,device) + +if nargin < 1 + help(mfilename); + return; +end; + +[dsfolder, filename]=fileparts(datafile) +studyname=strtok(filename,'_') +p=findstr(filename, '_') +database=filename(1:p(3)) +prev_dir=cd; +cd(dsfolder) +Study=load([studyname '_brainstormstudy']); +Channel=getfield(load([studyname '_channel']), 'Channel'); + +%Importing Electrodes to EEGLAB format +channelflags=zeros(length(Channel),1); +if nargin <2 + DEVICES={'EEG', 'MEG', 'EEG+MEG', 'ALL'}; + [s,v] = listdlg('PromptString','Select a device:',... + 'SelectionMode','single',... + 'ListString',{'EEG', 'MEG', 'EEG+MEG'}); + if all(v) + switch(s) + case 1 + device={'EEG'} + case 2, device={'MEG'} + case 3, device = {'EEG', 'MEG'} + end + end +end +for i=1:length(device) + channelflags=channelflags + strcmp(device{i},{Channel.Type}'); +end +%channelflags(strmatch('EEG', {Channel.Type}))=1; +%channelflags(strmatch('MEG', {Channel.Type}))=1; + +Channel=Channel(find(channelflags)); + +i=1; +%datafilename=sprintf('%s%d.mat', database , i); +f=dir(sprintf('%s*.mat', database)); +f=strvcat({f.name}); +f=f(:,length(database)+1:end); +trials=str2num(char(strrep(cellstr(f), '.mat', ''))); +trials=sort(trials); + +h = waitbar(0,'Importing Datafiles. Please wait...'); +for i=1:length(trials) + waitbar(i/length(trials),h) + datafilename=sprintf('%s%d.mat', database , trials(i)); + fprintf('Importing %s\n', datafilename) + Data(i)=load(datafilename); + %channelflag=channelflags & Data(i).ChannelFlag; + channelflag=channelflags; + Data(i).F=Data(i).F(find(channelflag),:); +end +close(h) + +return diff --git a/eeglab/plugins/bstorm1.01/bstormchannels2chanlocs.m b/eeglab/plugins/bstorm1.01/bstormchannels2chanlocs.m new file mode 100644 index 0000000..a9b1d35 --- /dev/null +++ b/eeglab/plugins/bstorm1.01/bstormchannels2chanlocs.m @@ -0,0 +1,67 @@ +% bstormchannels2chanlocs() - Converts brainstorm channels to EEGLAB chanlocs structure +% +% Usage: +% >> [chanlocs channelflags] = bstormchannels2chanlocs( Channel , device) +% +% Required Input: +% Channel = Brainstorm Channel structure +% (e.g. read from a study_channel.mat file) +% +% Optional Input: +% device = ['MEG'|'EEG'|'EEG+MEG'|'ALL'] +% +% Outputs: +% channelocs = N sensor positions +% channelflags = [Nx1] array of boolean, true if channel was imported +% +% Author: Karim N'Diaye, CNRS-UPR640, 01 Jan 2005 +% +% See also: +% POP_READBSTORM, EEGLAB + +% $Log: bstormchannels2chanlocs.m,v $ +% Revision 1.01 2005/02/01 00:07:38 knd +% pop_chanedit compatible (no sph_phi/theta_besa fields) + + +function [ chanlocs, channelflags ] = bstormchannels2chanlocs( Channel , device) +channelflags=zeros(length(Channel),1); +if nargin <2 + DEVICES={'EEG', 'MEG', 'EEG+MEG', 'ALL'}; + [s,v] = listdlg('PromptString','Select a device:',... + 'SelectionMode','single',... + 'ListString',{'EEG', 'MEG', 'EEG+MEG'}); + if all(v) + switch(s) + case 1 + device={'EEG'} + case 2, device={'MEG'} + case 3, device = {'EEG', 'MEG'} + end + end +end +if not(iscell(device)) + device={device}; +end +for i=1:length(device) + channelflags=channelflags + strcmp(device{i},{Channel.Type}'); +end +%channelflags(strmatch('EEG', {Channel.Type}))=1; +%channelflags(strmatch('MEG', {Channel.Type}))=1; + +Channel=Channel(find(channelflags)); + +for i=1:length(Channel) + eloc(i).X=Channel(i).Loc(1); + eloc(i).Y=Channel(i).Loc(2); + eloc(i).Z=Channel(i).Loc(3); + eloc(i).labels=Channel(i).Name; +end +% This ouptuts fields sph_theta_besa which are not compatible with +% pop_chanedit function (Revision 1.111) +% chanlocs=convertlocs(eloc, 'cart2all') +% + +chanlocs=pop_chanedit(eloc, 'convert', 'cart2topo', 'convert', 'cart2sph'); + +return diff --git a/eeglab/plugins/bstorm1.01/eegplugin_bstorm.m b/eeglab/plugins/bstorm1.01/eegplugin_bstorm.m new file mode 100644 index 0000000..2c1e4be --- /dev/null +++ b/eeglab/plugins/bstorm1.01/eegplugin_bstorm.m @@ -0,0 +1,69 @@ +% eegplugin_bstorm() - EEGLAB plugin for importing data from BrainStorm toolbox +% +% Usage: +% >> eegplugin_bstorm(fig, try_strings, catch_strings); +% +% Inputs: +% fig - [integer] EEGLAB figure +% try_strings - [struct] "try" strings for menu callbacks. +% catch_strings - [struct] "catch" strings for menu callbacks. +% +% Create a plugin: +% For more information on how to create an EEGLAB plugin see the +% help message of eegplugin_besa() or visit http://www.sccn.ucsd.edu/eeglab/contrib.html +% +% Author: Karim N'Diaye, CNRS-UPR640, 01 Feb 2005 +% +% See also: eeglab(), readbstorm() + +%123456789012345678901234567890123456789012345678901234567890123456789012 + +% Copyright (C) 2003 Arnaud Delorme, Salk Institute, arno@salk.edu +% +% This program is free software; you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation; either version 2 of the License, or +% (at your option) any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with this program; if not, write to the Free Software +% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +function eegplugin_bstorm( fig, try_strings, catch_strings); + +if nargin < 3 + error('eegplugin_bstorm requires 3 arguments'); +end; + +% add bstrom folder to path +% ------------------------- +if ~exist('pop_bstorm') + p = which('eegplugin_bstorm'); + p = p(1:findstr(p,'eegplugin_bstorm.m')-1); + addpath([ p ] ); +end; + +% find import data menu +% --------------------- +filemenu = findobj(fig, 'label', 'File'); +neuromenu = findobj(filemenu, 'tag', 'import data'); + +% e_try = 'try, if exist(''h'') == 1, clear h; disp(''EEGLAB note: variable h cleared''); end;'; +% e_catch = 'catch, errordlg2(lasterr, ''EEGLAB error''); LASTCOM= ''''; clear EEGTMP; end;'; +% nocheck = e_try; +% e_catch = 'catch, errordlg2(lasterr, ''EEGLAB error''); LASTCOM= ''''; clear EEGTMP; end;'; +% storenewcall = '[ALLEEG EEG CURRENTSET LASTCOM] = pop_newset(ALLEEG, EEG, CURRENTSET); h(LASTCOM);'; +% e_newnonempty = [e_catch 'h(LASTCOM, EEG); if ~isempty(LASTCOM) & ~isempty(EEGTMP), EEG = EEGTMP;' storenewcall 'disp(''Done.''); end; clear EEGTMP; eeglab(''redraw'');']; +% uimenu(neuromenu, 'Tag', 'bstormCmd', 'Label', 'From BrainStorm DS folder', 'CallBack', [ nocheck '[EEGTMP LASTCOM]= pop_readbstorm;' e_newnonempty ], 'Separator', 'on'); + +% menu callbacks +% -------------- +combio = [ try_strings.no_check '[EEG LASTCOM] = pop_readbstorm;' catch_strings.new_and_hist ]; +uimenu(neuromenu, 'Tag', 'bstormCmd', 'Label', 'From BrainStorm DS folder', 'CallBack', combio, 'Separator', 'on'); + +return \ No newline at end of file diff --git a/eeglab/plugins/bstorm1.01/pop_readbstorm.m b/eeglab/plugins/bstorm1.01/pop_readbstorm.m new file mode 100644 index 0000000..db450af --- /dev/null +++ b/eeglab/plugins/bstorm1.01/pop_readbstorm.m @@ -0,0 +1,161 @@ +% pop_readbstorm() - load BrainStorm Data files (pop out window if no arguments). +% +% Usage: +% >> EEG = pop_readbstorm; % a window pops up +% >> EEG = pop_readbstorm( dsfolder ); +% +% Inputs: +% datafile - A datafile within a DS folder containing BrainStorm datafiles +% +% Outputs: +% EEG - EEGLAB data structure +% +% Author: Karim N'Diaye, CNRS-UPR640, 01 Jan 2004 +% +% See also: eeglab(), readbstorm() + +%123456789012345678901234567890123456789012345678901234567890123456789012 + +% Copyright (C) 2004, CNRS - UPR640, N'Diaye Karim, +% karim.ndiaye@chups.jussieu.Fr +% +% This program is free software; you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation; either version 2 of the License, or +% (at your option) any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with this program; if not, write to the Free Software +% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +% $Log: pop_readbstorm.m,v $ +% Revision 0.1 2004/01/01 +% First alpha version for EEGLAB release 4.301 +% + +function [EEG, command] = pop_readbstorm(datafile); + +EEG = []; +command = ''; +if nargin < 1 + % ask user + try + %cd('..\..\data\studies\div\T01\div-d1-f.ds'); + catch + end + [filename, filepath] = uigetfile('*_data_*.mat', 'Choose a datafile in a DS folder -- pop_readbstorm()'); + drawnow; + if filename == 0 return; end; + datafile = [filepath filename]; +end; + +% load data +% --------- +EEG = eeg_emptyset; +[study EEG.chanlocs data dsfolder] = readbstorm(datafile); + +EEG.filename = study.Name +EEG.filepath = dsfolder; +EEG.setname = 'BrainStorm data'; +EEG.nbchan = length(EEG.chanlocs); +EEG.srate = 1/mean(diff(data(1).Time)); +EEG.trials = length(data); +EEG.pnts = length(data(1).Time); +EEG.times = data(1).Time; +EEG.xmin = EEG.times(1); % huh?? +EEG.xmax = EEG.times(end); % huh?? +%Caution! CELL2MAT is overloaded in EEGLAB environment +fprintf('Importing data...(may take a while)') +EEG.data=zeros([size(data(1).F,1) size(data(1).F,2) EEG.trials]); +for i=1:EEG.trials + EEG.data(:,:,i)=data(i).F; +end +EEG.comments = study.Session; +EEG.ref = ['common']; + + +%EEG=pop_epoch(EEG, struct('epoc',1, 'type', '', 'latency', -EEG.xmin*EEG.srate+1)) + +% EEG = eeg_checkset(EEG, 'eventconsistency'); +% EEG = eeg_checkset(EEG, 'makeur'); + +EEG = eeg_checkset(EEG); +command = sprintf('EEG = pop_readbstorm(''%s'');', dsfolder); + +EEG.event.type='TimeZero'; +EEG.event.latency = 0; +EEG.epoch.event=[1]; +EEG.epoch.eventlatency={EEG.event(1).latency}; +EEG.epoch.eventtype={EEG.event(1).type}; + +EEG = eeg_checkset(EEG); + +return; + + + +% importing the events +% -------------------- +if ~isempty(Eventdata) + orinbchans = EEG.nbchan; + for index = size(Eventdata,1):-1:1 + EEG = pop_chanevent( EEG, orinbchans-size(Eventdata,1)+index, 'edge', 'leading', ... + 'delevent', 'off', 'typename', Head.eventcode(index,:), ... + 'nbtype', 1, 'delchan', 'on'); + end; +end; +return + +%EEG = +% +% setname: 'Continuous EEG Data epochs' +% filename: 'eeg_demo_squareepochs.set' +% filepath: '' +% pnts: 384 +% nbchan: 32 +% trials: 80 +% srate: 128 +% xmin: -1 +% xmax: 1.9922 +% data: [32x384x80 single] +% icawinv: [] +% icasphere: [] +% icaweights: [] +% icaact: [] +% event: [1x157 struct] +% epoch: [1x80 struct] +% chanlocs: [1x32 struct] +% comments: [9x769 char] +% averef: 'no' +% rt: [] +% eventdescription: {[2x29 char] [2x63 char] [2x36 char] ''} +% epochdescription: {} +% specdata: [] +% specicaact: [] +% reject: [1x1 struct] +% stats: [1x1 struct] +% splinefile: [] +% ref: 'averef' +% history: [11x108 char] +% times: [1x384 double] + +% EEG.event +% 1x157 struct array with fields: +% type: 'square' or 'rt' +% position: [1] or [2] +% latency: in ms from the beginning of the continuous recording +% epoch: the epoch in which the event falls (indexed from 1) + +% EEG.epoch +% 1x80 struct array with fields: +% event: the index of the events that fall in that one epoch +% eventlatency: the latency of each event +% eventposition: the position of each event +% eventtype: the type of each event + + diff --git a/eeglab/plugins/bstorm1.01/readbstorm.m b/eeglab/plugins/bstorm1.01/readbstorm.m new file mode 100644 index 0000000..8630ed5 --- /dev/null +++ b/eeglab/plugins/bstorm1.01/readbstorm.m @@ -0,0 +1,107 @@ +% readbstorm() - read BrainStorm datafiles +% +% Usage: +% >> [study channel data dsfolder] = readbstorm(datafile,device) +% +% Required Input: +% datafile = A folder (all data_*_[1..N] will be imported) +% OR a data file within a DS folder +% (only this file will be imported) +% +% Optional Input: +% device = ['MEG'|'EEG'|'EEG+MEG'|'ALL'] +% +% Outputs: +% study = study info +% channel = sensor positions +% data = cell array of data (one cell by trial) +% +% Author: Karim N'Diaye, CNRS-UPR640, 01 Feb 2005 +% +% See also: +% POP_READBSTORM, EEGLAB + +%123456789012345678901234567890123456789012345678901234567890123456789012 + +% Copyright (C) 2004, CNRS - UPR640, N'Diaye Karim, +% karim.ndiaye@chups.jussieu.Fr +% +% This program is free software; you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation; either version 2 of the License, or +% (at your option) any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with this program; if not, write to the Free Software +% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +% $Log: readbstorm.m,v $ +% Revision 1.01 2005/02/01 00:07:38 knd +% now allows single data file importation + +function [Study,chanlocs,Data,dsfolder] = readbstorm(datafile,device) + +if nargin < 1 + help(mfilename); + return; +end; + +[dsfolder, filename]=fileparts(datafile) +studyname=strtok(filename,'_') +p=findstr(filename, '_') +database=filename(1:p(3)) +prev_dir=cd; +cd(dsfolder) +Study=load([studyname '_brainstormstudy']); + +fprintf('Importing channel location information') +Channel=getfield(load([studyname '_channel']), 'Channel'); +[chanlocs,channelflags]=bstormchannels2chanlocs( Channel ); + + +if exist(datafile,'file') + datafilename{1}=datafile; +elseif exist(datafile,'dir') + f=dir(sprintf('%s*.mat', database)); + f=strvcat({f.name}); + f=f(:,length(database)+1:end); + trials=str2double(strrep(cellstr(f), '.mat', '')); + trials=sort(trials(not(isnan(trials)))); + for i=1:length(trials) + datafilename{i}=sprintf('%s%d.mat', database , trials(i)); + end +end + +i=1; +%datafilename=sprintf('%s%d.mat', database , i); + +h = waitbar(0,'Importing Datafiles. Please wait...'); +for i=1:length(datafilename) + waitbar(i/length(datafilename),h) + fprintf('Importing %s\n', datafilename{i}) + d=load(datafilename{i}); + if exist('Data') + dfields=fieldnames(d); + Data(i)=Data(i-1); + for j=1:length(dfields) + Data(i)=setfield(Data(i), dfields{j}, getfield(d, dfields{j})); + end + else + Data=d; + end + %channelflag=channelflags & Data(i).ChannelFlag; + channelflag=channelflags; + if iscell(Data(i).F) + Data(i).F{1}=Data(i).F{1}(find(channelflag),:); + else + Data(i).F=Data(i).F(find(channelflag),:); + end +end +close(h) + +return diff --git a/eeglab/plugins/bstorm1.01/v1/pop_readbstorm.m b/eeglab/plugins/bstorm1.01/v1/pop_readbstorm.m new file mode 100644 index 0000000..3d752e1 --- /dev/null +++ b/eeglab/plugins/bstorm1.01/v1/pop_readbstorm.m @@ -0,0 +1,156 @@ +% pop_readbstorm() - load BrainStorm Data files (pop out window if no arguments). +% +% Usage: +% >> EEG = pop_readbstorm; % a window pops up +% >> EEG = pop_readbstorm( dsfolder ); +% +% Inputs: +% datafile - A datafile within a DS folder containing BrainStorm datafiles +% +% Outputs: +% EEG - EEGLAB data structure +% +% Author: Karim N'Diaye, CNRS-UPR640, 01 Jan 2004 +% +% See also: eeglab(), readbstorm() + +%123456789012345678901234567890123456789012345678901234567890123456789012 + +% Copyright (C) 2004, CNRS - UPR640, N'Diaye Karim, +% karim.ndiaye@chups.jussieu.Fr +% +% This program is free software; you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation; either version 2 of the License, or +% (at your option) any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with this program; if not, write to the Free Software +% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +% $Log: pop_readbstorm.m,v $ +% Revision 0.1 2004/01/01 +% First alpha version for EEGLAB release 4.301 +% + +function [EEG, command] = pop_readbstorm(datafile); + +EEG = []; +command = ''; +if nargin < 1 + % ask user + try + cd('D:\ndiaye\data\studies\'); + catch + end + [filename, filepath] = uigetfile('*_data_*.mat', 'Choose a datafile in a DS folder -- pop_readbstorm()'); + drawnow; + if filename == 0 return; end; + datafile = [filepath filename]; +end; + +% load data +% --------- +EEG = eeg_emptyset; +[study channel data dsfolder] = readbstorm(datafile); + +EEG.filename = study.Name +EEG.filepath = dsfolder; +EEG.setname = 'BrainStorm data'; +EEG.nbchan = length(channel); +EEG.srate = 1/mean(diff(data(1).Time)); +EEG.trials = length(data); +EEG.pnts = length(data(1).Time); +EEG.times = data(1).Time; +EEG.xmin = EEG.times(1); % huh?? +EEG.xmax = EEG.times(end); % huh?? +%Caution! CELL2MAT is overloaded in EEGLAB environment +fprintf('Importing data...(may take a while)') +EEG.data=zeros([size(data(1).F,1) size(data(1).F,2) EEG.trials]); +for i=1:EEG.trials + EEG.data(:,:,i)=data(i).F; +end +EEG.comments = study.Session; +EEG.ref = ['common']; + +fprintf('Importing channel location information') +for i=1:length(channel) + eloc(i).X=channel(i).Loc(1); + eloc(i).Y=channel(i).Loc(2); + eloc(i).Z=channel(i).Loc(3); + eloc(i).labels=channel(i).Name; +end +EEG.chanlocs=convertlocs(eloc, 'cart2all') + +EEG = eeg_checkset(EEG); +command = sprintf('EEG = pop_readbstorm(''%s'');', dsfolder); + +return; + + + +% importing the events +% -------------------- +if ~isempty(Eventdata) + orinbchans = EEG.nbchan; + for index = size(Eventdata,1):-1:1 + EEG = pop_chanevent( EEG, orinbchans-size(Eventdata,1)+index, 'edge', 'leading', ... + 'delevent', 'off', 'typename', Head.eventcode(index,:), ... + 'nbtype', 1, 'delchan', 'on'); + end; +end; +return + +%EEG = +% +% setname: 'Continuous EEG Data epochs' +% filename: 'eeg_demo_squareepochs.set' +% filepath: '' +% pnts: 384 +% nbchan: 32 +% trials: 80 +% srate: 128 +% xmin: -1 +% xmax: 1.9922 +% data: [32x384x80 single] +% icawinv: [] +% icasphere: [] +% icaweights: [] +% icaact: [] +% event: [1x157 struct] +% epoch: [1x80 struct] +% chanlocs: [1x32 struct] +% comments: [9x769 char] +% averef: 'no' +% rt: [] +% eventdescription: {[2x29 char] [2x63 char] [2x36 char] ''} +% epochdescription: {} +% specdata: [] +% specicaact: [] +% reject: [1x1 struct] +% stats: [1x1 struct] +% splinefile: [] +% ref: 'averef' +% history: [11x108 char] +% times: [1x384 double] + +% EEG.event +% 1x157 struct array with fields: +% type: 'square' or 'rt' +% position: [1] or [2] +% latency: in ms from the beginning of the continuous recording +% epoch: the epoch in which the event falls (indexed from 1) + +% EEG.epoch +% 1x80 struct array with fields: +% event: the index of the events that fall in that one epoch +% eventlatency: the latency of each event +% eventposition: the position of each event +% eventtype: the type of each event + + diff --git a/eeglab/plugins/bstorm1.01/v1/readbstorm.m b/eeglab/plugins/bstorm1.01/v1/readbstorm.m new file mode 100644 index 0000000..2f45468 --- /dev/null +++ b/eeglab/plugins/bstorm1.01/v1/readbstorm.m @@ -0,0 +1,106 @@ +% readbstorm() - read BrainStorm datafiles +% +% Usage: +% >> [study channel data dsfolder] = readbstorm(datafile,device) +% +% Required Input: +% datafile = Data file within a DS folder +% (all data_*_[1..N] will be imported) +% +% Optional Input: +% device = ['MEG'|'EEG'|'EEG+MEG'|'ALL'] +% +% Outputs: +% study = study info +% channel = sensor positions +% data = cell array of data (one cell by trial) +% +% Author: Karim N'Diaye, CNRS-UPR640, 01 Jan 2004 +% +% See also: +% POP_READBSTORM, EEGLAB + +%123456789012345678901234567890123456789012345678901234567890123456789012 + +% Copyright (C) 2004, CNRS - UPR640, N'Diaye Karim, +% karim.ndiaye@chups.jussieu.Fr +% +% This program is free software; you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation; either version 2 of the License, or +% (at your option) any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with this program; if not, write to the Free Software +% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +% $Log: readegi.m,v $ +% Revision 0.1 2004/01/01 +% First alpha version for EEGLAB release 4.301 + +function [Study,Channel,Data,dsfolder] = readbstorm(datafile,device) + +if nargin < 1 + help(mfilename); + return; +end; + +[dsfolder, filename]=fileparts(datafile) +studyname=strtok(filename,'_') +p=findstr(filename, '_') +database=filename(1:p(3)) +prev_dir=cd; +cd(dsfolder) +Study=load([studyname '_brainstormstudy']); +Channel=getfield(load([studyname '_channel']), 'Channel'); + +%Importing Electrodes to EEGLAB format +channelflags=zeros(length(Channel),1); +if nargin <2 + DEVICES={'EEG', 'MEG', 'EEG+MEG', 'ALL'}; + [s,v] = listdlg('PromptString','Select a device:',... + 'SelectionMode','single',... + 'ListString',{'EEG', 'MEG', 'EEG+MEG'}); + if all(v) + switch(s) + case 1 + device={'EEG'} + case 2, device={'MEG'} + case 3, device = {'EEG', 'MEG'} + end + end +end +for i=1:length(device) + channelflags=channelflags + strcmp(device{i},{Channel.Type}'); +end +%channelflags(strmatch('EEG', {Channel.Type}))=1; +%channelflags(strmatch('MEG', {Channel.Type}))=1; + +Channel=Channel(find(channelflags)); + +i=1; +%datafilename=sprintf('%s%d.mat', database , i); +f=dir(sprintf('%s*.mat', database)); +f=strvcat({f.name}); +f=f(:,length(database)+1:end); +trials=str2num(char(strrep(cellstr(f), '.mat', ''))); +trials=sort(trials); + +h = waitbar(0,'Importing Datafiles. Please wait...'); +for i=1:length(trials) + waitbar(i/length(trials),h) + datafilename=sprintf('%s%d.mat', database , trials(i)); + fprintf('Importing %s\n', datafilename) + Data(i)=load(datafilename); + %channelflag=channelflags & Data(i).ChannelFlag; + channelflag=channelflags; + Data(i).F=Data(i).F(find(channelflag),:); +end +close(h) + +return diff --git a/eeglab/plugins/ctf/ds2brainstorm.m b/eeglab/plugins/ctf/ds2brainstorm.m new file mode 100644 index 0000000..ec1254a --- /dev/null +++ b/eeglab/plugins/ctf/ds2brainstorm.m @@ -0,0 +1,977 @@ +function [F,Channel,imegsens,ieegsens,iothersens,irefsens,grad_order_no,no_trials,filter,Time, RunTitle] = ds2brainstorm(ds_directory,VERBOSE,READRES,CHANNELS,TIME, NO_TRIALS); +%DS2BRAINSTORM +% [F,Channel,imegsens,ieegsens,iothersens,irefsens,grad_order_no,no_trials,filter,Time, RunTitle] +% ... = ds2brainstorm(ds_directory,VERBOSE,READRES,CHANNELS,TIME, NO_TRIALS); +% Reads CTF Systems Inc. Data and Resource file formats (4.1 MEG Data Format) +% +% This ALPHA version is optimized for small data sets only (e.g. averaged-trials data) +% +% INPUTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% DS_DIRECTORY: full path to the .ds directory containing the orginal data set +% +% VERBOSE = 1 : Toggle VERBOSE mode ON +% -> Activate screen display +% -> Save channel information in channel_file.txt +% -> Save coefficient information in coef_file.txt +% +% VERBOSE = 0 : Toggle VERBOSE mode OFF +% +%---> The rest of the input parameters are optionnal (more specific to data block extraction) +% +% READRES = 1: Read the original .res4 file and writes out a file called **_res4.mat in the .ds folder, which is a shortened version of the CTF resources file +% READRES = 0: Do not read the original .res4 file assuming it has been read previously. Read the **_res4.mat instead +% Set READRES to 0 when you want to extract another block of data after you have already read one block with READRES = 1. +% +% If READRES is left blank, the whole data are read - ATTENTION when data set is large - possible 'out of memory' issues +% +% If the optional fields below are left blank, ds2brainstorm only reads the res4 file and generates a *_res4.mat file in the current .ds folder +% with all useful information regarding EEG, MEG channels etc. for subsequent data extraction. +% +% CHANNELS: the indices of the channels to be extracted as in IEEGSENS, IMEGSENS or IREFSENS +% If you don't know what are the indices of these channels, run ds2brainstorm with READRES = 1 first. +% +% TIME: the time window to be extracted in seconds within a trial; ie a subset of Time, a vector saved in **_res4.mat (see above) +% +% NO_TRIALS: a vector containing the number(s) of the trial(s) to be extracted. +% +% Example; ds2brainstorm('tipouic.ds',1,0,[10:30],[-.5 3],2) +% .. extracts in VERBOSE mode from tipouic.ds, assuming tipouic_res4.mat was already created during a previous call to ds2brainstorm with READRES = 1 +% channels 10 to 30 (could be MEG or other -> check IEEGSENS and IMEGSENS first), between -0.5sec and 3sec, in trial 2. +% +% % +% OUTPUTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% CHANNEL - MEG,EEG, REFERENCE and OTHER channel information +% CHANNEL is a cell array of structures following the BrainStorm data format. +% F - Data Matrix: One sensor per row; one time sample per column +% IMEGSENS - Indices of the MEG data rows in F +% IEEGSENS - Indices of the EEG data rows in F +% IOTHERSENS - Indices of the OTHER data rows (not including reference coils) in F +% IREFSENS - Indices of the REFERENCE data rows in F +% GRAD_ORDER_NO - Order of the gradient correction to be taken into account in the forward model only +% NO_TRIALS - Number of trials in the .ds folder. +% FILTER - An array of structures; each structure containing information related to each filter +% TIME - A vector containing the time instants at every data sample +% RUNTITLE - A character array labelling the current run. +% +% Please refer to CTF Systems Inc. Technical Note #1 for details on DataSet file formats + +%-- Sylvain Baillet, Ph.D. / Denis Swchartz, Ph.D. +% Cognitive Neuroscience & Brain Imaging Laboratory - CNRS - Paris France +% MEG Center - Hopital de la Salpetriere - Paris - France. + +%-- $RCSfile: ds2brainstorm.m,v $ -- $Revision: 7 $ -- $Date: 11/28/01 8:20a $ +%-- Signal and Image Processing Institute +%-- University of Southern Califonia +%-- Los Angeles - USA +%-> with contributions from Line Garnero, Ph.D. and Antoine Ducorps +% MEG Center +% & +% Cognitive Neuroscience & Brain Imaging Laboratory - CNRS +% La Salpetriere Hospital, Paris, France + +% 14/08/03 - KND (kari n'diaye): Updated to include Virtual Channeln, lines 486-544 +% 11/28/00 - SB: Updated to read the 3rd-roder gradient information +% 03/09/01 - SB: Data block extraction +% 15/11/01 - SB: Extraction of the ADC Input channels in iothersens, combined with the STIM channel +% 19/11/01 - SB: Save iothersens indices in the _res4.mat file +%********************************************* +% +% Check input arguments +% + +if nargin == 2 + READRES = 1; +elseif nargin==5 % No Marker file and marker range are defined + MARKER_FILE = 0; + MARKER_RANGE = []; +elseif nargin==3 + MARKER_FILE = 0; + MARKER_RANGE = []; + CHANNELS = []; + TIME = []; + NO_TRIALS = []; +elseif nargin==1 + VERBOSE=0; + READRES = 1; + MARKER_FILE = 0; + MARKER_RANGE = []; + CHANNELS = []; + TIME = []; + NO_TRIALS = []; +end + +%% Checking files +current_dir=cd; +cd(ds_directory) +[path,rootname] = fileparts(ds_directory); +meg4file = [rootname,'.meg4']; +rec4file = [rootname,'.res4']; +res4_mat = [rootname,'_res4.mat']; + +if ~exist(rec4file,'file') | ~exist(meg4file,'file') + errordlg([rec4file ' or ' meg4file ' missing']) + return +end + +if ~exist(res4_mat,'file') + % Force the reading the original .res4 file but skip the reading of the file + READRES = 2; +end + +if VERBOSE + disp(['Working in...',ds_directory ]) + drawnow +end + + +%********************************************* + +if READRES == 1 | READRES == 2 % Read the .res4 file + + %% Define constants + MAX_COILS = 8; + MAX_BALANCING = 50; + SENSOR_LABEL = 31; + MAX_AVERAGE_BINS = 8; + + %***** nfSetUp **** Data Structure + gSetUp = struct('no_samples',[],'no_channels',[],'sample_rate',[],... + 'epoch_time',[],'no_trials',[],'preTrigPts',[],'no_trials_done',[],'no_trials_display',[],... + 'save_trials',[],'primaryTrigger',[],'secondaryTrigger',[],'triggerPolarityMask',[],... + 'trigger_mode',[],'accept_reject_Flag',[],'run_time_display',[],'zero_Head_Flag',[],... + 'artifact_mode',[]); + + nfSetUp = struct('nf_run_name','','nf_run_title','','nf_instruments','','nf_collectdescriptor','',... + 'nf_subject_id','','nf_operator','','nf_sensorFileName',''); + + GenRes4 = struct('appName','','dataOrigin','','dataDescription','',... + 'no_trials_avgd',[],'data_time',[],'data_date',[],'gSetUp',gSetUp,'nfSetUp',nfSetUp,'rdlen',[]); + + %********************************************* + + + %% Reading Resources------------------------------------------------------------------------ + [rec,message] = fopen(rec4file,'rb','s'); % Big-endian byte ordering + if rec < 0 + errordlg(message) + return + end + + % Read HEADER + header = fread(rec,8,'char')'; + if VERBOSE + disp([char(header), ' file format']) + drawnow + end + + + % Read nfSetUp + GenRes4.appName = char(fread(rec,256,'char')'); + GenRes4.dataOrigin = char(fread(rec,256,'char')'); + GenRes4.dataDescription = char(fread(rec,256,'char')'); + + GenRes4.no_trials_avgd = (fread(rec,1,'int16')'); + GenRes4.data_time = char(fread(rec,255,'char')'); + GenRes4.data_date = char(fread(rec,255,'char')'); + + gSetUp.no_samples = (fread(rec,1,'int32')'); + gSetUp.no_channels = (fread(rec,1,'int16')'); + + fseek(rec,ceil(ftell(rec)/8)*8,-1); + gSetUp.sample_rate = (fread(rec,1,'double')'); + fseek(rec,ceil(ftell(rec)/8)*8,-1); + gSetUp.epoch_time = (fread(rec,1,'double')'); + gSetUp.no_trials = (fread(rec,1,'int16')'); + fseek(rec,ceil(ftell(rec)/4)*4,-1); + gSetUp.preTrigPts = (fread(rec,1,'int32')'); + gSetUp.no_trials_done = (fread(rec,1,'int16')'); + gSetUp.no_trials_display = (fread(rec,1,'int16')'); + fseek(rec,ceil(ftell(rec)/4)*4,-1); + gSetUp.save_trials = (fread(rec,1,'int32')'); + gSetUp.primaryTrigger = char(fread(rec,1,'uchar')'); + gSetUp.secondaryTrigger = char(fread(rec,MAX_AVERAGE_BINS,'uchar')'); + gSetUp.triggerPolarityMask = char(fread(rec,1,'uchar')'); + + gSetUp.trigger_mode = (fread(rec,1,'int16')'); + fseek(rec,ceil(ftell(rec)/4)*4,-1); + gSetUp.accept_reject_Flag = (fread(rec,1,'int32')'); + gSetUp.run_time_display = (fread(rec,1,'int16')'); + fseek(rec,ceil(ftell(rec)/4)*4,-1); + gSetUp.zero_Head_Flag = (fread(rec,1,'int32')'); + fseek(rec,ceil(ftell(rec)/4)*4,-1); + gSetUp.artifact_mode = (fread(rec,1,'int32')'); + gSetUp.padding = (fread(rec,1,'int32')'); + + + nfSetUp.nf_run_name = char(fread(rec,32,'char')'); + nfSetUp.nf_run_title = char(fread(rec,256,'char')'); + + RunTitle = nfSetUp.nf_run_title ; + + nfSetUp.nf_instruments = char(fread(rec,32,'char')'); + nfSetUp.nf_collect_descriptor = char(fread(rec,32,'char')'); + nfSetUp.nf_subject_id = char(fread(rec,32,'char')'); + nfSetUp.nf_operator = char(fread(rec,32,'char')'); + % DBG: The following line generates a warning + warnstate=warning; + warning off + nfSetUp.nf_sensorFileName = char(fread(rec,60,'char')'); + warning(warnstate) + clear warnstate + + fseek(rec,ceil(ftell(rec)/4)*4,-1); + nfSetUp.rdlen = fread(rec,1,'int32')'; + + GenRes4.gSetUp = gSetUp; + GenRes4.nfSetUp = nfSetUp; + + %---------------------------------------------------------------------------------------- + + if VERBOSE + fprintf('----------------------------------- Header information ---------------------------\n') + fprintf('Collected %s starting at %s\n', GenRes4.data_date, GenRes4.data_time) + fprintf('Run Name: %s\n', nfSetUp.nf_run_name) + fprintf('Run Titl: %s\n', nfSetUp.nf_run_title) + fprintf('Col Desc: %s\n', nfSetUp.nf_collect_descriptor) + fprintf('Run Desc: %s\n', GenRes4.dataDescription) + fprintf('Operator: %s\n', nfSetUp.nf_operator) + fprintf('Subject : %s\n', nfSetUp.nf_subject_id) + fprintf('Channels: %d\n',gSetUp.no_channels) + fprintf('Samples : %d per trial\n', gSetUp.no_samples) + fprintf('Rate : %g samples/sec\n', gSetUp.sample_rate) + fprintf('Trials : %d (average of %d)\n', gSetUp.no_trials, GenRes4.no_trials_avgd) + fprintf('Duration: %g seconds/trial\n', gSetUp.epoch_time/gSetUp.no_trials) + fprintf('Pre-trig: %g samples\n', gSetUp.preTrigPts) + fprintf('Sensor file name : %s\n', nfSetUp.nf_sensorFileName) + ON_OFF = {'Off','On'}; + fprintf('Head zeroing: %s\n', ON_OFF{gSetUp.zero_Head_Flag+1}) + fprintf('----------------------------------- End Header information -----------------------\n') + end + + + %------------------------------------------------------------------------------- + + %----------------------------------READ FILTERS--------------------------------- + + fseek(rec,1844,-1); + + % Run Description + rundescript = char(fread(rec,nfSetUp.rdlen,'char')); + if VERBOSE + fprintf('\nRun Description: %s\n', rundescript ); + drawnow + end + + classType = {'CLASSERROR','BUTTERWORTH'}; + filtType = {'TYPERROR','LOWPASS','HIGHPASS','NOTCH'}; + + + % Number of filters + no_filters = fread(rec,1,'int16'); + if VERBOSE + fprintf('\n----------------------------------- Filter information ---------------------------\n') + fprintf('Number of filters: %d\n', no_filters) + drawnow + end + + % JCM fixes 10/26/00, each of these should be looped through, not read in bulk + % old + if(0) + filter.freq = fread(rec,no_filters,'double'); + filter.fClass = classType{fread(rec,no_filters,'int32')+1}; + filter.fType = filtType{fread(rec,no_filters,'int32')+1}; + filter.numParam = fread(rec,no_filters,'int16'); + + filter.params = []; + for fi = 1:no_filters + for p = 1: filter(fi).numParam + filter(fi).params= fread(rec,filter(fi).numParam,'double'); + end + end + else % new + [filter(1:no_filters)] = struct('freq',[],'fClass',[],'fType',[],'numParam',[],'params',[]); + for fi = 1:no_filters, + filter(fi).freq = fread(rec,1,'double'); + filter(fi).fClass = classType{fread(rec,1,'int32')+1}; + filter(fi).fType = filtType{fread(rec,1,'int32')+1}; + filter(fi).numParam = fread(rec,1,'int16'); + filter(fi).params= fread(rec,filter(fi).numParam,'double'); + end + end + + if VERBOSE + for fi = 1:no_filters + fprintf('Filter - %d\n',fi) + fprintf('->Frequency: \t%g\n',filter(fi).freq) + fprintf('->Class: \t%s\n',filter(fi).fClass) + fprintf('->Type: \t%s\n',filter(fi).fType) + fprintf('->Number of parameters: \t%d\n',filter(fi).numParam) + if ~isempty(filter(fi).params) + fprintf('->Parameter Value(s): \t%g\n',filter(fi).params) + end + end + + fprintf('\n------------------------------- End Filter Information ---------------------------\n') + + fprintf('\n------------------------------- Reading Channel Information.....------------------------------\n') + drawnow + end + + % Channel Names + for chan = 1:gSetUp.no_channels + channel_name{chan} = fread(rec,32,'char')'; + tmp = channel_name{chan}; + tmp(tmp>127) = 0; + tmp(tmp<0) = 0; + channel_name{chan} = char(strtok(tmp,char(0))); + end + + % Sensor Resources + CoilType = {'CIRCULAR','SQUARE','???'}; + + for chan = 1:gSetUp.no_channels + oldtell = ftell(rec); + SensorRes(chan).sensorTypeIndex = fread(rec,1,'int16'); + SensorRes(chan).originalRunNum = fread(rec,1,'int16'); + + id = fread(rec,1,'int32')+1; + + if isempty(id) + id = -1; + end + + if id > 3 | id <0 + id = 3; + end + + SensorRes(chan).coilShape = CoilType{id}; + + SensorRes(chan).properGain = fread(rec,1,'double'); + SensorRes(chan).qGain = fread(rec,1,'double'); + SensorRes(chan).ioGain= fread(rec,1,'double'); + SensorRes(chan).ioOffset = fread(rec,1,'double'); + SensorRes(chan).numCoils = fread(rec,1,'int16'); + SensorRes(chan).grad_order_no = fread(rec,1,'int16'); + + padding = fread(rec,1,'int32'); + + for coil = 1:MAX_COILS + SensorRes(chan).coilTbl(coil).position.x = fread(rec,1,'double'); + SensorRes(chan).coilTbl(coil).position.y = fread(rec,1,'double'); + SensorRes(chan).coilTbl(coil).position.z = fread(rec,1,'double'); + SensorRes(chan).coilTbl(coil).position.junk = fread(rec,1,'double'); + SensorRes(chan).coilTbl(coil).orient.x = fread(rec,1,'double'); + SensorRes(chan).coilTbl(coil).orient.y = fread(rec,1,'double'); + SensorRes(chan).coilTbl(coil).orient.z = fread(rec,1,'double'); + SensorRes(chan).coilTbl(coil).orient.junk = fread(rec,1,'double'); + SensorRes(chan).coilTbl(coil).numturns = fread(rec,1,'int16'); + padding = fread(rec,1,'int32'); + padding = fread(rec,1,'int16'); + SensorRes(chan).coilTbl(coil).area = fread(rec,1,'double'); + end + + for coil = 1:MAX_COILS + SensorRes(chan).HdcoilTbl(coil).position.x = fread(rec,1,'double'); + SensorRes(chan).HdcoilTbl(coil).position.y = fread(rec,1,'double'); + SensorRes(chan).HdcoilTbl(coil).position.z = fread(rec,1,'double'); + SensorRes(chan).HdcoilTbl(coil).position.junk = fread(rec,1,'double'); + SensorRes(chan).HdcoilTbl(coil).orient.x = fread(rec,1,'double'); + SensorRes(chan).HdcoilTbl(coil).orient.y = fread(rec,1,'double'); + SensorRes(chan).HdcoilTbl(coil).orient.z = fread(rec,1,'double'); + SensorRes(chan).HdcoilTbl(coil).orient.junk = fread(rec,1,'double'); + SensorRes(chan).HdcoilTbl(coil).numturns = fread(rec,1,'int16'); + padding = fread(rec,1,'int32'); + padding = fread(rec,1,'int16'); + SensorRes(chan).HdcoilTbl(coil).area = fread(rec,1,'double'); + end + + end + + if VERBOSE + SensorNames ={'Ref Magnetometer','Ref Gradiometer' ,'' , '' , '' ,'MEG Sensor','' , '', '','EEG Sensor','ADC Input','Stimulation input',''}; % CHEAT - added the 13th '' ofor testing purposes + % SensorNames ={'Ref Magnetometer','Ref Gradiometer' ,'' , '' , '' ,'MEG Sensor','' , '', '','EEG Sensor','ADC Input','Stimulation input'); %OLD VERSION + + [channel_file, message] = fopen('channel_file.txt','wt+'); + if channel_file < 0 + errordlg(['Aborted - You need to the permission to write in ',ds_directory]); + return + end + for chan = 1:gSetUp.no_channels + fprintf(channel_file,'%s: sensor type Index %d (%s), %d coil(s), shape %s,Gains: proper %g q %g io %g, offset %g, gradient order %d\n',... + channel_name{chan}, SensorRes(chan).sensorTypeIndex, SensorNames{SensorRes(chan).sensorTypeIndex+1}, SensorRes(chan).numCoils, SensorRes(chan).coilShape,... + SensorRes(chan).properGain , SensorRes(chan).qGain , SensorRes(chan).ioGain, SensorRes(chan).ioOffset,SensorRes(chan).grad_order_no ) ; + + if SensorRes(chan).numCoils > 0 + fprintf(channel_file,'Dw:'); + PrintCoilPos(channel_file,SensorRes(chan).coilTbl, SensorRes(chan).numCoils ); + fprintf(channel_file,'Hd:'); + PrintCoilPos(channel_file,SensorRes(chan).HdcoilTbl, SensorRes(chan).numCoils ); + end + end + + fprintf('Channel Information written to channel_file.txt\n'); + + fprintf('\n------------------------------- End Channel Information --------------------------\n') + end + + disp('-------------------Converting Channel Information.....----------------------------') + + + nchan= length(channel_name); % Number of channels + + % Explode data according to channel type + SensorNames ={'Ref Magnetometer','Ref Gradiometer' ,'' , '' , '' ,'MEG Sensor','' , '', '','EEG Sensor','ADC Input','Stimulation input'}; + imegsens = find([SensorRes.sensorTypeIndex] == 5); % Indices of MEG sensors + ieegsens = find([SensorRes.sensorTypeIndex] == 9); % Indices of EEG sensors + iothersens = [find([SensorRes.sensorTypeIndex] == 11),find([SensorRes.sensorTypeIndex] == 10)]; % Indices of OTHER sensors ('Stimulation input' and 'ADC') + irefsens = find([SensorRes.sensorTypeIndex] == 0); % Reference Channels + irefsens = [irefsens,find([SensorRes.sensorTypeIndex] == 1)]; % Reference Channels + + + ieeg = 0; imeg = 0; + iother = 0; + iother2 = 0; + eegID = []; + otherID = []; + other2ID = []; + megID = []; + + iref = 0; + refID = []; + + chaneeg = [];% Index of EEG channels + chanmeg = [];chanother = []; + SensorNames ={'Ref Magnetometer','Ref Gradiometer' ,'' , '' , '' ,'MEG Sensor','' , '', '','EEG Sensor','ADC Input','Stimulation input',''}; % Added a 13th channel type, don't know why + + [Channel(1:nchan)] = deal(struct('Loc',[],'Orient',[],'Comment',[],'Weight',[],'Type',[],'RefChannel',[],'Gcoef',[],'Name','')); + + for chan = 1:nchan + switch SensorRes(chan).sensorTypeIndex + case {0,1} %'References coils + iref = iref+1; + MAX_COILS = 2; % Take only into account the 2 first coils for every sensor + tmp = [SensorRes(chan).HdcoilTbl(:).position]; + tmpx = [tmp(1:MAX_COILS).x]/100; % In meters + tmpy = [tmp(1:MAX_COILS).y]/100; + tmpz = [tmp(1:MAX_COILS).z]/100; + + Channel(chan).Loc = ... + [tmpx;tmpy;tmpz]; + + tmp = [SensorRes(chan).HdcoilTbl(:).orient]; + tmpx = [tmp(1:MAX_COILS).x]; + tmpy = [tmp(1:MAX_COILS).y]; + tmpz = [tmp(1:MAX_COILS).z]; + + Channel(chan).Orient = ... + [tmpx;tmpy;tmpz]; + + + % Reorient along the outward pointing normal + loc = Channel(chan).Loc'; + ps1=sum((loc(1,:).*Channel(chan).Orient(:,1)')')'; + ps2=sum((loc(2,:).*Channel(chan).Orient(:,2)')')'; + Channel(chan).Orient(:,1) = (sign(ps1)*ones(1,3).*Channel(chan).Orient(:,1)')'; + Channel(chan).Orient(:,2) = (sign(ps2)*ones(1,3).*Channel(chan).Orient(:,2)')'; + + Channel(chan).Comment = [SensorNames{SensorRes(chan).sensorTypeIndex+1},' ',int2str(chan)]; + Channel(chan).Type = SensorNames{SensorRes(chan).sensorTypeIndex+1} ; + if isempty(deblank(Channel(chan).Type)) + Channel(chan).Type = 'REF'; + end + + chanmeg = [chanmeg,chan]; + refID(iref) = chan; + Channel(chan).Weight = [1 -1] ; + + case 5 % MEG (including Virtual !) + + if SensorRes(chan).coilTbl(1).area > 0% Not a virtual one... I guess ! + + imeg = imeg+1; + MAX_COILS = 2; % Take only into account the 2 first coils for every sensor + tmp = [SensorRes(chan).HdcoilTbl(:).position]; + tmpx = [tmp(1:MAX_COILS).x]/100; % In meters + tmpy = [tmp(1:MAX_COILS).y]/100; + tmpz = [tmp(1:MAX_COILS).z]/100; + + Channel(chan).Loc = ... + [tmpx;tmpy;tmpz]; + + tmp = [SensorRes(chan).HdcoilTbl(:).orient]; + tmpx = [tmp(1:MAX_COILS).x]; + tmpy = [tmp(1:MAX_COILS).y]; + tmpz = [tmp(1:MAX_COILS).z]; + + Channel(chan).Orient = ... + [tmpx;tmpy;tmpz]; + + % Reorient along the outward pointing normal + loc = Channel(chan).Loc'; + ps1=sum((loc(1,:).*Channel(chan).Orient(:,1)')')'; + ps2=sum((loc(2,:).*Channel(chan).Orient(:,2)')')'; + Channel(chan).Orient(:,1) = (sign(ps1)*ones(1,3).*Channel(chan).Orient(:,1)')'; + Channel(chan).Orient(:,2) = (sign(ps2)*ones(1,3).*Channel(chan).Orient(:,2)')'; + + Channel(chan).Comment = [SensorNames{SensorRes(chan).sensorTypeIndex+1},' ',int2str(chan)]; + Channel(chan).Type = 'MEG';%SensorNames{SensorRes(chan).sensorTypeIndex+1} ; + + chanmeg = [chanmeg,chan]; + megID(imeg) = chan; + Channel(chan).Weight = [1 -1] ; + Channel(chan).Name = char(strtok(channel_name{chan},'-')); + else + %KND : To detect Virtual ones: + % Head Coil Position = (0,0,0) + % ie. SensorRes(chanNumber).HdcoilTbl(1).position.x=0 + % SensorRes(chanNumber).HdcoilTbl(1).position.y=0 + % SensorRes(chanNumber).HdcoilTbl(1).position.z=0 + % could also use : + % SensorRes(chan).coilTbl(coil).area = 0; + % + % disp([num2str(chan) ' is VIRTUAL']); + + Channel(chan).Loc = []; + Channel(chan).Orient = []; + Channel(chan).Comment = [SensorNames{SensorRes(chan).sensorTypeIndex+1},' ',int2str(chan)]; + Channel(chan).Type = 'VirtualMEG'; + Channel(chan).Weight = [] ; + Channel(chan).Name = char(strtok(channel_name{chan},'-')); + iother = iother+1; + chanother = [chanother,chan]; + otherID(iother) = chan; + %remove it from imegsens + imegsens=setdiff(imegsens, chan); + end + + case 9 % EEG + ieeg = ieeg + 1; + MAX_COILS = 2; % Take only into account the 2 first coils for every sensor + tmp = [SensorRes(chan).HdcoilTbl(:).position]; + tmpx = [tmp(1:MAX_COILS).x]/100;% In meters + tmpy = [tmp(1:MAX_COILS).y]/100; + tmpz = [tmp(1:MAX_COILS).z]/100; + + Channel(chan).Loc = ... + [tmpx;tmpy;tmpz]; + + Channel(chan).Orient = []; + Channel(chan).Comment = [SensorNames{SensorRes(chan).sensorTypeIndex+1},' ',int2str(chan)]; + + Channel(chan).Type = 'EEG';%SensorNames{SensorRes(chan).sensorTypeIndex+1} ; + Channel(chan).Comment = [SensorNames{SensorRes(chan).sensorTypeIndex+1},' ',int2str(chan)]; + Channel(chan).Weight = []; + Channel(chan).Name = char(strtok(channel_name{chan},'-'));%int2str(ieeg) ; + chaneeg = [chaneeg,chan]; + eegID(ieeg) = chan; + + case 11 % STIM + iother = iother+1; + Channel(chan).Loc = []; + Channel(chan).Orient = []; + Channel(chan).Type = SensorNames{SensorRes(chan).sensorTypeIndex+1} ; + if isempty(deblank(Channel(chan).Type )) + Channel(chan).Type = 'STIM'; + end + Channel(chan).Comment = [SensorNames{SensorRes(chan).sensorTypeIndex+1},' ',int2str(chan)]; + Channel(chan).Weight = []; + chanother = [chanother,chan]; + otherID(iother) = chan; + + otherwise + % KND for MicroMed EEG dataHandled... + % replaced line: iother2 = iother2+1; + % by : + iother = iother+1; + Channel(chan).Loc = []; + Channel(chan).Orient = []; + Channel(chan).Type = SensorNames{SensorRes(chan).sensorTypeIndex+1} ; + if isempty(deblank(Channel(chan).Type )) + Channel(chan).Type = 'OTHER'; + + end + Channel(chan).Comment = [SensorNames{SensorRes(chan).sensorTypeIndex+1},' ',int2str(chan)]; + Channel(chan).Weight = []; + chanother = [chanother,chan]; + other2ID(iother) = chan; + + end + end + + + disp('----------------------Channel Information Updated---------------------------------') + + %------------------------------------------------------------------------------- + + %----------------------------------READ Coefficients--------------------------------- + + % Number of coefficient records + nrec = fread(rec,1,'int16'); + + + % ----------------------------- Read Coefficient Information + + hexadef = {'00000000','47314252','47324252','47334252','47324f49','47334f49'}; + strdef = {'NOGRAD','G1BR','G2BR','G3BR','G2OI','G3OI'}; + + CoefInfo = cell(length(channel_name),length(strdef)-1); + + %SensorCoefResRec + if VERBOSE + h = waitbar(0,'Reading Coefficient Information...'); + end + + for k = 1:nrec + SensorCoefResRec(k).sensorName = char(fread(rec,32,'char')'); + SensorCoefResRec(k).coefType = (fread(rec,1,'bit32')); + padding = fread(rec,1,'int32'); + SensorCoefResRec(k).CoefResRec.num_of_coefs = fread(rec,1,'int16'); + + SensorCoefResRec(k).CoefResRec.sensor_list = char(fread(rec,[SENSOR_LABEL,MAX_BALANCING],'uchar')'); + SensorCoefResRec(k).CoefResRec.coefs_list = fread(rec,MAX_BALANCING,'double'); + + cchannel = strmatch( deblank(SensorCoefResRec(k).sensorName),(channel_name)); + CoefType = find(hex2dec(hexadef)==(SensorCoefResRec(k).coefType)); + + if CoefType > 0 + + CoefInfo{cchannel,CoefType-1}.num_of_coefs = SensorCoefResRec(k).CoefResRec.num_of_coefs; + + for i=1:CoefInfo{cchannel,CoefType-1}.num_of_coefs + CoefInfo{cchannel,CoefType-1}.sensor_list(i) = irefsens(strmatch(strtok(SensorCoefResRec(k).CoefResRec.sensor_list(i,:),char(0)),channel_name(irefsens))); + CoefInfo{cchannel,CoefType-1}.coefs(i) = SensorCoefResRec(k).CoefResRec.coefs_list(i); + end + end + if VERBOSE + waitbar(k/nrec) + end + + end + + if VERBOSE + delete(h) + end + + + % Channel Gains + gain_chan = zeros(size(channel_name,1),1); + gain_chan(imegsens) = ([SensorRes(imegsens).properGain]'.*[SensorRes(imegsens).qGain]'); + gain_chan(irefsens) = ([SensorRes(irefsens).properGain]'.*[SensorRes(irefsens).qGain]'); + % gain_chan(ieegsens) = 1./([SensorRes(ieegsens).qGain]'*1e-6); + gain_chan(ieegsens) = 1./([SensorRes(ieegsens).qGain]'); + gain_chan(iothersens) = ([SensorRes(iothersens).qGain]'); % Don't know exactly which gain to apply here + + if VERBOSE + fprintf('\n--------------------------- Coefficient Information ------------------------------\n') + fprintf('Number of coefficient records = %d \n', nrec); + [coef_file, message] = fopen('coef_file.txt','wt+'); + if coef_file < 0 + errordlg(message) + return + end + + fprintf(coef_file,'Number of coefficient records = %d \n', nrec); + + for k = 1:nrec + coefType = dec2hex(SensorCoefResRec(k).coefType); + coefType = strdef{hex2dec(hexadef)==hex2dec(coefType)}; + + fprintf(coef_file,'Channel %s type %s, ',SensorCoefResRec(k).sensorName, coefType); + + num_of_coefs = SensorCoefResRec(k).CoefResRec.num_of_coefs; + fprintf(coef_file,'number = %d:\n', num_of_coefs); + if 0 + for j=1:num_of_coefs + fprintf(coef_file,' %s ', SensorCoefResRec(k).CoefResRec.sensor_list{j}); + fprintf(coef_file,' %g ',SensorCoefResRec(k).CoefResRec.coefs_list(j)); + end + fprintf(coef_file,'\n'); + end + end + + fprintf('Coefficient Information saved in coef_file.txt\n') + + fprintf('\n----------------------- End Coefficient Information ------------------------------\n') + + end + + grad_order_no = [SensorRes(:).grad_order_no]; + + + if length(imegsens) + % Channel(imegsens(1)).grad_order_no = grad_order_no; + % Channel(imegsens(1)).CoefInfo = CoefInfo; + % Channel(imegsens(1)).gain_chan = gain_chan; + Channel(imegsens(1)).imegsens = imegsens; + Channel(imegsens(1)).ieegsens = imegsens; + Channel(imegsens(1)).irefsens = irefsens; + Channel(imegsens(1)).RefChannel = Channel; % Store all channel information in that field: facilitates the set-up for the forward computation + + % Calculus of the matrix for nth-order gradient correction + % Coefficients for unused reference channels are weigthed by zeros in + % the correction matrix. + + Gcoef = zeros(length(imegsens),length(min(irefsens):max(irefsens))); + + for k = 1:length(imegsens) + + % Reference coils for channel k + if grad_order_no(imegsens(k)) == 0 + %Data is saved as RAW + %Save 3rd order gradient sensor-list for subsequent correction if requested later by the user + [refs] = (CoefInfo{imegsens(k),3}.sensor_list); + Gcoef(k,refs-min(irefsens)+1) = CoefInfo{imegsens(k),3}.coefs ... + .* gain_chan(refs)'/gain_chan(imegsens(k)); + else + % PB with virtual channel + [refs] = (CoefInfo{imegsens(k),grad_order_no(imegsens(k))}.sensor_list); + %KND changed a transposition before "/" + Gcoef(k,refs-min(irefsens)+1) = CoefInfo{imegsens(k),grad_order_no(imegsens(k))}.coefs ... + .* gain_chan(refs)/gain_chan(imegsens(k)); + end + + end + + Channel(imegsens(1)).Gcoef = Gcoef; + end + Time = linspace(-GenRes4.gSetUp.preTrigPts/GenRes4.gSetUp.sample_rate,GenRes4.gSetUp.epoch_time/GenRes4.gSetUp.no_trials-GenRes4.gSetUp.preTrigPts/GenRes4.gSetUp.sample_rate,GenRes4.gSetUp.no_samples); + + save_sensor_locs(Channel) + + if VERBOSE + disp(['Sensor locations can be visualized with the MRI Tool by loading the file: sensor_result.mat']) + end + drawnow + + fclose('all'); + + % Create the BrainStorm res4 file for subsequent fast access to the data file + no_channels = length(Channel); + gain_chan(ieegsens)=1./gain_chan(ieegsens); + save(res4_mat,'gSetUp','meg4file','ieegsens','irefsens', 'iothersens', 'imegsens','Time','no_channels','gain_chan','channel_name','Channel','grad_order_no','filter','RunTitle'); + + if READRES == 2 + READRES = 0; %read the res4.mat file just created and reads the data by block. + if nargin == 2 % Read the whole thing + CHANNELS = [imegsens,ieegsens]; + TIME = Time; + NO_TRIALS = 1:gSetUp.no_trials; + end + % [F,Channel,imegsens,ieegsens,iothersens,irefsens,grad_order_no,no_trials,filter,Time, RunTitle] = ds2brainstorm(ds_directory,VERBOSE,READRES,CHANNELS,TIME, NO_TRIALS); + % + % return + end + + +else % READRES == 0 + load(res4_mat); + +end + + +%--------------------------- Readind DATA FILE ------------------------------ + +if VERBOSE + fprintf('\n--------------------------- Reading Data -----------------------------------------\n') +end + +if nargin <= 2 %| (nargin > 2 & READRES == 0) % Reads the whole block of data + + no_trials = gSetUp.no_trials; + + [meg,message] = fopen(meg4file,'rb','s'); % Big-endian byte ordering + if meg < 0 + errordlg(message) + return + end + + F = cell(gSetUp.no_trials,1); % allocate space for the data array % ASSUME SINGLE TRIAL FOR THE MOMENT - ie enough memory space + header = char(fread(meg,8,'char')'); + + nref=length(irefsens); + + for trial = 1:gSetUp.no_trials + + if VERBOSE + fprintf('Reading Trial %d/%d\n',trial,gSetUp.no_trials); + end + + F{trial} = zeros(gSetUp.no_channels,gSetUp.no_samples); + F{trial} = fread(meg,[gSetUp.no_samples gSetUp.no_channels],'int32')'; + + if VERBOSE + fprintf('-> Done %d/%d\n',trial,gSetUp.no_trials) + end + % IMPORTANT NOTICE : Applying Gradient Correction + % Data are saved in a given nth-order gradient correction + % Applying gradient correction si only needed for forward model computation + % or if it is desired to reverse to lower-order gradient correction (see importdata.m for instance). + + % Apply channel gains + % F{trial}(ieegsens,:) = diag( gain_chan(ieegsens))*F{trial}(ieegsens,:) ; %To get Microvolts + F{trial}(ieegsens,:) = diag(1./gain_chan(ieegsens))*F{trial}(ieegsens,:) ; %To get Microvolts->already inverted when saved _res4.mat + F{trial}(imegsens,:) = diag(1./gain_chan(imegsens))*F{trial}(imegsens,:) ; + F{trial}(irefsens,:) = diag(1./gain_chan(irefsens))*F{trial}(irefsens,:) ; + F{trial}(iothersens,:) = diag(1./gain_chan(iothersens))*F{trial}(iothersens,:) ; + + end + + fclose('all'); + + %------------------------------------------------------------------------------------------------------------------------------------ + +else % Reads sub-block of data + + %ds2brainstorm(ds_directory,VERBOSE,READRES,CHANNELS,TIME, NO_TRIALS); + if nargin <= 3 % CHANNELS not defined + CHANNELS=[1:gSetUp.no_channels]; + end + if nargin <= 4 % TIME not defined + TIME=[-gSetUp.preTrigPts*1/gSetUp.sample_rate (gSetUp.no_samples-gSetUp.preTrigPts)*1/gSetUp.sample_rate]; + end + if nargin <= 5 % NO_TRIALS not defined + no_trials = [1:gSetUp.no_trials]; + else + no_trials = [NO_TRIALS]; + end + + fclose('all'); + [meg,message] = fopen(meg4file,'rb','s'); % Big-endian byte ordering + if meg < 0 + errordlg(message) + return + end + + % no_samples = round((TIME(end)-TIME(1))*gSetUp.sample_rate) +1; % Number of time samples in a trial + + F = cell(length(no_trials),1); % allocate space for the data array % ASSUME SINGLE TRIAL FOR THE MOMENT - ie enough memory space + % Adjust time range to the closest time samples; + + if isempty(TIME) + return + end + % [m,t1] = min(abs(TIME(1)-Time)); + % [m,t2] = min(abs(TIME(end)-Time)); + % + % TIME(1) = Time(t1); + % TIME(end) = Time(t2); + % + header = char(fread(meg,8,'char')'); + + implicit.sample_rate = 1/(Time(2) - Time(1)); % i.e. the one used in the data file given the time begin_time end period. + % Time = (Time(1):1/implicit.sample_rate:Time(end)); + t_in = round((TIME(1)-Time(1))*implicit.sample_rate) +1; % Sample time offset since the beginning of the trial: beginning of the time window TIME + %t_out = round((TIME(end)-Time(1))*implicit.sample_rate)+1; % Sample time offset since the beginning of the trial: end of the time window TIME + + no_samples = round((TIME(end)-TIME(1))*implicit.sample_rate)+1; % Number of time samples to extract + t_out = t_in + no_samples - 1; + + %no_samples = t_out-t_in+1; + + + diff_trials = diff(no_trials)-1; % Number of trials between each selected trials (useful when skipping a few trials) + + ByteSizeOfTrial= no_channels*gSetUp.no_samples*4; % Byte size of a single trial of data (Int32 coding) + + samples_skip = gSetUp.no_samples-no_samples; %Number of time samples to skip per channel + + LastChannelSkip = (no_channels - max(CHANNELS))*gSetUp.no_samples*4; % Skip data from last channels + + channels = [min(CHANNELS):max(CHANNELS)]; % Block of channels to extract. + + FirstChannelSkip = (min(CHANNELS)-1)*gSetUp.no_samples*4; % Skip data from first channels + no_channels = length(channels); + + itrial = 0; + + for trial = no_trials + itrial = itrial+1; + if VERBOSE + fprintf('Reading Trial %d / %d\n',itrial,length(no_trials)); + end + + F{itrial} = zeros(no_channels,no_samples); + + if trial == no_trials(1) % Read first trial + fseek(meg,(trial-1)*ByteSizeOfTrial + FirstChannelSkip + (t_in-1)*4 ,0); + else % just shift from the size of a trial + fseek(meg,LastChannelSkip + diff_trials(itrial-1)*ByteSizeOfTrial + FirstChannelSkip ,0); + end + + F{itrial} = fread(meg,[no_samples no_channels],[num2str(no_samples),'*int32=>int32'], samples_skip*4)'; + F{itrial} = F{itrial}(CHANNELS-min(CHANNELS)+1,:); + + % IMPORTANT NOTICE : Applying Gradient Correction + % Data are saved in a given nth-order gradient correction + % Applying gradient correction is only needed for forward model computation + % or if it is desired to reverse to lower-order gradient correction (see importdata.m for instance). + + % Apply channel gains + F{itrial} = diag(1./gain_chan(CHANNELS))*double(F{itrial}); + + + if VERBOSE + fprintf('-> Done %d/%d\n',itrial,length(no_trials)) + end + + end + + fclose('all'); + + %Time = TIME(1):1/gSetUp.sample_rate:TIME(end); + +end +cd(current_dir) + + +function PrintCoilPos(channel_file, CoilRec_ext,nCoils) ; + +% Subroutine to DS2BRAINSTORM +% Formatted output of channel locations in a file (channel_file is the pointer to this file) +% as defined in theCoilRec_ext structure, according to the number of coils nCoils for every gradiometer + +for coil=1:nCoils + fprintf(channel_file,'Coil %d:', coil); + fprintf(channel_file,' pX %g,', CoilRec_ext(coil).position.x); + fprintf(channel_file,' pY %g,', CoilRec_ext(coil).position.y); + fprintf(channel_file,' pZ %g ', CoilRec_ext(coil).position.z); + fprintf(channel_file,'-'); + fprintf(channel_file,' oX %g,', CoilRec_ext(coil).orient.x); + fprintf(channel_file,' oY %g,', CoilRec_ext(coil).orient.y); + fprintf(channel_file,' oZ %g ', CoilRec_ext(coil).orient.z); + fprintf(channel_file,'-'); + fprintf(channel_file,' turns %d,', CoilRec_ext(coil).numturns); + fprintf(channel_file,' area %g', CoilRec_ext(coil).area); + + if coil < nCoils + fprintf(channel_file,' --- '); + end + + fprintf(channel_file,'\n'); +end + +return + +function save_sensor_locs(Channel) +% Save Channel location in a pseudo-result file for visualization in the MRITool + +nchan = length(Channel); +meg = good_channel(Channel,ones(nchan,1),'MEG'); +eeg = good_channel(Channel,ones(nchan,1),'EEG'); + +nchan= length([meg,eeg]); +SourceLoc = cell(1,nchan); + +i = 0; +for k = [meg,eeg] + i = i+1; + SourceLoc{i} = Channel(k).Loc(:,1); + SourceOrder(i) = -1; + Comment = 'Sensor Locations'; +end +DataFlag = 'Sensors'; +save sensor_result SourceLoc SourceOrder DataFlag Comment + +return diff --git a/eeglab/plugins/ctf/importctf.m b/eeglab/plugins/ctf/importctf.m new file mode 100644 index 0000000..dd67c48 --- /dev/null +++ b/eeglab/plugins/ctf/importctf.m @@ -0,0 +1,114 @@ +% importctf() - import CTF DataSets +% +% Usage: +% >> [data channel study] = importctf(datafile,device) +% +% Required Input: +% dasfolder = a Dataset (DS) folder +% +% Optional Input: +% device = ['MEG'|'EEG'|'EEG+MEG'|'ALL'] +% +% Outputs: +% data = cell array of data (one cell by trial) +% channel = sensor positions +% +% Author: Karim N'Diaye, CNRS-UPR640, 01 Jan 2004 +% +% See also: +% POP_READBSTORM, EEGLAB + +%123456789012345678901234567890123456789012345678901234567890123456789012 + +% Copyright (C) 2004, CNRS - UPR640, N'Diaye Karim, +% karim.ndiaye@chups.jussieu.Fr +% +% This program is free software; you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation; either version 2 of the License, or +% (at your option) any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with this program; if not, write to the Free Software +% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +% $Log: readegi.m,v $ +% Revision 0.1 2004/06/29 +% First alpha version for EEGLAB release 4.301 + +function [Data,Channel] = importctf(dsfolder,device) + +if nargin < 1 + help(mfilename); + return; +end; + +[ignore, studyname]=fileparts(dsfolder); +dsfolder + +prev_dir=cd; +cd(dsfolder) +% Converrting Data to BrainStorm format + +[Data.F,Channel,Data.imegsens,Data.ieegsens,Data.iothersens,Data.irefsens,Data.grad_order_no,Data.no_trials,Data.filter,Data.Time,Data.RunTitle] = ds2brainstorm(dsfolder,1,0); +% VERBOSE,READRES,CHANNELS, TIME, NO_TRIALS, DCOffset); + + +%Importing Electrodes to EEGLAB format +channelflags=zeros(length(Channel),1); +if nargin <2 + DEVICES={'EEG', 'MEG', 'EEG+MEG', 'ALL'}; + [s,v] = listdlg('PromptString','Select a device:',... + 'SelectionMode','single',... + 'ListString',{'EEG', 'MEG', 'EEG+MEG'}); + if not(all(v)) + s=3; + end + + switch(s) + case 1 + device={'EEG'} + case 2, device={'MEG'} + case 3, device = {'EEG', 'MEG'} + end + +end +for i=1:length(device) + channelflags=channelflags + strcmp(device{i},{Channel.Type}'); +end +%channelflags(strmatch('EEG', {Channel.Type}))=1; +%channelflags(strmatch('MEG', {Channel.Type}))=1; + +Channel=Channel(find(channelflags)); +for i=1:length(Data.F) + Data.F{i}=Data.F{i}(find(channelflags),:); +end + +return + +i=1; +%datafilename=sprintf('%s%d.mat', database , i); +f=dir(sprintf('%s*.mat', database)); +f=strvcat({f.name}); +f=f(:,length(database)+1:end); +trials=str2num(char(strrep(cellstr(f), '.mat', ''))); +trials=sort(trials); + +h = waitbar(0,'Importing Datafiles. Please wait...'); +for i=1:length(trials) + waitbar(i/length(trials),h) + datafilename=sprintf('%s%d.mat', database , trials(i)); + fprintf('Importing %s\n', datafilename) + Data(i)=load(datafilename); + %channelflag=channelflags & Data(i).ChannelFlag; + channelflag=channelflags; + Data(i).F=Data(i).F(find(channelflag),:); +end +close(h) + +return diff --git a/eeglab/plugins/ctf/pop_importctf.m b/eeglab/plugins/ctf/pop_importctf.m new file mode 100644 index 0000000..de1403d --- /dev/null +++ b/eeglab/plugins/ctf/pop_importctf.m @@ -0,0 +1,157 @@ +% pop_importctf() - imports CTF DataSet (pop out window if no arguments). +% +% Usage: +% >> EEG = pop_importctf; % a window pops up +% >> EEG = pop_importctf( dsfolder ); +% +% Inputs: +% datafile - A datafile within a DS folder containing BrainStorm datafiles +% +% Outputs: +% EEG - EEGLAB data structure +% +% Author: Karim N'Diaye, CNRS-UPR640, 01 Jan 2004 +% +% See also: eeglab(), importctf() + +%123456789012345678901234567890123456789012345678901234567890123456789012 + +% Copyright (C) 2004, CNRS - UPR640, N'Diaye Karim, +% karim.ndiaye@chups.jussieu.Fr +% +% This program is free software; you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation; either version 2 of the License, or +% (at your option) any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with this program; if not, write to the Free Software +% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +% $Log: pop_importctf.m,v $ +% Revision 0.1 2004/01/01 +% First alpha version for EEGLAB release 4.301 +% + +function [EEG, command] = pop_importctf(datafile); + +EEG = []; +command = ''; +if nargin < 1 + % ask user + try + cd('D:\ndiaye\data\studies\div\Savg\avgdata\div-d-avTone1.ds'); + catch + end + [filename, filepath] = uigetfile('*.hc', 'Choose a DS folder -- pop_importctf()'); + drawnow; + if filename == 0 return; end; + [dsfolder]=fileparts(fullfile(filepath, filename)) ; +end; + +% load data +% --------- +EEG = eeg_emptyset; +[data channel ] = importctf(dsfolder); + +EEG.filename = data.RunTitle +EEG.filepath = dsfolder; +EEG.setname = ['CTF data:' data.RunTitle ]; +EEG.nbchan = length(channel); +EEG.srate = 1/mean(diff(data.Time)); +EEG.trials = length(data.F); +EEG.pnts = length(data.Time); +EEG.times = data(1).Time; +EEG.xmin = EEG.times(1); % huh?? +EEG.xmax = EEG.times(end); % huh?? +%Caution! CELL2MAT is overloaded in EEGLAB environment +fprintf('Importing data...(may take a while)') +EEG.data=zeros([EEG.nbchan EEG.pnts EEG.trials]); +for i=1:EEG.trials + EEG.data(:,:,i)=data.F{i}; +end +% EEG.comments = study.Session; +EEG.comments = sprintf('Imported from CTF dataset: %s', dsfolder); +EEG.ref = ['common']; + +fprintf('Importing channel location information') +for i=1:length(channel) + eloc(i).X=channel(i).Loc(1); + eloc(i).Y=channel(i).Loc(2); + eloc(i).Z=channel(i).Loc(3); + eloc(i).labels=channel(i).Name; +end +EEG.chanlocs=convertlocs(eloc, 'cart2all') + +EEG = eeg_checkset(EEG); +command = sprintf('EEG = pop_importctf(''%s'');', dsfolder); + +return; + + + +% importing the events +% -------------------- +if ~isempty(Eventdata) + orinbchans = EEG.nbchan; + for index = size(Eventdata,1):-1:1 + EEG = pop_chanevent( EEG, orinbchans-size(Eventdata,1)+index, 'edge', 'leading', ... + 'delevent', 'off', 'typename', Head.eventcode(index,:), ... + 'nbtype', 1, 'delchan', 'on'); + end; +end; +return + +%EEG = +% +% setname: 'Continuous EEG Data epochs' +% filename: 'eeg_demo_squareepochs.set' +% filepath: '' +% pnts: 384 +% nbchan: 32 +% trials: 80 +% srate: 128 +% xmin: -1 +% xmax: 1.9922 +% data: [32x384x80 single] +% icawinv: [] +% icasphere: [] +% icaweights: [] +% icaact: [] +% event: [1x157 struct] +% epoch: [1x80 struct] +% chanlocs: [1x32 struct] +% comments: [9x769 char] +% averef: 'no' +% rt: [] +% eventdescription: {[2x29 char] [2x63 char] [2x36 char] ''} +% epochdescription: {} +% specdata: [] +% specicaact: [] +% reject: [1x1 struct] +% stats: [1x1 struct] +% splinefile: [] +% ref: 'averef' +% history: [11x108 char] +% times: [1x384 double] + +% EEG.event +% 1x157 struct array with fields: +% type: 'square' or 'rt' +% position: [1] or [2] +% latency: in ms from the beginning of the continuous recording +% epoch: the epoch in which the event falls (indexed from 1) + +% EEG.epoch +% 1x80 struct array with fields: +% event: the index of the events that fall in that one epoch +% eventlatency: the latency of each event +% eventposition: the position of each event +% eventtype: the type of each event + + diff --git a/ellipse.m b/ellipse.m new file mode 100644 index 0000000..4de6f54 --- /dev/null +++ b/ellipse.m @@ -0,0 +1,149 @@ +function h=ellipse(ra,rb,ang,x0,y0,C,Nb) +% Ellipse adds ellipses to the current plot +% +% ELLIPSE(ra,rb,ang,x0,y0) adds an ellipse with semimajor axis of ra, +% a semimajor axis of radius rb, a semimajor axis of ang, centered at +% the point x0,y0. +% +% The length of ra, rb, and ang should be the same. +% If ra is a vector of length L and x0,y0 scalars, L ellipses +% are added at point x0,y0. +% If ra is a scalar and x0,y0 vectors of length M, M ellipse are with the same +% radii are added at the points x0,y0. +% If ra, x0, y0 are vectors of the same length L=M, M ellipses are added. +% If ra is a vector of length L and x0, y0 are vectors of length +% M~=L, L*M ellipses are added, at each point x0,y0, L ellipses of radius ra. +% +% ELLIPSE(ra,rb,ang,x0,y0,C) +% adds ellipses of color C. C may be a string ('r','b',...) or the RGB value. +% If no color is specified, it makes automatic use of the colors specified by +% the axes ColorOrder property. For several circles C may be a vector. +% +% ELLIPSE(ra,rb,ang,x0,y0,C,Nb), Nb specifies the number of points +% used to draw the ellipse. The default value is 300. Nb may be used +% for each ellipse individually. +% +% h=ELLIPSE(...) returns the handles to the ellipses. +% +% as a sample of how ellipse works, the following produces a red ellipse +% tipped up at a 45 deg axis from the x axis +% ellipse(1,2,pi/8,1,1,'r') +% +% note that if ra=rb, ELLIPSE plots a circle +% + +% written by D.G. Long, Brigham Young University, based on the +% CIRCLES.m original +% written by Peter Blattner, Institute of Microtechnology, University of +% Neuchatel, Switzerland, blattner@imt.unine.ch + + +% Check the number of input arguments + +if nargin<1, + ra=[]; +end; +if nargin<2, + rb=[]; +end; +if nargin<3, + ang=[]; +end; + +%if nargin==1, +% error('Not enough arguments'); +%end; + +if nargin<5, + x0=[]; + y0=[]; +end; + +if nargin<6, + C=[]; +end + +if nargin<7, + Nb=[]; +end + +% set up the default values + +if isempty(ra),ra=1;end; +if isempty(rb),rb=1;end; +if isempty(ang),ang=0;end; +if isempty(x0),x0=0;end; +if isempty(y0),y0=0;end; +if isempty(Nb),Nb=300;end; +if isempty(C),C=get(gca,'colororder');end; + +% work on the variable sizes + +x0=x0(:); +y0=y0(:); +ra=ra(:); +rb=rb(:); +ang=ang(:); +Nb=Nb(:); + +if isstr(C),C=C(:);end; + +if length(ra)~=length(rb), + error('length(ra)~=length(rb)'); +end; +if length(x0)~=length(y0), + error('length(x0)~=length(y0)'); +end; + +% how many inscribed elllipses are plotted + +if length(ra)~=length(x0) + maxk=length(ra)*length(x0); +else + maxk=length(ra); +end; + +% drawing loop + +for k=1:maxk + + if length(x0)==1 + xpos=x0; + ypos=y0; + radm=ra(k); + radn=rb(k); + if length(ang)==1 + an=ang; + else + an=ang(k); + end; + elseif length(ra)==1 + xpos=x0(k); + ypos=y0(k); + radm=ra; + radn=rb; + an=ang; + elseif length(x0)==length(ra) + xpos=x0(k); + ypos=y0(k); + radm=ra(k); + radn=rb(k); + an=ang(k) + else + rada=ra(fix((k-1)/size(x0,1))+1); + radb=rb(fix((k-1)/size(x0,1))+1); + an=ang(fix((k-1)/size(x0,1))+1); + xpos=x0(rem(k-1,size(x0,1))+1); + ypos=y0(rem(k-1,size(y0,1))+1); + end; + + co=cos(an); + si=sin(an); + the=linspace(0,2*pi,Nb(rem(k-1,size(Nb,1))+1,:)+1); +% x=radm*cos(the)*co-si*radn*sin(the)+xpos; +% y=radm*cos(the)*si+co*radn*sin(the)+ypos; + h(k)=line(radm*cos(the)*co-si*radn*sin(the)+xpos,radm*cos(the)*si+co*radn*sin(the)+ypos); + set(h(k),'color',C(rem(k-1,size(C,1))+1,:)); + +end; + diff --git a/elproj.m b/elproj.m new file mode 100644 index 0000000..d673808 --- /dev/null +++ b/elproj.m @@ -0,0 +1,105 @@ +function [proj] = elproj(pos, method); + +% ELPROJ makes a azimuthal projection of a 3D electrode cloud +% on a plane tangent to the sphere fitted through the electrodes +% the projection is along the z-axis +% +% [proj] = elproj([x, y, z], 'method'); +% +% Method should be one of these: +% 'gnomic' +% 'stereographic' +% 'ortographic' +% 'inverse' +% 'default' +% +% Imagine a plane being placed against (tangent to) a globe. If +% a light source inside the globe projects the graticule onto +% the plane the result would be a planar, or azimuthal, map +% projection. If the imaginary light is inside the globe a Gnomonic +% projection results, if the light is antipodal a Sterographic, +% and if at infinity, an Orthographic. +% +% The default projection is an angular projection (BESA like). +% An inverse projection is the opposite of the default angular projection. + +% Copyright (C) 2000-2001, Robert Oostenveld +% +% $Log: elproj.m,v $ +% Revision 1.2 2003/03/17 10:37:28 roberto +% improved general help comments and added copyrights +% + +x = pos(:,1); +y = pos(:,2); +if size(pos, 2)==3 + z = pos(:,3); +end + +if nargin<2 + method='default'; +end + +if nargin<3 + secant=1; +end + +if strcmp(method, 'orthographic') + % this method compresses the lowest electrodes very much + % electrodes on the bottom half of the sphere are folded inwards + xp = x; + yp = y; + num = length(find(z<0)); + str = sprintf('%d electrodes may be folded inwards in orthographic projection\n', num); + if num + warning(str); + end + proj = [xp yp]; + +elseif strcmp(method, 'gnomic') + % the lightsource is in the middle of the sphere + % electrodes on the equator are projected at infinity + % electrodes below the equator are not projected at all + rad = mean(sqrt(x.^2 + y.^2 + z.^2)); + phi = cart2pol(x, y); + th = atan(sqrt(x.^2 + y.^2) ./ z); + xp = cos(phi) .* tan(th) .* rad; + yp = sin(phi) .* tan(th) .* rad; + num = length(find(th==pi/2 | z<0)); + str = sprintf('removing %d electrodes from gnomic projection\n', num); + if num + warning(str); + end + xp(find(th==pi/2 | z<0)) = NaN; + yp(find(th==pi/2 | z<0)) = NaN; + proj = [xp yp]; + +elseif strcmp(method, 'stereographic') + % the lightsource is antipodal (on the south-pole) + rad = mean(sqrt(x.^2 + y.^2 + z.^2)); + z = z + rad; + phi = cart2pol(x, y); + th = atan(sqrt(x.^2 + y.^2) ./ z); + xp = cos(phi) .* tan(th) .* rad * 2; + yp = sin(phi) .* tan(th) .* rad * 2; + num = length(find(th==pi/2 | z<0)); + str = sprintf('removing %d electrodes from stereographic projection\n', num); + if num + warning(str); + end + xp(find(th==pi/2 | z<0)) = NaN; + yp(find(th==pi/2 | z<0)) = NaN; + proj = [xp yp]; + +elseif strcmp(method, 'inverse') + % compute the inverse projection of the default angular projection + [th, r] = cart2pol(x, y); + [xi, yi, zi] = sph2cart(th, pi/2 - r, 1); + proj = [xi, yi, zi]; + +else + % use default angular projection + [az, el, r] = cart2sph(x, y, z); + [x, y] = pol2cart(az, pi/2 - el); + proj = [x, y]; +end diff --git a/entropy.m b/entropy.m new file mode 100644 index 0000000..339acfe --- /dev/null +++ b/entropy.m @@ -0,0 +1,32 @@ +function H = entropy(X,varargin) +%ENTROPY - Entropy in bits +% [H] = entropy(X,b) +% Computes Shannon's entropy (column-wise) in bits +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2006 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2006-10-24 Creation +% +% ----------------------------- Script History --------------------------------- +[e,n,f]=histk(X,varargin{:}); +if ~iscell(e) + e={e}; + n={n}; +end +for i=1:numel(e) + N=sum(n{i}); + % Trick: + % We replace 0 by -1 which has imaginary log + n{i}=numrep(n{i}./N,0,-1); + H(i)=-sum(nonzeros(n{i}).*log(nonzeros(n{i})))./log(2); + % Now we discard the imaginary part + H(i)=real(H(i)); +end \ No newline at end of file diff --git a/entropy2.m b/entropy2.m new file mode 100644 index 0000000..ae5e17d --- /dev/null +++ b/entropy2.m @@ -0,0 +1,68 @@ +function H = entropy(objectSet,varargin) +%ENTROPY Compute the Shannon entropy of a set of variables. +% ENTROPY(X,P) returns the (joint) entropy for the joint distribution +% corresponding to object matrix X and probability vector P. Each row of +% MxN matrix X is an N-dimensional object, and P is a length-M vector +% containing the corresponding probabilities. Thus, the probability of +% object X(i,:) is P(i). +% +% ENTROPY(X), with no probability vector specified, will assume a uniform +% distribution across the objects in X. +% +% If X contains duplicate rows, these are assumed to be occurrences of the +% same object, and the corresponding probabilities are added. (This is +% actually the only reason that object matrix X is needed -- to detect and +% merge repeated objects. Of course, the entropy itself only depends on +% the probability vector P.) Matrix X need NOT be an exhaustive list of +% all *possible* objects in the universe; objects that do not appear in X +% are simply assumed to have zero probability. +% +% The elements of probability vector P must sum to 1 +/- .00001. +% +% For further information about entropy in information theory, see +% Information entropy. Wikipedia, The Free Encyclopedia. +% +% See also: MUTUALINFO + +%% Error checking %% +if size(objectSet,1) == 1, + objectSet = objectSet'; + %warning('Object set row vector is being transposed to a column vector.') +end +if ~isempty(varargin), + probSet = varargin{1}; +else + probSet = repmat(1/size(objectSet,1),size(objectSet,1),1); +end +if ~isequal(size(objectSet,1),length(probSet)), + error('Object set must have an object for each probability.') +end +% Check probabilities sum to 1: +if abs(sum(probSet) - 1) > .00001, + error('Probablities don''t sum to 1.') +end + +%% Merge duplicate objects/probabilities %% +% We do not use objectSet in the calculations, but just need to deal with +% duplicated objects (add probabilities together). +[minimalObjSet I equivClass] = unique(objectSet,'rows'); +if ~isequal(size(minimalObjSet,1),size(objectSet,1)), + probSetReduced = zeros(size(minimalObjSet,1),1); + for i = 1:length(probSetReduced), + probSetReduced(i) = sum(probSet(equivClass==i)); + end + probSet = probSetReduced; +end + +%% Remove any zero probabilities %% +zeroProbs = find(probSet < eps); +if ~isempty(zeroProbs), + probSet(zeroProbs) = []; + %disp('Removed zero or negative probabilities.') +end + +%% Compute the entropy +H = -sum(probSet .* log2(probSet)); + + + diff --git a/epilepsie/dataplot_cb.m b/epilepsie/dataplot_cb.m new file mode 100644 index 0000000..0b079fd --- /dev/null +++ b/epilepsie/dataplot_cb.m @@ -0,0 +1,6685 @@ +function varargout = dataplot_cb(action,varargin) +%DATAPLOT_CB : Callback switchyard for the DATAPLOT Tool +% function dataplot_cb(action) +% Callback function for the DATAVIEWER tool +% +% /---Script Author--------------------------------------\ +% | | +% | *** Sylvain Baillet, Ph.D. | +% | Cognitive Neuroscience & Brain Imaging Laboratory | +% | CNRS UPR640 - LENA | +% | Hopital de la Salpetriere, Paris, France | +% | sylvain.baillet@chups.jussieu.fr | +% \------------------------------------------------------/ +% +% Date of creation: January 1999 +% Date of modification: November 2001 +%--------------------------------------------------------------------------------------------------------------------------- +% SB, 2001-09 / revamping for compliancy with DataManager + major plastic surgery +% SB, October 2001 - Added Cortical Probes, Mapping of Curvature, Colormap Scaling of Activations +% SB - DS, November 2001 - Multiple fixings; added 0ms adjustment to data maximum peak (useful for epileptic data analysis about the maximum of the spike) +% KND, Nov. 2003 : Changed lines 610-621 to allow MEG and EEG data plot... +% KND, Nov. 2003 : Changed line 290 to put F{1} in Data.F + +frontcolor = [.4 .4 .4]; +backcolor = [.8 .8 .8]; +linecolor = [.4 .4 .4]; +textcolor = linecolor; +verbose = 0;% Verbose mode for Mosher's tools: 0 - off ; 1 - on + +%---------------------------------------------------------------------------- + +DATAPLOT = openfig('dataplot.fig','reuse'); + +TASKBAR = findobj(0,'Tag','TASKBAR'); +Users = guidata(TASKBAR); + +MEG = get(findobj(DATAPLOT,'Tag','MEG'),'value'); +EEG = get(findobj(DATAPLOT,'Tag','EEG'),'value'); +OTHER = get(findobj(DATAPLOT,'Tag','OTHER'),'value'); +current = find([MEG,EEG,OTHER] == 1); +modality = {'MEG','EEG','OTHER'}; + +switch action + +case 'create' + DATAPLOT = openfig('dataplot.fig','reuse'); + % Check for data loaded from DataManager, if exists + if ~isfield(Users,'CurrentData') % No data was loaded using DataManager - do nothing (old way) + set(findobj(DATAPLOT,'style','pushbutton'),'enable','off'); + set(findobj(DATAPLOT,'string','Load'),'enable','on') + set(findobj(DATAPLOT,'string','Quit'),'enable','on') + set(findobj(DATAPLOT,'Tag','SurfaceViewer'),'enable','on') + return + end + + % Look for datafiles in current study folder + cd(Users.STUDIES); + [studypath,file,ext] = fileparts(Users.CurrentData.StudyFile); + cd(studypath) + + TestFiles = dir('*data*.mat'); + i = 0; + for k = 1:length(TestFiles) + if isempty(findstr(TestFiles(k).name,'results')) + i = i+1; + DataFiles{i} = TestFiles(k).name; + end + end + if isempty(DataFiles) + DataFiles = 'No data files in current study'; + end + + hDataplot = guihandles(DATAPLOT); + set(hDataplot.DataFileList,'String',DataFiles,'Value',1,'Max',1) + + + %Visu = get(DATAPLOT,'Userdata'); + if 1%isempty(Visu) + dataplot_cb('loaddata',Users.CurrentData.StudyFile,Users.CurrentData.DataFile); + end + + %---------------------------------------------------------------------------- + + +case 'mutincomp_cmapping' % Mutually icompatible checkboxes + mapping_win = findobj(0,'Tag','mapping'); + MEG = findobj(mapping_win ,'Tag','ABSOLUTE'); + EEG = findobj(mapping_win ,'Tag','RELATIVE'); + mutincomp([MEG,EEG]) + h = findobj([MEG,EEG],'Value',1); + if length(h)>1 + set(h(2),'Value',0) + end + %---------------------------------------------------------------------------- + +case 'mutincomp_colormap' % Mutually icompatible checkboxes + mapping_win = findobj(0,'Tag','tesselation_select'); + MEG = findobj(mapping_win ,'Tag','Normalize'); + EEG = findobj(mapping_win ,'Tag','FreezeColormap'); + mutincomp([MEG,EEG]) + h = findobj([MEG,EEG],'Value',1); + if length(h)>1 + set(h(2),'Value',0) + end + + %---------------------------------------------------------------------------- +case 'mutincomp_meanmax' % Mutually icompatible checkboxes + mapping_win = findobj(0,'Tag','tesselation_select'); + MEG = findobj(mapping_win ,'Tag','MeanCorticalArea'); + EEG = findobj(mapping_win ,'Tag','MaxCorticalArea'); + OTHER = findobj(mapping_win ,'Tag','AllCorticalArea'); + + mutincomp([MEG,EEG,OTHER]) + h = findobj([MEG,EEG,OTHER],'Value',1); + if length(h)>1 + set(h(2:end),'Value',0) + end + %---------------------------------------------------------------------------- +case 'mutincomp_opaque' % Mutually icompatible checkboxes + MEG = findobj(gcbf,'Tag','Opaque'); + EEG = findobj(gcbf,'Tag','transparent'); + mutincomp([MEG,EEG]) + h = findobj([MEG,EEG],'Value',1); + if length(h)>1 + set(h(2),'Value',0) + end + dataplot_cb mesh_vals + %---------------------------------------------------------------------------- + +case 'mapping_type' % Mutually icompatible checkboxes + mapping_win = findobj(0,'Tag','mapping'); + MEG = findobj(mapping_win ,'Tag','raw'); + EEG = findobj(mapping_win ,'Tag','gradient'); + OTHER = findobj(mapping_win,'Tag','magnetic'); + mutincomp([MEG,EEG,OTHER]) + h = findobj([MEG,EEG,OTHER],'Value',1); + if length(h)>1 + set(h(2),'Value',0) + end + %---------------------------------------------------------------------------- + +case 'mapping_head_shape' % Mutually icompatible checkboxes + mapping_win = findobj(0,'Tag','mapping'); + handles = guihandles(mapping_win); + % figure(mapping_win) + mutincomp([handles.sphere,handles.fit,handles.scalp]) + h = findobj([handles.sphere,handles.fit,handles.scalp],'Value',1); + if length(h)>1 + set(h(2),'Value',0) + end + %---------------------------------------------------------------------------- + +case 'selectmodality' % Select the current signals to visualize + MEG = findobj(DATAPLOT,'Tag','MEG'); + EEG = findobj(DATAPLOT,'Tag','EEG'); + OTHER = findobj(DATAPLOT,'Tag','OTHER'); + mutincomp([MEG,EEG,OTHER]) + h = findobj([MEG,EEG,OTHER],'Value',1); + if length(h)>1 + set(h(2),'Value',0) % Just one modality at a time please ! + end + QUICKLOOK_GUI = findobj(0,'Tag','QUICKLOOK_GUI'); + if ~isempty(QUICKLOOK_GUI) + vMEG = get(MEG,'value'); + vEEG = get(EEG,'value'); + vOTHER = get(OTHER,'value'); + current = find([vMEG,vEEG,vOTHER] == 1); + set(QUICKLOOK_GUI,'Name',['QuickLook ',modality{current}]) + end + + previous = findobj(0,'Tag','channel_select'); % Update Channel Selection Window with Current Modality + if ~isempty(previous) + dataplot_cb selectchannels + end + + mapping_win = findobj(0,'Tag','mapping'); + MEG = get(MEG,'value'); + EEG = get(EEG,'value'); + OTHER = get(OTHER,'value'); + current = find([MEG,EEG,OTHER] == 1); + if ~isempty(mapping_win) + set(mapping_win,'Name',[modality{current},' Mapping']) + RAW = findobj(mapping_win ,'Tag','raw'); + GRADIENT = findobj(mapping_win ,'Tag','gradient'); + MAGNETIC = findobj(mapping_win,'Tag','magnetic'); + + if current == 1 % If MEG + set([RAW,GRADIENT,MAGNETIC],'enable','on') + set(RAW,'Value',1) + else + set([RAW,GRADIENT,MAGNETIC],'enable','off') + end + end + + Visu = get(DATAPLOT,'Userdata'); + channel_select_win = findobj(0,'Tag','channel_select'); + goodchannels = setdiff([Visu.ChannelID{current}],find(Visu.Data.ChannelFlag == -1));% - min(Visu.ChannelID{current})+1; % Discard bad channels + badchannels = intersect([Visu.ChannelID{current}],find(Visu.Data.ChannelFlag == -1));% - min(Visu.ChannelID{current})+1; + + removed = findobj(channel_select_win,'Tag','removed'); + available = findobj(channel_select_win,'Tag','available'); + + + set(available,'String', cellstr(num2str(goodchannels'))); + set(removed,'String', cellstr(num2str(badchannels'))); + + set(available,'Value',1,'Max',length(get(available,'String'))) + set(removed,'Value',1,'Max',max([1,length(get(removed,'String'))])) + + %---------------------------------------------------------------------------- +case 'LoadFromDataFileList' % Load from popup menu list in the DataViewer + hDataplot = guihandles(DATAPLOT); + iFile = get(hDataplot.DataFileList,'Value'); + FileNames = get(hDataplot.DataFileList,'String'); + + dataplot_cb('loaddata',Users.CurrentData.StudyFile,FileNames{iFile}); + % dataplot_cb quicklook + + %---------------------------------------------------------------------------- +case 'LoadPrevFile' % Load previous file in the popup menu list in the DataViewer + hDataplot = guihandles(DATAPLOT); + iFile = get(hDataplot.DataFileList,'Value'); + FileNames = get(hDataplot.DataFileList,'String'); + + iFile = iFile - 1; + if iFile < 1 + iFile = 1; + return + end + + set(hDataplot.DataFileList,'Value',iFile) + dataplot_cb('loaddata',Users.CurrentData.StudyFile,FileNames{iFile}); + +% dataplot_cb quicklook + + %---------------------------------------------------------------------------- +case 'LoadNextFile' % Load previous file in the popup menu list in the DataViewer + hDataplot = guihandles(DATAPLOT); + iFile = get(hDataplot.DataFileList,'Value'); + FileNames = get(hDataplot.DataFileList,'String'); + + iFile = iFile + 1; + if iFile > length(FileNames) + iFile = length(FileNames); + return + end + + set(hDataplot.DataFileList,'Value',iFile) + dataplot_cb('loaddata',Users.CurrentData.StudyFile,FileNames{iFile}); + + % dataplot_cb quicklook + + %---------------------------------------------------------------------------- +case 'loaddata' % Only MAT BrainStorm file format is available - User has to import original data into BrainStorm otherwise + + TIME_MIN = findobj(DATAPLOT,'Tag','time_min'); + TIME_MAX = findobj(DATAPLOT,'Tag','time_max'); + + datatype = 'mat'; + % Read Study File + + cd(Users.STUDIES); + if nargin == 1 % Load the old-way + [studyfile,studypath] = uigetfile(['*brainstormstudy.',datatype], 'Enter file name for the study file'); + if (studyfile)==0, return, end + cd(studypath) + + [datafile,datapath] = uigetfile(['*data*.',datatype], 'Enter file name for data set'); + if (datafile)==0, return, end + disp('Loading data...') + cd(datapath) + elseif nargin == 3 + studyfile = varargin{1}; + datafile = varargin{2}; + end + [path,file,ext] = fileparts(studyfile); + + load(studyfile); + if ~isempty(path) + cd(path) + end + + try + Data= load(datafile); + catch + cd(Users.STUDIES) + Data= load(datafile); + end + + %KND + if iscell(Data.F) + Data.F=Data.F{1}; + end + + + Visu.Data = Data; clear Data + + set(findobj(DATAPLOT,'style','pushbutton'),'enable','on'); + + channelfile = strrep([file,ext],'brainstormstudy','channel'); + load(channelfile) + + % - Loading Subject Information + cd(Users.SUBJECTS) + if isempty(BrainStormSubject) + % errordlg('Please make sure you have assigned a Subject file to this study') + % study_editor_cb create + % + set(findobj(DATAPLOT,'Tag', 'SurfaceViewer'),'enable','off') + else + load(BrainStormSubject) + + Visu.Tesselation = Tesselation; + if isempty(Visu.Tesselation) + set(findobj(DATAPLOT,'Tag', 'SurfaceViewer'),'enable','off') + else + set(findobj(DATAPLOT,'Tag', 'SurfaceViewer'),'enable','on') + end + end + + % Check the nature of the channels + MEG_chk = []; + EEG_chk = []; + OTHER_chk = []; + + % Channel Identification + imeg = 0; % Index of MEG channels + ieeg = 0; + iother = 0; + megID = []; + eegID = []; + otherID = []; + + for chan = 1:max(size(Channel)) + if ~isempty(Channel(chan).Type) + switch Channel(chan).Type + % case {'MEG','MEG Sensor','Ref Magnetometer','Ref Gradiometer'} + % case {'MEG','Ref Magnetometer','Ref Gradiometer'} + case {'MEG','MEG Sensor'} + MEG_chk = 1; + imeg = imeg +1; + megID(imeg) = chan; + case {'EEG','EEG Sensor'} + EEG_chk = 1; + ieeg = ieeg +1; + eegID(ieeg) = chan; + otherwise + OTHER_chk = 1; + iother = iother +1; + otherID(iother) = chan; + end + end + end + + Visu.ChannelID = {megID,eegID,otherID}; + Visu.Channel = Channel; + + % Number of time samples - for GUI display purposes + nSamples = findobj(DATAPLOT,'tag','Samples'); + set(nSamples,'String',{int2str(size(Visu.Data.F,2)),'samples'}) + + set(TIME_MIN,'String',num2str(1000*min(Visu.Data.Time),5)) + set(TIME_MAX,'String',num2str(1000*max(Visu.Data.Time),5)) + + hDATAPLOT = guihandles(DATAPLOT); + + set([hDATAPLOT.MEG,hDATAPLOT.EEG,hDATAPLOT.OTHER,hDATAPLOT.RemoveEEGAverage],'enable','on') + + if isempty(MEG_chk) + set(hDATAPLOT.MEG,'enable','off','Value',0) + end + if isempty(EEG_chk) + set(hDATAPLOT.EEG,'enable','off','Value',0) + set(hDATAPLOT.RemoveEEGAverage,'enable','off') + end + if isempty(OTHER_chk ) + set(hDATAPLOT.OTHER,'enable','off') + end + + if ~isempty(MEG_chk) + set(hDATAPLOT.MEG,'value',~isempty(MEG_chk),'enable','on') + end + if ~isempty(EEG_chk) + set(hDATAPLOT.EEG,'enable','on','Value',1) + set(hDATAPLOT.RemoveEEGAverage,'value',0) + end + + if ~isempty(OTHER_chk) + set(hDATAPLOT.OTHER,'enable','on') + end + + set(DATAPLOT,'Userdata',Visu) + + htaskbar = guihandles(TASKBAR); + set(htaskbar.CurrentData,'String',... + { ['Study: ',''],... + ['Subject: ', Users.CurrentData.SubjectName],... + ['Data File: ',datafile],... + ['Database: ',Users.CurrentData.Database]... + }); + + + FileList = get(hDATAPLOT.DataFileList,'String'); + iFile = find(strcmp(FileList,datafile)); + if ~isempty(iFile) + set(hDATAPLOT.DataFileList,'Value',iFile) + end + + disp('Loading data... -> Done') + drawnow + + %---------------------------------------------------------------------------- + +case 'RemoveEEGAverage' % When box is checked, systematically displays the EEG as averaged-reference + + Visu = get(DATAPLOT,'Userdata'); + switch get(gcbo,'Value') + case 1 % Remove Average + % Get goodchannels + goodchannels = find(Visu.Data.ChannelFlag(Visu.ChannelID{2})==1); + goodchannels = Visu.ChannelID{2}(goodchannels); + if ~isfield(Visu,'DataOrig') % First time average reference is requested + Visu.DataOrig.F = Visu.Data.F; + end + Visu.Data.F(goodchannels,:) = Visu.Data.F(goodchannels,:) - repmat(mean(Visu.Data.F(goodchannels,:)),length(goodchannels),1); + ButtonName=questdlg('Would you like to overwrite the orignal EEG segment in the current data file with the averaged-reference EEG (ie current referenced EEG will be lost) or save it under another name ?',... + '','Overwrite','Save as a new file','Don''t save; just try it','Overwrite'); + switch ButtonName + case 'Overwrite' + % Save it back to the original data set file + cd(Users.STUDIES) + [path,file,ext] = fileparts(Users.CurrentData.StudyFile); + cd(path) + [path,file,ext] = fileparts(Users.CurrentData.DataFile); + if file == 0, return, end + + save_fieldnames(Visu.Data,file) + + Users.CurrentData.DataFile = file; + guidata(TASKBAR,Users) + + case 'Save as a new file' + + % Save it back to the original data set file + cd(Users.STUDIES) + [path,file,ext] = fileparts(Users.CurrentData.StudyFile); + cd(path) + [path,file,ext] = fileparts(Users.CurrentData.DataFile); + if file == 0, return, end + + file = [file,'_EEGAvrRef']; + + save_fieldnames(Visu.Data,file) + + msgbox(['Data saved in ',file]); + + Users.CurrentData.DataFile = file; + guidata(TASKBAR,Users) + + case 'Don''t save; just try it' + % Do nothing + case 'Cancel' + return + end + + otherwise % Back to original reference system + Visu.Data.F = Visu.DataOrig.F; + end + + Visu.Alter = 1; + + set(DATAPLOT,'Userdata',Visu) + dataplot_cb quicklook + + %---------------------------------------------------------------------------- +case {'set_time_min','set_time_max'} + dataplot_cb quicklook + IMAGING = findobj(0,'Tag','CorticalImaging'); + + if ~isempty(IMAGING) + dhandles = guihandles(DATAPLOT); + handles = guihandles(IMAGING); + set(handles.FromTo,'String',... + sprintf('From: %s\t To: %s (ms)', get(dhandles.time_min,'String'),get(dhandles.time_max,'String')),... + 'Userdata',[str2num(get(dhandles.time_min,'String')),str2num(get(dhandles.time_max,'String'))] ); + end + + %---------------------------------------------------------------------------- + +case 'SetPeakLatency' %0ms adjustment to data maximum peak (useful for epileptic data analysis about the maximum of the spike) + % ... prior to source localization about the spike + Visu = get(DATAPLOT,'Userdata'); + hDataPlot = guihandles(DATAPLOT); + + if get(hDataPlot.SetPeakLatency,'Value') + if length(current) > 1 + errordlg('Please select either MEG, EEG or OTHER data subset') + return + end + + % Extract channels from selected data + goodchannel = intersect(find(Visu.Data.ChannelFlag==1),[Visu.ChannelID{current}]); + F = Visu.Data.F(goodchannel,:); + + % Get value for time window of analysis + winPeak = str2num(get(hDataPlot.PeakDetectionWindow,'String')); + + % Corresponding number of samples + srate = abs(Visu.Data.Time(1)-Visu.Data.Time(2)); + winPeak = ceil(winPeak/1000/srate/2); + + % Find sample corresponding to time 0ms + zeroLat = round(abs(Visu.Data.Time(1))/srate)+1; + + % Extract data on the window of analysis: + F = F(:,zeroLat-winPeak:zeroLat+winPeak); + + % Find maximum at each time sample + [mF ,mFindx] = max((F)); + % Find absolute maximum + [mmF, mFindx] = max(mF); + + % Compute new time vector + Visu.Data.TimeOrig = Visu.Data.Time; + + % New sample number for 0ms latency + zeroLat = zeroLat-winPeak+mFindx-1; + % New time vector + Time(1) = sign(Visu.Data.Time(1))*(zeroLat-1)*srate; + Time = Time(1):srate:(Time(1)+(length(Visu.Data.Time)-1)*srate); + Visu.Data.Time = Time; + set(hDataPlot.time_min,'String',num2str(Time(1)*1000,'%3.2f')); + set(hDataPlot.time_max,'String',num2str(Time(end)*1000,'%3.2f')); + else + Visu.Data.Time = Visu.Data.TimeOrig; + end + + % Save it back to the original data set file + cd(Users.STUDIES) + [path,file,ext] = fileparts(Users.CurrentData.StudyFile); + cd(path) + [path,file,ext] = fileparts(Users.CurrentData.DataFile); + if file == 0, return, end + + save_fieldnames(Visu.Data,file) + file + + set(DATAPLOT,'Userdata',Visu) + dataplot_cb quicklook + + %---------------------------------------------------------------------------- + +case 'quicklook' + if isempty(current) + return + end + + Visu = get(DATAPLOT,'Userdata'); + + for k = 1:length(current) % For each of selected modality + previous{k} = findobj(0,'Tag',['waves_single',int2str(current(k))]); + if isempty(previous{k}) + previous{k} = open('wave_single.fig'); + set(previous{k},'color',backcolor) + set(previous{k},'Tag',['waves_single',int2str(current(k))]) + movegui(previous{k},'north'); + else + figure(previous{k}) + end + + zoom(previous{k},'on') + + if ~isempty(find(Visu.Data.ChannelFlag == -1)) + goodchannels{k} = setdiff(Visu.ChannelID{current(k)},find(Visu.Data.ChannelFlag == -1));%Discard bad channels + badchannels{k} = intersect(Visu.ChannelID{current(k)},find(Visu.Data.ChannelFlag == -1)); + else + goodchannels{k} = Visu.ChannelID{current(k)}; + badchannels{k} = []; + end + + end + + TIME_MIN = str2num(get(findobj(DATAPLOT,'Tag','time_min'),'String')); + TIME_MAX = str2num(get(findobj(DATAPLOT,'Tag','time_max'),'String')); + + if TIME_MAX > Visu.Data.Time(end) * 1000 + TIME_MAX = Visu.Data.Time(end) * 1000; + set(findobj(DATAPLOT,'Tag','time_max'),'String',num2str(TIME_MAX,5)) + end + + if TIME_MIN < Visu.Data.Time(1) * 1000 + TIME_MIN = Visu.Data.Time(1) * 1000; + set(findobj(DATAPLOT,'Tag','time_min'),'String',num2str(TIME_MIN,5)) + end + + mapping_win = findobj(0,'Tag','mapping'); + if ~isempty(mapping_win) + slider_time = findobj(mapping_win,'Tag','slider_time'); + set(slider_time,'Min',TIME_MIN,'Max',TIME_MAX) + set(slider_time,'Value',TIME_MIN) + end + + if length(Visu.Data.Time) >1 + delta_t = 1000*(Visu.Data.Time(2)-Visu.Data.Time(1)); + samples = round(([TIME_MIN:delta_t:TIME_MAX]-Visu.Data.Time(1)*1000)/delta_t)+1; + else + samples = 1; + end + % Number of time samples - for GUI display purposes + nSamples = findobj(DATAPLOT,'tag','Samples'); + set(nSamples,'String',{int2str(length(samples)),'samples'}) + + for k = 1:length(current) + figure(previous{k}) + switch current(k) + case 1 % MEG channels + try + plotwaves = plot([TIME_MIN:delta_t:TIME_MAX],Visu.Data.F(goodchannels{k},samples)); + catch + plotwaves = plot([TIME_MIN:delta_t:TIME_MAX],Visu.Data.F{1}(goodchannels{k},samples)); + end + set(plotwaves,'Tag','plotMEGwaves') + case 2 % EEG channels + try + plotwaves = plot([TIME_MIN:delta_t:TIME_MAX],Visu.Data.F(goodchannels{k},samples)); + catch + plotwaves = plot([TIME_MIN:delta_t:TIME_MAX],Visu.Data.F{1}(goodchannels{k},samples)); + end + set(plotwaves,'Tag','plotEEGwaves') + otherwise + plotwaves = plot([TIME_MIN:delta_t:TIME_MAX],Visu.Data.F(goodchannels{k},samples)); + set(plotwaves,'Tag','plotOTHERwaves') + end + + set(plotwaves,'ButtonDownFcn','dataplot_cb line_select') + set(gca,'xcolor', textcolor,'ycolor', textcolor,'fontweight','bold','Xlim',[TIME_MIN,TIME_MAX]); + grid on + set(gcf,'Userdata',plotwaves,'NumberTitle','off',... + 'Name',[modality{current(k)},' Waveforms']) + zoom(gcf,'on') + end + + %---------------------------------------------------------------------------- + +case 'line_select' % Mouse selection of a channel in the current waves_single window + Visu = get(DATAPLOT,'Userdata'); + MEG = get(findobj(DATAPLOT,'Tag','MEG'),'value'); + EEG = get(findobj(DATAPLOT,'Tag','EEG'),'value'); + OTHER = get(findobj(DATAPLOT,'Tag','OTHER'),'value'); + current = find([MEG,EEG,OTHER] == 1); + + figsingle = findobj(0,'Tag',['waves_single',int2str(current)]); % Figure of overlaping plots for the current modality + layout = findobj(0,'Tag',['layout_',modality{current}]); + + if ~isempty(find(Visu.Data.ChannelFlag == -1)) + goodchannels = setdiff([Visu.ChannelID{current}],find(Visu.Data.ChannelFlag == -1));%- min(Visu.ChannelID{current})+1 ; % Discard bad channels + badchannels = intersect([Visu.ChannelID{current}],find(Visu.Data.ChannelFlag == -1));% - min(Visu.ChannelID{current})+1; + else + goodchannels = [Visu.ChannelID{current}]; + badchannels = []; + end + + switch gcbf + case figsingle + line_qcklk = get(figsingle,'Userdata'); + set(line_qcklk,'linewidth',1) + axes_qcklk = get(line_qcklk(1),'Parent'); + + gco = get(figsingle,'CurrentObject'); + if ~strcmp(get(gco,'Type'),'line') + return % No channel has been cliked + end + ichan = (find(line_qcklk == gco)); % Number of the line plot that has been selected + current_chan = (ichan);%-min(Visu.ChannelID{current})+1 ; % Number of the Channel that has been selected + set(line_qcklk(ichan),'linewidth',2); + case layout + gco = get(layout,'CurrentAxes'); + ichan = str2num(get(gca,'Tag')); % Number of the channel that has been selected + line_qcklk(ichan) = findobj(get(gca,'Children'),'Type','line','color',frontcolor); + axes_qcklk = get(line_qcklk(ichan),'Parent'); + otherwise + return + end + + previous = findobj(0,'Tag',['single_wave_',[modality{current}],' ',Visu.Channel(goodchannels(current_chan)).Name]); % Plot exists already ? + if ~isempty(previous) + figure(previous) + return + end + + singlefig = figure; % Creation of a new window + set(singlefig','color',backcolor,'CreateFcn','movegui south','Position',get(singlefig,'Position')/1.5,'Name','') + switch gcbf + case figsingle + set(singlefig,'Tag',['single_wave_',[modality{current}],' ',Visu.Channel(goodchannels(current_chan)).Name]) + set(singlefig,'Name',['Selected Channel: ',[modality{current}],' ',Visu.Channel(goodchannels(current_chan)).Name]) + otherwise + set(singlefig,'Tag',['single_wave_',[modality{current}],' ',Visu.Channel(goodchannels(current_chan)).Name]) + set(singlefig,'Name',['Selected Channel: ',[modality{current}],' ',Visu.Channel(goodchannels(current_chan)).Name]) + end + + ax_single = copyobj(axes_qcklk,singlefig); + set(ax_single,'Visible','on','Xcolor',textcolor,'Ycolor',textcolor,'color',[1 1 1],... + 'Fontweight','bold','Position',get(0,'DefaultAxesPosition'),'Units','Normal') + grid on + + delete(get(ax_single,'children')) + wave = copyobj(line_qcklk(ichan),ax_single); + set(wave,'Linewidth',2,'color',linecolor) + + %---------------------------------------------------------------------------- + +case 'quicklook_plots' % Single axis or Multiple axis plots for waveforms + Visu = get(DATAPLOT,'USerdata'); + % Current Modality + MEG = get(findobj(DATAPLOT,'Tag','MEG'),'value'); + EEG = get(findobj(DATAPLOT,'Tag','EEG'),'value'); + OTHER = get(findobj(DATAPLOT,'Tag','OTHER'),'value'); + current = find([MEG,EEG,OTHER] == 1); + + QUICKLOOK_GUI = findobj(0,'Tag',['QUICKLOOK_GUI']); + figsingle = findobj(0,'Tag',['waves_single',int2str(current)]); % Figure of overlaping plots for the current modality + + if isempty(figsingle) + errordlg('Please load a data set first') + return + end + + % Get handles on the QUICKLOOK graphic objects + line_qcklk = get(figsingle,'Userdata');%findobj(figsingle,'Type','line'); + axes_qcklk = get(line_qcklk(1),'Parent'); + tag = get(gcbo,'TAG'); + + if ~isempty(find(Visu.Data.ChannelFlag == -1)) + goodchannels = setdiff([Visu.ChannelID{current}],find(Visu.Data.ChannelFlag == -1))- min([Visu.ChannelID{current}])+1 ; % Discard bad channels + else + goodchannels = [Visu.ChannelID{current}]- min([Visu.ChannelID{current}])+1 ; + end + + switch tag + + case 'SINGLE' + switch get(gcbo,'value') + case 0 + set(findobj(DATAPLOT,'Tag','MULTIPLE'),'Value',1) + + multi = findobj(0,'Tag',['waves_multi',int2str(current)]); + if ~isempty(multi) + set(multi,'Visible','on') + return + end + + k = length(line_qcklk); % Number of channels + c = ceil(sqrt(k)); + r = ceil(sqrt(k)); + wave_fig = get(axes_qcklk,'Parent'); + + waves_multi + multi = findobj(0,'Tag','waves_multi'); + set(multi,'Tag',[get(multi,'Tag'),int2str(current)]) + switch current + case 1 + set(gcf,'Name','MEG wave forms') + case 2 + set(gcf,'Name','EEG wave forms') + otherwise + set(gcf,'Name','Miscellaneous wave forms') + end + + Mx = get(axes_qcklk,'Xlim'); + My = get(axes_qcklk,'Ylim'); + + for kk = 1:k + ax(kk) = subplot(r,c,kk); + line_qcklk(kk)= copyobj(line_qcklk(kk),ax(kk)); + set(line_qcklk(kk),'color',frontcolor) + set(ax(kk),'xcolor', frontcolor,'ycolor', frontcolor,'fontweight','bold',... + 'XTicklabel',[],'YTicklabel',[],'Xlim',Mx,'Ylim',My,'color',backcolor,'Box','on'); + xlabel(int2str(goodchannels(kk)),'color',frontcolor,'Fontsize',8) + end + + otherwise + set(findobj(DATAPLOT,'Tag','MULTIPLE'),'Value',0) + switch current + case 1 + figure(findobj(0,'Tag',['waves_single',int2str(current)])) + case 2 + figure(findobj(0,'Tag',['waves_single',int2str(current)])) + otherwise + figure(findobj(0,'Tag',['waves_single',int2str(current)])) + end + + multi = findobj(0,'Tag',['waves_multi',int2str(current)]); + if ~isempty(multi) + set(multi,'Visible','off') + end + + end + case 'MULTIPLE' + switch get(gcbo,'value') + case 0 + set(findobj(DATAPLOT,'Tag','SINGLE'),'Value',1) + switch current + case 1 + figure(findobj(0,'Tag',['waves_single',int2str(current)])) + case 2 + figure(findobj(0,'Tag',['waves_single',int2str(current)])) + otherwise + figure(findobj(0,'Tag',['waves_single',int2str(current)])) + end + multi = findobj(0,'Tag',['waves_multi',int2str(current)]); + if ~isempty(multi) + set(multi,'Visible','off') + end + + otherwise + set(findobj(DATAPLOT,'Tag','SINGLE'),'Value',0) + + multi = findobj(0,'Tag',['waves_multi',int2str(current)]); + if ~isempty(multi) + set(multi,'Visible','on') + return + end + + k = length(line_qcklk); % Number of channels + c = ceil(sqrt(k)); + r = round(sqrt(k)); + wave_fig = get(axes_qcklk,'Parent'); + + waves_multi + multi = findobj(0,'Tag','waves_multi'); + set(multi,'Tag',[get(multi,'Tag'),int2str(current)]) + switch current + case 1 + set(gcf,'Name','MEG wave forms') + case 2 + set(gcf,'Name','EEG wave forms') + otherwise + set(gcf,'Name','Miscellaneous wave forms') + end + + Mx = get(axes_qcklk,'Xlim'); + My = get(axes_qcklk,'Ylim'); + + for kk = 1:k + ax(kk) = subplot(r,c,kk); + line_qcklk(kk)= copyobj(line_qcklk(kk),ax(kk)); + set(line_qcklk(kk),'color',frontcolor) + set(ax(kk),'xcolor', frontcolor,'ycolor', frontcolor,'fontweight','bold',... + 'XTicklabel',[],'YTicklabel',[],'Xlim',Mx,'Ylim',My,'color',backcolor,'Box','on'); + xlabel(Visu.Channel(Visu.ChannelID{current}(goodchannels(kk))).Name,'color',textcolor,'Fontsize',8) + end + end + end + %---------------------------------------------------------------------------- + +case 'Residuals' + Visu = get(DATAPLOT,'Userdata'); + + tesselation_select_win = findobj(0,'Tag','tesselation_select'); + if isempty(tesselation_select_win) + tesselation_select_win = open('tessellation_select.fig'); + end + handles = guihandles(tesselation_select_win); + + ResFiles = get(handles.ResultFiles,'String'); + ResFile = ResFiles{get(handles.ResultFiles,'Value')}; + [path,file,ext] = fileparts(Users.CurrentData.StudyFile); + ResFile = fullfile(Users.STUDIES,path,ResFile); + load(ResFile,'Fsynth','GUI','ImageGridTime'); + + if ~isempty(find(Visu.Data.ChannelFlag == -1))%~isempty(find(Visu.Data.ChannelFlag == 0)) + goodchannels = setdiff(Visu.ChannelID{current},find(Visu.Data.ChannelFlag == -1)); % - min(Visu.ChannelID{current})+1; % Discard bad channels + badchannels = intersect(Visu.ChannelID{current},find(Visu.Data.ChannelFlag == -1));% - min(Visu.ChannelID{current})+1; + else + goodchannels = Visu.ChannelID{current};% - min(Visu.ChannelID{current})+1; + badchannels = []; + end + + %F = Visu.Data.F(goodchannels,[GUI.Segment(1):GUI.Segment(end)]); % Try next line instead + F = Visu.Data.F(goodchannels,:); + + res = 100*norcol(F-Fsynth)./(norcol(F)); + clear Results; + mean_res = mean(res); + std_res = std(res); + min_res = min(res); + max_res = max(res); + + figres = figure; + powF = norm(norcol(F)); + [haxes, hline1,hline2] = plotyy(1000*ImageGridTime,res,1000*ImageGridTime,100*(norcol(F))/powF); + hold on + axes(haxes(1)) + set(haxes(1),'Ycolor','r') + set(hline1,'linewidth',2) + xlabel('Time (ms)'); + ylabel('Residuals (%)'); + + axes(haxes(2)) + set(haxes(2),'Ycolor','b') + xlabel('Time (ms)'); + ylabel('GFP (%)'); + grid on + set(hline1,'Color','r') + set(hline2,'Color','b') + grid on + + fprintf(... + 'Residuals\nAverage: %4.2f%%\nStd.:%4.2f%%\nMin.: %4.2f%%\nMax.: %4.2f%%\n',mean_res,std_res,min_res,max_res); + + set(figres,'Name','Residuals / GFP') + set(get(figres,'CurrentAxes'),'XGrid','on','YGrid','on') + + %---------------------------------------------------------------------------- + +case 'mesh_rendering' + + tesselation_select_win = findobj(0,'Tag','tesselation_select'); + if isempty(tesselation_select_win) + tesselation_select_win = open('tessellation_select.fig'); + end + + handles = guihandles(tesselation_select_win); + + set(handles.removed,'String','') + set(handles.available,'String','') + + Visu = get(DATAPLOT,'Userdata'); + if isempty(Visu) % Just Mesh rendering without any study + cd(Users.SUBJECTS); + [tessfile,tesspath] = uigetfile('*.mat','Please choose a tessellation file'); + load(fullfile(tesspath,tessfile)) + Visu.Tesselation = fullfile(tesspath,tessfile); + set(DATAPLOT,'Userdata',Visu) + set(handles.removed,'Userdata',[]) + set(handles.current_surface,'visible','off') % can't get rid of this object using GUIDE + set(handles.CurrentSurface,'String',tessfile) + else + try + load(Visu.Tesselation) + catch % Not in proper directory - try something else + cd(Users.SUBJECTS) + load(Visu.Tesselation) + end + set(handles.CurrentSurface,'String',Visu.Tesselation) + end + set(handles.light_props,'value',1) + + % How many meshes available ? + nmesh = max(size(Vertices)); + Vals = [ones(nmesh,1),zeros(nmesh,7)]; + set(handles.removed,'Userdata',Vals) + + set(handles.available,'String',Comment) + set(handles.available,'Max',length(Comment)) + + % Look for Results file in the current study folder + % Get the path + [path,file,ext] = fileparts(Users.CurrentData.StudyFile); + % Dir list and look for result files + cd(fullfile(Users.STUDIES,path)) + ResFiles = dir('*result*.mat'); + for k = 1:length(ResFiles) + ResFiles(k).name = strrep(ResFiles(k).name,'.mat',''); + end + set(handles.ResultFiles,'String',{ResFiles(:).name},'Value',1) + + + %dataplot_cb LoadResultFile + + %---------------------------------------------------------------------------- +case 'LoadResultFile' % Load selected Result File parameters for source visualization + tesselation_select_win = findobj(0,'Tag','tesselation_select'); + + if isempty(tesselation_select_win) + tesselation_select_win = open('tessellation_select.fig'); + end + handles = guihandles(tesselation_select_win); + + set(tesselation_select_win,'Pointer','watch'),drawnow + + set(handles.ZScore,'Value',0) + dataplot_cb('ToggleButtonColor',handles.ZScore) + + if nargin == 1 + ResFiles = get(handles.ResultFiles,'String'); + ResFile = ResFiles{get(handles.ResultFiles,'Value')}; + else % A result file is specified + ResFile = varargin{1}; + end + + [path,file,ext] = fileparts(Users.CurrentData.StudyFile); + ResFile = fullfile(Users.STUDIES,path,ResFile); + Results = load(ResFile); + set(handles.Refresh,'Userdata',ResFile) + + if isfield(Results,'ZScore') + if (get(handles.Baseline,'Userdata')) == 0 + try + set(handles.Baseline,'String',num2str(1000*(Results.ZScore.BaselineTime),'%4.2f ')); + if isfield(Results.ZScore,'BaselineFile') + disp(['ZScore available - baseline taken from file: ',Results.ZScore.BaselineFile]) + else + disp(['ZScore available - baseline was set to: ', num2str(1000*Results.ZScore.BaselineTime,'%4,2f '),' ms']) + end + + catch + set(handles.Baseline,'String','File'); + end + + elseif (get(handles.Baseline,'Userdata')) == 1 + set(handles.Baseline,'String','File'); + if isfield(Results.ZScore,'BaselineFile') + disp(['ZScore available - baseline taken from file: ',Results.ZScore.BaselineFile]) + else + disp(['ZScore available - baseline was set to: ', num2str(1000*Results.ZScore.BaselineTime,'%4,2f '),' ms']) + end + + end + + end + + %Load Associated Data File + cd(fullfile(Users.STUDIES,path)) + ResFiletmp = strrep(ResFile,Users.STUDIES,''); + ResFiletmp = strrep(ResFiletmp,'.mat',''); + Iunder = findstr(ResFiletmp,'_'); + DataFile= [ResFiletmp(1:Iunder(end-1)-1),'.mat']; + + if exist(DataFile,'file') + dataplot_cb('loaddata',Users.CurrentData.StudyFile,[DataFile]); + end + + + set(handles.ResultFiles,'Userdata',Results); % Save in GUI for future use + nSources = length(Results.SourceLoc); + if nSources == 0 % Probably an ImageGrid file + nSources = size(Results.ImageGridAmp,1); + BeginTime = Results.ImageGridTime(1)*1000; + EndTime = Results.ImageGridTime(end)*1000; + set(handles.CorticalMap,'enable','on') % Allo20w visualization of cortical current density maps + else + BeginTime = []; + EndTime = []; + set(handles.CorticalMap,'enable','off') + end + + DATA = {'MEG','EEG','FUSION'}; + + if ~isfield(Results,'ZScore') + ResultFileParamString= sprintf('%s\nNumber of Sources: %d\nTime window: %3.1f %3.1f msec',... + Results.Comment,nSources,BeginTime,EndTime); + else + if ~isempty(Results.ZScore.BaselineFile) + [tmp,tmpfile,ext] = fileparts(Results.ZScore.BaselineFile); + ResultFileParamString= sprintf(... + '%s-%s\nNumber of Sources: %d\nTime window: %3.1f %3.1f msec\nZScore done: baseline from file:\n%s\n%4.1f to %4.1f ms',... + Results.Comment,DATA{Results.GUI.DataType},nSources,BeginTime,EndTime,tmpfile,1000*(Results.ZScore.BaselineTime)); + else + ResultFileParamString= sprintf(... + '%s-%s\nNumber of Sources: %d\nTime window: %3.1f %3.1f msec\nZScore done: baseline set to\n%4.1f to %4.1f ms',... + Results.Comment,DATA{Results.GUI.DataType},nSources,BeginTime,EndTime,1000*(Results.ZScore.BaselineTime)); + end + end + + + + set(handles.ResultFileParam,'String',ResultFileParamString) + + % Find the corresponding tessellated envelope in the list + nsrc = size(Results.ImageGridAmp,1); % Number of cortical sources + % Is there a cortical surface available qith the same number of sources ? + Visu = get(DATAPLOT,'Userdata'); + try + load(Visu.Tesselation,'Vertices','Comment') + catch + cd(Users.SUBJECTS) + load(Visu.Tesselation,'Vertices','Comment') + end + + for k = 1:length(Vertices) + nverts(k) = size(Vertices{k},2); + end + clear Vertices + iCortex = find(nverts == nsrc); + if isempty(iCortex) + errordlg('No corresponding cortical surface was found among the available tessellated surfaces') + return + elseif length(iCortex)>1 + msgbox('Several tessellated surfaces may be corresponding to this result file. Please select the proper one manually') + return + end + + % Find the cortical surface is the list and mark it as selected + + set(handles.removed,'String',Comment{iCortex}) + set(handles.available,'String',Comment(setdiff(1:length(Comment),iCortex))) + + set(handles.CorticalMap,'Value',1); + Green = [.66 1 .43]; + Dark = [.4 .4 .4]; + set(handles.CorticalMap,'Backgroundcolor',Green,'Foregroundcolor',Dark) + + set(tesselation_select_win,'Pointer','arrow') + %---------------------------------------------------------------------------- +case 'mesh_props' % Indicate Mesh Surface Properties with Radiobuttons + Visu = get(DATAPLOT,'Userdata'); + % eval(['load ',Visu.Tesselation,' Comment']) + cd(Users.SUBJECTS) + load(Visu.Tesselation) + + nmesh = max(size(Comment)); + + tesselation_select_win = findobj(0,'Tag','tesselation_select'); + available = findobj(tesselation_select_win,'Tag','available'); + removed = findobj(tesselation_select_win,'Tag','removed'); + removeID = get(gcbo,'Value'); + IDs = get(gcbo,'String'); + mesh_names = IDs(removeID); + % current_surface = findobj(tesselation_select_win,'Tag','current_surface'); + % set(current_surface,'String',mesh_names{1}); + + OPAQUE = findobj(gcbf,'Tag','Opaque'); + TRANSPARENT = findobj(gcbf,'Tag','transparent'); + RIGHT = findobj(tesselation_select_win,'Tag','right'); + LEFT = findobj(tesselation_select_win,'Tag','left'); + FRONT = findobj(tesselation_select_win,'Tag','front'); + BACK = findobj(tesselation_select_win,'Tag','back'); + TOP = findobj(tesselation_select_win,'Tag','top'); + BOTTOM = findobj(tesselation_select_win,'Tag','bottom'); + + if length(removeID)>1 + return + end + + % Identification of the active mesh surfaces + for i = 1:length(removeID) + imesh(i) = find(strcmp(mesh_names{i},Comment)); + end + + if length(imesh)== 1 + set(findobj(gcbf,'Tag','faces'),'string',[int2str(size(Faces{imesh},1)),' Faces']) + set(findobj(gcbf,'Tag','vertices'),'string',[int2str(size(Vertices{imesh},2)), ' Vertices']) + if ~strcmp(get(gcbo,'Tag'),'removed'); return, end + end + + Vals = get(removed,'Userdata'); + if isempty(Vals) + Vals = [ones(nmesh,1),zeros(nmesh,7)]; + else + HANDLES = [OPAQUE TRANSPARENT RIGHT LEFT FRONT BACK TOP BOTTOM]; + for k = 1:length(HANDLES) + set(HANDLES(k),'Value',Vals(imesh,k)); + end + end + + %---------------------------------------------------------------------------- + +case 'camlight' % Add light in the tesselation window + previous = findobj(0,'Tag','tessellation_window'); + if isempty(previous) + return + end + figure(previous) + h = camlight('headlight'); + set(h,'color','w'); % mute the intensity of the lights + rotate3d on + %---------------------------------------------------------------------------- + +case 'remove_camlight' % Remove last camlight + previous = findobj(0,'Tag','tessellation_window'); + if isempty(previous) + return + end + figure(previous) + h = findobj(previous,'type','light'); + if isempty(h), return, end + delete(h(end)) + rotate3d on + %---------------------------------------------------------------------------- + +case 'mesh_vals' % Indicate Mesh Surface Properties with Radiobuttons + Visu = get(DATAPLOT,'Userdata'); + try + eval(['load ',Visu.Tesselation,' Comment']) + catch + cd(Users.SUBJECTS) + eval(['load ',Visu.Tesselation,' Comment']) + end + + nmesh = max(size(Comment)); + + tesselation_select_win = findobj(0,'Tag','tesselation_select'); + available = findobj(tesselation_select_win,'Tag','available'); + removed = findobj(tesselation_select_win,'Tag','removed'); + removeID = get(removed,'Value'); + IDs = get(removed,'String'); + if iscell(IDs) + mesh_names = IDs(removeID); + else + mesh_names = IDs; + end + + + current_surface = findobj(tesselation_select_win,'Tag','current_surface'); + % set(current_surface,'String',mesh_names{1}); + + OPAQUE = findobj(gcbf,'Tag','Opaque'); + TRANSPARENT = findobj(gcbf,'Tag','transparent'); + RIGHT = findobj(tesselation_select_win,'Tag','right'); + LEFT = findobj(tesselation_select_win,'Tag','left'); + FRONT = findobj(tesselation_select_win,'Tag','front'); + BACK = findobj(tesselation_select_win,'Tag','back'); + TOP = findobj(tesselation_select_win,'Tag','top'); + BOTTOM = findobj(tesselation_select_win,'Tag','bottom'); + + % Identification of the active mesh surfaces + if ~iscell(mesh_names) + imesh = find(strcmp(mesh_names,Comment)); + else + for i = 1:length(removeID) + imesh(i) = find(strcmp(mesh_names{i},Comment)); + end + end + + + Vals = get(removed,'Userdata'); + if 0%isempty(Vals) + Vals = [ones(nmesh,1),zeros(nmesh,7)]; + else + HANDLES = [OPAQUE TRANSPARENT RIGHT LEFT FRONT BACK TOP BOTTOM]; + tmp = get(HANDLES,'Value'); + Vals(imesh,:) = [tmp{:}]; + end + + set(removed,'Userdata',Vals) + + %---------------------------------------------------------------------------- +case 'ConcatenateTess' % Concatenate 2 selected envelopes into 1 + tesselation_select_win = findobj(0,'Tag','tesselation_select'); + Visu = get(DATAPLOT,'Userdata'); + removed = findobj(tesselation_select_win,'Tag','removed'); + removeID = get(removed,'Value'); + + IDs = get(removed,'String'); + if isempty(IDs) + return + end + + mesh_names = IDs(removeID); + eval(['load ',Visu.Tesselation,' Comment']) + + nmesh = max(size(Comment)); + + % Identification of the active mesh surfaces + for i = 1:length(removeID) + imesh{i} = find(strcmp(mesh_names{i},Comment)); + end + + if length(imesh) >2 + errordlg('Cannot concatenate more than 2 envelopes yet') % CHEAT - need to generalize this + return + end + + load(Visu.Tesselation,'Faces','Vertices') + Vertices{end+1} = [Vertices{imesh{1}},Vertices{imesh{2}}] ; + Faces{end+1} = [Faces{imesh{1}}; Faces{imesh{2}}+size(Vertices{imesh{1}},2)]; + Comment{end+1} = [Comment{imesh{1}},Comment{imesh{2}}] ; + + save(Visu.Tesselation,'Faces','Vertices','Comment','-append') + + dataplot_cb mesh_rendering + + %---------------------------------------------------------------------------- + +case 'SwapFaces' % Swap vertex ordering for face definition as some pacthes normal may happen to be oriented inwards and therefore look dark in 3d + + tesselation_select_win = findobj(0,'Tag','tesselation_select'); + Visu = get(DATAPLOT,'Userdata'); + removed = findobj(tesselation_select_win,'Tag','removed'); + removeID = get(removed,'Value'); + + IDs = get(removed,'String'); + if isempty(IDs) + return + end + + mesh_names = IDs(removeID); + eval(['load ',Visu.Tesselation,' Comment']) + + nmesh = max(size(Comment)); + + % Identification of the active mesh surfaces + for i = 1:length(removeID) + imesh{i} = find(strcmp(mesh_names{i},Comment)); + end + + if length(imesh) > 1 + errordlg('Cannot swap vertices of more than 1 envelope yet') % CHEAT - need to generalize this + return + end + + load(Visu.Tesselation,'Faces') + + Faces{imesh{1}} = Faces{imesh{1}}(:,[2 1 3]); + + save(Visu.Tesselation,'Faces','-append') + + dataplot_cb mesh_rendering + + + %---------------------------------------------------------------------------- + +case 'RenameTess' % Edit the Comment (ie label) of selected envelope + + tesselation_select_win = findobj(0,'Tag','tesselation_select'); + Visu = get(DATAPLOT,'Userdata'); + removed = findobj(tesselation_select_win,'Tag','removed'); + removeID = get(removed,'Value'); + + IDs = get(removed,'String'); + if isempty(IDs) + return + end + + mesh_names = IDs(removeID); + eval(['load ',Visu.Tesselation,' Comment']) + + nmesh = max(size(Comment)); + + % Identification of the active mesh surfaces + for i = 1:length(removeID) + imesh{i} = find(strcmp(mesh_names{i},Comment)); + end + + if length(imesh) > 1 + errordlg('Cannot rename more than 1 envelope yet') % CHEAT - need to generalize this + return + end + + newComment = inputdlg('Please enter a new label for the selected envelope'); + if isempty(newComment) + return + end + + Comment{imesh{1}} = newComment{:}; + + save(Visu.Tesselation,'Comment','-append') + + dataplot_cb mesh_rendering + + + + %---------------------------------------------------------------------------- +case 'DownsizeTess' + tesselation_select_win = findobj(0,'Tag','tesselation_select'); + Visu = get(DATAPLOT,'Userdata'); + removed = findobj(tesselation_select_win,'Tag','removed'); + removeID = get(removed,'Value'); + + IDs = get(removed,'String'); + if isempty(IDs) + return + end + + mesh_names = IDs(removeID); + eval(['load ',Visu.Tesselation,' Comment']) + + nmesh = max(size(Comment)); + + % Identification of the active mesh surfaces + for i = 1:length(removeID) + imesh{i} = find(strcmp(mesh_names{i},Comment)); + end + + if length(imesh) > 1 + errordlg('Cannot swap vertices of more than 1 envelope yet') % CHEAT - need to generalize this + return + end + + load(Visu.Tesselation,'Faces','Vertices') + + handles = guihandles(tesselation_select_win); + nfv.faces = Faces{imesh{1}}; + nfv.vertices = Vertices{imesh{1}}'; + + set(tesselation_select_win,'Pointer','watch') + NFV = reducepatch(nfv, str2num(get(handles.DownsizeFactor,'String'))); + + clear nfv + Faces{end+1} = NFV.faces; + Vertices{end+1} = NFV.vertices'; + clear NFV + Comment{end+1} = [Comment{imesh{1}},'_',get(handles.DownsizeFactor,'String')]; + + save(Visu.Tesselation,'Faces','Comment','Vertices', '-append') + + dataplot_cb mesh_rendering + + set(tesselation_select_win,'Pointer','arrow') + %---------------------------------------------------------------------------- +case 'mesh_add' % Add a meshed surface to visualization + Visu = get(DATAPLOT,'Userdata'); + load(Visu.Tesselation) + nmesh = max(size(Vertices)); + + tesselation_select_win = findobj(0,'Tag','tesselation_select'); + available = findobj(tesselation_select_win,'Tag','available'); + removed = findobj(tesselation_select_win,'Tag','removed'); + removeID = get(available,'Value'); + + %Check if it's a double-click + old_available = get(available,'Userdata'); + if isempty(old_available) | old_available ~= removeID % Single Click + set(available,'Userdata',removeID) + return + else + set(available,'Userdata',[]) + end + + IDs = get(available,'String'); + if isempty(IDs), return, end + chan = 1:length(get(available,'String')); + + set(available,'String', IDs(setdiff(chan,removeID))); + if isempty(get(removed,'String')) + set(removed,'String', IDs(removeID)); + else + strtmp = (get(removed,'String')); + strtmp(end+1:end+length(IDs)) = IDs(removeID) ; + set(removed,'String', sort(unique(strtmp))); + end + set(available,'String', IDs(setdiff(chan,removeID))); + set(available,'Value',1,'Max',length(get(available,'String'))) + set(removed,'Value',1,'Max',max([1,length(get(removed,'String'))])) + + %---------------------------------------------------------------------------- + +case 'delete_tess' + % Update the tessellation file (SubjectTess) by deleting the selected surface tessellation + tesselation_select_win = findobj(0,'Tag','tesselation_select'); + available = findobj(tesselation_select_win,'Tag','available'); + removed = findobj(tesselation_select_win,'Tag','removed'); + removeID = get(available,'Value'); + availableID = get(removed,'Value'); + IDs = get(removed,'String'); + if isempty(IDs) + errordlg('Please select a Tessellation in the ''Active'' listbox') + return + end + + mesh_names = IDs(availableID); + Visu = get(DATAPLOT,'Userdata'); + eval(['load ',Visu.Tesselation,' Comment']) + nmesh = max(size(Comment)); + % Identification of the active mesh surfaces + for i = 1:length(availableID) + imesh{i} = find(strcmp(mesh_names{i},Comment)); + end + ButtonName=questdlg(['Are you sure you want to delete permanently ',[Comment{[imesh{:}]}],' from the subject tessellation file ?'], ... + 'Warning', ... + 'Yes','No','No'); + switch ButtonName, + case 'No', + return + end + + set(removed,'Value',1) + set(removed,'String',setdiff(get(removed,'String'),char(Comment{[imesh{:}]}))) + + load(Visu.Tesselation) + tmp = 1:length(Comment); + Comment = Comment(setdiff(tmp,[imesh{:}])); + Vertices = Vertices(setdiff(tmp,[imesh{:}])); + Faces= Faces(setdiff(tmp,[imesh{:}])); + + save(Visu.Tesselation,'Faces','Vertices','Comment','-append') + + %---------------------------------------------------------------------------- + +case 'mesh_lighting_props' % Set lighting to Flat/Gouraud/Phong + + TessWin = findobj(0,'Tag','tessellation_window'); % Handle to the 3D display + SlidesWin = findobj(0,'Tag','subplot_tess_window'); % Handle to the Slides or Movie Window + if ~isempty(SlidesWin) + TessWin = SlidesWin; + end + + if isempty(TessWin), return, end + TessSelect = findobj(0,'Tag','tesselation_select'); + light_props = findobj(TessSelect,'Tag','light_props'); % Handle to the lighting properties pull-down menu + tmp = get(light_props,'String'); + + + try + set(findobj(TessWin,'type','patch'),'edgelighting',tmp{get(light_props,'Value')},... + 'facelighting',tmp{get(light_props,'Value')}) + + catch + if ~isempty(findstr(tmp{get(light_props,'Value')},'Flat')) + set(findobj(TessWin,'type','patch'),... + 'facecolor','flat') + else + set(findobj(TessWin,'type','patch'),... + 'facecolor','interp') + end + + end + + + %---------------------------------------------------------------------------- + +case 'mesh_remove' % Remove a mesh surface from visualization + tesselation_select_win = findobj(0,'Tag','tesselation_select'); + available = findobj(tesselation_select_win,'Tag','available'); + removed = findobj(tesselation_select_win,'Tag','removed'); + availableID = get(removed,'Value'); + Faces = findobj(tesselation_select_win,'Tag','faces'); + + %Check if it's a double-click + old_removed = get(Faces,'Userdata'); + if isempty(old_removed) | old_removed ~= availableID % Single Click + set(Faces ,'Userdata',availableID) + return + else + set(Faces ,'Userdata',[]) + end + + IDs = get(removed,'String'); + if isempty(IDs), return, end + chan = [1:length(IDs)]; + + set(removed,'Max',max([1,length(setdiff(chan,availableID))])) + set(removed,'Value',1,'String',IDs(setdiff(chan,availableID))); + + if isempty(get(available,'String')) + set(available,'String', [IDs(availableID)]); + else + strtmp = (get(available,'String')); + strtmp(end+1:end+length(IDs)) = IDs(availableID) ; + set(available,'String', sort(unique(strtmp))); + end + set(available,'Value',1,'Max',length(get(available,'String'))) + + %---------------------------------------------------------------------------- + +case 'change_color' % Changes the color of the current mesh surface + Visu = get(DATAPLOT,'Userdata'); + eval(['load ',Visu.Tesselation,' Comment']) + tesselation_select_win = findobj(0,'Tag','tesselation_select'); + available = findobj(tesselation_select_win,'Tag','available'); + removed = findobj(tesselation_select_win,'Tag','removed'); + removeID = get(removed,'Value'); + IDs = get(removed,'String'); + mesh_names = IDs(removeID); + + for i = 1:length(removeID) + imesh(i) = find(strcmp(mesh_names{i},Comment)); + end + previous = findobj(0,'Tag','tessellation_window'); + + if isempty(previous), return, end + + for k = imesh + h = findobj(previous,'Tag',Comment{k}); + corig = get(h,'Edgecolor'); + if ischar(corig) % Edgecolor 'none' + corig = get(h,'FaceVertexCdata'); + iok = find(isfinite(corig(:,1))); + corig = corig(iok,:); + corig = corig(1,:); + else + iok = []; + end + c = uisetcolor(corig, [Comment{k}, ' Color Change']); + if c == corig, return, end + + ctet = get(h,'vertices'); + vertexcolorh = get(h,'FaceVertexCData'); + siz = size(vertexcolorh,2); + if size(vertexcolorh ,1) == 1 + vertexcolorh = ones(size(ctet,1),1)*vertexcolorh; + end + if 0%size(ctet,1)~=length(iok) + vertexcolorh(tmp,:) = NaN * ones(length(tmp),siz); + end + + vertexcolorh(iok,:) = ones(length(iok),1)*c ; + set(h,'FaceVertexCData',vertexcolorh,'Userdata',c) + if isempty(iok) + set(h,'edgecolor',c) + end + + end + + %-------------------------------------------------------------------------------------------------------------------- + +case 'saveas' % Save the new data set as... + MEG = get(findobj(DATAPLOT,'Tag','MEG'),'value'); + EEG = get(findobj(DATAPLOT,'Tag','EEG'),'value'); + OTHER = get(findobj(DATAPLOT,'Tag','OTHER'),'value'); + current = find([MEG,EEG,OTHER] == 1); + + Visu = get(DATAPLOT,'Userdata'); + + Data = Visu.Data; + + TASKBAR = findobj(0,'Tag','TASKBAR'); + Current = get_user_directory; + cd(Current.STUDIES) + [FILENAME, PATHNAME] = uiputfile('*data*mat', 'New File Name'); + if FILENAME == 0, return, end + cd(PATHNAME) + + [F,Device,ChannelFlag,Time,NoiseCov,SourceCov,Projector,Comment] = ... + deal(Data.F,Data.Device,Data.ChannelFlag,Data.Time,Data.NoiseCov,Data.SourceCov,Data.Projector,Data.Comment); + clear Data + save(FILENAME,'Device','ChannelFlag','F','Time','NoiseCov','SourceCov','Projector','Comment'); + + I = findstr(FILENAME,['_data']); + if isempty(I) + error('File name should contain ''_data'' string ') + return + end + + channelfile = FILENAME(1:I-1); + channelfile = [channelfile,'_channel']; + Channel = Visu.Channel; + save (channelfile,'Channel') + + %---------------------------------------------------------------------------- +case 'create_filter_window' + filter_wind = findobj(0,'Tag','filter_win'); + if isempty(filter_wind) + filter_win + end + filter_wind = findobj(0,'Tag','filter_win'); + set(0,'CurrentFigure',filter_wind) + + %---------------------------------------------------------------------------- +case 'data_filter' % Filter the data and update visualization + filter_win = findobj(0,'Tag','filter_win'); + lpf = findobj(filter_win,'Tag','LPF'); % Low Cut-off + hpf = findobj(filter_win,'Tag','HPF'); % High Cut-off + AVG = findobj(filter_win,'Tag','average'); % Remove average + lpf = str2num(get(lpf,'string')); + hpf = str2num(get(hpf,'string')); + AVG = get(AVG,'Value'); + + Visu = get(DATAPLOT,'Userdata'); + MEG = get(findobj(DATAPLOT,'Tag','MEG'),'value'); + EEG = get(findobj(DATAPLOT,'Tag','EEG'),'value'); + OTHER = get(findobj(DATAPLOT,'Tag','OTHER'),'value'); + current = find([MEG,EEG,OTHER] == 1); + + if ~isempty(find(Visu.Data.ChannelFlag == -1))%~isempty(find(Visu.Data.ChannelFlag == 0)) + goodchannels = setdiff(Visu.ChannelID{current},find(Visu.Data.ChannelFlag == -1)); % - min(Visu.ChannelID{current})+1; % Discard bad channels + badchannels = intersect(Visu.ChannelID{current},find(Visu.Data.ChannelFlag == -1));% - min(Visu.ChannelID{current})+1; + else + %KND + goodchannels = [Visu.ChannelID{current}];% - min(Visu.ChannelID{current})+1; + badchannels = []; + end + + srate = abs(1/(Visu.Data.Time(2)-Visu.Data.Time(1))); + new_waves = zeros(size(Visu.Data.F));%Visu.ChannelID{current},:))); + %tmp = brainstorm_filt(Visu.Data.F(goodchannels+min(Visu.ChannelID{current})-1,:),srate,0,lpf); + tmp = brainstorm_filt(Visu.Data.F(goodchannels,:),srate,0,lpf); + new_waves(goodchannels,:) = tmp; clear tmp + % SB 11/10/00 back to space average which is the so-called reference in EEG + %JCM 10/27/00 changed the average to be the average in time, not space + if(1) + if 0%AVG == 1 % Remove average + new_waves(goodchannels,:) = new_waves(goodchannels,:) - repmat(mean(new_waves(goodchannels,:)),length(goodchannels),1); + end + else + if AVG == 1 % Remove average in time from each channel + AVG_MAX = Visu.Data.Time(end)*1000; % milliseconds + AVG_MIN = Visu.Data.Time(1)*1000; + prompt={'Enter the starting time (ms)','Enter the ending time (ms)'}; + def={sprintf('%g',AVG_MIN),sprintf('%g',AVG_MAX)}; + dlgTitle='Input the times to use for average removal'; + answer = inputdlg(prompt,dlgTitle,1,def); + if(isempty(answer)), + disp('Cancelled') + return + else + AVG_MIN = sscanf(answer{1},'%g'); + AVG_MAX = sscanf(answer{2},'%g'); + end + AVG_MIN = min(find(Visu.Data.Time >= AVG_MIN/1000)); % convert to index + AVG_MAX = max(find(Visu.Data.Time <= AVG_MAX/1000)); + if(isempty(AVG_MIN) | isempty(AVG_MAX)), % nothing + disp('Invalid time values') + return; + end + if(AVG_MAX < AVG_MIN), % bogus + disp('Inconsistent time values') + return; + end + + tmp = mean(new_waves(goodchannels,AVG_MIN:AVG_MAX)')'; % mean in time for each channel + new_waves(goodchannels,:) = new_waves(goodchannels,:) - tmp(:,ones(1,size(new_waves,2)),:); + end + end + % Visu.Data.F(goodchannels+min(Visu.ChannelID{current})-1 ,:) = new_waves(goodchannels,:); + Visu.Data.F(goodchannels,:) = new_waves(goodchannels,:); + Visu.Alter = 1; + + set(DATAPLOT,'Userdata',Visu); + dataplot_cb quicklook + + %---------------------------------------------------------------------------- +case 'show_fragment' + Visu = get(DATAPLOT,'Userdata'); + tesselation_select_win = findobj(0,'Tag','tesselation_select'); + available = findobj(tesselation_select_win,'Tag','available'); + removed = findobj(tesselation_select_win,'Tag','removed'); + availableID = get(removed,'Value'); + IDs = get(removed,'String'); + if isempty(IDs), return, end + chan = [1:length(IDs)]; + + load(Visu.Tesselation,'Clusters','Comment') + + if ~exist('Clusters','var') % No fragmentation available for this tessellation file + return + end + + % Identification of the active mesh surfaces + for i = 1:length(IDs) + imesh(i) = find(strcmp(IDs(i),Comment)); + end + + Vals = get(removed,'Userdata'); + if isempty(Vals) + Vals = [ones(nmesh,1),zeros(nmesh,8)]; + end + + % Check for clusters for the current surface (highlighted surface name in the "selected text listbox") + FragmentMenu = findobj(gcbf,'Tag','FragmentMenu'); + nsurf = imesh(availableID); % Selected surface + + dataplot_cb mesh_props + + if ~exist('Clusters','var') % Classes were not defined beforehand + set(FragmentMenu,'String','No Fragmentation','Value',1); + return + end + + if isempty(Clusters{nsurf}) % No cluster available for surface 'nsurf' + set(FragmentMenu,'String','No Fragmentation','Value',1); + return + else + if isempty(Clusters{nsurf}.Seed)% No fragmentation availlable for this surface + set(FragmentMenu,'String','No Fragmentation'); + else + sstring = {'No Fragmentation'}; + for k = 1:length(Clusters{nsurf}.Seed) + sstring{k+1} = int2str(Clusters{nsurf}.Seed(k)); + end + set(FragmentMenu,'String',sstring,'Value',2) + end + end + + %---------------------------------------------------------------------------- + +case 'SelectCorticalSpot' + + TessWin = findobj(0,'tag','tessellation_window'); + if isempty(TessWin) + TessWin = open('tessellation_window.fig'); + set(TessWin,'CurrentAxes',findobj(TessWin,'Tag','MainAxis')); + end + + figure(TessWin) + hold on + + if nargin == 1 + ginput(1); + else + TessWin = (varargin{2}); + figure(TessWin) + hold on + end + + MainAxes = findobj(TessWin,'Tag','MainAxes'); + + scurrent = plot3(0,0,0,'o','parent',MainAxes,'Markersize',5,'Markerfacecolor','r'); + set(scurrent,'visible','off') + + vertices = get(findobj(MainAxes,'type','patch'),'vertices'); + % Search for intersection with cortical nodes which + % are considered as visible only, ie FVCData ~- NaN + vert_isnan = get(findobj(MainAxes,'type','patch'),'FaceVertexCdata'); + if size(vert_isnan,2) == 1 + vert_isnan = find(~isnan(vert_isnan)); + else + vert_isnan = sum(vert_isnan ,2); + vert_isnan = find(~isnan(vert_isnan)); + end + + vertices = vertices(vert_isnan,:); + + x = vertices(:,1)'; + y = vertices(:,2)'; + z = vertices(:,3)'; + clear vertices + + if nargin == 1 % Vertex selection from a manual probe + + % Calcul de la distance minimale du cortex a la droite C + C = get(MainAxes,'Currentpoint'); + + % Vecteur directeur du rayon d'observation + u = (C(1,:)-C(2,:))'/norm(C(1,:)-C(2,:)); + + algo = 'new'; + if algo == 'old' + %% --------- Ancien algo + + normals = get(cortex,'Userdata'); + scalaire = normals*u'; + %%clear normals + %inorm = find(scalaire > mean(scalaire)+std(scalaire)/2); + %inorm = find(scalaire >= 0); + inorm = 1:length(scalaire); + + tempo = [C(1,1)-x(inorm);C(1,2)-y(inorm);C(1,3)-z(inorm)]; + distboite = norcol(tempo); + [minn,im] = min(distboite); + %% ------------------------ + else + %% ----------- Nouvel algo + inorm= 1:length(x); + tempo1 = [C(1,1)-x(inorm);C(1,2)-y(inorm);C(1,3)-z(inorm)]; + DIR = u * ones(1,length(inorm)); + tempo = cross(tempo1,DIR); + clear DIR + distdroite = norcol(tempo); + distboite = norcol(tempo1); + distdroite = distdroite/max(distdroite); + distboite = distboite/max(distboite); + [minn,im] = min(.04*distboite+0.96*distdroite); + clear tempo tempo1 + %% ----------- + end % algo + + clear tempo + + % Affiche source courante + + else % Point location is passed as an argument + inorm= 1:length(x); + im = varargin{1}; + end + + set(scurrent,'Xdata',x(inorm(im)),'Ydata',y(inorm(im)),'Zdata',z(inorm(im))) + drawnow + + set(scurrent,'visible','on') + if isempty(im) + warndlg('Please reselect your location','Location error') + end + + hTessWin = guihandles(TessWin); + TessWinUserData = get(TessWin,'Userdata'); + if isempty(TessWinUserData) + TessWinUserData = struct('CorticalSpots',[],'CorticalMarkers',[],'CorticalMarkersLabels',[]); + elseif ~isfield(TessWinUserData, 'CorticalSpots') + TessWinUserData.CorticalSpots = []; + TessWinUserData.CorticalMarkers = []; + TessWinUserData.CorticalMarkersLabels = []; + end + + % TessWinUserData.CorticalSpots = [TessWinUserData.CorticalSpots,inorm(im)]; + TessWinUserData.CorticalSpots = [TessWinUserData.CorticalSpots,vert_isnan(inorm(im))]; + if size(TessWinUserData.CorticalMarkers,1) > 1 + TessWinUserData.CorticalMarkers = TessWinUserData.CorticalMarkers'; + end + + TessWinUserData.CorticalMarkers = [TessWinUserData.CorticalMarkers,scurrent]; + + htext = text(1.05*x(inorm(im)),1.05*y(inorm(im)),1.05*z(inorm(im)),int2str(length(TessWinUserData.CorticalSpots))); + set(htext,'FontWeight','normal','color','g','Fontname','helvetica','Fontsize',10,'FontUnits','Point') + + TessWinUserData.CorticalMarkersLabels = [TessWinUserData.CorticalMarkersLabels,htext]; + + set(TessWin,'Userdata',TessWinUserData); + + CorticalSpotList = cell(length(TessWinUserData.CorticalSpots),1); + for k = 1:length(TessWinUserData.CorticalSpots) + CorticalSpotList{k} = k; + end + tesselation_select_win = findobj(0,'Tag','tesselation_select'); + hTessSelect = guihandles(tesselation_select_win); + if length(CorticalSpotList) == 1 + set(hTessSelect.CorticalSpotList,'String',{get(TessWinUserData.CorticalMarkersLabels,'string')}... + ,'Max',length(CorticalSpotList),'Value',1) + else + set(hTessSelect.CorticalSpotList,'String',get(TessWinUserData.CorticalMarkersLabels,'string')... + ,'Max',length(CorticalSpotList),'Value',1) + end + + rotate3d on + + + %---------------------------------------------------------------------------- +case 'DeleteCorticalSpot' + tesselation_select_win = findobj(0,'Tag','tesselation_select'); + hTessSelect = guihandles(tesselation_select_win); + SelectedArea = get(hTessSelect.CorticalSpotList,'Value'); + + AreaLabels = get(hTessSelect.CorticalSpotList,'String'); + + % Update label information + ResidualAreas = setdiff([1:length(AreaLabels)],SelectedArea); + + set(hTessSelect.CorticalSpotList,'String',AreaLabels(ResidualAreas),'Value',1,'Max',length(AreaLabels)); + + TessWin = findobj(0,'tag','tessellation_window'); + hTessWin = guihandles(TessWin); + TessWinUserData = get(TessWin,'Userdata'); + + TessWinUserData.CorticalSpots = TessWinUserData.CorticalSpots(ResidualAreas); + delete(TessWinUserData.CorticalMarkers(SelectedArea)) + delete(TessWinUserData.CorticalMarkersLabels(SelectedArea)) + TessWinUserData.CorticalMarkers = TessWinUserData.CorticalMarkers(ResidualAreas); + TessWinUserData.CorticalMarkersLabels = TessWinUserData.CorticalMarkersLabels(ResidualAreas); + + set(TessWin,'Userdata',TessWinUserData); + + %---------------------------------------------------------------------------- + +case 'ImportScoutsFromMRI' % Create a scout file from points selected in the MR + + TessWin = findobj(0,'tag','tessellation_window'); + tesselation_select_win = findobj(0,'Tag','tesselation_select'); + hTessSelect = guihandles(tesselation_select_win); + + cd(Users.STUDIES); + [filename, pathname] = uigetfile('*.txt', 'Select an MRI Point File'); + if filename == 0, return, end + CorticalScouts = struct('MarkersLabels','','CorticalSpots',[],'CorticalMarkers',[]); + + [Number,CorticalScouts.CorticalMarkersLabels,X,Y,Z,Xmri,Ymri,Zmri] = ... + textread(fullfile(pathname,filename),'%d %s %f %f %f %f %f %f',-1); + clear Number Xmri Ymri Zmri + + % What's the name of the current cortical envelope ? + ActiveTess = get(hTessSelect.removed,'String'); % Find the active Cortical surface + iCortex = get(hTessSelect.removed,'Value'); + if iscell(ActiveTess) + ActiveTess= ActiveTess{iCortex}; + end + + % What's the name of the current data set ? + ResultFiles = get(hTessSelect.ResultFiles,'String'); + ResultFile = ResultFiles{get(hTessSelect.ResultFiles,'Value')}; + + % XYZ Coordinates of the scouts + Visu = get(DATAPLOT,'Userdata'); + cd(Users.SUBJECTS) + load(Visu.Tesselation,'Comment','Vertices') + % What's the current surface ? + imesh = find(strcmp(Comment,ActiveTess)); + Vertices = Vertices{imesh}; + X = X/1000; + Y = Y/1000; + Z = Z/1000; + + for k = 1:length(X) % Find closest vertex in the tessellation + [mm CorticalScouts.CorticalSpots(k)] = min(norcol(... + [Vertices(1,:)-X(k); ... + Vertices(2,:)-Y(k); ... + Vertices(3,:)- Z(k)])); + CorticalScouts.CorticalMarkers(k,:) = Vertices(:,CorticalScouts.CorticalSpots(k))'; + end + CorticalScouts.CorticalMarkers = Vertices(:,CorticalScouts.CorticalSpots)'; + clear Vertices + + CorticalScouts + + % Create Cortical Scout File + cd(Users.STUDIES); + [filename, pathname] = uiputfile(['CorticalScoutFromMRI.mat'], 'Save Cortical Scout in...'); + save(fullfile(pathname,filename), 'ResultFile','ActiveTess','CorticalScouts','-mat'); + + % Visualization + dataplot_cb('LoadCorticalSpot',fullfile(pathname,filename)) + + + %---------------------------------------------------------------------------- + +case 'CorticalScoutFromThres' % Create a cortical scout file with areas above a given %-threshold + TessWin = findobj(0,'tag','tessellation_window'); + CorticalScouts = get(TessWin,'Userdata'); + tesselation_select_win = findobj(0,'Tag','tesselation_select'); + hTessSelect = guihandles(tesselation_select_win); + + % Let's do it simple first + Results = get(hTessSelect.ResultFiles,'Userdata'); + M = max(abs(Results.ImageGridAmp),[],2); % Take maximum over time + MAX = max(M); + cThres = str2num(get(hTessSelect.TruncateFactor,'string'))/100; + + % Indices of the sources above threshold + CorticalScouts.CorticalSpots = find(M >= cThres*MAX); + + + % What's the name of the current cortical envelope ? + ActiveTess = get(hTessSelect.removed,'String'); % Find the active Cortical surface + iCortex = get(hTessSelect.removed,'Value'); + if iscell(ActiveTess) + ActiveTess= ActiveTess{iCortex}; + end + + % XYZ Coordinates of the scouts + Visu = get(DATAPLOT,'Userdata'); + cd(Users.SUBJECTS) + load(Visu.Tesselation,'Comment','Vertices') + % What's the current surface ? + imesh = find(strcmp(Comment,ActiveTess)); + Vertices = Vertices{imesh}; + CorticalScouts.CorticalMarkers = Vertices(:,CorticalScouts.CorticalSpots)'; + clear Vertices + for k =1 : length(CorticalScouts.CorticalSpots) + CorticalScouts.CorticalMarkersLabels{k} = int2str(k); + end + + + % What's the name of the current data set ? + ResultFiles = get(hTessSelect.ResultFiles,'String'); + ResultFile = ResultFiles{get(hTessSelect.ResultFiles,'Value')}; + + cd(Users.STUDIES) + dirr = fileparts(Users.CurrentData.StudyFile); + cd(dirr); + + % Create Cortical Scout File + [filename, pathname] = uiputfile(['CorticalScoutFromThresh_',get(hTessSelect.TruncateFactor,'string'),'.mat'], 'Save Cortical Scout in...'); + save(fullfile(pathname,filename), 'ResultFile','ActiveTess','CorticalScouts','-mat'); + + % Visualization + dataplot_cb('LoadCorticalSpot',fullfile(pathname,filename)) + + + %---------------------------------------------------------------------------- + +case 'LoadCorticalSpot' % Load cortical scout locations + TessWin = findobj(0,'tag','tessellation_window'); + + if nargin == 1 + + CorticalScouts = get(TessWin,'Userdata'); + if ~isempty(CorticalScouts) % some exist - remove + if isfield(CorticalScouts,'CorticalMarkers') + delete(CorticalScouts.CorticalMarkers) + delete(CorticalScouts.CorticalMarkersLabels) + clear CorticalScouts + end + end + + cd(Users.STUDIES) + dirr = fileparts(Users.CurrentData.StudyFile); + cd(dirr); + + [filename, pathname] = uigetfile('CorticalScout.mat', 'Save Cortical Scout in...'); + if filename == 0 + return + end + load(fullfile(pathname,filename), 'ResultFile','ActiveTess','CorticalScouts','-mat'); + else + load(varargin{1}, 'ResultFile','ActiveTess','CorticalScouts','-mat'); + end + + figure(TessWin), hold on + MainAxes = findobj(TessWin,'Tag','MainAxes'); + vertices = get(findobj(MainAxes,'type','patch'),'vertices'); + x = vertices(:,1)'; + y = vertices(:,2)'; + z = vertices(:,3)'; + clear vertices + + CorticalScouts.CorticalMarkers = zeros(length(CorticalScouts.CorticalSpots),1); + for k = 1:length(CorticalScouts.CorticalSpots) + scurrent = plot3(0,0,0,'o','parent',MainAxes,'Markersize',5,'Markerfacecolor','r'); + set(scurrent,'visible','off') + set(scurrent,'Xdata',x(CorticalScouts.CorticalSpots(k)),... + 'Ydata',y(CorticalScouts.CorticalSpots(k)),... + 'Zdata', z(CorticalScouts.CorticalSpots(k))) + set(scurrent,'visible','on') + CorticalScouts.CorticalMarkers(k) = scurrent; + + r_text =1.1; + + if iscell(CorticalScouts.CorticalMarkersLabels) + htext = text(r_text*x(CorticalScouts.CorticalSpots(k)),... + r_text*y(CorticalScouts.CorticalSpots(k)),... + r_text*z(CorticalScouts.CorticalSpots(k)),... + CorticalScouts.CorticalMarkersLabels{k}); + else + htext = text(r_text*x(CorticalScouts.CorticalSpots(k)),... + r_text*y(CorticalScouts.CorticalSpots(k)),... + r_text*z(CorticalScouts.CorticalSpots(k)),... + CorticalScouts.CorticalMarkersLabels); + + end + + + set(htext,'FontWeight','normal','color','g','Fontname','helvetica',... + 'Fontsize',10,'FontUnits','Point') + + Label_tmp(k) = htext; + end + + tesselation_select_win = findobj(0,'Tag','tesselation_select'); + hTessSelect = guihandles(tesselation_select_win); + set(hTessSelect.CorticalSpotList,'String',CorticalScouts.CorticalMarkersLabels,... + 'Value',1,'Max', length(CorticalScouts.CorticalMarkersLabels)) + + CorticalScouts.CorticalMarkersLabels = Label_tmp; clear Label_tmp; + + set(TessWin,'Userdata',CorticalScouts); + + figure(TessWin) + rotate3d on + + %---------------------------------------------------------------------------- +case 'SaveCorticalSpot' % Save cortical scout locations + % Cortical scouts need to be indexed with both the corresponding cortical surface name + % and the current data set name + + TessWin = findobj(0,'tag','tessellation_window'); + CorticalScouts = get(TessWin,'Userdata'); + + CorticalScouts.CorticalMarkersLabels = get(CorticalScouts.CorticalMarkersLabels,'String'); + CorticalScouts.CorticalMarkers = [get(CorticalScouts.CorticalMarkers,'Xdata');... + get(CorticalScouts.CorticalMarkers,'Ydata');... + get(CorticalScouts.CorticalMarkers,'Zdata')]; + if iscell( CorticalScouts.CorticalMarkers) + CorticalScouts.CorticalMarkers = [CorticalScouts.CorticalMarkers{:}]; + end + + CorticalScouts.CorticalMarkers = reshape(CorticalScouts.CorticalMarkers,length(CorticalScouts.CorticalMarkersLabels),3); + % CorticalScouts.CorticalMarkers is now Number of Scouts x 3 + + tesselation_select_win = findobj(0,'Tag','tesselation_select'); + hSelect = guihandles(tesselation_select_win); + + % What's the name of the current cortical envelope ? + ActiveTess = get(hSelect.removed,'String'); % Find the active Cortical surface + iCortex = get(hSelect.removed,'Value'); + if iscell(ActiveTess) + ActiveTess= ActiveTess{iCortex}; + end + + if isfield(CorticalScouts,'CorticalProbePatches') % Process members of cortical patches to get their x,y,z's + Visu = get(DATAPLOT,'Userdata'); + try + load(Visu.Tesselation,'Comment','Vertices') + catch + cd(Users.SUBJECTS) + load(Visu.Tesselation,'Comment','Vertices') + end + % What's the current surface ? + imesh = find(strcmp(Comment,ActiveTess)); + Vertices = Vertices{imesh}; + CorticalScouts.CorticalProbePatchesXYZ = cell(size(CorticalScouts.CorticalProbePatches)); + for k=1:length(CorticalScouts.CorticalProbePatches) % for each area + CorticalScouts.CorticalProbePatchesXYZ{k} = ... + Vertices(:,CorticalScouts.CorticalProbePatches{k}); + end + + end + + % What's the name of the current data set ? + ResultFiles = get(hSelect.ResultFiles,'String'); + ResultFile = ResultFiles{get(hSelect.ResultFiles,'Value')}; + + cd(Users.STUDIES) + dirr = fileparts(Users.CurrentData.StudyFile); + cd(dirr); + + [filename, pathname] = uiputfile('CorticalScout.mat', 'Save Cortical Scout in...'); + save(fullfile(pathname,filename), 'ResultFile','ActiveTess','CorticalScouts','-mat'); + + %---------------------------------------------------------------------------- +case 'RenameCorticalSpot' + tesselation_select_win = findobj(0,'Tag','tesselation_select'); + hTessSelect = guihandles(tesselation_select_win); + SelectedArea = get(hTessSelect.CorticalSpotList,'Value'); + SelectedArea = SelectedArea(1); % only one at a time please + + AreaLabels = get(hTessSelect.CorticalSpotList,'String'); + + Title = ''; + Prompt = sprintf('Please enter a new label for %s',AreaLabels{SelectedArea}); + newLabel = inputdlg(Prompt,Title); + if isempty(newLabel), return, end + + AreaLabels{SelectedArea} = newLabel{:} ; + + set(hTessSelect.CorticalSpotList,'String',AreaLabels) + + TessWin = findobj(0,'tag','tessellation_window'); + TessWinUserData = get(TessWin,'Userdata'); + set(TessWinUserData.CorticalMarkersLabels(SelectedArea),'String',newLabel{:}) + set(TessWin,'Userdata',TessWinUserData); + + %---------------------------------------------------------------------------- +case 'ZScoreThreshold' + %Thresholded ZScore map in terms of multiples of sigmas in the baseline + + tesselation_select_win = findobj(0,'Tag','tesselation_select'); + hTSelect = guihandles(tesselation_select_win); + TessWin = findobj(0,'tag','tessellation_window'); + %if isempty(TessWin), return, end + %hTessWin = guihandles(TessWin); + + %TessWinUserData = get(TessWin,'Userdata'); + + % Are we switching from ZScore to Absolute mapping or vice-versa ? + Results = get(hTSelect.ResultFiles,'Userdata'); + set(hTSelect.AbsoluteCurrent,'Value',1) + + switch get(hTSelect.ZScoreThresholdApply,'Value') + case 1 % Switch to ZScore map and threshold it + if isempty(Results), return, end % No results were loaded + + if ~isfield(Results,'ZScore') % No ZScore was defined beforehand + dataplot_cb ZScore % Compute the ZScore map + Results = get(hTSelect.ResultFiles,'Userdata'); + end + + set(hTSelect.ZScore,'Value',1); + dataplot_cb('ToggleButtonColor',hTSelect.ZScore) + + ZThres = str2num(get(hTSelect.ZScoreThreshold,'String')); + if isempty(ZThres) + set(hTSelect.ZScoreThreshold,'String',2); % Apply Default + ZThres = str2num(get(hTSelect.ZScoreThreshold,'String')); + end + + Results.ZScore.ImageGridZ.ZThres = ZThres; + + set(hTSelect.ZScoreThreshold,'Userdata',ZThres / max(Results.ImageGridAmp(:))) + + case 0 + % Don't apply thresholded map + set(hTSelect.ZScoreThreshold,'Userdata',0) + + end + + + %Results.ImageGridAmp(iZeroed) = eps ; %Thresholded Map + + % Update display + set(hTSelect.ResultFiles,'Userdata',Results); + + dataplot_cb ScaleColormap + %dataplot_cb tesselation_select_done + %---------------------------------------------------------------------------- +case 'IntegSTHistogram' % Integrate Saptio-temporal histogram overtime + tesselation_select_win = findobj(0,'Tag','tesselation_select'); + hTSelect = guihandles(tesselation_select_win); + + avResults = get(hTSelect.ResultFiles,'Userdata'); + if ~isfield(avResults,'HistoSrc'), return, end % This is not an histogram result set + + % Integrate over time + figure, imagesc(avResults.ImageGridTime, 1:size(avResults.ImageGridAmp,1),avResults.ImageGridAmp) + colorbar + avResults.ImageGridAmp = max(avResults.ImageGridAmp,[],2) *ones(1,size(avResults.ImageGridAmp,2)); + + + set(hTSelect.ResultFiles,'Userdata',avResults); + + + %---------------------------------------------------------------------------- + +case 'STHistogram' % Spatio-temporal histogram of activations (in terms of ZScore) on a series of result files + + tesselation_select_win = findobj(0,'Tag','tesselation_select'); + hTSelect = guihandles(tesselation_select_win); + + % Look for Results file in the current study folder + % Get the path + [path,file,ext] = fileparts(Users.CurrentData.StudyFile); + % Dir list and look for result files + cd(fullfile(Users.STUDIES,path)) + ResFiles = dir('*result*.mat'); + for k = 1:length(ResFiles) + ResFiles(k).name = strrep(ResFiles(k).name,'.mat',''); + end + [ResSelect,ok] = listdlg('Liststring',{ResFiles.name},'Selectionmode','multiple','Name','Please select one or sevral result files',... + 'listsize',[400 400]); + + if ok == 0 + return + end + + hw = waitbar(0,['Computing the spatio-temporal histogram of activation maps through a set of ',int2str(length(ResSelect)),' result files...']); + nDataFiles = 0; + + % Get Value for Z threshold + ZThres = str2num(get(hTSelect.ZScoreThreshold,'String')); + if isempty(ZThres) + set(hTSelect.ZScoreThreshold,'String',2); % Apply Default + ZThres = str2num(get(hTSelect.ZScoreThreshold,'String')); + end + + + for k = 1:length(ResSelect) + dataplot_cb('LoadResultFile',ResFiles(ResSelect(k)).name) + ResFiles(ResSelect(k)).name + Results = get(hTSelect.ResultFiles,'Userdata'); + + if k == 1 + avResults = Results; + avResults.ImageGridAmp = 0* avResults.ImageGridAmp; % ImageGridAmp becomes a hit-count table - + % cell (i,j) will indicate how many times source i had a ZScore > ZThres at time j + + avResults.HistoSrc = cell(length(ResSelect)); + end + + if isfield(Results,'ZScore') + +% % Adapt length to the length of shortest result file +% cropMin = 0; +% cropMax = 0; +% if Results.Time(1) > avResults.Time(1) +% cropMin = Results.Time(1)-avResults.Time(1); +% avResults.ImageGridAmp = avResults.ImageGridAmp(:,cropMin+1:end); +% avResults.ImageGridTime = avResults.ImageGridTime(:,cropMin+1:end); +% avResults.Fsynth = avResults.Fsynth(:,cropMin+1:end); +% avResults.ZScore.ImageGridZ.Amp = avResults.ZScore.ImageGridZ.Amp(:,cropMin+1:end); +% +% end +% if Results.Time(1) < avResults.Time(1) +% cropMin = -Results.Time(1)+avResults.Time(1); +% % Results.ImageGridAmp = Results.ImageGridAmp(:,cropMin+1:end); +% Results.ZScore.ImageGridZ.Amp = Results.ZScore.ImageGridZ.Amp(:,cropMin+1:end); +% Results.Time = Results.Time(cropMin+1:end); +% end +% if Results.Time(end) > avResults.Time(end) +% cropMax = Results.Time(end)-avResults.Time(end); +% % Results.ImageGridAmp = Results.ImageGridAmp(:,1:end-cropMax); +% Results.ZScore.ImageGridZ.Amp = Results.ZScore.ImageGridZ.Amp(:,1:end-cropMax); +% Results.Time = Results.Time(1:end-cropMax); +% end +% if Results.Time(end) < avResults.Time(end) +% cropMax = -Results.Time(end)+avResults.Time(end); +% avResults.ImageGridAmp = avResults.ImageGridAmp(:,1:end-cropMax); +% avResults.ImageGridTime = avResults.ImageGridTime(:,1:end-cropMax); +% avResults.Fsynth = avResults.Fsynth(:,1:end-cropMax); +% avResults.ZScore.ImageGridZ.Amp = avResults.ZScore.ImageGridZ.Amp(:,1:end-cropMax); +% end +% + avResults.Time = Results.Time; + + Results.ZScore.ImageGridZ.Amp = (Results.ImageGridAmp)-repmat(Results.ZScore.ImageGridZ.mean,1,size(Results.ImageGridAmp,2)); + iStd = spdiags(1./Results.ZScore.ImageGridZ.std, 0, length(Results.ZScore.ImageGridZ.std), length(Results.ZScore.ImageGridZ.std)) ; + Results.ZScore.ImageGridZ.Amp = abs(iStd*Results.ZScore.ImageGridZ.Amp); + + avResults.HistoSrc{k} = sparse(Results.ZScore.ImageGridZ.Amp > ZThres); + % avResults.HistoSrc{k} stores the source indices and time where sources had greater ZScore that ZThres + avResults.ImageGridAmp = avResults.ImageGridAmp + avResults.HistoSrc{k} ; + + if k == 1 + prev = figure; + end + + nDataFiles = nDataFiles + 1; + k + figure(prev) + plot(avResults.ImageGridTime, sum(avResults.HistoSrc{k},1)) + + + else % No ZScore available - skip file + + disp([ResFiles(ResSelect(k)).name,' does not contain any ZScore map - skip file']) + + end + + waitbar(k/length(ResSelect)) + + end + + avResults.ImageGridAmp = 100*avResults.ImageGridAmp/nDataFiles; % Scale to 100% hits + + cd(Users.STUDIES); + [path,file,ext] = fileparts(Users.CurrentData.StudyFile); + cd(path) + c = clock; + ResFiletmp = strrep(ResFiles(ResSelect(end)).name,Users.STUDIES,''); + ResFiletmp = strrep(ResFiletmp,'.mat',''); + Iunder = findstr(ResFiletmp,'_'); + DataFile= [ResFiletmp(1:Iunder(end)-1)]; + + newname = ([DataFile sprintf('_HISTO_%02.0f%02.0f',c(4:5)) ext]); + i = 0; + while(exist(newname,'file')), + i = i+1; % subtract another minute + c(5) = mod(c(5) - 1,60); + newname = ([DataFile sprintf('_HISTO_%02.0f%02.0f',MethodeCode,c(4),c(5)) ext]); + end + + save_fieldnames(avResults,newname) + + delete(hw) + + msgbox(['Average activation maps saved in ', newname]) + set(hTSelect.ResultFiles,'Userdata',avResults) + + % Refresh results list + dataplot_cb mesh_rendering + ResFiles = get(hTSelect.ResultFiles,'String'); + iFile = find(strcmp(ResFiles,strrep(newname,'.mat',''))); + set(hTSelect.ResultFiles,'Value',iFile) + + + + %---------------------------------------------------------------------------- + +case 'AverageMaps' % Average activations across multiple result files + + tesselation_select_win = findobj(0,'Tag','tesselation_select'); + hTSelect = guihandles(tesselation_select_win); + + % Look for Results file in the current study folder + % Get the path + [path,file,ext] = fileparts(Users.CurrentData.StudyFile); + % Dir list and look for result files + cd(fullfile(Users.STUDIES,path)) + ResFiles = dir('*result*.mat'); + for k = 1:length(ResFiles) + ResFiles(k).name = strrep(ResFiles(k).name,'.mat',''); + end + [ResSelect,ok] = listdlg('Liststring',{ResFiles.name},'Selectionmode','multiple','Name','Please select one or sevral result files',... + 'listsize',[400 400]); + + if ok == 0 + return + end + + % Now compute ZScore for each of the selected files + hw = waitbar(0,['Averaging activation maps through a set of ',int2str(length(ResSelect)),' result files...']); + for k = 1:length(ResSelect) + dataplot_cb('LoadResultFile',ResFiles(ResSelect(k)).name) + ResFiles(ResSelect(k)).name + Results = get(hTSelect.ResultFiles,'Userdata'); + + if k == 1 + avResults = Results; + avResults.ImageGridAmp = avResults.ImageGridAmp/length(ResSelect); + end + + % !!!!!!Suppose all result files were computed on the same time window - discard what's commented below + +% % Adapt length to the length of shortes result file +% cropMin = 0; +% cropMax = 0; +% if Results.Time(1) > avResults.Time(1) +% cropMin = Results.Time(1)-avResults.Time(1); +% avResults.ImageGridAmp = avResults.ImageGridAmp(:,cropMin+1:end); +% avResults.ImageGridTime = avResults.ImageGridTime(:,cropMin+1:end); +% avResults.Time = avResults.Time(:,cropMin+1:end); +% avResults.Fsynth = avResults.Fsynth(:,cropMin+1:end); +% +% if isfield(Results,'ZScore') +% avResults.ZScore.ImageGridZ.Amp = avResults.ZScore.ImageGridZ.Amp(:,cropMin+1:end); +% end +% end +% if Results.Time(1) < avResults.Time(1) +% cropMin = -Results.Time(1)+avResults.Time(1); +% Results.ImageGridAmp = Results.ImageGridAmp(:,cropMin+1:end); +% Results.ImageGridTime = Results.ImageGridTime(:,cropMin+1:end); +% Results.Time = Results.Time(:,cropMin+1:end); +% if isfield(Results,'ZScore') +% Results.ZScore.ImageGridZ.Amp = Results.ZScore.ImageGridZ.Amp(:,cropMin+1:end); +% end +% end +% if Results.Time(end) > avResults.Time(end) +% cropMax = Results.Time(end)-avResults.Time(end); +% Results.ImageGridAmp = Results.ImageGridAmp(:,1:end-cropMax); +% Results.ImageGridTime = Results.ImageGridTime(:,1:end-cropMax); +% Results.Time = Results.Time(:,1:end-cropMax); +% if isfield(Results,'ZScore') +% Results.ZScore.ImageGridZ.Amp = Results.ZScore.ImageGridZ.Amp(:,1:end-cropMax); +% end +% end +% if Results.Time(end) < avResults.Time(end) +% cropMax = -Results.Time(end)+avResults.Time(end); +% avResults.ImageGridAmp = avResults.ImageGridAmp(:,1:end-cropMax); +% avResults.ImageGridTime = avResults.ImageGridTime(:,1:end-cropMax); +% avResults.Fsynth = avResults.Fsynth(:,1:end-cropMax); +% avResults.Time = avResults.Time(:,1:end-cropMax); +% if isfield(Results,'ZScore') +% avResults.ZScore.ImageGridZ.Amp = avResults.ZScore.ImageGridZ.Amp(:,1:end-cropMax); +% end +% end + + if k == 1 + prev = figure; + if isfield(Results,'ZScore') + Results.ZScore.ImageGridZ.Amp = (Results.ImageGridAmp)-repmat(Results.ZScore.ImageGridZ.mean,1,size(Results.ImageGridAmp,2)); + iStd = spdiags(1./Results.ZScore.ImageGridZ.std, 0, length(Results.ZScore.ImageGridZ.std), length(Results.ZScore.ImageGridZ.std)) ; + Results.ZScore.ImageGridZ.Amp = abs(iStd*Results.ZScore.ImageGridZ.Amp); + end + end + + if k >1 + avResults.ImageGridAmp = avResults.ImageGridAmp + (Results.ImageGridAmp)/length(ResSelect); + if isfield(Results,'ZScore') + avResults.ZScore.ImageGridZ.Amp = avResults.ZScore.ImageGridZ.Amp + (Results.ZScore.ImageGridZ.Amp)/length(ResSelect); + end + end + figure(prev) + plot(Results.ImageGridTime,mean(abs(Results.ImageGridAmp),1),'b'), hold on + plot(avResults.ImageGridTime,mean(abs(avResults.ImageGridAmp),1),'r'), hold off + + waitbar(k/length(ResSelect)) + + end + + %avResults.ImageGridAmp = avResults.ImageGridAmp/length(ResSelect); +% if isfield(avResults,'ZScore') +% avResults.ZScore.ImageGridZ.Amp = avResults.ZScore.ImageGridZ.Amp/length(ResSelect); +% end + + cd(Users.STUDIES); + [path,file,ext] = fileparts(Users.CurrentData.StudyFile); + cd(path) + c = clock; + ResFiletmp = strrep(ResFiles(ResSelect(end)).name,Users.STUDIES,''); + ResFiletmp = strrep(ResFiletmp,'.mat',''); + Iunder = findstr(ResFiletmp,'_'); + DataFile= [ResFiletmp(1:Iunder(end-1)-1)]; + + newname = ([DataFile sprintf('_AVRresults_%02.0f%02.0f',c(4:5)) ext]); + i = 0; + while(exist(newname,'file')), + i = i+1; % subtract another minute + c(5) = mod(c(5) - 1,60); + newname = ([DataFile sprintf('_AVRresults_%02.0f%02.0f',MethodeCode,c(4),c(5)) ext]); + end + + save_fieldnames(avResults,newname) + + delete(hw) + + msgbox(['Average activation maps saved in ', newname]) + set(hTSelect.ResultFiles,'Userdata',avResults) + + % Refresh results list + dataplot_cb mesh_rendering + ResFiles = get(hTSelect.ResultFiles,'String'); + iFile = find(strcmp(ResFiles,strrep(newname,'.mat',''))); + set(hTSelect.ResultFiles,'Value',iFile) + + %---------------------------------------------------------------------------- + +case 'DefineBaseline' + tesselation_select_win = findobj(0,'Tag','tesselation_select'); + hTSelect = guihandles(tesselation_select_win); + set(hTSelect.ZScore,'Value',1) + dataplot_cb('ToggleButtonColor',hTSelect.ZScore) + + if ~strcmp(get(hTSelect.Baseline,'String'),'File') + set(hTSelect.Baseline,'Userdata',0) % FLAG: Baseline defined on current file + end + + dataplot_cb('ZScore',1) % Force ZScore computation + + %---------------------------------------------------------------------------- + +case 'LoadBaseline' % Define Baseline from a different result file + cd(Users.STUDIES) + [resfile, respath] = uigetfile('*result*.mat','Please Select a Result File Containing the Activation Baseline'); + if resfile == 0, return, end + tesselation_select_win = findobj(0,'Tag','tesselation_select'); + hTSelect = guihandles(tesselation_select_win); + set(hTSelect.LoadBaseline,'userdata',fullfile(respath, resfile)); + set(hTSelect.Baseline,'String','File'); + set(hTSelect.Baseline,'Userdata',1) % FLAG: Baseline defined on a pre-loaded file + + dataplot_cb DefineBaseline + + %---------------------------------------------------------------------------- +case 'ZScoreBatch' % Compute ZScore on a set of Result files + + % Look for Results file in the current study folder + % Get the path + [path,file,ext] = fileparts(Users.CurrentData.StudyFile); + % Dir list and look for result files + cd(fullfile(Users.STUDIES,path)) + ResFiles = dir('*result*.mat'); + for k = 1:length(ResFiles) + ResFiles(k).name = strrep(ResFiles(k).name,'.mat',''); + end + [ResSelect,ok] = listdlg('Liststring',{ResFiles.name},'Selectionmode','multiple','Name','Please select one or sevral result files',... + 'listsize',[400 400]); + + if ok == 0 + return + end + + % Now compute ZScore for each of the selected files + hw = waitbar(0,['Computing ZScore for the selected set of ',int2str(length(ResSelect)),' result files...']); + for k = 1:length(ResSelect) + dataplot_cb('LoadResultFile',ResFiles(ResSelect(k)).name) + ResFiles(ResSelect(k)).name + tesselation_select_win = findobj(0,'Tag','tesselation_select'); + hTSelect = guihandles(tesselation_select_win); + set(hTSelect.ZScore,'Value',1) + dataplot_cb('ToggleButtonColor',hTSelect.ZScore) + dataplot_cb('ZScore',1) % Force the computation of the ZScore for each of the file (ie force test to 1 in dataplot_cb ZScore) + waitbar(k/length(ResSelect)) + end + set(hTSelect.Baseline,'Userdata',0) + + delete(hw) + + %---------------------------------------------------------------------------- +case 'ZScore' % Switches from Z-Score to Absolute current density mapping + + tesselation_select_win = findobj(0,'Tag','tesselation_select'); + hTSelect = guihandles(tesselation_select_win); + TessWin = findobj(0,'tag','tessellation_window'); + + switch get(hTSelect.ZScore,'Value') + case 1 % Switch to ZScore map + + % Are we switching from ZScore to Absolute mapping or vice-versa ? + set(hTSelect.AbsoluteCurrent,'Value',1) + + if get(hTSelect.Baseline,'Userdata') == 1 % Baseline is defined from an existing result file, load it + Results = load(get(hTSelect.LoadBaseline,'Userdata')); + FlagBaselineFile = 1; + else % else use current loaded results + Results = get(hTSelect.ResultFiles,'Userdata'); + FlagBaselineFile = 0; + end + + if isempty(Results), return, end % No results were loaded + + test = 0; % By default, consider not computing a new ZScore - check if a previously computation is available - + % if test is set to 1 anytime below, this will conduct to a new ZScore computation either because the baseline has changed + % or the computation is conducted on a new set of data. + if nargin == 1 + varargin{1} = 0; + end + + if (~isfield(Results,'ZScore') | varargin{1} == 1) % No ZScore was defined beforehand or computation is forced + Results.ZScore.Baseline = [-Inf Inf]; % Dummy Score + test = 1; % Force ZScore computation + end + + %Get baseline time limits + if FlagBaselineFile == 1 + set(hTSelect.Baseline,'String',... + [num2str(Results.ImageGridTime(1)*1000,'%3.1f'),' ',num2str(Results.ImageGridTime(end)*1000,'%3.1f')]); + + baseline = [Results.ImageGridTime(1) Results.ImageGridTime(end)]; + else + baseline = str2num(get(hTSelect.Baseline,'String'))/1000; + end + + if length(baseline) < 2 + errordlg('Please enter 2 time values to define the baseline interval') + return + end + + if isfield(Results.ZScore,'FlagBaselineFile') % Was this ZScore computed from a stored baseline ? + if Results.ZScore.FlagBaselineFile == 0 % No - check new baseline limits + % Get exact time samples closest to these values + if round(1000*baseline(1)) < round(1000*Results.ImageGridTime(1)) | round(1000*baseline(2)) > round(1000*Results.ImageGridTime(end)) % Accuracy set to the ms + errordlg('Please set baseline time extremas within the available time window for this result file') + return + end + + [tmp, tmin] = min(abs(Results.ImageGridTime-baseline(1))); + [tmp, tmax] = min(abs(Results.ImageGridTime-baseline(2))); + + baseline_new = [tmin tmax]; + + test = (sum(baseline_new == ... + Results.ZScore.Baseline) ~= 2); % New baseline requested - recompute Z-Score + + elseif test ~= 1 % Yes - Baseline was stored in a file - do not recompute the ZScore + + [tmp, tmin] = min(abs(Results.ImageGridTime-baseline(1))); + [tmp, tmax] = min(abs(Results.ImageGridTime-baseline(2))); + + test = 0; + + elseif varargin{1} == 1 % Force computation + [tmp, tmin] = min(abs(Results.ImageGridTime-baseline(1))); + [tmp, tmax] = min(abs(Results.ImageGridTime-baseline(2))); + + test = 1; + + end + + else + [tmp, tmin] = min(abs(Results.ImageGridTime-baseline(1))); + [tmp, tmax] = min(abs(Results.ImageGridTime-baseline(2))); + + test = 1; + end + + if 1%test + + set(tesselation_select_win,'Pointer','watch') + + disp('Computing a new Z-Score map from this baseline...'), drawnow + + disp(sprintf('Baseline is %d time samples large (out of %d samples available)',... + tmax-tmin+1, length(Results.ImageGridTime))) + + % Store original results patter in memory + if 1%(~isfield(Results,'ImageGridAmpOrig') & FlagBaselineFile == 0) | varargin{1} == 1 + Results.ImageGridAmpOrig = Results.ImageGridAmp; + end + + if FlagBaselineFile == 0 % remove the zero here !!! + Results.ImageGridAmp = Results.ImageGridAmpOrig; + end + + %Results.ImageGridAmp = abs(Results.ImageGridAmp); + + + % Compute ZScores on that baseline + Results.ZScore.Baseline = [tmin tmax]; + Results.ZScore.BaselineTime = [baseline(1) baseline(2)]; + + % Average activity and its standard deviation over the baseline, for each source + Results.ZScore.ImageGridZ.mean = ... + mean((Results.ImageGridAmp(:,tmin:tmax)),2); + Results.ZScore.ImageGridZ.std = ... + std((Results.ImageGridAmp(:,tmin:tmax)),0,2); + Results.ZScore.ImageGridZ.std = ... + Results.ZScore.ImageGridZ.std + max(Results.ZScore.ImageGridZ.std)*eps; % Avoid devide-by-zero issues + + if ~isempty(get(hTSelect.CorrectionFactor,'String')) + Results.ZScore.ImageGridZ.std = str2num(get(hTSelect.CorrectionFactor,'String'))*Results.ZScore.ImageGridZ.std; + end + + % Compute Z-Scores + if FlagBaselineFile == 1 % Now load current results + ZScore = Results.ZScore; + Results = get(hTSelect.ResultFiles,'Userdata'); + Results.ZScore = ZScore; clear ZScore + if ~isfield(Results,'ImageGridAmpOrig') + Results.ImageGridAmpOrig = []; + end + if isempty(Results.ImageGridAmpOrig) + Results.ImageGridAmpOrig = Results.ImageGridAmp; % Store original current density map + end + Results.ImageGridAmp = Results.ImageGridAmpOrig; + Results.ZScore.BaselineFile = get(hTSelect.LoadBaseline,'Userdata'); + Results.ZScore.FlagBaselineFile = 1; + else + Results.ZScore.FlagBaselineFile = 0; + Results.ZScore.BaselineFile = ''; + end + + disp('Updating Result File...'), drawnow + Results.ImageGridAmp = Results.ImageGridAmpOrig; + tmp = Results.ImageGridAmpOrig; + Results.ImageGridAmpOrig = []; + save_fieldnames(Results,get(hTSelect.Refresh,'Userdata')); + %Results.ImageGridAmp = Results.ZScore.ImageGridZ.Amp; + Results.ImageGridAmpOrig = tmp; clear tmp + + Results.ImageGridAmp = (Results.ImageGridAmp)-repmat(Results.ZScore.ImageGridZ.mean,1,size(Results.ImageGridAmp,2)); + iStd = spdiags(1./Results.ZScore.ImageGridZ.std, 0, length(Results.ZScore.ImageGridZ.std), length(Results.ZScore.ImageGridZ.std)) ; + + %Results.ZScore.ImageGridZ.Amp = Results.ImageGridAmp; + + disp('Computing a new Z-Score map from this baseline...-> DONE'), drawnow + + else % Compute ZScore from saved mean and std maps + + Results.ImageGridAmpOrig = Results.ImageGridAmp; + %Results.ImageGridAmp= Results.ZScore.ImageGridZ.Amp ; + + Results.ImageGridAmp = (Results.ImageGridAmp)-repmat(Results.ZScore.ImageGridZ.mean,1,size(Results.ImageGridAmp,2)); + iStd = spdiags(1./Results.ZScore.ImageGridZ.std, 0, length(Results.ZScore.ImageGridZ.std), length(Results.ZScore.ImageGridZ.std)) ; + + end + + %Results.ImageGridAmp = rand(size(iStd*Results.ImageGridAmp)); + Results.ImageGridAmp = abs(iStd*Results.ImageGridAmp); + + set(tesselation_select_win,'Pointer','arrow') + + case 0 % Switch to absolute current density mapping + + Results = get(hTSelect.ResultFiles,'Userdata'); + + disp('Previous Z-Score map on same baseline is used'), drawnow + Results.ImageGridAmp = Results.ImageGridAmpOrig; + set(hTSelect.ZScoreThresholdApply,'Value',0) + dataplot_cb('ToggleButtonColor',hTSelect.ZScoreThresholdApply) + end + + % Update display + set(hTSelect.ResultFiles,'Userdata',Results); + + dataplot_cb tesselation_select_done + + %---------------------------------------------------------------------------- + + +case 'AnalyzeCorticalMap' % Spatio-temporal segmentation of the cortical current density maps + + tesselation_select_win = findobj(0,'Tag','tesselation_select'); + hTessSelect = guihandles(tesselation_select_win); + available = findobj(tesselation_select_win,'Tag','available'); + removed = findobj(tesselation_select_win,'Tag','removed'); + availableID = get(removed,'Value'); + IDs = get(removed,'String'); + if isempty(IDs), return, end + + % Identification of the active mesh surfaces + Visu = get(DATAPLOT,'Userdata'); + + try + load(Visu.Tesselation,'Comment') + catch + cd(Users.SUBJECTS) + load(Visu.Tesselation,'Comment') + end + + if ~iscell(IDs) + imesh = find(strcmp(IDs,Comment)); + end + + nsurf = imesh(availableID); % Selected surface + + TessWin = findobj(0,'tag','tessellation_window'); + hTessWin = guihandles(TessWin); + TessWinUserData = get(TessWin,'Userdata'); + + if ~isfield(TessWinUserData,'VertConn') % Vertex connectivity not available here - load or compute + nsurfaces = length(Comment); + + % ----------------- Load current surface parameters - + load(Visu.Tesselation,'Faces','Vertices') + FV.faces = Faces{nsurf}; clear Faces + FV.vertices = Vertices{nsurf}'; clear Vertices + % ----------------- Load current surface parameters - DONE + + load(Users.CurrentData.SubjectFile,'VertConn'); % Is the vertex connectivity available ? + + if isempty(VertConn) % Vertex connectivity was not defined before + VertConn = cell(nsurfaces,1); + else + if exist(VertConn,'file') + load(VertConn) + else + VertConn = cell(nsurfaces,1); + end + end + + if isempty(VertConn{nsurf}) % Compute the vertex connectivity + [pathname, filename] = fileparts(Visu.Tesselation); + VertConn{nsurf} = vertices_connectivity(FV); + clear FV + VertConnFile = [filename,'_vertconn.mat']; + + % Save this file in the current subject folder + [path,name,ext] = fileparts(Users.CurrentData.SubjectFile); + if ~isempty(path) + cd(path) + else + cd(Users.SUBJECTS) + end + + save(VertConnFile,'VertConn'); + + VertConn = VertConnFile; + save(Users.CurrentData.SubjectFile,'VertConn','-append') + else % Load VC from the file + load(Users.CurrentData.SubjectFile,'VertConn'); + end + load(VertConn); + VertConn = VertConn{nsurf}; + TessWinUserData.VertConn = VertConn; clear VertConn; + end + + + % Get Z threshold + ZThres = str2num(get(hTessSelect.ZScoreThreshold,'String')) ; + + % Vertices which maximum Z score is above ZThres + Results = get(hTessSelect.ResultFiles,'Userdata'); + ImageGridAmp = Results.ImageGridAmp; clear Results + + % Get baseline info + Results = get(hTessSelect.ResultFiles,'userdata'); + AnalyzeWin = [0 370]/1000; % in sec + [tmp, tmin] = min(abs(Results.ImageGridTime-AnalyzeWin(1))); + [tmp, tmax] = min(abs(Results.ImageGridTime-AnalyzeWin(2))); + clear Results + + %Baseline = Results.ZScore.Baseline; clear Results + %Time = Baseline(end)+1:size(ImageGridAmp,2); %setdiff([1:size(ImageGridAmp,2)],[Baseline(1):Baseline(end)]); + + Time = [tmin:tmax]; + + MaxVerts = max(ImageGridAmp(:,Time)'); + iThres = find(MaxVerts > ZThres); + % Threshold in terms of correlation + CorrThres = .9; + + Scout{1} = []; + iScout = 1; + imax = 1; + + % Sort MaxVerts + [s, iMaxVertsSort] = sort(MaxVerts); clear s + + CONNEX = 0; % if 1; enforce connexity of each clusters + + + while ~isempty(imax) + %%%% Segmentation process + if iScout == 1 % We are starting from scratch + % Initialization + % Start region growing from the vertex with maximum ZScore + imax = iMaxVertsSort(end); + else + tmp = setdiff(iMaxVertsSort,[Scout{:}]); + [mm, imax] = max(MaxVerts(tmp)); clear mm + imax = tmp(imax); + end + + % Does imax checks iThres + if isempty(find(iThres == imax)) + break + end + + + if CONNEX == 1 + % Look for his neighbors + voiz = patch_swell(imax,TessWinUserData.VertConn); + % Select those that check the Zthres condition + voiz = intersect(iThres,voiz); + + else % Just look for all the points above ZThres + voiz = setdiff(iThres,imax); + end + + % Remove those previously selected + voiz = setdiff(voiz,[Scout{:}]); + + % Compute correlation coefficients of their time series + %CorrCoeff = abs(corrcoef(ImageGridAmp([imax,voiz],Time)')); + if ~isempty(voiz) + CorrCoeff = abs(ImageGridAmp(imax,Time)/norm(ImageGridAmp(imax,Time)))... + *abs(ImageGridAmp(voiz,Time)'*inorcol(ImageGridAmp(voiz,Time)')); + else + CorrCoeff = CorrThres/10; + end + + % Apply Correlation Threshold + %icorr = find(CorrCoeff(1,2:end)>CorrThres); + icorr = find(CorrCoeff>CorrThres); + + Scout{iScout} = [imax,voiz(icorr)]; + + while ~isempty(icorr) + + if CONNEX == 1 + voiz = patch_swell(voiz(icorr),TessWinUserData.VertConn); + % Select those that check the Zthres condition + voiz = intersect(iThres,voiz); + else + voiz = setdiff(iThres,imax); + end + + % Remove those previously selected + voiz = setdiff(voiz,[Scout{:}]); + + % Compute correlation coefficients of their time series + %CorrCoeff = abs(corrcoef(ImageGridAmp([imax,voiz],Time)')); + if ~isempty(voiz) + CorrCoeff = abs(ImageGridAmp(imax,Time)/norm(ImageGridAmp(imax,Time)))... + *abs(ImageGridAmp(voiz,Time)'*inorcol(ImageGridAmp(voiz,Time)')); + + CorrCoeffBaseLine = abs(ImageGridAmp(imax,125:500)/norm(ImageGridAmp(imax,125:500)))... + *abs(ImageGridAmp(voiz,125:500)'*inorcol(ImageGridAmp(voiz,125:500)')); + else + + CorrCoeff = CorrThres/10; + end + + % Apply Correlation Threshold + %icorr = find(CorrCoeff(1,2:end)>CorrThres); + icorr = find(CorrCoeff>CorrThres); + + Scout{iScout} = [Scout{iScout},voiz(icorr)]; + + end + + if length([Scout{:}]) ~= length(unique([Scout{:}])) + keyboard + end + + length(iThres) - length([Scout{:}]) + + CorticalScouts.CorticalProbePatches(iScout) = Scout(iScout); + CorticalScouts.CorticalMarkersLabels{iScout} = int2str(iScout); + CorticalScouts.CorticalSpots(iScout) = imax; + if iScout == 1 + load(Visu.Tesselation,'Vertices') + end + CorticalScouts.CorticalMarkers(iScout,:) = Vertices{nsurf}(:,Scout{iScout}(1)); + + iScout = iScout+1; + disp('') + + + end + + newScout = 1; + + for iScout = 1:length(Scout) + if length(Scout{iScout}) == 1 % Single point scout: do we keep it ? + % Check if its neighbors with activation < ZThres have time series that correlate with his with corrcoeff > CorrThres + voiz = patch_swell(Scout{iScout},TessWinUserData.VertConn); + + if ~isempty(voiz) + CorrCoeff = abs(ImageGridAmp(imax,Time)/norm(ImageGridAmp(imax,Time)))... + *abs(ImageGridAmp(voiz,Time)'*inorcol(ImageGridAmp(voiz,Time)')); + else + CorrCoeff = CorrThres/10; + end + + icorr = find(CorrCoeff>CorrThres); + + if isempty(icorr) + disp('Remove spurious Scout') + % Scout{iScout} = []; + % iScout = iScout - 1; + else + newCorticalScouts.CorticalProbePatches(newScout) = Scout(iScout); + newCorticalScouts.CorticalMarkersLabels{newScout} = int2str(newScout); + newCorticalScouts.CorticalSpots(newScout) = Scout{iScout}(1); + if newScout == 1 + load(Visu.Tesselation,'Vertices') + end + newCorticalScouts.CorticalMarkers(newScout,:) = Vertices{nsurf}(:,Scout{iScout}(1)); + newScout = newScout + 1; + end + + else + newCorticalScouts.CorticalProbePatches(newScout) = Scout(iScout); + newCorticalScouts.CorticalMarkersLabels{newScout} = int2str(newScout); + newCorticalScouts.CorticalSpots(newScout) = Scout{iScout}(1); + if newScout == 1 + load(Visu.Tesselation,'Vertices') + end + newCorticalScouts.CorticalMarkers(newScout,:) = Vertices{nsurf}(:,Scout{iScout}(1)); + newScout = newScout + 1; + end + + clear CorticalScouts + CorticalScouts = newCorticalScouts; + + end + + + CorticalScouts.VertConn = TessWinUserData.VertConn; + CorticalScouts.CorticalProbeDepth = ones(1,length(Scout)); + + cd(Users.STUDIES) + + length(CorticalScouts.CorticalProbePatches) + + save test CorticalScouts + + %---------------------------------------------------------------------------- +case 'ShowClusters' % Show all cortical scouts/clusters at once + tesselation_select_win = findobj(0,'Tag','tesselation_select'); + hTessSelect = guihandles(tesselation_select_win); + available = findobj(tesselation_select_win,'Tag','available'); + removed = findobj(tesselation_select_win,'Tag','removed'); + availableID = get(removed,'Value'); + IDs = get(removed,'String'); + if isempty(IDs), return, end + + % Identification of the active mesh surfaces + Visu = get(DATAPLOT,'Userdata'); + + try + load(Visu.Tesselation,'Comment') + catch + cd(Users.SUBJECTS) + load(Visu.Tesselation,'Comment') + end + + if ~iscell(IDs) + imesh = find(strcmp(IDs,Comment)); + end + + nsurf = imesh(availableID); % Selected surface + + TessWin = findobj(0,'tag','tessellation_window'); + hTessWin = guihandles(TessWin); + TessWinUserData = get(TessWin,'Userdata'); + + if ~isfield(TessWinUserData,'VertConn') % Vertex connectivity not available here - load or compute + nsurfaces = length(Comment); + + % ----------------- Load current surface parameters - + load(Visu.Tesselation,'Faces','Vertices') + FV.faces = Faces{nsurf}; clear Faces + FV.vertices = Vertices{nsurf}'; clear Vertices + % ----------------- Load current surface parameters - DONE + + load(Users.CurrentData.SubjectFile,'VertConn'); % Is the vertex connectivity available ? + + if isempty(VertConn) % Vertex connectivity was not defined before + VertConn = cell(nsurfaces,1); + else + if exist(VertConn,'file') + load(VertConn) + else + VertConn = cell(nsurfaces,1); + end + end + + if isempty(VertConn{nsurf}) % Compute the vertex connectivity + [pathname, filename] = fileparts(Visu.Tesselation); + VertConn{nsurf} = vertices_connectivity(FV); + VertConnFile = [filename,'_vertconn.mat']; + + % Save this file in the current subject folder + [path,name,ext] = fileparts(Users.CurrentData.SubjectFile); + if ~isempty(path) + cd(path) + else + cd(Users.SUBJECTS) + end + + save(VertConnFile,'VertConn'); + + VertConn = VertConnFile; + save(Users.CurrentData.SubjectFile,'VertConn','-append') + else % Load VC from the file + load(Users.CurrentData.SubjectFile,'VertConn'); + end + load(VertConn); + VertConn = VertConn{nsurf}; + TessWinUserData.VertConn = VertConn; clear VertConn; + end + + % Now grow a patch around the selected probe and selected neighbors + + SelectedArea = get(hTessSelect.CorticalSpotList,'Value'); +% if ~isfield(TessWinUserData,'CorticalProbePatches') +% TessWinUserData.CorticalProbePatches = cell(get(hTessSelect.CorticalSpotList,'Max'),1); +% TessWinUserData.CorticalProbeDepth = zeros(get(hTessSelect.CorticalSpotList,'Max'),1); % Number of patch_swells necessary to achieve the patch growth; +% end +% if length(TessWinUserData.CorticalProbePatches) < SelectedArea % User just added a new probe +% nareas = length(TessWinUserData.CorticalProbePatches); +% TessWinUserData.CorticalProbePatches(nareas+1:SelectedArea) = cell(SelectedArea-nareas,1); +% TessWinUserData.CorticalProbeDepth(nareas+1:SelectedArea) = 0; +% end + +% if isempty(TessWinUserData.CorticalProbePatches{SelectedArea}) % No areas around patches were defined +% TessWinUserData.CorticalProbeDepth(SelectedArea) = 1; +% TessWinUserData.CorticalProbePatches{SelectedArea} = ... +% [TessWinUserData.CorticalSpots(SelectedArea),patch_swell(TessWinUserData.CorticalSpots(SelectedArea),TessWinUserData.VertConn)]; +% else +% TessWinUserData.CorticalProbeDepth(SelectedArea) = TessWinUserData.CorticalProbeDepth(SelectedArea) +1; +% TessWinUserData.CorticalProbePatches{SelectedArea} = [TessWinUserData.CorticalProbePatches{SelectedArea},... +% patch_swell([TessWinUserData.CorticalProbePatches{SelectedArea}],TessWinUserData.VertConn)]; +% end + + ActiveTess = get(hTessSelect.removed,'String'); % Find the active scalp surface + iCortex = get(hTessSelect.removed,'Value'); % Find the active scalp surface + if isempty(iCortex) + h = msgbox('Please select a cortex surface from the tessellations available.'); + return + end + if iscell(ActiveTess) + Cortex = findobj(get(TessWin,'CurrentAxes'),'Type','patch','Tag',ActiveTess{iCortex}); %CHEAT - need to be improved ? + else + Cortex = findobj(get(TessWin,'CurrentAxes'),'Type','patch','Tag',ActiveTess); %CHEAT - need to be improved ? + end + + FVCData = get(Cortex,'FaceVertexCData'); + MM = max(abs(FVCData)); + if size(FVCData,2) == 1 % No curvature mapping + FVCData = .1*MM*ones(size(FVCData)); + else + FVCData = repmat(MM,size(FVCData,1),1); + end + + if ~isfield(TessWinUserData,'FVCData') + TessWinUserData.FVCData = FVCData; % Keep track of the original activation vertex colordata + end + + if size(FVCData,2) == 1 % No curvature mapping + FVCData([ TessWinUserData.CorticalProbePatches{SelectedArea} ]) = MM; + else + FVCData([ TessWinUserData.CorticalProbePatches{SelectedArea} ],:) = repmat(MM,length(TessWinUserData.CorticalProbePatches{SelectedArea} ),1); + end + + set(Cortex,'FaceVertexCData',FVCData); + + set(Cortex,'FaceVertexCData',FVCData); + set(TessWin,'Userdata',TessWinUserData) + + + %---------------------------------------------------------------------------- + + %---------------------------------------------------------------------------- +case 'GrowCorticalArea' % Grow a survey cortical area around a probe to include connected vertices + tesselation_select_win = findobj(0,'Tag','tesselation_select'); + hTessSelect = guihandles(tesselation_select_win); + available = findobj(tesselation_select_win,'Tag','available'); + removed = findobj(tesselation_select_win,'Tag','removed'); + availableID = get(removed,'Value'); + IDs = get(removed,'String'); + if isempty(IDs), return, end + + % Identification of the active mesh surfaces + Visu = get(DATAPLOT,'Userdata'); + + try + load(Visu.Tesselation,'Comment') + catch + cd(Users.SUBJECTS) + load(Visu.Tesselation,'Comment') + end + + if ~iscell(IDs) + imesh = find(strcmp(IDs,Comment)); + end + + nsurf = imesh(availableID); % Selected surface + + TessWin = findobj(0,'tag','tessellation_window'); + hTessWin = guihandles(TessWin); + TessWinUserData = get(TessWin,'Userdata'); + + if ~isfield(TessWinUserData,'VertConn') % Vertex connectivity not available here - load or compute + nsurfaces = length(Comment); + + % ----------------- Load current surface parameters - + load(Visu.Tesselation,'Faces','Vertices') + FV.faces = Faces{nsurf}; clear Faces + FV.vertices = Vertices{nsurf}'; clear Vertices + % ----------------- Load current surface parameters - DONE + + load(Users.CurrentData.SubjectFile,'VertConn'); % Is the vertex connectivity available ? + + if isempty(VertConn) % Vertex connectivity was not defined before + VertConn = cell(nsurfaces,1); + else + if exist(VertConn,'file') + load(VertConn) + else + VertConn = cell(nsurfaces,1); + end + end + + if isempty(VertConn{nsurf}) % Compute the vertex connectivity + [pathname, filename] = fileparts(Visu.Tesselation); + VertConn{nsurf} = vertices_connectivity(FV); + VertConnFile = [filename,'_vertconn.mat']; + + % Save this file in the current subject folder + [path,name,ext] = fileparts(Users.CurrentData.SubjectFile); + if ~isempty(path) + cd(path) + else + cd(Users.SUBJECTS) + end + + save(VertConnFile,'VertConn'); + + VertConn = VertConnFile; + save(Users.CurrentData.SubjectFile,'VertConn','-append') + else % Load VC from the file + load(Users.CurrentData.SubjectFile,'VertConn'); + end + load(VertConn); + VertConn = VertConn{nsurf}; + TessWinUserData.VertConn = VertConn; clear VertConn; + end + + % Now grow a patch around the selected probe and selected neighbors + + SelectedArea = get(hTessSelect.CorticalSpotList,'Value'); + if ~isfield(TessWinUserData,'CorticalProbePatches') + TessWinUserData.CorticalProbePatches = cell(get(hTessSelect.CorticalSpotList,'Max'),1); + TessWinUserData.CorticalProbeDepth = zeros(get(hTessSelect.CorticalSpotList,'Max'),1); % Number of patch_swells necessary to achieve the patch growth; + end + if length(TessWinUserData.CorticalProbePatches) < SelectedArea % User just added a new probe + nareas = length(TessWinUserData.CorticalProbePatches); + TessWinUserData.CorticalProbePatches(nareas+1:SelectedArea) = cell(SelectedArea-nareas,1); + TessWinUserData.CorticalProbeDepth(nareas+1:SelectedArea) = 0; + end + + if isempty(TessWinUserData.CorticalProbePatches{SelectedArea}) % No areas around patches were defined + TessWinUserData.CorticalProbeDepth(SelectedArea) = 1; + TessWinUserData.CorticalProbePatches{SelectedArea} = ... + [TessWinUserData.CorticalSpots(SelectedArea),patch_swell(TessWinUserData.CorticalSpots(SelectedArea),TessWinUserData.VertConn)]; + else + TessWinUserData.CorticalProbeDepth(SelectedArea) = TessWinUserData.CorticalProbeDepth(SelectedArea) +1; + TessWinUserData.CorticalProbePatches{SelectedArea} = [TessWinUserData.CorticalProbePatches{SelectedArea},... + patch_swell([TessWinUserData.CorticalProbePatches{SelectedArea}],TessWinUserData.VertConn)]; + end + + ActiveTess = get(hTessSelect.removed,'String'); % Find the active scalp surface + iCortex = get(hTessSelect.removed,'Value'); % Find the active scalp surface + if isempty(iCortex) + h = msgbox('Please select a cortex surface from the tessellations available.'); + return + end + if iscell(ActiveTess) + Cortex = findobj(get(TessWin,'CurrentAxes'),'Type','patch','Tag',ActiveTess{iCortex}); %CHEAT - need to be improved ? + else + Cortex = findobj(get(TessWin,'CurrentAxes'),'Type','patch','Tag',ActiveTess); %CHEAT - need to be improved ? + end + + FVCData = get(Cortex,'FaceVertexCData'); + + if ~isfield(TessWinUserData,'FVCData') + TessWinUserData.FVCData = FVCData; % Keep track of the original activation vertex colordata + end + + if size(FVCData,2) == 1 % No curvature mapping + FVCData([ TessWinUserData.CorticalProbePatches{SelectedArea} ]) = max(abs(FVCData)); + else + FVCData([ TessWinUserData.CorticalProbePatches{SelectedArea} ],:) = repmat(max(abs(FVCData)),length(TessWinUserData.CorticalProbePatches{SelectedArea} ),1); + end + + set(Cortex,'FaceVertexCData',FVCData); + set(TessWin,'Userdata',TessWinUserData) + + + %---------------------------------------------------------------------------- +case 'ReduceCorticalArea' % See above 'GrowCorticalArea' - except we are here reducing the size of the survey cortical zone to monitor + tesselation_select_win = findobj(0,'Tag','tesselation_select'); + hTessSelect = guihandles(tesselation_select_win); + available = findobj(tesselation_select_win,'Tag','available'); + removed = findobj(tesselation_select_win,'Tag','removed'); + availableID = get(removed,'Value'); + IDs = get(removed,'String'); + if isempty(IDs), return, end + + % Identification of the active mesh surfaces + Visu = get(DATAPLOT,'Userdata'); + + try + load(Visu.Tesselation,'Comment') + catch + cd(Users.SUBJECTS) + load(Visu.Tesselation,'Comment') + end + + if ~iscell(IDs) + imesh = find(strcmp(IDs,Comment)); + end + + nsurf = imesh(availableID); % Selected surface + + TessWin = findobj(0,'tag','tessellation_window'); + hTessWin = guihandles(TessWin); + TessWinUserData = get(TessWin,'Userdata'); + + if ~isfield(TessWinUserData,'VertConn') % Vertex connectivity not available here - load or compute + nsurfaces = length(Comment); + + % ----------------- Load current surface parameters - + load(Visu.Tesselation,'Faces','Vertices') + FV.faces = Faces{nsurf}; clear Faces + FV.vertices = Vertices{nsurf}'; clear Vertices + % ----------------- Load current surface parameters - DONE + + load(Users.CurrentData.SubjectFile,'VertConn'); % Is the vertex connectivity available ? + + if isempty(VertConn) % Vertex connectivity was not defined before + VertConn = cell(nsurfaces,1); + else + if exist(VertConn,'file') + load(VertConn) + else + VertConn = cell(nsurfaces,1); + end + end + + if isempty(VertConn{nsurf}) % Compute the vertex connectivity + [pathname, filename] = fileparts(Visu.Tesselation); + VertConn{nsurf} = vertices_connectivity(FV); + VertConnFile = [filename,'_vertconn.mat']; + + save(VertConnFile,'VertConn'); + + VertConn = VertConnFile; + save(Users.CurrentData.SubjectFile,'VertConn','-append') + else % Load VC from the file + load(Users.CurrentData.SubjectFile,'VertConn'); + end + load(VertConn); + VertConn = VertConn{nsurf}; + TessWinUserData.VertConn = VertConn; clear VertConn; + end + + + % Now grow a patch around the selected probe and selected neighbors + SelectedArea = get(hTessSelect.CorticalSpotList,'Value'); + + tmp = []; + + for k = 1:TessWinUserData.CorticalProbeDepth(SelectedArea)-1 + if k == 1 + tmp = ... + patch_swell(TessWinUserData.CorticalSpots(SelectedArea),TessWinUserData.VertConn); + else + tmp = ... + [tmp,patch_swell(tmp,TessWinUserData.VertConn)]; + end + end + + TessWinUserData.CorticalProbeDepth(SelectedArea) = max([TessWinUserData.CorticalProbeDepth(SelectedArea)-1,0]); + + ActiveTess = get(hTessSelect.removed,'String'); % Find the active scalp surface + iCortex = get(hTessSelect.removed,'Value'); % Find the active scalp surface + + if isempty(iCortex) + h = msgbox('Please select a cortex surface from the tessellations available.'); + return + end + if iscell(ActiveTess) + Cortex = findobj(get(TessWin,'CurrentAxes'),'Type','patch','Tag',ActiveTess{iCortex}); %CHEAT - need to be improved ? + else + Cortex = findobj(get(TessWin,'CurrentAxes'),'Type','patch','Tag',ActiveTess); %CHEAT - need to be improved ? + end + FVCData = get(Cortex,'FaceVertexCData'); + old_members = intersect(TessWinUserData.CorticalProbePatches{SelectedArea},tmp); % Older members of the cortical survey zone (keep them) + if isempty(old_members) + old_members = TessWinUserData.CorticalSpots(SelectedArea); + end + new_members = setdiff(TessWinUserData.CorticalProbePatches{SelectedArea},tmp); % Last members of the cortical survey zone (remove them) + + if size(FVCData,2) == 1 % No curvature mapping + FVCData(new_members) = TessWinUserData.FVCData(new_members); % Reset their color to original + else + FVCData(new_members,:) = TessWinUserData.FVCData(new_members,:); % Reset their color to original + end + + TessWinUserData.CorticalProbePatches{SelectedArea} = old_members; + + if size(FVCData,2) == 1 % No curvature mapping + FVCData([ TessWinUserData.CorticalProbePatches{SelectedArea} ]) = max(abs(FVCData)); + else + FVCData([ TessWinUserData.CorticalProbePatches{SelectedArea} ],:) = repmat(max(abs(FVCData)),length(TessWinUserData.CorticalProbePatches{SelectedArea} ),1); + end + + set(Cortex,'FaceVertexCData', TessWinUserData.FVCData) + set(Cortex,'FaceVertexCData',FVCData); + + set(TessWin,'Userdata',TessWinUserData) + + + %---------------------------------------------------------------------------- +case 'CorticalSpotActivity' + tesselation_select_win = findobj(0,'Tag','tesselation_select'); + hTessSelect = guihandles(tesselation_select_win); + + % What kind of display is wanted here ? + MEAN = get( hTessSelect.MeanCorticalArea,'Value'); + MAX = get(hTessSelect.MaxCorticalArea,'Value'); + ALL = get(hTessSelect.AllCorticalArea,'Value'); + + SelectedArea = get(hTessSelect.CorticalSpotList,'Value'); + + TessWin = findobj(0,'tag','tessellation_window'); + hTessWin = guihandles(TessWin); + TessWinUserData = get(TessWin,'Userdata'); + + Results = get(hTessSelect.ResultFiles,'Userdata'); + + if get(hTessSelect.ZScoreThresholdApply,'Value') % Threshold is applied on ZScore map: indicate significant Z Scores time samples + flagZScoreT = 1; + flagHisto = 0; + else + flagZScoreT = 0; + if get(hTessSelect.ZScore,'Value') + flagZScore = 1; + flagHisto = 0; + else + flagZScore = 0; + % !!! CorticalActivity is scaled to nA.m + if max(abs(Results.ImageGridAmp(:))) < 1e-5 % Probably dipole moment values - scale to nA.m + flagHisto = 0; + Results.ImageGridAmp = 1e9*Results.ImageGridAmp; + else + % This is a plot of histogram of activations + flagHisto = 1; + end + + end + + end + + + if ~isfield(TessWinUserData,'CorticalProbePatches') + if get(hTessSelect.AbsoluteCurrent,'Value') + CorticalActivity = abs(Results.ImageGridAmp(TessWinUserData.CorticalSpots(SelectedArea),:)); + else + CorticalActivity = Results.ImageGridAmp(TessWinUserData.CorticalSpots(SelectedArea),:); + end + else + for k = 1:length(SelectedArea) % For each cortical survey area + if length(TessWinUserData.CorticalProbePatches{SelectedArea(k)})>1 + if MEAN == 1% Average of the patch activity at each time instant + if get(hTessSelect.AbsoluteCurrent,'Value') + CorticalActivity(k,:) = mean(abs(Results.ImageGridAmp(TessWinUserData.CorticalProbePatches{SelectedArea(k)},:))); + else + CorticalActivity(k,:) = sign(mean(Results.ImageGridAmp(TessWinUserData.CorticalProbePatches{SelectedArea(k)},:)))... + .* mean((Results.ImageGridAmp(TessWinUserData.CorticalProbePatches{SelectedArea(k)},:))) ; + end + elseif MAX==1 % Strongest + nor = 1e9*norlig(Results.ImageGridAmp(TessWinUserData.CorticalProbePatches{SelectedArea(k)},:)); + [m,iMax] = max(nor); + if get(hTessSelect.AbsoluteCurrent,'Value') + CorticalActivity(k,:) = max(abs(Results.ImageGridAmp(TessWinUserData.CorticalProbePatches{SelectedArea(k)},:))); + else + CorticalActivity(k,:) = max(Results.ImageGridAmp(TessWinUserData.CorticalProbePatches{SelectedArea(k)},:)); + end + + else % Display every source activity in the patch + % All sources need to be displayed + if get(hTessSelect.AbsoluteCurrent,'Value') + CorticalActivity{k} = abs(Results.ImageGridAmp(TessWinUserData.CorticalProbePatches{SelectedArea(k)},:)); + else + CorticalActivity{k} = Results.ImageGridAmp(TessWinUserData.CorticalProbePatches{SelectedArea(k)},:); + end + % Detect Maxima in amplitudes (needed for proper display in the offset plot) + maxCorticalActivity(k,:) = sign(min(CorticalActivity{k})).*max(abs(CorticalActivity{k})); + end + else + if get(hTessSelect.AbsoluteCurrent,'Value') + tmp = abs(Results.ImageGridAmp(TessWinUserData.CorticalSpots(SelectedArea(k)),:)); + else + tmp = Results.ImageGridAmp(TessWinUserData.CorticalSpots(SelectedArea(k)),:); + end + if (MEAN | MAX) + CorticalActivity(k,:) = tmp; clear tmp + else + CorticalActivity{k} = tmp; clear tmp + maxCorticalActivity(k,:) = CorticalActivity{k}; + end + end + end + end + + t = 1000*Results.ImageGridTime; + fActivation = figure; + % Display set-up + switch(find([MEAN,MAX,ALL])) + case 1 + tail = 'MEAN'; + case 2 + tail = 'MAX'; + case 3 + tail = 'ALL'; + end + + set(gcf,'Tag','source_time_series','Name',['Activation time series: ', tail],'color','w' ) + Color = get(gca,'ColorOrder'); % Default Colormapping for line plots + + if ~iscell(CorticalActivity) + S = CorticalActivity'; + + % %Sn = colnorm(S); + % %Sn = S./Sn(ones(1,size(S,1)),:); + % % each waveform is now unity in norm. Weight + % % by its total contribution to the field + % %Snw = Sn .* (ones(size(S,1),1)*(colnorm(A).*colnorm(S))); + % [outmatnw, col_offsetnw] = offset(S); + % plot(t,outmatnw,'linewidth',1), + % hold on + % plot(t,col_offsetnw,'linewidth',2) % want same color order as data + + % Get Max amplitude and apply to all axes + M = max(abs(S(:))); + for k = 1:size(CorticalActivity,1) + subplot(size(CorticalActivity,1)+1,1,k) % One-column display + plot(t,CorticalActivity(k,:),'color',Color(modulo(k,size(Color,1)),:),'Tag','AllSamples') + hold on + set(gca,'XlimMode','manual') + if get(hTessSelect.AbsoluteCurrent,'Value') + axis([t(1) t(end) 0 M+.05*M]) + else + axis([t(1) t(end) -M-.05*M M+.05*M]) + end + + + if flagZScoreT == 1 % Add markers for significant amplitudes along time + iZeroed = find(... + abs(CorticalActivity(k,:)) ... + - Results.ZScore.ImageGridZ.ZThres... + <=0); + CorticalActivity(k,iZeroed) = NaN; + hold on + if length(iZeroed) ~= length(CorticalActivity(k,:)) + plot(t,CorticalActivity(k,:),'color',Color(modulo(k,size(Color,1)),:),'linewidth',2,'Tag','SignificantSamples') + end + + end + + if 0 < t(end) & 0 > t(1) + if get(hTessSelect.AbsoluteCurrent,'Value') + line([0 0], [0 M+.05*M],'color','k','linestyle','--') + else + line([0 0] ,[-M-.05*M M+.05*M],'color','k','linestyle','--') + end + end + + % Plastic Surgery to Axes Display + set(gca,'box','off','XGrid','off','FontUnits','Points','fontsize',9,'LineWidth',2) + + Label = get(TessWinUserData.CorticalMarkersLabels(SelectedArea(k)),'String'); + + if flagZScoreT == 1 + if length(iZeroed) ~= length(CorticalActivity(k,:)) + hLeg = legend(sprintf(... + '%s',Label),... + sprintf('p<%4.2f', 1/Results.ZScore.ImageGridZ.ZThres^2)... + ,0); + else + hLeg = legend(sprintf(... + '%s (n.s.)',Label),0); + + end + + else + hLeg = legend(sprintf(... + '%s',Label)... + ); + end + + %set(hLeg,'color',Color(modulo(k,size(Color,1)),:),'fontsize',9) + %legend('boxoff') + + if flagZScoreT == 1 + line([t(1) t(end)], [Results.ZScore.ImageGridZ.ZThres Results.ZScore.ImageGridZ.ZThres],... + 'Color',Color(modulo(k,size(Color,1)),:),'linestyle','--') + if k==1 + if flagHisto == 0 + ylabel(['Z-Score (\bf\sigma)'],'fontunits','point') + else + ylabel(['% Spikes'],'fontunits','point') + end + + xlabel('time (ms)','fontunits','point','fontsize',9) + end + + hold off + else + if k == 1 + if flagZScore == 0 + if flagHisto == 0 + ylabel(['nA\cdotm'],'fontunits','point') + else + ylabel(['% Spikes'],'fontunits','point') + end + + else + if flagHisto == 0 + ylabel(['Z-Score (\bf\sigma)'],'fontunits','point') + else + ylabel(['% Spikes'],'fontunits','point') + end + + end + xlabel('time (ms)','fontunits','point','fontsize',9) + end + end + + % Summary plot + subplot(size(CorticalActivity,1)+1,1,size(CorticalActivity,1)+1) + plot(t,CorticalActivity(k,:),'color',Color(modulo(k,size(Color,1)),:),'Tag','AllSamples') + hold on + set(gca,'XlimMode','manual') + set(gca,'box','off','XGrid','off','FontUnits','point','fontsize',10,'LineWidth',2) + if get(hTessSelect.AbsoluteCurrent,'Value') + axis([t(1) t(end) 0 M+.05*M]) + else + axis([t(1) t(end) -M-.05*M M+.05*M]) + end + + if 0 < t(end) & 0 > t(1) + if get(hTessSelect.AbsoluteCurrent,'Value') + line([0 0], [0 M+.05*M],'color','k','linestyle','--') + else + line([0 0] ,[-M-.05*M M+.05*M],'color','k','linestyle','--') + end + end + + if flagZScoreT == 1 + line([t(1) t(end)], [Results.ZScore.ImageGridZ.ZThres Results.ZScore.ImageGridZ.ZThres],... + 'Color',Color(modulo(k,size(Color,1)),:),'linestyle','--') + if k==1 + ylabel(['Z-Score (\bf\sigma)'],'fontunits','point') + xlabel('time (ms)','fontunits','point','fontsize',9) + end + else + if k == 1 + if flagZScore == 0 + if flagHisto == 0 + ylabel(['nA\cdotm'],'fontunits','point') + else + ylabel(['% Spikes'],'fontunits','point') + end + + else + if flagHisto == 0 + ylabel(['Z-Score (\bf\sigma)'],'fontunits','point') + else + ylabel(['% Spikes'],'fontunits','point') + end + end + xlabel('time (ms)','fontunits','point','fontsize',9) + end + end + + + + %---------------- + + + end + + else + + % Find MAX + for k = 1:length(CorticalActivity) + M(k) = max(abs([CorticalActivity{k}(:)])); + end + M = max(M); + for k = 1:length(CorticalActivity) + subplot(length(CorticalActivity)+1,1,k) % One-column display + plot(t,CorticalActivity{k},'color',Color(modulo(k,size(Color,1)),:)) + hold on + + set(gca,'XlimMode','manual') + if get(hTessSelect.AbsoluteCurrent,'Value') + axis([t(1) t(end) 0 M+.05*M]) + else + axis([t(1) t(end) -M-.05*M M+.05*M]) + end + + + if flagZScoreT == 1 % Add markers for significant amplitudes along time + iZeroed = find(... + abs(CorticalActivity{k}) ... + - Results.ZScore.ImageGridZ.ZThres... + <=0); + CorticalActivity{k}(iZeroed) = NaN; + hold on + if length(iZeroed) ~= length(CorticalActivity{k}(:)) + plot(t,CorticalActivity{k},'color',Color(modulo(k,size(Color,1)),:),'linewidth',2,'Tag','SignificantSamples') + end + if k== 1 + if flagHisto == 0 + ylabel(['Z-Score (\bf\sigma)'],'fontunits','point') + else + ylabel(['% Spikes'],'fontunits','point') + end + + xlabel('time (ms)','fontunits','point') + end + + else + if k==1 + if flagZScore == 0 + if flagHisto == 0 + ylabel(['nA\cdotm'],'fontunits','point') + else + ylabel(['% Spikes'],'fontunits','point') + end + + else + if flagHisto == 0 + ylabel(['Z-Score (\bf\sigma)'],'fontunits','point') + else + ylabel(['% Spikes'],'fontunits','point') + end + + end + xlabel('time (ms)','fontunits','point') + end + + end + + if 0 < t(end) & 0 > t(1) + if get(hTessSelect.AbsoluteCurrent,'Value') + line([0 0], [0 M+.05*M],'color','k','linestyle','--') + else + line([0 0] ,[-M-.05*M M+.05*M],'color','k','linestyle','--') + end + end + + % Plastic Surgery to Axes Display + set(gca,'box','off','XGrid','off','FontUnits','point','fontsize',10,'LineWidth',2) + Label = get(TessWinUserData.CorticalMarkersLabels(SelectedArea(k)),'String'); + % title(sprintf(... + % ' Area: %s',Label)... + % ); + if flagZScoreT == 1 + if length(iZeroed) ~= length(CorticalActivity{k}) + hLeg = legend(sprintf(... + '%s',Label),... + sprintf('p<%4.2f', 1/Results.ZScore.ImageGridZ.ZThres^2)... + ,0); + else + hLeg = legend(sprintf(... + '%s (n.s.)',Label),0); + end + + else + hLeg = legend(sprintf(... + ' Area: %s',Label)... + ,0); + end + + %set(hLeg,'color',Color(modulo(k,size(Color,1)),:),'fontunits','points','fontsize',9) + %set(findobj(get(hLeg,'children'),'type','text'),'color',Color(modulo(k,size(Color,1)),:)) + %%legend('boxoff') + + % S = maxCorticalActivity'; + % [outmatnw, col_offsetnw] = offset(S); + % off = plot(t,col_offsetnw,'linewidth',2); % want same color order as data + % hold on + % for k = 1:length(CorticalActivity) + % Src = CorticalActivity{k} + repmat(col_offsetnw(:,k)',size(CorticalActivity{k},1),1); + % plot(t,Src,'linewidth',1,'color',get(off(k),'color')), + % set(off(k),'color',[.4 .4 .4]) + + + % Summary plot + subplot(length(CorticalActivity)+1,1,length(CorticalActivity)+1) + plot(t,CorticalActivity{k},'color',Color(modulo(k,size(Color,1)),:),'Tag','AllSamples') + hold on + set(gca,'XlimMode','manual') + set(gca,'box','off','XGrid','off','FontUnits','point','fontsize',10,'LineWidth',2) + if get(hTessSelect.AbsoluteCurrent,'Value') + axis([t(1) t(end) 0 M+.05*M]) + else + axis([t(1) t(end) -M-.05*M M+.05*M]) + end + + if 0 < t(end) & 0 > t(1) + if get(hTessSelect.AbsoluteCurrent,'Value') + line([0 0], [0 M+.05*M],'color','k','linestyle','--') + else + line([0 0] ,[-M-.05*M M+.05*M],'color','k','linestyle','--') + end + end + + + if flagZScoreT == 1 + line([t(1) t(end)], [Results.ZScore.ImageGridZ.ZThres Results.ZScore.ImageGridZ.ZThres],... + 'Color',Color(modulo(k,size(Color,1)),:),'linestyle','--') + hold off + end + + if flagZScoreT == 1 + line([t(1) t(end)], [Results.ZScore.ImageGridZ.ZThres Results.ZScore.ImageGridZ.ZThres],... + 'Color',Color(modulo(k,size(Color,1)),:),'linestyle','--') + if k==1 + if flagHisto == 0 + ylabel(['Z-Score (\bf\sigma)'],'fontunits','point') + else + ylabel(['% Spikes'],'fontunits','point') + end + + xlabel('time (ms)','fontunits','point','fontsize',9) + end + + hold off + else + if k == 1 + if flagZScore == 0 + if flagHisto == 0 + ylabel(['nA\cdotm'],'fontunits','point') + else + ylabel(['% Spikes'],'fontunits','point') + end + else + if flagHisto == 0 + ylabel(['Z-Score (\bf\sigma)'],'fontunits','point') + else + ylabel(['% Spikes'],'fontunits','point') + end + + end + xlabel('time (ms)','fontunits','point','fontsize',9) + end + end + + + %---------------- + + + + end + end + + hold off + + % for i = 1:size(S,2) + % Label = get(TessWinUserData.CorticalMarkersLabels(SelectedArea(i)),'String'); + % htext = text(t(end),col_offsetnw(end,i),sprintf(... + % ' Area: %s',Label)... + % ,'color','k'); + % set(htext, 'FontUnits','Normal','FontName','Helvetica') + % end + + % zoom on + % set(gca,'xgrid','on','xcolor','k','ycolor','k','box','on') + % set(gca,'yticklabel',[]) + % set(gca,'Position',[.05 .10 .75 .9]) + % drawnow + + % Draw Source Time Cursor + if 0 + figsingle = findobj(0,'Tag','source_time_series'); + src_slider_time = findobj(SRC_MAPPING,'Tag','src_slider_time'); + ctime = get(src_slider_time,'Value'); + cursor = line([ctime ctime],get(gca,'Ylim')); + set(cursor,'color','r','linewidth',3,'Tag','cursor','erasemode','Xor') + end + + %---------------------------------------------------------------------------- +case 'tesselation_select_done' % Launch/Refresh Mesh Surface Visulization + Visu = get(DATAPLOT,'Userdata'); + TessSelect = findobj(0,'Tag','tesselation_select'); + hSelect = guihandles(TessSelect); + + available = findobj(TessSelect,'Tag','available'); + removed = findobj(TessSelect,'Tag','removed'); + + previous = findobj(0,'tag','tessellation_window'); + tmp = findobj(0,'tag','subplot_tess_window'); + if ~isempty(tmp) + previous = tmp; % Consider the slides or movie window + end + + if isempty(previous) + previous = open('tessellation_window.fig'); + set(previous,'CurrentAxes',findobj(previous,'Tag','MainAxis'),... + 'Renderer','OpenGL',... + 'Color','w', 'Colormap',gray); + rotate3d on + set(hSelect.TruncateFactor,'String',0) + set(hSelect.ColorMAP,'Userdata',[]) + set(hSelect.ScaleColormap,'Userdata',[]) + + availableID = get(removed,'Value'); + IDs = get(removed,'String'); + if isempty(IDs), return, end + chan = [1:length(IDs)]; + + try + load(Visu.Tesselation) + catch + cd(Users.SUBJECTS) + load(Visu.Tesselation) + end + + % How many meshes available ? + nmesh = max(size(Vertices)); + + % Identification of the active mesh surfaces + if ischar(IDs) % only one envelope was selected + imesh = find(strcmp(IDs,Comment)); + else + for i = 1:length(IDs) + imesh(i) = find(strcmp(IDs(i),Comment)); + end + end + + Vals = get(removed,'Userdata'); + if isempty(Vals) + Vals = [ones(nmesh,1),zeros(nmesh,8)]; + end + Vals = set(removed,'Userdata'); + nsurf = imesh(availableID); % Selected surface + set(hSelect.Apply,'Userdata',{imesh,nsurf,Comment,Vertices,Faces}) + else + tmp = get(hSelect.Apply,'Userdata'); + imesh = tmp{1}; + nsurf = tmp{2}; + Comment = tmp{3}; + Vertices = tmp{4}; + Faces = tmp{5}; + end + figure(previous) + + Vals = get(removed,'Userdata'); + + FragmentMenu = findobj(gcbf,'Tag','FragmentMenu'); + nclust = get(FragmentMenu,'Value')-1; % Get label of clustering to visualize + + TAG = get(hSelect.OrthoViews,'Value'); + + if TAG == 1 + OrthoViews = 1; + else + OrthoViews = 0; + end + hpatch = []; + if OrthoViews == 0 + hpatch = findobj(findobj(previous,'Tag','MainAxes'),'Type','patch','Visible','on'); + else + if ~isempty(findobj(findobj(previous,'Tag','axsub1'),'Type','patch')) + hpatch(1) = findobj(findobj(previous,'Tag','axsub1'),'type','patch'); + hpatch(2) = findobj(findobj(previous,'Tag','axsub2'),'type','patch'); + hpatch(3) = findobj(findobj(previous,'Tag','axsub3'),'type','patch'); + hpatch(4) = findobj(findobj(previous,'Tag','axsub4'),'type','patch'); + else + if isempty(hpatch) + hpatch = []; + end + end + end + + % What kind of visualization type is needed here ? ------------------------------- + ScalpMaps = get(hSelect.ScalpMaps,'Value'); + ColorSensors = get(hSelect.ColorSensors,'Value'); + CorticalMap = get(hSelect.CorticalMap,'Value'); + + nmesh = 0; % Number of meshes to display + + for k = imesh + h = findobj(hpatch,'Tag',Comment{k}); + set(h,'Visible','on') + + if isempty(h) + h{1}= 'new_patch'; + elseif iscell(h) + if get(h{1},'Parent') ~= get(previous,'CurrentAxes') + h{1}= 'new_patch'; + end + else + hh = cell(length(h),1); + for kh = 1:length(hh) + hh{kh} = h(kh); + end + h = hh; clear hh + end + + switch h{1} + case 'new_patch' + + h = patch('vertices',Vertices{k}','Faces',Faces{k},... + 'Facecolor',[.8 .8 .8],'Edgecolor','none',... + 'Visible','off','Tag',Comment{k}); + + % Surface orientation + if ~isfield(Visu.Data,'System') + Visu.Data.System = 'ctf'; + end + + switch(Visu.Data.System) + case 'ctf' + view(-90,90) + end + + %------------------------- + + if length(imesh)>1 + nmesh = nmesh+1; + hpatch(nmesh) = h; + else + hpatch = h; + end + % Lightning properties ------------------------------------------------------- + set(h,'facelighting','none','edgelighting','none',... + 'ambientstrength',.5,... + 'DiffuseStrength',.3,... + 'specularcolorreflectance',.0,... + 'specularexponent',1,'specularstrength',.2,... + 'backfacelighting','unlit'); + + lightangle(0,0) + lightangle(0,90) + lightangle(180,0) + axis normal , axis equal, axis vis3d, axis off + + end + end + + if get(hSelect.MapCurvature,'Value') % ColorCoding of surface curvature is requested + TessWin = findobj(0,'tag','tessellation_window'); + hTessWin = guihandles(TessWin); + TessWinUserData = get(TessWin,'Userdata'); + + if ~isfield(get(hpatch, 'Userdata'),'CDepth') % No curvature mapping is available - compute it + % Try to load the depth map from the tessellation file + nsurfaces = length(Comment); + + try + load(Visu.Tesselation,'Curvature') + catch + cd(Users.SUBJECTS) + load(Visu.Tesselation,'Curvature') + end + + if ~exist('Curvature','var') + Curvature = cell(nsurfaces,1); + end + + if length(Curvature) < nsurf + tmp = cell(nsurf,1); + tmp(1:length(Curvature)) = Curvature; + Curvature = tmp; clear tmp + end + + if isempty(Curvature{nsurf}) %| ~isfield(TessWinUserData,'VertConn') % Vertex connectivity not available here - load or compute + + load(Users.CurrentData.SubjectFile,'VertConn'); % Is the vertex connectivity available ? + + if isempty(VertConn) % Vertex connectivity was not defined before + VertConn = cell(nsurfaces,1); + else + if exist(VertConn,'file') + load(VertConn) + else + VertConn = cell(nsurfaces,1); + end + end + + if length(VertConn) < nsurf + tmp = cell(nsurf,1); + tmp(1:length(VertConn)) = VertConn; + VertConn = tmp; clear tmp + end + + if isempty(VertConn{nsurf}) % Compute the vertex connectivity + + % ----------------- Load current surface parameters + load(Visu.Tesselation,'Faces','Vertices') + FV.faces = Faces{nsurf}; clear Faces + FV.vertices = Vertices{nsurf}'; clear Vertices + % ----------------- Load current surface parameters - DONE + + [pathname, filename] = fileparts(Visu.Tesselation); + VertConn{nsurf} = vertices_connectivity(FV); + VertConnFile = [filename,'_vertconn.mat']; + + % Save this file in the current subject folder + [path,name,ext] = fileparts(Users.CurrentData.SubjectFile); + if isempty(path) + cd(Users.SUBJECTS) + else + cd(path) + end + save(VertConnFile,'VertConn'); + + cd(Users.SUBJECTS) + VertConn = fullfile(path,VertConnFile); + save(Users.CurrentData.SubjectFile,'VertConn','-append') + load(VertConn) + end + VertConn = VertConn{nsurf}; + TessWinUserData.VertConn = VertConn; clear VertConn; + set(TessWin,'Userdata',TessWinUserData) + % Compute colormap for this surface + FV.vertices = get(hpatch,'Vertices'); + FV.faces = get(hpatch,'Faces'); + [vertexcolorh,map]=c_activ(FV,TessWinUserData.VertConn); clear FV + Curvature{nsurf} = vertexcolorh; + save(Visu.Tesselation,'Curvature','-append') + else + vertexcolorh = Curvature{nsurf}; + clear Curvature + end + + if strcmp(get(hpatch(1),'Visible'),'off') + set(hpatch,'FaceVertexCData',vertexcolorh) + end + % Now save it back to the Tesselation file + + else + + vertexcolorh = get(hpatch,'Userdata'); + vertexcolorh = vertexcolorh.CDepth; + + end + + cortex = get(hpatch,'Userdata'); + cortex.CDepth = vertexcolorh; + set(hpatch,'Userdata',cortex); + + else + + vertexcolorh = get(hpatch,'FaceVertexCdata'); + + end + + if ColorSensors + dataplot_cb DataOnSensors + end + + if ScalpMaps + dataplot_cb DataOnScalp + end + + if CorticalMap + dataplot_cb('CorticalMap',hpatch) + else + if get(hSelect.MapCurvature,'Value') + set(hpatch,'FaceVertexCData',vertexcolorh) + end + end + + set(hpatch,'Visible','on') + + vertexcolorh = get(hpatch,'FaceVertexCdata'); + + nmesh = 0; + for k = imesh + nmesh = nmesh + 1; + h = findobj(get(previous,'CurrentAxes'),'Tag',Comment{k},'Visible','on'); + if isempty(h) + + if k == nsurf & nclust > 0 % Some clustering has been selected - display clusters on the surface + set(h,'FacevertexCdata',Clusters{nsurf}.Cluster{nclust},'Userdata',Clusters{nsurf}.Cluster{nclust}') + colormap(rand(Clusters{nsurf}.NClusters(nclust),3)) + end + + hold on + + if Vals(k,1) == 1 + set(h,'edgecolor','none','facecolor','interp') + else + set(h,'edgecolor',[.7 .7 .7],'facecolor','none') + end + + else + + if isempty(vertexcolorh) + vertexcolorh = get(h,'FaceVertexCData'); + elseif iscell(vertexcolorh) + vertexcolorh = vertexcolorh{nmesh}; + end + + if Vals(k,1) == 1 + + if isempty(vertexcolorh) + set(h,'edgecolor','none','facecolor',[.5 .5 .5]) + else + set(h,'edgecolor','none','facecolor','interp') + end + + else + set(h,'edgecolor','interp','facecolor','none') + end + end + % Update envelope properties ------------------------------------------------------ + + ctet = get(h,'vertices'); + siz = size(vertexcolorh,2); + if size(vertexcolorh,1) == 1 + vertexcolorh = ones(size(ctet,1),1)*vertexcolorh; + end + + + if Vals(k,3) == 1 + vertexcolorh(find(ctet(:,2) < 0),:) = NaN * ones(length(find(ctet(:,2) < 0)),siz); + end + if Vals(k,4) == 1 + vertexcolorh(find(ctet(:,2) > 0),:) = NaN * ones(length(find(ctet(:,2) > 0)),siz); + end + if Vals(k,5) == 1 + vertexcolorh(find(ctet(:,1) > 0),:) = NaN * ones(length(find(ctet(:,1) > 0)),siz); + end + if Vals(k,6) == 1 + vertexcolorh(find(ctet(:,1) < 0),:) = NaN * ones(length(find(ctet(:,1) < 0)),siz); + end + if Vals(k,7) == 1 + vertexcolorh(find(ctet(:,3) > 0),:) = NaN * ones(length(find(ctet(:,3) > 0)),siz); + end + if Vals(k,8) == 1 + vertexcolorh(find(ctet(:,3) < 0),:) = NaN * ones(length(find(ctet(:,3) < 0)),siz); + end + + clear ctet + if iscell(vertexcolorh) + set(h,'FaceVertexCData',vertexcolorh{1},'Visible','on') + if length(hpatch)>1 % OrthoViews + for kk = 1:length(hpatch) + h = hpatch(kk); + set(h,'FaceVertexCData',vertexcolorh{1},'Visible','on') + end + + end + + else + if OrthoViews == 1 + set(hpatch,'FaceVertexCData',vertexcolorh,'Visible','on') + else + set(h,'FaceVertexCData',vertexcolorh,'Visible','on') + end + end + vertexcolorh = []; % Move to next surface + end + + figure(previous) + hold off + rotate3d on + if ~isempty(findobj(gcf,'Tag','MainAxes')) + if length(hpatch)>1 & length(imesh) == 1% suplots with only one surface to display (need to generalize this) + if ~strcmp(get(hpatch(4),'Visible'),'on') + set(gca,'View',get(findobj(gcf,'Tag','MainAxes'),'view')) + end + end + + end + + dataplot_cb mesh_lighting_props + + %---------------------------------------------------------------------------- +case 'listchan_create' % Selection of channel names for n-plot visualization + Visu = get(DATAPLOT,'Userdata'); + if isempty(Visu) + errordlg('Please load a data set first') + return + end + 0 + str = {'MEG','EEG','OTHER'}; + + LISTCHAN = findobj(0,'Tag','listchan'); + if isempty(LISTCHAN) + LISTCHAN = open('listchan.fig'); + end + + goodchannels = setdiff(Visu.ChannelID{current},find(Visu.Data.ChannelFlag == -1)) - min(Visu.ChannelID{current})+1; % Discard bad channels + badchannels = intersect(Visu.ChannelID{current},find(Visu.Data.ChannelFlag == -1)) - min(Visu.ChannelID{current})+1; + + available = findobj(LISTCHAN,'Tag','available'); + removed = findobj(LISTCHAN,'Tag','removed'); + + set(available,'String', {Visu.Channel(Visu.ChannelID{current}(goodchannels)).Name}); + + if ~isempty(badchannels) + set(removed,'String', {Visu.Channel(Visu.ChannelID{current}(badchannels)).Name}); + else + set(removed,'String', ''); + end + + set(available,'Value',1,'Max',length(get(available,'String'))) + set(removed,'Value',1,'Max',max([1,length(get(removed,'String'))])) + + %---------------------------------------------------------------------------- + +case 'channel_plot_remove' % Remove Channels + channel_select_win = findobj(0,'Tag','listchan'); + available = findobj(channel_select_win,'Tag','available'); + removed = findobj(channel_select_win,'Tag','removed'); + removeID = get(available,'Value'); + IDs = get(available,'String'); + chan = [1:length(IDs)]; + + set(available,'String', IDs(setdiff(chan,removeID))); + if isempty(get(removed,'String')) + set(removed,'String', [IDs(removeID)] ); + else + strtmp = ([cellstr(get(removed,'String'));IDs(removeID)]); + strtmp = sort(strtmp); + set(removed,'String', cellstr(strtmp)); + end + set(available,'Value',1,'Max',length(get(available,'String'))) + set(removed,'Value',1,'Max',max([1,length(get(removed,'String'))])) + + + %---------------------------------------------------------------------------- + +case 'channel_plot_restore' % Restore Channels - Make them become available again + channel_select_win = findobj(0,'Tag','listchan'); + available = findobj(channel_select_win,'Tag','available'); + removed = findobj(channel_select_win,'Tag','removed'); + availableID = get(removed,'Value'); + IDs = get(removed,'String'); + chan = [1:length(IDs)]; + + set(removed,'Max',max([1,length(setdiff(chan,availableID))])) + set(removed,'Value',1,'String',IDs(setdiff(chan,availableID))); + + if isempty(get(available,'String')) + set(available,'String', [IDs(availableID)]); + else + strtmp = ([cellstr(get(available,'String'));IDs(availableID)]); + strtmp = sort(strtmp); + set(available,'String', cellstr(strtmp)); + end + set(available,'Value',1,'Max',length(get(available,'String'))) + + %---------------------------------------------------------------------------- + +case 'listchan_SelectAll' + + channel_select_win = findobj(0,'Tag','listchan'); + available = findobj(channel_select_win,'Tag','available'); + set(available,'Value',[1:get(available,'Max')]) + dataplot_cb channel_plot_remove + + %---------------------------------------------------------------------------- +case 'listchan_RemoveAll' + + channel_select_win = findobj(0,'Tag','listchan'); + available = findobj(channel_select_win,'Tag','removed'); + set(available,'Value',[1:get(available,'Max')]) + dataplot_cb channel_plot_restore + + %---------------------------------------------------------------------------- +case 'listchan_refresh' % Update the channel information / plots + + Visu = get(DATAPLOT,'Userdata'); + str = {'MEG','EEG','OTHER'}; + + channel_select_win = findobj(0,'Tag','listchan'); + removed = findobj(channel_select_win,'Tag','removed'); + available = findobj(channel_select_win,'Tag','available'); + handles = guihandles(channel_select_win); + ncol = str2num(get(handles.NumberOfColumns,'String')); % Number of columns in the layout + + if isempty(get(removed,'String')) + errordlg('Please Select Channels for the Plot first') + return + end + + [tmp,IDs] = intersect({Visu.Channel(Visu.ChannelID{current}).Name},get(removed,'String')); + + % Get time window + TIME_MIN = str2num(get(findobj(DATAPLOT,'Tag','time_min'),'String')); + TIME_MAX = str2num(get(findobj(DATAPLOT,'Tag','time_max'),'String')); + + if TIME_MAX > Visu.Data.Time(end) * 1000 + TIME_MAX = Visu.Data.Time(end) * 1000; + set(findobj(DATAPLOT,'Tag','time_max'),'String',num2str(TIME_MAX,5)) + end + + if TIME_MIN < Visu.Data.Time(1) * 1000 + TIME_MIN = Visu.Data.Time(1) * 1000; + set(findobj(DATAPLOT,'Tag','time_min'),'String',num2str(TIME_MIN,5)) + end + + delta_t = 1000*(Visu.Data.Time(2)-Visu.Data.Time(1)); + samples = round(([TIME_MIN:delta_t:TIME_MAX]-Visu.Data.Time(1)*1000)/delta_t)+1; + + M = max(max(abs(Visu.Data.F(Visu.ChannelID{current}(IDs),samples)))); + + fignplot = findobj(0,'Tag','nplot_win'); % Figure of overlaping plots for the current modality + if isempty(fignplot) + fig = figure; + delete(findobj(fig,'type','axes')) + movegui(fig,'center') + set(fig,'Tag','nplot_win') + else + figure(fignplot) + clf + end + + % Visualization + N = ceil(length(IDs)/ncol); % Maximum number of channels to visualize per column + + delta = .95/(N+1); % Space between plots (in normalized units) + hold on + for col = 1:ncol % For each column of the layout + + if col1 + axes + end + + i = 0; + + for k = IDscol + i = i+1; + plotwaves = plot([TIME_MIN:delta_t:TIME_MAX],Visu.Data.F(Visu.ChannelID{current}(k),samples)+(2*M*i)); + hold on + end + + set(gca,'units','normalized','ygrid','on','xgrid','on','Box','on',... + 'Position',[(col-1)*0.96/ncol + 0.039, 0.035, 0.80/ncol, 0.96],... + 'Yticklabel',{Visu.Channel(Visu.ChannelID{current}(IDscol)).Name},'Ytick',2*M*[1:length(IDscol)],... + 'FontName','Helvetica','FontSize',7,'FontUnits','Points','Fontweight','light',... + 'xColor',[.4 .4 .4], 'ycolor', [.05 .45 .80],'XMinorGrid','on',... + 'Xlim',[TIME_MIN,TIME_MAX],'Ylim',[0 2*M*(N+1)]) + % set(gca,'Fontunits','normal') + liine = line([0 0],get(gca,'Ylim'),'linewidth',1); + set(liine,'color','r','Tag','cursor') + end + + hold off + %--------------------------------------------------------------'-------------- +case 'selectchannels' % Selection of Good/Bad Channels + Visu = get(DATAPLOT,'Userdata'); + previous = findobj(0,'Tag','channel_select'); + + if length(current) > 1 + errordlg('Please select either MEG, EEG or OTHER data subset') + return + end + + for kur = 1:length(current) + + goodchannels = setdiff(Visu.ChannelID{current(kur)},find(Visu.Data.ChannelFlag == -1)) - min(Visu.ChannelID{current(kur)})+1; % Discard bad channels + + if ~isempty(previous) + figure(previous) + channel_select_win = previous; + available = findobj(channel_select_win,'Tag','available'); + removed = findobj(channel_select_win,'Tag','removed'); + channel_removed = get(channel_select_win,'Userdata'); + if isfield(channel_removed,str{current}) + eval(['removedIDs = channel_removed.',str{current},';']); + set(removed,'String',{Visu.ChannelID{current(kur)}(goodchannels(removedIDs)).Name}) + + set(removed,'Value',1,'Max',length(get(removed,'String'))) + chan = [1:length(Visu.ChannelID{current(kur)})]; + + set(available,'String',{Visu.Channel(Visu.ChannelID{current(kur)}(goodchannels(setdiff(chan,removedIDs)))).Name}); + set(available,'Value',1,'Max',length(get(available,'String'))) + else + set(removed,'String','') + set(available,'String', {Visu.Channel(Visu.ChannelID{current(kur)}(goodchannels)).Name}); + + set(available,'Max',length(Visu.ChannelID{current(kur)})) + set(removed,'Max',length(get(removed,'String'))) + end + else + channel_select_win = openfig('channel_select.fig'); + available = findobj(channel_select_win,'Tag','available'); + removed = findobj(channel_select_win,'Tag','removed'); + goodchannels = setdiff(Visu.ChannelID{current},find(Visu.Data.ChannelFlag == -1)) - min(Visu.ChannelID{current(kur)})+1; % Discard bad channels + badchannels = intersect(Visu.ChannelID{current},find(Visu.Data.ChannelFlag == -1)) - min(Visu.ChannelID{current(kur)})+1; + + removed = findobj(channel_select_win,'Tag','removed'); + available = findobj(channel_select_win,'Tag','available'); + + set(available,'String', {Visu.Channel(Visu.ChannelID{current(kur)}(goodchannels)).Name}); + + if ~isempty(badchannels) + set(removed,'String', {Visu.Channel(Visu.ChannelID{current(kur)}(badchannels)).Name}); + else + set(removed,'String', ''); + end + + set(available,'Value',1,'Max',length(get(available,'String'))) + set(removed,'Value',1,'Max',max([1,length(get(removed,'String'))])) + + end + end + + + set(channel_select_win,'Name',[modality{current(kur)},' Channels']) + + %---------------------------------------------------------------------------- + +case 'channel_remove' % Remove Channels + if length(current) > 1 + errordlg('Please select either MEG, EEG or OTHER data subset') + return + end + + channel_select_win = findobj(0,'Tag','channel_select'); + available = findobj(channel_select_win,'Tag','available'); + removed = findobj(channel_select_win,'Tag','removed'); + removeID = get(available,'Value'); + IDs = get(available,'String'); + if isempty(IDs) + return + end + chan = [1:length(IDs)]; + + set(available,'String', IDs(setdiff(chan,removeID))); + if isempty(get(removed,'String')) + set(removed,'String', [IDs(removeID)] ); + else + strtmp = ([cellstr(get(removed,'String'));IDs(removeID)]); + strtmp = sort(strtmp); + set(removed,'String', cellstr(strtmp)); + end + set(available,'Value',1,'Max',length(get(available,'String'))) + set(removed,'Value',1,'Max',max([1,length(get(removed,'String'))])) + + + %---------------------------------------------------------------------------- + +case 'channel_restore' % Restore Channels - Make them become available again + if length(current) > 1 + errordlg('Please select either MEG, EEG or OTHER data subset') + return + end + channel_select_win = findobj(0,'Tag','channel_select'); + available = findobj(channel_select_win,'Tag','available'); + removed = findobj(channel_select_win,'Tag','removed'); + availableID = get(removed,'Value'); + IDs = get(removed,'String'); + if isempty(IDs) + return + end + chan = [1:length(IDs)]; + + set(removed,'Max',max([1,length(setdiff(chan,availableID))])) + set(removed,'Value',1,'String',IDs(setdiff(chan,availableID))); + + if isempty(get(available,'String')) + set(available,'String', [IDs(availableID)]); + else + strtmp = ([cellstr(get(available,'String'));IDs(availableID)]); + strtmp = sort(strtmp); + set(available,'String', cellstr(strtmp)); + end + set(available,'Value',1,'Max',length(get(available,'String'))) + + + %---------------------------------------------------------------------------- +case 'GenerateAverage' % Generate Time-Locked Average of all Data Files in Current Study + + hDATAPLOT = guihandles(DATAPLOT); + + disp('Time-locked averaging of all data files from current study...') + cd(Users.STUDIES) + [path,file,ext] = fileparts(Users.CurrentData.StudyFile); + + [DataDir,DataPopup,Leader] = find_brainstorm_files('data',fullfile(Users.STUDIES,path)); + ndatafiles = 0; + + for file = DataDir + + cd(fullfile(Users.STUDIES,path)) + if isempty(findstr(file{:},'results')) & isempty(findstr(file{:},'avr.mat')) + ndatafiles = ndatafiles+1 + load(file{:},'F','Time') + Users.CurrentData.DataFile = file{:}; + guidata(TASKBAR,Users) + + if get(hDATAPLOT.SetPeakLatency,'Value') % Adjust peak latency + Visu = get(DATAPLOT,'Userdata'); + Visu.Data.F = F; + Visu.Data.Time = Time; + set(DATAPLOT,'Userdata',Visu) + dataplot_cb SetPeakLatency + + %Adjust time window + Visu = get(DATAPLOT,'Userdata'); + if ndatafiles == 1 + srate = abs(Visu.Data.Time(1)-Visu.Data.Time(2)); + zeroLat = round(abs(Visu.Data.Time(1))/srate)+1; + zeroLatOld = zeroLat; + end + + end + + if ndatafiles == 1 + Fav = F; + iunder = findstr(file{:},'_'); % Generate average data file name + avFileName = file{:}(1:iunder(end)); + Mod = {'MEG','EEG','OTHER'}; + avFileName = [avFileName,Mod{current},'avr.mat']; + Data = load(file{:}); + + else + + if get(hDATAPLOT.SetPeakLatency,'Value') % Adjust peak latency + % Find sample corresponding to time 0ms + zeroLat = round(abs(Visu.Data.Time(1))/srate)+1; + if zeroLat < zeroLatOld + Fav = Fav(:,zeroLatOld-zeroLat+1:end); + % F = F(:,1:end-zeroLatOld+zeroLat); + Fav = Fav(:,1:min([size(F,2),size(Fav,2)])); + F = F(:,1:min([size(F,2),size(Fav,2)])); + zeroLatOld = zeroLat; + else + F = F(:,zeroLat-zeroLatOld+1:end); + % Fav = Fav(:,1:end-zeroLat+zeroLatOld); + Fav = Fav(:,1:min([size(F,2),size(Fav,2)])); + F = F(:,1:min([size(F,2),size(Fav,2)])); + zeroLat = zeroLatOld; + end + end + + Fav = Fav+F; + % Find maximum latency of this running average + Time(1) = sign(Visu.Data.Time(1))*(zeroLat-1)*srate; + Time = Time(1):srate:(Time(1)+(size(Fav,2)-1)*srate); + Visu.Data.Time = Time; + Visu.Data.F = Fav; + set(DATAPLOT,'Userdata',Visu) + Users.CurrentData.DataFile = avFileName; + guidata(TASKBAR,Users) + + dataplot_cb SetPeakLatency + + Visu = get(DATAPLOT,'Userdata'); + zeroLat = round(abs(Visu.Data.Time(1))/srate)+1; + zeroLatOld = zeroLat + end + end + end + + srate = abs(Visu.Data.Time(1)-Visu.Data.Time(2)); + % Find sample corresponding to time 0ms + + % New time vector + Time(1) = sign(Visu.Data.Time(1))*(zeroLat-1)*srate; + Time = Time(1):srate:(Time(1)+(size(Fav,2)-1)*srate); + Visu.Data.Time = Time; + set(hDATAPLOT.time_min,'String',num2str(Time(1)*1000,'%3.2f')); + set(hDATAPLOT.time_max,'String',num2str(Time(end)*1000,'%3.2f')); + + Data.F = Fav/ndatafiles; + Data.Time = Time; + + save_fieldnames(Data,avFileName); + disp('Time-locked averaging all data files from current study...->DONE') + disp([num2str(ndatafiles),' files averaged']) + + +%---------------------------------------------------------------------------- + +case 'channel_select_done' % Update the channel information / plots + + Visu = get(DATAPLOT,'Userdata'); + + if length(current) > 1 + errordlg('Please select either MEG, EEG or OTHER data subset') + return + end + + str = {'MEG','EEG','OTHER'}; + + channel_select_win = gcbf; + removed = findobj(channel_select_win,'Tag','removed'); + + [tmp,IDs] = intersect({Visu.Channel(Visu.ChannelID{current}).Name},get(removed,'String')); + + % Keep Removed Channels in Memory + channel_removed = get(channel_select_win,'Userdata'); + if ~isempty(channel_removed) + eval(['channel_removed.',str{current},' = IDs;']) + set(channel_select_win,'Userdata',channel_removed); + end + + if isempty(IDs) + Visu.Data.ChannelFlag(Visu.ChannelID{current}) = ones(size(Visu.ChannelID{current})); %Restore to Good Channel + else + Visu.Data.ChannelFlag(IDs + min(Visu.ChannelID{current}) - 1) = -ones(size(IDs)); % Mark as bad, i.e. -1 + end + + Visu.Alter = 1; % Data set is altered from original version (channels were removed - keep that in mind when calling SourceImaging tools) + + set(DATAPLOT,'Userdata',Visu) + + figsingle = findobj(0,'Tag',['waves_single',int2str(current)]); % Figure of overlaping plots for the current modality + if isempty(figsingle) + return + end + +% if current==2 % In case of EEG data - remove average reference when requested +% dataplot_cb RemoveEEGAverage +% end + + ChannelSelectAllDataSets = findobj(channel_select_win,'Tag','ChannelSelectAllDataSets');; + if get(ChannelSelectAllDataSets,'Value') % Apply this selection to all the data setsfrom this study + disp('Updating all data files from current study...') + cd(Users.STUDIES) + [path,file,ext] = fileparts(Users.CurrentData.StudyFile); + + [DataDir,DataPopup,Leader] = find_brainstorm_files('data',fullfile(Users.STUDIES,path)); + ndatafiles = 0; + clear ChannelFlag + for file = DataDir + cd(fullfile(Users.STUDIES,path)) + if isempty(findstr(file{:},'results')) + load(file{:},'ChannelFlag') + end + if exist('ChannelFlag','var') % Skip this file + ndatafiles = ndatafiles+1 + ChannelFlag = Visu.Data.ChannelFlag; + save(file{:},'ChannelFlag','-append') + clear ChannelFlag + end + end + disp('Updating all data files from current study...-> DONE') + disp([num2str(ndatafiles),' file updated']) + else + disp('Updating current data file...') + cd(Users.STUDIES) + [path,file,ext] = fileparts(Users.CurrentData.StudyFile); + cd(path) + clear ChannelFlag + load(Users.CurrentData.DataFile,'ChannelFlag') + if exist('ChannelFlag','var') % Skip this file + ChannelFlag = Visu.Data.ChannelFlag; + save(Users.CurrentData.DataFile,'ChannelFlag','-append') + clear ChannelFlag + end + disp('Updating current data file... -> DONE') + end + + dataplot_cb quicklook + + %---------------------------------------------------------------------------- + +case '2dlayout' + Visu = get(DATAPLOT,'Userdata'); + MEG = get(findobj(DATAPLOT,'Tag','MEG'),'value'); + EEG = get(findobj(DATAPLOT,'Tag','EEG'),'value'); + OTHER = get(findobj(DATAPLOT,'Tag','OTHER'),'value'); + current = find([MEG,EEG,OTHER] == 1); + channel_select_win = findobj(0,'Tag','channel_select'); + layout = findobj(0,'Tag',['layout_',modality{current}]); + + timescale = 10; + + if ~isempty(find(Visu.Data.ChannelFlag == -1)) + goodchannels = setdiff(Visu.ChannelID{current},find(Visu.Data.ChannelFlag == -1));%- min(Visu.ChannelID{current})+1 ; % Discard bad channels + badchannels = intersect(Visu.ChannelID{current},find(Visu.Data.ChannelFlag == -1));%- min(Visu.ChannelID{current})+1; + else + goodchannels = setdiff(Visu.ChannelID{current},find(Visu.Data.ChannelFlag == -1));%- min(Visu.ChannelID{current})+1 ;;%Visu.ChannelID{current}; + badchannels = []; + end + + TIME_MIN = str2num(get(findobj(DATAPLOT,'Tag','time_min'),'String')); + TIME_MAX = str2num(get(findobj(DATAPLOT,'Tag','time_max'),'String')); + delta_t = 1000*(Visu.Data.Time(2)-Visu.Data.Time(1)); + samples = round(([TIME_MIN:delta_t:TIME_MAX]-Visu.Data.Time(1)*1000)/delta_t)+1; + + + j = 0; + for chan = goodchannels%Visu.ChannelID{current} + j = j+1; + chanloc(:,j) = Visu.Channel(chan).Loc(:,1); + end + + figure + set(gcf,'Color','k') + + minn = min(chanloc')'; + maxx = max(chanloc')'; + chanloc(3,:) = chanloc(3,:) - maxx(3); + + [TH,PHI,R] = cart2sph(chanloc(1,:),chanloc(2,:),chanloc(3,:)); + PHI2 = zeros(size(PHI)); + R2 = R./cos(PHI).^.2; + + [Y,X] = pol2cart(TH,R2); + dat = Visu.Data.F(goodchannels,samples); + M = max(abs(dat(:))); + + My = max(abs(Y)); + Mx = max(abs(X)); + Mt = TIME_MAX-TIME_MIN; + + + for i = 1:length(goodchannels) + dat = Visu.Data.F(goodchannels(i),samples); + h = plot(Mx*[TIME_MIN:delta_t:TIME_MAX]/(timescale*Mt)-X(i),(My*dat/(10*M)+Y(i)),'color',[.9 .9 .9]); + set(h,'ButtondownFcn','dataplot_cb line_select') + hold on + ttl = text(Mx*TIME_MIN/(timescale*Mt)-X(i),.5*My*M/(10*M)+Y(i),Visu.Channel((goodchannels(i))).Name); + set(ttl,'fontsize',8,'color',.8*[1 1 1]) + end + axis equal + axis off + + %---------------------------------------------------------------------------- +case 'mapping_create' % Generates SLIDES and/or MOVIES + + Visu = get(DATAPLOT,'Userdata'); + MEG = get(findobj(DATAPLOT,'Tag','MEG'),'value'); + EEG = get(findobj(DATAPLOT,'Tag','EEG'),'value'); + OTHER = get(findobj(DATAPLOT,'Tag','OTHER'),'value'); + current = find([MEG,EEG,OTHER] == 1); + + mapping_win = findobj(0,'Tag','mapping'); + if isempty(mapping_win) + open mapping.fig; + mapping_win = findobj(0,'Tag','mapping'); + else + return + end + + RAW = findobj(mapping_win ,'Tag','raw'); + GRADIENT = findobj(mapping_win ,'Tag','gradient'); + MAGNETIC = findobj(mapping_win,'Tag','magnetic'); + + if current == 1 % If MEG + set([RAW,GRADIENT,MAGNETIC],'enable','on') + set(RAW,'Value',1) + else + set([RAW,GRADIENT,MAGNETIC],'enable','off') + end + + start = findobj(mapping_win,'Tag','start'); + step = findobj(mapping_win,'Tag','step'); + stop = findobj(mapping_win,'Tag','stop'); + current_time = findobj(mapping_win,'Tag','current_time'); + slider_time = findobj(mapping_win,'Tag','slider_time'); + go = findobj(mapping_win,'Tag','go'); + SPHERE = findobj(mapping_win,'Tag','sphere'); + fit = findobj(mapping_win,'Tag','fit'); + ROTATE = findobj(mapping_win,'Tag','rotate'); + fit = findobj(mapping_win,'Tag','fit'); + slides = findobj(mapping_win,'Tag','slides'); + single = findobj(mapping_win,'Tag','single'); + MOVIE = findobj(mapping_win,'Tag','movie'); + + handles = guihandles(mapping_win); + + set(mapping_win,'Name',[get(mapping_win,'Name'), ' - ', modality{current}]) + + set(start,'String', num2str(1000*Visu.Data.Time(1))) + set(stop,'String', num2str(1000*Visu.Data.Time(end))) + if length(Visu.Data.Time) > 1 + set(step,'String', num2str(1000*(Visu.Data.Time(2)-Visu.Data.Time(1)),2)) + else + set(step,'String', 0) + end + + + TIME_MIN = str2num(get(findobj(DATAPLOT,'Tag','time_min'),'String')); + TIME_MAX = str2num(get(findobj(DATAPLOT,'Tag','time_max'),'String')); + + if TIME_MAX > Visu.Data.Time(end) * 1000 + TIME_MAX = Visu.Data.Time(end) * 1000; + set(findobj(DATAPLOT,'Tag','time_max'),'String',num2str(TIME_MAX,5)) + end + + if TIME_MIN < Visu.Data.Time(1) * 1000 + TIME_MIN = Visu.Data.Time(1) * 1000; + set(findobj(DATAPLOT,'Tag','time_min'),'String',num2str(TIME_MIN,5)) + end + + if ~isempty(mapping_win) + slider_time = findobj(mapping_win,'Tag','slider_time'); + if length(Visu.Data.Time) > 1 + set(slider_time,'enable','on') + set(slider_time,'Min',TIME_MIN,'Max',TIME_MAX) + else + set(slider_time,'Min',TIME_MIN,'Max',2*TIME_MAX) + set(slider_time,'enable','off') + end + + end + set(slider_time,'Value',TIME_MIN,'enable','on') + + set(current_time,'String',num2str(get(slider_time,'Value'),4)) + set(single,'Value',1) + mutincomp([single,slides,MOVIE]) + set(SPHERE,'Value',1) + mutincomp([SPHERE,fit,handles.scalp]) + + % Draw time cursor + figsingle = findobj(0,'Tag',['waves_single',int2str(current)]); % Figure of overlaping plots for the current modality + if ~isempty(figsingle) + figure(figsingle) + ctime = get(slider_time,'Value'); + cursor = line([ctime ctime],get(gca,'Ylim')); + set(cursor,'color','r','linewidth',3,'Tag','cursor','erasemode','Xor') + end + + fignplot = findobj(0,'Tag','nplot_win'); % superimposed n-plots + if ~isempty(fignplot) + figure(fignplot) + ctime = get(slider_time,'Value'); + cursor = line([ctime ctime],get(gca,'Ylim')); + set(cursor,'color','r','linewidth',3,'Tag','cursor','erasemode','Xor') + end + %---------------------------------------------------------------------------- + +case 'mapping_slider' % Update of the current time sample using the slider + + Visu = get(DATAPLOT,'Userdata'); + + mapping_win = openfig('mapping.fig','reuse'); + current_time = findobj(mapping_win,'Tag','current_time'); + slider_time = findobj(mapping_win,'Tag','slider_time'); + step = findobj(mapping_win,'Tag','step'); + + set(current_time,'String',num2str(get(slider_time,'Value'),5)) + + ctime = get(slider_time,'Value')/1000; + + % Draw time cursor + for k=1:length(current) + % Figure of overlaping plots for the current modality + figsingle{k} = findobj(0,'Tag',['waves_single',int2str(current(k))]); + if ~isempty(figsingle{k}) + figure(figsingle{k}) + cursor = findobj(figsingle{k},'Tag','cursor'); + if isempty(cursor) + cursor = line(1000*[ctime ctime],get(gca,'Ylim')); + set(cursor,'color','r','linewidth',1,'Tag','cursor','erasemode','Xor') + else + set(cursor,'Xdata',1000*[ctime ctime]) + end + + text_cursor = findobj(figsingle{k},'Tag','text_cursor'); + if isempty(text_cursor) + text_cursor = text(1000*[ctime],max(get(gca,'Ylim'))/1.1,num2str(1000*ctime,4)); + set(text_cursor ,'Horizontalalignment','right','Fontweight','bold','color','k','fontsize',12,... + 'Tag','text_cursor','erasemode','xor') + else + set(text_cursor,'Position',[1000*[ctime],max(get(gca,'Ylim'))/1.1],'String',num2str(1000*ctime,4)) + end + end + end + + fignplot = findobj(0,'Tag','nplot_win'); % Figure of overlaping plots for the current modality + if ~isempty(fignplot) + figure(fignplot) + cursor = findobj(fignplot,'Tag','cursor'); + if isempty(cursor) + cursor = line(1000*[ctime ctime],get(gca,'Ylim')); + set(cursor,'color','r','linewidth',3,'Tag','cursor','erasemode','Xor') + else + set(cursor,'Xdata',1000*[ctime ctime]) + end + + text_cursor = findobj(fignplot,'Tag','text_cursor'); + if isempty(text_cursor) + text_cursor = text(1000*[ctime],.98*max(get(gca,'Ylim')),num2str(1000*ctime,4)); + set(text_cursor ,'Horizontalalignment','right','Fontweight','demi','color','k',... + 'fontsize',10,'fontunits','points',... + 'Tag','text_cursor','erasemode','xor') + else + set(text_cursor,'Position',[1000*[ctime],.98*max(get(gca,'Ylim'))],'String',num2str(1000*ctime,4)) + end + end + + % SurfaceViewer update + TessSelectWin = findobj(0,'Tag','tesselation_select'); + if ~isempty(TessSelectWin) % Check if Surface Viewer is open + handles = guihandles(TessSelectWin ); + ScalpMaps = get(handles.ScalpMaps,'Value'); + ColorSensors = get(handles.ColorSensors,'Value'); + CorticalMap = get(handles.CorticalMap,'Value'); + dataplot_cb tesselation_select_done + end + + %Update data mapping if exists + for k = 1:length(current) + map_single_win = findobj(0,'Tag',['map_single',int2str(current(k))]); + if isempty(map_single_win), return, end + map_sph = get(map_single_win,'Userdata'); + srate = abs(Visu.Data.Time(1)-Visu.Data.Time(2) ); + ctime = round(( get(slider_time,'Value')/1000-Visu.Data.Time(1))/srate)+1; + mapping_win = findobj(0,'Tag','mapping'); + RAW = findobj(mapping_win ,'Tag','raw'); + GRADIENT = findobj(mapping_win ,'Tag','gradient'); + MAGNETIC = findobj(mapping_win,'Tag','magnetic'); + if strcmp(get(RAW,'enable'),'on') % MEG Case + RAW = get(RAW,'Value'); + GRADIENT = get(GRADIENT,'Value'); + MAGNETIC = get(MAGNETIC,'Value'); + current_visu = find([RAW,GRADIENT,MAGNETIC] == 1); + else + current_visu = NaN; + end + ABSOLUTE = get(findobj(mapping_win,'Tag','ABSOLUTE'),'value'); + RELATIVE = get(findobj(mapping_win,'Tag','RELATIVE'),'value'); + + % Channels Locations: + if ~isempty(find(Visu.Data.ChannelFlag == -1)) + goodchannels = setdiff(Visu.ChannelID{current(k)},find(Visu.Data.ChannelFlag == -1));%- min(Visu.ChannelID{current})+1 ; % Discard bad channels + badchannels = intersect(Visu.ChannelID{current(k)},find(Visu.Data.ChannelFlag == -1));%- min(Visu.ChannelID{current})+1; + else + goodchannels = setdiff(Visu.ChannelID{current(k)},find(Visu.Data.ChannelFlag == -1));%- min(Visu.ChannelID{current})+1 ; + badchannels = []; + end + data = Visu.Data.F(goodchannels,ctime) ; + + if (current_visu == 2) & (current(k) == 1)% & ~isempty(badchannels) % MEG with bad channels and visualization of the gradient amplitude + channels = [1:length(Visu.ChannelID{current(k)})]; + odd_channels = channels(find(rem(channels,2) == 1)); + if ~isempty(badchannels) + odd = badchannels(find(rem(badchannels,2) == 1)); + even = setdiff(badchannels,odd); + odd = unique([odd',intersect(odd_channels ,(even - 1))]); + else + odd = []; + even = []; + end + + good_odd_channels = setdiff(odd_channels,odd); + grad_meg = []; + j = 0; + for i = (good_odd_channels) + j = j+1; + grad_meg(j) = norm([Visu.Data.F(i,ctime),Visu.Data.F(i+1,ctime)]); + end + data = grad_meg; + elseif current_visu == 3 & current(k) == 1 + data = Visu.MNEdata(:,ctime); + end + + try + cdata_map = griddata(map_sph.x,map_sph.y,data,map_sph.xs,map_sph.ys,'invdist'); + catch + errordlg('First Hit the Refresh Button of the Mapping Window when Channels are Removed') + return + end + + set(map_sph.handle,'CData',cdata_map) + + if RELATIVE == 1 + if current_visu == 3 + datacomplete = Visu.MNEdata; + else + datacomplete = Visu.Data.F(goodchannels,:); + end + + Max = max(abs(datacomplete(:))); + figure(map_single_win) + if (current_visu == 2) & (current(k) == 1)% & ~isempty(badchannels) % MEG with bad channels and visualization of the gradient amplitude + caxis([0,Max]) + else + caxis([-Max,Max]) + end + else + figure(map_single_win) + if (current_visu == 2) & (current(k) == 1)% & ~isempty(badchannels) % MEG with bad channels and visualization of the gradient amplitude + caxis([0,max(abs(data))]) + else + caxis([-max(abs(data)),max(abs(data))]) + end + + end + end + + %---------------------------------------------------------------------------- + +case 'set_current_time' + Visu = get(DATAPLOT,'Userdata'); + + mapping_win = findobj(0,'Tag','mapping'); + current_time = findobj(mapping_win,'Tag','current_time'); + slider_time = findobj(mapping_win,'Tag','slider_time'); + step = findobj(mapping_win,'Tag','step'); + + ctime = str2num(get(current_time,'String'))/1000; + + set(slider_time,'Value',1000*ctime); + dataplot_cb mapping_slider + + % Draw time cursor + figsingle = findobj(0,'Tag',['waves_single',int2str(current)]); % Figure of overlaping plots for the current modality + if ~isempty(figsingle) + figure(figsingle) + cursor = findobj(figsingle,'Tag','cursor'); + if isempty(cursor) + cursor = line(1000*[ctime ctime],get(gca,'Ylim')); + set(cursor,'color','r','linewidth',3,'Tag','cursor','erasemode','Xor') + else + set(cursor,'Xdata',1000*[ctime ctime]) + end + + text_cursor = findobj(figsingle,'Tag','text_cursor'); + if isempty(text_cursor) + text_cursor = text([1000*ctime],max(get(gca,'Ylim'))/1.1,num2str(1000*ctime,4)); + set(text_cursor ,'Horizontalalignment','right','Fontweight','bold','color','k','fontsize',12,... + 'Tag','text_cursor','erasemode','xor') + else + set(text_cursor,'Position',[1000*[ctime],max(get(gca,'Ylim'))/1.1],'String',num2str(1000*ctime,4)) + end + end + + + % Draw time cursor + fignplot = findobj(0,'Tag','nplot_win'); + if ~isempty(fignplot) + figure(fignplot) + cursor = findobj(fignplot,'Tag','cursor'); + if isempty(cursor) + cursor = line(1000*[ctime ctime],get(gca,'Ylim')); + set(cursor,'color','r','linewidth',3,'Tag','cursor','erasemode','Xor') + else + set(cursor,'Xdata',1000*[ctime ctime]) + end + + text_cursor = findobj(fignplot,'Tag','text_cursor'); + if isempty(text_cursor) + text_cursor = text(1000*[ctime],max(get(gca,'Ylim')),num2str(1000*ctime,4)); + set(text_cursor ,'Horizontalalignment','right','Fontweight','bold','color','k','fontsize',12,... + 'Tag','text_cursor','erasemode','xor') + else + set(text_cursor,'Position',[1000*[ctime],max(get(gca,'Ylim'))],'String',num2str(1000*ctime,4)) + end + end + + %Update data mapping if exists + map_single_win = findobj(0,'Tag',['map_single',int2str(current)]); + if isempty(map_single_win), return, end + map_sph = get(map_single_win,'Userdata'); + srate = abs(Visu.Data.Time(1)-Visu.Data.Time(2) ); + ctime = round((ctime-Visu.Data.Time(1))/srate)+1; + mapping_win = findobj(0,'Tag','mapping'); + RAW = findobj(mapping_win ,'Tag','raw'); + GRADIENT = findobj(mapping_win ,'Tag','gradient'); + MAGNETIC = findobj(mapping_win,'Tag','magnetic'); + if strcmp(get(RAW,'enable'),'on') % MEG Case + RAW = get(RAW,'Value'); + GRADIENT = get(GRADIENT,'Value'); + MAGNETIC = get(MAGNETIC,'Value'); + current_visu = find([RAW,GRADIENT,MAGNETIC] == 1); + else + current_visu = NaN; + end + ABSOLUTE = get(findobj(mapping_win,'Tag','ABSOLUTE'),'value'); + RELATIVE = get(findobj(mapping_win,'Tag','RELATIVE'),'value'); + + % Channels Locations: + if ~isempty(find(Visu.Data.ChannelFlag == -1)) + goodchannels = setdiff(Visu.ChannelID{current},find(Visu.Data.ChannelFlag == -1));%- min(Visu.ChannelID{current})+1 ; % Discard bad channels + badchannels = intersect(Visu.ChannelID{current},find(Visu.Data.ChannelFlag == -1));%- min(Visu.ChannelID{current})+1; + else + goodchannels = setdiff(Visu.ChannelID{current},find(Visu.Data.ChannelFlag == -1));%- min(Visu.ChannelID{current})+1 ; + badchannels = []; + end + data = Visu.Data.F(goodchannels,ctime) ; + + if (current_visu == 2) & (current == 1)% & ~isempty(badchannels) % MEG with bad channels and visualization of the gradient amplitude + channels = [1:length(Visu.ChannelID{current})]; + odd_channels = channels(find(rem(channels,2) == 1)); + if ~isempty(badchannels) + odd = badchannels(find(rem(badchannels,2) == 1)); + even = setdiff(badchannels,odd); + odd = unique([odd',intersect(odd_channels ,(even - 1))]); + else + odd = []; + even = []; + end + + good_odd_channels = setdiff(odd_channels,odd); + grad_meg = []; + j = 0; + for i = (good_odd_channels) + j = j+1; + grad_meg(j) = norm([Visu.Data.F(i,ctime),Visu.Data.F(i+1,ctime)]); + end + data = grad_meg; + end + + try + cdata_map = griddata(map_sph.x,map_sph.y,data,map_sph.xs,map_sph.ys,'invdist'); + catch + errordlg('First Hit the Refresh Button of the Mapping Window when Channels are Removed') + return + end + + set(map_sph.handle,'CData',cdata_map) + if RELATIVE == 1 + + if current_visu == 3 + datacomplete = Visu.MNEdata; + else + datacomplete = Visu.Data.F(goodchannels,:); + end + Max = max(abs(datacomplete(:))); + figure(map_single_win) + if (current_visu == 2) & (current == 1)% & ~isempty(badchannels) % MEG with bad channels and visualization of the gradient amplitude + caxis([0,Max]) + else + caxis([-Max,Max]) + end + + else + figure(map_single_win) + if (current_visu == 2) & (current == 1)% & ~isempty(badchannels) % MEG with bad channels and visualization of the gradient amplitude + caxis([0,max(abs(data))]) + else + caxis([-max(abs(data)),max(abs(data))]) + end + + end + + + %----------------------------------------------------------------------------------------------- + +case 'gomapping' % Display the map according to featured parameters + + set(findobj(0,'Tag','rotate'),'Value',0) + + Visu = get(DATAPLOT,'Userdata'); + + str = {'MEG','EEG','OTHER'}; + mapping_win = findobj(0,'Tag','mapping'); + + ABSOLUTE = get(findobj(mapping_win,'Tag','ABSOLUTE'),'value'); + RELATIVE = get(findobj(mapping_win,'Tag','RELATIVE'),'value'); + + RAW = findobj(mapping_win ,'Tag','raw'); + GRADIENT = findobj(mapping_win ,'Tag','gradient'); + MAGNETIC = findobj(mapping_win,'Tag','magnetic'); + if strcmp(get(RAW,'enable'),'on') % MEG Case + RAW = get(RAW,'Value'); + GRADIENT = get(GRADIENT,'Value'); + MAGNETIC = get(MAGNETIC,'Value'); + current_visu = find([RAW,GRADIENT,MAGNETIC] == 1); + else + current_visu = NaN; + end + + handles = guihandles(mapping_win); + + % Detect Display/Type + display_type = get([handles.single,handles.slides,handles.movie],'Value'); + head_shape =(get([handles.sphere,handles.fit,handles.scalp],'Value')); % Sphere, Adujst to sensors or map to scalp. + head_shape =find([head_shape{:}]); + + for k = 1:length(current) + % Channels Locations: + if ~isempty(find(Visu.Data.ChannelFlag == -1)) + goodchannels{k} = setdiff(Visu.ChannelID{current(k)},find(Visu.Data.ChannelFlag == -1)); + badchannels{k} = intersect(Visu.ChannelID{current(k)},find(Visu.Data.ChannelFlag == -1)); + else + goodchannels{k} = setdiff(Visu.ChannelID{current(k)},find(Visu.Data.ChannelFlag == -1)); + badchannels{k} = []; + end + + j = 0; + for chan = goodchannels{k} + j = j+1; + chanloc{k}(:,j) = Visu.Channel(chan).Loc(:,1); + end + end + + %------------------------------------------------------------- + + % Check if we have access to the projection of the magnetic field + + % Load MNE matrix in the headmodel.mat file + if ~isfield(Visu.Data,'System') + Visu.Data.System = 'ctf'; + end + if ~isfield(Visu,'MNEdata') & ~strcmp(Visu.Data.System,'ctf') % non ctf systems only + + rooot = findstr(Users.CurrentData.StudyFile,'brainstormstudy.mat'); + if isempty(rooot) + errordlg('Study file name should be of the form ''*brainstormstudy.mat''') + return + end + rooot = Users.CurrentData.StudyFile(1:rooot-2); + HeadModelFile = fullfile(Users.STUDIES,[rooot ,'_headmodel.mat']); + MEG = (find(current == 1)); + if ~isempty(MEG) % MEG was selected as a modality + if verbose + disp('Computing gain matrix for Min. Norm estimate of the magnetic field from sensors...') + drawnow + end + + sensMEGMNE = goodchannels{MEG}; + global ChannelFlag + ChannelFlag = ones(length(Visu.Channel),1); + ChannelFlag(badchannels{MEG}) = -1; + nsrc = length(sensMEGMNE); + mne_src = [ones(size(chanloc{MEG})).*chanloc{MEG}/3]; % Choose sources this way -> hence they are inside the sensor helmet + % Sources should be quite deep to smooth out their contribution to the sensors + Chan = Visu.Channel(sensMEGMNE); + [Chan.Weight] = deal([1 0]); % Null weight associated to second coil of the gradiometer + + mass = mean(chanloc{MEG}'); % center of mass of the scalp vertex locations + R0 = mean(norlig(chanloc{MEG}' - ones(size(chanloc{MEG}',1),1)*mass)); % Average distance betw + vec0 = [mass,R0]; + [minn,brp] = fminsearch('dist_sph',vec0,[],chanloc{MEG}'); + center = minn(1:end-1)'; % 3x1 + R = minn(end); + + [Param(1:length(sensMEGMNE))] = deal(struct('Center',center,'Radii',R)); + Par = Param; + + Gmne2mag = os_meg(mne_src, Chan, Par, -1); + Gmne2mag = Gmne2mag(:,1:3:end); + Gmne2grad = os_meg(mne_src, Visu.Channel(sensMEGMNE), Par, -1); + Gmne2grad = Gmne2grad(:,1:3:end); + + matt = Gmne2grad'*Gmne2grad; + MNEMag = Gmne2mag * inv(matt+1e-6*norm(matt,'fro')*eye(size(matt)))*Gmne2grad'; + + clear Par Chan + + if verbose + disp('Computing gain matrix for Min. Norm estimate of the magnetic field from sensors...-> DONE') + drawnow + end + + if ~exist(HeadModelFile,'file') + save(HeadModelFile,'MNEMag') + else + save(HeadModelFile,'MNEMag','-append') + end + + Visu.MNEdata = MNEMag*Visu.Data.F(goodchannels{MEG},:); + else + load(HeadModelFile,'MNEMag'); + end + + set(DATAPLOT,'Userdata',Visu) + end + + %------------------------------------------------------------- + + % Sampling rate + srate = abs(Visu.Data.Time(1)-Visu.Data.Time(2) ); % Assuming time is in sec. + + for kur = 1:length(current) + + switch find([display_type{:}]) + + case 1 % Single Window + % Detect 'Single-Map' window; creates if does not exist + map_single_win = findobj(0,'Tag',['map_single',int2str(current(kur))]); + if isempty(map_single_win) + map_single_win = open('map_single.fig'); + set(map_single_win,'Tag',['map_single',int2str(current(kur))],'Name',... + [str{current(kur)},' Mapping'],... + 'color',backcolor); + else + figure(map_single_win) + end + + ctime = round((get(handles.slider_time,'Value')/1000-Visu.Data.Time(1))/srate)+1; + data = Visu.Data.F(goodchannels{kur},ctime) ; + + if (current_visu == 2) & (current(kur) == 1)% & ~isempty(badchannels) % MEG with bad channels and visualization of the gradient amplitude + channels = [1:length(goodchannels{kur})]; + odd_channels = goodchannels{kur}(find(rem(goodchannels{kur},2) == 1)); + + if ~isempty(badchannels{kur}) + odd = badchannels{kur}(find(rem(badchannels{kur},2) == 1)); + even = setdiff(badchannels{kur},odd); + odd = unique([odd',intersect(odd_channels,(even - 1))]); + else + odd = []; + even = []; + end + + good_odd_channels = setdiff(odd_channels,odd); + [c,ia,ib] = intersect(goodchannels{kur},good_odd_channels);%-goodchannels(1)+1); + chanloc = chanloc(:,ia); + grad_meg = []; + j = 0; + for i = good_odd_channels + j = j+1; + grad_meg(j) = norm([Visu.Data.F(i,ctime),Visu.Data.F(i+1,ctime)]); + end + data = grad_meg; + + elseif current_visu == 3 & current(kur) == 1 % MEG and MN estimation of the magnetic field at the planar gradiometers + data = Visu.MNEdata(:,ctime); + end + + switch head_shape + case 1 % Sphere + [x,y,z,xs,ys,sensor,map_sph] = carto_sph((chanloc{kur})',data,[0 0 0]); + if get(handles.ShowSensorLocs,'Value') + set(sensor,'Visible','on'); + else + set(sensor,'Visible','off'); + end + + if current(kur) == 1 + if exist('good_odd_channels','var') + n_chan = length(good_odd_channels); + name = good_odd_channels; + else + n_chan = length(goodchannels{kur}); + name = goodchannels{kur} - min(Visu.ChannelID{current(kur)})+1 ; + end + + if get(handles.ShowSensorLabels,'Value') + for i = 1:n_chan + h = text(1.05*x(i),1.05*y(i),1.05*z(i),Visu.Channel(goodchannels{kur}(i)).Name); + set(h,'horizontalalignment','center','fontsize',8,'color',... + textcolor,'fontweight','normal') + end + end + else + if get(handles.ShowSensorLabels,'Value') + for i = 1:length(goodchannels{kur}) + h = text(1.05*x(i),1.05*y(i),1.05*z(i),Visu.Channel(goodchannels{kur}(i)).Name); + set(h,'horizontalalignment','center','fontsize',10,'color',... + textcolor,'fontweight','bold') + end + end + end + axis vis3d, %axis equal + if ~isfield(Visu.Data,'Device'); + Visu.Data.Device = 'jpol'; % Default setting + end + + if findstr(lower(Visu.Data.Device),'ctf') + view(-90,90) % Noise pointing upwards + end + + case 2 % Sensors shape + [x,y,xs,ys,sensors,map_sph] = carto_sensors(chanloc{kur}',data); + + case 3 % Scalp Surface + close(map_single_win) + + dataplot_cb mesh_rendering + TessWin = findobj(0,'Tag','tesselation_select'); + figure(TessWin) + set(findobj(TessWin,'Tag','DataVisualization'),'Value',2) + dataplot_cb AddData + return + end + + if RELATIVE == 1 + + if current_visu == 3 + datacomplete = Visu.MNEdata; + else + datacomplete = Visu.Data.F(goodchannels{kur},:); + end + M = max(abs(datacomplete(:))); + % MEG with bad channels and visualization of the gradient amplitude + if (current_visu == 2) & (current(kur) == 1) + caxis([0,M]) + colormap hot + clrbr = colorbar('horiz'); + set(clrbr,'xcolor',frontcolor,'ycolor',frontcolor) + else + caxis([-M,M]) + end + + else + if (current_visu == 2) & (current(kur) == 1)% MEG with bad channels and visualization of the gradient amplitude + caxis([0,max(abs(data))]) + else + caxis([-max(abs(data)),max(abs(data))]) + end + + end + + dataplot_cb Colorbar_mapping + + map_sph.handle = map_sph; + map_sph.x = x; + map_sph.y = y; + map_sph.xs = xs; + map_sph.ys = ys; + set(map_single_win,'Userdata',map_sph) + + + case 2 % Slides + sstart = str2num(get(handles.start,'String'))/1000; + if sstart < Visu.Data.Time(1) + sstart = Visu.Data.Time(1); + end + + sstep = str2num(get(handles.step,'String'))/1000; + + sstop = str2num(get(handles.stop,'String'))/1000; + if sstop > Visu.Data.Time(end) + sstop = Visu.Data.Time(end); + end + + samples = round(([sstart,sstop]-Visu.Data.Time(1))/srate)+1; + sstep = round(sstep/srate); + samples = samples(1):sstep:samples(2); + nsamples = length(samples); + if nsamples > 40 % Warning, too many slides may take a while to create. + button = questdlg({'You have requested ',int2str(nsamples),' slide(s)','Do you still want to proceed ?'},'','Yes','No','Yes'); + if strcmp(button,'No') + return + end + end + + map_slides_win = findobj(0,'Tag',['map_slides',int2str(current(kur)),'_',get(handles.start,'String'),'_',get(handles.step,'String'),'_',get(handles.stop,'String')]); + + if isempty(map_slides_win ) + map_slides_win = open('map_single.fig'); + set(map_slides_win ,'Tag',['map_slides',int2str(current(kur)),'_',get(handles.start,'String'),'_',get(handles.step,'String'),'_',get(handles.stop,'String')],... + 'color',backcolor); + + drawnow + + else + figure(map_slides_win) + end + + % Figure Layout + k = size(samples,2); + c = ceil(sqrt(k)); + r = ceil(sqrt(k)); + switch head_shape + case 1 % Sphere + iplot = 0; + for ctime = samples + iplot = iplot+1; + figure(map_slides_win) + subplot(c,r,iplot) + axis off + data = Visu.Data.F(goodchannels{kur},ctime)+eps; + + if (current_visu == 2) & (current == 1) % & ~isempty(badchannels) % MEG with bad channels and visualization of the gradient amplitude + if ctime == samples(1) + channels = [1:length(Visu.ChannelID{current(kur)})]; + odd_channels = channels(find(rem(channels,2) == 1)); + if ~isempty(badchannels{kur}) + odd = badchannels{kur}(find(rem(badchannels{kur},2) == 1)); + even = setdiff(badchannels{kur},odd); + odd = unique([odd',intersect(odd_channels,(even - 1))]); + + else + odd = []; + even = []; + end + good_odd_channels = setdiff(odd_channels,odd); + [cc,ia,ib] = intersect(goodchannels{kur},good_odd_channels-goodchannels{kur}(1)+1); + chanloc{kur} = chanloc{kur}(:,ia); + end + grad_meg = []; + j = 0; + for i = (good_odd_channels) + j = j+1; + grad_meg(j) = norm([Visu.Data.F(i,ctime),Visu.Data.F(i+1,ctime)]); + end + data = grad_meg; + elseif current_visu == 3 & current(kur) == 1 + data = Visu.MNEdata(:,ctime); + end + + if iplot == 1 + [x,y,z,xs,ys,sensor,map_sph] = carto_sph(chanloc{kur}',data,[0 0 0]); + set(findobj(get(gca,'Children'),'Type','line'),'Visible','off') + axorig = get(map_sph,'Parent'); + set(gcf,'CurrentAxes',axorig) + cmap = get(gcf,'Colormap'); + map_sph2 = copyobj(map_sph,gca); % Trick to enforce proper scaling of the axis + delete(map_sph) + map_sph = map_sph2; + else + map_sph2 = copyobj(map_sph,gca); + cdata_map = griddata(x,y,data,xs,ys,'invdist'); + set(map_sph2,'CData',cdata_map) + end + + if RELATIVE == 1 + if iplot == 1 + if current_visu == 3 + datacomplete = Visu.MNEdata; + else + datacomplete = Visu.Data.F(goodchannels{kur},:); + end + M = max(abs(datacomplete(:))); + end + caxis([-M,M]) + else + caxis([-max(abs(data)),max(abs(data))]) + end + axis equal + if findstr(lower(Visu.Data.Device),'ctf') + view(-90,90) % Noise pointing upwards + end + axis tight + + h = title(num2str(((ctime-1)*srate + Visu.Data.Time(1))*1000,4)); + set(h,'fontweight','normal','color',frontcolor,'Fontname','helvetica','Fontsize',8,'FontUnits','Point') + %set(h,'fontunits','normal') + % SurfaceViewer update + TessSelectWin = findobj(0,'Tag','tesselation_select'); + if ~isempty(TessSelectWin) % Check if Surface Viewer is open + cortx_handles = guihandles(TessSelectWin ); + ScalpMaps = get(cortx_handles.ScalpMaps,'Value'); + ColorSensors = get(cortx_handles.ColorSensors,'Value'); + CorticalMap = get(cortx_handles.CorticalMap,'Value'); + previous = findobj(0,'tag','tessellation_window'); + if ctime == samples(1); + pprevious = findobj(0,'tag','tesselation_select'); + hpprevious = guihandles(pprevious); + OrthoViews = get(hpprevious.OrthoViews,'Value'); +% clear hprevious + end + + SlidesWin = findobj(0,'tag','subplot_tess_window'); + if OrthoViews == 0 + if isempty(SlidesWin) + SlidesWin = open('tessellation_window.fig'); + set(SlidesWin,'tag','subplot_tess_window'); + end + else % Open a new window for each time sample when OrthoViews are requested + SlidesWin = open('tessellation_window.fig'); + set(SlidesWin,'tag','subplot_tess_window'); + end + + mapping_win = findobj(0,'Tag','mapping'); + + if ctime == samples(1) | OrthoViews == 1 + phandles = guihandles(previous); + VIEW = get(phandles.MainAxes,'view'); + Lights = findobj(phandles.MainAxes,'type','light'); + Colormap = get(previous,'Colormap'); + set(SlidesWin,'Colormap',Colormap) + end + + if ~isempty(mapping_win) + slider_time = findobj(mapping_win,'Tag','slider_time'); + current_time= findobj(mapping_win,'Tag','current_time'); + end + + set(slider_time,'Value',1000*Visu.Data.Time(ctime)) + set(current_time,'String',1000*Visu.Data.Time(ctime)) + figure(SlidesWin) + if OrthoViews == 0 + hold on + ax = subplot(c,r,iplot); + axis off, axis tight + % copyobj(Lights,ax); + else + set(previous,'Tag','') + set(SlidesWin,'Tag','tessellation_window'); + + end + dataplot_cb tesselation_select_done + axis equal, axis tight + if OrthoViews == 1 + dataplot_cb OrthoViews + set(SlidesWin,'Name',[num2str(((ctime-1)*srate + Visu.Data.Time(1))*1000,4), ' ms']) + else + set(ax,'view',VIEW) + end + + figure(SlidesWin) + h = title(num2str(((ctime-1)*srate + Visu.Data.Time(1))*1000,4)); + set(h,'fontweight','normal','color',frontcolor,'Fontname','helvetica','Fontsize',8,'FontUnits','Point') + %set(h,'Fontunits','normal') + + if OrthoViews == 1 & get(findobj(mapping_win,'Tag','SaveFigures'),'Value') % OrthoVoews are requested : Save each slide in a separate window + if ctime == samples(1) + ResFiles = get(hpprevious.ResultFiles,'String'); + ResFile = ResFiles{get(hpprevious.ResultFiles,'Value')}; + [path,file,ext] = fileparts(Users.CurrentData.StudyFile); + ResFile = fullfile(Users.STUDIES,path,ResFile); + [path,ResFile,ext] = fileparts(ResFile); + end + + time = num2str(((ctime-1)*srate + Visu.Data.Time(1))*1000,4); + + ImageFile = fullfile(path,[ResFile,'_OrthoViews_',time,'.jpg']); + saveas(SlidesWin,ImageFile,'t') + + end + + end + + end + end + map_sph.handle = map_sph; + map_sph.x = x; + map_sph.y = y; + map_sph.xs = xs; + map_sph.ys = ys; + set(map_slides_win,'Userdata',map_sph) + + if OrthoViews == 0 & get(findobj(mapping_win,'Tag','SaveFigures'),'Value') % Regular single slide window - no orthoview + ResFiles = get(hpprevious.ResultFiles,'String'); + ResFile = ResFiles{get(hpprevious.ResultFiles,'Value')}; + [path,file,ext] = fileparts(Users.CurrentData.StudyFile); + ResFile = fullfile(Users.STUDIES,path,ResFile); + [path,ResFile,ext] = fileparts(ResFile); + + time = [num2str(((samples(1)-1)*srate + Visu.Data.Time(1))*1000,4),'_', num2str(((samples(end)-1)*srate + Visu.Data.Time(1))*1000,4)]; + + ImageFile = fullfile(path,[ResFile,'_Slides_',time,'.jpg']); + saveas(SlidesWin,ImageFile,'jpg') + + end + + if get(findobj(mapping_win,'Tag','SaveFigures'),'Value') + ImageFile = fullfile(path,[ResFile,'_Data_Slides_',time,'.jpg']); + saveas(map_slides_win,ImageFile,'jpg') + end + + case 3 % Movie + sstart = str2num(get(handles.start,'String'))/1000; + if sstart < Visu.Data.Time(1) + sstart = Visu.Data.Time(1); + end + + sstep = str2num(get(handles.step,'String'))/1000; + + sstop = str2num(get(handles.stop,'String'))/1000; + if sstop > Visu.Data.Time(end) + sstop = Visu.Data.Time(end); + end + + samples = round(([sstart,sstop]-Visu.Data.Time(1))/srate)+1; + sstep = round(sstep/srate); + samples = samples(1):sstep:samples(2); + nsamples = length(samples); + if nsamples > 40 % Warning, too many slides may take a while to create. + button = questdlg({'You have requested ',int2str(nsamples),' slide(s)','Do you still want to proceed ?'},'','Yes','No','Yes'); + if strcmp(button,'No') + return + end + end + + map_movie_win = findobj(0,'Tag',['map_movie',int2str(current(kur)),'_',get(handles.start,'String'),'_',get(handles.step,'String'),'_',get(handles.stop,'String')]); + if isempty(map_movie_win) + map_single = open('map_single.fig'); % Create a new window + set(map_single,'Tag',['map_movie',int2str(current(kur)),'_',get(handles.start,'String'),'_',get(handles.step,'String'),'_',get(handles.stop,'String')],... + 'color','w') + map_movie_win = findobj(0,'Tag',['map_movie',int2str(current(kur)),'_',get(handles.start,'String'),'_',get(handles.step,'String'),'_',get(handles.stop,'String')]); + else + figure(map_movie_win) + end + + switch head_shape + case 1 % Sphere + iplot = 0; + + [moviefile, moviepath] = uiputfile('*.avi', 'Save Movie File as...'); + if moviefile == 0, return, end + cd(moviepath) + + mov = avifile(moviefile,'FPS',2,'QUALITY',50); + moviefiletopo = moviefile; + [path,file,ext] = fileparts(moviefiletopo); + moviefiletopo = [file,'_topo.avi']; + + movtopo = avifile(moviefiletopo,'FPS',2,'QUALITY',50); + + for ctime = samples + iplot = iplot+1; + + data = Visu.Data.F(goodchannels{kur},ctime)+eps ; + if (current_visu == 2) & (current(kur) == 1) %MEG with bad channels and visualization of the gradient amplitude + if ctime == samples(1) + channels = [1:length(Visu.ChannelID{current(kur)})]; + odd_channels = channels(find(rem(channels,2) == 1)); + if ~isempty(badchannels{kur}) + odd = badchannels{kur}(find(rem(badchannels{kur},2) == 1)); + even = setdiff(badchannels{kur},odd); + odd = unique([odd',intersect(odd_channels ,(even - 1))]); + else + odd = []; + even = []; + end + good_odd_channels = setdiff(odd_channels,odd); + [c,ia,ib] = intersect(goodchannels{kur},good_odd_channels-goodchannels{kur}(1)+1); + chanloc{kur} = chanloc{kur}(:,ia); + end + grad_meg = []; + j = 0; + for i = (good_odd_channels) + j = j+1; + grad_meg(j) = norm([data(i),data(i+1)]); + end + data = grad_meg; + elseif current_visu == 3 & current(kur) == 1 + data = Visu.MNEdata(:,ctime); + end + + + if ctime == samples(1) + [x,y,z,xs,ys,sensor,map_sph] = carto_sph(chanloc{kur}',data,[0 0 0]); + set(findobj(get(gca,'Children'),'Type','line'),'Visible','off') % Erase the sensor positions + axorig = get(map_sph,'Parent'); + set(gcf,'CurrentAxes',axorig) + %M = moviein(nsamples,map_movie_win); + %h = title(num2str(((ctime-1)*srate+Visu.Data.Time(1))*1000,4)); + h = text(0.05,0.05,0,num2str(((ctime-1)*srate + Visu.Data.Time(1))*1000,4),'units','normalized'); + set(h,'fontweight','normal','color',frontcolor,'Fontname','helvetica','Fontsize',8,'FontUnits','Point') + + set(h,'fontweight','normal','color',frontcolor,'Fontname','helvetica','Fontsize',8,'FontUnits','Point') + axis manual % Freeze Axes limits + set(gca,'nextplot','replacechildren'); + if RELATIVE == 1 + if current_visu == 3 + datacomplete = Visu.MNEdata; + else + datacomplete = Visu.Data.F(goodchannels{kur},:); + end + Max = max(abs(datacomplete(:))); + caxis([-Max,Max]) + else + caxis([-max(abs(data)),max(abs(data))]) + end + if findstr(lower(Visu.Data.Device),'ctf') + view(-90,90) % Noise pointing upwards + end + axis tight + + else + cdata_map = griddata(x,y,data,xs,ys,'invdist'); + set(h,'String',num2str(((ctime-1)*srate+Visu.Data.Time(1))*1000,4)); + set(map_sph,'CData',cdata_map) + end + % axis equal, axis vis3d + Ftopo = getframe(get(map_sph,'Parent')); + movtopo = addframe(movtopo,Ftopo); + + %[M(:,iplot)] = getframe(map_movie_win); + + % SurfaceViewer update + TessSelectWin = findobj(0,'Tag','tesselation_select'); + if ~isempty(TessSelectWin) % Check if Surface Viewer is open + handles = guihandles(TessSelectWin ); + ScalpMaps = get(handles.ScalpMaps,'Value'); + ColorSensors = get(handles.ColorSensors,'Value'); + CorticalMap = get(handles.CorticalMap,'Value'); + previous = findobj(0,'tag','tessellation_window'); + + if isempty(previous) + previous = open('tessellation_window.fig'); + end + figure(previous) + mapping_win = findobj(0,'Tag','mapping'); + if ~isempty(mapping_win) + slider_time = findobj(mapping_win,'Tag','slider_time'); + current_time= findobj(mapping_win,'Tag','current_time'); + end + + set(slider_time,'Value',1000*Visu.Data.Time(ctime)) + set(current_time,'String',1000*Visu.Data.Time(ctime)) + + dataplot_cb tesselation_select_done + + figure(previous) + if ctime == samples(1) + hxt = text(0.05,0.05,0,num2str(((ctime-1)*srate + Visu.Data.Time(1))*1000,4),'units','normalized'); + set(hxt,'fontweight','normal','color',frontcolor,'Fontname','helvetica','Fontsize',8,'FontUnits','Point') + + set(gca,'nextplot','replacechildren'); + + else +% set(hxt,'String',' '), drawnow + set(hxt,'String',num2str(((ctime-1)*srate + Visu.Data.Time(1))*1000,4)) + end + + drawnow + + F = getframe(gca); + mov = addframe(mov,F); + + end + + end + cmap = colormap; % Retrieves Current Colormap + + end + + mov = close(mov); + movtopo = close(movtopo); + + % Save Movie file +% [moviefile, moviepath] = uiputfile('*.mpg', 'Save Movie File as...'); +% if moviefile == 0, return, end +% cd(moviepath) +% %save(moviefile,'cmap','M') Matlab format +% disp('Saving Movie File...') +% mpgwrite(M,cmap,moviefile) + msgbox('Movie Save Completed') + disp('Saving Movie File... -> Done') + + map_sph.handle = map_sph; + map_sph.x = x; + map_sph.y = y; + map_sph.xs = xs; + map_sph.ys = ys; + set(map_movie_win,'Userdata',map_sph) + end + end + + set(findobj(0,'Tag','rotate'),'Userdata',get(map_sph.handle,'Parent')) % Axes handle for possible future rotattion using 'rotate' + + + %---------------------------------------------------------------------------- + +case 'mapping_rotate' % Make the last 3D view rotate + ax = get(gcbo,'Userdata'); + if isempty(ax), return, end + axes(ax) + switch get(gcbo,'Value') + case 1 + rotate3d on + otherwise + rotate3d off + end + + %---------------------------------------------------------------------------- + +case 'mapping_display_type' % Checkboxes Mutually incompatible + DATAPLOT = findobj(0,'Tag','mapping'); + MEG = findobj(DATAPLOT,'Tag','single'); + EEG = findobj(DATAPLOT,'Tag','slides'); + OTHER = findobj(DATAPLOT,'Tag','movie'); + mutincomp([MEG,EEG,OTHER]) + h = findobj([MEG,EEG,OTHER],'Value',1); + if length(h)>1 + set(h(2),'Value',0) % Just one modality at a time please ! + end + + %---------------------------------------------------------------------------- + +case 'quit' + close gcbf + + %---------------------------------------------------------------------------- +case 'see_sensors' % Visualize sensor locations using 3D spheres + + TessWin = findobj(0,'Tag','tesselation_select'); + hTessWin = guihandles(TessWin); + + % mutincomp([hTessWin.Sensors3D,hTessWin.SensorsMarkers]); + previous = openfig('tessellation_window.fig','reuse'); + + if get(hTessWin.Sensors3D,'Value') == 0 + if get(hTessWin.SensorsMarkers,'Value') == 1 + sph= findobj(previous,'Tag','SENSORS'); + delete(sph); set(previous,'Userdata',[]) % Redraw all sensors - to avoid mess with handles when resizing and coloring the sphere + dataplot_cb see_sensors_markers + return + else + sph= findobj(previous,'Tag','SENSORS'); + delete(sph); set(previous,'Userdata',[]) % Redraw all sensors - to avoid mess with handles when resizing and coloring the sphere + return + end + end + + Visu = get(DATAPLOT,'Userdata'); + previous = openfig('tessellation_window.fig','reuse'); + + + SIZE = sqrt(str2num(get(hTessWin.SensorSize,'String'))/10); % get sensor size + if isempty(SIZE), SIZE = 1; end + + sph= findobj(previous,'Tag','SENSORS'); + delete(sph); set(previous,'Userdata',[]) % Redraw all sensors - to avoid mess with handles when resizing and coloring the sphere + + % Channels Locations: + if ~isempty(find(Visu.Data.ChannelFlag == -1)) + goodchannels = setdiff(Visu.ChannelID{current},find(Visu.Data.ChannelFlag == -1));% - min(Visu.ChannelID{current})+1; % Discard bad channels + badchannels = intersect(Visu.ChannelID{current},find(Visu.Data.ChannelFlag == -1));% - min(Visu.ChannelID{current})+1; + else + goodchannels = Visu.ChannelID{current}; + badchannels = []; + end + j = 0; + chanloc = [Visu.Channel(goodchannels).Loc]; + chanloc = chanloc(:,1:2:end); + for chan = goodchannels %Visu.ChannelID{current} + j = j+1; + figure(previous) + hold on + if j == 1 + maxx = max(abs(get(gca,'Xlim')/50))*SIZE; + [X,Y,Z] = sphere; + end + sph(j) = surf(maxx*X+chanloc(1,j),maxx*Y+chanloc(2,j),maxx*Z+chanloc(3,j),'Visible','off'); + end + + set(sph,'Userdata',SIZE,'Visible','on','facelighting','Gouraud','edgelighting','Gouraud','Tag','SENSORS','facecolor',[.1 .1 .9],'Edgecolor','none') + axis equal, axis vis3d + varargout{1} = sph; + + %-------------------------------------------------------------------------------------------------------------------- +case 'see_sensors_markers' % Visualize sensor locations using regular markers + + TessWin = findobj(0,'Tag','tesselation_select'); + hTessWin = guihandles(TessWin); + + % mutincomp([hTessWin.Sensors3D,hTessWin.SensorsMarkers]); + previous = openfig('tessellation_window.fig','reuse'); + + if get(hTessWin.SensorsMarkers,'Value') == 0 + if get(hTessWin.Sensors3D,'Value') == 1 + sph= findobj(previous,'Tag','SENSORS'); + delete(sph); set(previous,'Userdata',[]) % Redraw all sensors - to avoid mess with handles when resizing and coloring the sphere + dataplot_cb see_sensors + return + else + sph= findobj(previous,'Tag','SENSORS'); + delete(sph); set(previous,'Userdata',[]) % Redraw all sensors - to avoid mess with handles when resizing and coloring the sphere + return + end + end + + Visu = get(DATAPLOT,'Userdata'); + + SIZE = sqrt(str2num(get(hTessWin.SensorSize,'String'))); % get sensor size + if isempty(SIZE), SIZE = 1; end + + sph= findobj(previous,'Tag','SENSORS'); + delete(sph); set(previous,'Userdata',[]) % Redraw all sensors - to avoid mess with handles when resizing and coloring the sphere + + % Channels Locations: + if ~isempty(find(Visu.Data.ChannelFlag == -1)) + goodchannels = setdiff(Visu.ChannelID{current},find(Visu.Data.ChannelFlag == -1));% - min(Visu.ChannelID{current})+1; % Discard bad channels + badchannels = intersect(Visu.ChannelID{current},find(Visu.Data.ChannelFlag == -1));% - min(Visu.ChannelID{current})+1; + else + goodchannels = Visu.ChannelID{current}; + badchannels = []; + end + j = 0; + chanloc = [Visu.Channel(goodchannels).Loc]; + chanloc = chanloc(:,1:2:end); + hold on + sph = scatter3(chanloc(1,:),chanloc(2,:),chanloc(3,:),'filled'); + + set(sph,'Userdata',SIZE,'Visible','on','Tag','SENSORS',... + 'Markerfacecolor',[.1 .1 .9],'Markeredgecolor','k', 'MarkerSize',SIZE); + axis equal, axis vis3d + varargout{1} = sph; + + %-------------------------------------------------------------------------------------------------------------------- + + +case 'DataOnSensors' % Sensors are color-coded according to the data at each location + + dataplot_cb mapping_create + Visu = get(DATAPLOT,'Userdata'); + previous = openfig('tessellation_window.fig','reuse'); + + + % Handles to the spherical representation of the sensors + sph = findobj(previous,'Tag','SENSORS'); + if isempty(sph) | isempty(get(previous,'userdata')) + + if ~isempty(sph) + if strcmp(get(sph(1),'type'),'surface') + sph = dataplot_cb('see_sensors'); + else + sph = dataplot_cb('see_sensors_markers'); + end + else + TessWin = findobj(0,'Tag','tesselation_select'); + hTessWin = guihandles(TessWin); + if get(hTessWin.Sensors3D,'Value') + sph = dataplot_cb('see_sensors'); + else + sph = dataplot_cb('see_sensors_markers'); + end + end + set(previous,'UserData',sph); + else + sph = get(previous,'Userdata'); % Need this and line above to keep sensor ordering correct - sph = findobj creates an arbitrary ordering otherwise + end + + % Bring the time slider - call the mapping window if available + MAPPING = openfig('mapping.fig','reuse'); + hMAPPING = guihandles(MAPPING); + + % Good/Bad Channels + if ~isempty(find(Visu.Data.ChannelFlag == -1)) + goodchannels = setdiff(Visu.ChannelID{current},find(Visu.Data.ChannelFlag == -1)); + badchannels = intersect(Visu.ChannelID{current},find(Visu.Data.ChannelFlag == -1)); + else + goodchannels = setdiff(Visu.ChannelID{current},find(Visu.Data.ChannelFlag == -1)); + badchannels = []; + end + + % Sampling rate + srate = abs(Visu.Data.Time(1)-Visu.Data.Time(2) ); % Assuming time is in sec. + ctime = round((get(hMAPPING.slider_time,'Value')/1000-Visu.Data.Time(1))/srate)+1; + axx = get(sph(1),'Parent'); + + data = Visu.Data.F(goodchannels,:); clear Visu + %set(axx,'Clim',[-max(abs(data(:))) max(abs(data(:)))]); + caxis(axx,[-max(abs(data(:))) max(abs(data(:)))]); + data = data(:,ctime); + + C = [flipud(fliplr(hot(128)));hot(128)]; + colormap(axx,C); + + % Manipulation to get the color properly for each sensor sphere + + set(sph,'visible','off') + TessWin = findobj(0,'Tag','tesselation_select'); + hTessWin = guihandles(TessWin); + + if get(hTessWin.Sensors3D,'Value') + zdata = get(sph(1),'Zdata'); + for k=1:length(sph) + cdata = data(k)*ones(size(get(sph(k),'zdata'))); + set(sph(k),'CData',cdata,'facecolor','flat','CDataMapping','scaled'); + end + else + for k=1:length(sph) + set(sph(k),'Markerfacecolor',C( max([round(size(C,1)*(data(k)-min(caxis(axx)))/(max(caxis(axx))-min(caxis(axx)))),1]),:)); + end + + end + + set(sph,'visible','on') + + %-------------------------------------------------------------------------------------------------------------------- +case 'DataOnScalp' % Scalp topography + + dataplot_cb mapping_create + + TessSelect = findobj(0,'Tag','tesselation_select'); + hSelect = guihandles(TessSelect); + ActiveTess = get(hSelect.removed,'String'); % Find the active scalp surface + % Check if the CORTEX keyword is present + for k = 1:length(ActiveTess) + flag(k) = ~isempty(findstr(lower(ActiveTess{k}),'head')); %KND:scalp->head + end + iScalp = find(flag); + iScalp = iScalp(1); + if isempty(iScalp) + h = msgbox('Please select a scalp envelope from the tessellated surfaces available.'); + return + end + TessWin = openfig('tessellation_window.fig','reuse'); + + Visu = get(DATAPLOT,'Userdata'); + if ~isempty(find(Visu.Data.ChannelFlag == -1)) + goodchannels = setdiff(Visu.ChannelID{current},find(Visu.Data.ChannelFlag == -1));% - min(Visu.ChannelID{current})+1; % Discard bad channels + badchannels = intersect(Visu.ChannelID{current},find(Visu.Data.ChannelFlag == -1));% - min(Visu.ChannelID{current})+1; + else + goodchannels = Visu.ChannelID{current}; + badchannels = []; + end + + Scalp = findobj(TessWin,'Type','patch','Tag',ActiveTess{iScalp}); % Need to find a better way to identify scalp when multiple surfaces are present in tessellation_win + if isempty(Scalp), tesselation_select_done, return, end + + if isempty(get(Scalp,'Userdata')) + figure(TessWin) + C = [flipud(fliplr(grayish(hot(128),.3)));grayish(hot(128),.3)]; + colormap(C) + + scalp.vertices = get(Scalp,'vertices'); + % Channels Locations + j = 0; + for chan = goodchannels + j = j+1; + %Projection of sensors on surface + celec = ones(size(scalp.vertices,1),1)*Visu.Channel(chan).Loc(:,1)'; + dist = norlig(scalp.vertices-celec); + [minn(j) imin(j)] = min(dist); + end + scalp.chanloc = [scalp.vertices(imin,1),scalp.vertices(imin,2),scalp.vertices(imin,3)]; + + % Sampling rate + scalp.srate = abs((Visu.Data.Time(2)-Visu.Data.Time(1))); + data = Visu.Data.F(goodchannels,:); + axx= get(Scalp,'Parent'); + set(axx,'Clim',[-max(abs(data(:))) max(abs(data(:)))]); + set(Scalp,'Userdata',scalp) + else + scalp = get(Scalp,'Userdata'); + end + + + % Get the data from the mapping window + MAPPING = openfig('mapping.fig','reuse'); + hMAPPING = guihandles(MAPPING); + ctime = round((get(hMAPPING.slider_time,'Value')/1000-Visu.Data.Time(1))/scalp.srate)+1; + vertxcolor = interp_mail(scalp.vertices,scalp.chanloc,Visu.Data.F(goodchannels,ctime)); + set(Scalp,'FaceVertexCData',vertxcolor,'facecolor','interp'); + + %-------------------------------------------------------------------------------------------------------------------- + +case 'CorticalMap' % See cortical current maps interpolated on the proper cortical surface + + TessSelect = findobj(0,'Tag','tesselation_select'); + hSelect = guihandles(TessSelect); + ActiveTess = get(hSelect.removed,'String'); % Find the active scalp surface + iCortex = get(hSelect.removed,'Value'); % Find the active scalp surface + + if isempty(iCortex) + h = msgbox('Please select a cortex surface from the tessellations available.'); + return + end + TessWin = findobj(0,'Tag','tessellation_window'); + SlidesWin = findobj(0,'Tag','subplot_tess_window'); + if ~isempty(SlidesWin) + TessWin = SlidesWin; % Subplot or movie window + end + + if isempty(TessWin) + TessWin = openfig('tessellation_window.fig'); + dataplot_cb('loaddata',Users.CurrentData.StudyFile,Users.CurrentData.DataFile); % Refresh by reloading data + end + + % Check if loaded Results match this surface (ie number of sources = number of vertices of selected cortical surface) + Results = get(hSelect.ResultFiles,'Userdata'); + nSources = size(Results.ImageGridAmp,1); + + if nargin == 1 & get(hSelect.OrthoViews,'Value') == 0 + if iscell(ActiveTess) + Cortex = findobj(get(TessWin,'CurrentAxes'),'Type','patch','Tag',ActiveTess{iCortex}); %CHEAT - need to be improved ? + else + Cortex = findobj(get(TessWin,'CurrentAxes'),'Type','patch','Tag',ActiveTess); %CHEAT - need to be improved ? + end + elseif nargin == 1 & get(hSelect.OrthoViews,'Value') == 1 % Orthogonal views are requested + Cortex = findobj(get(TessWin,'CurrentAxes'),'Type','patch','Tag',ActiveTess,'Visible','on'); %CHEAT - need to be improved ? + Cortex = Cortex(1); + else + Cortex = varargin{1}(1); + end + + %set(Cortex,'Visible','off') + if ~isfield(get(Cortex,'Userdata'),'srate') + Visu = get(DATAPLOT,'Userdata'); + figure(TessWin) + + cortex = get(Cortex,'Userdata'); + + cortex.vertices = get(Cortex,'vertices'); + % Sampling rate + cortex.srate = abs(Visu.Data.Time(2)-Visu.Data.Time(1)); + data = Results.ImageGridAmp; + axx= get(Cortex,'Parent'); + set(axx,'Clim',[-max(abs(data(:))) max(abs(data(:)))]); + set(Cortex,'Userdata',cortex) + + % Result time window has priority over the original time window for the data - replace + Visu.Data.Time = Results.ImageGridTime; % Need to do better than that - CHEAT + try + Visu.Data.F = Visu.Data.F(:,Results.Time(1):Results.Time(end)); % Should work only the first time the result time series are called - otherwise do nothing + catch + Visu.Data.F = Visu.Data.F; % Do nothing + end + + set(DATAPLOT,'Userdata',Visu); + + M = max(abs(Results.ImageGridAmp(:))); + set(hSelect.Colorbar,'Userdata',M); + + else + cortex = get(Cortex,'Userdata'); + end + + dataplot_cb mapping_create + + % Get the data from the mapping window + MAPPING = findobj(0,'Tag','mapping'); + hMAPPING = guihandles(MAPPING); + + if iscell(cortex) + ctime = round((get(hMAPPING.slider_time,'Value')/1000- Results.ImageGridTime(1))/cortex{1}.srate)+1; + else + ctime = round((get(hMAPPING.slider_time,'Value')/1000- Results.ImageGridTime(1))/cortex.srate)+1; + end + if ctime < 0, ctime =1; end + + if get(hSelect.AbsoluteCurrent,'Value') + set(Cortex,'FaceVertexCData',abs(Results.ImageGridAmp(:,ctime))); + else + set(Cortex,'FaceVertexCData',Results.ImageGridAmp(:,ctime)); + end + + figure(TessWin) + hTessWin = guihandles(TessSelect); + if get(hTessWin.Normalize,'Value') + TessWin = findobj(0,'Tag','tessellation_window'); + axx = findobj(TessWin,'tag','MainAxes'); + set(axx,'ClimMode','auto') + M = max(abs(Results.ImageGridAmp(:,ctime))); + set(hSelect.Normalize,'Userdata',M) + + SlidesWin = findobj(0,'Tag','subplot_tess_window'); + if ~isempty(SlidesWin) + TessWin = SlidesWin; % Subplot or movie window + axx = findobj(TessWin,'type','axes'); + set(axx,'ClimMode','auto') + end + + else + if get(hTessWin.FreezeColormap,'Value') + data = get(Cortex,'FaceVertexCData'); + FreezeTime = get(hTessWin.FreezeColormap,'Userdata'); + M = max(abs(Results.ImageGridAmp(:,FreezeTime))); + else + M = max(abs(Results.ImageGridAmp(:))); + end + + if get(hSelect.AbsoluteCurrent,'Value') + set(findobj(TessWin,'type','axes'),'ClimMode','manual','Clim',[0 M]) + else + set(findobj(TessWin,'type','axes'),'ClimMode','manual','Clim',[-M M]) + end + end + + if isempty(get(hSelect.ColorMAP,'Userdata')) + %if ~isfield(cortex,'CDepth') & ~get(hSelect.MapCurvature,'Value')% No curvature mapping + if ~get(hSelect.MapCurvature,'Value')% No curvature mapping + if ~get(hSelect.AbsoluteCurrent,'Value') + C = [flipud(fliplr(grayish(hot(128),.3)));grayish(hot(128),.3)]; + else + %C = [(grayish(hot(128),.3))]; + load bst_cactivCmap + C = map; + end + else + [FVC,C]=catci(get(Cortex,'FaceVertexCData'),cortex.CDepth,M); + set(Cortex,'FaceVertexCData',FVC); + end + + else % Truncated Colormap + if ~isfield(cortex,'CDepth') % No curvature mapping + C = get(hSelect.ColorMAP,'Userdata'); % Truncated Colormap + if get(hSelect.ZScoreThresholdApply,'Value') & get(hSelect.ZScore,'Value') + cThres = str2num(get(hSelect.ZScoreThreshold,'String')); + else + cThres = str2num(get(hTessWin.TruncateFactor,'String')); + end + FVC = get(Cortex,'FaceVertexCData'); + if get(hSelect.OrthoViews,'Value') == 0 + set(Cortex,'FaceVertexCData',FVC); + Cortex = findobj(TessWin,'Type','patch','Tag',ActiveTess,'Visible','on'); %CHEAT - need to be improved ? + FVC(FVC 0 + ColorMap(1:round(ScaleFactor*end/100),:) = ... + repmat(min([.3,sum(ColorMap(round(ScaleFactor*end/100),:))])*[1 1 1],length(1:round(ScaleFactor*size(ColorMapOld,1)/100)),1); + else + ColorMap = ColorMapOld; + end + + set(TessWin,'Colormap',ColorMap); + set(hTessSelect.ColorMAP,'Userdata',ColorMap) + + dataplot_cb CorticalMap + %-------------------------------------------------------------------------------------------------------------------- + + +case 'Colorbar' % Add colorbar to the Surface Viewer window + handles = guihandles(gcbf); + ViewColorbar = get(handles.Colorbar,'Value'); % Add or remove colorbar + TessWin = findobj(0,'Tag','tessellation_window'); + + if isempty(TessWin), return, end + if isempty(get(handles.Colorbar,'Userdata')) | (ViewColorbar == 1) + figure(TessWin); + Colorbar = colorbar('horiz'); + set(handles.Colorbar,'Userdata',Colorbar); + set(Colorbar,'Xcolor',textcolor,'Ycolor',textcolor) + elseif ~isempty(get(handles.Colorbar,'Userdata')) + delete(get(handles.Colorbar,'Userdata')); + set(handles.Colorbar,'Userdata',[]) + end + figure(TessWin) + rotate3d on + hTessSelect = guihandles(findobj(0,'Tag','tesselation_select')); + if get(hTessSelect.Normalize,'Value') & ViewColorbar == 1% Normalized colorbar + M=get(hTessSelect.Normalize,'Userdata'); + set(get(handles.Colorbar,'Userdata'),'XTickLabel',num2str(linspace(0,M,6)',1)) + end + %-------------------------------------------------------------------------------------------------------------------- +case 'Colorbar_mapping' % Add colorbar to the Surface Viewer window + handles = guihandles(gcbf); + ViewColorbar = get(handles.Colorbar,'Value'); % Add or remove colorbar + for k = 1:length(current) + TessWin = findobj(0,'Tag',['map_single',int2str(current(k))]); + + if isempty(TessWin), return, end + if (ViewColorbar == 1) + figure(TessWin); + axes(get(TessWin,'CurrentAxes')); + Colorbar{k} = colorbar('horiz'); + set(handles.Colorbar,'Userdata',Colorbar); + set(Colorbar{k},'Xcolor',textcolor,'Ycolor',textcolor) + elseif ~isempty(get(handles.Colorbar,'Userdata')) + Colorbar = get(handles.Colorbar,'Userdata'); + if isempty(Colorbar) + return + end + delete([Colorbar{:}]); + set(handles.Colorbar,'Userdata',[]) + end + end + + %-------------------------------------------------------------------------------------------------------------------- +case 'QuitTessSelect' % Bye + delete(findobj(0,'tag','tesselation_select')); + delete(findobj(0,'tag','tessellation_window')); + + +end % Switch action + diff --git a/epilepsie/mergedata.m b/epilepsie/mergedata.m new file mode 100644 index 0000000..7e925a3 --- /dev/null +++ b/epilepsie/mergedata.m @@ -0,0 +1,41 @@ +function mergedata() + +for istud=1:3 + studyname=sprintf('debcrise%d-long',istud) + local=sprintf('/tmp/epil/debcrise%d-long.ds/',istud) + switch(istud) + case 1 + distal='/epilepsie/data/EPILEPSIE/03Mar18/PART1/debcrise1-long.ds/' + Time=84+(cumsum(ones(1,12500))-1)*8e-4; + case 2 + distal='/epilepsie/data/EPILEPSIE/03Mar18/PART2/debcrise2-long.ds/' + Time=33+(cumsum(ones(1,12500))-1)*8e-4; + case 3 + distal='/epilepsie/data/EPILEPSIE/03Mar18/PART2/debcrise3-long.ds/' + Time=106+(cumsum(ones(1,12500))-1)*8e-4; + end + F=[]; + ChannelFlag=[]; + + if 0 + for i=1:10 + clear d + f=fullfile(distal, sprintf('%s_data_trial_%d.mat',studyname, i)); + disp(sprintf('Loading... %s',f)); + d=load(f); + F=[F d.F(:,1:end-1)]; + ChannelFlag=d.ChannelFlag; + Time=[Time, d.Time(1:end-1)]; + disp(sprintf('Saving... %s',studyname)); + save(sprintf('%s%s_data_trial_0.mat', local,studyname),'F', 'ChannelFlag', 'Time' ) + end + F=[F d.F(:,end)]; + % Time=[Time, d.Time(end)]; + save(sprintf('%s%s_data_trial_0.mat', local,studyname),'F', 'ChannelFlag', 'Time' ) + copyfile(sprintf('%ssensor_result.mat', distal),sprintf('%s', local)) + copyfile(sprintf('%s%s_brainstormstudy.mat', distal,studyname),sprintf('%s', local)) + copyfile(sprintf('%s%s_channel.mat', distal,studyname),sprintf('%s', local)) + end + save(sprintf('%s%s_data_trial_0.mat', local,studyname),'-APPEND', 'Time' ) + end +return diff --git a/epilepsie/mergeresults.m b/epilepsie/mergeresults.m new file mode 100644 index 0000000..dc0567d --- /dev/null +++ b/epilepsie/mergeresults.m @@ -0,0 +1,47 @@ +function mergeresults() +% Fusionne les donnees epilepsie du sujet bern en 1 seul gros fichier + for istud=1:3 + ImageGridAmp=single(zeros(12501,10569)); + studyname=sprintf('debcrise%d-long',istud) + local=sprintf('/tmp/epil/debcrise%d-long.ds/',istud) + switch(istud) + case 1 + distal='/epilepsie/data/EPILEPSIE/03Mar18/PART1/debcrise1-long.ds/' + ImageGridTime=84+(cumsum(ones(1,12500))-1)*8e-4; + case 2 + distal='/epilepsie/data/EPILEPSIE/03Mar18/PART2/debcrise2-long.ds/' + ImageGridTime=33+(cumsum(ones(1,12500))-1)*8e-4; + case 3 + distal='/epilepsie/data/EPILEPSIE/03Mar18/PART2/debcrise3-long.ds/' + ImageGridTime=106+(cumsum(ones(1,12500))-1)*8e-4; + end + %f=dir(sprintf('*_data_trial_1.mat')); + %studyname=f(1).name(1:findstr('_data_trial_',f(1).name)-1); + %data=load(f(1).name, 'Time'); + if 1 + for i=1:10 + clear d + pack; + T=((i-1)*1250+1):i*1250; + f=dir(sprintf('%s*_data_trial_%d_MNE_*.mat',distal,i)); + disp(sprintf('Loading... %s',f(1).name)); + d=load([distal f(1).name], 'ImageGridAmp'); + ImageGridAmp(:,T)=single(d.ImageGridAmp(:,1:end-1)); + disp(sprintf('Saving... %s',studyname)); + % save(sprintf('%s%s_data_trial_0_MNE_MEGresults.mat', local,studyname),'ImageGridAmp') + end + ImageGridAmp(:,12501)=single(d.ImageGridAmp(:,end)); + %ImageGridTime=getfield(load(sprintf('%s%s_data_trial_0.mat',local, studyname), 'Time'),'Time'); + save(sprintf('%s%s_data_trial_0_MNE_MEGresults.mat', local,studyname),'ImageGridAmp','ImageGridTime') + + end + + SourceLoc=[]; + Comment=studyname; + save(sprintf('%s%s_data_trial_0_MNE_MEGresults.mat', local, ... + studyname),'-APPEND','SourceLoc', 'Comment', 'ImageGridTime') + + end + + return + \ No newline at end of file diff --git a/epilepsie/scriptStudySubject.m b/epilepsie/scriptStudySubject.m new file mode 100644 index 0000000..b81be0a --- /dev/null +++ b/epilepsie/scriptStudySubject.m @@ -0,0 +1,50 @@ + + +load /tmp/epil/test/debcrise1-long_data_trial_1_MNE_MEGresults_1747.mat + +StudySubject.Subject = 'bern_brainstormsubject.mat' +StudySubject.SubjectImage = 'bern_subjectimage.mat' +StudySubject.SubjectTess = 'tri/bern_Lwhite-CTF_tess.mat' +StudySubject.SubjectTriConn = [] +StudySubject.SubjectVertConn = 'bern_Lwhite-CTF_tess_vertconn.mat' +StudySubject.Results = [] + + +for STUDY=1:3 + + +if STUDY==1 +StudySubject.Study = 'deb1/debcrise1-long_brainstormstudy.mat' +StudySubject.Channel = 'deb1/debcrise1-long_channel.mat' +StudySubject.HeadModel = '' +StudySubject.Data = {'deb1/debcrise1-long_data_trial_0.mat'} +GUI.DataName='deb1/debcrise1-long_data_trial_0.mat' + +r='deb1/debcrise1-long_data_trial_0_MNE_MEGresults.mat' +end + +if STUDY==2 +StudySubject.Study = 'deb2/debcrise2-long_brainstormstudy.mat' +StudySubject.Channel = 'deb2/debcrise2-long_channel.mat' +StudySubject.HeadModel = '' +StudySubject.Data = {'deb2/debcrise2-long_data_trial_0.mat'} +GUI.DataName='deb2/debcrise2-long_data_trial_0.mat' + +r='deb2/debcrise2-long_data_trial_0_MNE_MEGresults.mat' +end + +if STUDY==3 +StudySubject.Study = 'deb3/debcrise3-long_brainstormstudy.mat' +StudySubject.Channel = 'deb3/debcrise3-long_channel.mat' +StudySubject.HeadModel = '' +StudySubject.Data = {'deb3/debcrise3-long_data_trial_0.mat'} +GUI.DataName='deb3/debcrise3-long_data_trial_0.mat' + +r='deb3/debcrise3-long_data_trial_0_MNE_MEGresults.mat' +end + +disp(sprintf('Saving... %s', r)) + + % save(r, '-append', 'StudySubject') + save(r, '-append', 'Comment', 'GUI') +end \ No newline at end of file diff --git a/epilepsie/splitdata.m b/epilepsie/splitdata.m new file mode 100644 index 0000000..e9a44c3 --- /dev/null +++ b/epilepsie/splitdata.m @@ -0,0 +1,20 @@ +function []=splitdata() +load('debcrise1-f_data_trial_0.mat') +G=F; +T=Time; + +for i=1:10 + if i<10 + t=1250*(i-1)+1:1250*(i); + else + t=1250*(i-1)+1:1250*(i)+1; + end + + Time=T(t)'; + F=G(:,t); + size(Time) + size(F) + save(sprintf('debcrise1-f_data_trial_%d.mat', i), 'ChannelFlag', 'Comment', 'Device', 'F', 'NoiseCov', 'Projector','SourceCov', 'System', 'Time') +end + + diff --git a/epilepsie/view_cortex.m b/epilepsie/view_cortex.m new file mode 100644 index 0000000..3b72b40 --- /dev/null +++ b/epilepsie/view_cortex.m @@ -0,0 +1,54 @@ +function [h,g]=view(faces,vertices, data) +% function [h,g]=view(faces,vertices, data) + +if nargin==1 + data=faces; + global faces + global vertices +end + + +if(size(vertices,2) > 3), % assume transposed + vertices = vertices'; % if the assumption is wrong, will crash below anyway +end + +% if(~exist('cdata','var')), +% cdata = rownorm(vertices); +% end + +% h = windfind(figname); +% figure(h) +% windclf +% h=figure +cla +h = patch('faces',faces,'vertices',vertices,... + 'facevertexcdata',data(:),'facecolor','interp','edgecolor','none'); + + +hl(1) = camlight(-20,30); +hl(2) = camlight(20,30); +hl(3) = camlight(-20,-30); +for i = 1:length(hl), + set(hl(i),'color',[.8 1 1]/length(hl)/1.2); % mute the intensity of the lights +end +g=light; +set(g, 'position', [0 -1 0]) +g=light; +set(g, 'position', [0 1 0]) + +% h=view_surface('',faces, vertices, data); + + +view(2) +axis image +axis off +rotate3d +view([0 0]) + + +lighting phong +material dull +colormap(hot(256)) +setmycolormap(50) + +set(h, 'Userdata', {'Cortex'}) diff --git a/epilepsie/view_minnorm_cb.m b/epilepsie/view_minnorm_cb.m new file mode 100644 index 0000000..47b1012 --- /dev/null +++ b/epilepsie/view_minnorm_cb.m @@ -0,0 +1,451 @@ +function view_minnorm_cb(action); +% VIEW_MINNORM_CB Callback for viewing minimum norm solutions +% function view_minnorm_cb(action); +% Call with no arguments to build gui. + +% John C. Mosher, Ph.D., See Copyright.m file for information. +% $Date: 4/19/01 6:15a $ $Revision: 4 $ +% Additions: +% +% +% 04-03-01 +% Sylvain Baillet - CNRS-UPR640 +% Added orthogonal views button and callbacks +% + +if(~exist('action','var')), + action = 'build'; +end + +hf = gcbf; % who called me + +switch deblank(lower(action)) +case 'build' + open('view_minnorm_cb.fig'); + +case 'reset' + % Reset all of the buttons to initial values, refresh the studies list + % Objects: AxisStudy, AxisSignal, AxisNoise, AxisSVD + % EditMinTime, EditMaxTime, EditRank, EditCorr, EditReg + % SliderRank, SliderCorr, + % PopupSVD, PopupReg, PopupOrder, ExecuteRAP + + % Set the menu action + + hur = findobj(hf,'Tag','UimenuReset'); % handle of uimenu for reseting + hud = findobj(hf,'Tag','UimenuDataSets'); + hub = findobj(hf,'Tag','UimenuBSTStudy'); % the studies + delete(hur); + delete(hud); + delete(hub); + + find_brainstorm_files('studies',[],hf); % restablish the menu + hub = findobj(hf,'Tag','UimenuBSTStudy'); % the studies created by that call + % now create a menu to find the datasets for those studies + hud = uimenu('Label','DataSets','Tag','UimenuDataSets'); + % and the call to reset this gui + hur = uimenu('Label','Reset GUI','Tag','UimenuReset',... + 'Callback','view_minnorm_cb(''reset'')'); + + % So the studies menu has a list of StudySubjects to write to UserData + % now append to each study menu callback a datasets menu action + hub1 = get(hub,'children'); % what are all of the studies + % hub1r = findobj(hub1,'Label','Refresh Studies'); + % hub1(find(hub1==hub1r)) = []; % don't update the refresher child + + for i = 1:length(hub1), % each submenu of the study menu + cb = get(hub1(i),'Callback'); + % datamenu will make a menu of data sets for min norm + set(hub1(i),'Callback',[cb 'viewminnormdatamenu;']); + end + + %So the menus are rebuilt. Now for the other objects. Activate all of them + TAGS = {'AxisStudy', 'AxisSeries', 'AxisView'}; + + for i = 1:length(TAGS), + + axes(findobj(hf,'Tag',TAGS{i})); + cla reset + set(gca,'Tag',TAGS{i},'Units','Normalized'); % set tag back in + box on + + if i==3 + set(gca,'color',[.35 .35 .35]) + set(gca,'Xtick',[],'Ytick',[]) + end + + set(gca,'xcolor',[.7 .7 0],'ycolor',[.7 .7 0]); + axis on + grid off + + end + + hm = findobj(gcf,'Label','Toggle Face'); + delete(hm) + hm = findobj(gcf,'Label','Toggle Lighting'); + delete(hm) + + uimenu(gcf,'Label','Toggle Face','callback','toggleface(get(gcbo,''UserData''))','UserData',[]); + uimenu(gcf,'Label','Toggle Lighting','callback','togglelight(get(gcbo,''UserData''))','UserData',[]); + + + % end of reset + +case 'view data' + % user has clicked on some data in the data menu. + ht = findobj(0,'Tag','TASKBAR'); + UD = get(ht,'UserData'); % get the userdata + % interested in StudySubject information and DataName string + Results = load_brainstorm_file(UD.DataName); + + if(~isempty(findstr('_results',deblank(lower(Results.GUI.DataName))))), + % CHEAT + % The DataName has the string '_results' in it + % We are trying to view independent topographies as min norm data. Eventually this + % capability will be moved to a new routine. + OrgData= load(Results.GUI.DataName); % the original data + % now synthesize a Data structure from this + [ignorm,Anrm] = colnorm(OrgData.IndepTopo); + Data = struct('F',Anrm,'Channel',OrgData.Channel,... + 'Time',[1:size(OrgData.IndepTopo,2)],... % time is now simply an indexer + 'NoiseCov',OrgData.NoiseCov,'SourceCov',OrgData.SourceCov,'Projector',OrgData.Projector,... + 'Comment',sprintf('Synthesized Data Set from Results: %s',OrgData.Comment)); + GoodChannel = [1:size(Anrm,2)]; % all channels good in the IndepTopo + else + % CHEAT + % the GUI.DataName does not have the word "results" in it, so we are viewing legitimate + % minimum norm results. In the future, we might key in on the letters "mn" , for instance + % assume it is a data file + User = get_user_directory; + cd(User.STUDIES) + Data = load(Results.GUI.DataName,'F','Channel','Time','Projector','Comment'); + Channel = load(Results.StudySubject.Channel); + Channel = Channel.Channel; + GoodChannel = good_channel(Channel,Results.ChannelFlag,'MEG'); + + % CHEAT SILVIN + %GoodChannel = good_channel(Channel,Results.ChannelFlag,'EEG'); + %% CHEAT, show data and residual in a separate window for now + figure(windfind(sprintf('MinNorm Data and Residuals: %s',Results.GUI.DataName))) + windclf + plot([Data.F(GoodChannel,Results.Time) Results.Fsynth Data.F(GoodChannel,Results.Time)-Results.Fsynth]'); + title(sprintf('%s: Data, Synth, Resid',Results.GUI.DataName),'interpreter','none') + drawnow + + end + + ha = findobj(hf,'Tag','AxisStudy'); + axes(ha) + cla + line(1000*Data.Time(Results.Time),Data.F(GoodChannel,Results.Time)') + title(Data.Comment) + axis tight + grid on + + % now we can only handle a couple hundred lines + MAXLINE = 500; + MAXLINE = min(MAXLINE,size(Results.ImageGridAmp)); % in case there are fewer sources + AmpNrm = rownorm(Results.ImageGridAmp); + [ignore,iAmpNrm] = sort(AmpNrm); + iAmpNrm = iAmpNrm(end:-1:1); % biggest to smallest + + ha = findobj(hf,'Tag','AxisSeries'); + axes(ha) + cla + line(1000*Data.Time(Results.Time),Results.ImageGridAmp(iAmpNrm(1:MAXLINE),:)') + axis tight + grid on + + [ignore,GUI.iTime] = max(max(abs(Results.ImageGridAmp(iAmpNrm(1:MAXLINE),:)))); + % gives us the max time index + + V = axis; % what's the settings + UD = struct('iTime',GUI.iTime,'Time',Data.Time(Results.Time)); + % iTime is the integer time index in the array of data. Time is the time in engineering units + hline = line(1000*[UD.Time(UD.iTime);UD.Time(UD.iTime)],[V(3);V(4)],'Color','b','linewidth',2); + set(ha,'UserData',setfield(UD,'hline',hline)); + + drawnow + + view_minnorm_gui(Results,GUI) + + hpatch = findobj(gca,'Type','patch'); % find the patch + set(hpatch,'FaceLighting','flat'); % much faster rendering + hm = findobj(gcf,'Label','Toggle Face'); + set(hm,'UserData',hpatch); + hm = findobj(gcf,'Label','Toggle Lighting'); + set(hm,'UserData',hpatch); + + + hud = findobj(hf,'Tag','UimenuDataSets'); + set(hud,'UserData',Results); % set results into ram + + rotate3d on + axis vis3d % freeze the size for three d rotation + +case 'set time' + ha = findobj(hf,'Tag','AxisSeries'); + UD = get(ha,'UserData'); + % UD.Time tells us where we are in true unit + set(UD.hline,'Xdata',[1000 1000]*UD.Time(floor(UD.time_scale *(UD.iTime-1)+1))); % move it + axes(ha); + xlabel(sprintf('Time: %7.1f',1000*UD.Time(floor(UD.time_scale *(UD.iTime-1)+1)))); + +case 'scale_colormap' + % let the user adjust the color scaling for greater saturation + hv = findobj(hf,'Tag','AxisView'); + axes(hv); % set to the current figure + org_caxis = caxis; % what is the color axis + ScaleFactor = inputdlg('Enter Caxis Scaling Factor','BrainStorm Movie Maker',[1 50],{'1'}); + ScaleFactor = str2num(ScaleFactor{:}); + %caxis(str2num(ScaleFactor{:})*org_caxis); + cmap_orig = grayish(hot(128),.33); + %cmap_orig(1:round(size(cmap_orig,1)/5),:) = brighten(round(size(cmap_orig,1)/5)); + + ind_trim = min([128,floor(128*( 1 - exp(-(ScaleFactor-1)/6 )))+1]); + cmap = (cmap_orig); + cmap(1:ind_trim,:) = (repmat(cmap(1,:),length(1:ind_trim),1)); + cmap(ind_trim+1:end,:) = (grayish(hot(size(cmap(ind_trim+1:end,:),1)),.33)); + + hud = findobj(hf,'Tag','UimenuDataSets'); + Results = get(hud,'UserData'); % the results data + M = max(abs(Results.ImageGridAmp(:))); + colormap((cmap)) + caxis([0,M]) + +case 'create movie' + hud = findobj(hf,'Tag','UimenuDataSets'); + hv = findobj(hf,'Tag','AxisView'); + hp = findobj(hv,'Type','Patch'); % get the patch + Results = get(hud,'UserData'); % the results data + if(isempty(Results)), + msgbox('Load data first','Notice','modal'); + return + end + + NF = getframe(hf); % the whole figure + + mpg_limits = size(NF.cdata); %the size of the figure + if ((mpg_limits(1) > 480) | (mpg_limits(2) > 640)), + ButtonName = questdlg(sprintf('Resize your window to 480 x 640?')); + switch ButtonName + case 'Yes' + set(hf,'units','pixels') + Vposition = get(hf,'Position'); + set(hf,'Position',[Vposition(1:2) 640 480]); % don't know why this translates to 480 x 640 + %NF = getframe(hf); % the whole figure again + case 'No' + % do nothing + case 'Cancel' + % user punted, so will we + return + end + end + + + % let the user adjust the color scaling for greater saturation + axes(hv); % set to the current figure + org_caxis = caxis; % what is the color axis + if 0 + ScaleFactor = inputdlg('Enter Caxis Scaling Factor','BrainStorm Movie Maker',[1 50],{'1'}); + caxis(str2num(ScaleFactor{:})*org_caxis); + end + + + % Check if we have enough memory + MovieSiz = length(Results.Time)*prod(size(NF.cdata)); + if MovieSiz > 128e6 + disp(['The memory requirement might be too large for your system : ', round(num2str(MovieSiz/1e6,'%4.0f')),' MBytes']) + ans = input('Would you like to decimate the time series ? (Y/N) ','s'); + switch(lower(ans)) + case 'y' + disp(['Current number of time samples: ', int2str(length(Results.Time))]) + num = input('How many time samples would you like to achieve ? : '); + oldtime = Results.Time; + Results.Time = linspace(Results.Time(1),Results.Time(end),num); + time_scale = abs(Results.Time(2)-Results.Time(1))/abs(oldtime(2)-oldtime(1)); + otherwise + % nothing + time_scale = 1; + end + else + time_scale = 1; + end + + ha = findobj(hf,'Tag','AxisSeries'); + UD = get(ha,'UserData'); % get the userdata + UD.time_scale = time_scale; + set(ha,'UserData',UD); + + %[NF(1:length(Results.Time))] = (deal(NF)); % allocate memory + + ha = findobj(hf,'Tag','AxisSeries'); + disp(sprintf('Making movie of %.0f slices',length(Results.Time))); + Results.ImageGridAmp = abs(Results.ImageGridAmp); + for i = 1:length(Results.Time), + set(ha,'UserData',setfield(get(ha,'UserData'),'iTime',i)); + view_minnorm_cb('set time') % set the time bar + set(hp,'FaceVertexCdata',(Results.ImageGridAmp(:,floor((i-1)*time_scale+1)))); + drawnow + disp(' ') + % NF(i) = getframe(hf); + end + caxis(org_caxis); % reset the color axis back to original + + + set(hv,'UserData',struct('Movie',NF)); + msgbox('Done. You may save and/or play the movie','Make Movie','modal'); + +case 'save movie' + ht = findobj(0,'Tag','TASKBAR'); + UD = get(ht,'UserData'); % get the userdata + % want UD.DataName + + ha = findobj(hf,'Tag','AxisView'); + UD1 = get(ha,'UserData'); + % want the movie + if(~isfield(UD1,'Movie')), + msgbox('No movie to save','Notice','modal'); + return + end + + [PATH,NAME,EXT,VER] = fileparts(UD.DataName); + + ButtonName = questdlg('Save as Matlab Movie or MPEG','Movie Save','Matlab','MPEG','Tiff','MPEG'); + switch ButtonName + case 'Matlab' + EXT = '.mat'; + case 'MPEG' + EXT = '.mpg'; + case 'Tiff' + EXT = '.tif'; + end + + cd(PATH); + switch ButtonName + case {'Matlab' 'MPEG'} + [NAME,PATH] = uiputfile([NAME '_movie' EXT],'Save the Movie information'); + case 'Tiff' + [NAME,PATH] = uiputfile([NAME '_movie' EXT],'Establish a Tif Root Name'); + end + + if(~NAME), % user punted + return + end + + Movie = UD1.Movie; + switch ButtonName + case 'Matlab' + save(fullfile(PATH,NAME),'Movie'); + case 'MPEG' + % note that in Matlab 5.3, the colormap is bogus in mpgwrite + mpgwrite(Movie,hot(64),fullfile(PATH,NAME)); + case 'Tiff' + [PATH,NAME,EXT,VER] = fileparts(NAME); % knock the extension back off + for i = 1:length(Movie) + imwrite(frame2im(Movie(i)),fullfile(PATH,sprintf('%s_%03.0f.tif',NAME,i)),'tif'); + end + end + + msgbox('Done Saving','BrainStorm Movie Maker','modal'); + +case 'load movie' + [NAME,PATH] = uigetfile('*movie.mat','Load the Matlab Movie File'); + if(NAME==0), + return + end + + UD = load(fullfile(PATH,NAME)); + if(~isfield(UD,'Movie')), + msgbox('No movie to load','Notice','modal'); + return + end + ha = findobj(hf,'Tag','AxisView'); + set(ha,'UserData',setfield(get(ha,'UserData'),'Movie',UD.Movie)); + + +case 'play movie' + ha = findobj(hf,'Tag','AxisView'); + UD1 = get(ha,'UserData'); + % want the movie + if(~isfield(UD1,'Movie')), + msgbox('No movie to play','Notice','modal'); + return + end + movie_player(UD1.Movie); + + +case 'orthoviews' + %delete(findobj(gcf,'Type','Light')); + ha = findobj(gcf','Tag','AxisView'); + hp = findobj(ha,'Type','Patch'); + ha1= findobj(gcf','Tag','axsub1'); + ha2= findobj(gcf','Tag','axsub2'); + ha3= findobj(gcf','Tag','axsub3'); + ha4= findobj(gcf','Tag','axsub4'); + hps= findobj([ha1,ha2,ha3,ha4],'Type','Patch'); + + TAG = get(gcbo,'Userdata'); + + if isempty(TAG)|TAG == 1 + TAG = 0; + %set(ha,'Visible','off') + if isempty(hps) + hps= copyobj([hp,hp,hp,hp],[ha1,ha2,ha3,ha4]); + axes(ha1), view(-180, 90), axis equal, axis off, camlight + axes(ha2), view(-180, 0), axis equal, axis off, camlight + axes(ha3), view(-90, 0), axis equal, axis off, camlight + axes(ha4), view(0, 0), axis equal, axis off, camlight + else + set(hps,'visible','on') + end + set(hp,'visible','off') + else + TAG = 1; + set(hps,'visible','off') + set(hp,'visible','on') + end + set(gcbo,'Userdata',TAG); + +case 'normalize_colormap' % Normalize colormap to maximum + ha(1) = findobj(gcf','Tag','AxisView'); + ha(2)= findobj(gcf','Tag','axsub1'); + ha(3)= findobj(gcf','Tag','axsub2'); + ha(4)= findobj(gcf','Tag','axsub3'); + ha(5)= findobj(gcf','Tag','axsub4'); + + hps= findobj(gcbf,'Type','Patch'); + hps = hps(1); + crtx_amp = get(hps,'FaceVertexCData'); + set(ha,'clim',[0 max(crtx_amp)]); + + +case 'gotime' % Map the current map at a specified time instant + gotime = findobj(gcf','Tag','gotime'); + time = str2num(get(gotime,'String'))/1000; + + ht = findobj(0,'Tag','TASKBAR'); + h = findobj(gcf,'Type','Patch'); + Results = get(h(1),'Userdata'); + if isempty(Results) + UD = get(ht,'UserData'); % get the userdata + cd(UD.Users.STUDIES) + Results = load(UD.DataName,'ImageGridTime','ImageGridAmp'); + set(h(1),'Userdata',Results) + end + + [mm, itime] = min(abs(Results.ImageGridTime - time)); + Results.ImageGridAmp = Results.ImageGridAmp(:,itime); + set(h,'FaceVertexCData',abs(Results.ImageGridAmp)) + + ha = findobj(gcf,'Tag','AxisSeries'); + axes(ha) + UD = get(ha,'UserData'); + set(UD.hline,'Xdata',[Results.ImageGridTime(itime)... + Results.ImageGridTime(itime)]*1000) + +end + + + +return diff --git a/epilepsie/view_topography_cb.m b/epilepsie/view_topography_cb.m new file mode 100644 index 0000000..27bb17e --- /dev/null +++ b/epilepsie/view_topography_cb.m @@ -0,0 +1,849 @@ +function view_topography_cb(action); +% VIEW_TOPOGRAPHY_CB Callback for viewing topographies as min norm solutions +% function view_topography_cb(action); +% Call with no arguments to build gui. +% Calls view_minnorm_gui to actually create the first minimum norm solution +% Sequence is to call rap music first, then this routine applied to the results file + +% John C. Mosher, Ph.D., See Copyright.m file for information. +% $Date: 1/24/01 5:50p $ $Revision: 1 $ + +if(~exist('action','var')), + action = 'build'; +end + +hf = gcbf; % who called me + +switch deblank(lower(action)) +case 'build' + open('view_topography_cb.fig'); + +case 'reset' + % Reset all of the buttons to initial values, refresh the studies list + % Objects: AxisStudy, AxisSignal, AxisNoise, AxisSVD + % EditMinTime, EditMaxTime, EditRank, EditCorr, EditReg + % SliderRank, SliderCorr, + % PopupSVD, PopupReg, PopupOrder, ExecuteRAP + + % Set the menu action + + hur = findobj(hf,'Tag','UimenuReset'); % handle of uimenu for reseting + hud = findobj(hf,'Tag','UimenuDataSets'); + hub = findobj(hf,'Tag','UimenuBSTStudy'); % the studies + delete(hur); + delete(hud); + delete(hub); + + find_brainstorm_files('studies',[],hf); % restablish the menu + hub = findobj(hf,'Tag','UimenuBSTStudy'); % the studies created by that call + % now create a menu to find the datasets for those studies + hud = uimenu('Label','DataSets','Tag','UimenuDataSets'); + % and the call to reset this gui + hur = uimenu('Label','Reset GUI','Tag','UimenuReset',... + 'Callback','view_topography_cb(''reset'')'); + + % So the studies menu has a list of StudySubjects to write to UserData + % now append to each study menu callback a datasets menu action + + hub1 = get(hub,'children'); % what are all of the studies + % These line unnecessary now that JCM dropped the refresh + % hub1r = findobj(hub1,'Label','Refresh Studies'); + % hub1(find(hub1==hub1r)) = []; % don't update the refresher child + + for i = 1:length(hub1), % each submenu of the study menu + cb = get(hub1(i),'Callback'); + % datamenu will make a menu of data sets for min norm + set(hub1(i),'Callback',[cb 'viewtopographydatamenu;']); + end + + % so the menus are rebuilt. Now for the other objects. Activate all of them + % Study has the original data, Series has the time series, View has the patch rendering + + TAGS = {'AxisStudy', 'AxisSeries', 'AxisView'}; + + for i = 1:length(TAGS), + axes(findobj(hf,'Tag',TAGS{i})); + cla reset + set(gca,'Tag',TAGS{i},'Units','Normalized'); % set tag back in + axis on + grid off + end + + hm = findobj(gcf,'Label','Toggle Face'); + delete(hm) + hm = findobj(gcf,'Label','Toggle Lighting'); + delete(hm) + + uimenu(gcf,'Label','Toggle Face','callback','toggleface(get(gcbo,''UserData''))','UserData',[]); + uimenu(gcf,'Label','Toggle Lighting','callback','togglelight(get(gcbo,''UserData''))','UserData',[]); + + % end of reset + +case 'view data' + % user has clicked on some data in the data menu. + ht = findobj(0,'Tag','TASKBAR'); + UD = get(ht,'UserData'); % get the userdata + % interested in StudySubject information and DataName string + + SourceResults = load_brainstorm_file(UD.DataName); + + % we expect this file to be the results file of Rap music (or least squares). + % so the DataName should have the name "results" in it. + if(~isempty(findstr('_results',deblank(lower(UD.DataName))))), + % name has the string '_results' in it, see if it is has indep topographies + if(isempty(SourceResults.IndepTopo)), + msgbox('Data has no IndepTopo to view','Wrong Data Set','modal'); + return + end + % Get the original Data structure + Data = load_brainstorm_file(SourceResults.GUI.DataName); + else + % assume it is a data file + msgbox('DataName does not have the name ''results''','Wrong Data Set','modal') + return + end + + Channel = load_brainstorm_file(SourceResults.StudySubject.Channel); + Channel = Channel.Channel; + GoodChannel = good_channel(Channel,Data.ChannelFlag,'MEG'); + + %% CHEAT, show data and residual in a separate window for now + figure(windfind(sprintf('Synthesized Data and Residuals: %s',UD.DataName))) + windclf + plot([Data.F(GoodChannel,SourceResults.Time) ... + SourceResults.Fsynth Data.F(GoodChannel,SourceResults.Time)-SourceResults.Fsynth]'); + title(sprintf('%s: Data, Synth, Resid',UD.DataName),'interpreter','none') + drawnow + + ha = findobj(hf,'Tag','AxisStudy'); + axes(ha) + cla + line(Data.Time(SourceResults.Time),Data.F(GoodChannel,SourceResults.Time)') + title(Data.Comment) + axis tight + grid on + + ha = findobj(hf,'Tag','AxisSeries'); + axes(ha) + cla + line(Data.Time(SourceResults.Time),SourceResults.TimeSeries) + axis tight + grid on + + drawnow + + hud = findobj(hf,'Tag','UimenuDataSets'); + set(hud,'UserData',SourceResults); % set results into ram of UimenuDataSets + + view_topography_cb('make topographies') + + rotate3d on + +case 'make topographies' + + hud = findobj(hf,'Tag','UimenuDataSets'); + SourceResults = get(hud,'UserData'); + ha = findobj(hf,'Tag','AxisView'); + axes(ha) + cla + User = get_user_directory; % default directory + % want to understand how many vertices there are, partial load + HeadModel = load(fullfile(User.STUDIES,SourceResults.StudySubject.HeadModel),'ImageGridLoc'); + + % just how many grid points are there? + % CHEAT, always looks at grid 1 + if ~isfield(HeadModel,'ImageGridLoc') + errordlg('Please Compute the Gain Matrix (ie an Image Grid) over the Cortical Surface First ') + return + end + + GUI.iGrid = [HeadModel.ImageGridLoc{:}]; + % CHEAT: expects only one ImageGridLOc + GUI.iGrid = GUI.iGrid(1); + + User = get_user_directory; + load(fullfile(User.SUBJECTS,SourceResults.StudySubject.SubjectTess),'Vertices'); + Vertices = Vertices{HeadModel.ImageGridLoc{GUI.iGrid}}; + NumVerts = size(Vertices,2); % number of grid points Cheat ImageGridLoc + + % CHEAT, hidden string to trigger a simulated patch + DataStored = whos('-file',SourceResults.GUI.DataName); + if(~isempty(strmatch('pndx',{DataStored.name}))), + % Yes! We stored a pndx matrix in the original data file. + % synthesize the ImageGridAmp from this information + Answer = questdlg('Min Norm or Use the Patch','Patch Index Detected',... + 'Min Norm','Patch','Patch'); + switch Answer + case 'Patch' + % which patch, the true or the estimated + if(~isempty(strmatch('pndxs',{DataStored.name}))), + TrueOrFound = questdlg('True or Estimated Patch','Patch Solution Detected',... + 'True','Estimated','True'); + switch TrueOrFound + case 'True' + temp = load_brainstorm_file(SourceResults.GUI.DataName,'pndx'); + pndx = temp.pndx; % the patch indices + case 'Estimated' + temp = load_brainstorm_file(SourceResults.GUI.DataName,'pndxs'); + pndx = temp.pndxs; % the patch indices + %CHEAT: For IPMI, the first dipolar source can't be seen. Replace it with the + % the bigger true patch + msgbox('Overwriting the first estimated source with the true patch',... + 'IPMI Viewchart Cheat for Presentation','modal'); + temp = load_brainstorm_file(SourceResults.GUI.DataName,'pndx'); + pndx{1} = temp.pndx{1}; + end + else + % Estimated patch not detected + temp = load_brainstorm_file(SourceResults.GUI.DataName,'pndx'); + pndx = temp.pndx; % the patch indices + end + % clear the min norm answer + SourceResults.ImageGridAmp = zeros(NumVerts,length(pndx)); + for i = 1:length(pndx), + SourceResults.ImageGridAmp(pndx{i},i) = 1; % unity patch + end + [ignore,SourceResults.ImageGridAmp] = colnorm(SourceResults.ImageGridAmp); + set(hud,'UserData',SourceResults); % save back in + otherwise + % do nothing, we calculated the min norm below + end + end + + % if it is still empty, then we minimum norm the data + if(isempty(SourceResults.ImageGridAmp)), % topo's have not been converted + % Cheat, I always want Tikhonov Condition on the min norm data + SourceResults.GUI.REG = 'Tikhonov'; + SourceResults.GUI.Tikhonov = inputdlg('Enter the Tikhonov Condition Number: ',... + 'Topography Mininum Norm',[1 50],{'1000'}); + if(isempty(SourceResults.GUI.Tikhonov)), % user bailed so will we + return + end + SourceResults.GUI.Tikhonov = str2num(SourceResults.GUI.Tikhonov{1}); + + MinResults = minnorm(SourceResults); % local call below + [ignore,SourceResults.ImageGridAmp] = colnorm(MinResults.ImageGridAmp); + % CHEAT notice, would love to save these results, but as of 6/17 have become + % worried about overwriting the original filename in taskbar. + % Stored results would also not allow different regularization parameters. + set(hud,'UserData',SourceResults); % save back in + end + + SimulateMinNorm = questdlg('Synthesize a "mininmum norm" sequence for viewing?'); + SourceResults.SimulateMinNorm = SimulateMinNorm; % save into ram version + switch SimulateMinNorm + case 'Yes' + % Lets Synthesize the ImageGridAmp information as though it were a minimum norm + % Each column of ImageGridAmp is the new min norm solution to the topography. + % Let's column normalize each, then multiply it by it's time series, which already + % anticipates a unity column norm signal + [ignore,TempIndepTopo] = colnorm(SourceResults.ImageGridAmp); % make these the topographies + SourceResults.ImageGridAmp = zeros(size(SourceResults.ImageGridAmp,1),size(SourceResults.TimeSeries,1)); + for i = 1:size(TempIndepTopo,2), % for each topography + SourceResults.ImageGridAmp = SourceResults.ImageGridAmp + ... + TempIndepTopo(:,i)*SourceResults.TimeSeries(:,i)'; + end + end + + set(hud,'UserData',SourceResults); % save back in, including min norm answer + + + cdata = SourceResults.ImageGridAmp(:,1); + SubjectTess = load_brainstorm_file(SourceResults.StudySubject.SubjectTess); + + view_minnorm(SubjectTess.Faces{GUI.iGrid},SubjectTess.Vertices{GUI.iGrid}',cdata,grayish(bluehot(128),.33),ha); % local call below + % creates a patch in the axisview + + mx_val = max(SourceResults.ImageGridAmp(:)); + mn_val = min(SourceResults.ImageGridAmp(:)); + + if(0) + if(mn_val < 0), % we have negative values + % balance the colors + caxis(max(abs([mx_val mn_val]))*[-1 1]); + else + caxis([0 mx_val]) + end + else + % always balance the colors + caxis(max(abs([mx_val mn_val]))*[-1 1]); + end + + rotate3d on + axis vis3d + + hpatch = findobj(gca,'Type','patch'); % find the patch + set(hpatch,'FaceLighting','flat'); % much faster rendering + hm = findobj(gcf,'Label','Toggle Face'); + set(hm,'UserData',hpatch); + hm = findobj(gcf,'Label','Toggle Lighting'); + set(hm,'UserData',hpatch); + + switch SimulateMinNorm + case 'Yes' + % set time marker + ha = findobj(hf,'Tag','AxisSeries'); + axes(ha) + V = axis; % what's the settings + hline = findobj(ha,'Type','line'); % get the lines + Xdata = get(hline(1),'Xdata'); % get the time line + UD = struct('iTime',1,'Time',Xdata); + % iTime is the integer time index in the array of xdata. Time is the time in engineering units + hline = line([UD.Time(UD.iTime);UD.Time(UD.iTime)],[V(3);V(4)],'Color','b','linewidth',2); + set(ha,'UserData',setfield(UD,'hline',hline)); + +case 'No' % making topographies + depth_point = SourceResults.SourceLoc{1}; %first solution + depth_point = depth_point(:,1); % first source points + [ignore,ignore,ignore,ignore,hlinedepth,hstringdepth] = ... + depthgauge(depth_point,depth_point/norm(depth_point),100/1000,5/1000,1000,gca); + hseries = findobj(gcbf,'Tag','AxisSeries'); % the time series + tempcolor = get(hseries,'ColorOrder'); % colororder + % set the color of the depthgauge to that of the default line colors in the axes + set(hlinedepth,'Color',tempcolor(1,:),'MarkerFaceColor',tempcolor(1,:)) +end + +case 'create movie' + hud = findobj(hf,'Tag','UimenuDataSets'); + hv = findobj(hf,'Tag','AxisView'); + hp = findobj(hv,'Type','Patch'); % get the patch + SourceResults = get(hud,'UserData'); % the results data + if(isempty(SourceResults)), + msgbox('Load data first','Notice','modal'); + return + end + + axes(hv); + org_caxis = caxis; % the original axis + + ScaleFactor = inputdlg('Enter Caxis Scaling Factor','BrainStorm Movie Maker',[1 50],{'1'}); + caxis(str2num(ScaleFactor{:})*org_caxis); + + NF = getframe(hf); % the whole figure + mpg_limits = size(NF.cdata); %the size of the figure + if ((mpg_limits(1) > 480) | (mpg_limits(2) > 640)), + ButtonName = questdlg(sprintf('Resize your window to 480 x 640?')); + switch ButtonName + case 'Yes' + Vposition = get(hf,'Position'); + set(hf,'Position',[Vposition(1:2) 480 360]); % don't know why this translates to 480 x 640 + NF = getframe(hf); % the whole figure again + case 'No' + % do nothing + case 'Cancel' + % user punted, so will we + return + end + end + + [NF(1:size(SourceResults.ImageGridAmp,2))] = deal(NF); % allocate memory + + hobjs = get(hv,'chil'); % the children of the view axis + for i = 1:length(hobjs), + switch deblank(lower(get(hobjs(i),'type'))) + case {'text','line'} % get rid of the old depthgauges + delete(hobjs(i)) + end + end + + hlinedepth = []; % the depthgauge information + hstringdepth = []; + disp(sprintf('Making movie for %.0f topographies',size(SourceResults.ImageGridAmp,2))); + for i = 1:size(SourceResults.ImageGridAmp,2), % for each source + delete(hlinedepth) + delete(hstringdepth) + set(hp,'FaceVertexCdata',SourceResults.ImageGridAmp(:,i)); + + switch SourceResults.SimulateMinNorm + case 'Yes' + % we are simulating the time series, update the time index in the time series + ha = findobj(hf,'Tag','AxisSeries'); + UD = get(ha,'UserData'); + % UD.Time tells us where we are in true unit + set(UD.hline,'Xdata',[1 1]*UD.Time(i)); % move it + axes(ha); + xlabel(sprintf('Time: %7.1f',UD.Time(i))); + + case 'No' + % we are viewing topographies, drop in a depthgauge + depth_point = SourceResults.SourceLoc{i}; % next source points + hlinedepth = zeros(size(depth_point,2),1); % each location + hstringdepth = hlinedepth; % numbers, initial + for i =1:length(hlinedepth), + [ignore,ignore,ignore,ignore,hlinedepth(i),hstringdepth(i)] = ... + depthgauge(depth_point(:,i),... + depth_point(:,i)/norm(depth_point(:,i)),100/1000,5/1000,1000,gca); + end + hseries = findobj(gcbf,'Tag','AxisSeries'); % the time series + tempcolor = get(hseries,'ColorOrder'); % colororder + % set the color of the depthgauge to that of the default line colors in the axes + mod_i = mod(i-1,size(tempcolor,1))+1; % modulo the length + set(hlinedepth,'Color',tempcolor(mod_i,:),'MarkerFaceColor',tempcolor(mod_i,:)) + end + + drawnow + NF(i) = getframe(hf); % get the entire frame + end + + axes(hv); + caxis(org_caxis); % reset the scale axes + + set(hv,'UserData',struct('Movie',NF)); + + msgbox('Done. You may save and/or play the movie','Make Movie','modal'); + +case 'save movie' + ht = findobj(0,'Tag','TASKBAR'); + UD = get(ht,'UserData'); % get the userdata + % want UD.DataName + + ha = findobj(hf,'Tag','AxisView'); + UD1 = get(ha,'UserData'); + % want the movie + if(~isfield(UD1,'Movie')), + msgbox('No movie to save','Notice','modal'); + return + end + + [PATH,NAME,EXT,VER] = fileparts(UD.DataName); + % overwrite the extension with the desired form + ButtonName = questdlg('Save as Matlab Movie or MPEG','Movie Save','Matlab','MPEG','Tiff','MPEG'); + switch ButtonName + case 'Matlab' + EXT = '.mat'; + case 'MPEG' + EXT = '.mpg'; + case 'Tiff' + EXT = '.tif'; + end + + cd(PATH); + switch ButtonName + case {'Matlab' 'MPEG'} + [NAME,PATH] = uiputfile([NAME '_movie' EXT],'Save the Movie information'); + case 'Tiff' + [NAME,PATH] = uiputfile([NAME '_movie' EXT],'Establish a Tif Root Name'); + end + + if(~NAME), % user punted + return + end + + Movie = UD1.Movie; + switch ButtonName + case 'Matlab' + save(fullfile(PATH,NAME),'Movie'); + case 'MPEG' + % note that in Matlab 5.3, the colormap is bogus in mpgwrite + mpgwrite(Movie,hot(64),fullfile(PATH,NAME)); + case 'Tiff' + [PATH,NAME,EXT,VER] = fileparts(NAME); % knock the extension back off + for i = 1:length(Movie) + imwrite(frame2im(Movie(i)),fullfile(PATH,sprintf('%s_%03.0f.tif',NAME,i)),'tif'); + end + end + + msgbox('Done Saving','BrainStorm Movie Maker','modal'); + +case 'load movie' + [NAME,PATH] = uigetfile('*movie.mat','Load the Matlab Movie File'); + if(NAME==0), + return + end + + UD = load(fullfile(PATH,NAME)); + if(~isfield(UD,'Movie')), + msgbox('No movie to load','Notice','modal'); + return + end + ha = findobj(hf,'Tag','AxisView'); + set(ha,'UserData',setfield(get(ha,'UserData'),'Movie',UD.Movie)); + +case 'play movie' + ha = findobj(hf,'Tag','AxisView'); + UD1 = get(ha,'UserData'); + % want the movie + if(~isfield(UD1,'Movie')), + msgbox('No movie to play','Notice','modal'); + return + end + movie_player(UD1.Movie); + +end + +return + +%-----------------------------------------------------% + +function view_minnorm(faces,vertices,cdata,cmap,ha); +% VIEW_MINNORM view neural activity on the cortex with emphasis on speed for min norm solutions +% function [hf,hs,hl,tconn] = view_minnorm(faces,vertices,cdata,tconn,cmap,fname); +% faces, vertices, cdata is the standard patch or trisurf information. +% tconn is the triangle connectivity, if not given then will be computed and +% returned in the outputs. +% cmap is the colormap to apply to cdata, defaults to bluehot with a gray minimum +% fname is the window to write, defaults to current + +% Ambient, Diffusion, Specular, spec exp, spec reflect (see material +MaterialBack = [0.5 0.7 0.3 10 1]; % qualities of the background image +MaterialSource = [0.8 0.7 0.8 20 1]; % qualities for the data + +% handle user inputs +if(~exist('cmap')), + cmap= []; % activate default +end + +%% keep the text on how to create the triangle connectivity +%% TriConn +if(0) + if(isempty(tconn)), % create it for the user + tconn=cell(size(vertices,1),1); % one empty cell per vertex + disp(sprintf('Processing %.0f faces into triangle connectivity',nf)) + for iface = 1:size(faces,1), %for each triangle, what are the vertices + if(~rem(iface,25000)), + fprintf(' %.0f',iface); + end + for iside = 1:size(faces,2), %each vertex of the face + tconn{faces(iface,iside)}(end+1) = iface; % map face number to vertex cell + end + end + fprintf('\n'); + end +end + +% check out the vertex data + +mn = min(cdata(:)); % minimum non-zero, could be negative +mx = max(cdata(:)); % maximum non-zero + +cmap = grayish(hot(128),.33); % CHEAT +if (1)%isempty(cmap)), % need to generate a good colormap CHEAt + if(mn >= 0), % all positive information + cmap = grayish(hot(128),.33); + else + % we have negative values as well + cmap = grayish(bluehot(128),.33); + end +end + +% now lets plot +hs = zeros(2,1); % 1 is data, 2 is background + +axes(ha); +cla + +% form the data +hs(1) = patch('faces',faces,'vertices',vertices,... + 'facevertexcdata',cdata,'facecolor','interp','edgecolor','none',... + 'facelighting','phong'); +set_material(hs(1),MaterialSource); % local function + +colormap(cmap) + +if(mn < 0), %balance for negative values + cx = caxis; + caxis(max(abs(cx))*[-1 1]); +end + +view(2) +axis image +axis off + +hl(1) = camlight(-20,30); +hl(2) = camlight(20,30); +hl(3) = camlight(-20,-30); +for i = 1:length(hl), + set(hl(i),'color',[.8 1 1]/length(hl)/1.2); % mute the intensity of the lights +end + +return + +function set_material(isurf,data); +% set material, set only for specified handle +ka = data(1); +kd = data(2); +ks= data(3); +n= data(4); +sc= data(5); +set(isurf,'AmbientStrength', ka, 'DiffuseStrength', kd, 'SpecularStrength', ks, ... + 'SpecularExponent', n, 'SpecularColorReflectance', sc) +return + +function Results = minnorm(SourceResults); +% MINNORM Mininum norm solution as called from above, dedicated to topographies only +% As kluged up from the original minnorm_gui call: +% Parameters in GUI are: +% .DataName, string of actual data file used +% we want to minnorm the indeptopo, so let GUI.DataName = IndepTopo matrix +% .Results, optional file to write results +% don't write anywhere +% .Segment, two element vector giving the start and stop index numbers to use in +% the spatiotemporal data matrix. +% all +% .iGrid, which imaging grid to use in the subject information +% CHEAT, always set to 1 for now +% .Rank, rank to use, e.g. 10 +% no svd of topographies +% .ChannelFlag, an index of channels in the data file to process +% the full indeptopo, since it's already good +% +% The following three structure fields are optional for regularization purposes. +% They may be null or missing to represent unused. If multiple fields are given, +% then precedence is given in the order given below. +% .Condition, condition number to use in truncation, e.g. 100 +% .Energy, fractional energy to use in truncation, e.g. .95 +% .Column_norm, string of 'y' or 'n' to use in regularization +% Whatever was originally used in the call +% +% If all are null, no regularization is performed in the RAP-MUSIC loops. +% If GUI.Results is non-null,then writes results to that file. + + +BLOCK = 10000; % process by blocks + + +User = get_user_directory; +% load up the information needed for the gain matrix, partial load +HeadModel = load(fullfile(User.STUDIES,SourceResults.StudySubject.HeadModel),'Function','Param',... + 'ImageGridLoc','ImageGain','ImageGainCovar'); % expensive load + +GUI.iGrid = [HeadModel.ImageGridLoc{:}]; +% CHEAT: expects only one ImageGridLOc +GUI.iGrid = GUI.iGrid(1); + +if(isempty(HeadModel.ImageGain{GUI.iGrid})), + msgbox('Your Grid Gain Matrix is empty, please run Head Modeler to build','Error','modal'); + return +end +if 0%(isempty(HeadModel.ImageGainCovar{GUI.iGrid})), + disp(' ') + disp('Your Grid Gain Correlation matrix is empty, consider running Head Modeler to build'); + disp(' ') +end + +% ImageGains are assumed stored in the local directory to the subject head model +[path,name,ext,ver] = fileparts(fullfile(User.STUDIES,SourceResults.StudySubject.HeadModel)); +cd(path) +fid = fopen(... + fullfile(path,char(HeadModel.ImageGain{GUI.iGrid})),... + 'rb','ieee-be'); +if(fid == -1), + msgbox(sprintf('Failed to open %s',HeadModel.ImageGain{GUI.iGrid}),'Error','modal') + error(sprintf('Failed to open %s',HeadModel.ImageGain{GUI.iGrid})); + return +end + +% synthesize data +Channel = load_brainstorm_file(SourceResults.StudySubject.Channel); +Channel = Channel.Channel; +Data = struct('F',SourceResults.IndepTopo,... + 'ChannelFlag',SourceResults.ChannelFlag,... + 'Projector',SourceResults.Projector,'Comment',SourceResults.Comment); + +% now alter the data according to the bad channels +GoodChannel = good_channel(Channel,SourceResults.ChannelFlag,'MEG'); %good channels + +if(isempty(HeadModel.ImageGainCovar{GUI.iGrid})), + hwaitbar = waitbar(0,'Creating Gain matrix Covariance'); + + % just how many grid points are there? + User = get_user_directory; + load(fullfile(User.SUBJECTS,SourceResults.StudySubject.SubjectTess),'Vertices'); + Vertices = Vertices{HeadModel.ImageGridLoc{GUI.iGrid}}; + nv = size(Vertices,2); % number of grid points + + disp(sprintf('Processing for %.0f grid points . . .',nv)); + + frewind(fid); + rows = fread(fid,1,'uint32'); + + AAt = zeros(rows); + + for i = 1:BLOCK:nv, + + waitbar(i/nv,hwaitbar); + + ndx = [0:(BLOCK-1)]+i; + if(ndx(end) > nv), % last block too long + ndx = [ndx(1):nv]; + end + + cols = length(ndx); % how many columns to retrieve this time + + % 8 bytes per element, find starting point + offset = 4 + (ndx(1)-1)*rows*4; + status = fseek(fid,offset,'bof'); + if(status == -1), + error(sprintf('Error reading file at column %.0f',i)); + end + + temp = fread(fid,[rows,cols],'float32'); + + AAt = AAt + temp*temp'; % next chunk of correlations + + end + + close(hwaitbar); + + disp('Saving the gain covar back into your head model'); + % CHEAT, only works on first imaging matrix + ImageGainCovar{1} = AAt; + save(SourceResults.StudySubject.HeadModel,'ImageGainCovar','-append'); + + +else + + % already exists, load it + AAt = HeadModel.ImageGainCovar{GUI.iGrid}{1}; + +end % creating gain covariance matrix + +% knock out the rows and columns of the correlation matrix + +AAt = AAt(GoodChannel,:); +AAt = AAt(:,GoodChannel); + +% the ImageGain matrix is handled separately, due to it's size + +% did the user provide an existing control. Decompose it +if(isfield(Data,'Projector')), + if(isempty(Data.Projector)), + A = []; + Ua = []; + else + A = Data.Projector; % initialize + Ua = orth(A); + end +else % user did not give a projector + A = []; + Ua = []; +end + +% use all of the "data" (all of the indeptopographies) +F = Data.F; + +% handle this in the call +% [ignore,F] = colnorm(F); % column norm the topographies + +% use all time (all of the topographies), time is indexer to topography +Time = [1:size(F,2)]; + +% Begin the minimum norm estimate + +% recall the y = Ax, let x = A'c -> y = AA'c, solve for c, the 'coefs' of x. +% c = pinv(AA')*y, and we will regularize the inverse of AA' based on user selection +% Then form x = A'c. + +[U,S,V] = svd(AAt); +% NOTE: we regularize the indep topographies the same way as the data + +U = regsubspace(SourceResults.GUI,U,sqrt(S)); % condition based on svd of gain matrix +reg_rank = size(U,2); % reduced rank based on regularization +S = diag(S); +switch deblank(lower(SourceResults.GUI.REG)) +case 'tikhonov' + % pinv(A)*b is inv(A'*A)*A'*b. Let A = [A;lamb I]. inv(A'*A + lamb^2 I)*A'*b + Lambda = sqrt(S(1))/SourceResults.GUI.Tikhonov; % sing values already squared + Si = 1../(S(1:reg_rank)+Lambda^2); % filtered inverse +otherwise + % do nothing, truncation only + Si = 1../S(1:reg_rank); +end + +% the min norm coefs +coefs = V(:,1:reg_rank)*... + ((spdiags(Si,0,reg_rank,reg_rank)*U(:,1:reg_rank)') * F); + +% let rAAt = regularized(AAt), as found by the above +% So F = AAt *c, c = inv(rAAt), so Fsynth = AAt*c, let's calculate and stick into results +Fsynth = AAt * coefs; +% Store conveniently in the Results, Fsynth here is the regularized topography + +% now calculate the final solution + + +% just how many grid points are there? +User = get_user_directory; +load(fullfile(User.SUBJECTS,SourceResults.StudySubject.SubjectTess),'Vertices'); +Vertices = Vertices{HeadModel.ImageGridLoc{GUI.iGrid}}; +nv = size(Vertices,2); % number of grid points + +ImageGridAmp = zeros(nv,size(F,2)); % one column per topography + +hwaitbar = waitbar(0,sprintf('Processing %.0f point final inverse transform',nv)); + +disp(sprintf('Processing for %.0f grid points . . .',nv)); + +frewind(fid); +rows = fread(fid,1,'uint32'); + +for i = 1:BLOCK:nv, + + waitbar(i/nv,hwaitbar); + + ndx = [0:(BLOCK-1)]+i; + if(ndx(end) > nv), % last block too long + ndx = [ndx(1):nv]; + end + + cols = length(ndx); % how many columns to retrieve this time + + % 8 bytes per element, find starting point + offset = 4 + (ndx(1)-1)*rows*4; + status = fseek(fid,offset,'bof'); + if(status == -1), + error(sprintf('Error reading file at column %.0f',i)); + end + + temp = fread(fid,[rows,cols],'float32'); + temp = temp(GoodChannel,:); + + ImageGridAmp(ndx,:) = temp'*coefs; % next chunk of solutions + +end + +close(hwaitbar); + +fclose(fid); + + +% now map to results +if(0) % only care about a few things + + Comment = sprintf('MIN NORM, rank %.0f',GUI.Rank); + Date = datestr(datenum(now),0); + Subject = SourceResults.Subject; + Study = Study; + Time = [GUI.Segment(1):GUI.Segment(2)]; + ChannelFlag = GUI.ChannelFlag; + NoiseCov = []; + SourceCov = []; + Projector = Data.Projector; + SourceLoc = []; + SourceOrder = []; + SourceOrientation = cell(1); + ModelGain = []; + IndepTopo = []; + TimeSeries = []; + PatchNdx = []; + PatchAmp = []; + % ImageGridAmp = ImageGridAmp; + ImageGridTime = Data.Time(Time); + Function = mfilename; % name of this calling routine + + Results = struct('Comment',Comment,'Function',mfilename,'StudySubject',StudySubject,... + 'GUI',GUI,'Date',Data,'Subject',Subject,'Study',Study,'Time',Time,... + 'ChannelFlag',ChannelFlag,'NoiseCov',NoiseCov,'SourceCov',SourceCov,... + 'Projector',Projector,'SourceLoc',SourceLoc,'SourceOrder',SourceOrder,... + 'SourceOrientation',[],'ModelGain',ModelGain,... + 'IndepTopo',IndepTopo,'TimeSeries',TimeSeries,'PatchNdx',PatchNdx,... + 'PatchAmp',PatchAmp,'ImageGridAmp',ImageGridAmp,'ImageGridTime',ImageGridTime,'Fsynth',Fsynth); + Results.SourceOrientation = SourceOrientation; % struct command could kron out +end + +% we only care about imagegrid amp and Fsynth + +Results = struct('ImageGridAmp',ImageGridAmp,'Fsynth',Fsynth); + + +return % to the calling routine above diff --git a/extrema.m b/extrema.m new file mode 100644 index 0000000..2a9d265 --- /dev/null +++ b/extrema.m @@ -0,0 +1,146 @@ +function [xmax,imax,xmin,imin] = extrema(x) +%EXTREMA Gets the global extrema points from a time series. +% [XMAX,IMAX,XMIN,IMIN] = EXTREMA(X) returns the global minima and maxima +% points of the vector X ignoring NaN's, where +% XMAX - maxima points in descending order +% IMAX - indexes of the XMAX +% XMIN - minima points in descending order +% IMIN - indexes of the XMIN +% +% DEFINITION (from http://en.wikipedia.org/wiki/Maxima_and_minima): +% In mathematics, maxima and minima, also known as extrema, are points in +% the domain of a function at which the function takes a largest value +% (maximum) or smallest value (minimum), either within a given +% neighbourhood (local extrema) or on the function domain in its entirety +% (global extrema). +% +% Example: +% x = 2*pi*linspace(-1,1); +% y = cos(x) - 0.5 + 0.5*rand(size(x)); y(40:45) = 1.85; y(50:53)=NaN; +% [ymax,imax,ymin,imin] = extrema(y); +% plot(x,y,x(imax),ymax,'g.',x(imin),ymin,'r.') +% +% See also EXTREMA2, MAX, MIN + +% Written by +% Lic. on Physics Carlos Adrián Vargas Aguilera +% Physical Oceanography MS candidate +% UNIVERSIDAD DE GUADALAJARA +% Mexico, 2004 +% +% nubeobscura@hotmail.com + +% From : http://www.mathworks.com/matlabcentral/fileexchange +% File ID : 12275 +% Submited at: 2006-09-14 +% 2006-11-11 : English translation from spanish. +% 2006-11-17 : Accept NaN's. +% 2007-04-09 : Change name to MAXIMA, and definition added. + + +xmax = []; +imax = []; +xmin = []; +imin = []; + +% Vector input? +Nt = numel(x); +if Nt ~= length(x) + error('Entry must be a vector.') +end + +% NaN's: +inan = find(isnan(x)); +indx = 1:Nt; +if ~isempty(inan) + indx(inan) = []; + x(inan) = []; + Nt = length(x); +end + +% Difference between subsequent elements: +dx = diff(x); + +% Is an horizontal line? +if ~any(dx) + return +end + +% Flat peaks? Put the middle element: +a = find(dx~=0); % Indexes where x changes +lm = find(diff(a)~=1) + 1; % Indexes where a do not changes +d = a(lm) - a(lm-1); % Number of elements in the flat peak +a(lm) = a(lm) - floor(d/2); % Save middle elements +a(end+1) = Nt; + +% Peaks? +xa = x(a); % Serie without flat peaks +b = (diff(xa) > 0); % 1 => positive slopes (minima begin) + % 0 => negative slopes (maxima begin) +xb = diff(b); % -1 => maxima indexes (but one) + % +1 => minima indexes (but one) +imax = find(xb == -1) + 1; % maxima indexes +imin = find(xb == +1) + 1; % minima indexes +imax = a(imax); +imin = a(imin); + +nmaxi = length(imax); +nmini = length(imin); + +% Maximum or minumim on a flat peak at the ends? +if (nmaxi==0) && (nmini==0) + if x(1) > x(Nt) + xmax = x(1); + imax = indx(1); + xmin = x(Nt); + imin = indx(Nt); + elseif x(1) < x(Nt) + xmax = x(Nt); + imax = indx(Nt); + xmin = x(1); + imin = indx(1); + end + return +end + +% Maximum or minumim at the ends? +if (nmaxi==0) + imax(1:2) = [1 Nt]; +elseif (nmini==0) + imin(1:2) = [1 Nt]; +else + if imax(1) < imin(1) + imin(2:nmini+1) = imin; + imin(1) = 1; + else + imax(2:nmaxi+1) = imax; + imax(1) = 1; + end + if imax(end) > imin(end) + imin(end+1) = Nt; + else + imax(end+1) = Nt; + end +end +xmax = x(imax); +xmin = x(imin); + +% NaN's: +if ~isempty(inan) + imax = indx(imax); + imin = indx(imin); +end + +% Same size as x: +imax = reshape(imax,size(xmax)); +imin = reshape(imin,size(xmin)); + +% Descending order: +[temp,inmax] = sort(-xmax); clear temp +xmax = xmax(inmax); +imax = imax(inmax); +[xmin,inmin] = sort(xmin); +imin = imin(inmin); + + +% Carlos Adrián Vargas Aguilera. nubeobscura@hotmail.com \ No newline at end of file diff --git a/extrema2.m b/extrema2.m new file mode 100644 index 0000000..53a5e76 --- /dev/null +++ b/extrema2.m @@ -0,0 +1,174 @@ +function [xymax,smax,xymin,smin] = extrema2(xy,varargin) +%EXTREMA2 Gets the extrema points from a surface. +% [XMAX,IMAX,XMIN,IMIN] = EXTREMA2(X) returns the maxima and minima +% elements of the matriz X ignoring NaN's, where +% XMAX - maxima points in descending order (the bigger first and so on) +% IMAX - linear indexes of the XMAX +% XMIN - minima points in descending order +% IMIN - linear indexes of the XMIN. +% The program uses EXTREMA. +% +% The extrema points are searched only through the column, the row and +% the diagonals crossing each matrix element, so it is not a perfect +% mathematical program and for this reason it has an optional argument. +% The user should be aware of these limitations. +% +% [XMAX,IMAX,XMIN,IMIN] = EXTREMA2(X,1) does the same but without +% searching through the diagonals (less strict and perhaps the user gets +% more output points). +% +% DEFINITION (from http://en.wikipedia.org/wiki/Maxima_and_minima): +% In mathematics, maxima and minima, also known as extrema, are points in +% the domain of a function at which the function takes a largest value +% (maximum) or smallest value (minimum), either within a given +% neighbourhood (local extrema) or on the function domain in its entirety +% (global extrema). +% +% Note: To change the linear index to (i,j) use IND2SUB. +% +% Example: +% [x,y] = meshgrid(-2:.2:2,3:-.2:-2); +% z = x.*exp(-x.^2-y.^2); z(10,7)= NaN; z(16:19,13:17) = NaN; +% surf(x,y,z), shading interp +% [zmax,imax,zmin,imin] = extrema2(z); +% hold on +% plot3(x(imax),y(imax),zmax,'bo',x(imin),y(imin),zmin,'ro') +% for i = 1:length(zmax) +% text(x(imax(i)),y(imax(i)),zmax(i),[' ' num2str(zmax(i))]) +% end +% for i = 1:length(zmin) +% text(x(imin(i)),y(imin(i)),zmin(i),[' ' num2str(zmin(i))]) +% end +% hold off +% +% See also EXTREMA, MAX, MIN + +% Written by +% Lic. on Physics Carlos Adrián Vargas Aguilera +% Physical Oceanography MS candidate +% UNIVERSIDAD DE GUADALAJARA +% Mexico, 2005 +% +% nubeobscura@hotmail.com + +% From : http://www.mathworks.com/matlabcentral/fileexchange +% File ID : 12275 +% Submited at: 2006-09-14 +% 2006-11-11 : English translation from spanish. +% 2006-11-17 : Accept NaN's. +% 2006-11-22 : Fixed bug in INDX (by JaeKyu Suhr) +% 2007-04-09 : Change name to MAXIMA2, and definition added. + +M = size(xy); +if length(M) ~= 2 + error('Entry must be a matrix.') +end +N = M(2); +M = M(1); + +% Search peaks through columns: +[smaxcol,smincol] = extremos(xy); + +% Search peaks through rows, on columns with extrema points: +im = unique([smaxcol(:,1);smincol(:,1)]); % Rows with column extrema +[smaxfil,sminfil] = extremos(xy(im,:).'); + +% Convertion from 2 to 1 index: +smaxcol = sub2ind([M,N],smaxcol(:,1),smaxcol(:,2)); +smincol = sub2ind([M,N],smincol(:,1),smincol(:,2)); +smaxfil = sub2ind([M,N],im(smaxfil(:,2)),smaxfil(:,1)); +sminfil = sub2ind([M,N],im(sminfil(:,2)),sminfil(:,1)); + +% Peaks in rows and in columns: +smax = intersect(smaxcol,smaxfil); +smin = intersect(smincol,sminfil); + +% Search peaks through diagonals? +if nargin==1 + % Check peaks on down-up diagonal: + [iext,jext] = ind2sub([M,N],unique([smax;smin])); + [sextmax,sextmin] = extremos_diag(iext,jext,xy,1); + + % Check peaks on up-down diagonal: + smax = intersect(smax,[M; (N*M-M); sextmax]); + smin = intersect(smin,[M; (N*M-M); sextmin]); + + % Peaks on up-down diagonals: + [iext,jext] = ind2sub([M,N],unique([smax;smin])); + [sextmax,sextmin] = extremos_diag(iext,jext,xy,-1); + + % Peaks on columns, rows and diagonals: + smax = intersect(smax,[1; N*M; sextmax]); + smin = intersect(smin,[1; N*M; sextmin]); +end + +% Extrema points: +xymax = xy(smax); +xymin = xy(smin); + +% Descending order: +[temp,inmax] = sort(-xymax); clear temp +xymax = xymax(inmax); +smax = smax(inmax); +[xymin,inmin] = sort(xymin); +smin = smin(inmin); + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +function [smax,smin] = extremos(matriz) +% Peaks through columns or rows. + +smax = []; +smin = []; + +for n = 1:length(matriz(1,:)) + [temp,imaxfil,temp,iminfil] = extrema(matriz(:,n)); clear temp + if ~isempty(imaxfil) % Maxima indexes + imaxcol = repmat(n,length(imaxfil),1); + smax = [smax; imaxfil imaxcol]; + end + if ~isempty(iminfil) % Minima indexes + imincol = repmat(n,length(iminfil),1); + smin = [smin; iminfil imincol]; + end +end + + +function [sextmax,sextmin] = extremos_diag(iext,jext,xy,A) +% Peaks through diagonals (down-up A=-1) + +[M,N] = size(xy); +if A==-1 + iext = M-iext+1; +end +[iini,jini] = cruce(iext,jext,1,1); +[iini,jini] = ind2sub([M,N],unique(sub2ind([M,N],iini,jini))); +[ifin,jfin] = cruce(iini,jini,M,N); +sextmax = []; +sextmin = []; +for n = 1:length(iini) + ises = iini(n):ifin(n); + jses = jini(n):jfin(n); + if A==-1 + ises = M-ises+1; + end + s = sub2ind([M,N],ises,jses); + [temp,imax,temp,imin] = extrema(xy(s)); clear temp + sextmax = [sextmax; s(imax)']; + sextmin = [sextmin; s(imin)']; +end + + +function [i,j] = cruce(i0,j0,I,J) +% Indexes where the diagonal of the element io,jo crosses the left/superior +% (I=1,J=1) or right/inferior (I=M,J=N) side of an MxN matrix. + +arriba = 2*(I*J==1)-1; + +si = (arriba*(j0-J) > arriba*(i0-I)); +i = (I - (J+i0-j0)).*si + J+i0-j0; +j = (I+j0-i0-(J)).*si + J; + + +% Carlos Adrián Vargas Aguilera. nubeobscura@hotmail.com \ No newline at end of file diff --git a/fakebar.m b/fakebar.m new file mode 100644 index 0000000..9accb9f --- /dev/null +++ b/fakebar.m @@ -0,0 +1,43 @@ +function fakebar(varargin) +% fakebar(value) displays progressing waitbars until you press cancel +% value: how many loops are to be done until a waitbar is full. Try +% values > 1000 (optional parameter) +% +% Example: +% fakebar(20000); +% +% Creates a waitbar with changing random progress. Use it to show +% your boss that you can't work at the moment because systemload +% is 100% and the calculations will take some time -> coffee ;-) +% +% Version 1.0 by Stefan Eireiner (stefan@algorithmus.info) +% last change 18.06.2004 + +if nargin > 0 + divisor = varargin{1}; +else + divisor = 1000; +end + +while(true) + fb = waitbar(0, 'Calculating Data...', 'CreateCancelBtn', 'delete(get(0,''CurrentFigure''))', 'Name', 'Analyzer 1.0'); + counter = 0; + while(counter < divisor && ishandle(fb)) + il = round((rand^2)*divisor*0.5); + adder = rand; + for m=1:il + counter = counter + adder^2; + try + waitbar(counter/divisor,fb); + catch + disp('Calculations done!'); + return; + end + end + end + if(ishandle(fb)) + delete(fb); + else + return; + end +end \ No newline at end of file diff --git a/fftfilter.m b/fftfilter.m new file mode 100644 index 0000000..abbcdae --- /dev/null +++ b/fftfilter.m @@ -0,0 +1,183 @@ +function [xf] = fftfilter(x,Fc,SR,tdim,df) +% fftfilter - Filter signal (using FFT transform) +% [xf]=fftfilter(x,Fc,SR) +% Fc =[ HPFc LPFc ] are high- and low-pass cutoff frequencies +% SR: Sampling rate +% Set one Fc to NaN to use one-sided filter. For a notch (band-stop) +% filter use one single value. +% +% [xf]=fftfilter(x,Fc,SR,tdim) +% Filtering is done along dimension tdim (default: the longest +% dimension) +% +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Based on EEGLAB eegfiltfft() & Matlab's fftfilt +% Copyright (C) 2011 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2011-06-09 Creation +% +% ----------------------------- Script History --------------------------------- + + +% A causal fft algorithm is applied (i.e. no phase shift). The filter +% functions is constructed from a Hamming window. +notch=0; +if nargin<3 + error +end +if numel(Fc)==1 + notch = 1; + Fc(2)=Fc(1); +end +sx=size(x); +if nargin<4 % Default longest dimension + [ignore,tdim]=max(sx); +end + +%Ensure x has an even number of data point +if rem(sx(tdim),2) + sx0 = sx; + sX0(tdim) = 1; + x = cat(tdim, x, zeros(sx0)); +end + +nyq = sx(tdim)/2; + +f = [0:nyq-1 nyq:-1:1]./sx(tdim)*SR; % Frequency vector for plotting + +% find closest freq in fft decomposition +if ~isnan(Fc(1)) && Fc(1) ~= 0 + [tmp idxl]=min(abs(f-Fc(1))); +else + idxl = 0; +end; +if ~isnan(Fc(2)) && Fc(2) ~= 0 + [tmp idxh]=min(abs(f-Fc(2))); +else + %idxh = ceil(length(fv)/2); + idxh = nyq; +end; + +% filter the data +xf=fft(x,[],tdim); +if notch + xf(idxl+1:idxh-1)=0; + if mod(length(xf),2) == 0 + xf(end/2:end)=0; + else + xf((end+1)/2:end)=0; + end; +else + xf(1:idxl)=0; + xf(end-idxl:end)=0; + xf(idxh:end)=0; +end; +xf = 2*real(ifft(xf)); + +return + + + +% Construct the filter function H(f) using frequencies in Nyquist units +N = sx(tdim)+rem(sx(tdim),2); +B = fir2(N-1,[0 rf*2 (rf+df)*2 1],[1 1 0 0]); +H = abs(fft(B)); % Make zero-phase filter function +H=repmat(H',[ 1 sx(px(2:end))] ); +k=1; +xf = real(ifft(fft(x) .* H)); +xf = ipermute(reshape(xf(1:sx(tdim),:),sx(px)),px); + + +% This is the original function by Mario: + +function xf = lowpassFilter(x,Fs,Fp2) +% function XF = lowpassFilter(X,FS,FP2) +% +% Bandpass filter for the signal x. An causal fft algorithm +% is applied (i.e. no phase shift). The filter functions is +% constructed from a Hamming window. +% +% Fs : sampling frequency +% +% The passband (Fp2) and stop band (Fs2) are defined as +% +% --------------------------- +% |\ +% | \ +% | \ +% | \ +% | ----------------- +% | | +% Fp2 Fs2 = Fp2 + 0.5 (Hz) +% +% +% +% If NO OUTPUTS arguments are assigned the filter function H(f) and +% impulse response are plotted. +% +% NOTE: for long data traces the filter is very slow. +% +% EXEMPLE +% x= sin(2*pi*12*[0:1/200:10])+sin(2*pi*30*[0:1/200:10]) +% y=lowpassFilter(x,200,10); lowpass filter between 0 and 10 Hz +%------------------------------------------------------------------------ +% Originally produced by the Helsinki University of Technology, +% Adapted by Mariecito SCHMUCKEN 2001 +%------------------------------------------------------------------------ +Fs2 = Fp2 + 0.5; + +if size(x,1) == 1 + x = x'; +end +% Make x even +Norig = size(x,1); +if rem(Norig,2) + x = [x' zeros(size(x,2),1)]'; +end + +% Normalize frequencies +Ns2 = Fs2/(Fs/2); +Np2 = Fp2/(Fs/2); + +% Construct the filter function H(f) +N = size(x,1); +Nh = N/2; + +% B = fir2(N-1,[0 Ns1 Np1 Np2 Ns2 1],[0 0 1 1 0 0]); +B = fir2(N-1,[0 Np2 Ns2 1],[1 1 0 0]); +H = abs(fft(B)); % Make zero-phase filter function +IPR = real(ifft(H)); + +% Visual display if off +% if nargout == 0 +% figure, +% subplot(2,1,1) +% f = Fs*(0:Nh-1)/(N); +% plot(f,H(1:Nh)); +% xlim([0 2*Fs2]) +% ylim([0 1]); +% title('Filter function H(f)') +% xlabel('Frequency (Hz)') +% subplot(2,1,2) +% plot((1:Nh)/Fs,IPR(1:Nh)) +% xlim([0 2/Fp2]) +% xlabel('Time (sec)') +% ylim([min(IPR) max(IPR)]) +% title('Impulse response') +% end + + +if size(x,2) > 1 + for k=1:size(x,2) + xf(:,k) = real(ifft(fft(x(:,k)) .* H')); + end + xf = xf(1:Norig,:); +else + xf = real(ifft(fft(x') .* H)); + xf = xf(1:Norig); +end diff --git a/field2struct.m b/field2struct.m new file mode 100644 index 0000000..07e6c3c --- /dev/null +++ b/field2struct.m @@ -0,0 +1,39 @@ +function B = field2struct(A,label,target) +%FIELD2STRUCT - Export values from a field as a fields of a new struct +% [B] = field2struct(A,label,target) uses field named label in structure +% A to create a new structure B whose fieldnames are the +% 'A.label' values and values are the corresponding 'A.target' +% +% +% Example +% >> field2struct +% +% See also: + +% Author: K. N'Diaye (kndiaye01gmail.com) +% Copyright (C) 2011 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2011-06-08 Creation +% +% ----------------------------- Script History --------------------------------- +B = []; +for i=1:numel(A) + if isfield(B,getfield(A(i),label)) + warning('field2struct:DuplicatedLabel',sprintf('Duplicated label: %s', getfield(A(i),label))); + end + try + B=setfield(B,getfield(A(i),label), getfield(A(i),target)); + catch ME + switch (ME(end).identifier) + case 'MATLAB:AddField:InvalidFieldName' + warning('field2struct:InvalidFieldName','%s is not a valid field name. It will be discarded', getfield(A(i),label)) + otherwise + rethrow(ME) + end + end +end \ No newline at end of file diff --git a/fieldtriptopo.m b/fieldtriptopo.m new file mode 100644 index 0000000..0faddce --- /dev/null +++ b/fieldtriptopo.m @@ -0,0 +1,162 @@ +function fieldtriptopo(Channel,data) +xyz=getChannelLoc(Channel); +labels={Channel.Name}; +cfg.zlim='absmax'; +cfg.colorbar='yes'; +cfg.showlabels='no'; +cfg.showzlim='yes'; +cfg.fontsize=8; +topoplot(cfg,xyz(:,1),xyz(:,2),data,labels); +return + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% This is a subfunction for topoplotTFR and topoplotER that takes care of +% the actual interpolation and plotting. +% +% It is a modified version from the topoplot function of EEGLAB +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% Copyright (C) Andy Spydell, Colin Humphries & Arnaud Delorme +% CNL / Salk Institute, Aug, 1996 +% +% This program is free software; you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation; either version 2 of the License, or +% (at your option) any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with this program; if not, write to the Free Software +% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +function topoplot(cfg,X,Y,Values,Labels) + +% User Defined Defaults: +MAXCHANS = 256; +INTERPLIMITS = 'head'; % head, electrodes +GRID_SCALE = 67; % 67 in original +CONTOURNUM = 6; +STYLE = 'both'; % both,straight,fill,contour,blank +HCOLOR = [0 0 0]; +ECOLOR = [0 0 0]; +CONTCOLOR = [0 0 0]; +EMARKERSIZE = 8; +EFSIZE = get(0,'DefaultAxesFontSize'); +HLINEWIDTH = 2; +EMARKER = '.'; +SHADING = 'flat'; % flat or interp +INTERPOLATION = 'v4'; + +Ypos = -0.45+0.9*(X - min(X)) /(max(X) - min(X)); +Xpos = -0.45+0.9*(Y - min(Y)) /(max(Y) - min(Y)); + +rmax = .5; + +ha = gca; +cla +hold on + +if ~strcmp(STYLE,'blank') + % find limits for interpolation + if strcmp(INTERPLIMITS,'head') + xmin = min(-.5,min(Xpos)); xmax = max(0.5,max(Xpos)); + ymin = min(-.5,min(Ypos)); ymax = max(0.5,max(Ypos)); + else + xmin = max(-.5,min(Xpos)); xmax = min(0.5,max(Xpos)); + ymin = max(-.5,min(Ypos)); ymax = min(0.5,max(Ypos)); + end + + xi = linspace(xmin,xmax,GRID_SCALE); % x-axis description (row vector) + yi = linspace(ymin,ymax,GRID_SCALE); % y-axis description (row vector) + + [Xi,Yi,Zi] = griddata(Ypos,Xpos,Values,yi',xi,INTERPOLATION); % Interpolate data + + % Take data within head + mask = (sqrt(Xi.^2+Yi.^2) <= rmax); + ii = find(mask == 0); + Zi(ii) = NaN; + + % calculate colormap limits + m = size(colormap,1); + if isstr(cfg.zlim) + if strcmp(cfg.zlim,'absmax') + amin = -max(max(abs(Zi))); + amax = max(max(abs(Zi))); + elseif strcmp(cfg.zlim,'maxmin') + amin = min(min(Zi)); + amax = max(max(Zi)); + end + else + amin = cfg.zlim(1); + amax = cfg.zlim(2); + end + delta = xi(2)-xi(1); % length of grid entry + + % Draw topoplot on head + if strcmp(STYLE,'contour') + contour(Xi,Yi,Zi,CONTOURNUM,'k'); + elseif strcmp(STYLE,'both') + surface(Xi-delta/2,Yi-delta/2,zeros(size(Zi)),Zi,'EdgeColor','none',... + 'FaceColor',SHADING); + contour(Xi,Yi,Zi,CONTOURNUM,'k'); + elseif strcmp(STYLE,'straight') + surface(Xi-delta/2,Yi-delta/2,zeros(size(Zi)),Zi,'EdgeColor','none',... + 'FaceColor',SHADING); + elseif strcmp(STYLE,'fill') + contourf(Xi,Yi,Zi,CONTOURNUM,'k'); + else + error('Invalid style') + end + caxis([amin amax]) % set coloraxis +end + +set(ha,'Xlim',[-rmax*1.3 rmax*1.3],'Ylim',[-rmax*1.3 rmax*1.3]) + +% Define the contours of the head +l = 0:2*pi/100:2*pi; +basex = .18*rmax; +tip = rmax*1.15; base = rmax-.004; +EarX = [.497 .510 .518 .5299 .5419 .54 .547 .532 .510 .489]; +EarY = [.0555 .0775 .0783 .0746 .0555 -.0055 -.0932 -.1313 -.1384 -.1199]; + +% Plot Electrodes +if strcmp(cfg.showlabels,'markers') + hp2 = plot(Ypos,Xpos,EMARKER,'Color',ECOLOR,'markersize',EMARKERSIZE); +elseif strcmp(cfg.showlabels,'yes') + for i = 1:length(Xpos) + text(Ypos(i),Xpos(i),Labels(i),'HorizontalAlignment','center',... + 'VerticalAlignment','middle','Color',ECOLOR,... + 'FontSize',EFSIZE) + end +elseif strcmp(cfg.showlabels,'numbers') + for i = 1:length(Xpos) + text(Ypos(i),Xpos(i),int2str(i),'HorizontalAlignment','center',... + 'VerticalAlignment','middle','Color',ECOLOR,... + 'FontSize',EFSIZE) + end +end + +% Plot Head, Ears, Nose +plot(cos(l).*rmax,sin(l).*rmax,... + 'color',HCOLOR,'Linestyle','-','LineWidth',HLINEWIDTH); +plot([.18*rmax;0;-.18*rmax],[base;tip;base],... + 'Color',HCOLOR,'LineWidth',HLINEWIDTH); + +plot(EarX,EarY,'color',HCOLOR,'LineWidth',HLINEWIDTH) +plot(-EarX,EarY,'color',HCOLOR,'LineWidth',HLINEWIDTH) + +if strcmp(cfg.colorbar,'yes') + colorbar +end + +if strcmp(cfg.showzlim,'yes') + text(0,-0.6,sprintf('Color limits:\n %.2e to %.2e ',amin,amax),'fontsize',cfg.fontsize,'HorizontalAlignment','center') +end + +hold off +axis off diff --git a/fig2pdf.m b/fig2pdf.m new file mode 100644 index 0000000..a11a2be --- /dev/null +++ b/fig2pdf.m @@ -0,0 +1,68 @@ +function [filename] = fig2pdf(varargin) +%FIG2PDF - Save a figure into a PDF file +% fig2pdf() savethe current figure in the current folder using its name +% as filename, if name is empty, filename will be like: fig_N.pdf +% A '_datetime' suffix is added if a file already exists with that name. +% +% fig2pdf(h) save the figure specified by handle h +% +% fig2pdf(h,filename) uses the specified filename (may overwrite it) +% +% Example +% >> fig2pdf +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2009 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2009-02-27 Creation +% +% ----------------------------- Script History --------------------------------- + +if nargin<1 + error('No data!') +elseif nargin==1 || isnumeric(action) + varargin=[{action} varargin ]; + action='init'; +end + +try + varargout={eval(sprintf('action_%s(varargin{:});',action))}; +catch + eval(sprintf('action_%s(varargin{:});',action)); +end + + +function [ha]=action_init(data, varargin) +% ------------------------------------------------------------------------- +% Check OPTIONS and default values when optional arguments are not +% specified +Def_OPTIONS = struct(... + 'option1',[],... + ); +if nargin < 2 + OPTIONS = Def_OPTIONS; +else + if length(varargin)>1 + OPTIONS = cell2struct(varargin(2:2:end),varargin(1:2:end),2); + % struct(varargin{:}) bugs with something like {'string1' 'string2'} in the inputs! + else + OPTIONS = varargin{1}; + end + % Check field names of passed OPTIONS and fill missing ones with default values + DefFieldNames = fieldnames(Def_OPTIONS); + for k = 1:length(DefFieldNames) + if ~isfield(OPTIONS,DefFieldNames{k}) + OPTIONS = setfield(OPTIONS,DefFieldNames{k},getfield(Def_OPTIONS,DefFieldNames{k})); + end + end + clear DefFieldNames +end +clear Def_OPTIONS +% ------------------------------------------------------------------------- \ No newline at end of file diff --git a/figure_call.m b/figure_call.m new file mode 100644 index 0000000..bc1f87d --- /dev/null +++ b/figure_call.m @@ -0,0 +1,62 @@ +function varargout = figure_call(name, focus) +%FIGURE_CALL - One line description goes here. +% [hf] = figure_call(name) creates figure with name if it does not exist +% [hf] = figure_call(name,focus) if focus=1 (default) the figuer get the +% focus (it is then the current window) +% +% Example +% >> figure_call(name,focus) +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2010 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2010-01-21 Creation +% +% ----------------------------- Script History --------------------------------- + +hf = findobj(0,'-depth', 1, 'type', 'figure', 'name',name); +if isempty(hf) + hf=figure('name',name); + set(hf, 'PaperType', 'a4letter'); +end +if nargin<2 || isempty(focus) + focus=1; +end +if focus + figure(hf) +end +if nargout>0 + vaerargout= {hf}; +end +% +% % Example how to adjust your figure properties for +% % publication needs +% s = hf; +% % Select the default font and font size +% % Note: Matlab does internally round the font size +% % to decimal pt values +% set(s, 'DefaultTextFontSize', 10); % [pt] +% set(s, 'DefaultAxesFontSize', 10); % [pt] +% set(s, 'DefaultAxesFontName', 'Times New Roman'); +% set(s, 'DefaultTextFontName', 'Times New Roman'); +% % Select the preferred unit like inches, centimeters, +% % or pixels +% set(s, 'Units', 'centimeters'); +% pos = get(s, 'Position'); +% pos(3) = 8; % Select the width of the figure in [cm] +% pos(4) = 6; % Select the height of the figure in [cm] +% set(s, 'Position', pos); +% set(s, 'PaperType', 'a4letter'); +% % From SVG 1.1. Specification: +% % "1pt" equals "1.25px" +% % "1pc" equals "15px" +% % "1mm" would be "3.543307px" +% % "1cm" equals "35.43307px" +% % "1in" equals "90px" \ No newline at end of file diff --git a/filefinder.m b/filefinder.m new file mode 100644 index 0000000..340ccb6 --- /dev/null +++ b/filefinder.m @@ -0,0 +1,62 @@ +function = filefind(p,wd) +%FILEFIND - Find files using wildcards & recursive search +% [] = filefind.m(input) +% +% Example +% >> filefind.m +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2010 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2010-10-14 Creation +% +% ----------------------------- Script History --------------------------------- + +if nargin<1 + error('No data!') +elseif nargin==1 || isnumeric(action) + varargin=[{action} varargin ]; + action='init'; +end + +try + varargout={eval(sprintf('action_%s(varargin{:});',action))}; +catch + eval(sprintf('action_%s(varargin{:});',action)); +end + + +function [ha]=action_init(data, varargin) +% ------------------------------------------------------------------------- +% Check OPTIONS and default values when optional arguments are not +% specified +Def_OPTIONS = struct(... + 'option1',[],... + ); +if nargin < 2 + OPTIONS = Def_OPTIONS; +else + if length(varargin)>1 + OPTIONS = cell2struct(varargin(2:2:end),varargin(1:2:end),2); + % struct(varargin{:}) bugs with something like {'string1' 'string2'} in the inputs! + else + OPTIONS = varargin{1}; + end + % Check field names of passed OPTIONS and fill missing ones with default values + DefFieldNames = fieldnames(Def_OPTIONS); + for k = 1:length(DefFieldNames) + if ~isfield(OPTIONS,DefFieldNames{k}) + OPTIONS = setfield(OPTIONS,DefFieldNames{k},getfield(Def_OPTIONS,DefFieldNames{k})); + end + end + clear DefFieldNames +end +clear Def_OPTIONS +% ------------------------------------------------------------------------- \ No newline at end of file diff --git a/fileparts2.m b/fileparts2.m new file mode 100644 index 0000000..2b235bc --- /dev/null +++ b/fileparts2.m @@ -0,0 +1,74 @@ +function S = fileparts2(fname,arg, mode) +%FILEPARTS2 - One line description goes here. +% [S] = fileparts2(FILE,ARG) returns a specific part of a filename +% ARG can be specified as text or as a numerical value: +% 'path' [0] (default) +% 'name' [1] +% 'ext' [0.1] +% 'name.ext' [1.1] +% 'shortname' [2] +% 'path\name' [3] +% 'root' [-Inf] +% if ARG is a negative integer, retrieves the n-th folder up. +% [S] = fileparts2(FILE,ARG,MODE) +% if MODE is: +% 'none', retrieves path as given in the FILE argument +% 'absolute' converts output to an absolute path +% 'relative' force output to be a relative path +% +% Example +% >> fileparts2('d:\tmp\test.ext','name') returns 'test' +% >> fileparts2('d:\tmp\test.ext','root') returns 'd:' +% >> fileparts2('d:\tmp\test.ext','root') returns 'd:' +% +% See also: fileparts + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2008 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2008-09-18 Creation +% +% ----------------------------- Script History --------------------------------- +[p,n,e]=fileparts(fname); +if nargin==1 + S = p; + return; +end +switch arg + case {'path' 0} + S = p; + case {'root' -Inf} + S = regexp(p, [filesep '.*\'], 'split'); + i=find(~cellfun('isempty',S)); + S = [repmat(filesep,1,i(1)-1) S{i(1)} filesep]; + case {'shortname' 2} + if ispc + % shortname for windows 95, XP, Vista, etc. + if not(exist(fname,'file')) + error('File not found: %s', fname) + end + [ok,w]=system(sprintf('dir "%s" /x/a-d/-c',fname)); + if ok + error(w); + end + w=strread(w, '%s', 'delimiter',char(10)); + w=strread(w{6}, '%s'); + S = w{4}; + else + warning('Shortname is irrelevant in non-Windows OS.'); + S = [n e]; + end + case {'name',1} + S = n; + case {'ext' .1} + S = e; + case {'path\name' 3} + S= fullfile(p,n); + case {'name.ext' 1.1} + S = [n e]; +end \ No newline at end of file diff --git a/fillarray.m b/fillarray.m new file mode 100644 index 0000000..55be553 --- /dev/null +++ b/fillarray.m @@ -0,0 +1,117 @@ +function Y = fillarray(C,X,S,O) +%FILLARRAY - Fill an array with given values +% [Y] = fillarray(C,X,S) +% Creates an array of size S whose values in position C are X +% C and X should be either numerical arrays or cell lists of the same +% length. Each cell should be of the same length, but X or X{i} may be +% scalar, in which case it is expanded to match C{i} length. If X is +% an array +% [Y] = fillarray(C,[],S) and [Y] = fillarray(C,1,S) +% Fills the array of size S with ones (default value). +% [Y] = fillarray(C,X) +% Fills a vertical array at positions C with values in X. +% The length of Y is the maximum index found in C +% [Y] = fillarray(C) +% Fills a vertical array of size [max(C) 1] with 1's at positions C +% [Y] = fillarray(C,X,S,O) +% Non specified values are O (O=1, if you want ones instead of zeros) +% +% Example +% >> fillarray +% +% See also: sparse + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2006 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2006-01-05 Creation +% KND 2008-01-23 Input size S is no more mandatory +% KND 2010-03-22 Corr. Bug. & parsing inputs +% ----------------------------- Script History --------------------------------- + +if nargin<1 || nargin>4 + error('Wrong number of inputs') +end + +if nargin<2 + X=[]; +end +if ~iscell(C) + C={C}; + if ~iscell(X) && ~isempty(X) + X={X}; + end +end +if ~isempty(X) + nX=numel(X); + nC=numel(C); + if ~iscell(X) + if nX==1 + X = repmat({X},nC,1); + nX=nC; + else + X = num2cell(X); + end + end + + if nC~=nX + error('Sizes of C and X do not match'); + end +end + + +if nargin<3 + S=[]; +end +if nargin<4 + O=0; +end +if isempty(S) + S=[NaN]; +end +if length(S)<2 && ~isnan(S(1)) + S=[S 1]; +end +if all(~isnan(S)) + Y=zeros(S); +else + Y=[]; +end +twopass=1; +if ~isequal(O,0) + twopass=2; +end +while twopass + Y(:)=O; + if isempty(X) + Y([C{:}])=ones(length([C{:}]),1); + else %if max(S)==prod(S) + for i=1:length(C) + if length(X{i})==1 + X{i}=repmat(X{i}, length(C{i}),1); + end + if isvector(C{i}) + Y(C{i})=X{i}; + elseif size(C{i},2) == 2 + if all(isnan(NaN)) + S=[Inf]; + end + for j=1:size(C{i},1) + Y(C{i}(j,1),C{i}(j,2))=X{i}(j); + end + else + error('C must be a 1-D list of absolute indices or subs indices (currently for 2D arrays only)') + end + end + end + twopass = twopass-1; +end + +if all(isnan(S)) %verticalize Y + Y=Y(:); +end diff --git a/findTessellationHandles.m b/findTessellationHandles.m new file mode 100644 index 0000000..e6cbdb4 --- /dev/null +++ b/findTessellationHandles.m @@ -0,0 +1,92 @@ +function [ hp, ha, hf ] = findTessellationHandles( varargin ) +%findTessellationHandles - Find ancestors handles related to a patch surface +% +% [ hp, ha, hf ] = findTessellationHandles +% Try to guess the handles to the patch, its axes and it figure +% +% [ hp, ha, hf ] = findTessellationHandles( h ) +% h may be a (list of) handle to a patch, axes or figure + +h=[]; +hf=[]; +ha=[]; +hp=[]; +if nargin>0 + h=varargin{1}; +end +h=h(ishandle(h)); +if isempty(h) + h=0; + h=[get(h(1), 'CurrentFigure') h ]; + h=[get(h(1), 'CurrentAxes') h ]; + h=[gco h ]; +end +if ~isempty(h) + if isequal(get(get(h(end), 'Parent'), 'Type'), 'axes') + h=[h get(h, 'Parent')]; + end + if isequal(get(get(h(end), 'Parent'), 'Type'), 'figure') + h=[h get(h(end), 'Parent')]; + end +end +for i=1:length(h) + hp=[findobj([h(i)], 'Type', 'patch'); ... + ]; % findobj([h(i)], 'Type', 'surface');]; + if ~isempty(hp) + break + end +end +switch length(hp) + case 0 + hp=[]; + ha=findobj([h], 'Type', 'axes'); + hf=findobj([h], 'Type', 'figure'); + return + case 1 + % nothing to do + otherwise + hp=hp(1); +end + +ha=get(hp, 'Parent'); +hf=get(ha, 'Parent'); + +return + + +% +% OLDIES +% + + + + + +if isequal(get(get(h, 'Parent'), 'Type'), 'axes') + +end + +if ~isempty(hp) + ha=get(hp, 'Parent'); +elseif isequal(htype, 'axes') + ha=h; +end + +if ~isempty(ha) + hf=get(ha, 'Parent'); +elseif isequal(ha, 'figure') + hf=h; +end + +if isempty(hp) + + try, hp=hp(1); end +end + +if isempty(hp) + ha=get(hp, 'Parent'); + if isempty(ha) + ha=findobj(hf, 'type', 'axes'); + end + hf=get(ha, 'Parent'); +end \ No newline at end of file diff --git a/findclosest2.m b/findclosest2.m new file mode 100644 index 0000000..7f81fc7 --- /dev/null +++ b/findclosest2.m @@ -0,0 +1,60 @@ +function [VecInd,minn] = findclosest2(VecGuess, VecRef, mode) +% FINDCLOSEST: Find entries of closest elements between two vectors. +% +% USAGE: VecInd = findclosest(VecGuess, VecRef); +% USAGE: VecInd = findclosest(VecGuess, VecRef, mode); +% +% DESCRIPTION: +% VecGuess is a vector for which one wants to find the closest entries in vector VecRef +% VecInd is the vector of indices pointing at the entries in +% vector VecRef that are the closest to VecWin +% VecInd is of the same length as VecGuess +% 'mode' is a string specifying how the search is made: +% - 'closest': find the closest value (above or below) +% - 'above': find the closest value above the VecGuess +% - 'below': +% In other words, VecRef(VecInd(i)) is the element of VecRef closest to VecGuess(j) +% VecRef and VecGuess do not need to be the same length + +% @============================================================================= +% This software is part of The Brainstorm Toolbox +% http://neuroimage.usc.edu/brainstorm +% +% Copyright (c)2010 Brainstorm by the University of Southern California +% This software is distributed under the terms of the GNU General Public License +% as published by the Free Software Foundation. Further details on the GPL +% license can be found at http://www.gnu.org/copyleft/gpl.html. +% +% FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE +% UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY +% WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF +% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY +% LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE. +% +% For more information type "brainstorm licence" at command prompt. +% =============================================================================@ +% +% Authors: ? +% ----------------------------- Script History --------------------------------- +% +% ------------------------------------------------------------------------------ + +if isvector(VecRef) + if size(VecRef,1) == 1 + VecRef = VecRef'; + end +end +if nargin<3 + mode='closest'; +end +tmp = repmat(VecRef,1,length(VecGuess)); +switch(mode) + case 'closest' + [minn VecInd] = min(abs(repmat(VecGuess(:)',length(VecRef),1) - tmp)); + case 'above' + [minn VecInd] = min(abs(repmat(VecGuess(:)',length(VecRef),1) - tmp)); + case 'below' + [minn VecInd] = min(abs(repmat(VecGuess(:)',length(VecRef),1) - tmp)); +end +VecInd = reshape(VecInd, size(VecGuess)); +minn = reshape(minn, size(VecGuess)); \ No newline at end of file diff --git a/findinstruct.m b/findinstruct.m new file mode 100644 index 0000000..980b77a --- /dev/null +++ b/findinstruct.m @@ -0,0 +1,57 @@ +function y = findinstruct(x,S,fun,varargin) +%FINDINSTRUCT - Finds a value in the fields (and subfields) of a structure +% [y] = findinstruct(x,S) +% Searches in structure S any field equal to x and outputs a cell +% array of subscripted references to the matching elements +% +% [y] = findinstruct(x,S,fun) +% Allows user to specify his/her own function to be tested using +% input x and S content. E.g. 'strmatch(x,S)' +% +% Example +% >> a.b=1;a.c='toto'; +% >> findinstruct(1,a) +% >> findinstruct('tot',a,'strmatch(x,S)') +% +% See also: subsref + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2006 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2006-12-06 Creation +% +% ----------------------------- Script History --------------------------------- +if nargin<3 || isempty(fun) + fun='isequal(x,S)'; +end +y=[]; +if isstruct(S) + f=fieldnames(S); + for j=1:numel(S) + for i=1:length(f) + s=struct('type', '.', 'subs', f{i}); + z=findinstruct(x,subsref(S(j),s),fun,varargin{:}); + if ~isempty(z) + if numel(S)>1 + s=[struct('type', '()', 'subs', {{j}}) s]; + end + for k=1:length(z) + y{end+1}=[s z{k}]; + end + end + end + end +else + try + if eval(fun) + y={[]}; + end + end +end + + diff --git a/findn.m b/findn.m new file mode 100644 index 0000000..4e6c057 --- /dev/null +++ b/findn.m @@ -0,0 +1,33 @@ +function ind=findn(arr); + +%FINDN Find indices of nonzero elements. +% I = FINDN(X) returns the indices of the vector X that are +% non-zero. For example, I = FINDN(A>100), returns the indices +% of A where A is greater than 100. See RELOP. +% +% This is the same as find but works for N-D matrices using +% ind2sub function +% +% It does not return the vectors as the third output arguement +% as in FIND +% +% The returned I has the indices (in actual dimensions) +% +% x(:,:,1) x(:,:,2) x(:,:,3) +% = [ 1 2 3 =[11 12 13 =[21 22 23 +% 4 5 6 14 15 16 24 25 26 +% 7 8 9] 17 18 19] 27 28 29] +% +% I=find(x==25) will return 23 +% but findn(x==25) will return 2,2,3 +% +% Also see find, ind2sub + +% Loren Shure, Mathworks Inc. improved speed on previous version of findn +% by Suresh Joel Mar 3, 2003 + +in=find(arr); +sz=size(arr); +if isempty(in), ind=[]; return; end; +[out{1:ndims(arr)}] = ind2sub(sz,in); +ind = cell2mat(out); \ No newline at end of file diff --git a/finitefun.m b/finitefun.m new file mode 100644 index 0000000..9e88328 --- /dev/null +++ b/finitefun.m @@ -0,0 +1,37 @@ +function [y,nonfinite]=finitefun(x,D,fun,varargin) +%FINITEFUN Apply a function on finite values, discarding NaN, Inf... +% [y,nonfinite]=finitefun(X,D,fun,...) apply (vectorized) function 'fun' +% along dimension D, on the finite values only in an array X +% +% If D > 0 +% Apply function along dimension D, ignoring non-finite values +% If D < 0 +% Do not include complementary dimensions of the data array if any +% nonfinite found along the given dimension (ie. discard the whole +% hyperplan) +% +% Example: +% a = cat(3,magic(5), cumsum(ones(5)));a([3 35])=[ NaN Inf ] +% finitefun(a,1,@mean) +% meannan(a,-2) +% +%See also: meannan() + +nonfinite=~isfinite(x); +dim = abs(D); +x = nd2array(x,dim); +nonfinite(any(nonfinite(:,:),2),:)=1; +if dim < 0 + dim=-dim; + otherdims = setdiff(1:ndims(x),dim); + permdims = [dim otherdims]; + nonfinite=permute(nonfinite,permdims); + nonfinite(any(nonfinite(:,:),2),:)=1; + nonfinite=ipermute(nonfinite,permdims); +end +x(nonfinite)=0; +nonfinite=double(nonfinite); +y = sum(x,dim)./(size(x,dim)-sum(nonfinite,dim)); +if nargin>1 + nonfinite=logical(nonfinite); +end \ No newline at end of file diff --git a/forcecell.m b/forcecell.m new file mode 100644 index 0000000..a600f9b --- /dev/null +++ b/forcecell.m @@ -0,0 +1,10 @@ +function [x]=forcecell(x) +% forcecell - make sure x is cell +% +% Some functions inconsistently return cell or numeric array (even native +% ones as get: get(h, 'Xdata') returns cells when h is an array of handles, +% but the same would returns a double array when h is a single handle... +if ~iscell(x) + x={x}; +end + diff --git a/fread2.m b/fread2.m new file mode 100644 index 0000000..d79f972 --- /dev/null +++ b/fread2.m @@ -0,0 +1,86 @@ +function A = fread2(file,precision,volume,selection,offset) +%FREAD2 - Optimize fread call for reading subsets of N-dimensional data +% A = fread2(FILE,PRECISION,SIZE,SUBS) reads N-dimensional binary data +% from specified file +% Inputs: +% FILE: File name or FID (from FOPEN) +% PRECISION: See help FREAD +% SIZE: a N-element vector equals to the size of the data in the file +% SUBS: a N-element cell array of the list of indices of the elemnts +% to read in the file, may be non-continuous ranges. +% +% Example +% >> fread2('data.bin','float32',[100 3 12],{ [1:50 55 6] [-2] 'all' }) +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2010 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2010-05-12 Creation +% +% ----------------------------- Script History --------------------------------- + +error('not yet') + +switch(precision) + case {'uint8','int8','uchar','schar','char'} + f_bytes = 2; + case {'uint16','int32','short','ushort'} + f_bytes = 2; + case {'float32','single', 'float','uint32','int32','int','uint','long'} + f_bytes = 4; + case {'float64','double','uint64','int64'} + f_bytes = 8; + otherwise + precision = 'float32'; + % error('read_lena:UnknownPrecision','Unknown precision: %s -- Go through the Full monthy', precision); + % return +end +if nargin<5 + offset = 0; +end + +ndim = numel(selection); +%% Bounding box +% Compute the bounding box of the volume of data to read +range = zeros(4,ndim); +f_selection = cell(1,ndim); +for i=1:ndim + range(1:2,i)=[min(selection{i});max(selection{i})]; + f_selection{i} = selection{i}-range(1,i)+1; +end +% Line 3 of 'range' is the length of the bounding box on each dimension +% equivalent to what would be f_volume in the convtion used here +range(3,:)= range(2,:)-range(1,:)+1; +% Line 4 of 'range' is the number of elements outsidethe bounding box +range(4,:) = volume-range(3,:) +volume +%% Segments to read +cumvol = cumprod([1 volume]) +% The first segment to read starts after skipping a whole block of data +f_offset = sum(cumvol(1:end-1).*(range(1,:)-1)) +% To skip between each read +f_skip = sum(cumvol(1:end-1).*(range(4,:))) +% Extent of the segment to read +f_selection = [cumvol(1:end-2).*(range(3,1:end-1)-1)] +f_selection(1) = 1+f_selection(1); +%f_size = (1+cumsum(f_selection(1:end)))./cumprod([1 +%f_selection(1:end-1)+1]) +f_precision = sum(f_selection); + +f_size = [ f_selection(1) f_precision/f_selection(1)] + + +fid = fopen(file, 'rb'); + +fseek(fid,offset + f_offset,'bof'); +A = fread(fid,f_size,[num2str(f_precision) '*' precision], f_skip*f_bytes); +numel(A) +A = reshape(A,range(3,:)); +fclose(fid); diff --git a/fullfiles.m b/fullfiles.m new file mode 100644 index 0000000..02df11a --- /dev/null +++ b/fullfiles.m @@ -0,0 +1,41 @@ +function [f]=fullfiles(varargin) +%fullfiles - Build full filenames from parts (allow multiple path/basename/ext). +% fullfiles(D1, D2, ... , FILES) with D1 being { D1a D1b ...} idem for D2 and FILES +% will (recursively) produce a cell array of full filenames +% { D1a/D2a/FILE1 ; D1a/D2a/FILE2 ; D1a/D2b/FILE1 ; D1a/D2b/FILE2 ; ... } +% +% See: fullfile() + + +% THE FOLLOWING OPTION DOES NOT WORK YET ! + +% fullfiles(D1, D2, ... , FILES, [n1 n2]) will match the n1-th and n2-th +% arguments on a 1-by-1 basis so that: +% >> fullfiles({'a' 'b'}, 'c', {'e' 'f'}, [1 3]) +% 'a/c/e' +% 'b/c/f' +% instead of: +% 'a/c/e' +% 'a/c/f' +% 'b/c/e' +% 'b/c/f' + +if nargin<2 + f=varargin{1}; + if ischar(f) + f={f}; + end + return +end + +d=varargin{1}; +g=fullfiles(varargin{2:end}); +f=[]; +if ischar(d) + d={d}; +end +for i=1:length(d) + for j=1:length(g); + f=[ f; {fullfile(d{i}, g{j})}]; + end +end \ No newline at end of file diff --git a/geod_cluster.m b/geod_cluster.m new file mode 100644 index 0000000..1f24002 --- /dev/null +++ b/geod_cluster.m @@ -0,0 +1,218 @@ +function [GD,TC, GDbis, PCL]=geod_cluster(VC, Coord, SS) + +% function [GD, TC, GDbis, PCL] = geod_cluster(VC , Coord, SS ) +% la function geodbis est la fonction geod qui +% construit - à partir du vecteur de connectivité VC et du +% tableau Coord des coordonnées de chacune des sources (c'est le FV.vertices) - les distances géodésiques +% de chacune des sources aux graines de la liste SS. +% +% +% 'GD' sera le tableau des distances géodésiques de chacune de nos sources +% à chacune des graines +% 'TC' sera le tableau donnant les no de chacune des sur-parcelles +% auxquelles appartiennent chacune des sources +% 'GDbis' est la liste des distances (GDbis(s,1)) de chacune des sources s a sa graine la +% plus proche (GDbis(s,2)) +% PCL est la liste des sources de chacune des parcelles + + +K=size(SS,1); %nombre de graines + +nv= size(Coord, 1); % nombre de sources +TC = cell(nv,1); % la 'cell' TC sera la liste des sur-parcelles dont + % fait partie chaque source, initialisée au vide. + +Vis = zeros(nv,1); % Vis[i] indique si la source i a déjà été visitée + +PH = cell(K,1); % PH sera une structure cell de taille K x dmax x Nmax qui sera le + % tableau de "piles hierarchiques" de G. Flandin. + % Attention: dans la colonne d de PH(k) on a les + % sources à la distance d-1 (en mm) + +infty = 10000; + +GD = infty*ones(K,nv); % tableau des distances géodésiques de la source i à + % la graine k (en mm). Il est initialisé à infty, qui + % est pour nous la distance infinie (on pourrait + % plutot prendre un 'sparse' mais bon...) + +GDbis = ones(nv,2); % nous donne pour chaque source s, sa graine la plus + % proche GDbis(s,2), et la distance a cette graine GDbis(s,1) + + +PCL = cell(K,1); % liste de chacune des parcelles, ie pour la graine k les sources associees + + +GDbis(:,1)=infty*GDbis(:,1); +GDbis(:,2)=0*GDbis(:,2); + +S=SS; + +% on initialise les valeurs de nos tableaux pour nos graines + +for k=1:K, + S(k,2)=0; + TC(S(k,1))={[k]}; + GD(k,S(k,1))=0; + GDbis(S(k,1),1)=0; + GDbis(S(k,1),2)=k; + Vis(S(k,1))=1; + PH(k,1)={[S(k,1)]}; +end + + + + + +nsv=K ; % nsv indique le nombre de sources visitées, initialisé à K car + % les graines ont déjà été visitées + +d=0 ; % d est la distance à laquelle on se trouve dans nos piles hierarchiques + +while nsvS(k,2)), + S(k,2)=dist; + PH(k,dist+1)={[V]}; + else + PH(k,dist+1)={[PH{k,dist+1},V]}; + end + + + if dist20 + PCL(GDbis(s,2))={[PCL{GDbis(s,2)},s]}; + end +end + +while Nbsourcesnonvisitees>0 + +for s=1:nv % c'est juste une boucle rajoutee car je suis tombe + % sur des sources qui 'boguaient' systematiquement + % sans comprendre pourquoi. Donc on attribue aux sources + % pas suffisamment visitees la mm parcelle que son premier + % voisin. L'indicateur 'nsf' dans le reste du programme + l=1; % sert ainsi a eviter les boucles sans fin dues + % a ces sources a probleme + + if GDbis(s,2)==0 + % s + while (GDbis(s,2)==0 & (l0 + for i=1:size(VC{s},2) + if GDbis(VC{s}(i),2)==0 + GDbis(VC{s}(i),2)=GDbis(s,2); + PCL(GDbis(s,2))={[PCL{GDbis(s,2)},VC{s}(i)]}; + end + end + PCL(GDbis(s,2))={[PCL{GDbis(s,2)},s]}; + end + end + +end + +I=find(GDbis(:,2)==0); +Nbsourcesnonvisitees=size(I,1); + +end + + diff --git a/geod_cluster2.m b/geod_cluster2.m new file mode 100644 index 0000000..d349de2 --- /dev/null +++ b/geod_cluster2.m @@ -0,0 +1,218 @@ +function [GD,TC, GDbis, PCL]=geod_cluster2(VC, Coord, SS, varargin) + +% function [GD, TC, GDbis, PCL] = geod_cluster2(VC , Coord, SS [, options]) +% la function geodbis est la fonction geod qui +% construit - à partir du vecteur de connectivité VC et du +% tableau Coord des coordonnées de chacune des sources (c'est le FV.vertices) - les distances géodésiques +% de chacune des sources aux graines de la liste SS. +% +% +% 'GD' sera le tableau des distances géodésiques de chacune de nos sources +% à chacune des graines +% 'TC' sera le tableau donnant les no de chacune des sur-parcelles +% auxquelles appartiennent chacune des sources +% 'GDbis' est la liste des distances (GDbis(s,1)) de chacune des sources s a sa graine la +% plus proche (GDbis(s,2)) +% PCL est la liste des sources de chacune des parcelles + +debug=1; + +K=size(SS,1); %nombre de graines + +nv= size(Coord, 1); % nombre de sources +TC = cell(nv,1); % la 'cell' TC sera la liste des sur-parcelles dont + % fait partie chaque source, initialisée au vide. + +Vis = zeros(nv,1); % Vis[i] indique si la source i a déjà été visitée + +PH = cell(K,1); % PH sera une structure cell de taille K x dmax x Nmax qui sera le + % tableau de "piles hierarchiques" de G. Flandin. + % Attention: dans la colonne d de PH(k) on a les + % sources à la distance d-1 (en mm) + +infty = 10000; + +GD = infty*ones(K,nv); % tableau des distances géodésiques de la source i à + % la graine k (en mm). Il est initialisé à infty, qui + % est pour nous la distance infinie (on pourrait + % plutot prendre un 'sparse' mais bon...) + +GDbis = ones(nv,2); % nous donne pour chaque source s, sa graine la plus + % proche GDbis(s,2), et la distance a cette graine GDbis(s,1) + + +PCL = cell(K,1); % liste de chacune des parcelles, ie pour la graine k les sources associees + + +GDbis(:,1)=infty*GDbis(:,1); +GDbis(:,2)=0*GDbis(:,2); + +S=SS; + +% on initialise les valeurs de nos tableaux pour nos graines + +for k=1:K, + S(k,2)=0; + TC(S(k,1))={[k]}; + GD(k,S(k,1))=0; + GDbis(S(k,1),1)=0; + GDbis(S(k,1),2)=k; + Vis(S(k,1))=1; + PH(k,1)={[S(k,1)]}; +end + + + + + +nsv=K ; % nsv indique le nombre de sources visitées, initialisé à K car + % les graines ont déjà été visitées + +d=0 ; % d est la distance à laquelle on se trouve dans nos piles hierarchiques + +while nsvS(k,2)), + S(k,2)=dist; + PH(k,dist+1)={[V]}; + else + PH(k,dist+1)={[PH{k,dist+1},V]}; + end + + + if dist20 + PCL(GDbis(s,2))={[PCL{GDbis(s,2)},s]}; + end +end + +while Nbsourcesnonvisitees>0 + +for s=1:nv % c'est juste une boucle rajoutee car je suis tombe + % sur des sources qui 'boguaient' systematiquement + % sans comprendre pourquoi. Donc on attribue aux sources + % pas suffisamment visitees la mm parcelle que son premier + % voisin. L'indicateur 'nsf' dans le reste du programme + l=1; % sert ainsi a eviter les boucles sans fin dues + % a ces sources a probleme + + if GDbis(s,2)==0 + % s + while (GDbis(s,2)==0 & (l0 + for i=1:size(VC{s},2) + if GDbis(VC{s}(i),2)==0 + GDbis(VC{s}(i),2)=GDbis(s,2); + PCL(GDbis(s,2))={[PCL{GDbis(s,2)},VC{s}(i)]}; + end + end + PCL(GDbis(s,2))={[PCL{GDbis(s,2)},s]}; + end + end + +end + +I=find(GDbis(:,2)==0); +Nbsourcesnonvisitees=size(I,1); + +end + + diff --git a/getfield2.m b/getfield2.m new file mode 100644 index 0000000..d51aafd --- /dev/null +++ b/getfield2.m @@ -0,0 +1,117 @@ +function [f,index,a] = getfield2(s,varargin) +%GETFIELD2 Get structure field contents (even for array). +% F = GETFIELD2(S,'field') returns the contents of the specified +% field. It expands the native GETFIELD to structure arrays. +% GETFIELD2(F,'field) is equivalent to the syntax F = S.field when S is a +% 1-by-1 structure. If S is NOT a 1-by-1 structure, the output is a cell +% array with the same size as S. +% +% F = GETFIELD2(S,{i,j},'field',{k}) is equivalent to the syntax +% F = S(i,j).field(k). +% +% F = GETFIELD2(S,N) retrieves the n-th field (to be used with caution!) +% See: orderfields() +% +% [F,I,A] = GETFIELD2(S,...) may output: +% I: vector of indices of S elements which had the requested field +% A: a struct array equal to the cell array [F{:}] +% +% See: GETFIELD + +% Check for sufficient inputs +if (isempty(varargin)) + error('MATLAB:GETFIELD2:DeprecatedFunction:InsufficientInputs','Not enough input arguments.') +end + +index = varargin{1}; +if iscell(s) + warning('MATLAB:GETFIELD2:Cell arrays of struct may not be supported in the future'); + sz=size(s); + s = [s{:}]; + [f,index]=getfield2(s,varargin{:}); + if numel(f) == prod(sz) + f = reshape(f,sz); + end +elseif (length(varargin)==1 && isstr(index)) + % The most common case + if any(index=='.') + [f1,f2]=strtok(index,'.'); + % strtok keeps delimiter at the beginning of f2 + % in case f1 is addressing something like : x.f(n) + paren_f1 = regexp(f1, '\(.*\)'); + if isempty(paren_f1) + if iscell(s) + + else + s = [s.(deblank(f1))]; + end + else + s = [s.(deblank(f1(1:paren_f1-1)))]; + s = eval(['s' f1(paren_f1:end)]); + end + if nargout<3 + [f,index]=getfield2(s,f2(2:end)); + else + [f,index,a]=getfield2(s,f2(2:end)); + end + return + else + if numel(s)==1 + f = s.(deblank(index)); + else + sf=size(s); + f = {s.(deblank(index))}; + f=reshape(f, sf); + end + %warning('MATLAB:GETFIELD:DeprecatedFunction:SingleInput', ... + % sprintf(['GETFIELD is deprecated. Please use: \n' ... + % 's.(fieldname) instead of getfield(s,fieldname)'])); + + end + + +else + f = s; + %warnflag = false; + for i = 1:length(varargin) + index = varargin{i}; + if (isa(index, 'cell')) + f = f(index{:}); + elseif isstr(index) + if length(f)==1 + f = f.(deblank(index)); % deblank field name + else + sf=size(f); + f = {f.(deblank(index))}; + f=reshape(f, sf); + end + %if (~warnflag) + %warning('MATLAB:GETFIELD:DeprecatedFunction:MultiInput', ... + % sprintf(['GETFIELD is deprecated. Please use: \n' ... + % 's(i,j).(fieldname)(k) instead of getfield(s,{i,j},fieldname,{k}) OR \n' ... + % 's(i,j).(field1).(field2) instead of getfield(s,{i,j},field1,field2)'])); + %warnflag = true; + %end + elseif isnumeric(index) + if numel(index)>1 + error('Only one field at a time!') + end + fn=fieldnames(s); + index=[fn{index}]; + f = f.(deblank(index)); + else + error('MATLAB:GETFIELD:DeprecatedFunction:InvalidType', 'Inputs must be either cell arrays or strings.'); + end + end +end +if nargout<3 + return +end +a = [f{:}]; +if numel(a) == numel(f) + a=reshape(a,size(f)); +else + warning('The requested field was absent in some elements of the struct array') +end +return + diff --git a/gfp.m b/gfp.m new file mode 100644 index 0000000..4b5336c --- /dev/null +++ b/gfp.m @@ -0,0 +1,6 @@ +function [g]=gfp(F) +% gfp - Global Field Power of data +% Computes global field power (ie. root-mean-square) of data +% See: rootmeansquare() +g=rootmeansquare(F); + diff --git a/globaldissimilarity.m b/globaldissimilarity.m new file mode 100644 index 0000000..874a8c6 --- /dev/null +++ b/globaldissimilarity.m @@ -0,0 +1,29 @@ +function [gd]=globaldissimilarity(F1,F2); +%globalfieldpower() - compute global dissimilarity between two fields +% +% [gd]=globaldissimilarity(F1,F2) +% F1 and F2 are two [N (N2=N1) channels]x[T samples] fields +% +% Defined in Lehman & Skandries, 1980 as: +% gd=rms(F1/rms(F1) - F2/rms(F2)); +% +% Typical use: +% >> g=globaldissimilarityd(F(:,1:end-1), F(:,2:end)) +% will give you maxima when the field abruptly change. +% +% See also: rootmeansquare() +% +% Ref: +% Lehmann D (1987): Principles of spatial analysis. In: Gevins +% AS , R?mond A , editors. Methods of analysis of brain electrical +% and magnetic signals. EEG handbook. Vol. 1. Amsterdam: Elsevier. +% +% Brandeis D, Lehmann D. Event-related potentials of the brain +% and cognitive processes: approaches and +% applications. Neuropsychologia 1986; 24: 151-168). + +gd=F1./repmat(std(F1,1,1),[ size(F1,1) 1 ])- ... + F2./repmat(std(F2,1,1),[ size(F2,1) 1 ]); +gd=std(gd,1,1); + + diff --git a/gower.m b/gower.m new file mode 100644 index 0000000..e86a16a --- /dev/null +++ b/gower.m @@ -0,0 +1,23 @@ +function Y = gower(X) +%GOWER - Gower Centered Matrix +% [Y] = gower(X) +% Y(i,j)= X(i,j) - mean(X(:,j)) - mean(X(i,:)) + mean(X(:)) + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2006 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2006-02-14 Creation +% +% ----------------------------- Script History --------------------------------- + +n=size(X); +if ~isequal(n(1), n(2:end)) + error('Squared matrix needed'); +end +n=n(1); +Y=(eye(n) - ones(n)./n)* X *(eye(n) - ones(n).he/n); diff --git a/grayhot.m b/grayhot.m new file mode 100644 index 0000000..fed7bc3 --- /dev/null +++ b/grayhot.m @@ -0,0 +1,9 @@ +function [c]=grayhot(n) +%GRAYHOT - Gray-hot colormap +% grayhot(n) returns a n-by-3 matrix +% grayhot by itself is the same length as the current colormap +if nargin<1 + n=length(colormap); +end +b=([[n:-1:1]/n]'*[1 1 1]).^(300); +c=hot(n).*(1-b*.6)+ (b*.6); \ No newline at end of file diff --git a/grayish.m b/grayish.m new file mode 100644 index 0000000..0d7e0ed --- /dev/null +++ b/grayish.m @@ -0,0 +1,37 @@ +function [cmap]=grayish(cmap,p,graycolor) +%GRAYISH - Make a colormap more gray +% [cmap2]=grayish(cmap,p,graycolor) +% Make the colors in cmap (default: current color map) to go from a +% gray color (default: .6 .6 .6) to the hues already present. The amount +% of gray-ing is given by p (default: p=1/3). Greater p make the colormap +% more gray. +% +if nargin<1 + cmap=colormap; +end +if nargin<2 + p=1/3; +end +if p==0 + return +end +if nargin<3 + graycolor=[.6 .6 .6]; +end +n=abs(p); +if n<=1 + n=round(length(cmap)*n); +end +x=linspace(0,1,n+1)'*[1 1 1]; +x(end,:)=[]; +%x=x.^(1 - min(1-sqrt(eps),abs(beta))); +if p>0 + cmap(1:n,:)=repmat(graycolor,n,1).*(1-x)+cmap(1:n,:).*x; +else + nc=length(cmap); + cmap(nc+1-[1:n],:)=repmat(graycolor,n,1).*flipud(x)+cmap(nc+1-[1:n],:).*x; +end + +return +b=([[n:-1:1]/n]'*[1 1 1]).^(300); +c=hot(n).*(1-b*.6)+ (b*.6); \ No newline at end of file diff --git a/greyish.m b/greyish.m new file mode 100644 index 0000000..f2b4633 --- /dev/null +++ b/greyish.m @@ -0,0 +1,75 @@ +function [cmap]=greyish(cmap,p,greycolor,style) +%GREYISH - Make a colormap more grey +% [cmap2]=greyish(cmap,p,greycolor) +% Make the colors in cmap (default: current color map) to go from a +% grey color (default: .6 .6 .6) to the hues already present. The amount +% of grey-ing is given by p (default: p=1/3). Greater p make the colormap +% more grey. +% +% [cmap2]=greyish(cmap,p,greycolor,style) +% style = 'bottom'(default) 'middle' 'top' + +if nargin<1 + cmap=colormap; +end +if nargin<2 || isempty(p) + p=1/3; +end +if p==0 + return +end +if nargin<3 || isempty(greycolor) + greycolor=[.6 .6 .6]; +end +if nargin<4 + style=0; +end +if ischar(style) + switch(style) + case 'bottom', style = 0 ; + case 'top', style = 1; + case 'middle', style = 0.5 ; + end +end + +if p<0 + %in earlier versions, negative p's put the grey at the top + p=-p; + style=1-style; +end +nc = size(cmap,1); +if p<=1 + n=round(nc*p); +else + n=p; +end +if style == .5 + n=(n/2); + x=linspace(0,1,ceil(n)+1)'*[1 1 1]; + x(end,:)=[]; + if size(x,1)<=n + x=[flipud(x); 0 0 0; x(2:end,:)]; + else + x=[flipud(x);x(2:end,:)]; + end +else + x=linspace(0,1,n+1)'*[1 1 1]; + x(end,:)=[]; +end +%x=x.^(1 - min(1-sqrt(eps),abs(beta))); + +if style == 0 && p > 0 + cmap(1:n,:)=repmat(greycolor,n,1).*(1-x)+cmap(1:n,:).*x; +elseif (style ==1 && p > 0) || (style == 0 && p < 0) + nc=length(cmap); + cmap(nc+1-[1:n],:)=repmat(greycolor,n,1).*flipud(x)+cmap(nc+1-[1:n],:).*x; +elseif style==.5 + i = nc*style+ceil([-(n):(n-1)]); + i=i(i>0 & i<=nc); + cmap(i,:)=repmat(greycolor,length(i),1).*(1-x)+cmap(i,:).*x; +end + + +return +b=([[n:-1:1]/n]'*[1 1 1]).^(300); +c=hot(n).*(1-b*.6)+ (b*.6); \ No newline at end of file diff --git a/groupfun.m b/groupfun.m new file mode 100644 index 0000000..c183c75 --- /dev/null +++ b/groupfun.m @@ -0,0 +1,85 @@ +function [R,uG,XG,n] = groupfun(X,G,varargin) +%GROUPFUN - Perform computation according grouping variables +% [R,uG] = groupfun(X,G,fun) +% e.g. groupfun(randn(100,1),round(rand(100,1)),'mean') +% e.g. +% +% [R,uG,XG,n] = ... also outputs: +% uG: the list of the unique values for groups +% XG: a cell array of X values split into groups +% n: number of matches for each group +% +% Example +% >> groupfun +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2009 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2009-11-19 Creation +% +% ----------------------------- Script History --------------------------------- +s=size(X); +if size(G,1) ~= s(1) + error('The number of line of X must match the number of lines in G') +end +if prod(s)==max(s) & numel(G)==max(s) & s(1)==1 + % Transpose + G=G'; + X=X'; + s=size(X); +end + if nargin<3 + %default: mean + varargin = {'mean' 1}; +end +[g,uG] = unicize_groups(G); +ng = size(uG,1); +n=zeros(ng,1); +R=[]; +for i=1:ng + if isnan(uG(i)) + r=X(g==i,:); + else + r = feval(varargin{1},X(g==i,:),varargin{2:end}); + if numel(r) < sum(s(2:end)) % && numel(r)~= prod(size(r)) [this was obviously true!] + error('Wrong output of function %s (maybe 1-line vector issue with sum and the like)', varargin{1}); + end + end + R(i,:) = r; + if nargout>2 || nargout==0 + n(i,1) = sum(g==i); + s(1)= n(i); + XG{i,1} = reshape(X(g==i,:),s); + end +end +s(1)=ng; +R=reshape(R,s); +if nargout==0 +<<<<<<< .mine + disp([ 'Grouping by ' inputname(2) ':']); + disp(uG) + disp(n) +======= + disp('Grouping: Result'); + for i=1:ng + disp([ sprintf2(uG(i)) ' (n=' sprintf2(n(i)) '): ' sprintf2(R(i,:)) ]); + end +>>>>>>> .r684 + if any(isnan(uG)) + disp('For NaN''s:') + disp(feval(varargin{1},X(isnan(uG(g)),:),varargin{2:end})); + end +end +return + +% Unicize groups +function [g,uG] = unicize_groups(G) +[uG,ignore,g] = unique(G, 'rows'); +return \ No newline at end of file diff --git a/groupfun.m.mine b/groupfun.m.mine new file mode 100644 index 0000000..3c81731 --- /dev/null +++ b/groupfun.m.mine @@ -0,0 +1,78 @@ +function [R,uG,XG,n] = groupfun(X,G,varargin) +%GROUPFUN - Perform computation according grouping variables +% [R,uG] = groupfun(X,G,fun) +% e.g. groupfun(randn(100,1),round(rand(100,1)),'mean') +% e.g. +% +% [R,uG,XG,n] = ... also outputs: +% uG: the list of the unique values for groups +% XG: a cell array of X values split into groups +% n: number of matches for each group +% +% Example +% >> groupfun +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2009 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2009-11-19 Creation +% +% ----------------------------- Script History --------------------------------- +s=size(X); +if size(G,1) ~= s(1) + error('The number of line of X must match the number of lines in G') +end +if prod(s)==max(s) & numel(G)==max(s) & s(1)==1 + % Transpose + G=G'; + X=X'; + s=size(X); +end + if nargin<3 + %default: mean + varargin = {'mean' 1}; +end +[g,uG] = unicize_groups(G); +ng = size(uG,1); +n=zeros(ng,1); +R=[]; +for i=1:ng + if isnan(uG(i)) + r=X(g==i,:); + else + r = feval(varargin{1},X(g==i,:),varargin{2:end}); + if numel(r) < sum(s(2:end)) % && numel(r)~= prod(size(r)) [this was obviously true!] + error('Wrong output of function %s (maybe 1-line vector issue with sum and the like)', varargin{1}); + end + end + R(i,:) = r; + if nargout>2 || nargout==0 + n(i,1) = sum(g==i); + s(1)= n(i); + XG{i,1} = reshape(X(g==i,:),s); + end +end +s(1)=ng; +R=reshape(R,s); +if nargout==0 + disp([ 'Grouping by ' inputname(2) ':']); + disp(uG) + disp(n) + if any(isnan(uG)) + disp('For NaN''s:') + disp(feval(varargin{1},X(isnan(uG(g)),:),varargin{2:end})); + end +end +return + +% Unicize groups +function [g,uG] = unicize_groups(G) +[uG,ignore,g] = unique(G, 'rows'); +return \ No newline at end of file diff --git a/groupfun.m.r565 b/groupfun.m.r565 new file mode 100644 index 0000000..1be30e5 --- /dev/null +++ b/groupfun.m.r565 @@ -0,0 +1,74 @@ +function [R,uG,XG,n] = groupfun(X,G,varargin) +%GROUPFUN - Perform computation according grouping variables +% [R,uG] = groupfun(X,G,fun) +% e.g. groupfun(randn(100,1),round(rand(100,1)),'mean') +% e.g. +% +% [R,uG,XG,n] = ... also outputs: +% uG: the list of the unique values for groups +% XG: a cell array of X values split into groups +% n: number of matches for each group +% +% Example +% >> groupfun +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2009 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2009-11-19 Creation +% +% ----------------------------- Script History --------------------------------- +s=size(X); +if size(G,1) ~= s(1) + error('The number of line of X must match the number of lines in G') +end +if prod(s)==max(s) & numel(G)==max(s) & s(1)==1 + % Transpose + G=G'; + X=X'; + s=size(X); +end + if nargin<3 + %default: mean + varargin = {'mean' 1}; +end +[g,uG] = unicize_groups(G); +ng = length(uG); +n=zeros(ng,1); +R=[]; +for i=1:ng + r = feval(varargin{1},X(g==i,:),varargin{2:end}); + if numel(r) < sum(s(2:end)) % && numel(r)~= prod(size(r)) [this was obviously true!] + error('Wrong output of function %s (maybe 1-line vector issue with sum and the like)', varargin{1}); + end + R(i,:) = r; + if nargout>2 || nargout==0 + n(i,1) = sum(g==i); + s(1)= n(i); + XG{i,1} = reshape(X(g==i,:),s); + end +end +s(1)=ng; +R=reshape(R,s); +if nargout==0 + disp('Grouping:'); + disp(uG) + disp(n) + if any(isnan(uG)) + disp('For NaN''s:') + disp(feval(varargin{1},X(isnan(uG(g)),:),varargin{2:end})); + end +end +return + +% Unicize groups +function [g,uG] = unicize_groups(G) +[uG,ignore,g] = unique(G, 'rows'); +return \ No newline at end of file diff --git a/groupfun.m.r684 b/groupfun.m.r684 new file mode 100644 index 0000000..8eecda9 --- /dev/null +++ b/groupfun.m.r684 @@ -0,0 +1,75 @@ +function [R,uG,XG,n] = groupfun(X,G,varargin) +%GROUPFUN - Perform computation according grouping variables +% [R,uG] = groupfun(X,G,fun) +% e.g. groupfun(randn(100,1),round(rand(100,1)),'mean') +% e.g. +% +% [R,uG,XG,n] = ... also outputs: +% uG: the list of the unique values for groups +% XG: a cell array of X values split into groups +% n: number of matches for each group +% +% Example +% >> groupfun +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2009 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2009-11-19 Creation +% +% ----------------------------- Script History --------------------------------- +s=size(X); +if size(G,1) ~= s(1) + error('The number of line of X must match the number of lines in G') +end +if prod(s)==max(s) & numel(G)==max(s) & s(1)==1 + % Transpose + G=G'; + X=X'; + s=size(X); +end + if nargin<3 + %default: mean + varargin = {'mean' 1}; +end +[g,uG] = unicize_groups(G); +ng = length(uG); +n=zeros(ng,1); +R=[]; +for i=1:ng + r = feval(varargin{1},X(g==i,:),varargin{2:end}); + if numel(r) < sum(s(2:end)) % && numel(r)~= prod(size(r)) [this was obviously true!] + error('Wrong output of function %s (maybe 1-line vector issue with sum and the like)', varargin{1}); + end + R(i,:) = r; + if nargout>2 || nargout==0 + n(i,1) = sum(g==i); + s(1)= n(i); + XG{i,1} = reshape(X(g==i,:),s); + end +end +s(1)=ng; +R=reshape(R,s); +if nargout==0 + disp('Grouping: Result'); + for i=1:ng + disp([ sprintf2(uG(i)) ' (n=' sprintf2(n(i)) '): ' sprintf2(R(i,:)) ]); + end + if any(isnan(uG)) + disp('For NaN''s:') + disp(feval(varargin{1},X(isnan(uG(g)),:),varargin{2:end})); + end +end +return + +% Unicize groups +function [g,uG] = unicize_groups(G) +[uG,ignore,g] = unique(G, 'rows'); +return \ No newline at end of file diff --git a/hash.m b/hash.m new file mode 100644 index 0000000..bfc4450 --- /dev/null +++ b/hash.m @@ -0,0 +1,79 @@ +function h = hash(inp,meth) +% HASH - Convert an input variable into a message digest using any of +% several common hash algorithms +% +% USAGE: h = hash(inp,'meth') +% +% inp = input variable, of any of the following classes: +% char, uint8, logical, double, single, int8, uint8, +% int16, uint16, int32, uint32, int64, uint64 +% h = hash digest output, in hexadecimal notation +% meth = hash algorithm, which is one of the following: +% MD2, MD5, SHA-1, SHA-256, SHA-384, or SHA-512 +% +% NOTES: (1) If the input is a string or uint8 variable, it is hashed +% as usual for a byte stream. Other classes are converted into +% their byte-stream values. In other words, the hash of the +% following will be identical: +% 'abc' +% uint8('abc') +% char([97 98 99]) +% The hash of the follwing will be different from the above, +% because class "double" uses eight byte elements: +% double('abc') +% [97 98 99] +% You can avoid this issue by making sure that your inputs +% are strings or uint8 arrays. +% (2) The name of the hash algorithm may be specified in lowercase +% and/or without the hyphen, if desired. For example, +% h=hash('my text to hash','sha256'); +% (3) Carefully tested, but no warranty. Use at your own risk. +% (4) Michael Kleder, Nov 2005 +% +% EXAMPLE: +% +% algs={'MD2','MD5','SHA-1','SHA-256','SHA-384','SHA-512'}; +% for n=1:6 +% h=hash('my sample text',algs{n}); +% disp([algs{n} ' (' num2str(length(h)*4) ' bits):']) +% disp(h) +% end + +inp=inp(:); +% convert strings and logicals into uint8 format +if ischar(inp) || islogical(inp) + inp=uint8(inp); +else % convert everything else into uint8 format without loss of data + inp=typecast(inp,'uint8'); +end + +% verify hash method, with some syntactical forgiveness: +meth=upper(meth); +switch meth + case 'SHA1' + meth='SHA-1'; + case 'SHA256' + meth='SHA-256'; + case 'SHA384' + meth='SHA-384'; + case 'SHA512' + meth='SHA-512'; + otherwise +end +algs={'MD2','MD5','SHA-1','SHA-256','SHA-384','SHA-512'}; +if isempty(strmatch(meth,algs,'exact')) + error(['Hash algorithm must be ' ... + 'MD2, MD5, SHA-1, SHA-256, SHA-384, or SHA-512']); +end + +% create hash +x=java.security.MessageDigest.getInstance(meth); +x.update(inp); +h=typecast(x.digest,'uint8'); +h=dec2hex(h)'; +if(size(h,1))==1 % remote possibility: all hash bytes < 128, so pad: + h=[repmat('0',[1 size(h,2)]);h]; +end +h=lower(h(:)'); +clear x +return \ No newline at end of file diff --git a/headplot2.m b/headplot2.m new file mode 100644 index 0000000..1b0bf2a --- /dev/null +++ b/headplot2.m @@ -0,0 +1,797 @@ +% headplot() - plot a spherically-splined EEG field map on a semi-realistic +% 3-D head model. Rotate head using left mouse button. +% Example: +% >> headplot example - show an example spherical 'eloc_angles' file +% >> headplot cartesian - show an example cartesian 'eloc_angles' file +% +% Usage (do once): +% >> headplot('setup',['eloc_angles'],['splinefile'],['comment'],['type']) +% +% Inputs: +% 'eloc_angles' - file of electrode locations in spherical (or cartesian) coords. +% cf. functions: cart2topo() and topo2sph() +% 'splinefile' - name of spline file to save splining info into +% 'comment' - optional string vector containing info for spline file +% 'type' - type of electrode location file ('cartesian' or default +% 'spherical') +% +% General usage: +% >> headplot(values,'spline_file','Param','Value',...) +% +% Inputs: +% values - vector containing value at each electrode position +% 'spline_file' - spline filename computed and saved by running 'setup' +% +% Optional Parameters: +% 'title' - Plot title {default none} +% 'electrodes' - 'on'|'off' -> show electrode positions {default 'on'} +% 'labels' - 2 -> plot stored electrode labels; +% 1 -> plot channel numbers; 0 -> no labels {default} +% 'cbar' - 0 -> Plot colorbar {default: no colorbar} +% H -> Colorbar axis handle (e.g., choose location) +% 'view' - Camera viewpoint in deg. [azimuth elevation] +% 'back'|'b'=[ 0 30]; 'front'|'f'=[180 30] +% 'left'|'l'=[-90 30]; 'right'|'r'=[ 90 30]; +% 'frontleft'|'bl','backright'|'br', etc., +% 'top'=[0 90] {default [143 18]} +% 'maplimits' - 'absmax' -> make limits +/- the absolute-max +% 'maxmin' -> scale to data range +% [min,max] -> user-definined values +% {default = 'absmax'; red +, blue -, green 0} +% 'lights' - (3,N) matrix whose rows give x,y,z pos. of +% N lights {default: 4 lights at corners} +% 'lighting' - 'off' = show wire frame {default 'on'} +% 'colormap' - 3-column colormap matrix {default jet(64)} +% 'verbose' - 'off' -> no msgs, no rotate3d {default 'on'} +% +% Note: if an error is generated, headplot may close the current figure +% +% Authors: Colin Humphries & Scott Makeig, SCCN/INC/UCSD, La Jolla, 1998 + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Copyright (C) Colin Humphries and Scott Makeig, CNL / Salk Institute, Feb. 1998 +% +% Spherical spline method: Perrin et al. (1989) Electroenceph clin Neurophys +% +% This program is free software; you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation; either version 2 of the License, or +% (at your option) any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with this program; if not, write to the Free Software +% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +% $Log: headplot.m,v $ +% Revision 1.17 2003/08/02 00:24:10 scott +% nothing +% +% Revision 1.16 2002/11/26 19:42:24 arno +% /2 to sqrt in extentricity calculation (thanks to Tyler Lorig) +% +% Revision 1.15 2002/11/19 20:08:28 arno +% allowing ascii and binary read of mhead +% +% Revision 1.14 2002/11/19 19:45:09 arno +% back to original +% +% Revision 1.13 2002/11/19 19:34:00 arno +% mhead.dat -> mhead +% +% Revision 1.12 2002/11/19 19:30:37 arno +% mhead -> mhead.dat +% +% Revision 1.11 2002/11/19 19:29:59 arno +% mhead.mat -> mhead for windows compatibility +% +% Revision 1.10 2002/10/23 19:21:24 arno +% fixing division error +% +% Revision 1.9 2002/10/23 19:01:51 arno +% nothing +% +% Revision 1.8 2002/10/23 17:19:10 arno +% cleaning up, removing ls for windows +% +% Revision 1.7 2002/10/23 17:06:26 arno +% saving syntax change for windows +% +% Revision 1.6 2002/10/23 16:52:23 arno +% testing +% +% Revision 1.5 2002/08/27 00:35:39 arno +% closing current figure +% +% Revision 1.4 2002/08/14 16:48:54 arno +% remove ICADIR +% +% Revision 1.3 2002/07/25 18:24:08 arno +% debugging +% +% Revision 1.2 2002/04/17 20:56:14 arno +% changing XYZ coordinate transformation for eloc structure +% +% Revision 1.1 2002/04/05 17:36:45 jorn +% Initial revision +% + +% 12-12-98 changed electrode label lines to MarkerColor -sm +% 12-12-98 added colorbar option -sm (still graphically marred by tan rect.) +% 12-13-98 implemented colorbar option using enhanced cbar -sm +% 12-13-98 implemented 'setup' comment option -sm +% 03-20-00 added cartesian electrode locations option -sm +% 07-14-00 fixed line in calgx() -sm from -ch +% 03-23-01 documented 'cartesian' locfile option -sm +% 01-25-02 reformated help & license, added links -ad +% 03-21-02 added readlocs and the use of eloc input structure -ad + +function [] = headplot(values,arg1,p1,v1,p2,v2,p3,v3,p4,v4,p5,v5,p6,v6) + +if nargin < 1 + help headplot + return +end + +%%%%%%%%%%%%%%%%%%%%%%%%%% Set Defaults %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% icadefs % load definitions +BACKCOLOR = [.93 .96 1]; % Background figure color + +set(gca,'Color',BACKCOLOR); +% mesh_file = [ICADIR '/newupper.mat']; % whole head model file (183K) +% mesh_file = ['mhead.mat']; % upper head model file (987K) +mesh_file = ['head1.mat'] + +Lighting = 'on'; +Maplimits = 'absmax'; +Lights = [-125 125 80; ... + 125 125 80; ... + 125 -125 125; ... + -125 -125 125]; % default lights at four corners + +View = [143 18]; % default viewpoint +Colorbar = 0; % default no colorbar (Note: has tan top - bug) +ColorbarAxes = 0; + +HeadCenter = [0 0 30]; % Center ?? + + +Cmap = jet(64); % default colormap +FaceColor = [.8 .55 .35]*1.1; % ~= ruddy Caucasian - pick your complexion! +Electrodes = 'on'; % show electrode positions by default +MAX_ELECTRODES = 1024; +Elecnums = 0; % 0 is 'off; 1 is 'on' +Elecnames = 0; % 0 is 'off; 1 is 'on' +ElectDFac = 1.06; % plot electrode marker dots out from head surface +NamesDFac = 1.05; % plot electrode names/numbers out from markers +NamesColor = 'k'; % 'r'; +NamesSize = 10; % FontSize for electrode names +MarkerColor= 'k'; + +sqaxis = 1; % if non-zero, make head proportions anatomical +title_font = 18; +titl = []; +verbose = 'on'; +if isstr(values) + values = lower(values); + if strcmp(values,'setup') + % + %%%%%%%%%%%%%%%%%%% Perform splining file setup %%%%%%%%%%%%%%%%%%%%%%%%%%%% + % + if nargin<3|nargin>5 + error(['headplot(): setup requires 3-5 arguments.\n']) + end + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % Compute and save splining matrix for the electrode array + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + fprintf('Setting up splining matrix.\n'); + + eloc_file = arg1; + if nargin==5 + loctype = p2; + else + loctype = 'spherical'; + end + spline_file = p1; + if nargin >= 4 + comment = v1; + end + %%%%%%%%%%%%%%%%%%%%%%%%%%% + % Open electrode file + %%%%%%%%%%%%%%%%%%%%%%%%%%% + if isstr(eloc_file) + + if strcmp(loctype,'spherical') + [a,b,c, s]=textread(eloc_file, '%d %f %f %s'); + A=[a b c]; + ElectrodeNames=char(s); + elseif strcmp(loctype,'cartesian') + [a,x,y,z, s]=textread(eloc_file, '%d %f %f %f %s'); + A=[a x y z]; + ElectrodeNames=char(s); + else + error(['headplot: unknown electrode location file type.\n']); + end + if 0 % OLD THING WITH dot in EEGnames etc ! + fid = fopen(eloc_file); + if fid == -1 + error(['headplot(): Error opening file: ',eloc_file]) + end + if strcmp(loctype,'spherical') + A = fscanf(fid,'%d %f %f %s',[7 MAX_ELECTRODES]); + elseif strcmp(loctype,'cartesian') + A = fscanf(fid,'%d %f %f %f %s',[8 MAX_ELECTRODES]); + else + error(['headplot: unknown electrode location file type.\n']); + end + fclose(fid); + fprintf(['Electrode file ',eloc_file,' opened.\n']) + A = A'; + fprintf('Location data for %d electrodes read.\n',size(A,1)); + + %%%%%%%%%%%%%%%%%%%%%%%%%%% + % Record ElectrodeNames + %%%%%%%%%%%%%%%%%%%%%%%%%%% + ElectrodeNames = zeros(size(A,1),4); + for r = 1:size(ElectrodeNames,1) + if strcmp(loctype,'spherical') + ElectrodeNames(r,:) = sprintf('%s',A(r,4:7)); + elseif strcmp(loctype,'cartesian') + ElectrodeNames(r,:) = sprintf('%s',A(r,5:8)); + end + for c=1:4 + if ElectrodeNames(r,c) == '.' + ElectrodeNames(r,c) = ' '; + end + end + if ElectrodeNames(r,3)== ' ' & ElectrodeNames(r,4)== ' ' + ElectrodeNames(r,[2 3]) = ElectrodeNames(r,[1 2]); + ElectrodeNames(r,1) = ' '; % move 1|2-letter label to center + end + end + end % That was the old thing + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % Convert from spherical coords to Cartesian + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + if strcmp(loctype,'spherical') + Th = pi/180*A(:,2); + Phi = pi/180*A(:,3); + Xe = cos(Th).*cos(Phi); + Ye = sin(Th).*cos(Phi); + Ze = sin(Phi); + elseif strcmp(loctype,'cartesian') + Xe = A(:,2); + Ye = A(:,3); + Ze = A(:,4); + dists = sqrt(Xe.^2+Ye.^2+Ze.^2); + Xe = Xe./dists; + Ye = Ye./dists; + Ze = Ze./dists; + end + else + %%%%%%%%%%%%%%%%%%%%% + % Electrode structure + %%%%%%%%%%%%%%%%%%%%% + if ~isfield(eloc_file, 'X') | isempty(eloc_file(1).X) % no X Y Z coordinates + [tmp labels Th Rd] = readlocs(eloc_file); + ElectrodeNames = strvcat(labels); + labels = strvcat(labels); + [Phi Th] = topo2sph( [Th(:) Rd(:)]); + Th = pi/180*Th; + Phi = pi/180*Phi; + [Xe Ye Ze] = sph2cart( Th, Phi, ones(size(Th))); + fprintf('Headplot: generating XYZ coordinates from polar coordinates\n'); + dists = sqrt(Xe.^2+Ye.^2+Ze.^2); + Xe = Xe./dists; + Ye = Ye./dists; + Ze = Ze./dists; + else + fprintf('Headplot: using existing XYZ coordinates\n'); + ElectrodeNames = strvcat({ eloc_file.labels }); + Xe = cell2mat( { eloc_file.X } )'; + Ye = cell2mat( { eloc_file.Y } )'; + Ze = cell2mat( { eloc_file.Z } )'; + dists = sqrt(Xe.^2+Ye.^2+Ze.^2); + Xe = Xe./dists; + Ye = Ye./dists; + Ze = Ze./dists; + end; + Xetmp = Xe; + Xe = -Ye; + Ye = Xetmp; + end; + Xe = Xe(:); + Ye = Ye(:); + Ze = Ze(:); + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % Calculate g(x) for electrodes + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + enum = length(Xe); + onemat = ones(enum,1); + G = zeros(enum,enum); + for i = 1:enum + ei = onemat-sqrt((Xe(i)*onemat-Xe).^2 + (Ye(i)*onemat-Ye).^2 + ... + (Ze(i)*onemat-Ze).^2); % default was /2 and no sqrt + %ei = onemat-((Xe(i)*onemat-Xe).^2 + (Ye(i)*onemat-Ye).^2 + ... + % (Ze(i)*onemat-Ze).^2)/2; + gx = zeros(1,enum); + for j = 1:enum + gx(j) = calcgx(ei(j)); + end + G(i,:) = gx; + end + fprintf('Calculating splining matrix...\n') + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % Open mesh file - contains POS and index1 + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + if ~exist(mesh_file) + fprintf('headplot(): mesh_file "%s" not found.\n',mesh_file); + fprintf(' Change file name in headplot.m.\n'); + return + end + try, eval(['load ',mesh_file,' -mat']); + catch, + POS = load('mheadpos.txt', '-ascii'); + TRI1 = load('mheadtri1.txt', '-ascii'); + TRI2 = load('mheadtri2.txt', '-ascii'); + index1 = load('mheadindex1.txt', '-ascii'); + center = load('mheadcenter.txt', '-ascii'); + end; + fprintf('Loaded mesh file %s\n',mesh_file); + newPOS = POS(index1,:); + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % Project head vertices onto unit sphere + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + nPOS = newPOS-ones(size(newPOS,1),1)*HeadCenter; + spherePOS = sqrt(ones./(sum((nPOS.*nPOS)')))'*ones(1,3).*nPOS; + x = spherePOS(:,1); + y = spherePOS(:,2); + z = spherePOS(:,3); + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % Calculate new electrode positions + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + fprintf('Computing electrode locations on head...\n'); + for i=1:length(Xe) + elect = [Xe(i) Ye(i) Ze(i)]; + dists = distance(elect,spherePOS'); + [S,I] = sort(dists); + npoints = I(1:3); + diffe = newPOS(npoints,:)-spherePOS(npoints,:); + newElect(i,:) = elect+mean(diffe)*ElectDFac; + if Ze(i) < -1 % WAS 0 !!!! % HeadCenter(3) ???? + newElect(i,:) = [0 0 0]; % mark as electrode position not to be plotted + end + end + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % Calculate g(x) for sphere mesh vertices + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + fprintf('Computing %d vertices. Should take a while (see wait bar)\n',... + length(x)) + fprintf(' but doesnt have to be done again for this montage...\n'); + hwb = waitbar(0,'Computing spline file (percent done)...'); + + %figure; plot3(x (:), y(:), z(:), '.' ); + %figure; plot3(Xe(:), Ye(:), Ze(:), '.' ); + hwbend = length(x); + for j = 1:length(x) + % fprintf('%d ',j) + X = x(j); + Y = y(j); + Z = z(j); + ei = onemat-sqrt((X*onemat-Xe).^2 + (Y*onemat-Ye).^2 + (Z*onemat-Ze).^2); %default /2 no sqrt + %ei = onemat-((X*onemat-Xe).^2 + (Y*onemat-Ye).^2 + (Z*onemat-Ze).^2)/2; + for i = 1:length(ei) + gx(j,i) = calcgx(ei(i)); + end + waitbar(j/hwbend) + end + close(hwb) + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % Save spline file + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + fprintf('Saving (587k) file %s\n',spline_file); + if nargin == 3 + save(spline_file, '-mat', 'Xe', 'Ye', 'Ze', 'G', 'gx', 'newElect', 'ElectrodeNames'); + else + save(spline_file, '-mat', 'Xe', 'Ye', 'Ze', 'G', 'gx', 'newElect', 'ElectrodeNames', 'comment'); + end; + return + + elseif strcmp(values,'example') | strcmp(values,'demo') + % + %%%%%%%%%%%%%%%%%% Show an example electrode angles file %%%%%%%%%%%%%%%%%%%%%%%% + % + fprintf(['\nExample of a headplot() electrode angles file (spherical coords.)\n',... + 'Fields: chan_num cor_deg horiz_deg channel_name\n\n',... + ' 1 -90 -72 Fp1.\n',... + ' 2 90 72 Fp2.\n',... + ' 3 -62 -57 F3..\n',... + ' 4 62 57 F4..\n',... + ' 5 -45 0 C3..\n',... + ' 6 45 0 C4..\n',... + ' 7 -118 2 A1..\n',... + ' 8 118 -2 A2..\n',... + ' 9 -62 57 P3..\n',... + ' 10 62 -57 P4..\n',... + ' 11 -90 72 O1..\n',... + ' 12 90 -72 O2..\n',... + ' 13 -90 -36 F7..\n',... + ' 14 90 36 F8..\n',... + ' 15 -90 0 T3..\n',... + ' 16 90 0 T4..\n',... + ' 17 -90 36 T5..\n',... + ' 18 90 -36 T6..\n',... + ' 19 45 90 Fz..\n',... + ' 20 0 0 Cz..\n',... + ' 21 45 -90 Pz..\n',... + '\nA 90 deg coronal rotation points to right ear, -90 to left.\n' ,... + 'A positive horizontal rotation is counterclockwise from above.\n',... + 'Use pol2sph() to convert from topoplot() format to spherical.\n',... + 'Channel names should have 4 chars (. = space).\n\n\n']); + return + elseif strcmp(values,'cartesian') + % + %%%%%%%%%%%%%%%%%% Show an example cartesian electrode file %%%%%%%%%%%%%%%%%%% + % + fprintf(['\nExample of a headplot() electrode location file (cartesian coords.)\n',... + 'Fields: chan_num x y z channel_name\n\n',... + ' 1 0.4528 0.8888 -0.0694 Fp1.\n',... + 'Channel names should have 4 chars (. = space).\n\n\n']); + return + else + fprintf('headplot(): Unknown first argument (%s).\n',values) + help headplot + end +else + % + %%%%%%%%%%%%%%%%%%%%%%%%%% Make the plot %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % + if nargin < 2 + help headplot + return + end + spline_file = arg1; + nargs = nargin; + if nargs > 2 + if ~(floor(nargs/2) == nargs/2) + error('headplot(): Cannot have an odd number of inputs.') + end + for i = 3:2:nargs + Param = eval(['p',int2str((i-3)/2 +1)]); + Value = eval(['v',int2str((i-3)/2 +1)]); + if ~isstr(Param) + error('headplot(): Parameters must be strings.') + end + switch lower(Param) + case 'cbar' + if isstr(Value) + error('headplot(): Colorbar value must be 0 or axis handle.') + end + Colorbar = 1; + if size(Value,1) == 1 & size(Value,2) == 1 + ColorbarAxes = 0; + else + ColorbarAxes = Value; + end + case 'lighting' + if ~isstr(Value) + close; error('headplot(): Lighting value must be on or off.') + end + Value = lower(Value); + if ~strcmp(Value,'on') & ~strcmp(Value,'off') + close; error('headplot(): Lighting value must be on or off.') + end + Lighting = lower(Value); + case 'maplimits' + Maplimits = Value; + case 'title' + titl = Value; + case 'lights' + Lights = Value; + if size(Lights,2) ~= 3 + close; error('headplot(): Light matrix must be (3,N).') + end + case 'view' + View = Value; + case 'verbose' + if ~isstr(Value) + close; error('headplot(): verbose value must be on or off.') + end + Value = lower(Value); + if ~strcmp(Value,'on') & ~strcmp(Value,'off') + close; error('headplot(): verbose value must be on or off.') + end + verbose = Value; + case {'colormap','cmap'} + if size(Value,2) ~= 3 + close; error('Colormap must be an n x 3 matrix.') + end + Cmap = Value; + case {'electrodes','elec'} + if ~isstr(Value) + close; error('headplot(): electrodes value must be on or off.') + elseif ~strcmp(Value,'on') & ~strcmp(Value,'off') + close; error('headplot(): electrodes value must be on or off.') + end + Electrodes = Value; + case 'labels' + if isstr(Value) + close; error(['headplot(): labels value must be 0, 1, or 2.']) + end + if Value>2 | Value<0 + close; error(['headplot(): labels value must be 0, 1, or 2.']) + end + if Value == 1 + Elecnums = 1; + elseif Value == 2 + Elecnames = 1; + end + case 'facecolor' + FaceColor=Value; + otherwise + fprintf('headplot(): Unknown Parameter %s\n',Param) + return + end + end + end + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % Open head mesh and electrode spline files + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + if ~exist(spline_file) + fprintf(... + 'headplot(): spline_file "%s" not found. Run headplot in "setup" mode.\n',... + spline_file); + return + end + eval(['load ',spline_file, ' -mat']) + + if ~exist(mesh_file) + close; + error(['headplot(): head mesh file ',mesh_file,... + ' not found.\n Change file name in headplot.m.\n']) + end + try, eval(['load ',mesh_file,' -mat']) + catch, + POS = load('mheadpos.txt', '-ascii'); + TRI1 = load('mheadtri1.txt', '-ascii'); + TRI2 = load('mheadtri2.txt', '-ascii'); + index1 = load('mheadindex1.txt', '-ascii'); + center = load('mheadcenter.txt', '-ascii'); + end; + + %KND + POS=rotation(POS, [0,0,0], [0 1 0] , -30); + + enum = length(values); + if enum ~= length(Xe) + close; + error(['headplot(): Number of values in spline file should equal number of electrodes.']) + end + + %%%%%%%%%%%%%%%%%%%%%%%%%% + % Perform interpolation + %%%%%%%%%%%%%%%%%%%%%%%%%% + + meanval = mean(values); + values = values - meanval; + onemat = ones(enum,1); + lamd = 0.1; + C = pinv([(G + lamd);ones(1,enum)])*[values(:);0]; % fixing division error + P = zeros(1,size(gx,1)); + for j = 1:size(gx,1) + P(j) = dot(C,gx(j,:)); + end + P = P + meanval; + + %%%%%%%%%%%%%%%%%%%%%%%%%% + % Plot surfaces + %%%%%%%%%%%%%%%%%%%%%%%%%% + cla % clear axis + HeadAxes = gca; + + W = zeros(1,size(POS,1)); + m = size(Cmap,1); + if size(Maplimits) == [1,2] + amin = Maplimits(1); + amax = Maplimits(2); + elseif strcmp(Maplimits,'maxmin') | strcmp(Maplimits,'minmax') + amin = min(min(abs(P)))*1.02; % 2% shrinkage keeps within color bounds + amax = max(max(abs(P)))*1.02; + elseif strcmp(Maplimits,'absmax') + amin = -max(max(abs(P)))*1.02; % 2% shrinkage keeps within color bounds + amax = -amin; + end + idx = min(m,round((m-1)*(P-amin)/(amax-amin))+1); % get colormap indices + W(index1) = idx; + colormap(Cmap) + p1 = patch('Vertices',POS,'Faces',TRI1,'FaceVertexCdata',W(:),... + 'FaceColor','interp'); %%%%%%%%% plot scalp map %%%%%%%%% + + FCmap = [Cmap; Cmap(end,:); FaceColor; FaceColor; FaceColor]; + colormap(FCmap) + W = ones(1,size(POS,1))*(m+2); + p2 = patch('Vertices',POS,'Faces',TRI2,'FaceColor','interp',... + 'FaceVertexCdata',W(:)); %%%%%%%% plot face and lower head %%%%%% + + axis([-125 125 -125 125 -125 125]) + axis off % hide axis frame + + %%%%%%%%%%%%%%%%%%%%%%%%% + % Draw colorbar - Note: uses enhanced cbar() function by Colin Humphries + %%%%%%%%%%%%%%%%%%%%%%%%% + if Colorbar==1 + BACKCOLOR = get(gcf,'Color'); + if ColorbarAxes == 0 + ColorbarHandle = cbar(0,3,[amin amax]); + else + ColorbarHandle = cbar(ColorbarAxes,3,[amin amax]); + end + pos = get(ColorbarHandle,'position'); % move left & shrink to match head size + set(ColorbarHandle,'position',[pos(1)-.05 pos(2)+0.13 pos(3)*0.7 pos(4)-0.26]); + end + axes(HeadAxes); + + %%%%%%%%%%%%%%%%%%%%%%%%% + % Turn on lights + %%%%%%%%%%%%%%%%%%%%%%%%% + + if strcmp(Lighting,'on') + set([p1 p2],'EdgeColor','none') + + for i = 1:size(Lights,1) + hl(i) = light('Position',Lights(i,:),'Color',[1 1 1],... + 'Style','infinite'); + end + set(p2,'DiffuseStrength',.6,'SpecularStrength',0,... + 'AmbientStrength',.4,'SpecularExponent',5) + set(p1,'DiffuseStrength',.6,'SpecularStrength',0,... + 'AmbientStrength',.3,'SpecularExponent',5) + lighting phong % all this gives a matte reflectance + end + + %%%%%%%%%%%%%%%%%%%%%%%%% + % Set viewpoint + %%%%%%%%%%%%%%%%%%%%%%%%% + + if isstr(View) + switch lower(View) + case {'front','f'} + view(-180,30) + case {'back','b'} + view(0,30) + case {'left','l'} + view(-90,30) + case {'right','r'} + view(90,30) + case {'frontright','fr'} + view(135,30) + case {'backright','br'} + view(45,30) + case {'frontleft','fl'} + view(-135,30) + case {'backleft','bl'} + view(-45,30) + case 'top' + view(0,90) + case 'bottom' % undocumented option! + view(0,-90) + Lights = [-125 125 80; ... + 125 125 80; ... + 125 -125 125; ... + -125 -125 125; ... + 0 10 -80]; % add light from below! + otherwise + close; error(['headplot(): Invalid View value %s',View]) + end + else + if ~isstr(View) + [h,a] = size(View); + if h~= 1 | a~=2 + close; error('headplot(): View matrix size must be (1,2).') + end + end + view(View) % set camera viewpoint + end + + if strcmp(Electrodes,'on') % plot the electrode locations + if exist('newElect') + newNames = newElect*NamesDFac; % Calculate electrode label positions + if Elecnames | Elecnums + LineColor = MarkerColor; % 'y'; + else + LineColor = MarkerColor; + end + for i = 1:size(newElect,1) + if newElect(i,:) ~= [0 0 0] % plot radial lines to electrode sites + line([newElect(i,1) HeadCenter(1)],[newElect(i,2) HeadCenter(2)],... + [newElect(i,3) HeadCenter(3)],'color',LineColor,'linewidth',1); + + if Elecnums % plot electrode numbers + t=text(newNames(i,1),newNames(i,2),newNames(i,3),int2str(i)); + set(t,'Color',NamesColor,'FontSize',NamesSize,'FontWeight','bold',... + 'HorizontalAlignment','center'); + + elseif Elecnames % plot electrode names + if exist('ElectrodeNames') + name = sprintf('%s',ElectrodeNames(i,:)); + t=text(newNames(i,1),newNames(i,2),newNames(i,3),name); + set(t,'Color',NamesColor,'FontSize',NamesSize,'FontWeight','bold',... + 'HorizontalAlignment','center'); + else + fprintf('Variable ElectrodeNames not read from spline file.\n'); + end + else % plot electrode markers + line(newElect(:,1),newElect(:,2),newElect(:,3),'marker',... + '.','markersize',20,'color',MarkerColor,'linestyle','none'); + end + end + end + else + fprintf('Variable newElect not read from spline file.\n'); + end + end + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % Turn on rotate3d, allowing rotation of the plot using the mouse + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + if strcmp(verbose,'on') + rotate3d on; % Allow 3-D rotation of the plot by dragging the + else % left mouse button while cursor is on the plot + rotate3d off + end + % Make axis square + if sqaxis + axis image % keep the head proportions human and as large as possible + end + % Add a plot title + if ~isempty(titl); + % title(['\n' titl],'fontsize',title_font); + title([titl],'fontsize',title_font); % Note: \n not interpreted by matlab-5.2 + end +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% calcgx() - function used in 'setup' +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +function [out] = calcgx(in) + +out = 0; +m = 4; % 4th degree Legendre polynomial +for n = 1:7 % compute 7 terms + L = legendre(n,in); + % out = out + ((2*n+1)/n^m*(n+1)^m)*L(1) ; + out = out + ((2*n+1)/(n^m*(n+1)^m))*L(1) ; % bug fix by Colin 7/00 +end +out = out/(4*pi); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% distance() - function used in 'setup' +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +function [out] = distance(w,p) +% w is a matrix of row vectors +% p is a matrix of column vectors + +l1 = size(w,1); +l2 = size(p,2); +out = zeros(l1,l2); + +for i = 1:l1 + x = w(i,:)'*ones(1,l2); + out(i,:) = sum((x-p).^2).^.5; +end diff --git a/hindex.m b/hindex.m new file mode 100644 index 0000000..b5d9e5e --- /dev/null +++ b/hindex.m @@ -0,0 +1,176 @@ +function [h ncited]=hindex(name,varargin) +%HINDEX Computes the h-index of an author from Google Scholar. +% HINDEX(AUTHORNAME) computes the h-index of the author AUTHORNAME, on +% the basis of the publications referenced by Google Scholar +% (http://scholar.google.com). An active internet connection is required. +% +% The index h is defined as "the number of papers with citation number +% higher or equal to h", and has been proposed by J.E. Hirsch to +% "characterize the scientific output of a researcher" [Proc. Nat. Acad. +% Sci. 46, 16569 (2005)]. Note that the number of citations referenced by +% Google Scholar may be lower than the actual one (old publications are +% not available online). +% +% The string AUTHORNAME should contain the last name, or the initial(s) +% of the first name(s) followed by the last name, of the author (eg, +% 'A. Einstein'). Do not put the initial(s) after the last name. The scan +% is not case sensitive. Points (.) and spaces ( ) are not taken into +% account. See Google Scholar Help for more details about the syntax. +% +% Example: HINDEX('A. Einstein') returns 43 (ie: 43 papers by A. Einstein +% have been cited at least 43 times, according to Google Scholar). +% +% H = HINDEX(AUTHORNAME) only returns the h-index, without display. +% +% HINDEX(AUTHORNAME, 'Property1',...) specifies the properties: +% 'verb' also displays the list of papers returned by Google +% Scholar, rejecting the ones for which AUTHORNAME is not +% one of the authors. +% 'plot' also plots the 'Cited by' number as a function of the +% paper rank. +% +% HINDEX should be used with care. Many biases exist (homonyms, errors +% from Google Scholar, old papers are not available online, but +% unpublished or draft papers are...) For the true h-index of an author, +% it is recommended to use an official citation index database (eg, ISI). +% Use HINDEX just for fun. +% +% Remark: Massive usage of hindex may be considered by the Google +% Scholar server as a spam attack, and may invalidate the IP number of +% your computer. If this happens, you get an 'Internet connection failed' +% error message -- but you still can use Google Scholar from a web +% browser. +% +% F. Moisy, moisy@fast.u-psud.fr +% Revision: 1.11, Date: 10-jul-2006 + + +% History: +% 22-jan-2006: v1.00-1.10, first versions. +% 07-jul-2006: v1.11, check ML version; help text improved; use properties + + +% check the matlab version: +if str2double(version('-release'))<14, + error('hindex requires Matlab 7 (R14) or higher.'); +end; + +error(nargchk(1,2,nargin)); + +% clean the input text: +name=lower(name); +name=strrep(name,'.',' '); +name=strrep(name,' ',' '); + +ncit=0; % total number of citation +ncitinthispage=0; % number of citation in the current page +ncited=[]; + +seenextpage=1; +numpage=0; +while seenextpage, + numpage=numpage+1; + % find the web page: + pagename=['http://scholar.google.com/scholar?num=100&start=' num2str(100*(numpage-1)) '&q=%22author%3A' strrep(name,' ','+author%3A') '%22']; + if nargout==0, + disp(['Scanning: ' pagename]); + end; + [s, res]=urlread(pagename); + if ~res, + error('Internet connection failed.'); + end; + + rem=s; % remainder of the string + + while strfind(rem,'Cited by '), + pauth1 = strfind(rem,'')+18; pauth1=pauth1(1); + pauth2 = strfind(rem(pauth1:min(end,(pauth1+500))),'')-1; pauth2=pauth2(1); + authstring = lower(rem(pauth1:(pauth1+pauth2-1))); % list of authors of the paper + authstring = strrep(authstring,'',''); + authstring = strrep(authstring,'',''); + authstring = strrep(authstring,'…','...'); + + % check that the required name is indeed in the author list. + paperok=0; + pos=strfind(authstring,name); + if length(pos), + pos=pos(1); + paperok=1; + if pos>1, + % check for wrong initials (eg, 'ga einstein' should not + % match for 'a einstein') + pl=authstring(pos-1); + if ((pl>='a')&&(pl<='z'))||(pl=='-'), + paperok=0; + end; + end; + if pos<(length(authstring)-length(name)), + % check for wrong 'suffix' (eg, 'einstein-joliot' should not + % match for 'einstein') + nl=authstring(pos+length(name)); + if ((nl>='a')&&(nl<='z'))||(nl=='-'), + paperok=0; + end; + end; + end; + + if paperok, % if the required name is indeed in the author list + ncit = ncit+1; + ncitinthispage = ncitinthispage +1; + p=strfind(rem,'Cited by ')+9; p=p(1); + substr=rem(p:(p+5)); + pend=strfind(substr,'<'); pend=pend(1); + ncited(ncit)=str2double(substr(1:(pend-1))); + rem=rem((p+2):end); + if any(strncmpi(varargin,'verb',1)) + disp(['#' num2str(ncit) ': (' num2str(ncited(ncit)) '), ' authstring]); + end; + else + if any(strncmpi(varargin,'verb',1)) + disp(['rejected: ' authstring]); + end; + p=strfind(rem,'Cited by ')+9; p=p(1); + rem=rem((p+2):end); + end; + end; + if any(strncmpi(varargin,'verb',1)) + disp(' '); + end; + if ncit==0, + seenextpage=0; + else + if ((ncited(ncit)<2)||(~length(findstr(rem,'Next')))), + seenextpage=0; + end; + end; +end; % while seenextpage + +if length(ncited), + % sort the list (it should be sorted, but sometimes GS produces unsorted results) + ncited=sort(ncited); ncited=ncited(ncit:-1:1); + + % computes the H-factor: + h=sum(ncited>=(1:ncit)); + + % plot the 'Cited by' number: + if any(strncmpi(varargin,'plot',1)) + loglog(1:ncit,ncited,'.-',1:ncit,1:ncit,'--',h,h,'o'); + xlabel('Paper rank'); ylabel('Number of citations'); + title(['h-index plot for ''' name ''' (h=' num2str(h) ')']); + end; + + % some displays if no output argument: + if nargout==0, + disp(['Number of cited papers: ' num2str(length(ncited))]); + disp(['''Cited by'' list: ' num2str(ncited)]); + disp(['Total number of citations: ' num2str(sum(ncited))]); + disp(['h-index = ' num2str(h)]); + clear h; + end; +else + h=0; + if nargout==0, + disp('No result found'); + clear h; + end; +end; diff --git a/hist2d.m b/hist2d.m new file mode 100644 index 0000000..4580eb4 --- /dev/null +++ b/hist2d.m @@ -0,0 +1,67 @@ +%function mHist = hist2d ([vY, vX], vYEdge, vXEdge) +%2 Dimensional Histogram +%Counts number of points in the bins defined by vYEdge, vXEdge. +%size(vX) == size(vY) == [n,1] +%size(mHist) == [length(vYEdge) -1, length(vXEdge) -1] +% +%EXAMPLE +% mYX = rand(100,2); +% vXEdge = linspace(0,1,10); +% vYEdge = linspace(0,1,20); +% mHist2d = hist2d(mYX,vYEdge,vXEdge); +% +% nXBins = length(vXEdge); +% nYBins = length(vYEdge); +% vXLabel = 0.5*(vXEdge(1:(nXBins-1))+vXEdge(2:nXBins)); +% vYLabel = 0.5*(vYEdge(1:(nYBins-1))+vYEdge(2:nYBins)); +% pcolor(vXLabel, vYLabel,mHist2d); colorbar +function mHist = hist2d (mX, vYEdge, vXEdge) +[nRow, nCol] = size(mX); +if nCol < 2 + error ('mX has less than two columns') +end + +nRow = length (vYEdge)-1; +nCol = length (vXEdge)-1; + +vRow = mX(:,1); +vCol = mX(:,2); + +mHist = zeros(nRow,nCol); + +for iRow = 1:nRow + rRowLB = vYEdge(iRow); + rRowUB = vYEdge(iRow+1); + + [mIdxRow] = find (vRow > rRowLB & vRow <= rRowUB); + vColFound = vCol(mIdxRow); + + if (~isempty(vColFound)) + + + vFound = histc (vColFound, vXEdge); + + nFound = (length(vFound)-1); + + if (nFound ~= nCol) + [nFound nCol] + error ('hist2d error: Size Error') + end + + [nRowFound, nColFound] = size (vFound); + + nRowFound = nRowFound - 1; + nColFound = nColFound - 1; + + if nRowFound == nCol + mHist(iRow, :)= vFound(1:nFound)'; + elseif nColFound == nCol + mHist(iRow, :)= vFound(1:nFound); + else + error ('hist2d error: Size Error') + end + end + +end + + diff --git a/histfun.m b/histfun.m new file mode 100644 index 0000000..22b3b42 --- /dev/null +++ b/histfun.m @@ -0,0 +1,69 @@ +function [Z,C,B] = histfun(X,Y,E,fun) +%HISTFUN - Apply a function to each class of a histogram +% [Z,C] = histfun(X,Y,E,fun) +% +% Example +% >> x = randn(100,1); +% >> y = x.^2 +% >> histfun(x,y,10,'mean') +% +% See also: histc, cellfun, histk() + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2009 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2009-02-10 Creation +% +% ----------------------------- Script History --------------------------------- + +if nargin<4 + fun = @mean; +end +if isempty(Y) + Y=X; +end +if isempty(E) + E=10; +end + +if numel(E)==1 + [C] = bins(X,E-1); + C = [-Inf C Inf]; + [N,B] = histc(X,C); +else + C=E; + [N,B] = histc(X,[E]); +end +for i=1:(length(C)-1) + if any(B==i) + Z(i)=feval(fun, Y(B==i)); + else + Z(i)=NaN; + end +end +return + +function n=count(x) +n=length(x); + +function [b]=bins(y,n) +% Compute n bins from data +% +miny = min(y(~isnan(y))); +maxy = max(y(~isnan(y))); +if (isempty(miny)) + % In acase all nan... + miny = NaN; + maxy = NaN; +end +if miny == maxy, + miny = miny - floor(n/2) - 0.5; + maxy = maxy + ceil(n/2) - 0.5; +end +bw = (maxy - miny) ./ n; +b = miny + bw/2 + bw*(0:n-1) ; \ No newline at end of file diff --git a/histk.m b/histk.m new file mode 100644 index 0000000..4cef025 --- /dev/null +++ b/histk.m @@ -0,0 +1,69 @@ +function [E,N,J] = histk(X,varargin) +%HISTK - Histogram count and ranking +% [E,N,J] = histk(X) +% E is the list of values in X ranked by their relative frequency +% N is their number of occurences +% J is the class to which each element of X belongs to, so: E(J) == X +% +% [E,...] = histk(X, 'rows') works on rows +% +% Note: histk() works even if (numeric) X contains NaN and Inf +% +% See also: hist, histc, histfun() + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2006 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2009-10-22 ReCreation +% KND 2006-10-23 Fix NaN issue +% ----------------------------- Script History --------------------------------- +if ischar(X) + [U,I,J]=unique(X, 'rows'); +else + if nargin<2 || ~isequal(lower(varargin{1}), 'rows') + if ~isvector(X) + for i=1:size(X(:,:),2) + if nargout<3 + [E{i},N{i}]=histk(X(:,i)); + else + [E{i},N{i},J{i}]=histk(X(:,i)); + end + end + return + end + end + [U,I,J]=unique(X,varargin{:}); +end +if isnumeric(X) + % special treatment for possible NaNs in numeric arrays + nans = isnan(U); + if any(nans) + U(nans)=[]; + U(end+1)=NaN; + J(ismember(J,find(nans)))=length(U); + end +end +% Bin data into classes and sort them classes according to their +% cardinality (in decreasing order) +[N,i]=sort(-histc(J,1:length(U))); +% Because we tricked sort using negative values : +N=-N; +if ischar(X) + % In case X is a char array, keep the rest of the rows with "(i,:)" instead + % of only "(i)" + E=U(i,:); +elseif size(U,2)>1 + E=U(i,:); +else +E=U(i); +end +if nargout>2 + [J,J]=ismember(J,i); +elseif nargout==0 + E=[E N]; +end diff --git a/histo2D.m b/histo2D.m new file mode 100644 index 0000000..f39308c --- /dev/null +++ b/histo2D.m @@ -0,0 +1,31 @@ +% function H = histo2D(D,[Xlo Xhi],Xn,[Ylo Yhi],Yn,Xlab,Ylab,Title) +% +% 2 Dimensional Histogram (size(H) == [Yn Xn]) +% Counts number of points in the bins defined by +% X = linspace(Xlo,Xhi,Xn) and +% Y = linspace(Ylo,Yhi,Yn) + +function H = histo2D(D,Xrange,Xn,Yrange,Yn,Xlab,Ylab,Title) + +Xlo = Xrange(1) ; Xhi = Xrange(2) ; +Ylo = Yrange(1) ; Yhi = Yrange(2) ; +X = linspace(Xlo,Xhi,Xn)' ; +Y = linspace(Ylo,Yhi,Yn)' ; + +Dx = D(:,1) ; Dy = D(:,2) ; +n = length(D) ; + +H = zeros(Yn,Xn) ; + +for i = 1:n + x = dsearchn(X,Dx(i)) ; + y = dsearchn(Y,Dy(i)) ; + H(y,x) = H(y,x) + 1 ; +end ; + +figure , pcolor(X,Y,H) ; +% Xmid = 0.5*(X(1:end-1)+X(2:end)) ; +% Ymid = 0.5*(Y(1:end-1)+Y(2:end)) ; +% figure , pcolor(Xmid,Ymid,H) ; +colorbar ; shading flat ; axis square tight ; grid on ; +xlabel(Xlab) ; xlabel(Ylab) ; title(Title) ; \ No newline at end of file diff --git a/hline.m b/hline.m new file mode 100644 index 0000000..61d9972 --- /dev/null +++ b/hline.m @@ -0,0 +1,58 @@ +function varargout = hline(y,varargin) +%HLINE - Create a horizontal line on a plot +% [h] = hline(y) +% Adds as many horizontal lines on the current plot as there are values in y. +% +% See also: line, vline + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2006 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2006-02-02 Creation +% +% ----------------------------- Script History --------------------------------- + +%x=max(abs(get(gca, 'Xlim')))*[-1 1]; +%x=x*10; +x=get(gca, 'Xlim'); +y=y(:); +y=[y*ones(1,2)]'; +x=repmat(x,size(y,2),1)'; + +holdstate=ishold; +hold on; +if nargin>1 && ischar(varargin{1}) && mod(nargin-1,2)==1 + opt=[]; + c = ismember(varargin{1}, 'bgrcmykw'); + m = ismember(varargin{1}, '.ox+*sdv^<>ph'); + if any(c) + opt=[{'Color', varargin{1}(c)} opt]; + end + if any(m) + opt=[{'Marker', varargin{1}(m)} opt]; + end + if any(~c & ~m) + opt=[{'LineStyle', varargin{1}(~c & ~m)} opt]; + end + varargin(1)=[]; + varargin = [opt varargin]; +end +h=line(x,y,varargin{:}); +if ~holdstate + hold off; +end +if nargin==1 + LineStyles={'-', ':', '--', '-.'}; + for i=1:length(h); + set(h(i), 'LineStyle', LineStyles{ceil(i/size(get(gca, 'ColorOrder'),1))}); + end +end +if nargout==0 + return +end +varargout={h}; diff --git a/horizalign.m b/horizalign.m new file mode 100644 index 0000000..e44d1b5 --- /dev/null +++ b/horizalign.m @@ -0,0 +1,30 @@ +function [s]=horizalign(s,FLAG) +% horizalign - NOT WORKING !!! Horizontal Alignment of a char array + + + +error('') + +% [s]=horizalign(s,FLAG) +% FLAG: -1 for left alignment, +1 for right, 0: centered [default:-1] +if nargin<2 + FLAG=-1; +end +if ~isequal(FLAG,1) & ~isequal(FLAG,-1) & ~isequal(FLAG,0) + error('FLAG should be -1 or +1') +end + +% + + +m= + +for i=1:size(s,1) + k=strfind(s(1,:), ' ') + + z=find(diff(k)>1) + if ~isempty(z) | + s(i,:)=circshift(s(i,:), [0 FLAG*(z(1))]); + end +end + diff --git a/hostname.m b/hostname.m new file mode 100644 index 0000000..06ce73b --- /dev/null +++ b/hostname.m @@ -0,0 +1,4 @@ +function h=hostname() +[h,h]=system('hostname'); +h=deblank(h); + diff --git a/hot2.m b/hot2.m new file mode 100644 index 0000000..ca9c1d4 --- /dev/null +++ b/hot2.m @@ -0,0 +1,32 @@ +function h = hot2(m,pR,pY) +%HOT2 Alternative Black-red-yellow-white color map. +% HOT2(M,R,Y) returns an M-by-3 matrix containing a "hot" colormap in +% which the proportion of red is set by R (default: 0.5) +% and that of yellow by Y (default: 0.25). +% HOT2, by itself, is the same length as the current colormap. +% +% For example, to reset the colormap of the current figure: +% +% colormap(hot2) +% +% See also HSV, GRAY, PINK, COOL, BONE, COPPER, FLAG, +% COLORMAP, RGBPLOT. + +% K.N'Diaye +% C. Moler, 8-17-88, 5-11-91, 8-19-92. +% Copyright 1984-2002 The MathWorks, Inc. +% $Revision: 5.7 $ $Date: 2002/04/08 22:00:14 $ + +if nargin < 1, m = size(get(gcf,'colormap'),1); end +if nargin < 2, pR = .5; end +if nargin < 3, pY = .1; end + +n1 = fix(pR*m); +n3 = fix(pY*m); +n2 = m-n1-n3; + +r = [(1:n1)'/n1; ones(n2+n3,1)]; +g = [zeros(n1,1); (1:n2)'/n2; ones(n3,1)]; +b = [zeros(n1+n2,1); (1:m-n1-n2)'/(n3)]; + +h = [r g b]; \ No newline at end of file diff --git a/huemap.m b/huemap.m new file mode 100644 index 0000000..b4062ce --- /dev/null +++ b/huemap.m @@ -0,0 +1,52 @@ +function [cmap]=huemap(hue,n,beta,skew) +% huemap - creates a colormap around a given hue +% +% [cmap]=huemap(hue,n,beta) +% The colormap is from black to white, centered around the hue +% INPUTS: +% hue: a 1x3 or 3x3 RGB colormap. First line is the hue. Other are +% optional +% Second is the bottom of the spectrum (default: black). +% Third is the top default: white. +% n: number of colors in the colormap, default the size of the current +% colormap +% beta: a parameter for the shape of the spectrum, default=0 +% beta>0 => more contrast +% If beta is a 2x1 vector, beta(1) apply to the darker part of the +% spectrum and beta(2) apply to its lighter part. +% OUTUTS: +% cmap: a nx3 colormap +if size(hue,1)<2 + hue(2,:)=[0 0 0]; +end +if size(hue,1)<3 + hue(3,:)=[1 1 1]; +end +if nargin<3 + beta=0; +end +if length(beta)==1 + beta=[beta beta]; +end +if nargin<2 + n=size(colormap,1); +end +% From BRIGHTEN function: +tol = sqrt(eps); +glight=1 - min(1-tol,beta(2)); +gdark=1/(1 + max(-1+tol,-beta(1))); + +% lighter hues +b=linspace(0,1,n-round(n/2)+1).^glight'; +cmap = [ ones(n-round(n/2)+1,1)*hue(1,:) + b*[hue(3,:)-hue(1,:)] ]; + +% remove duplicated center hue +cmap(1,:)=[]; +% darker hues +b=linspace(0,1,round(n/2)).^gdark'; +b=flipud(b); +cmap=[ ones(round(n/2),1)*(hue(1,:)) + b*(hue(2,:)-hue(1,:)) ; cmap]; + +cmap=max(cmap,0); +cmap=min(cmap,1); +return diff --git a/icatb/icatb_check_path.m b/icatb/icatb_check_path.m new file mode 100644 index 0000000..28b9179 --- /dev/null +++ b/icatb/icatb_check_path.m @@ -0,0 +1,92 @@ +function icatb_check_path +% Check path + +% Check startup file +checkStartup; + +% Check required paths for the toolboxes +checkReqdPaths; + +% run only on windows +icatb_openMatlabServer; + + +function checkStartup +% check gift start up file + +if (exist('gift_startup.m', 'file') == 2) || (exist('gift_startup.m', 'file') == 6) + fprintf( 'Executing gift_startup ...\n' ); + % execute gift start up file + gift_startup; + +else + + % Get the full file path of the gift file + giftPath = which('gift.m'); + % Folder location of the gift + giftPath = fileparts(giftPath); + + % all directories on path + pathstr = path; + + % Get the directories on path + allDirs = strread(pathstr, '%s', 'delimiter', pathsep); + + if ~isempty(allDirs) + + [indices] = regexp(allDirs, 'icatb$'); + indices = good_cells(indices); + matchedDirs = allDirs(indices); + matchedDirs(good_cells(regexp(matchedDirs, 'matlab')))=[]; + + if length(matchedDirs) > 1 + error(['Fix MATLAB path such that it has only one version of GIFT at a time']); + elseif length(matchedDirs) == 1 + if strcmpi(giftPath, matchedDirs{1}) + return; + else + error(['Fix MATLAB path such that it has only one version of GIFT at a time']); + end + end + + end + +end +% end for adding path + +function ind = good_cells( mycell ) + +if ~iscell(mycell) + mycell = {mycell}; +end + +for j=1:length(mycell) + ind(j)=~isempty(mycell{j}); +end + + +function checkReqdPaths +% Check required paths + +% Get the full file path of the gift file +giftPath = which('gift.m'); +% Folder location of the gift +giftPath = fileparts(giftPath); + +reqdDirs = str2mat(giftPath, strcat(giftPath, filesep, str2mat('icatb_analysis_functions', 'icatb_batch_files', 'icatb_display_functions', ... + 'icatb_helpManual', 'icatb_helper_functions', 'icatb_io_data_functions', 'icatb_spm2_files', ... + 'icatb_spm5_files', ['toolbox', filesep, 'eegiftv1.0a']))); + +pathstr = path; + +allDirs = strread(pathstr, '%s', 'delimiter', pathsep); + +% Add required paths +for nDir = 1:size(reqdDirs, 1) + currentPath = deblank(reqdDirs(nDir, :)); + check = strmatch(currentPath, allDirs, 'exact'); + if isempty(check) + addpath(genpath(currentPath), '-end'); + end +end +% End for adding required paths \ No newline at end of file diff --git a/icatb/icatb_displayGUI.m b/icatb/icatb_displayGUI.m new file mode 100644 index 0000000..1fc331e --- /dev/null +++ b/icatb/icatb_displayGUI.m @@ -0,0 +1,1690 @@ +function icatb_displayGUI(paramFile) +% Purpose: Display GUI is used to switch between different visualization +% methods. There are four visualization methods like Component Explorer, +% Subect Explorer, Orthogonal Viewer, Composite Viewer. +% These visualization methods except Subject Explorer can be called +% independently of Display GUI by +% clicking on the buttons like Composite Viewer, Component Explorer, +% Orthogonal Viewer + +icatb_defaults; + +global BG_COLOR; +global BG2_COLOR; +global BUTTON_COLOR; +global FG_COLOR; +global AXES_COLOR; +global FONT_COLOR; +global BUTTON_FONT_COLOR; +global COMPONENT_EXPLORER_PIC; +global COMPOSITE_VIEWER_PIC; +global ORTHOGONAL_VIEWER_PIC; +global UI_FONTNAME; +global UI_FONTUNITS; +global UI_FS; +global SORT_COMPONENTS; +global PARAMETER_INFO_MAT_FILE; +global ZIP_IMAGE_FILES; + +% load parameters file +filterP = ['*', PARAMETER_INFO_MAT_FILE, '*.mat']; +if ~exist('paramFile', 'var') + [paramFile] = icatb_selectEntry('typeEntity', 'file', 'title', 'Select Parameter File', ... + 'filter', filterP); +end + +drawnow; + +[pathstr, fileName] = fileparts(paramFile); + +cd(pathstr); % Cd to parameter file location +load(paramFile); + +if ~exist('sesInfo', 'var') + error('Please select the ICA parameter file'); +end + +if ~sesInfo.isInitialized + error('Please run the analysis to display the results'); +end + + +if isfield(sesInfo, 'modality') + if ~strcmpi(sesInfo.modality, 'fmri') + error('Use EEGIFT toolbox to display EEG results'); + end +end + + +%try + displayGUIMsg = 'Opening display GUI. Please wait ...'; + disp(displayGUIMsg); + helpHandle = helpdlg(displayGUIMsg, 'Opening Display GUI'); + [sesInfo, complexInfoRead] = icatb_name_complex_images(sesInfo, 'read'); + [sesInfo, complexInfoWrite] = icatb_name_complex_images(sesInfo, 'write'); + + + zipContents.zipFiles = {}; + zipContents.files_in_zip(1).name = {}; + if isfield(sesInfo, 'zipContents') + zipContents = sesInfo.zipContents; + end + + %%%%%%%% Test if the data sets have different number of time points %%% + % Count the number of files in each of the datasets + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + varCount = 0; + countFiles = zeros(1, sesInfo.numOfSub*sesInfo.numOfSess); + + % check the time points information + if isfield(sesInfo, 'diffTimePoints') + + for numSubjects = 1:sesInfo.numOfSub + for numSessions = 1:sesInfo.numOfSess + varCount = varCount + 1; + % get the time points information + countFiles(varCount) = sesInfo.diffTimePoints(varCount); + % end for getting the count for time points + countTimePoints.sub(numSubjects).sess(numSessions) = countFiles(varCount); + end + end + + else + for numSubjects = 1:sesInfo.numOfSub + for numSessions = 1:sesInfo.numOfSess + varCount = varCount + 1; + % get the time points information + countFiles(varCount) = icatb_get_countTimePoints(sesInfo.userInput.files(varCount).name); + % end for getting the count for time points + countTimePoints.sub(numSubjects).sess(numSessions) = countFiles(varCount); + end + end + % set the time points info to sesInfo + sesInfo.userInput.diffTimePoints = countFiles; + sesInfo.diffTimePoints = countFiles; + end + % end for checking time points information + + % check for same number of time points or different + checkTimePoints = find(countFiles ~= countFiles(1)); + + if ~isempty(checkTimePoints) + flagTimePoints = 'different_time_points'; + else + flagTimePoints = 'same_time_points'; + end + + %get results from sesInfo file + dispParameters.icaOutputFiles = sesInfo.icaOutputFiles; + dispParameters.numOfSess = sesInfo.numOfSess; + dispParameters.numOfSub = sesInfo.numOfSub; + dispParameters.numOfComp = sesInfo.numComp; + dispParameters.inputFiles = sesInfo.inputFiles; + dispParameters.HInfo = sesInfo.HInfo; + dispParameters.spmMatrices = sesInfo.userInput.designMatrix; + dispParameters.outputDir = pathstr; % save the output directory + dispParameters.paramFile = paramFile; % parameter file + dispParameters.inputPrefix = sesInfo.userInput.prefix; + dispParameters.complexInfoRead = complexInfoRead; + dispParameters.complexInfoWrite = complexInfoWrite; + dispParameters.zipContents = zipContents; + dispParameters.mask_ind = sesInfo.mask_ind; + dispParameters.button_original_font_color = BUTTON_FONT_COLOR; + dispParameters.button_new_font_color = [0 1 0]; + + if isfield(sesInfo, 'flip_analyze_images') + dispParameters.flip_analyze_images = sesInfo.flip_analyze_images; + end + + + maskImage = fullfile(dispParameters.outputDir, [dispParameters.inputPrefix, 'Mask.img']); + % save mask image + dispParameters.mask_file = maskImage; + + %%%%%%%%% Construct a mask Image for dealing with z scores%%%%%%%%%%%%%%% + % get voxel dimensions + voxelDim = sesInfo.HInfo.DIM; + % Initialise mask data + maskData = zeros(1, prod(voxelDim)); + % use indices in the mask as one + maskData(sesInfo.mask_ind) = 1; + % convert to 3D data + maskData = reshape(maskData, voxelDim); + + % first scan of the functional data + firstScan = deblank(sesInfo.inputFiles(1).name(1, :)); + + [firstScan, fileNum] = icatb_parseExtn(firstScan); + + if ~exist(firstScan, 'file') + disp(['file: ', firstScan, ' is missing.']); + firstScan = icatb_selectEntry('typeEntity', 'file', 'typeSelection', 'single', 'filter', '*.img;*.nii', ... + 'title', 'Select first scan of functional data', 'fileType', 'image', 'fileNumbers', 1); + if isempty(firstScan) + error('file is not selected'); + end + % update the files information + sesInfo.inputFiles(1).name = str2mat(firstScan, sesInfo.inputFiles(1).name(2:end, :)); + sesInfo.userInput.files = sesInfo.inputFiles; + dispParameters.inputFiles = sesInfo.inputFiles; + end + + % get the first image volume + V = icatb_getVol(firstScan, fileNum); + V.fname = maskImage; + V.n(1) = 1; + icatb_write_vol(V, maskData); + %%%%%%%%%%%%% end for creating mask %%%%%%%%%%% + + % include spm mat flag + if isfield(sesInfo.userInput, 'spmMatFlag') + dispParameters.spmMatFlag = sesInfo.userInput.spmMatFlag; + else + dispParameters.spmMatFlag = 'not_specified'; + end + + % include data type + if isfield(sesInfo.userInput, 'dataType') + dispParameters.dataType = lower(sesInfo.userInput.dataType); + else + dispParameters.dataType = 'real'; + end + + % include data type + if isfield(sesInfo.userInput, 'write_complex_images') + dispParameters.write_complex_images = lower(sesInfo.userInput.write_complex_images); + else + dispParameters.write_complex_images = 'real&imaginary'; + end + + % sorting text file (optional way to enter the regressors) + if isfield(sesInfo.userInput, 'sortingTextFile') + dispParameters.sortingTextFile = sesInfo.userInput.sortingTextFile; + else + dispParameters.sortingTextFile = []; + end + + icatb_save(paramFile, 'sesInfo'); + + clear sesInfo; + + % store the information about the structural file + [dispParameters] = icatb_getStructuralFile(dispParameters); + + dispParameters.flagTimePoints = flagTimePoints; + dispParameters.countTimePoints = countTimePoints; + outputFiles = dispParameters.icaOutputFiles; + + % delete a previous figure of display GUI + checkDispGUI = findobj('tag', 'displaywindow'); + + if ~isempty(checkDispGUI) + for ii = 1:length(checkDispGUI) + delete(checkDispGUI(ii)); + end + end + + % display figure + graphicsHandle = icatb_getGraphics('Display GUI', 'displayGUI', 'displaywindow', 'off'); + + set(graphicsHandle, 'CloseRequestFcn', @figCloseCallback); + + % set graphics handle menu none + set(graphicsHandle, 'menubar', 'none'); + + dispParameters.diffTimePoints = countFiles; % store the count for the images + + % plot display defaults + display_defaultsMenu = uimenu('parent', graphicsHandle, 'label', 'Display Defaults', 'callback', ... + {@display_defaults_callback, graphicsHandle}); + + % Display GUI Options Menu + dispGUIOptionsMenu = uimenu('parent', graphicsHandle, 'label', 'Display GUI Options'); + designMatrixMenu = uimenu(dispGUIOptionsMenu, 'label', 'Design Matrix', 'callback', ... + {@designMatrixOptionsCallback, graphicsHandle}); + + + % IC navigator + %icNavigatorMenuH = uimenu(dispGUIOptionsMenu, 'label', 'IC Navigator', 'callback', @icNavigatorCallback); + + % save display parameters using save option in options menu + saveDataMenu = uimenu(dispGUIOptionsMenu, 'label', 'Save Display Parameters', 'callback', ... + {@saveDispParametersCallback, graphicsHandle}); + + + % load display parmeters + loadDataMenu = uimenu(dispGUIOptionsMenu, 'label', 'Load Display Parameters', 'callback', ... + {@loadDispParametersCallback, graphicsHandle}); + + % enter sorting text file + sortingTextFileMenu = uimenu(dispGUIOptionsMenu, 'label', 'Load file for temporal sorting', 'callback', ... + {@sortingTextFileCallback, graphicsHandle}); + + % Help Menu + helpMenu = uimenu('parent', graphicsHandle, 'label', 'GIFT-Help'); + htmlHelpMenu = uimenu(helpMenu, 'label', 'Display GUI', 'callback', 'icatb_openHTMLHelpFile(''icatb_displayGUI.htm'');'); + + + % parameters to plot in a menu + + % offsets + xOffset = 0.02; yOffset = 0.02; + + %%%%%%%%%%%%% Draw Title here %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % title color + titleColor = [0 0.9 0.9]; + % fonts + titleFont = 13; + axes('Parent', graphicsHandle, 'position', [0 0 1 1], 'visible', 'off'); + xPos = 0.5; yPos = 0.97; + text(xPos, yPos, 'Visualization Methods', 'color', titleColor, 'FontAngle', 'italic', 'fontweight', 'bold', ... + 'fontsize', titleFont, 'HorizontalAlignment', 'center', 'FontName', UI_FONTNAME); + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + % Plot controls in two frames + % Upper frame: Images + % Lower frame: UIcontrols + + % plot display button + buttonWidth = 0.2; buttonHeight = 0.05; + displayButtonPos = [0.75 yOffset buttonWidth buttonHeight]; + + + displayButtonH = icatb_uicontrol('parent', graphicsHandle, 'units', 'normalized', 'style', 'pushbutton', ... + 'position', displayButtonPos, 'string', 'Display', 'tag', 'display_button', 'callback', ... + {@displayButtonCallback, graphicsHandle}); + % extentPos = get(displayButtonH, 'extent'); % get the extent of the load anatomical button + % displayButtonPos(3) = extentPos(3) + 0.02; displayButtonPos(4) = extentPos(4) + 0.002; + % % get the new position for load anatomical position + % set(displayButtonH, 'position', displayButtonPos); + + % plot load anatomical + buttonWidth = 0.26; buttonHeight = 0.05; + loadAnatomicalPos = [0.05 yOffset buttonWidth buttonHeight]; + loadAnatomicalH = icatb_uicontrol('parent', graphicsHandle, 'units', 'normalized', 'style', 'pushbutton', ... + 'position', loadAnatomicalPos, 'string', 'Load Anatomical', 'tag', 'load_anatomical_button', ... + 'callback', {@loadAnatomicalCallback, graphicsHandle}); + % extentPos = get(loadAnatomicalH, 'extent'); % get the extent of the load anatomical button + % loadAnatomicalPos(3) = extentPos(3) + 0.01; loadAnatomicalPos(4) = extentPos(4) + 0.001; + % % get the new position for load anatomical position + % set(loadAnatomicalH, 'position', loadAnatomicalPos); + + paramFrameYOrigin = loadAnatomicalPos(2) + loadAnatomicalPos(4) + yOffset; + availableSpace = yPos - paramFrameYOrigin - yOffset; + + paramHeight = 0.65*availableSpace; + + % define frame positions + parameterFramePos = [xOffset paramFrameYOrigin 1 - 2*xOffset paramHeight]; + + paramFrameYOrigin = parameterFramePos(2) + parameterFramePos(4) + yOffset; + paramFrameWidth = 1 - 2*xOffset; + paramFrameHeight = yPos - 2*yOffset - paramFrameYOrigin; + + buttonFramePos = [parameterFramePos(1) paramFrameYOrigin paramFrameWidth paramFrameHeight]; + + parametersFrameH = icatb_uicontrol('parent', graphicsHandle, 'units', 'normalized', 'style', 'frame', 'position', ... + parameterFramePos, 'tag', 'parameters-frame', 'visible', 'off'); + + buttonsFrameH = icatb_uicontrol('parent', graphicsHandle, 'units', 'normalized', 'style', 'frame', 'position', ... + buttonFramePos, 'tag', 'button-frame', 'visible', 'off'); + + % component Button + buttonWidth = (1 - 2*xOffset - 5*xOffset) / 4; + buttonHeight = 0.05; + componentButtonPos = [buttonFramePos(1) + xOffset buttonFramePos(2) + buttonFramePos(4) - yOffset - 0.5*buttonHeight ... + buttonWidth buttonHeight]; + + componentButtonH = icatb_uicontrol('parent', graphicsHandle, 'units', 'normalized', 'style', 'pushbutton', ... + 'position', componentButtonPos, 'string', 'Component', 'tag', 'component_button', ... + 'callback', {@componentExplorerCallback, graphicsHandle}); + % subject explorer button + subjectButtonPos = componentButtonPos; + subjectButtonPos(1) = subjectButtonPos(1) + subjectButtonPos(3) + xOffset; + subjectButtonH = icatb_uicontrol('parent', graphicsHandle, 'units', 'normalized', 'style', 'pushbutton', ... + 'position', subjectButtonPos, 'string', 'Subject', 'tag', 'subject_button', 'callback', ... + {@subjectExplorerCallback, graphicsHandle}); + + % Orthogonal Viewer button + orthButtonPos = subjectButtonPos; + orthButtonPos(1) = orthButtonPos(1) + orthButtonPos(3) + xOffset; + orthoButtonH = icatb_uicontrol('parent', graphicsHandle, 'units', 'normalized', 'style', 'pushbutton', ... + 'position', orthButtonPos, 'string', 'Orthogonal', 'tag', 'ortho_button', ... + 'callback', {@orthoViewerCallback, graphicsHandle}); + + % Orthogonal Viewer button + compositeButtonPos = orthButtonPos; + compositeButtonPos(1) = compositeButtonPos(1) + compositeButtonPos(3) + xOffset; + compositeButtonH = icatb_uicontrol('parent', graphicsHandle, 'units', 'normalized', 'style', 'pushbutton', ... + 'position', compositeButtonPos, 'string', 'Composite', 'tag', 'composite_button', ... + 'callback', {@compositeViewerCallback, graphicsHandle}); + + % Component Explorer Picture + componentPicPos = componentButtonPos; + componentPicPos(2) = buttonFramePos(2); + componentPicPos(4) = componentButtonPos(2) - 0.5*componentButtonPos(4) - componentPicPos(2); + componentPicture = axes('Parent', graphicsHandle, 'units','normalized', 'position', componentPicPos); + pic1 = imread(COMPONENT_EXPLORER_PIC); + ImageAxis1 = image(pic1, 'parent', componentPicture, 'CDataMapping', 'scaled'); + axis(componentPicture, 'off'); + + % Subject Explorer Picture + subjectPicPos = componentPicPos; + subjectPicPos(1) = subjectButtonPos(1); + subjectPicture = axes('Parent', graphicsHandle, 'units','normalized', 'position', subjectPicPos); + pic2 = imread(COMPONENT_EXPLORER_PIC); + ImageAxis2 = image(pic2, 'parent', subjectPicture, 'CDataMapping', 'scaled'); + axis(subjectPicture, 'off'); + + % Orthogonal Viewer Picture + orthoPicPos = componentPicPos; + orthoPicPos(1) = orthButtonPos(1); + orthoPicture = axes('Parent', graphicsHandle, 'units','normalized', 'position', orthoPicPos); + pic3 = imread(ORTHOGONAL_VIEWER_PIC); + ImageAxis3 = image(pic3, 'parent', orthoPicture, 'CDataMapping', 'scaled'); + axis(orthoPicture, 'off'); + + % Composite Viewer Picture + compositePicPos = componentPicPos; + compositePicPos(1) = compositeButtonPos(1); + compositePicture = axes('Parent', graphicsHandle, 'units','normalized', 'position', compositePicPos); + pic4 = imread(COMPOSITE_VIEWER_PIC); + ImageAxis4 = image(pic4, 'parent', compositePicture, 'CDataMapping', 'scaled'); + axis(compositePicture, 'off'); + + dispParameters.slicePlane = 'axial'; + dispParameters.compList = zeros(dispParameters.numOfComp, 1); + dispParameters.displayType = 'component explorer'; + + % Plot sort components followed by listboxes for viewing set and + % component number + + % plot sort components here + textHeight = 0.05; textWidth = 0.35; + control_width = 0.2; control_height = 0.05; + % text origin + textYOrigin = parameterFramePos(2) + parameterFramePos(4) - xOffset - textHeight; + textPos = [parameterFramePos(1), textYOrigin, textWidth, textHeight]; + + % plot text + textH = icatb_uicontrol('parent', graphicsHandle, 'units', 'normalized', 'style', 'text', 'position', ... + textPos, 'string', 'Sort Components', 'HorizontalAlignment', 'center'); + + % horizontal alignment - center, vertical alignment - middle + align(textH,'center','middle'); + + popupPos = [textPos(1) + textPos(3) + 2*xOffset, textPos(2), ... + control_width, control_height]; + + clear options; + + % Sort Options + options(1).str = SORT_COMPONENTS; + sortOptions = {'No', 'Yes'}; + % Apply defaults from icatb_defaults + sortOptions = checkDefaults_gui(options(1).str, sortOptions, 'exact'); + + for jj = 1:length(sortOptions) + options(jj).str = sortOptions{jj}; + end + + popupH = icatb_getUIPopUp(graphicsHandle, str2mat(options.str), popupPos, '', 'on', 'sort_components'); + + clear options; + + % plot component listbox and viewing set listbox + textPos(2) = textPos(2) - textHeight - yOffset; + + textWidth = 0.25; textHeight = 0.05; + % draw listbox + listWidth = 0.5; listHeight = 0.3; + + textPos = [textPos(1) + 0.5*listWidth - 0.5*textWidth textPos(2) textWidth textHeight]; + + % plot text + textH = icatb_uicontrol('parent', graphicsHandle, 'units', 'normalized', 'style', 'text', 'position', ... + textPos, 'string', 'Viewing Set', 'HorizontalAlignment', 'center'); + + % horizontal alignment - center, vertical alignment - middle + align(textH,'center','middle'); + + counter = 1; + numOfSets = length(outputFiles); + % loop over number of sets + for jj = 1 : numOfSets + % loop over sessions + for kk = 1 : length(outputFiles(jj).ses) + str = outputFiles(jj).ses(kk).name(1, :); + [pathstr fileName] = fileparts(str); + underScoreIndex = icatb_findstr(fileName, '_'); + str = fileName(1:underScoreIndex(end) - 1); + str = [num2str(jj),'-', num2str(kk), ' ', str]; + options(counter).str = str; + counter = counter + 1; + end + + end + + listboxPos = [parameterFramePos(1) textPos(2) - listHeight - yOffset listWidth listHeight]; + % list box position + listH = icatb_uicontrol('parent', graphicsHandle, 'units', 'normalized', 'style', 'listbox', 'position', ... + listboxPos, 'HorizontalAlignment', 'center', 'string', str2mat(options.str), 'value', 1, 'tag', ... + 'viewing_set', 'min', 0, 'max', 1); + + + listWidth = 0.25; + + listboxPos = [parameterFramePos(1) + parameterFramePos(3) - 2*xOffset - listWidth, listboxPos(2), listWidth, ... + listHeight]; + textPos = [listboxPos(1) + 0.5*listWidth - 0.5*textWidth textPos(2) textWidth textHeight]; + + text2H = icatb_uicontrol('parent', graphicsHandle, 'units', 'normalized', 'style', 'text', 'position', ... + textPos, 'string', 'Component No:', 'HorizontalAlignment', 'center'); + + % horizontal alignment - center, vertical alignment - middle + align(text2H, 'center', 'middle'); + + % number of components + numComp = dispParameters.numOfComp; + + clear options; + + % return component index + for ii = 1:numComp + options(ii).str = icatb_returnFileIndex(ii); + end + + % list box position + list2H = icatb_uicontrol('parent', graphicsHandle, 'units', 'normalized', 'style', 'listbox', 'position', ... + listboxPos, 'string', str2mat(options.str), 'HorizontalAlignment', 'center', ... + 'tag', 'component_number', 'min', 0, 'max', 2); + + clear options + % store the data to handles data + %handles_data.dispParameters = dispParameters; + + [dispParameters, inputText] = icatb_displayGUI_defaults('init', [], dispParameters, 'off'); + %dispParameters.visualizationmethods = 'component explorer'; + handles_data.dispParameters = dispParameters; handles_data.inputText = inputText; + + % set the figure data + set(graphicsHandle, 'userdata', handles_data); + + clear handles_data; + + % by default execute the component explorer callback + componentExplorerCallback(componentButtonH, [], graphicsHandle); + + try + delete(helpHandle); + catch + end + + % Make the figure visible + set(graphicsHandle, 'visible', 'on'); + + +%catch +% if exist('helpHandle', 'var') +% if ishandle(helpHandle) +% delete(helpHandle); +% end +% end +% icatb_displayErrorMsg; +%end + + +%%%%%%%%%%%%%% Function Callbacks %%%%%%%%%%%%%%%%%%%%%%%%% + +function componentExplorerCallback(hObject, event_data, handles) +% Component explorer callback +% set the visualization methods field to component explorer + + +subjectH = findobj(handles, 'tag', 'subject_button'); +orthoH = findobj(handles, 'tag', 'ortho_button'); +compositeH = findobj(handles, 'tag', 'composite_button'); + +handles_data = get(handles, 'userdata'); +dispParameters = handles_data.dispParameters; +dispParameters.visualizationmethods = 'component explorer'; + +% set all the other buttons normal font +set(hObject, 'fontweight', 'bold', 'ForegroundColor', dispParameters.button_new_font_color); +set(subjectH, 'fontweight', 'normal', 'ForegroundColor', dispParameters.button_original_font_color); +set(orthoH, 'fontweight', 'normal', 'ForegroundColor', dispParameters.button_original_font_color); +set(compositeH, 'fontweight', 'normal', 'ForegroundColor', dispParameters.button_original_font_color); + +% Viewing set listbox +viewingsetH = findobj(handles, 'tag', 'viewing_set'); +set(viewingsetH, 'enable', 'on'); + +% component listbox +compListH = findobj(handles, 'tag', 'component_number'); +set(compListH, 'enable', 'off'); + +handles_data.dispParameters = dispParameters; +% set the user data +set(handles, 'userdata', handles_data); + +% sort parameters popup control +sortCompH = findobj(handles, 'tag', 'sort_components'); +set(sortCompH, 'enable', 'on'); + + +function subjectExplorerCallback(hObject, event_data, handles) +% Subject Component explorer callback +% set the visualization methods field to subject component explorer + +componentH = findobj(handles, 'tag', 'component_button'); +orthoH = findobj(handles, 'tag', 'ortho_button'); +compositeH = findobj(handles, 'tag', 'composite_button'); + +handles_data = get(handles, 'userdata'); +dispParameters = handles_data.dispParameters; +dispParameters.visualizationmethods = 'subject component explorer'; + + +% set all the other buttons normal font +set(hObject, 'fontweight', 'bold', 'ForegroundColor', dispParameters.button_new_font_color); +set(componentH, 'fontweight', 'normal', 'ForegroundColor', dispParameters.button_original_font_color); +set(orthoH, 'fontweight', 'normal', 'ForegroundColor', dispParameters.button_original_font_color); +set(compositeH, 'fontweight', 'normal', 'ForegroundColor', dispParameters.button_original_font_color); + + +% component listbox +compListH = findobj(handles, 'tag', 'component_number'); +set(compListH, 'enable', 'on'); + +% viewing set listbox +viewingsetH = findobj(handles, 'tag', 'viewing_set'); +set(viewingsetH, 'enable', 'off'); + +compVal = get(compListH, 'value'); +if length(compVal) > 1 + set(compListH, 'value', compVal(1)); + % single selection listbox +end +% make the component listbox single selection +set(compListH, 'max', 1, 'min', 0); + +% set the user data +handles_data.dispParameters = dispParameters; +set(handles, 'userdata', handles_data); + +% sort parameters popup control +sortCompH = findobj(handles, 'tag', 'sort_components'); +set(sortCompH, 'enable', 'off'); + +function orthoViewerCallback(hObject, event_data, handles) +% Orthogonal viewer callback +% set the visualization methods field to orthogonal viewer + +componentH = findobj(handles, 'tag', 'component_button'); +subjectH = findobj(handles, 'tag', 'subject_button'); +compositeH = findobj(handles, 'tag', 'composite_button'); + + +handles_data = get(handles, 'userdata'); +dispParameters = handles_data.dispParameters; +dispParameters.visualizationmethods = 'orthogonal viewer'; + +% set all the other buttons normal font +set(hObject, 'fontweight', 'bold', 'ForegroundColor', dispParameters.button_new_font_color); +set(componentH, 'fontweight', 'normal', 'ForegroundColor', dispParameters.button_original_font_color); +set(subjectH, 'fontweight', 'normal', 'ForegroundColor', dispParameters.button_original_font_color); +set(compositeH, 'fontweight', 'normal', 'ForegroundColor', dispParameters.button_original_font_color); + +% Viewing set listbox +viewingsetH = findobj(handles, 'tag', 'viewing_set'); +set(viewingsetH, 'enable', 'on'); + +% component listbox +compListH = findobj(handles, 'tag', 'component_number'); +set(compListH, 'enable', 'on'); + +compVal = get(compListH, 'value'); + +if length(compVal) > 1 + set(compListH, 'value', compVal(1)); +end + +% make the component listbox single selection +set(compListH, 'min', 0, 'max', 1); + +% set the figure data +handles_data.dispParameters = dispParameters; +set(handles, 'userdata', handles_data); + +% sort parameters popup control +sortCompH = findobj(handles, 'tag', 'sort_components'); +set(sortCompH, 'enable', 'off'); + +% case subject component explorer +function compositeViewerCallback(hObject, event_data, handles) +% Composite viewer callback +% set the visualization methods field to composite viewer + + +componentH = findobj(handles, 'tag', 'component_button'); +subjectH = findobj(handles, 'tag', 'subject_button'); +orthoH = findobj(handles, 'tag', 'ortho_button'); + +handles_data = get(handles, 'userdata'); +dispParameters = handles_data.dispParameters; +dispParameters.visualizationmethods = 'composite viewer'; + +% set all the other buttons normal font +set(hObject, 'fontweight', 'bold', 'ForegroundColor', dispParameters.button_new_font_color); +set(componentH, 'fontweight', 'normal', 'ForegroundColor', dispParameters.button_original_font_color); +set(subjectH, 'fontweight', 'normal', 'ForegroundColor', dispParameters.button_original_font_color); +set(orthoH, 'fontweight', 'normal', 'ForegroundColor', dispParameters.button_original_font_color); + +% viewing set listbox +viewingsetH = findobj(handles, 'tag', 'viewing_set'); +set(viewingsetH, 'enable', 'on'); + +% component listbox +compListH = findobj(handles, 'tag', 'component_number'); +set(compListH, 'enable', 'on'); + +compVal = get(compListH, 'value'); + +if length(compVal) > 5 + disp('A maximum of five components can be plotted in Composite Viewer with different color bars'); + set(compListH, 'value', compVal(1:5)); + % single selection listbox +end + +% make the component listbox multiple selection +set(compListH, 'min', 0, 'max', 2); + +% set the figure data +handles_data.dispParameters = dispParameters; +set(handles, 'userdata', handles_data); + +% sort parameters popup control +sortCompH = findobj(handles, 'tag', 'sort_components'); +set(sortCompH, 'enable', 'off'); + +function loadAnatomicalCallback(hObject, event_data, handles) +% load structural image +% change the inputText structure array where index matches slice_range: +% set the structural file location to dispParameters + +handles_data = get(handles, 'userdata'); +% display parameters +dispParameters = handles_data.dispParameters; +% get the input text Structure +inputText = handles_data.inputText; +% get the structural file +structFile = icatb_selectEntry('typeEntity', 'file', 'title', 'Select Structural File', 'filter', ... + '*.img;*.nii', 'fileType', 'image', 'fileNumbers', 1); + +if ~isempty(structFile) + dispParameters.structFile = structFile; + + % get the anatomical plane + getIndex = strmatch('anatomical_plane', str2mat(inputText.tag), 'exact'); + % get the three views + allStrs = inputText(getIndex).answerString; + % get the current value + selectVal = inputText(getIndex).value; + + if iscell(allStrs) + selectStr = allStrs{selectVal}; + else + selectStr = deblank(allStrs(selectVal, :)); + end + % end for checking + + if isempty(selectStr) + selectStr = 'axial'; + end + + selectStr = lower(selectStr); + % find slice_range location + getIndex = strmatch('slice_range', str2mat(inputText.tag), 'exact'); + % get structVol + %imagVol = icatb_returnHInfo(structFile); + imagVol = icatb_get_vol_nifti(structFile); + % get the slices in mm for the corresponding plane + [sliceParameters] = icatb_get_slice_def(imagVol, selectStr); + % get the slices in mm + slices_in_mm = sliceParameters.slices; + clear sliceParameters; + % construct string + slices_in_mm = icatb_constructString(slices_in_mm); + % set the slice range + inputText(getIndex).answerString = slices_in_mm; + % set display parameters + [dispParameters, inputText] = icatb_displayGUI_defaults('no-init', inputText, dispParameters, 'off'); +end + + +% set the user data to the figure +handles_data.dispParameters = dispParameters; +handles_data.inputText = inputText; +set(handles, 'userdata', handles_data); + +function displayButtonCallback(hObject, event_data, handles) +% display button callback + +try + + % get the figure data + handles_data = get(handles, 'userdata'); + dispParameters = handles_data.dispParameters; + % load parameter file + load(dispParameters.paramFile); + % indices in the mask + dispParameters.mask_ind = sesInfo.mask_ind; + mask_ind = sesInfo.mask_ind; + zipContents.zipFiles = {}; + zipContents.files_in_zip(1).name = {}; + if isfield(sesInfo, 'zipContents') + zipContents = sesInfo.zipContents; + end + % zip contents + dispParameters.zipContents = zipContents; + [sesInfo, complexInfoRead] = icatb_name_complex_images(sesInfo, 'read'); + [sesInfo, complexInfoWrite] = icatb_name_complex_images(sesInfo, 'write'); + % complex information + dispParameters.complexInfoRead = complexInfoRead; + dispParameters.complexInfoWrite = complexInfoWrite; + clear sesInfo; + + % put a check for the visualization methods + if isfield(dispParameters, 'visualizationmethods') + displayMethod = dispParameters.visualizationmethods; + else + error('Visualization method is not selected'); + end + + % find viewing set listbox + viewListH = findobj(handles, 'tag', 'viewing_set'); + viewingsetStr = get(viewListH, 'string'); + viewingsetVal = get(viewListH, 'value'); + + % viewing set selected + viewingset = deblank(viewingsetStr(viewingsetVal, :)); + + % component listbox + compListH = findobj(handles, 'tag', 'component_number'); + % get the component numbers + component_numbers = get(compListH, 'value'); + + if isempty(component_numbers) + component_numbers = 1; + end + + % checking display method + if strcmpi(displayMethod, 'composite viewer') + if length(component_numbers) > 5 + component_numbers = component_numbers(1:5); + disp('A maximum of five components can be plotted in Composite Viewer with different color bars'); + end + else + if strcmpi(displayMethod, 'subject component explorer') | strcmpi(displayMethod, 'orthogonal viewer') + if length(component_numbers) > 1 + component_numbers = component_numbers(1); + end + end + end + % end for checking display method + + dispParameters.componentnumber = component_numbers; + + % store viewing set + dispParameters.viewingset = viewingset; + returnValue = dispParameters.returnValue; + convertToZ = dispParameters.convertToZ; + threshValue = dispParameters.thresholdvalue; + + % sort parameters + sortCompH = findobj(handles, 'tag', 'sort_components'); + sortCompStr = get(sortCompH, 'string'); sortCompVal = get(sortCompH, 'value'); + sortcomponents = deblank(sortCompStr(sortCompVal, :)); + dispParameters.sortcomponents = sortcomponents; + + handles_data.dispParameters = dispParameters; + set(handles, 'userdata', handles_data); + if isfield(handles_data.dispParameters, 'text_left_right') + parameters.text_left_right = handles_data.dispParameters.text_left_right; + end + clear handles_data; + + + % parameters or variables common to all the display methods + + % load structural file + P = dispParameters.structFile; % structural file name + [pathstr, name, ext, versn] = fileparts(P); + if isempty(pathstr) + P = fullfile(pwd, P); + end + + icatb_defaults; + global DETRENDNUMBER; + global SMOOTHPARA; + global SMOOTHINGVALUE; + + parameters.flip_analyze_images = []; + if isfield(dispParameters, 'flip_analyze_images') + parameters.flip_analyze_images = dispParameters.flip_analyze_images; + end + + % parameter structure used to display components according to the + % visualization selected + parameters.structFile = dispParameters.structFile; + parameters.imagevalues = returnValue; + parameters.convertToZ = convertToZ; + parameters.thresholdvalue = threshValue; + % actual subjects, sessions and components + parameters.numOfSub = dispParameters.numOfSub; + parameters.numOfSess = dispParameters.numOfSess; + parameters.numComp = dispParameters.numOfComp; + parameters.filesOutputDir = dispParameters.outputDir; + parameters.inputPrefix = dispParameters.inputPrefix; + + % initialise model time course + modelTimecourse = []; + % field required for detecting the type of complex images + parameters.write_complex_images = dispParameters.write_complex_images; + complexInfoWrite.complexInfoRead = complexInfoRead; + % add complex type to the structure + parameters.complexInfoRead = complexInfoRead; + parameters.complexInfoWrite = complexInfoWrite; + parameters.dataType = dispParameters.dataType; + parameters.zipContents = dispParameters.zipContents; + parameters.outputDir = dispParameters.outputDir; + parameteres.mask_file = dispParameters.mask_file; + + switch lower(displayMethod) + case 'component explorer' + %icatb_defaults; + global TMAP_AN3_FILE; + global COMPLEXVIEWER; % get the viewer type (real&imaginary or magnitude&phase) + + % check the viewing set + if ~isfield(dispParameters, 'viewingset') + error('Viewing set is not selected'); + else + if isempty(dispParameters.viewingset) + error('Viewing set is not selected'); + end + end + + % load components + dashIndex = icatb_findstr(dispParameters.viewingset, '-'); + spaceIndex = icatb_findstr(dispParameters.viewingset, ' '); + a = dispParameters.viewingset(1 : dashIndex(1) - 1); + b = dispParameters.viewingset(dashIndex(1) + 1 : spaceIndex(1)); + P = dispParameters.icaOutputFiles(str2num(a)).ses(str2num(b)).name; + + compFiles = P; + + % get the files from the zip file + [zipFileName, files_in_zip] = icatb_getViewingSet_zip(compFiles, complexInfoWrite, ... + dispParameters.dataType, zipContents); + + % component files + compFiles = icatb_fullFile('directory', dispParameters.outputDir, 'files', compFiles); + + % number of components + numOfComp = parameters.numComp; + + % end loop over component files + figLabel = dispParameters.viewingset; + + % put a default spm flag + if isfield(dispParameters, 'spmMatFlag') + spmMatFlag = dispParameters.spmMatFlag; + else + spmMatFlag = 'not_specified'; + end + + % do interpolation of images separately for components that are + % not sorted and sorted + + % when components are not sorted + if strcmpi(dispParameters.sortcomponents, 'no') + set(handles, 'pointer', 'watch'); + % resize the image and return header Info + [icasig, icaTimecourse, structuralImage, coords, HInfo, text_left_right] = icatb_loadICAData('structFile', ... + dispParameters.structFile, 'compFiles', compFiles, 'slicePlane', dispParameters.anatomicalplane, ... + 'sliceRange', dispParameters.slicerange, 'comp_numbers', [1:1:parameters.numComp], ... + 'convertToZ', convertToZ, 'returnValue', returnValue, 'threshValue', threshValue, ... + 'dataType', dispParameters.dataType, 'complexInfo', complexInfoWrite, ... + 'zipfile', zipFileName, 'files_in_zip_file', files_in_zip, 'mask_file', parameteres.mask_file, 'flip_analyze_images', ... + parameters.flip_analyze_images); + set(handles, 'pointer', 'arrow'); + + % set up default labels and sorting Index + for ii = 1:parameters.numComp + compLabels(ii).string = ['Component ', num2str(ii), ' Not Sorted']; + end + % smooth ica time course + if strcmpi(SMOOTHPARA, 'yes') + icaTimecourse = icatb_gauss_smooth1D(icaTimecourse, SMOOTHINGVALUE); + end + spatialTemplate = []; + parameters.compLabels = compLabels; + parameters.undetrendICA = icaTimecourse; + % linearly detrend ICA Timecourse + parameters.icaTimecourse = icatb_detrend(icaTimecourse, 1, size(icaTimecourse, 1), DETRENDNUMBER); + %detrend(icaTimecourse); % linearly detrended ica time course + clear icaTimecourse; + parameters.modelTimecourse = modelTimecourse; + clear modelTimecourse; + parameters.refInfo = []; + parameters.icasig = icasig; % store component images + clear icasig; + parameters.figLabel = figLabel; + parameters.htmlFile = 'icatb_component_explorer.htm'; + else + % display GUI parameters + dispGUI_Parameters = struct('returnValue', returnValue, 'convertToZ', convertToZ, 'threshValue', ... + threshValue, 'flagTimePoints', dispParameters.flagTimePoints, 'countTimePoints', ... + dispParameters.countTimePoints, 'icaOutputFiles', dispParameters.icaOutputFiles, 'numOfSub', ... + dispParameters.numOfSub, 'numOfSess', dispParameters.numOfSess, 'numOfComp', ... + dispParameters.numOfComp, 'spmMatrices', dispParameters.spmMatrices, 'outputDir', ... + dispParameters.outputDir, 'paramFile', dispParameters.paramFile, 'spmMatFlag', ... + spmMatFlag, 'sortingTextFile', dispParameters.sortingTextFile, 'structFile', ... + dispParameters.structFile, 'slicePlane', dispParameters.anatomicalplane, ... + 'sliceRange', dispParameters.slicerange, 'dataType', dispParameters.dataType, ... + 'complexInfo', complexInfoWrite, 'displayGUI', handles, 'inputPrefix', ... + dispParameters.inputPrefix, 'zipContents', zipContents); + % Output contains the sorted parameters information + [sortParameters] = icatb_sortComponentsGUI(dispGUI_Parameters); + if isempty(sortParameters) + return; %error('error in getting data from sorting gui.'); + end + + clear dispGUI_Parameters; + parameters.sortParameters = sortParameters; + clear sortParameters; + spmMatFlag = parameters.sortParameters.stateDesignMatrix; + + % consider temporal and spatial cases + if strcmpi(parameters.sortParameters.sortingType, 'temporal') + set(handles, 'pointer', 'watch'); + % load ICA images and time courses + [icasig, icaTc, structuralImage, coords, HInfo, text_left_right] = icatb_loadICAData(... + 'structFile', dispParameters.structFile, 'compFiles', compFiles, 'slicePlane', ... + dispParameters.anatomicalplane, 'sliceRange', dispParameters.slicerange, ... + 'comp_numbers', [1:1:parameters.numComp], 'convertToZ', convertToZ, 'returnValue', ... + returnValue, 'threshValue', threshValue, 'dataType', dispParameters.dataType, ... + 'complexInfo', complexInfoWrite, 'zipfile', zipFileName, ... + 'files_in_zip_file', files_in_zip, 'mask_file', parameteres.mask_file, ... + 'flip_analyze_images', parameters.flip_analyze_images); + set(handles, 'pointer', 'arrow'); + + else + + spatialInfo = parameters.sortParameters.spatialInfo; + + % spatialInfo.files = compFiles; + % spatialInfo.viewingSet = figureData.viewingSet; + % spatialInfo.zipFileName = zipFileName; + % spatialInfo.files_in_zip = files_in_zip; + + %compFiles = parameters.sortParameters.compFiles; + + %parameters.sortParameters = rmfield(parameters.sortParameters, 'icasig'); % remove the field + + % load ICA images and time courses + [icasig, icaTc, structuralImage, coords, HInfo, text_left_right] = icatb_loadICAData(... + 'structFile', dispParameters.structFile, 'compFiles', spatialInfo.files, 'slicePlane', ... + dispParameters.anatomicalplane, 'sliceRange', dispParameters.slicerange, ... + 'comp_numbers', [1:1:parameters.numComp], 'convertToZ', convertToZ, 'returnValue', ... + returnValue, 'threshValue', threshValue, 'dataType', dispParameters.dataType, ... + 'complexInfo', complexInfoWrite, 'mask_file', parameteres.mask_file, 'flip_analyze_images', ... + parameters.flip_analyze_images); + + + if ~isempty(spatialInfo.zipFileName) + files_in_zip = str2mat(spatialInfo.files_in_zip); + %fileN = icatb_fullFile('files', files_in_zip, 'directory', parameters.outputDir); + icatb_delete_file_pattern(files_in_zip, parameters.outputDir); + end + + + figLabel = spatialInfo.viewingSet; + + % get the left and right convention + %text_left_right = parameters.sortParameters.text_left_right; + %parameters.text_left_right = text_left_right; + + % get the component image + %icasig = parameters.sortParameters.icasig; + + %parameters.sortParameters = rmfield(parameters.sortParameters, 'icasig'); % remove the field + + % get the structural image + %structuralImage = parameters.sortParameters.structuralImage; + % get the header infomation + %HInfo = parameters.sortParameters.structHInfo; + % remove both component images and structural image + %parameters.sortParameters = rmfield(parameters.sortParameters, 'structuralImage'); % remove the field + + end + % end for checking the sorting type (temporal or spatial) + + parameters.htmlFile = 'icatb_component_explorer.htm'; + % keyword for datasets + keywd_SubnSess = parameters.sortParameters.keywd; + %sort icasig using sorting index + tempicasig = zeros(size(icasig)); + for ii = 1:numOfComp + tempicasig(ii, :) = icasig(parameters.sortParameters.sortedIndices(ii), :); + end + clear icasig; + icasig = tempicasig; + clear tempicasig; + % get the sorted order of the spatial maps + parameters.icasig = icasig; + clear icasig; + parameters.figLabel = figLabel; + end + % end for checking whether the components are sorted or not + + if ~exist('keywd_SubnSess', 'var') + keywd_SubnSess = 'not-specified'; + end + + switch keywd_SubnSess + case 'all_subjects_sessions' + % Parameters to report number of subjects and sessions + parameters.numSubjects = dispParameters.numOfSub; + parameters.numSessions = dispParameters.numOfSess; + case 'single_session' + % Parameters to report number of subjects and sessions + parameters.numSubjects = dispParameters.numOfSub; + parameters.numSessions = 1; + case 'single_subject' + % Parameters to report number of subjects and sessions + parameters.numSubjects = 1; + parameters.numSessions = dispParameters.numOfSess; + otherwise + % Parameters to report number of subjects and sessions + parameters.numSubjects = 1; + parameters.numSessions = 1; + end + + %%%%%%%%% Apply structural image parameters %%%%%%%% + parameters.structHInfo = HInfo; + structDIM = HInfo.DIM; + parameters.structuralImage = structuralImage; + + % pass the correponding selected display parameters + parameters.anatomicalplane = dispParameters.anatomicalplane; + parameters.slicerange = dispParameters.slicerange; + parameters.imagesperfigure = dispParameters.imagesperfigure; + parameters.imagevalues = returnValue; + parameters.spmMatFlag = spmMatFlag; + % New component explorer that takes parameters structure as + % input. + + % structural file + parameters.structFile = dispParameters.structFile; + + + if ~isfield(parameters, 'text_left_right') + parameters.text_left_right = text_left_right; + end + + icatb_componentExplore(parameters); + + case 'composite viewer' + + % put a check for the component number + if ~isfield(dispParameters, 'componentnumber') + error('Component number is not selected'); + end + + %icatb_defaults; + global TMAP_AN3_FILE; + + if ~isfield(dispParameters, 'viewingset') + error('Viewing set is not selected'); + else + if isempty(dispParameters.viewingset) + error('Viewing set is not selected'); + end + end + + % load components + dashIndex = icatb_findstr(dispParameters.viewingset,'-'); + spaceIndex = icatb_findstr(dispParameters.viewingset,' '); + a = dispParameters.viewingset(1:dashIndex(1)-1); + b = dispParameters.viewingset(dashIndex(1)+1:spaceIndex(1)); + P = dispParameters.icaOutputFiles(str2num(a)).ses(str2num(b)).name; + if threshValue == 0 + warning('Please set the threshold to view the components.'); + end + + if length(dispParameters.componentnumber) == 0 + icatb_error('No Components Selected'); + end + % get the full file path + files_to_load = P; + % get the files from the zip file + [zipFileName, files_in_zip] = icatb_getViewingSet_zip(files_to_load, complexInfoWrite, ... + dispParameters.dataType, zipContents); + files_to_load = icatb_fullFile('directory', dispParameters.outputDir, 'files', files_to_load); + set(handles, 'pointer', 'watch'); + clear P; + % parameters structure + parameters.numComp = length(dispParameters.componentnumber(:)); %size(files_to_load, 1); + % load ICA images and time courses + [icasig, icaTimecourse, structuralImage, coords, HInfo, text_left_right] = icatb_loadICAData('structFile', dispParameters.structFile, ... + 'compFiles', files_to_load, 'slicePlane', dispParameters.anatomicalplane, 'sliceRange', ... + dispParameters.slicerange, 'comp_numbers', dispParameters.componentnumber, ... + 'convertToZ', convertToZ, 'returnValue', returnValue, 'threshValue', threshValue, 'complexInfo', ... + complexInfoWrite, 'dataType', dispParameters.dataType, ... + 'zipfile', zipFileName, 'files_in_zip_file', files_in_zip, 'mask_file', parameteres.mask_file, ... + 'flip_analyze_images', parameters.flip_analyze_images); + + set(handles, 'pointer', 'arrow'); + + %%%%%%%%% Apply structural image parameters %%%%%%%% + parameters.structHInfo = HInfo; + structDIM = HInfo.DIM; + parameters.structuralImage = structuralImage; + + tdim = size(icaTimecourse, 1); + % show only the first field + if isa(icasig, 'complex_data') + icasig = getfield(icasig, 'firstField'); + end + + if isa(icaTimecourse, 'complex_data') + icaTimecourse = getfield(icaTimecourse, 'firstField'); + end + + % take only the absolute value of the data + % component dimensions + xdim = HInfo.DIM(1); + ydim = HInfo.DIM(2); + zdim = HInfo.DIM(3); + tdim = size(icaTimecourse, 1); + % put icasig into correct dimensions + icasig = reshape(icasig, size(icasig, 1), xdim, ydim, zdim); + parameters.icasig = icasig; + clear icasig; + parameters.slicePlane = dispParameters.anatomicalplane; + parameters.sliceRange = dispParameters.slicerange; + parameters.componentnumber = dispParameters.componentnumber; + % detrend ica Time course + parameters.icaTimecourse = icatb_detrend(icaTimecourse, 1, size(icaTimecourse, 1), DETRENDNUMBER); + parameters.icaDIM = [xdim ydim zdim tdim]; + parameters.htmlFile = 'icatb_composite_viewer.htm'; + + + if ~isfield(parameters, 'text_left_right') + parameters.text_left_right = text_left_right; + end + + % composite viewer + icatb_compositeViewer(parameters); + + case 'orthogonal viewer' + % put a check for the component number + if ~isfield(dispParameters, 'componentnumber') + error('Component number is not selected'); + end + %icatb_defaults; + global TMAP_AN3_FILE; + + % load original data files for timecourse information + subNum = 1; + P = dispParameters.inputFiles(subNum).name; + outputDir = dispParameters.outputDir; + count = 0; + for ii = 1:dispParameters.numOfSub + for jj = 1:dispParameters.numOfSess + count = count + 1; + fmriFiles(ii).ses(jj).name = dispParameters.inputFiles(count).name; + end + end + clear fmri; + clear HInfo; + + % setup icaData + dashIndex = icatb_findstr(dispParameters.viewingset, '-'); + spaceIndex = icatb_findstr(dispParameters.viewingset, ' '); + a = dispParameters.viewingset(1:dashIndex(1)-1); + b = dispParameters.viewingset(dashIndex(1)+1:spaceIndex(1)); + if isempty(dispParameters.componentnumber) + error('Component is not selected for orthogonal viewer.'); + end + compNum = dispParameters.componentnumber(end); + %P = dispParameters.icaOutputFiles(str2num(a)).ses(str2num(b)).name(compNum,:); + P = dispParameters.icaOutputFiles(str2num(a)).ses(str2num(b)).name; + icaFiles = P; + % if(~isempty(findstr(TMAP_AN3_FILE,P))) + % icatb_new_directions('displayGUI-tmap'); + % end + parameters.compNum = compNum; + lastUnderscore = icatb_findstr(deblank(P(1, :)), '_'); + parameters.title = [P(1, 1:lastUnderscore(end)), icatb_returnFileIndex(compNum)]; + + % use the component file + files_to_load = P; + % get the files from the zip file + [zipFileName, files_in_zip] = icatb_getViewingSet_zip(files_to_load, complexInfoWrite, ... + dispParameters.dataType, zipContents); + files_to_load = icatb_fullFile('directory', dispParameters.outputDir, 'files', files_to_load); + set(handles, 'pointer', 'watch'); + + clear P; + + % Interpolate the ICA images + [icasig, icaTimecourse, structuralImage, coords, HInfo, text_left_right] = icatb_loadICAData('structFile', ... + dispParameters.structFile, 'compFiles', files_to_load, ... + 'convertToZ', convertToZ, 'returnValue', returnValue, 'threshValue', ... + threshValue, 'imreshape', 'yes', 'open_dialog', 'no', 'complexInfo', ... + complexInfoWrite, 'dataType', dispParameters.dataType, 'comp_numbers', compNum, ... + 'zipfile', zipFileName, 'files_in_zip_file', files_in_zip, 'mask_file', parameteres.mask_file, ... + 'flip_analyze_images', parameters.flip_analyze_images); + + set(handles, 'pointer', 'arrow'); + + structuralImage = squeeze(structuralImage); + + icasig = squeeze(icasig); + + % show only the first field + if isa(icasig, 'complex_data') + icasig = getfield(icasig, 'firstField'); + end + + if isa(icaTimecourse, 'complex_data') + icaTimecourse = getfield(icaTimecourse, 'firstField'); + end + + %%%%%%%%% Apply structural image parameters %%%%%%%% + parameters.structHInfo = HInfo; + structDIM = HInfo.DIM; + parameters.structuralImage = structuralImage; + parameters.icasig = icasig; + clear icasig; + parameters.icaTimecourse = icatb_detrend(icaTimecourse, 1, size(icaTimecourse, 1), DETRENDNUMBER); + clear icaTimecourse; + parameters.icaFiles = icaFiles; + clear icaFiles; + parameters.fmriFiles = fmriFiles; + clear fmriFiles; + % store the new dimensions + parameters.icaHInfo = HInfo; + parameters.fmriHInfo = HInfo; + parameters.htmlFile = 'icatb_ortho_viewer.htm'; + % structural file + parameters.structFile = dispParameters.structFile; + parameters.diffTimePoints = dispParameters.diffTimePoints; + parameters.real_world_coords = coords; + + if ~isfield(parameters, 'text_left_right') + parameters.text_left_right = text_left_right; + end + + icatb_orthoViewer(parameters); + + case 'subject component explorer' + % put a check for the component number + if ~isfield(dispParameters, 'componentnumber') + error('Component number is not selected'); + end + %icatb_defaults; + global TMAP_AN3_FILE; + global WRITE_COMPLEX_IMAGES; + + if isempty(dispParameters.componentnumber) + error('Component is not selected'); + end + + numTimePoints = min(dispParameters.diffTimePoints); + + %numComp = length(dispParameters.icaOutputFiles); + numComp = 0; + % get the total number of components + for ii = 1:length(dispParameters.icaOutputFiles) + numComp = numComp + length(dispParameters.icaOutputFiles(ii).ses); + end + + + compNum = dispParameters.componentnumber(end); + counter = 0; + outputDir = dispParameters.outputDir; + if strcmp(lower(dispParameters.flagTimePoints), 'different_time_points') + %error('Subject explorer cannot be used with different number of images over data-sets. Please use other visualization methods.'); + disp('Time points are different across subjects'); + disp(['Using first ', num2str(numTimePoints), ' time points to display the time courses']); + end + + % get the write type of complex images + WRITE_COMPLEX_IMAGES = parameters.complexInfoRead.complexType; + + set(handles, 'pointer', 'watch'); + + % resize the image and return header Info + helpHandle = helpdlg('Interpolating component images. Please wait ...', 'Interpolating components'); + % loop over remaining files + for kk = 1:length(dispParameters.icaOutputFiles) + for ii = 1:length(dispParameters.icaOutputFiles(kk).ses) + counter = counter + 1; + %P = dispParameters.icaOutputFiles(kk).ses(ii).name(compNum, :); + P = str2mat(dispParameters.icaOutputFiles(kk).ses(ii).name); + PFullFiles = P; + % get the files from the zip file + [zipFileName, files_in_zip] = icatb_getViewingSet_zip(PFullFiles, complexInfoWrite, ... + dispParameters.dataType, zipContents); + PFullFiles = icatb_fullFile('directory', dispParameters.outputDir, 'files', PFullFiles); + % load ICA images and time courses + [tempicasig, tempA, structuralImage, coords, HInfo, textLR] = icatb_loadICAData('structFile', ... + dispParameters.structFile, 'compFiles', PFullFiles, 'slicePlane', ... + dispParameters.anatomicalplane, 'sliceRange', dispParameters.slicerange, ... + 'convertToZ', convertToZ, 'returnValue', returnValue, 'threshValue', ... + threshValue, 'imreshape', 'no', 'open_dialog', 'no', 'complexInfo', ... + complexInfoWrite, 'dataType', dispParameters.dataType, 'comp_numbers', compNum, ... + 'zipfile', zipFileName, 'files_in_zip_file', files_in_zip, 'mask_file', parameteres.mask_file, ... + 'flip_analyze_images', parameters.flip_analyze_images); + + tempA = tempA(1:numTimePoints, :); + + % smooth ica time course + if strcmpi(SMOOTHPARA, 'yes') + tempA = icatb_gauss_smooth1D(tempA, SMOOTHINGVALUE); + end + + % initialise component tc's and images + if kk == 1 & ii == 1 + icaTimecourse = zeros(size(tempA, 1), numComp); + icasig = zeros(numComp, size(tempicasig, 2)); + if strcmpi(parameters.dataType, 'complex') + icasig = complex_data(icasig); + icaTimecourse = complex_data(icaTimecourse); + end + end + + icasig(counter, :) = tempicasig; + clear tempicasig; + + + + icaTimecourse(:, counter) = tempA; + clear tempA; + % end for checking the data type structure or double + [pathstr, fileName, extn] = fileparts(deblank(P(1, :))); + % image extension + under_score_positions = icatb_findstr(fileName, '_'); + fileName = [fileName(1:under_score_positions(end)), icatb_returnFileIndex(compNum)]; + compLabels(counter).string = fileName; + % store text for left right + text_left_right = textLR; + clear P1; clear P: + end + + end + + if ishandle(helpHandle) + delete(helpHandle); + end + + % make the pointer arrow + set(handles, 'pointer', 'arrow'); + figLabel = ['Component ', num2str(compNum)]; + clear P; + tdim = size(icaTimecourse, 1); + % number of components + numOfComp = counter; + %%%%%%%%% Apply structural image parameters %%%%%%%% + parameters.structHInfo = HInfo; + structDIM = HInfo.DIM; + parameters.structuralImage = structuralImage; + %%%%%%%%%%%%%%%%%%%%%%%%%%% + DIM = [HInfo.DIM, tdim]; + % handle structure data type + icasig = reshape(icasig, [counter, DIM(1:end-1)]); + % parameters data structure + parameters.numComp = numComp; + parameters.icasig = icasig; + clear icasig; + parameters.icaTimecourse = icatb_detrend(icaTimecourse, 1, size(icaTimecourse, 1), DETRENDNUMBER); + parameters.undetrendICA = icaTimecourse; + clear icaTimecourse; + parameters.numSubjects = 1; % number of subjects concatenated + parameters.numSessions = 1; % number of sessions concatenated + otherFields = {'modelTimecourse', 'refInfo', 'diffTimePoints'}; % fields to be added + for ii = 1:length(otherFields) + if ~isfield(parameters, otherFields{ii}) + parameters = setfield(parameters, otherFields{ii}, []); + end + end + parameters.anatomicalplane = dispParameters.anatomicalplane; + parameters.slicerange = dispParameters.slicerange; + parameters.imagesperfigure = dispParameters.imagesperfigure; + parameters.compLabels = compLabels; + parameters.figLabel = figLabel; + parameters.htmlFile = 'icatb_subject_explorer.htm'; + % structural file + parameters.structFile = dispParameters.structFile; + % store text for left right + %parameters.text_left_right = str2mat(text_left_right.tag); + + if ~isfield(parameters, 'text_left_right') + parameters.text_left_right = text_left_right; + end + + % Component Explorer function + icatb_componentExplore(parameters); + end + + handles_data = get(handles, 'userdata'); + + handles_data.dispParameters.text_left_right = parameters.text_left_right; + + set(handles, 'userdata', handles_data); + +catch + set(handles, 'pointer', 'watch'); + icatb_displayErrorMsg; +end + + +function designMatrixOptionsCallback(hObject, event_data, handles) +% design matrix menu callback +% +% Options menu for the design matrix: +% saves the selected design matrix information in the parameter file and +% subject file as well. +% Useful when many data sets are involved and if 'no' option is selected +% during the analysis. + +% get the user data +designMatrixData = get(hObject, 'userdata'); + +% get the figure data +figureData = get(handles, 'userdata'); + +% set the spm matrices +icatb_selectDesignMatrix(handles); + +function saveDispParametersCallback(hObject, event_data, handles) +% save display parameters menu callback + +handles_data = get(handles, 'userdata'); +% display parameters +dispParameters = handles_data.dispParameters; +% get outputDir information +outputDir = dispParameters.outputDir; +% open input dialog box +prompt = {'Enter Valid File Name:'}; +dlg_title = 'Save Display Parameters as'; +num_lines = 1; +def = {'dispParameters'}; +% save the file with the file name specified +fileName = icatb_inputdlg2(prompt, dlg_title, num_lines, def); + +if ~isempty(fileName) + % make a full file + fileName = fullfile(outputDir, [fileName{1}, '.mat']); + icatb_save(fileName, 'dispParameters'); + disp(['Display parameters are saved in file: ', fileName]); +end + +function loadDispParametersCallback(hObject, event_data, handles) +% load display parameters menu callback + +global DISP_PARAMETERS +handles_data = get(handles, 'userdata'); + +dispParameters = handles_data.dispParameters; + +% get outputDir information +outputDir = dispParameters.outputDir; + +clear handles_data; + +clear dispParameters; + +% open file selection box +[P] = icatb_selectEntry('typeEntity', 'file', 'title', 'Select Display Parameter File', 'filter', '*.mat', ... + 'startPath', outputDir); +if ~isempty(P) + load(P); + if ~exist('dispParameters', 'var') + error('Not a valid display parameter file'); + end + DISP_PARAMETERS = dispParameters; + D(1).string = 'Global DISP_PARAMETERS variable is created.'; + D(size(D, 2) + 1).string = ''; + D(size(D, 2) + 1).string = 'Type the following statements at the command prompt:'; + D(size(D, 2) + 1).string = ''; + D(size(D, 2) + 1).string = 'global DISP_PARAMETERS; DISP_PARAMETERS'; + disp(str2mat(D.string)); +end + + +function openHelpDesignMatrix(hObject, event_data, handles) + +% get the user data +helpDetails = get(hObject, 'userdata'); +helpH = icatb_dialogBox('textType', 'large', 'textBody', helpDetails.str, 'title', helpDetails.title); +waitfor(helpH); + +function sortingTextFileCallback(hObject, event_data, handles) +% function callback for sorting text file + +% user data for the figure +handles_data = get(handles, 'userdata'); + +% disp parameters +dispParameters = handles_data.dispParameters; + +% parameter file +paramFile = dispParameters.paramFile; + +% load the parameter file +load(paramFile); + +% sorting text file +sesInfo.userInput.sortingTextFile = icatb_selectEntry('title', 'Select text file for regressors', 'typeSelection', ... + 'single', 'typeEntity', 'file', 'filter', '*.txt'); + +icatb_save(paramFile, 'sesInfo'); + +% set the file to display parameters +dispParameters.sortingTextFile = sesInfo.userInput.sortingTextFile; + +clear sesInfo; + +% set display parameters +handles_data.dispParameters = dispParameters; + +% set the figure data +set(handles, 'userdata', handles_data); + + +function [dispParameters] = icatb_getStructuralFile(dispParameters) +% get the structural file for plotting component images + +currentFile = dispParameters.inputFiles(1).name(1, :); + +[currentFile] = icatb_parseExtn(currentFile); + +% check the existence of the input file +if ~exist(currentFile, 'file') + msgString = [deblank(dispParameters.inputFiles(1).name(1, :)), ' file doesn''t exist.' ... + ' Do you want to select the structural file?']; + [answerQuestion] = icatb_questionDialog('title', 'Select Structural File', 'textbody', msgString); + if answerQuestion == 1 + % structural file + [structuralFile] = icatb_selectEntry('typeEntity', 'file', 'title', 'Select Structural File', 'filter', ... + '*.img;*.nii', 'fileType', 'image'); + else + error(deblank(dispParameters.inputFiles(1).name(1, :)), ' file doesn''t exist.'); + end +else + % structural file + structuralFile = dispParameters.inputFiles(1).name(1, :); + % create magnitude image for complex data type + if ~strcmpi(dispParameters.dataType, 'real') + complexFile = dispParameters.inputFiles(1).name(1, :); + [data, HInfo] = icatb_loadData(complexFile, dispParameters.dataType, ... + dispParameters.complexInfoRead, 'read', 1); + data = abs(data); + structuralFile = fullfile(dispParameters.outputDir, [dispParameters.inputPrefix, 'structFile.img']); + V = HInfo.V(1); + V.fname = structuralFile; + icatb_write_vol(V, data); + clear data; clear V; + end + % create magnitude image +end + +% add structural file to the dispParameters structure +dispParameters.structFile = structuralFile; + + +function display_defaults_callback(hObject, event_data, handles) +% get the display defaults + +handles_data = get(handles, 'userdata'); + +[parameters, inputText] = icatb_displayGUI_defaults('no-init', handles_data.inputText, handles_data.dispParameters); + +handles_data.dispParameters = parameters; +handles_data.inputText = inputText; + +set(handles, 'userdata', handles_data); + +function optionsString = checkDefaults_gui(choiceString, optionsString, stringChar) +% put the defaults at the Top + +temp = optionsString; % Assign options string to a temporary variable + +if strcmp(stringChar, 'exact') + matchIndex = strmatch(lower(choiceString), lower(temp), stringChar); % find the index of the choice string +else + matchIndex = strmatch(lower(choiceString), lower(temp)); % find the index of the choice string +end + +jj = 1; optionsString{1} = temp{matchIndex}; % choice string + +for numOptions = 1:length(optionsString) + if numOptions ~= matchIndex + jj = jj + 1; + optionsString{jj} = temp{numOptions}; % collect other options below the choice string + end +end + +function figCloseCallback(hObject, event_data, handles) +% figure close callback + +delete(hObject); + +function icNavigatorCallback(hObject, event_data, handles) + +icatb_ic_navigator; diff --git a/icatb/icatb_drawTimecourses.m b/icatb/icatb_drawTimecourses.m new file mode 100644 index 0000000..1a7ad3c --- /dev/null +++ b/icatb/icatb_drawTimecourses.m @@ -0,0 +1,924 @@ +function icatb_drawTimecourses(parameters) +% Expanded view of the time course +% uses function callbacks and menus to display the options and the +% utilities + +% load defaults +icatb_defaults; + +% Screen Color Defaults +global BG_COLOR; +global BG2_COLOR; +global BUTTON_COLOR; +global BUTTON_FONT_COLOR; +global FG_COLOR; +global AXES_COLOR; +global FONT_COLOR; +global UI_FONTNAME; +global UI_FONTUNITS; +global UI_FS; + +% set figure data the parameters +figureData = parameters; +outputDir = parameters.filesOutputDir; +clear parameters; + +% open time course figure +timecourse_handle = icatb_getGraphics(figureData.titleFig, 'timecourse', 'expaned_view_timecourse'); + +% get all the options to include in options menu +input_parameters = icatb_displayOptions; + +% Plot Options menu +optionsMenu = uimenu(timecourse_handle, 'label', 'Options'); +timecourseOptionsMenu = uimenu(optionsMenu, 'label', 'Timecourse Options', 'callback', ... + {@optionsWindowCallback, timecourse_handle}, 'userdata', input_parameters); +clear input_parameters; + +figureData.optionsMenu = timecourseOptionsMenu; + +% Plot Utilities menu +utilitiesMenu = uimenu(timecourse_handle, 'label', 'Utilities'); +% FFT +fftMenu = uimenu(utilitiesMenu, 'label', 'Power Spectrum', 'callback', {@fftCallback, timecourse_handle}); +figureData.fftMenu = fftMenu; + + +% show split time course menu +if figureData.num_DataSets > 1 + splitTimecourseMenu = uimenu(utilitiesMenu, 'label', 'Split Timecourses', 'callback', ... + {@splitTimecoursesCallback, timecourse_handle}); % SPLIT TIME COURSES + figureData.splitTimecourseMenu = splitTimecourseMenu; +end + +% Plot Help menu +helpMenu = uimenu(timecourse_handle, 'label', 'GIFT-Help'); +% Time courses help +timecoursesHelpMenu = uimenu(helpMenu, 'label', 'Expanded View of Timecourse', 'callback', ... + 'icatb_openHTMLHelpFile(''icatb_expanded_view_tc.htm'');'); +figureData.fftMenu = fftMenu; + +% Initialise vars +sortingCriteria = []; +sortingType = []; + +% get the sorting criteria and sorting type +if isfield(figureData, 'sortParameters') + sortingCriteria = lower(figureData.sortParameters.sortingCriteria); + sortingType = lower(figureData.sortParameters.sortingType); +end + +if strcmp(sortingCriteria, 'multiple regression') & ~strcmp(sortingType, 'spatial') + % adjust ICA only for multiple regression + adjustICAMenu = uimenu(optionsMenu, 'label', 'Adjust ICA', 'callback', ... + {@adjustICACallback, timecourse_handle}); % ADJUST ICA + % Regular method to compute event average + eventAvgMenu = uimenu(utilitiesMenu, 'label', 'Event Average (Regular Method)', 'callback', ... + {@eventRelatedCallback, timecourse_handle}, 'userdata', figureData.sortParameters.refInfo, 'tag', 'event_avg_regular'); % EVENT AVG + % Deconvolution method to compute event average + deconv_eventAvgMenu = uimenu(utilitiesMenu, 'label', 'Event Average (Deconvolution Method)', 'callback', ... + {@eventRelatedCallback, timecourse_handle}, 'userdata', figureData.sortParameters.refInfo, 'tag', 'event_avg_deconv'); % Deconv method for EVENT AVG + figureData.adjustICAMenu = adjustICAMenu; + figureData.eventAvgMenu = eventAvgMenu; + figureData.deconv_eventAvgMenu = deconv_eventAvgMenu; +elseif strcmp(sortingCriteria, 'correlation') & ~strcmp(sortingType, 'spatial') + % Regular method to compute event average + eventAvgMenu = uimenu(utilitiesMenu, 'label', 'Event Average (Regular Method)', 'callback', ... + {@eventRelatedCallback, timecourse_handle}, 'userdata', figureData.sortParameters.refInfo, 'tag', 'event_avg_regular'); % EVENT AVG + % Deconvolution method to compute event average + deconv_eventAvgMenu = uimenu(utilitiesMenu, 'label', 'Event Average (Deconvolution Method)', 'callback', ... + {@eventRelatedCallback, timecourse_handle}, 'userdata', figureData.sortParameters.refInfo, 'tag', 'event_avg_deconv'); % Deconv method for EVENT AVG + figureData.eventAvgMenu = eventAvgMenu; + figureData.deconv_eventAvgMenu = deconv_eventAvgMenu; +end + +% Save figure data menu +saveTimecourseDataMenu = uimenu(optionsMenu, 'label', 'Save Figure Parameters', 'callback', ... + {@saveTimecourseDataCallback, timecourse_handle}, 'userdata', outputDir); + +% Save figure data menu +loadTimecourseDataMenu = uimenu(optionsMenu, 'label', 'Load Figure Parameters', 'callback', ... + {@loadTimecourseDataCallback, timecourse_handle}, 'userdata', outputDir); + +% Procedure: Initial plot will be an exact replica of the draw time courses +% figure but plots in expanded view. Options can be selected using ICA +% Options window. The results can be viewed using plot button. + +% draw axis +axesPosition = [0.05 .1 .8 .8]; +axesTag = 'plot'; +% draw plot button +plotWidth = 0.1; plotHeight = 0.1; +pos = [axesPosition(1) + axesPosition(3) + 0.02 0.5 - 0.5*plotHeight plotWidth plotHeight]; +plotH = icatb_uicontrol('parent', timecourse_handle, 'units', 'normalized', 'style', 'pushbutton', 'string', ... + 'Plot', 'position', pos, 'callback', {@plotTC_expandedMode, timecourse_handle}, 'tag', 'plot_button'); +figureData.axesPosition = axesPosition; +figureData.axesTag = axesTag; + +set(timecourse_handle, 'userdata', figureData); + +plotTC_expandedMode(plotH, [], timecourse_handle); % execute the plot time course callback + +function plotTC_expandedMode(handleObj, event_data, handles) +% plot time courses in expanded mode + +% get the options from the Time course options menu +% pass the handle visibility off +% get the results here. + +% use the undetrended timecourse +% apply smoothing defaults +% remove the mean (detrend(.., 0)) +% use the sorting criteria as selected in sort components GUI + +% adjust ICA uses the modeling parameters to remove the nuisance parameters +% and plots in the same axis + +% Utilities menu use the options from the plot of the timecourse: +% 1ike split timecourse, event average + +set(handles, 'pointer', 'watch'); + +if ~exist('flag_calculate_stats', 'var') + flag_calculate_stats = 'yes'; +end + +% set the fields from timecourse options to figure data +figureData = get(handles, 'userdata'); % get the figure data + +%%%%%%%%%%%% Options from options window %%%%%%%%%%%%%%%%%%%% +% get the options from the Time course options menu +% pass the handle visibility off +% get the results here. +optionsWindowCallback(figureData.optionsMenu, [], handles, 'off'); % options callback +input_parameters = get(figureData.optionsMenu, 'userdata'); % input parameters +timeCourseOptions = input_parameters.results; % get the time course options +clear input_parameters; +%%%%%%%%%%%% End of Options from options window %%%%%%%%%%%%%%%%%%%% +timecourseFields = fieldnames(timeCourseOptions); % get all the fields + +for ii = 1:length(timecourseFields) + figureData = setfield(figureData, timecourseFields{ii}, getfield(timeCourseOptions, timecourseFields{ii})); +end + +clear timecourseFields; +clear timeCourseOptions; + +% get all the options from the options window +smoothPara = figureData.icaSmoothPara; +smoothValue = figureData.icaSmoothValue; +modelNormalize = figureData.modelNormalize; +%modelZeroMean = figureData.modelZeroMean; +modelLinearDetrend = figureData.modelLinearDetrend; +icaDetrend = figureData.icaDetrend; +icaFlip = figureData.icaFlip; +icaSmoothPara = figureData.icaSmoothPara; +icaSmoothValue = figureData.icaSmoothValue; +eventWindowSize = figureData.eventWindowSize; +eventInterpFactor = figureData.eventInterpFactor; + +% data sets information +num_DataSets = figureData.num_DataSets; +numSubjects = figureData.numSubjects; +numSessions = figureData.numSessions; +diffTimePoints = []; +if isfield(figureData, 'sortParameters') + % different time points variable + if isfield(figureData.sortParameters, 'diffTimePoints') + diffTimePoints = figureData.sortParameters.diffTimePoints; + else + diffTimePoints = figureData.diffTimePoints; + end +end + +% Depending on the title name +% Select the appropriate sorting criteria +title_fig = get(handles, 'name'); +compareTitle = lower(title_fig); +sortingCriteria = []; +sortingType = []; + +if isfield(figureData, 'sortParameters') + sortingCriteria = lower(figureData.sortParameters.sortingCriteria); + sortingType = lower(figureData.sortParameters.sortingType); +end + +% load defaults +icatb_defaults; + +% Screen Color Defaults +global BG_COLOR; +global BG2_COLOR; +global FG_COLOR; +global AXES_COLOR; +global FONT_COLOR; +global LEGENDCOLOR; + +currentAxes = get(handles, 'currentaxes'); + +delete(currentAxes); + +axisH = axes('parent', handles, 'position', figureData.axesPosition, 'tag', figureData.axesTag); + +%%%%%%%%%%% Time course calculations %%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Use original ICA Timecourse and detrend ICA by removing mean +undetrendICA = figureData.undetrendICA; + +if isempty(sortingType) | strcmp(sortingType, 'spatial') + % remove the mean using the specified information in the options window + timecourse = icatb_detrend(undetrendICA, num_DataSets, diffTimePoints, icaDetrend); +else + timecourse = icatb_detrend(undetrendICA, num_DataSets, diffTimePoints, 0); % remove the mean +end + +%timecourse = figureData.icaTimecourse; + +% apply smoothing values +timecourse = icatb_smoothTimecourse(timecourse, diffTimePoints, num_DataSets, 1, ... + icaSmoothPara, icaSmoothValue); + +% ica timecourse calculations +if icaFlip == 1 + timecourse = timecourse*-1; +end +%%%%%%%%%%% End for Time course calculations %%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%%%%%%%%%%%%%%%%% MODEL TC Calculations %%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Model Timecourse calculations +if ~isempty(figureData.modelTimecourse) + % get the model time course + modelTimecourse = figureData.modelTimecourse; + + if modelLinearDetrend == 1 + % remove the linear trend + modelTimecourse = icatb_detrend(modelTimecourse, num_DataSets, diffTimePoints, 1); + end + + % if modelZeroMean == 1 + % % remove the mean + % modelTimecourse = icatb_detrend(modelTimecourse, num_DataSets, diffTimePoints, 0); + % end + % normalize the model time course + if modelNormalize == 1 + modelTimecourse = icatb_normalizeTimecourse(modelTimecourse, timecourse, diffTimePoints, num_DataSets); + end +end + +%%%%%%%%%%%%%%%%%% End for Model Time course Calculations %%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% Plot appropriate sorting criteria title +if strcmp(sortingCriteria, 'correlation') & ~strcmp(sortingType, 'spatial') + [sortingVal, timecourse] = icatb_correlateFunctions(modelTimecourse, timecourse, num_DataSets, diffTimePoints, icaDetrend); + titlestr = ['Correlation = ', num2str(sortingVal)]; +elseif strcmp(sortingCriteria, 'kurtosis') & ~strcmp(sortingType, 'spatial') + [sortingVal, timecourse] = icatb_kurtosis(timecourse, num_DataSets, diffTimePoints, icaDetrend); + titlestr = ['Kurtosis = ', num2str(sortingVal)]; + % Plot the linefit to model timecourse and the ICA timecourse +elseif strcmp(sortingCriteria, 'multiple regression') & ~strcmp(sortingType, 'spatial') + [sortingVal, bRegress, ModelIndices, otherIndices, linearRegress, removeTrend, timecourse] = ... + icatb_multipleRegression(modelTimecourse, timecourse, size(modelTimecourse, 2), num_DataSets, ... + diffTimePoints, icaDetrend); + % store the data for adjusting timecourses + adjustData = struct('bRegress', bRegress, 'ModelIndices', ModelIndices, 'otherIndices', otherIndices, ... + 'linearRegress', linearRegress, 'removeTrend', removeTrend, 'icaLine', 'icaline'); + + set(figureData.adjustICAMenu, 'userdata', adjustData); + + titlestr = ['R-Square Statistic = ', num2str(sortingVal)]; +else + titlestr = title_fig; +end + +titlestr = [titlestr, ' (Temporal Detrend value = ' num2str(icaDetrend), ')']; + +figureData.timecourse = timecourse; % set the field time course to figure data +if num_DataSets > 1 + parameters.numSubjects = numSubjects; % number of subjects + parameters.numSessions = numSessions; % number of sessions + parameters.titleFig = 'Time courses window'; + %parameters.timecourse = timecourse; % time course + if exist('modelTimecourse', 'var') + parameters.modelTimecourse = modelTimecourse; % model time course + else + parameters.modelTimecourse = []; + end + set(figureData.splitTimecourseMenu, 'userdata', parameters); % split time courses menu +end + +storeHandlesCount = 0; legendString = {}; + +% check the model time courses +if ~isempty(figureData.modelTimecourse) + referenceString = 'Reference Function'; + + + %KND: + [nrows, ncols] = size(modelTimecourse); + referenceString = repmat({'Reference Function'},ncols,1); + + %if length(figureData.sortParameters.refInfo.selectedRegressors)==1 + referenceString = cellstr(figureData.sortParameters.refInfo.selectedRegressors.name); + %end + + % plot the reference functions and append the legend string + % Plot all the model timecourses + + for i = 1:ncols + modelPlotHandle(i) = plot(modelTimecourse(:, i), 'y:', 'parent', axisH, 'tag', 'regressor'); + set(modelPlotHandle(i), 'ButtonDownFcn', [ ... + 'set(findobj(gcbf, ''tag'', ''regressor'', ''Type'', ''line''),''Color'',''y'',''LineWidth'',1,''LineStyle'', '':'');' ... + 'set(gcbo,''LineWidth'',2,''LineStyle'', '':'',''Color'', ''y'')' ... + ]); + legendString{length(legendString) + 1} = referenceString{i}; + hold on; + end + % store handles to be used later for plotting legend + storeHandlesCount = storeHandlesCount + 1; + %legendString{length(legendString) + 1} = referenceString; % store the reference string + storeHandles(storeHandlesCount) = modelPlotHandle(end); % store only the end handle + hold on; + if strcmp(sortingCriteria, 'multiple regression') & ~strcmp(sortingType, 'spatial') + % Plot ICA Timecourses + icaTimecourseLine = plot(timecourse, 'm', 'tag', adjustData.icaLine, 'parent', axisH); + hold on; + storeHandlesCount = storeHandlesCount + 1; + storeHandles(storeHandlesCount) = icaTimecourseLine; + legendString{length(legendString) + 1} = 'ICA Timecourse'; + % % plot regressor + lineRegressH = plot(linearRegress, '-.c', 'parent', axisH); + hold off; + storeHandlesCount = storeHandlesCount + 1; + storeHandles(storeHandlesCount) = lineRegressH; + legendString{length(legendString) + 1} = 'Linefit to ICA'; + else + % Plot ICA Time course + icaTimecourseLine = plot(timecourse, 'm', 'parent', axisH); + storeHandlesCount = storeHandlesCount + 1; + storeHandles(storeHandlesCount) = icaTimecourseLine; + legendString{length(legendString) + 1} = 'ICA Timecourse'; + hold off; + end +else + icaTimecourseLine = plot(timecourse, 'm', 'parent', axisH); + storeHandlesCount = storeHandlesCount + 1; + storeHandles(storeHandlesCount) = icaTimecourseLine; + legendString{length(legendString) + 1} = 'ICA Timecourse'; + hold off; +end + +icatb_legend(legendString{:}); + +% Plot a dotted line separating the subjects +% Change later to incorporate different timepoints for each subject +if num_DataSets > 1 + lengthTotalTimecourse = length(timecourse); + %lengthIndividualTimecourse = lengthTotalTimecourse/num_DataSets; + axisYlim = get(axisH, 'ylim'); + ySpacing = (axisYlim(2) - axisYlim(1))/4; + plotYY = (axisYlim(1):ySpacing:axisYlim(2)); + for numdataSets = 2:num_DataSets + hold on; + %lengthIndividualTimecourse = sum(figureData.refInfo.diffTimePoints(1:numdataSets - 1)); + %valX = lengthIndividualTimecourse*(numdataSets - 1); + valX = sum(figureData.sortParameters.diffTimePoints(1:numdataSets - 1)); + plotXX(1:length(plotYY)) = valX; + plot(plotXX, plotYY, '--g', 'parent', axisH); + end +end + +axis(axisH, 'tight'); + +set(axisH, 'YColor', FONT_COLOR, 'XColor', FONT_COLOR); + +set(handles, 'userdata', figureData); + +clear figureData; + +hold off; + +title(titlestr, 'parent', axisH); +set(handles, 'pointer', 'arrow'); + +%%%%%%%%%%%%%%%%%% Function callbacks %%%%%%%%%%%%%%%% +function fftCallback(hObject, event_data, handles) +% powerSpectrum + +psdFile = which('psd.m'); + +if ~isempty(psdFile) + + figureData = get(handles, 'userdata'); + + icatb_defaults; + % Screen Color Defaults + global BG_COLOR; + global BG2_COLOR; + global FG_COLOR; + global AXES_COLOR; + global FONT_COLOR; + global SMOOTHINGVALUE; + global SMOOTHPARA; + + fftHandle = icatb_getGraphics('Power Spectrum', 'normal', 'Power Spectrum Figure'); + timecourse = figureData.timecourse; + fftH = axes('parent', fftHandle, 'units', 'normalized', 'position', [0.1 0.1 0.8 0.8]); + % calculate power spectrum + psd(timecourse, 500); + axis(fftH, 'tight'); + set(fftH, 'YColor', FONT_COLOR, 'XColor', FONT_COLOR); + % set the color to magenta + set(get(gca, 'children'), 'color', [1 0 1]); + clear figureData; +else + disp('Need signal processing toolbox to view power spectrum'); + return; +end +% end for checking psd + +% Splits time courses in the main window if there are more than one time +% course +function splitTimecoursesCallback(handleObj, evd, handles) + +% get the user data associated with the push button +parameters = get(handleObj, 'userdata'); +handles_data = get(handles, 'userdata'); +% Get the variables here +numSubjects = parameters.numSubjects; +numSessions = parameters.numSessions; +titleFig = parameters.titleFig; +%timecourse = parameters.timecourse; +timecourse = handles_data.timecourse; +modelTimecourse = parameters.modelTimecourse; +diffTimePoints = []; +% get different time points +if isfield(handles_data, 'sortParameters') + diffTimePoints = handles_data.sortParameters.diffTimePoints; +end +% convert all vars to structure format +[timeCourseStruct, modelTimeCourseStruct, xAxis] = icatb_convertTo_struct('timecourse', timecourse, 'model', ... + modelTimecourse, 'numSubjects', numSubjects, 'numSessions', numSessions, 'difftimepoints', diffTimePoints); + +% show multiple plots in a figure +icatb_showTimeCourses('TC', timeCourseStruct, 'x_axis', xAxis, 'numsubjects', numSubjects, ... + 'numsessions', numSessions, 'titlefig', 'Splitting Time courses', 'meandisplay', 'yes', 'SEM', ... + 'yes', 'model', modelTimeCourseStruct, 'timecourse_color', 'm', 'model_timecourse_color', 'y:'); + +function eventRelatedCallback(hObject, evd, handles) + +% Computes the event related averages. Gets the ICA time course under +% consideration. If the units is seconds then selected onsets will be +% divided by TR. +% get the event related vector (correct Onset???). Interpolate the time course by a factor +% which is factor to multiply * TR. + +% Get the related sequence where the events occur + +% get the user data +figureData = get(handles, 'userdata'); + +eventAvgObjTag = get(hObject, 'tag'); + +if strcmpi(eventAvgObjTag, 'event_avg_regular') + figTitle = 'Event Average (Regular Method)'; + fprintf('\n'); + disp('Using regular method to compute event average ...'); + fprintf('\n'); +else + figTitle = 'Event Average (Deconvolution Method)'; + fprintf('\n'); + disp('Using deconvolution method to compute event average ...'); + fprintf('\n'); +end + +% get the options from the options menu handle +optionsWindowCallback(figureData.optionsMenu, [], handles, 'off'); % options callback +input_parameters = get(figureData.optionsMenu, 'userdata'); % input parameters +timeCourseOptions = input_parameters.results; % get the time course options +clear input_parameters; +%%%%%%%%%%%% End of Options from options window %%%%%%%%%%%%%%%%%%%% + +timecourseFields = fieldnames(timeCourseOptions); % get all the fields +% set the figure data with the corresponding fields +for ii = 1:length(timecourseFields) + figureData = setfield(figureData, timecourseFields{ii}, getfield(timeCourseOptions, timecourseFields{ii})); +end + +icatb_defaults; + +global FONT_COLOR; + +% Window size +windowSize = figureData.eventWindowSize; % in seconds +interpFactor = figureData.eventInterpFactor; % interpolation factor + +eventData = struct('eventInterpFactor', interpFactor, 'eventWindowSize', windowSize); + +icaTimecourse = figureData.timecourse; % time course + +% Get the information about the data sets here +% number of subjects and sessions +num_DataSets = figureData.num_DataSets; +numSubjects = figureData.numSubjects; +numSessions = figureData.numSessions; +numOfSub = figureData.numOfSub; +numOfSess = figureData.numOfSess; +actualDataSets = numOfSub*numOfSess; +detrendNumber = figureData.icaDetrend; + +% get the subject string to be displayed as a string in listbox +if numSubjects > 1 & numSessions > 1 + count = 0; + for ii = 1:numSubjects + for jj = 1:numSessions + count = count + 1; + subject_string(count).name = ['Subject ', num2str(ii), ' Session ', num2str(jj)]; + end + end +elseif numSubjects > 1 & numSessions == 1 + for ii = 1:numSubjects + subject_string(ii).name = ['Subject ', num2str(ii)]; + end +elseif numSubjects == 1 & numSessions > 1 + for ii = 1:numSessions + subject_string(ii).name = ['Session ', num2str(ii)]; + end +else + subject_string.name = ['Subject''s Session']; +end + +diffTimePoints = []; +% get different time points +if isfield(figureData, 'sortParameters') + diffTimePoints = figureData.sortParameters.diffTimePoints; + if isfield(figureData.sortParameters, 'session_number') + data_sessionNumber = figureData.sortParameters.session_number; + end +end + +% check the spm mat flag +if isfield(figureData, 'spmMatFlag') + spmMatFlag = figureData.spmMatFlag; +else + spmMatFlag = 'not_specified'; +end + +clear figureData; +% convert timecourses to required structure format: incorporate different +% time points +[timeCourses] = icatb_convertTo_struct('timecourse', icaTimecourse, 'numSubjects', numSubjects, 'numSessions', ... + numSessions, 'diffTimePoints', diffTimePoints); + +clear icaTimecourse; + +% get the figure data +refInfo = get(hObject, 'userdata'); + +%%%%%%%%%%%%%%%%% REGRESSOR GUI %%%%%%%%%%%%%%%%%%% +[selectedRegressors] = icatb_returnRegressor(refInfo, num_DataSets, numSubjects, ... + numSessions, spmMatFlag, subject_string); + +countDataSet = 0; +% Loop over number of subjects and sessions +for numSub = 1:numSubjects + % check if the size of the SPM file is greater than one + if size(refInfo.SPMFile, 2) > 1 + spmFileNumber = (numSub - 1)*numSessions + 1; + % get the current spm file + SPMfileName = refInfo.SPMFile(spmFileNumber).name; + spmData = refInfo.spmAppData(spmFileNumber).data; + else + % get the current spm file + SPMfileName = refInfo.SPMFile.name; + % check the special case for session specific regressors + if length(refInfo.spmAppData) > 1 + spmData = refInfo.spmAppData(1).data; + else + spmData = refInfo.spmAppData.data; + end + end + + % SPM information + spmVar = spmData.SPM; + + flag_refFunc = spmData.flag_refFunc; + + clear spmData; + + % get the SPM mat file + TimeResponse = spmVar.xY.RT; + + % Original Interpolation factor + origInterpFactor = eventData.eventInterpFactor; + + % Interpolation factor + interpFactor = ceil(origInterpFactor*TimeResponse); + + % loop over the sessions + for numSess = 1:numSessions + countDataSet = countDataSet + 1; + % pull the session number from the selected regressors + currentRegressor = deblank(selectedRegressors(countDataSet, :)); % get the current regressor + % get the onsets and data session number using the regressor + % name + + if strcmp(lower(flag_refFunc), 'session_specific') + if numSessions == numOfSess + data_sessionNumber = numSess; + end + [temp_data_sessionNumber, onset_number] = icatb_get_onsets(currentRegressor, spmVar); + else + [data_sessionNumber, onset_number] = icatb_get_onsets(currentRegressor, spmVar); + end + + if numSubjects*numSessions == 1 + % displaying the reference function + disp(['Selected reference function to compute event related average for subject''s session is ', currentRegressor]); + elseif numSubjects == 1 & numSessions > 1 + % displaying the reference function + disp(['Selected reference function to compute event related average for session ', num2str(numSess), ' is ', currentRegressor]); + elseif numSessions == 1 & numSubjects > 1 + % displaying the reference function + disp(['Selected reference function to compute event related average for subject ', num2str(numSub), ' is ', currentRegressor]); + else + % displaying the reference function + disp(['Selected reference function to compute event related average for subject ', num2str(numSub), ... + ' session ', num2str(numSess), ' is ', currentRegressor]); + end + + % % determine the selected onset + % % or current onset number + if data_sessionNumber > length(spmVar.Sess) + data_sessionNumber = length(spmVar.Sess); + end + + %%%%%% Modifying selected onset + if strcmp(lower(spmVar.xBF.UNITS), 'scans') + selectedOnset = round(spmVar.Sess(data_sessionNumber).U(onset_number).ons); + else + selectedOnset = round((spmVar.Sess(data_sessionNumber).U(onset_number).ons) / TimeResponse); + end + + timeCourses.sub(numSub).sess(numSess).tc = icatb_detrend(timeCourses.sub(numSub).sess(numSess).tc, 1, ... + length(timeCourses.sub(numSub).sess(numSess).tc), detrendNumber); + + if strcmpi(eventAvgObjTag, 'event_avg_regular') + % Calculate event average + eventAvg = icatb_calculate_eventAvg(timeCourses.sub(numSub).sess(numSess).tc, origInterpFactor, TimeResponse, eventData.eventWindowSize, selectedOnset); + timeAxis = (1:length(eventAvg)) / origInterpFactor; % time axis + else + eventAvg = icatb_calculate_eventAvg_deconv(timeCourses.sub(numSub).sess(numSess).tc, TimeResponse, eventData.eventWindowSize, selectedOnset); + timeAxis = (1:length(eventAvg)); + end + + timeCourseStruct.sub(numSub).sess(numSess).tc = eventAvg; % event average + xAxis.sub(numSub).sess(numSess).tc = timeAxis; % time axis + end + if exist('spmVar', 'var') + clear spmVar; + end +end +% end for loop over number of subjects and sessions + + +% number of data sets is more than 1 +if num_DataSets == 1 + % if the number of data sets is 1 else show the plots in a figure + % browse + + plotH = icatb_getGraphics(figTitle, 'normal', 'figure'); + + plot(timeAxis, eventAvg, 'm'); + + title(figTitle, 'color', FONT_COLOR, 'HorizontalAlignment', 'center'); + + xlabel(['Time in seconds where ', ' window size = ', num2str( eventData.eventWindowSize)]); + + set(gca, 'YColor', FONT_COLOR, 'XColor', FONT_COLOR, 'position', [0.1 0.1 0.8 0.8]); + + axis(gca, 'tight'); + +else + icatb_showTimeCourses('TC', timeCourseStruct, 'x_axis', xAxis, 'numsubjects', numSubjects, 'numsessions', numSessions, ... + 'titlefig', figTitle, 'meandisplay', 'yes', 'SEM', 'yes', 'timecourse_color', 'm'); +end + +% context callback +function adjustICACallback(hObject, evd, handles) +% context menu callback +% open figure window prompting the user to select the reference function +% remove the nuisance parameters and the other model coefficients from the +% ica timecourse +% plot the new ica timecourse + +% get the userdata +adjustData = get(hObject, 'userdata'); + +% store the data for adjusting timecourses +bRegress = adjustData.bRegress; ModelIndices = adjustData.ModelIndices; +otherIndices = adjustData.otherIndices; linearRegress = adjustData.linearRegress; +removeTrend = adjustData.removeTrend; +icaLine = adjustData.icaLine; + +% figure data +figData = get(handles, 'userdata'); + +timecourse = figData.timecourse; +detrendNumber = figData.icaDetrend; + +% check the spm mat flag +if isfield(figData, 'spmMatFlag') + spmMatFlag = figData.spmMatFlag; +else + spmMatFlag = 'not_specified'; +end + +% data information +numSubjects = figData.numSubjects; +numSessions = figData.numSessions; +num_DataSets = figData.num_DataSets; +modelTimecourse = figData.modelTimecourse; + +% reference function information +if ~isfield(figData, 'sortParameters') + error('sortParameters field is missing in figData'); +end +diffTimePoints = figData.sortParameters.diffTimePoints; +refInfo = figData.sortParameters.refInfo; +num_Regress = size(refInfo.modelIndex, 2); + +% spm file names +SPMFile = refInfo.SPMFile; + +if length(SPMFile) > 1 + % spmNames + spmNames = str2mat(SPMFile.name); +else + spmNames = SPMFile.name; +end + +% % details of the help push button +% helpDetails.title = 'Adjust ICA time course'; +% +% D(1).string = ['For adjusting ICA time-course, only one reference function is used. Please click on the left top listbox to select the data set and the reference functions for that data set will be displayed in the left bottom listbox.']; +% D(size(D,2)+1).string = ''; +% D(size(D,2)+1).string = ['Select the reference function in the left bottom listbox and the selected string will appear in the right top listbox. These selections can be made as long as Ok button is pressed.']; +% str = {D.string}; + +% string for help button +%helpDetails.str = str; + +% get the subject string to be displayed as a string in listbox +if numSubjects > 1 & numSessions > 1 + count = 0; + for ii = 1:numSubjects + for jj = 1:numSessions + count = count + 1; + subject_string(count).name = ['Subject ', num2str(ii), ' Session ', num2str(jj)]; + end + end +elseif numSubjects > 1 & numSessions == 1 + for ii = 1:numSubjects + subject_string(ii).name = ['Subject ', num2str(ii)]; + end +elseif numSubjects == 1 & numSessions > 1 + for ii = 1:numSessions + subject_string(ii).name = ['Session ', num2str(ii)]; + end +else + subject_string.name = ['Subject''s Session']; +end + +if (size(refInfo.modelIndex, 2) == 1) + icatb_dialogBox('title', 'Adjust ICA', 'textBody', 'Cannot adjust ICA timecourse as only one regressor is selected during sorting.', ... + 'textType', 'large'); + return; +end + +%%%%%%%%%%%%%%%%% REGRESSOR GUI %%%%%%%%%%%%%%%%%%% + +[selectedRegressors, selectedIndices] = icatb_returnRegressor(refInfo, num_DataSets, numSubjects, ... + numSessions, spmMatFlag, subject_string, 'adjust_ica'); + +% Remove the nuisance parameters and the other than selected model indices +rowStart = 1; +for nn = 1:num_DataSets + rowEnd = sum(diffTimePoints(1:nn)); + % Regressor matrix + [X] = icatb_modelX(modelTimecourse(rowStart:rowEnd, :), diffTimePoints(nn), detrendNumber); + % Total terms + TotalTerms = size(X, 2); + regressIndices = [1:TotalTerms]; + % Not selected indices + not_selected_indices = find(regressIndices ~= selectedIndices(nn)); + % Other indices + otherIndxs = TotalTerms*(nn - 1) + (num_Regress + 1 : TotalTerms); + % Model indices + modelIndxs = TotalTerms*(nn - 1) + (1:num_Regress); + allIndxs = [modelIndxs, otherIndxs]; + notSelIndices = allIndxs(not_selected_indices); + timecourse(rowStart:rowEnd) = timecourse(rowStart:rowEnd) - X(:, not_selected_indices)*bRegress(notSelIndices); + rowStart = rowEnd + 1; +end + +icaTimecourseLine = findobj(handles, 'tag', icaLine); + +axisH = get(handles, 'currentaxes'); + +newStr = ['ICA time course (ICA tc) adjusted by the selected reference function. Click plot to view the old ICA tc.']; +title(newStr, 'parent', axisH); + +% plot the adjusted time course +% Only plot the adjusted time course and don't calculate the ICA time +% course +set(icaTimecourseLine, 'Ydata', timecourse); + +axis(axisH, 'tight'); + +figData.timecourse = timecourse; + +set(handles, 'userdata', figData); + +function optionsWindowCallback(hObject, event_data, handles, figVisibility) +% hObject - menu string +% handles - time course figure + +if ~exist('figVisibility', 'var') + figVisibility = 'on'; +end +input_parameters = get(hObject, 'userdata'); % get the menu user data +defaults = input_parameters.defaults; % get the defaults +inputParameters = input_parameters.inputParameters; % get the input parameters +clear input_parameters; +% plot the figure window to display the options +[output_parameters, valueButton] = icatb_OptionsWindow(inputParameters, defaults, figVisibility); +input_parameters.inputParameters = output_parameters.inputParameters; +input_parameters.defaults = defaults; +input_parameters.results = output_parameters.results; % get the results +set(hObject, 'userdata', input_parameters); % set the object user data (updated options) +% Show the title after the options are updated by clicking on the ok button +if valueButton == 1 + plotH = findobj(handles, 'tag', 'plot_button'); + plotTC_expandedMode(plotH, [], handles); % execute the plot time course callback + % % set the title at the plot window + % axisH = get(handles, 'currentaxes'); + % newStr = ['Click plot to view the time courses with the updated options.']; + % title(newStr, 'parent', axisH); + + % call the plot function here + +end + +function saveTimecourseDataCallback(hObject, event_data, handles) +% save figure data + +outputDir = get(hObject, 'userdata'); +if isempty(outputDir) + outputDir = pwd; +end +figureData = get(handles, 'userdata'); + +% open input dialog box +prompt = {'Enter Valid File Name:'}; +dlg_title = 'Save Figure Parameters as'; +num_lines = 1; +def = {'TimecourseParameters'}; +% save the file with the file name specified +fileName = icatb_inputdlg2(prompt, dlg_title, num_lines, def); + +if ~isempty(fileName) + % make a full file + fileName = [fileName{1}, '.mat']; + fileName = fullfile(outputDir, fileName); + icatb_save(fileName, 'figureData'); + disp(['Figure parameters are saved in file: ', fileName]); +end + +function loadTimecourseDataCallback(hObject, event_data, handles) +% load figure data + +try + outputDir = get(hObject, 'userdata'); + + global FIGURE_DATA + + % open file selection box + [P] = icatb_selectEntry('typeEntity', 'file', 'title', 'Select Timecourse Parameters File', 'filter', '*.mat', ... + 'startPath', outputDir); + if ~isempty(P) + load(P); + if ~exist('figureData', 'var') + error('Not a valid timecourse parameters file'); + end + FIGURE_DATA = figureData; + clear figureData; + D(1).string = 'Global FIGURE_DATA variable is created.'; + D(size(D, 2) + 1).string = ''; + D(size(D, 2) + 1).string = 'Type the following statements at the command prompt:'; + D(size(D, 2) + 1).string = ''; + D(size(D, 2) + 1).string = 'global FIGURE_DATA; FIGURE_DATA'; + disp(str2mat(D.string)); + end +catch + icatb_errorDialog(lasterr, 'Figure Parameters'); + icatb_displayErrorMsg; +end \ No newline at end of file diff --git a/icatb/icatb_loadSPM_new.m b/icatb/icatb_loadSPM_new.m new file mode 100644 index 0000000..a239034 --- /dev/null +++ b/icatb/icatb_loadSPM_new.m @@ -0,0 +1,467 @@ +function [spmData] = icatb_loadSPM_new(varargin) + +% Procedure: There are four steps: +% First Step: Check SPM design matrix and do error checking. +% ************** Store the data in spmData ****************** +% Store the following fields: nscan, flag_refFunc ('all' or +% 'session_specfic') and data_sessionNumber. +% Update SPM.Sess and store in spmData as field SPM. +% +% Second Step: Get session specific regressors if possible. +% ************** Store the data in spmData ****************** +% store sel_refnames and sel_indices as fields in spmData +% +% Third Step: Select regressors +% get model time course information as modelT +% store modelT in spmData with the following fields: +% 1. designMatrixName +% 2. selectedRegressors +% 3. name (all regressors available) +% 4. nsess - modified length of scans +% 5. actualIndices W.r.t SPM.xX.name as sel_indices +% ************** Store the data in spmData ****************** +% store modelT information +% +% Fourth Step: Getting time course information +% Use SPM and modelT fields in spmData to get the time courses and modified +% SPM structure +% +% Inputs: +% 1. spmName - full file name of the spm design matrix +% 2. countTimePoints - length of time points for each session and is a +% vector tor like [220 220 ...] depending on the number of sessions used. +% 3. optional argument: sessionNumber to pull out the reference function names +% time courses of that session. +% +% Output: +% spmData containing the information about selected time courses and +% regressors + +% Initialise vars +spmName = []; +countTimePoints = []; +data_sessionNumber = []; % session number is used to pull the reference function names +flag_refFunc = 'all'; % by default pull all the reference functions names and timecourses +analType = 'gui'; % default is GUI +prompt = 'Select Timecourse'; % title of the listbox +typeStr = 'single'; % selection is single by default +maxSelection = []; % Maximum selection +check_spm_design_matrix = 'yes'; +check_regressors = 'no'; +flag_selecting_regressors = 'no'; +get_timecourses = 'no'; +title_fig = 'Reference Time courses'; + +% loop over number of input arguments +for ii = 1:2:nargin + if strcmp(lower(varargin{ii}), 'spmname') + spmName = varargin{ii + 1}; + elseif strcmp(lower(varargin{ii}), 'counttimepoints') + countTimePoints = varargin{ii + 1}; + elseif strcmp(lower(varargin{ii}), 'data_sessionnumber') + data_sessionNumber = varargin{ii + 1}; + elseif strcmp(lower(varargin{ii}), 'analtype') + analType = varargin{ii + 1}; + elseif strcmp(lower(varargin{ii}), 'typestr') + typeStr = varargin{ii + 1}; + elseif strcmp(lower(varargin{ii}), 'maxselection') + maxSelection = varargin{ii + 1}; + elseif strcmp(lower(varargin{ii}), 'flag_selecting_regressors') + flag_selecting_regressors = varargin{ii + 1}; + elseif strcmp(lower(varargin{ii}), 'check_spm_design_matrix') + check_spm_design_matrix = lower(varargin{ii + 1}); + elseif strcmp(lower(varargin{ii}), 'check_regressors') + check_regressors = varargin{ii + 1}; + elseif strcmp(lower(varargin{ii}), 'get_timecourses') + get_timecourses = varargin{ii + 1}; + elseif strcmp(lower(varargin{ii}), 'spmdata') + spmData = varargin{ii + 1}; + elseif strcmp(lower(varargin{ii}), 'prompt') + prompt = varargin{ii + 1}; + elseif strcmp(lower(varargin{ii}), 'title_fig') + title_fig = varargin{ii + 1}; + end +end + +try + + if isempty(data_sessionNumber) + data_sessionNumber = 1; + end + + % check the design matrix + if strcmp(check_spm_design_matrix, 'yes') + + % load the spm file + % Note: storing in spmVar as loading SPM design matrix produces SPM variable + % which is same as function name + spmVar = load(spmName); + + % Check the existence of SPM variable + if ~exist('spmVar', 'var') + error('SPM variable doesn''t exist. Please check the .MAT file.'); + end + + fieldNamesOfSPM = fieldnames(spmVar); % get the field names + + spmMat = getfield(spmVar, fieldNamesOfSPM{1}); % get the SPM design matrix here + + clear spmVar; + + % get the number of scans + if isfield(spmMat , 'nscan') + nscan = spmMat.nscan; + else + error('Field nscan doesn''t exist in structure SPM .'); + end + + % number of reference functions + if isfield(spmMat , 'xX') + numRefFunc = length(spmMat.xX.name); + if not(isempty(spmMat.xCon)) + for iCon=1:length(spmMat.xCon) + if isequal('T', spmMat.xCon(iCon).STAT) + spmMat.xX.X = [ spmMat.xX.X spmMat.xX.X(:,1:numRefFunc)*spmMat.xCon(iCon).c ]; + spmMat.xX.name = [spmMat.xX.name {spmMat.xCon(iCon).name}]; + end + end + end + numRefFunc = length(spmMat.xX.name); + else + error('Field xX doesn''t exist in structure SPM .'); + end + + + + errorString = ['SPM design matrix doesn''t match with the data']; + + % Modifying nscan + % check the nscan with the time points + % takes care of different or same session timings + % automatically converts to vector form. + if length(nscan) == 1 + if nscan == sum(countTimePoints) + nscan = countTimePoints; % update nscan with time points + SessInfo = spmMat.Sess; % session information + flag_refFunc = 'session_specific'; % pass flag + else + if nscan == countTimePoints(data_sessionNumber) + data_sessionNumber = 1; % update data_sessionNumber + else + error(errorString); + end + end + else + lengthScan = length(nscan); + lengthTimePoints = length(countTimePoints); + if sum(nscan) == sum(countTimePoints) & lengthScan == lengthTimePoints + if nscan == countTimePoints + flag_refFunc = 'session_specific'; + else + error(errorString); + end + else + matchedIndex = find(nscan == countTimePoints(data_sessionNumber)); % find the matching entries + if isempty(matchedIndex) + error(errorString); + elseif length(matchedIndex) == 1 + data_sessionNumber = matchedIndex; % update the data_sessionNumber + flag_refFunc = 'session_specific'; % pass flag + end + end + end + + if exist('SessInfo', 'var') + % get correct onsets from the vectorized session information + if length(nscan) > length(SessInfo) & length(SessInfo) == 1 + temp = repmat(SessInfo, 1, length(nscan)); % replicate with length of the nscan + % loop over number of onsets + for jj = 1:length(SessInfo.U) + % get the concatenated onset + currentOnset = SessInfo.U(jj).ons; + startTp = 1; numberToSubtract = 0; + % loop over number of nscan + for ii = 1:length(nscan) + endTp = sum(nscan(1:ii)); + subtractedOnset = currentOnset - numberToSubtract; % get the corrected onset for that session + temp(ii).U(jj).ons = subtractedOnset(find(subtractedOnset < nscan(ii) & subtractedOnset >= 0)); % get the onsets + startTp = endTp + 1; + numberToSubtract = endTp; + end + % end loop for number of nscan + end + % end loop for number of onsets + spmMat.Sess = temp; + clear temp; + end + end + + % clear variables in SPM structure to save memory + xBF.UNITS = spmMat.xBF.UNITS; + repetition_time = spmMat.xY.RT; + spmMat = rmfield(spmMat, 'xBF'); + spmMat.xBF = xBF; + clear xBF; + spmMat = rmfield(spmMat, 'xY'); + spmMat.xY.RT = repetition_time; + + + % remove all the other fields + fieldsReq1 = {'nscan', 'xX', 'xBF', 'xY', 'Sess'}; + % include only the required fields + spmMat = icatb_includeFields_struct(spmMat, fieldsReq1); + clear fieldsReq1; + Sess_new = spmMat.Sess; + spmMat = rmfield(spmMat, 'Sess'); + + if isfield(Sess_new, 'U') + + if ~isempty(Sess_new(1).U) + Sess_new = icatb_includeFields_struct(Sess_new, {'U'}); + fieldsReq1 = {'name', 'ons'}; + % include only the required fields + for ii = 1:length(Sess_new) + Sess_new(ii).U = icatb_includeFields_struct(Sess_new(ii).U, fieldsReq1); + end + clear fieldsReq1; + end + + end + + spmMat.Sess = Sess_new; + clear Sess_new; + + % remove unnecessary fields + XField = spmMat.xX.X; + nameField = spmMat.xX.name; + spmMat = rmfield(spmMat, 'xX'); + spmMat.xX.X = XField; + spmMat.xX.name = nameField; + clear XField; clear nameField; + + + spmMat.nscan = nscan; + % store the data in spmData + spmData.SPM = spmMat; + spmData.nscan = nscan; + spmData.flag_refFunc = flag_refFunc; + spmData.data_sessionNumber = data_sessionNumber; + clear spmMat; + end + % end for checking SPM design matrix + + % get session specific regressors if possible + if strcmp(lower(check_regressors), 'yes') + if ~exist('spmData', 'var') + error('spmData structure should be passed'); + end + spmMat = spmData.SPM; % number of scans + nscan = spmData.nscan; + flag_refFunc = spmData.flag_refFunc; + data_sessionNumber = spmData.data_sessionNumber; + %%%%%%%%%%%%%%%%%%% + % selection process + + allNames = spmMat.xX.name; % all reference function names + % Initialise selected reference function names and indices + sel_refnames = {}; + sel_indices = []; + if strcmp(lower(flag_refFunc), 'session_specific') & ~isempty(data_sessionNumber) + count = 0; + % pull the session specific or all regressors names + for ii = 1:numRefFunc + firstBracket = find(allNames{ii} == '('); + endBracket = find(allNames{ii} == ')'); + if isempty(firstBracket) || isempty(endBracket) + warning('Contrasts appear in all sessions') + currentNumber = data_sessionNumber;%:length(spmMat.Sess); + else + currentNumber = str2num(allNames{ii}(firstBracket(1) + 1:endBracket(1) - 1)); % get the session number + end + if currentNumber == data_sessionNumber + count = count + 1; + sel_refnames{count} = allNames{ii}; + sel_indices(count) = ii; + end + end + end + % check if the selected reference function names is empty + if isempty(sel_refnames) + sel_refnames = allNames; + sel_indices = (1:length(sel_refnames)); + end + + for ii = 1:length(sel_refnames) + options(ii).string = sel_refnames{ii}; + end + sel_refnames = str2mat(options.string); % convert cell to matrix + clear options; + % store the data + spmData.sel_refnames = sel_refnames; % selected reference function names + spmData.sel_indices = sel_indices; % selected session specific indices + end + % end for checking regressors + + % get the regressors of interest + if strcmp(lower(flag_selecting_regressors), 'yes') + if ~exist('spmData', 'var') + error('spmData structure should be passed'); + end + sel_refnames = spmData.sel_refnames; % selected reference function names + sel_indices = spmData.sel_indices; % selected session specific indices + spmMat = spmData.SPM; + nscan = spmData.nscan; + + % get the indices of the selected regressor names + if strcmp(lower(analType), 'batch') + if isfield(design_Info, 'refFunNames') + selectedRefFunNames = design_Info.refFunNames; + else + error('Specify the reference function names for the batch analysis'); + end + % check the index + for ii = 1:length(selectedRefFunNames) + tempIndex = strmatch(lower(selectedRefFunNames{ii}), lower(sel_refnames), 'exact'); + if isempty(tempIndex) + error(['Specified reference function with name ', selectedRefFunNames{ii}, ' doesn''t exist']); + end + % store the indices + getIndex(ii) = tempIndex; + end + else + if strcmp(typeStr, 'single') + [getIndex, name_button] = icatb_listdlg('PromptString', prompt, 'SelectionMode','single', 'ListString', sel_refnames, ... + 'movegui', 'center', 'windowStyle', 'modal', 'title_fig', title_fig); + else + if isempty(maxSelection) + [getIndex, name_button] = icatb_listdlg('PromptString', prompt, 'SelectionMode','multiple',... + 'ListString', sel_refnames, 'movegui', 'center', 'windowStyle', 'modal', ... + 'title_fig', title_fig); + else + [getIndex, name_button] = icatb_listdlg('PromptString', prompt, 'SelectionMode','multiple',... + 'ListString', sel_refnames, 'movegui', 'center', 'windowStyle', 'modal', ... + 'maxSelection', maxSelection, 'title_fig', title_fig); + end + end + if isempty(getIndex) + error('Reference function name should be selected in order to select the model time course'); + end + end + clear options; + %get information from SPM design matrix + spmData.selectedRegressors = sel_refnames(getIndex, :); % store the selected regressors + spmData.getIndex = getIndex; + spmData.nsess = length(nscan); % get the number of sessions in design matrix + + end + % end for getting the regressors of interest + + % get the time courses of interest + if strcmp(lower(get_timecourses), 'yes') + if ~exist('spmData', 'var') + error('spmData structure should be passed'); + end + spmMat = spmData.SPM; % get the SPM structure + numRefFunc = length(spmMat.xX.name); % number of reference functions + nscan = spmMat.nscan; % number of scans + data_sessionNumber = spmData.data_sessionNumber; % session number + flag_refFunc = spmData.flag_refFunc; + sel_refnames = spmData.sel_refnames; % selected reference function names + sel_indices = spmData.sel_indices; % selected session specific indices + getIndex = spmData.getIndex; + selectedRegressors = spmData.selectedRegressors; + + % check the size of the time course + if isfield(spmMat.xX, 'X') + % pull the time course + checkX = spmMat.xX.X; + % check the size of the design matrix + sizeX = size(checkX); + + if sizeX(1) == 1 & sizeX(2) == numRefFunc*sum(nscan) + checkX = checkX'; + % check the size of the design matrix + sizeX = size(checkX); + end + + if sizeX(1) == numRefFunc & sizeX(2) == sum(nscan) + checkX = checkX'; + checkX = reshape(checkX, prod(size(checkX)), 1); + % check the size of the design matrix + sizeX = size(checkX); + end + + % modify the model time course as nrows == sum(nscan), ncols = num + % reference functions + %disp('Checking SPM.Mat file ...'); + if sizeX(2) == numRefFunc + %disp('SPM.mat is compatible with the GIFT.'); + elseif sizeX(2) == 1 & sizeX(1) == sum(nscan)*numRefFunc + numberScans = sum(nscan); + temp = zeros(numberScans, numRefFunc); + for ii = 1:numRefFunc + temp(:, ii) = checkX(numberScans*(ii - 1) + 1 : numberScans*ii, 1); + end + clear checkX; + %SPMFile = fullfile(inputDir, 'newSPM.mat'); + spmMat.xX.X = temp; + clear temp; + disp('Modified the model time course to make compatible with the GIFT'); + % save the modified file + %save(SPMFile, 'SPM'); + %disp(['Modified the model time course. Please use the new SPM design matrix ', SPMFile, ' to do temporal sorting.']); + else + disp('The specified design matrix is not compatible with the GIFT software.'); + end + + else + error('Field X doesn''t exist in structure SPM .xX'); + end + + % converted SPM time course to (sum(nscans) by numRefFunc) format + % check if the reference functions are session_specific or all + if strcmp(lower(flag_refFunc), 'session_specific') & ~isempty(data_sessionNumber) + selTimecourses = spmMat.xX.X(:, sel_indices(getIndex)); + startTp = 1; + for ii = 1:data_sessionNumber + endTp = sum(nscan(1:ii)); + temp = selTimecourses(startTp:endTp, :); + startTp = endTp + 1; + end + timecourse = temp; % modified time course + spmData.nsess = 1; % number of sessions + clear temp; + else + for ii = 1:length(getIndex) + % get the data session number + [sesNum] = icatb_get_onsets(deblank(selectedRegressors(ii, :)), spmMat); + if sesNum == 1 + startTp = 1; + endTp = nscan(1); + else + startTp = sum(nscan(1 : sesNum - 1)) + 1; % sum until length of nscans - 1 + endTp = sum(nscan(1:sesNum)); % sum until length of nscans + end + timecourse(:, ii) = spmMat.xX.X(startTp:endTp, sel_indices(getIndex(ii))); + end + end + spmData.timecourse = timecourse; + clear timecourse; + % Fields required: + fieldsRequired = {'xBF', 'xY', 'Sess', 'nscan'}; + spmTemp = struct; + for ii = 1:length(fieldsRequired) + spmTemp = setfield(spmTemp, fieldsRequired{ii}, getfield(spmMat, fieldsRequired{ii})); + end + clear spmMat; + % % clear variables in SPM structure to save memory + spmData.SPM = spmTemp; + + end + % end for getting time courses of interest + +catch + + icatb_displayErrorMsg; + +end \ No newline at end of file diff --git a/idxmember.m b/idxmember.m new file mode 100644 index 0000000..717a11d --- /dev/null +++ b/idxmember.m @@ -0,0 +1,28 @@ +function [loc,nzloc,nmemb]=idxmember(A,S,varargin) +% [loc,nzloc,nmemb]=idxmember(A,S) indexes of values A in the set S. +% loc: indices of values from A found in S (0 if not found) +% nzloc: list of non-null indices, ie: S(nzloc) == intersect(A,S) +% nmemb: values of A that are not in S, ie: nmemb == setdiff(A,S) +% +% [...] = idxmember(A,S,'rows') +% +% See: ISMEMBER + +sizeS=size(S); +sizeA=size(A); +if (iscell(S)) + S=S(:); +end +if ischar(A) + sizeA=[1 1]; +elseif (iscell(A)) + A=A(:); +end +[ignore,loc]=ismember(A,S,varargin{:}); +loc=reshape(loc, sizeA); +if nargout>1 + nzloc=nonzeros(loc); +end +if nargout>2 + nmemb=A(find(loc==0)); +end \ No newline at end of file diff --git a/iff.m b/iff.m new file mode 100644 index 0000000..2bf9dc3 --- /dev/null +++ b/iff.m @@ -0,0 +1,42 @@ +function [x]=iff(test,vt,vf) +% iff - +% [val] = iff(X,vtrue,vfalse) outputs vtrue (resp. vfalse) if X is true +% (resp. false) +% +% vtrue/vfalse can be numeric, char or a cell of char +% X can be a ND array +% +% [val] = iff(X,@fun) returns those values in X that match the test +% function @fun +% +% [val] = iff(X,@fun,vf) returns val(i)=X(i) that match the test +% function @fun, and val(i) = vf otherwise. +% +converted2cell=0; +if isa(vt, 'function_handle') + if ~exist('vf','var') + x=test(vt(test)); + return + else + x=test; + x(~vt(test))=vf; + return + end +end +if isempty(vt) | isempty(vf) | (ischar(vt) & length(vt)>1) | (ischar(vf) & length(vf)>1) + x=repmat([{vt} ; {vf}], [1 size(test)]); + converted2cell=1; +else + x=repmat([vt ; vf], [1 size(test)]); +end +x=x([reshape(logical(test), [1 size(test)]) ; reshape(~logical(test), [1 size(test)]) ]); +x=reshape(x, size(test)); +if converted2cell + if length(x)==1 + x=x{1}; + else + warning('IFF:ConversionToCell', 'array was converted to cell') + end +end + +return diff --git a/image_spm.m b/image_spm.m new file mode 100644 index 0000000..88de1bd --- /dev/null +++ b/image_spm.m @@ -0,0 +1,59 @@ +function [hp]=image_spm(y,v,pos) +% image_spm - display a MRI slice +% [hp]=image_spm(hdr,vol,pos) +% hdr: SPM Analyze header +% vol: SPM Analyze volume +% pos: position of the slice in mm. +% To slice along one direction, put the other dimension to NaN. +% E.g. [NaN NaN 0] will display a Z=0 slice (default) +% + +if nargin<3 + pos=[0 0 0]; +end + +slicing=find(~isnan(pos)); +if length(slicing)~=1 + slicing=3; +end + +xyz=[[eye(3) ; diag(y.dim(1:3))]-1 ones(6,1)]*y.mat'; +xyz=[diag(xyz(1:3,1:3)) diag(xyz(4:6,1:3))]; +dims=y.dim(1:3); +xyz=[xyz dims(:)]; + +% Position of the slice in the voxel cube +vpos=diag(diag(pos(:)-y.mat(1:3,4))*inv(y.mat(1:3,1:3))'); + +switch slicing + case 1 + img=v(vpos(1),:,:); + case 2 + img=v(:,vpos(2),:); + case 3 + img=v(:,:,vpos(3)); +end +xyz(slicing,:)=[pos(slicing) pos(slicing) 1]; + +[X,Y,Z]=ndgrid(... + linspace(xyz(1,1),xyz(1,2),xyz(1,3)),... + linspace(xyz(2,1),xyz(2,2),xyz(2,3)),... + linspace(xyz(3,1),xyz(3,2),xyz(3,3))); +X=squeeze(X); +Y=squeeze(Y); +Z=squeeze(Z); +img=squeeze(img); +if isempty(get(0, 'CurrentFigure')) | isempty(get(gcf, 'CurrentAxes')) + NewAxes=1; +else + NewAxes=0; +end +hp=surf(X,Y,Z,img) +set(hp, 'edgecolor', 'none') +set(hp, 'AlphaData', double(img>0)) +set(hp, 'FaceAlpha', 'interp') +axis image +if NewAxes + view(60,30) +end + diff --git a/imagepval.m b/imagepval.m new file mode 100644 index 0000000..2576581 --- /dev/null +++ b/imagepval.m @@ -0,0 +1,10 @@ +function [hp]=imagepval(p,thd) +% imaepval - image p-values +if nargin<2 + thd=0, +end +imagesc(-log(p)) +colorbar +colormap([.6 .6 .6 ; jet]) +set(gca,'CLim', [-log(thd) max(get(gca,'CLim'))]) + diff --git a/imax.m b/imax.m new file mode 100644 index 0000000..9aa0d6e --- /dev/null +++ b/imax.m @@ -0,0 +1,28 @@ +function [i,v] = imax(x,varargin) +%IMAX - Retrieves the index of the maximum value across multiple dimensions +% [I,V] = imax(X); +% [I,V] = imax(X,[],dim) +% I is the index (in the given dimension) of the maximum +% V is/are the values +% +% Example: +% >> imax(magic(3), [], [1 2]) +% ans= 3 2 +% I.e. the maximum of the magic square is on the 3rd row and 2nd column +% +% See also: max2 + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2006 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2006-02-20 Creation +% +% ----------------------------- Script History --------------------------------- + + +[v,i]=max2(x,varargin{:}); \ No newline at end of file diff --git a/img_labels.m b/img_labels.m new file mode 100644 index 0000000..8df2729 --- /dev/null +++ b/img_labels.m @@ -0,0 +1,18 @@ +function [h]=img_labels(xyz,ref,labels) +% img_labels - add labels on a MR slice +% [h]=img_labels(xyz,ref,labels) + +if nargin<3 + labels=cellstr(num2str([1:max(ref)]')); +end +h=[]; +for i=unique(ref)' + if i>0 + centroid=mean(xyz(find(i==ref),:),1); + h=[h; .... + text( centroid(1),centroid(2),centroid(3), labels{i}) ... + plot3(centroid(1),centroid(2),centroid(3), 'x', 'MarkerSize', 10)]; + + end +end +set(h(:,1), 'Interpreter', 'none') \ No newline at end of file diff --git a/img_slice.m b/img_slice.m new file mode 100644 index 0000000..1d0196e --- /dev/null +++ b/img_slice.m @@ -0,0 +1,175 @@ +function [hp]=img_slice(hdr,pos,v) +%IMG_SLICE - display a MRI slice +% +% [hp]=img_slice(hdr,pos,v) +% hdr: SPM Analyze header (or filename) +% pos: position of the slice in mm. +% To slice along one direction, put the other dimensions to NaN. +% E.g. [NaN NaN 0] will display a Z=0 slice +% Default, Z=0 if = in the cube, otherwise in the middle +% v: SPM Analyze volume of data (if empty, read the hdr.fname) +% +% [hp]=img_slice(hdr,pos,['calculation'],...) does a calculation on the data +% before plotting 3D data are in X. +% Ex: img_slice(hdr,'X=X(:)-min(X(:))',...) +% Note: Data are reshaped to their original size if vectorized. +% +% Example +% >> img_slice +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2009 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2009-07-03 Creation +% +% ----------------------------- Script History --------------------------------- +if nargin<1 + hdr=[]; +end +if nargin<3 + v=[]; +end +if isempty(hdr) && isempty(v) + hdr = fullfile(spm('dir'), 'canonical','single_subj_T1.nii'); + if ~exist(hdr,'file') + hdr = fullfile(spm('dir'), 'canonical','single_subj_T1.img'); + end + if ~exist(hdr,'file') + hdr = fullfile(spm('dir'), 'canonical','single_subj_T1.mnc'); + end +end +if ischar(hdr) + if exist(hdr,'file') + hdr=spm_vol(hdr); + else + error('File doesn''t exist: %s',y) + end +end +if nargin<2 + pos=[]; +end +if ischar(v) + calc=v; + v=[]; +else + calc=[]; +end +if size(calc, 1)>1 + error('Calculation should be a 1-line text'); +end +if isempty(v) + v=spm_read_vols(hdr); +end +if nargin<4 + thd=0; +end +if nargin<5 + dynamic=0; +end + +xyz=[[eye(3) ; diag(hdr.dim(1:3))]-1 ones(6,1)]*hdr.mat'; +xyz=[diag(xyz(1:3,1:3)) diag(xyz(4:6,1:3))]; +dims=hdr.dim(1:3); +XYZ=[xyz dims(:)]; +if isempty(pos) + if XYZ(3,1)<0 & XYZ(3,2)>0 + pos=[NaN NaN 0]; + else + pos=[NaN NaN (XYZ(3,2)-XYZ(3,1))/2]; + end +end +if numel(pos) ~= 3 + error('Position of the slices should be given in XYZ 3-coordinate vector'); +end +pos = pos(:); + +if isempty(get(0, 'CurrentFigure')) | isempty(get(gcf, 'CurrentAxes')) + NewAxes=1; +else + NewAxes=0; +end +holdstate=ishold; +if ~isempty(calc) + [v]=calculation(v,calc); +end + +bbox = [XYZ(:,1:2)]'; + +hp=[]; +for slicing = find(~isnan(pos(:)')); + p=zeros([1 3]); + p(slicing)=pos(slicing); + if not((XYZ(slicing,1)<=p(slicing) & p(slicing)<=XYZ(slicing,2)) | (XYZ(slicing,2)<=p(slicing) & p(slicing)<=XYZ(slicing,1))) + Slice out of bound! + letter = 'XYZ'; + warning('Slice at %s=%g is out of bounds [%g %g]!', letter(slicing), p(slicing), XYZ(slicing,1),XYZ(slicing,2)); + else + + % Position of the slice in the voxel cube + vpos=diag(diag(p(:)-hdr.mat(1:3,4))*inv(hdr.mat(1:3,1:3))'); + + vpos=round(vpos); + switch slicing + case 1 + img=v(vpos(1),:,:); + case 2 + img=v(:,vpos(2),:); + case 3 + img=v(:,:,vpos(3)); + end + xyz=XYZ; + xyz(slicing,:)=[p(slicing) p(slicing) 1]; + + [X,Y,Z]=ndgrid(... + linspace(xyz(1,1),xyz(1,2),xyz(1,3)),... + linspace(xyz(2,1),xyz(2,2),xyz(2,3)),... + linspace(xyz(3,1),xyz(3,2),xyz(3,3))); + X=squeeze(X); + Y=squeeze(Y); + Z=squeeze(Z); + img=squeeze(img); + + hp=[hp surf(X,Y,Z,img)]; + set(hp(end), 'AlphaData', double(img>thd)) + hold on + + l = [bbox].*[logical(p);logical(p)] + ones(2,1)*(pos'.*(~p)) + + line(l(:,1),l(:,2),l(:,3)) + if 1%dynamic + setappdata(hp(end),'data', v); + setappdata(hp(end),'XYZ', XYZ); + setappdata(hp(end),'pos', pos); + end + end +end +if ~isempty(hp) + set(hp, 'edgecolor', 'none') + %set(hp, 'FaceAlpha', 'interp') + set(hp, 'FaceAlpha', 1) + axis image + if NewAxes + view(60,30) + end + if ~holdstate + hold('off') + end +end + + +function [v]=calculation(v,calc) +% Process this independently of the main workspace +X=v; +try + [t,X]=evalc(calc); +catch + evalc(calc); +end +v=reshape(X,size(v)); \ No newline at end of file diff --git a/imgfilt.m b/imgfilt.m new file mode 100644 index 0000000..9777819 --- /dev/null +++ b/imgfilt.m @@ -0,0 +1,143 @@ +function [Y,w,ft,ft2]=imgfilt(X,lf,hf,revfilt,filtorder,phaseshift) +%imgfilt - filters image +% [Y,w,ft,ft2]=imgfilt(X,lf,hf,revfilt,filtorder,phaseshift) +% X: +% lf: lowest frequency in output (default: 0) +% hf: highest frequency in output (default: infinity) +% [lf,hf] produces a bandpass filte or a stop-band if revfilter=1 +% +% revfilt: reverse filter properties +% filteorder: default 50, see: help fir1 +% phaseshift: 'random' = random permutation of phases +%Requires: Image Processing Toolbox + + +if nargin==0 + load mandrill + subplot(2,2,1); imagesc(X) + subplot(2,2,2); imagesc(imgfilt(X,0,10,0,50)); + subplot(2,2,3); imagesc(imgfilt(X,5,50,1)); + subplot(2,2,4); imagesc(imgfilt(X,0,5,[],[],'random')); + colormap(gray) + return +end + +X=double(X); +sx=[size(X) 1]; +fnyquist = min(sx(1),sx(2))/2; +%fnyquist = sx(1)/2; +if nargin<2 + lf=[]; +end +if nargin<3 + hf=[]; +end +if nargin<4 + revfilt = []; +end +if nargin<5 + filtorder = []; +end +if nargin<6 + phaseshift = []; +end + + +if isempty(hf) + hf=inf; +end +if isempty(lf) + lf=0; +end +if isempty(revfilt) + revfilt = 0; +end +if revfilt + if lf==0 + lf=hf; + hf=0; + revfilt=0; + elseif isinf(hf) + hf=lf; + lf=0; + revfilt=0; + end +end +if isempty(filtorder) + filtorder = 50; +end +if ischar(phaseshift) + switch phaseshift + case 'random' + %shouldn't permute the dc component + phaseshift = [1 randperm(prod(sx(1:2))-1)+1]; + end +end + +if ~isempty(phaseshift) + sp=[size(phaseshift) 1]; + if prod(sp(1:2))==prod(sx(1:2)) + phaseshift = reshape(phaseshift,sx(1:2)); + sp=[size(phaseshift) 1]; + end + if sp(1)==1 + phaseshift = repmat(phaseshift, [sx(1) 1 1]); + end + if sp(2)==1 + phaseshift = repmat(phaseshift, [1 sx(2) 1]); + end + if sp(3)==1 + phaseshift = repmat(phaseshift, [1 1 sx(3)]); + end +end + +for ilayer=1:sx(3); + % Compute Fourier transform + ft = fft2(X(:,:,ilayer)); + dc=ft(1); + ft(1)=0; + % Move quadrants so that low freq are in the middle + ft = fftshift(ft); + if isinf(hf) & lf == 0 + % do nothing + ft2 = ft; + else + if isinf(hf) + % User asked for a high-pass filter + w = ftrans2(fir1(filtorder, lf/fnyquist,'high')); + elseif lf==0 + % User asked for a low-pass filter + w = ftrans2(fir1(filtorder, hf/fnyquist)); + else + % User asked for a band-pass / stop-band filter + if revfilt + w = ftrans2(fir1(filtorder, [lf,hf]/fnyquist, 'stop')); + else + w = ftrans2(fir1(filtorder, [lf,hf]/fnyquist, 'bandpass')); + end + end + % Compute filter in the frequency space + w=freqz2(w, size(ft)); + % Apply filter + ft2 = w .* ft; + end + % Restore quadrant positions + ft2 = ifftshift(ft2); + if ~isempty(phaseshift) + ft2 = abs(ft2) .* exp(i*angle(ft2(phaseshift(:,:,ilayer)))); + end + ft2(1) = dc; + % Inverse FFT + Y(:,:,ilayer)=real(ifft2(ft2)); +end +if all(X(:)>=0 & X(:)<=1) + Y=max(min(Y,1),0); +end +return + +x=imread('C:\Documents and Settings\ndiayek\My Documents\My Pictures\photos\Karim & Anne Sénégal Hiver 2006-2007\DSC02099.JPG'); +x=mean(x,3); +f=fftshift(fft2(x)); +g=gausswin2([size(f)],[.2]); + + diff --git a/ind2sub2.m b/ind2sub2.m new file mode 100644 index 0000000..4902a37 --- /dev/null +++ b/ind2sub2.m @@ -0,0 +1,49 @@ +function varargout = ind2sub(siz,ndx) +%IND2SUB2 - Multiple subscripts from linear index. +% IND2SUB2 is used to determine the equivalent subscript values +% corresponding to a given single index into an array. +% It is an extension of the native IND2SUB (R14) as it returns a +% multicolumn matrix of subscripts S, each column being a dimension. +% S =[ I1 I2 ... In ] +% when a single output is asked for. +% +% [I,J] = IND2SUB(SIZ,IND) returns the arrays I and J containing the +% equivalent row and column subscripts corresponding to the index +% matrix IND for a matrix of size SIZ. +% For matrices, [I,J] = IND2SUB(SIZE(A),FIND(A>5)) returns the same +% values as [I,J] = FIND(A>5). +% +% [I1,I2,I3,...,In] = IND2SUB(SIZ,IND) returns N subscript arrays +% I1,I2,..,In containing the equivalent N-D array subscripts +% equivalent to IND for an array of size SIZ. +% +% See also SUB2IND, FIND. + +% Copyright 1984-2000 The MathWorks, Inc. +% $Revision: 1.11 $ $Date: 2000/06/01 16:46:44 $ + +siz=siz(:)'; +nout = max(nargout,1); +if nout==1 + % do nothing +elseif length(siz)<=nout, + siz = [siz ones(1,nout-length(siz))]; +else + siz = [siz(1:nout-1) prod(siz(nout:end))]; +end +n = length(siz); +k = [1 cumprod(siz(1:end-1))]; +ndx = ndx(:) - 1; +for i = n:-1:1, + if nout<=1 + if i==n + vout{1}=NaN*zeros(length(ndx), n); + end + vout{1}(1:length(ndx),i) = floor(ndx./k(i))+1; + else + vout{i} = floor(ndx./k(i))+1; + end + ndx = rem(ndx,k(i)); +end + +varargout=vout; \ No newline at end of file diff --git a/interpolate_cortex1.m b/interpolate_cortex1.m new file mode 100644 index 0000000..511de80 --- /dev/null +++ b/interpolate_cortex1.m @@ -0,0 +1,107 @@ +function [InterpMatrix] = interpolate_cortex(sparseFV,denseFV,vertConn) +% INTERPOLATE_CORTEX Interpolate current density from a sparse tesselation to a dense tesselation +% function InterpMatrix = interpolate_cortex(sparseFV,denseFV,vertConn); +% sparseFV is the sparse Faces/Vertices structure +% denseFV is the dense Faces/Vertices structure +% vertConn is the vertices connectivity of the dense Faces/Vertices structure +% +% InterpMatrix returned is a sparse matrix (nDenseVertices x nSparseVertices) used for interpolation, +% where nDenseVertices in the number of vertices in the dense structure and nSparseVertices is the +% number of vertices in the sparse structure +% Interpolated current density is given by DenseCurrentDensity = InterpMatrix * SparseCurrentDensity +% +% Remarks: Every vertex in sparseFV should have a corresponding one in denseFV (i.e. same coordinates) +% sparseFV=reducepatch(denseFV) matlab function produces such tesselations +% Surfaces should be connected, i.e. there should exist a path through triangles connecting every pair +% of vertices on the surface +% interpolate_cortex performs interpolation in terms of surface neighbours and not in terms of volume +% neighbours +% +% See also REDUCEPATCH VERTICES_CONNECTIVITY PATCH_SWELL + +% Dimitrios Pantazis, Ph.D. +% 11-Apr-02 +% +% + + +%choose whether to display bars +if(~exist('VERBOSE','var')), + VERBOSE = 1; % default non-silent running of waitbars +end + +%Initialize variables +nDenseVertices=size(denseFV.vertices,1); +nSparseVertices=size(sparseFV.vertices,1); +existInSparse=zeros(nDenseVertices,1); +InterpMatrix=sparse(nDenseVertices,nSparseVertices); + + +%find vertices that have been preserved in sparse patch +if(VERBOSE) + commonVertices=0; + hwait = waitbar(0,sprintf('Finding common vertices for interpolation... %.0f found',commonVertices)); + drawnow %flush the display + step=round(nDenseVertices/10); +end +for i=1:nDenseVertices + if(VERBOSE) + if(~rem(i,step)) % ten updates + commonVertices=length(find(existInSparse>0)); + waitbar(i/nDenseVertices,hwait,sprintf('Finding common vertices for interpolation... %.0f found',commonVertices)); + drawnow %flush the display + end + end + x=sparseFV.vertices(:,1)-denseFV.vertices(i,1); + I=find(x==0); %get vertices that have the same x coord. I is the index in sparseFV + if(I) %if coordinate x is same + for j=1:length(I) + distance=sum(abs(denseFV.vertices(i,:)-sparseFV.vertices(I(j),:))); + if (distance==0) %the point is the same! + existInSparse(i)=I(j); + InterpMatrix(i,I(j))=1; + end + end + end +end +close(hwait); + + +%interpolate the rest of the vertices +if(VERBOSE) + hwait = waitbar(0,sprintf('Interpolating the %.0f remaining vertices...',nDenseVertices-nSparseVertices)); + drawnow %flush the display + step=round(nDenseVertices/20); +end +for i=1:nDenseVertices + if(VERBOSE) + if(~rem(i,step)) % ten updates + waitbar(i/nDenseVertices,hwait); + drawnow %flush the display + end + end + if(~existInSparse(i)) + startPatch=i; + nLayer=0; + neighboursKnown=[]; + neighboursKnownScale=[]; + while(length(neighboursKnown)<3 & nLayer <10 ) + swelledPatch=patch_swell(startPatch,vertConn); %swell patch to find know vertices + if(isempty(swelledPatch)) + %disp('surface not closed'); + break; + end + temp=existInSparse(swelledPatch); + neighboursNew=temp(temp>0)'; + nLayer=nLayer+1; + neighboursKnown=[neighboursKnown neighboursNew]; + neighboursKnownScale=[neighboursKnownScale nLayer*ones(1,length(neighboursNew))]; + startPatch=[swelledPatch startPatch]; + end + %update InterpMatrix + InterpMatrix(i,neighboursKnown)=(1./neighboursKnownScale)/sum(1./neighboursKnownScale); + end +end +if(VERBOSE) + close(hwait); +end \ No newline at end of file diff --git a/isfield2.m b/isfield2.m new file mode 100644 index 0000000..cf1f7a4 --- /dev/null +++ b/isfield2.m @@ -0,0 +1,42 @@ +function [TF,index] = isfield2(s,varargin) +%ISFIELD2 - Advanced ISFIELD, to test if a field is in structure array +% [TF] = isfield2(S,F) returns true if the string F is the name of a +% field or a subfield in the structure array S. +% +% Example +% >> isfield2( +% +% See also: getfield2 + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2009 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2009-03-31 Creation +% +% ----------------------------- Script History --------------------------------- + +if (isempty(varargin)) + error('MATLAB:GETFIELD:DeprecatedFunction:InsufficientInputs','Not enough input arguments.') +end +TF=0; +% The most common case +index = varargin{1}; +if (length(varargin)==1 && isstr(index)) + if any(index=='.') + [f1,f2]=strtok(index,'.'); + if ~isfield(s, deblank(f1)) + return + end + TF=isfield2([s.(deblank(f1))],f2(2:end)); + else + TF = isfield(s, deblank(index)); + return + end +end + +return diff --git a/ismember2.m b/ismember2.m new file mode 100644 index 0000000..f917ba3 --- /dev/null +++ b/ismember2.m @@ -0,0 +1,13 @@ +function [tf, loc]=ismember2(a,s) +% ISMEMBER2 : ISMEMBER function compliant with older versions of Matlab +v=ver; +if str2num(v(1).Version)>= 6.5 + [tf, loc]=ismember(a,s); +else + % In Matlab 6.1, ISMEMBER function does not allow multiple varargout + [tf]=ismember(a, s); + for i=find(tf) + idx=max(find(a(i)==s)); + loc(i)=idx(1); + end +end diff --git a/isort.m b/isort.m new file mode 100644 index 0000000..1e83aa5 --- /dev/null +++ b/isort.m @@ -0,0 +1,4 @@ +function [j,v] = isort(x) +%ISORT +% Index of the sort +[v,j]=sort(x); \ No newline at end of file diff --git a/isscalar2.m b/isscalar2.m new file mode 100644 index 0000000..6e31c8a --- /dev/null +++ b/isscalar2.m @@ -0,0 +1,13 @@ +function t = isscalar(x) +%ISSCALAR True for scalar input. +% +% ISSCALAR(X) returns 1 if it's argument is a scalar and 0 otherwise. +% Note that this function does not consider the empty matrix a scalar. + +% Author: Peter J. Acklam +% Time-stamp: 2002-03-03 13:50:54 +0100 +% E-mail: pjacklam@online.no +% URL: http://home.online.no/~pjacklam +error(nargchk(1, 1, nargin)); +t = numel(x)==1; +% t = all(size(x) == 1); diff --git a/issfield.m b/issfield.m new file mode 100644 index 0000000..351f1d8 --- /dev/null +++ b/issfield.m @@ -0,0 +1,12 @@ +function tf = issfield(s,f) +%ISSFIELD True if field.subfield is in structure array +% F = SISFIELD(S,'field.subfield...') returns true if 'field.subfield' is the +% name of a field and its subfield in the structure array S. +% +% See also SSTRUCT, SSETFIELD, SFIELDNAMES. + +if isa(s,'struct') + tf = any(strcmp(sfieldnames(s(1)),f)); +else + tf = logical(0); +end diff --git a/javauigetfiles/MatlabR13FileFilter.class b/javauigetfiles/MatlabR13FileFilter.class new file mode 100644 index 0000000000000000000000000000000000000000..c61c6d03829bcce4a0e1a48fcaf5b8ce55925e62 GIT binary patch literal 1870 zcmZ`)SyLNF5dKCItPl%^<@6!EAqUb5kg*+N0w%G+BuWH6aB#pmJS>B>&azAGt^)ok zR37r^s+1p+svxT3s`w!fsmhN^rF$igitvMaW_$Yh`s<$2KmY#mCz66G$!iTT0+U+40m$ZD&&jkL9LI8l&vn z(p{5*!{1G1Dyll6z(9FfMy}j^G<9RiaTWa{6%elUF)oT5Mlui(C9*yT45x0Fwyky} zf!?Np;}<#HY<`3Mw`nKP-9UYOLMd49$sIncsA%ylSN*wsYmow(RlgS4jFOy&&p|%P z6zg~&>jEREbg$H0_kr(tRNK9MMjAKdT3MQBI%MYjnt5Dy-t?5Yt7?(?*Dq0^OphW= za^Rwd4INK0O^jGNzQMNwNo$g}-{E@!=y(^SI^JXPj1NaoVU%1~Rit|-sML0p7oCAJ zx7r*7)0Up+N2cw2k#szB$EV~`ReGjRfGWyUfn%F?RR+?gYRoZfj9^J@vaof$gHeIO z)0B-tyIb4dR@}p!jx7*(BClf`(_GL88Y()faCAIp%@1&O?7$W1JBQ$AJ^RIQj|RBN zEwrn?AF3d3ICX|l5hFPsi=O}EpeqsiWq(fv3o;}an&WM${rlT*nJk*Pu0U0&S%k!{ zDw@~fT&Z<1N+q7NPFIFe={R6VSlbNaTSk23$fDJav!1VSm%F>l+Y%T*Z{S-ZHR$DA z6Zs9o1@yS~=Cl(yCtP%b_@=*z=^?-xP5nwd{~5kwQTW7(s&Ao!%+rJ@HlZe8r8f@r z+$Ff-v`g^R#VZXSttkj@Ey*!J2(*y~cwrqu6#honYb4f<NN~t1{bk_OIXGgJfxGSRIWn> zdUUHoDrg@$Un2iC|hK~^JH;Whf# zk1${i)-gm>3>m{D>bQ7>OQ)FmUj&pROYu4HV8i<)??^MRAoNqs9D8JwB-;s!dielt zsaLeQLS!$GTfd{Tjx3SPTfL5v18Aw2NF`s9LxPF}k-`0%f`;qpz)d3P!#K6s2zruw ug3fPH%3h-CW?Zr$%jqTvE|}?Pqda}To&;}?mAB%6dc&X@P~fB3>eheQ`<~GN literal 0 HcmV?d00001 diff --git a/javauigetfiles/MatlabR13FileFilter.java b/javauigetfiles/MatlabR13FileFilter.java new file mode 100644 index 0000000..ce7cd51 --- /dev/null +++ b/javauigetfiles/MatlabR13FileFilter.java @@ -0,0 +1,58 @@ +import java.io.File; +import java.util.*; +import javax.swing.*; +import javax.swing.filechooser.*; + +public class MatlabR13FileFilter extends FileFilter{ + + private String description; + private String selector1,selector2; + + //Constructor + public MatlabR13FileFilter(String selector,String description){ + if(selector == null){ + throw new NullPointerException("La description (ou selector) ne peut être null."); + } + selector=selector.toLowerCase(); + int star = selector.indexOf("*"); + if (selector.regionMatches(0, "*.*", 0, 3)) + { + this.selector1 = ""; + this.selector2 = ""; + } + else + { + if(selector.lastIndexOf("*") != star){ + throw new IllegalArgumentException("Selector cannot contain more than one generic character '*'."); + } + if (star>=0) + { + this.selector1 = selector.substring(0,star); + this.selector2 = selector.substring(star+1); + } + else + { + this.selector1 = selector; + this.selector2 = selector; + } + } + this.description = description; + } + + public MatlabR13FileFilter(String selector){ + this(selector,"(" + selector + ")"); + } + + + // FileFilter methods + public boolean accept(File file){ + if(file.isDirectory()) { + return true; + } + String filename = file.getName().toLowerCase(); + return filename.startsWith(selector1) && filename.endsWith(selector2); + } + public String getDescription(){ + return description; + } +} \ No newline at end of file diff --git a/javauigetfiles/uiGetFiles.m b/javauigetfiles/uiGetFiles.m new file mode 100644 index 0000000..939730b --- /dev/null +++ b/javauigetfiles/uiGetFiles.m @@ -0,0 +1,88 @@ +function [filenames, pathname] = uigetfiles(varargin) +% This is a Java interfaced version of UIGETFILES, that brings multiple file +% open dialog box. +% +% [filenames, pathname] = uigetfiles; displays a dialog box file browser +% from which the user can select multiple files. The selected files are +% returned to FILENAMES as an arrayed strings. The directory containing +% these files is returned to PATHNAME as a string. +% +% A successful return occurs only if the files exist. If the user selects +% a file that does not exist, an error message is displayed to the command +% line. If the Cancel button is selected, zero is assigned to FILENAMES +% and current directory is assigned to PATHNAME. +% +% Allows the same syntax for file filter as Matlab (R13) function UIGETFILE : +% uigetfiles(filterspec, title) +% filterspec should be a N-by-2 cell array of strings. +% See: UIGETFILE +% +% This program has an equivalent function to that of a C version of +% "uigetfiles.dll" downloadable from www.mathworks.com under support, file +% exchange (ID: 331). +% +% It should work for matlab with Java 1.3.1 or newer. +% +% Modified by KND, 2005-11-08, kndiaye01yahoo.fr +% +% Shanrong Zhang +% Department of Radiology +% University of Texas Southwestern Medical Center +% 02/09/2004 +% +% email: shanrong.zhang@utsouthwestern.edu + +% mainFrame = com.mathworks.ide.desktop.MLDesktop.getMLDesktop.getMainFrame; +filechooser = javax.swing.JFileChooser(pwd); +filechooser.setMultiSelectionEnabled(true); +filechooser.setFileSelectionMode(filechooser.FILES_ONLY); +if nargin>0 + filechooser.setAcceptAllFileFilterUsed(false); + filter=varargin{1}; + for i=1:size(filter, 1) + % Parse string sith multiple '*.ext' + ext=strread(filter{i}, '%s', 'delimiter', ';'); + for j=1:length(ext) + if size(filter,2)>1 + flt=javaObject('MatlabR13FileFilter', ext{j}, filter{i,2}); + else + flt=javaObject('MatlabR13FileFilter', ext{j}); + end + filechooser.addChoosableFileFilter(flt); + end + end +end +if nargin>1 + filechooser.setDialogTitle(varargin{2}) +end +% The following does NOT work. I dunno why... +% if nargin>2 +% if ~iscell(varargin{3}) +% varargin{3}={varargin{3}}; +% end +% selfiles=javaArray('java.io.File',length(varargin{3}) ); +% for i=1:length(varargin{3}) +% selfiles(i)=java.io.File(varargin{3}{i}) +% end +% filechooser.setSelectedFiles(selfiles) +% end +if nargin>2 + error('uiGetFiles cannot deal with pre-selected files') +end + +selectionStatus = filechooser.showOpenDialog(com.mathworks.mwswing.MJFrame); + +if selectionStatus == filechooser.APPROVE_OPTION + pathname = [char(filechooser.getCurrentDirectory.getPath), ... + java.io.File.separatorChar]; + selectedfiles = filechooser.getSelectedFiles; + for k = 1:1:size(selectedfiles) + filenames(k) = selectedfiles(k).getName; + end + filenames = char(filenames); +else + pathname = pwd; + filenames = 0; +end + +% End of code \ No newline at end of file diff --git a/karim.m b/karim.m new file mode 100644 index 0000000..63368cd --- /dev/null +++ b/karim.m @@ -0,0 +1,28 @@ +%donnee +[Channel,F,imegsens,ieegsens,iothersens,irefsens,grad_order_no,no_trials,filter,Time, RunTitle] = ds2brainstorm(fic_ds,0); +%1er essai +champ=F{1}; +%MEG +champ_meg = champ(imegsens,:); +%EEG +champ_eeg = champ(ieegsens,:); + +%maillage (.tri) +[noeud,face,n_noeud,n_face] = lfictri(fic_tri);-----------> 120000 noeuds + +%matrice de gain et maillage format olivier +load(fic_gain) gain_5000.mat +-->vmat [imegsens,n_noeud] +load(fic_maillage) maillage_5000.mat +-->noeud, face,xyz=noeud(1:3,:), voisin (voisinage des noeuds), aire (surface des dipoles) + +%minimum norm +[[J_meg;J_oculaire],residus] = mne(champ_meg,[vmat oculaire],lambda); +lambda = 1e-4; (explique pratiquement toutes les donnees) + +%projection +meg_corrige = vmat*J_meg; + +%svd +[U,S,V] = svd(champ_oculaire); +%vecteurs spatiaux V horizontal (a verifier) diff --git a/labelbads.m b/labelbads.m new file mode 100644 index 0000000..1844095 --- /dev/null +++ b/labelbads.m @@ -0,0 +1,33 @@ +function [Bads]=labelbads(ctfdata) +% labelbads - label as BAD channels / trials / channel&trial + +% Bads.Trials : Bad trials +% Bads.Channels : +% Bads.TrialChannel : Isolated events + + +Bads=cell(1,3); + +if size(ctfdata.data, 1) == ctfdata.setup.number_samples + ctfdata.data=permute(ctfdata.data, [2 1 3]); +end + +x=ctfdata.data(ctfdata.sensor.index.eeg_sens,:,:); +sx=size(x); +figure +[h,t]=hist(abs(x(:)),10); +[i,j,k]=ind2sub(sx, find(abs(x)>t(5))); + +b=zeros(sx([1 3])); +b(i,k)=1; +% b(i,1)=NaN; +pcolor(b) +ylabel('Channels') +xlabel('Trials') + +x(i,j,k)=NaN; +figure +hist(abs(x(:)),100); +[h,t]=hist(abs(x(:)),10) +[i,j,k]=ind2sub(sx, find(abs(x)>t(5))); + diff --git a/labelize2.m b/labelize2.m new file mode 100644 index 0000000..673795f --- /dev/null +++ b/labelize2.m @@ -0,0 +1,57 @@ +function [r]=labelize(vertices, t_xyz, t_ref,varargin) +% labelize - labelize vertrices according to a reference template +% +% [r]=labelize(vertices, t_xyz, t_ref,['TimeBar']) +% +% Each item of the vertices will be labeled with the t_ref value +% of the closest vertex in the t_xyz template (geometrical distance). +% +% Input: +% vertices: [Nvx3] coordinates of Nv vertices for which labels are computed +% t_xyz: [Ntx3] matrix, positions of the Nt template vertices +% t_ref: [Ntx1] vector, labels of the template vertices +% +% Output: +% r: [Nvx1] vector of the labels +% +% Options: +% TimeBar 1 or 'on' to show a progress bar +if nargin>3 +options=struct(varargin{:}); +end +if isfield(options, 'TimeBar') + if strmatch('on', lower(options.TimeBar)) + options.TimeBar=1; + else + options.TimeBar=0; + end +else + options.TimeBar=0; +end + +r=zeros(length(vertices),1)*NaN; +if options.TimeBar + h=timebar('Progres...','Labeling vertices'); +end + +nxyz=size(t_xyz,1); +nv=size(vertices,1); + +for i=1:nv + d=sum(power(t_xyz-repmat(vertices(i,:),nxyz,1),2),2); + + % local "smoothing" + % r(i)=imax(hist(ref(d<5e-3), .5:length(labels))); + + [r(i),r(i)]=min(d); + r(i)=t_ref(r(i)); + + if options.TimeBar + timebar(h, i/nv,1); + end + %set(p, 'FaceVertexCData',r); drawnow;w +end +if options.TimeBar + close(h) +end + diff --git a/labels2parcels.m b/labels2parcels.m new file mode 100644 index 0000000..81c8bf3 --- /dev/null +++ b/labels2parcels.m @@ -0,0 +1,46 @@ +function [C,labels,ROI] = labels2parcels(r,norli,labels) +%LABELS2PARCELS - Creates a parcellation matrix from labels on a mesh +% [C,labels,ROI] = labels2parcels(r) +% From a labelling vector (r) of N points, it creates a [M-by-N] +% parcellation matrix with ones (1) in a given line indicates that +% the points of the corresponding columns belong to that region. +% +% [C,labels,ROI] = labels2parcels(r,norli) +% When norli=1, normalizes each line of C by the number of points +% belonging to it (default: norli=0, ie don't normalize); +% +% [C,labels,ROI] = labels2parcels(r,norli,labels) +% The values in r are referenced according to the labels given. +% +% +% Example +% >> C=labels2parcels(mni.atlas.r,0,1:116) +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2006 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2006-02-06 Creation +% +% ----------------------------- Script History --------------------------------- +[l,i,j]=unique(r(:)); +if nargin>2 + [ign,ll]=ismember(l,labels); + j=ll(j); +else + labels=l; +end +M=length(labels); +N=length(r); +C=sparse(j,(1:N)',ones(N,1),M,N,N); +if norli + nC=1./(sum(C,2)+eps); + nC(isnan(nC))=0; + C=diag(nC)*C; +end \ No newline at end of file diff --git a/labnic/Labnic_1st_analysis.m b/labnic/Labnic_1st_analysis.m new file mode 100644 index 0000000..709bd5a --- /dev/null +++ b/labnic/Labnic_1st_analysis.m @@ -0,0 +1,203 @@ +function [] = Labnic_analyse +% analysing of the subjects you will define: +% you must have a structure like +% **...\data_fMRI\populations\subjects\scans\sessions\swaf\** +% your behavioral data are in a matrix begining by ONS in: +% **...\data_fMRI\populations\subjects\behavior\** +% your analysis will be in : +% **...\data_fMRI\populations\subjects\analyse\ana1\** +% for your processed images. + +% this function has been adapted by Yann Cojan (yann.cojan@unige.ch) +% improved by Virginie Sterpenich (Virginie.Sterpenich@unige.ch) + +global TR Session_filter im_format Cnam ons_unit name_ana Ons; +% defaults.modality = 'FMRI'; + + +%% parameters to specify for each study +TR = 1.7; +im_format = 'nii'; %or 'img'; +Session_filter ='*RUN*'; %name of the study in the directory of the sessions +root_path = 'C:\experiments\FLANKER'; % directory where are the populations, main directory +name_ana = 'ana'; %or 'ana2'; etc +ons_unit = 'secs'; %or 'scans'; + +%% defining the pop_style +cd (root_path) +Population = spm_select(Inf,'dir','which population do you want to process?'); +npop = size(Population,1); + +%% Definition of subjects to process +for pop=1:npop + cd(Population(pop,1:end)) + Subject{pop}= spm_select(Inf,'dir','which subjects do you want to process?'); +end +Subject = char(Subject); +nsuj = size(Subject,1); + +%% analysis +for pop = 1:npop + % go to the population folder + Pop{pop} = deblank(Population(pop,:)); + sep_pop = findstr(filesep,Pop{pop}); + popu = Pop{pop}(1,sep_pop(end-1)+1:sep_pop(end)); + for sub = 1:nsuj + %create the subject's analyse folder + sufolder= deblank(Subject(sub,:)); + sep_suj = findstr(filesep,Subject(sub,:)); + sujet = Subject(sub,sep_suj(end-1)+1:sep_suj(end)-1); + su = Subject(sub,sep_suj(end-1)+2:sep_suj(end)-1); + if ~exist([sufolder 'analyse' filesep],'dir'); + mkdir(sufolder, 'analyse'); + end + mkdir([sufolder 'analyse' filesep],name_ana); + wkdir=[sufolder 'analyse' filesep name_ana]; + eval(['cd ' wkdir]); + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % read the behavior files to define onsets and conditions + % this is the tricky part of reading the behavior files, + % contact the script writers for help.... + + %[Ons Cnam] = readres_FLANKER_missed_pooled([sufolder 'behavior' filesep],su); + %% onsets definition + % load your ONS* mat in the behavior directory + % ONS = spm_select('List',[path 'behavior'],strcat('^ONS+.+\.mat$')); + % cd([path 'behavior']); + % load(ONS); + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % do the 1st level analysis for each subject + ana(sufolder); + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + end +end + +%% FUNCTION SECTION + +%% function analyse +function [] = ana(suj_path) +global TR Session_filter im_format Cnam Ons ons_unit name_ana; + +% select the sessions of interest +Session=get_directories(fullfile([suj_path 'scans'],Session_filter)); +nses = size(Session,2); + +% select swaf images for each Session +%=================================================================== +im_style = 'swaf'; +image_filter = '*swaf*'; +nscans = []; +SPM.xY.P = []; + +% selection of the swaf img of each session +%=================================================================== +for ses = 1:nses + smoothfolder = [suj_path 'scans' filesep deblank(Session{ses}) filesep 'swaf' filesep]; + tmp{ses} = spm_select('List',smoothfolder,['^' im_style '.*' im_format]); + nscans = [nscans size(tmp{ses},1)]; + SPM.xY.P = strvcat(SPM.xY.P,[repmat(smoothfolder,nscans(ses),1),tmp{ses}]); +end +SPM.nscan = nscans; +cd([suj_path 'analyse' filesep name_ana]); + +% building matrix +%==================================================================== +for ses = 1:nses + nconds=length(Cnam{ses}); + for c=1:nconds + SPM.Sess(ses).U(c).name = {Cnam{ses}{c}}; + SPM.Sess(ses).U(c).ons = Ons{ses}{c}; + SPM.Sess(ses).U(c).dur = 0; + + % define the parametric modulation according to your sessions + % (comment the adequate modulation: parametric modulation(if you + % already specify it before), or time modulation (nothing to + % specify in modul) or none (nothing to specify in modul) + %% parametric modulation + % SPM.Sess(ses).U(c).P(1).name ='modul'; + % SPM.Sess(ses).U(c).P(1).i = [1 2]; + % SPM.Sess(ses).U(c).P(1).P = eval(modul{ses}{c}); + % SPM.Sess(ses).U(c).P(1).h = 1; + %% time modulation + % SPM.Sess(ses).U(c).P(1).name ='time'; + % SPM.Sess(ses).U(c).P(1).i = [1 2]; + % SPM.Sess(ses).U(c).P(1).P = eval(Cnam{ses}{c}); + % SPM.Sess(ses).U(c).P(1).h = 1; + %% no modulation + SPM.Sess(ses).U(c).P(1).name = 'none'; + end +end + +% multiple regressors for mvts parameters +%=================================================================== +rnam = {'X','Y','Z','x','y','z'}; +for ses=1:nses + fn = spm_select('List',[suj_path 'scans' filesep deblank(Session{ses})],['^rp_.*txt']); + [r1,r2,r3,r4,r5,r6] = textread([suj_path 'scans' filesep deblank(Session{ses}) filesep fn(1,:)],'%f%f%f%f%f%f'); + SPM.Sess(ses).C.C = [r1 r2 r3 r4 r5 r6]; + SPM.Sess(ses).C.name = rnam; +end + +% basis functions and timing parameters +%==================================================================== +defaults.stats.fmri.t = 16; +defaults.stats.fmri.t0 = 8; + +SPM.xY.RT = TR; +SPM.xBF.name = 'hrf'; % 'hrf (with time derivative)' +SPM.xBF.order = 1; % 2 = hrf + time deriv +SPM.xBF.length = 32; % length in seconds + +% find number of slices +%==================================================================== +V = spm_vol(SPM.xY.P(1,:)); +if iscell(V) + nslices = V{1}{1}.dim(3); +else + nslices = V(1).dim(3); +end +ref_slice = floor(nslices/2); % middle slice in time +SPM.xBF.T = defaults.stats.fmri.t; % number of time bins per scan % useless? cf. defaults above +SPM.xBF.T0 = defaults.stats.fmri.t/2; % middle slice/timebin % useless? cf. defaults above +SPM.xBF.UNITS = ons_unit; % OPTIONS: 'scans'|'secs' for onsets +SPM.xBF.Volterra = 1; % OPTIONS: 1|2 = order of convolution + +% global normalization: OPTIONS:'Scaling'|'None' +%--------------------------------------------------------------------------- +SPM.xGX.iGXcalc = 'None'; + +% low frequency confound: high-pass cutoff (secs) [Inf = no filtering] +%--------------------------------------------------------------------------- +SPM.xX.K(1).HParam = 128; + +% intrinsic autocorrelations: OPTIONS: 'none'|'AR(1) + w' +%----------------------------------------------------------------------- +SPM.xVi.form = 'AR(1)'; %AR(0.2)???? SOSART + +% specify SPM working dir for this sub +%=========================================================================== +SPM.swd = pwd; + +% Configure design matrix +%=========================================================================== +SPM = spm_fmri_spm_ui(SPM); + +% Estimate parameters +%========================================================================== +SPM = spm_spm(SPM); +return + +function dirs = get_directories(suj_path) +% returns a cell array holding true directories located at path +D=dir(suj_path); +dirs={D(~(strcmp({D.name},'.')+strcmp({D.name},'..')+~[D.isdir])).name}; +% dirs = {'morphing_titrage_run1',... +% 'morphing_titrage_run2',... +% 'morphing_test_run5',... +% 'morphing_faceloc_run11'}; + +return \ No newline at end of file diff --git a/labnic/Labnic_1st_contrast_spm5.m b/labnic/Labnic_1st_contrast_spm5.m new file mode 100644 index 0000000..8380fdc --- /dev/null +++ b/labnic/Labnic_1st_contrast_spm5.m @@ -0,0 +1,107 @@ +function [] = Labnic_contrast_spm5 +% analysing of the subjects you will define: +% you must have a structure like +% **...\data_fMRI\populations\subjects\scans\sessions\swaf\** +% your behavioral data are in a matrix begining by ONS in: +% **...\data_fMRI\populations\subjects\behavior\** +% your analysis will be in : +% **...\data_fMRI\populations\subjects\analyse\ana1\** +% for your processed images. + +% this function has been adapted by Yann Cojan (yann.cojan@unige.ch) +% improved by Virginie Sterpenich (Virginie.Sterpenich@unige.ch) + +global name_ana; + +%% parameters to specify for each study +root_path = 'D:\'; % directory where are the populations, main directory +name_ana = 'ana'; %or 'ana2'; etc + +%% defining the pop_style +cd (root_path) +Population = spm_select(Inf,'dir','which population do you want to process?'); +npop = size(Population,1); + +%% Definition of subjects to process +for pop=1:npop + cd(Population(pop,1:end)) + Subject{pop}= spm_select(Inf,'dir','which subjects do you want to process?'); +end +Subject = char(Subject); +nsuj = size(Subject,1); + +%% starting analysing +for pop = 1:npop + % go to the population folder + Pop{pop} = deblank(Population(pop,:)); + sep_pop = findstr(filesep,Pop{pop}); + popu = Pop{pop}(1,sep_pop(end-1)+1:sep_pop(end)); + for sub = 1:nsuj + %go to the subject's analyse folder + sufolder= deblank(Subject(sub,:)); + path_ana =[sufolder 'analyse' filesep name_ana]; + eval(['cd ' path_ana]); + %select SPM.mat + [files]=spm_select('List',path_ana,'^SPM.mat$'); + jobs{1}.stats{1}.con.spmmat = {fullfile(path_ana,files)}; + % create the F contrasts + [Cf,Cnamesf] = f_con; + for iconf = 1:size(Cf,2) + % put the contrast in the job + jobs{1}.stats{1}.con.consess{iconf}.fcon.name = Cnamesf{iconf}; + jobs{1}.stats{1}.con.consess{iconf}.fcon.convec = {Cf{iconf}}; + end + +% create the T contrasts + [Ct,Cnamest] = t_con; + for icont = 1:size(Ct,1) + % put the contrast in the job + jobs{1}.stats{1}.con.consess{iconf+icont}.tcon.name = Cnamest{icont}; + jobs{1}.stats{1}.con.consess{iconf+icont}.tcon.convec = Ct(icont,:); + end + % save a matrix with the names of the contrasts + eval('save contrasts_name Ct Cnamest'); + eval('save contrast jobs'); + % run the job + spm_jobman('run',jobs) + end +end + +%% FUNCTION SECTION: create contrasts + +function [Cf,Cnamesf] = f_con +load SPM +SPM.xCon = []; +save SPM SPM + +Cf = []; Cnamesf = []; +for i = 1:length(SPM.xX.name)% creating a variable connam which contain all possible contrasts + b = strfind(SPM.xX.name{i},'*bf'); + connam{i} = SPM.xX.name{i}(7:b-1); +end + +%% then named the contrasts +Lbissec_placebo = double(strcmp(connam,'Lbissec_placebo')); + +Cnamesf{end+1} = 'F-Task'; +Cf{end+1} = [Lbissec_placebo;Mbissec_placebo;Rbissec_placebo;Lmemo_placebo;Mmemo_placebo;Rmemo_placebo;Lsearch_placebo;Msearch_placebo;Rsearch_placebo;... + Lbissec_nico;Mbissec_nico;Rbissec_nico;Lmemo_nico;Mmemo_nico;Rmemo_nico;Lsearch_nico;Msearch_nico;Rsearch_nico];% example + +return + +function [Ct,Cnamest] = t_con +load SPM + +Ct = []; Cnamest = []; +for i = 1:length(SPM.xX.name)% creating a variable connam which contain all possible contrasts + b = strfind(SPM.xX.name{i},'*bf'); + connam{i} = SPM.xX.name{i}(7:b-1); +end + +%% then named the contrasts +Mmemo_nico = double(strcmp(connam,'Mmemo_nico')); + +Cnamest{end+1} = 'Lbissec_placebo'; +Ct(end+1,:) = Lbissec_placebo;% example + +return \ No newline at end of file diff --git a/labnic/Labnic_1stlevel_spm8.m b/labnic/Labnic_1stlevel_spm8.m new file mode 100644 index 0000000..5fb9057 --- /dev/null +++ b/labnic/Labnic_1stlevel_spm8.m @@ -0,0 +1,205 @@ +function [] = Labnic_1stlevel_spm8 +% analysing of the subjects you will define: +% you must have a structure like +% **...\data_fMRI\populations\subjects\scans\sessions\swaf\** +% your behavioral data are in a matrix begining by ONS in: +% **...\data_fMRI\populations\subjects\behavior\** +% your analysis will be in : +% **...\data_fMRI\populations\subjects\analyse\ana1\** +% for your processed images. + +% this function has been adapted by Yann Cojan (yann.cojan@unige.ch) +% improved by Virginie Sterpenich (Virginie.Sterpenich@unige.ch) +clear global +global TR Session_filter im_format Cnam ons_unit name_ana Ons Modul; + +%% parameters to specify for each study +TR = 1.7; +im_format = 'nii'; %or 'img'; +Session_filter ='*ATTHYPNO_RUN*'; %name of the study in the directory of the sessions +root_path = 'F:\FLANKER'; % directory where are the populations, main directory +name_ana = 'ana_E_CI_parametric_1back_2mm'; %or 'ana2'; etc +ons_unit = 'secs'; %or 'scans'; + +%% Definition of subjects to process +cd ([root_path '\controls']) +Subject{1}= spm_select(Inf,'dir','which subjects do you want to process?'); + +Subject = char(Subject); +nsuj = size(Subject,1); + +%% analysis +for sub = 1:nsuj + %create the subject's analyse folder + sufolder= deblank(Subject(sub,:)); + sep_suj = findstr('\',Subject(sub,:)); + su = Subject(sub,sep_suj(end-1)+2:sep_suj(end)-1); + if ~exist([sufolder 'analyse\'],'dir'); + mkdir(sufolder, 'analyse\'); + end + mkdir([sufolder 'analyse\'],name_ana); + wkdir=[sufolder 'analyse\' name_ana]; + eval(['cd ' wkdir]); + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % read the behavior files to define onsets and conditions + % this is the tricky part of reading the behavior files, + % contact the script writers for help.... + [Ons Modul Cnam] = readres_flanker_E_CI_parametric([sufolder 'behavior\'],su); + %% onsets definition + % load your ONS* mat in the behavior directory + % ONS = spm_select('List',[path 'behavior'],strcat('^ONS+.+\.mat$')); + % cd([path 'behavior']); + % load(ONS); + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % do the 1st level analysis for each subject + ana(sufolder); + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + % do the 1st level analysis for each subject + con(sufolder); + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +end + +%% FUNCTION SECTION + +%% function analyse +function [] = ana(suj_path) +global TR Session_filter im_format Cnam Ons ons_unit name_ana Modul; + +% select the sessions of interest +Session=get_directories(fullfile([suj_path 'scans'],Session_filter)); +nses = size(Session,2); + +% select swaf images for each Session +%=================================================================== +im_style = 'swf'; +nscans = []; + +matlabbatch{1}.spm.stats.fmri_spec.dir = {[suj_path 'analyse\' name_ana '\']}; + + +% selection of the swaf img of each session +%=================================================================== +for ses = 1:nses + matlabbatch{1}.spm.stats.fmri_spec.sess(ses) = struct('scans',[],'cond',[],'multi',{{''}},'regress',struct('name',{},'val',{}),'multi_reg',[],'hpf',128); + smoothfolder = [suj_path 'scans\' deblank(Session{ses}) '\swf']; + tmp{ses} = spm_select('ExtFPList',smoothfolder,['^' im_style '.*' im_format]); + nscans = [nscans size(tmp{ses},1)]; + matlabbatch{1}.spm.stats.fmri_spec.sess(ses).scans = cellstr(tmp{ses}); + + % building matrix + %==================================================================== + nconds=length(Cnam{ses}); + for c=1:nconds + matlabbatch{1}.spm.stats.fmri_spec.sess(ses).cond(c).name = Cnam{ses}{c}; + matlabbatch{1}.spm.stats.fmri_spec.sess(ses).cond(c).onset = Ons{ses}{c}; + matlabbatch{1}.spm.stats.fmri_spec.sess(ses).cond(c).duration = 0; + + % no modulation + matlabbatch{1}.spm.stats.fmri_spec.sess(ses).cond(c).tmod = 0;%1 for time modulation + matlabbatch{1}.spm.stats.fmri_spec.sess(ses).cond(c).pmod = struct('name',{},'param',{},'poly',{}); +% % modulation +% if c < 3 +% count = 0; +% count = count +1; +% matlabbatch{1}.spm.stats.fmri_spec.sess(ses).cond(c).pmod(count).name ='preE'; +% matlabbatch{1}.spm.stats.fmri_spec.sess(ses).cond(c).pmod(count).param = Modul{ses}{c}(:,1); +% matlabbatch{1}.spm.stats.fmri_spec.sess(ses).cond(c).pmod(count).poly = 1; +% end + end + + % multiple regressors for mvts parameters + %=================================================================== + fn = spm_select('FPList',[suj_path 'scans\' deblank(Session{ses})],'^rp_.*txt'); + matlabbatch{1}.spm.stats.fmri_spec.sess(ses).multi_reg = {fn}; +end + +% basis functions and timing parameters +%==================================================================== +matlabbatch{1}.spm.stats.fmri_spec.timing.fmri_t = 16; +matlabbatch{1}.spm.stats.fmri_spec.timing.fmri_t0 = 8; + +matlabbatch{1}.spm.stats.fmri_spec.timing.RT = TR; %TR + +matlabbatch{1}.spm.stats.fmri_spec.bases.hrf.derivs = [0 0]; %time derivatives +matlabbatch{1}.spm.stats.fmri_spec.timing.units = ons_unit; % OPTIONS: 'scans'|'secs' for onsets +matlabbatch{1}.spm.stats.fmri_spec.volt = 1; % OPTIONS: 1|2 = order of convolution +matlabbatch{1}.spm.stats.fmri_spec.mask = {''}; % explicit masking + +% global normalization: OPTIONS:'Scaling'|'None' +%--------------------------------------------------------------------------- +matlabbatch{1}.spm.stats.fmri_spec.global = 'None'; + +matlabbatch{1}.spm.stats.fmri_spec.fact = struct('name',{},'levels',{}); + +% intrinsic autocorrelations: OPTIONS: 'none'|'AR(1) + w' +%--------------------------------------------------------------------------- +matlabbatch{1}.spm.stats.fmri_spec.cvi = 'AR(1)'; %AR(0.2)???? SOSART + +matlabbatch{2}.spm.stats.fmri_est.spmmat = {[matlabbatch{1}.spm.stats.fmri_spec.dir{1} '\SPM.mat']}; +matlabbatch{2}.spm.stats.fmri_est.method.Classical = 1; + +save analyse matlabbatch +% Configure & Estimate +%========================================================================== +spm_jobman('run',matlabbatch); +return + +function [] = con(sudir) +global name_ana; + +cd ([sudir '\analyse\' name_ana]); +load('SPM.mat'); +SPM.xCon =[]; % reset xCon + +for i = 1:length(SPM.xX.name)%adding 6 for mvt regressors that are in SPM.xX.name + b = strfind(SPM.xX.name{i},'*bf'); +% connam{i} = SPM.xX.name{i}([7:b-1 end-1]); + connam{i} = SPM.xX.name{i}(7:b-1); +end + +C = double(strcmp(connam,'C')); C_preE = double(strcmp(connam,'CxpreE^1')); C_postE = double(strcmp(connam,'CxpostE^1')); +I = double(strcmp(connam,'I')); I_preE = double(strcmp(connam,'IxpreE^1')); I_postE = double(strcmp(connam,'IxpostE^1')); +C_preE0 = double(strcmp(connam,'CxpreE0^1')); C_postE0 = double(strcmp(connam,'CxpostE0^1')); +I_preE0 = double(strcmp(connam,'IxpreE0^1')); I_postE0 = double(strcmp(connam,'IxpostE0^1')); +EC = double(strcmp(connam,'EC')); +EI = double(strcmp(connam,'EI')); +%---------- +%F TASK +%---------- +cn=1; +Cnames{cn} = 'F-task'; +cwgt{cn} = [C;I;EC;EI]; +ctyp{cn} = 'F'; + +%% main effects +cn=cn+1; +Cnames{cn} = 'C'; +cwgt{cn} = C; +ctyp{cn} = 'T'; +cn=cn+1; +Cnames{cn} = 'I'; +cwgt{cn} = I; +ctyp{cn} = 'T'; + + +save contrasts_name Cnames +for c = 1:cn + cw = [cwgt{c}]'; % pad with zero for constant + tmp(c) = spm_FcUtil('Set',Cnames{c},ctyp{c},'c',cw,SPM.xX.xKXs); +end +SPM.xCon = tmp; +%save SPM SPM and evaluate--------------------------------------------------------------------------- +SPM = spm_contrasts(SPM); +return + +function dirs = get_directories(suj_path) +% returns a cell array holding true directories located at path +D=dir(suj_path); +dirs={D(~(strcmp({D.name},'.')+strcmp({D.name},'..')+~[D.isdir])).name}; +return \ No newline at end of file diff --git a/labnic/Labnic_Anova_2groups_spm8.m b/labnic/Labnic_Anova_2groups_spm8.m new file mode 100644 index 0000000..5563fef --- /dev/null +++ b/labnic/Labnic_Anova_2groups_spm8.m @@ -0,0 +1,187 @@ +function [] = Labnic_rfx_anova_2groups + +%% parameters to specify for each study +root_path = 'D:\SEXO\'; % directory where are the populations, main directory +name_ana = 'analyse_fMRI\ana_sexo_rating\'; %or 'ana2'; etc +dirname = 'RFX_sexo_2groups'; % name for the directory of the random analysis +cons = [7 9 11 13];% which contrast is it in the 1st level analysis? + +%% defining the pop_style +cd (root_path) +Population = spm_select(Inf,'dir','which population do you want to process?'); +npop = size(Population,1); + +%% Definition of subjects to process +for pop=1:npop + cd(Population(pop,1:end)) + subject{pop}= spm_select(Inf,'dir','which subjects do you want to process?'); +end +Subject = char(subject); +nsuj = size(Subject,1); +n1 = size(subject{1},1); +n2 = size(subject{2},1); +%% Creation of directory for the rfx +dirstr = strcat(root_path,dirname); +if ~exist(strcat(root_path,dirname)) + mkdir(strcat(root_path,dirname)) +end +cd (dirstr) +ncon = length(cons); + +fnm = 'con'; +% get image files names +matlabbatch{1}.spm.stats.factorial_design.des.fblock.fac(1).name = 'group';%second factor +matlabbatch{1}.spm.stats.factorial_design.des.fblock.fac(2).name = 'condition'; %first factor +matlabbatch{1}.spm.stats.factorial_design.des.fblock.fac(3).name = 'subject'; + +matlabbatch{1}.spm.stats.factorial_design.des.fblock.fac(1).dept = 0; %conditions are dept because coming from the same subject +matlabbatch{1}.spm.stats.factorial_design.des.fblock.fac(2).dept = 1; %deriv are dept +matlabbatch{1}.spm.stats.factorial_design.des.fblock.fac(3).dept = 0; %subjects are not dept + +matlabbatch{1}.spm.stats.factorial_design.des.fblock.fac(1).variance = 1; %unequal variance for groups +matlabbatch{1}.spm.stats.factorial_design.des.fblock.fac(2).variance = 0; %equal variance for condition +matlabbatch{1}.spm.stats.factorial_design.des.fblock.fac(3).variance = 0; %equal variance for subjects + +for s = 1:nsuj + su = deblank(Subject(s,:)); + scans = []; + conds = []; + count = 0; + for cond = 1:ncon + count = count+1; + con = cons(count); + scans = [scans;{sprintf('%s%s_%04d.img',su,fnm,con)}]; + if s<16 + group = 1; + else + group = 2; + end + conds = [conds; group cond s]; + end + matlabbatch{1}.spm.stats.factorial_design.des.fblock.fsuball.fsubject(s).scans = scans; + matlabbatch{1}.spm.stats.factorial_design.des.fblock.fsuball.fsubject(s).conds = conds; +end +matlabbatch{1}.spm.stats.factorial_design.des.fblock.maininters{1}.fmain.fnum = 1; +matlabbatch{1}.spm.stats.factorial_design.des.fblock.maininters{2}.fmain.fnum = 2; +matlabbatch{1}.spm.stats.factorial_design.des.fblock.maininters{3}.inter.fnums = [1 2]; +matlabbatch{1}.spm.stats.factorial_design.dir = {dirstr}; + +%%for covariation +% matlabbatch{1}.spm.stats.factorial_design.cov.c = repmat([20 24 32 23 30 31 24 17 25 26 25 27 21 29 20 21 25 22 29 26 25 25 25 28]',1,ncon); +% %matlabbatch{1}.spm.stats.factorial_design.cov.c = repmat([92 106 92 105 91 96 89 94 107 112 106 107 101 105 87 93 89 105 118 80 95 85 90 105]',1,8); +% matlabbatch{1}.spm.stats.factorial_design.cov.cname = 'impulsivity'; +% matlabbatch{1}.spm.stats.factorial_design.cov.iCFI = 1; +% matlabbatch{1}.spm.stats.factorial_design.cov.iCC = 1; + +matlabbatch{2}.spm.stats.fmri_est.spmmat = {[dirstr filesep 'SPM.mat']}; +matlabbatch{2}.spm.stats.fmri_est.method.Classical = 1; +save('ANOVA_2groups','matlabbatch') +spm_jobman('run',matlabbatch); + +%% Contrast part +load ([dirstr filesep 'SPM.mat']) +SPM = rmfield(SPM,'xCon'); + +%% F contrast +cn=1; +cnam{cn} = 'F-task'; +cwgt{cn} = [kron(eye(npop),ones(ncon,1)) repmat(eye(ncon),npop,1) eye(ncon*npop)]; +ctyp{cn} = 'F'; +%% main effect of group +cn = cn+1; +cnam{cn} = 'C-P'; +cwgt{cn} = [1 -1 zeros(1,ncon) ones(1,ncon)/ncon -ones(1,ncon)/ncon]; +ctyp{cn} = 'T'; +cn = cn+1; +cnam{cn} = 'P-C'; +cwgt{cn} = -cwgt{cn-1}; +ctyp{cn} = 'T'; + +%% main effect of conditions +cn = cn+1; +cnam{cn} = 'E-N'; +con = [1 1 -1 -1]; +cwgt{cn} = [zeros(1,npop) con con*n1/nsuj con*n2/nsuj]; +ctyp{cn} = 'T'; +cn = cn+1; +cnam{cn} = 'N-E'; +cwgt{cn} = -cwgt{cn-1}; +ctyp{cn} = 'T'; + +cn = cn+1; +cnam{cn} = 'M-C'; +con = [1 -1 1 -1]; +cwgt{cn} = [zeros(1,npop) con con*n1/nsuj con*n2/nsuj]; +ctyp{cn} = 'T'; +cn = cn+1; +cnam{cn} = 'C-M'; +cwgt{cn} = -cwgt{cn-1}; +ctyp{cn} = 'T'; + +cn = cn+1; +cnam{cn} = 'EM-NM'; +con = [1 0 -1 0]; +cwgt{cn} = [zeros(1,npop) con con*n1/nsuj con*n2/nsuj]; +ctyp{cn} = 'T'; +cn = cn+1; +cnam{cn} = 'NM-EM'; +cwgt{cn} = -cwgt{cn-1}; +ctyp{cn} = 'T'; + +cn = cn+1; +cnam{cn} = 'EC-NC'; +con = [0 1 0 -1]; +cwgt{cn} = [zeros(1,npop) con con*n1/nsuj con*n2/nsuj]; +ctyp{cn} = 'T'; +cn = cn+1; +cnam{cn} = 'NC-EC'; +cwgt{cn} = -cwgt{cn-1}; +ctyp{cn} = 'T'; + +cn = cn+1; +cnam{cn} = 'EC-EM'; +con = [-1 1 0 0]; +cwgt{cn} = [zeros(1,npop) con con*n1/nsuj con*n2/nsuj]; +ctyp{cn} = 'T'; +cn = cn+1; +cnam{cn} = 'EM-EC'; +cwgt{cn} = -cwgt{cn-1}; +ctyp{cn} = 'T'; + +cn = cn+1; +cnam{cn} = 'NC-NM'; +con = [0 0 -1 1]; +cwgt{cn} = [zeros(1,npop) con con*n1/nsuj con*n2/nsuj]; +ctyp{cn} = 'T'; +cn = cn+1; +cnam{cn} = 'NM-NC'; +cwgt{cn} = -cwgt{cn-1}; +ctyp{cn} = 'T'; +%% interactions +cn = cn+1; +cnam{cn} = 'E-N_C-P'; +con = [1 1 -1 -1]; +cwgt{cn} = [zeros(1,npop+ncon) con -con]; +ctyp{cn} = 'T'; +cn = cn+1; +cnam{cn} = 'E-N_P-C'; +cwgt{cn} = -cwgt{cn-1}; +ctyp{cn} = 'T'; + +cn = cn+1; +cnam{cn} = 'M-C_C-P'; +con = [1 -1 1 -1]; +cwgt{cn} = [zeros(1,npop+ncon) con -con]; +ctyp{cn} = 'T'; +cn = cn+1; +cnam{cn} = 'M-C_P-C'; +cwgt{cn} = -cwgt{cn-1}; +ctyp{cn} = 'T'; + +for c = 1:cn + cw = [cwgt{c}]'; % pad with zero for constant + SPM.xCon(c) = spm_FcUtil('Set',cnam{c},ctyp{c},'c',cw,SPM.xX.xKXs); +end + +spm_contrasts(SPM); +return \ No newline at end of file diff --git a/labnic/Labnic_Anova_spm8.m b/labnic/Labnic_Anova_spm8.m new file mode 100644 index 0000000..749a6cc --- /dev/null +++ b/labnic/Labnic_Anova_spm8.m @@ -0,0 +1,97 @@ +function [] = Labnic_rfx_anova_with_covariates_spm8 +% create the second level analyis (random effect analysis RFX) +% create an ANOVA with 2 factors in this example +% you must specified the 4 first lines of the script +% name_ana_grp is the directory for the main RFX analysis +% Analysis of each subjects must be in: +% **...\populations\subjects\analyse\ana1\** + +%% parameters to specify for each study %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +myjob = [1 1]; % myjob(1)==1 : build + estimate model myjob(2)==1 : build contrasts +root_path = 'e:\Data XP\fMRI EWA\data_fMRI\'; % directory where are the populations, main directory +name_ana_ind = '3. analyse\quadriface\'; % Folder name of 1st level analysis +name_ana_grp = 'grp_analysis\Quadri_ANOVA12_21S'; % name for the directory of the random analysis +cons = [3:6 8:11 13:16]; % which contrast is it in the 1st level analysis? +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +dirstr = strcat(root_path,name_ana_grp); +ncon = length(cons); + +if myjob(1) + %% defining the subject list + dbstop if error + cd (root_path) + Subject{1}= spm_select(Inf,'dir','which subjects do you want to process?'); + + Subject = char(Subject); + nsuj = size(Subject,1); + + %% Creation of directory for the rfx + if ~exist(strcat(root_path,name_ana_grp)) + mkdir(strcat(root_path,name_ana_grp)) + end + cd (dirstr) + fnm = 'con'; + + answer_sphericity = questdlg('Do you want to correct for potential violations of sphericity of the factor condition ?','Sphericity correction', 'Yes' , 'No' ,'Yes'); + answer_sphericity = strcmp(answer_sphericity, 'Yes'); + + %% defining the factors + matlabbatch{1}.spm.stats.factorial_design.des.fblock.fac(1).name = 'condition'; % first factor condition + matlabbatch{1}.spm.stats.factorial_design.des.fblock.fac(2).name = 'subject'; % second factor subject + + %% Dependance within factors + matlabbatch{1}.spm.stats.factorial_design.des.fblock.fac(1).dept = 1; % Conditions are dependant (=1) because the measure is performed several times within each subject + matlabbatch{1}.spm.stats.factorial_design.des.fblock.fac(2).dept = 0; % Subjects are independant (=0) because they are assumed to pertain to the same group + + %% Variance within factors (ie sphericity assumed or not) + matlabbatch{1}.spm.stats.factorial_design.des.fblock.fac(1).variance = answer_sphericity; % Unequal variance assumed (0: equal; 1:unequal) + matlabbatch{1}.spm.stats.factorial_design.des.fblock.fac(2).variance = 0; % Unequal variance assumed (0: equal; 1:unequal) + + %% get the images from each subject + for s = 1:nsuj + su = deblank(Subject(s,:)); + scans = []; + conds = []; + for cond = 1:ncon + con = cons(cond); + scans = [scans;{sprintf('%s%s%s%s%s_%04d.img',su,filesep,name_ana_ind,filesep,fnm,con)}]; + conds = [conds; cond s]; + end + matlabbatch{1}.spm.stats.factorial_design.des.fblock.fsuball.fsubject(s).scans = scans; + matlabbatch{1}.spm.stats.factorial_design.des.fblock.fsuball.fsubject(s).conds = conds; + end + matlabbatch{1}.spm.stats.factorial_design.des.fblock.maininters{1}.fmain.fnum = 1; + matlabbatch{1}.spm.stats.factorial_design.des.fblock.maininters{2}.fmain.fnum = 2; + matlabbatch{1}.spm.stats.factorial_design.dir = {dirstr}; + + %% covariates if any + % matlabbatch{1}.spm.stats.factorial_design.cov(1).c = [10,9,11,7,10,9,3,7,11,10,10,8,6,10,7,10,2,5,7,3,10,4,6,7,2,1,11,9,8,6,4,8,]; + % matlabbatch{1}.spm.stats.factorial_design.cov(1).cname = 'age';%name of the covariate + % matlabbatch{1}.spm.stats.factorial_design.cov(1).iCFI = 2; + % matlabbatch{1}.spm.stats.factorial_design.cov(1).iCC = 1;% do you want interaction with factor iCFI-1? + + matlabbatch{2}.spm.stats.fmri_est.spmmat = {[dirstr filesep 'SPM.mat']}; + matlabbatch{2}.spm.stats.fmri_est.method.Classical = 1; + save('ANOVA','matlabbatch') %save the .mat + spm_jobman('run',matlabbatch); + +end + +if myjob(2) + %% Contrast part + cd (dirstr) + load ([dirstr filesep 'SPM.mat']) + SPM = rmfield(SPM,'xCon'); + matlabbatch = []; + + matlabbatch{1}.spm.stats.con.spmmat = {[pwd filesep 'SPM.mat']}; + matlabbatch{1}.spm.stats.con.consess{1}.fcon.name = 'F(Task)'; + matlabbatch{1}.spm.stats.con.consess{1}.fcon.convec = {ones(ncon)*(-1/ncon)+eye(ncon)}; + matlabbatch{1}.spm.stats.con.consess{1}.fcon.sessrep = 'none'; + matlabbatch{1}.spm.stats.con.delete = 1; + + spm_jobman('run',matlabbatch); +end + +return \ No newline at end of file diff --git a/labnic/Labnic_PPI_spm8.m b/labnic/Labnic_PPI_spm8.m new file mode 100644 index 0000000..10e05aa --- /dev/null +++ b/labnic/Labnic_PPI_spm8.m @@ -0,0 +1,753 @@ +function [] = Labnic_PPI_spm8 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +global Session_filter scandir namevar psyconvar rad_sphere TR nslices region + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +mypoints = [42 -43 -23]; %% seed voxel +region = 'rFFA'; %% region corresponding to the voxel +namevar={'Cf' 'If' 'Cs' 'Is'}; +psyconvar=[1 1 -1 -1]; +root_path = 'C:\experiments\FLANKER\'; % directory where are the populations, main directory +anadir = 'ana_woST\'; %or 'ana2'; etc +Session_filter = '*ATTHYPNO_RUN*'; +rad_sphere = 8; % radius of the sphere in mm +TR = 1.7; % your TR +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +mycon=1; %will take the F-task contrast (1) to define the conditions of the task +%% defining the pop_style +cd (root_path) +Population = spm_select(Inf,'dir','which population do you want to process?'); +npop = size(Population,1); + +%% Definition of subjects to process +for pop=1:npop + cd(Population(pop,1:end)) + Subject{pop}= spm_select(Inf,'dir','which subjects do you want to process?'); +end +Subject = char(Subject); +nsuj = size(Subject,1); + +for sub = 1:size(Subject,1) + su = deblank(Subject(sub,:)); + scandir = [su 'scans']; + + xyz= mypoints; + path_ana =fullfile(deblank(Subject(sub,:)),'analyse',anadir); + [SPM,xSPM] = spm_getSPM_PPI(path_ana,mycon); + + % find number of slices +%==================================================================== +V = spm_vol(SPM.xY.P(1,:)); +if iscell(V) + nslices = V{1}{1}.dim(3); +else + nslices = V(1).dim(3); +end + + XYZ = SPM.xVol.XYZ; + XYZmm = SPM.xVol.M(1:3,:)*[XYZ; ones(1,size(XYZ,2))]; + [xyz,i] = spm_XYZreg('NearestXYZ',xyz,XYZmm); + + %% extract neural signal from seed region + myPPI = spm_regions_PPI(SPM,xSPM,xyz); + + %% create a new model and estimates it + Labnic_analyse_PPI(su,myPPI,region); + + %% make the contrasts + Labnic_contrast_PPI(su,region); +end + +return + +%% subfunctions +function dirs = get_directories(suj_path) +% returns a cell array holding true directories located at path +D=dir(suj_path); +dirs={D(~(strcmp({D.name},'.')+strcmp({D.name},'..')+~[D.isdir])).name}; +% dirs = {'morphing_titrage_run1',... +% 'morphing_titrage_run2',... +% 'morphing_test_run5',... +% 'morphing_faceloc_run11'}; + +return + +function PPI = Labnic_extract_PPI(SPM, myvoi,psyconvar,namevar) +% Bold deconvolution to create physio- or psycho-physiologic interactions + + +% check inputs and set up variables +%---------------------------------------------------------------------- +RT = SPM.xY.RT; +dt = SPM.xBF.dt; +NT = round(RT/dt); + + +% Ask whether to perform physiophysiologic or psychophysiologic interactions +%-------------------------------------------------------------------------- +% set(Finter,'name','PPI Setup') +% ppiflag = 'psychophysiologic interaction'; + +% case 'psychophysiologic interaction' % get hemodynamic response +%===================================================================== +% spm_input('physiological variable:... ',2,'d'); +% P = spm_get(1,'VOI*.mat',{'select VOIs'}); +P = myvoi; +p = load(P,'xY'); +xY(1) = p.xY; +Sess = SPM.Sess(xY(1).Sess); + +% get 'causes' or inputs U +%---------------------------------------------------------------------- +%spm_input('Psychological variable:... ',2,'d'); +U.name = {}; +U.u = []; +U.w = []; + +for i = 1:length(Sess.U) + Name(i) = Sess.U(i).name; +end +count = 0; +for i = 1:length(namevar) + if any(strcmp(namevar(i),Name)); + count = count +1; + mypsyconvar = psyconvar(i); + for j = 1:length(Sess.U(count).name) + U.u = [U.u Sess.U(count).u(33:end,j)];% don't understand + U.name{end + 1} = Sess.U(count).name{j}; + U.w = [U.w mypsyconvar]; + end + end +end +if sum(U.w) < 0 + U.w(U.w == 1) = 4/(4+sum(U.w)); +end + +% name of PPI file to be saved +%------------------------------------------------------------------------- +[tmp, myfile]= fileparts(myvoi); +PPI.name = ['PPI_' myfile] + +% Setup variables +%------------------------------------------------------------------------- +N = length(xY(1).u); +k = 1:NT:N*NT; % microtime to scan time indices + +% create basis functions and hrf in scan time and microtime +%------------------------------------------------------------------------- +%spm('Pointer','watch') +hrf = spm_hrf(dt); + +% create convolved explanatory {Hxb} variables in scan time +%------------------------------------------------------------------------- +xb = spm_dctmtx(N*NT + 128,N); +Hxb = zeros(N,N); +for i = 1:N + Hx = conv(xb(:,i),hrf); + Hxb(:,i) = Hx(k + 128); +end +xb = xb(129:end,:); + + +% get confounds (in scan time) and constant term +%------------------------------------------------------------------------- +X0 = xY(1).X0; +M = size(X0,2); + + +% get response variable, +%------------------------------------------------------------------------- +for i = 1:size(xY,2) + Y(:,i) = xY(i).u; +end + + +% remove confounds and save Y in ouput structure +%------------------------------------------------------------------------- +% Yc = Y - X0*inv(X0'*X0)*X0'*Y; %SOSART +Yc = Y - X0*pinv(X0'*X0)*X0'*Y; +PPI.Y = Yc(:,1); +if size(Y,2) == 2 + PPI.P = Yc(:,2); +end + + +% specify covariance components; assume neuronal response is white +% treating confounds as fixed effects +%------------------------------------------------------------------------- +Q = speye(N,N)*N/trace(Hxb'*Hxb); +Q = blkdiag(Q, speye(M,M)*1e6 ); + +% get whitening matrix (NB: confounds have already been whitened) +%------------------------------------------------------------------------- +W = SPM.xX.W(Sess.row,Sess.row); + +% create structure for spm_PEB +%------------------------------------------------------------------------- +clear P +P{1}.X = [W*Hxb X0]; % Design matrix for lowest level +P{1}.C = speye(N,N)/4; % i.i.d assumptions +P{2}.X = sparse(N + M,1); % Design matrix for parameters (0's) +P{2}.C = Q; + + +% case 'psychophysiologic interaction' +%===================================================================== + +% COMPUTE PSYCHOPHYSIOLOGIC INTERACTIONS +% use basis set in microtime +%--------------------------------------------------------------------- +% get parameter estimates and neural signal; beta (C) is in scan time +% This clever trick allows us to compute the betas in scan time which is +% much quicker than with the large microtime vectors. Then the betas +% are applied to a microtime basis set generating the correct neural +% activity to convolve with the psychological variable in mircrotime +%--------------------------------------------------------------------- +C = spm_PEB(Y,P); +xn = xb*C{2}.E(1:N); +xn = spm_detrend(xn); + +% setup psychological variable from inputs and contast weights +%--------------------------------------------------------------------- +PSY = zeros(N*NT,1); +for i = 1:size(U.u,2) + tmp = full(U.u(:,i)*U.w(:,i)); + tmp = tmp(1:(N*NT)); + PSY = PSY + tmp; +end +PSY = spm_detrend(PSY); + +% multiply psychological variable by neural signal +%--------------------------------------------------------------------- +PSYxn = PSY.*xn; + +% convolve and resample at each scan for bold signal +%--------------------------------------------------------------------- +ppi = conv(PSYxn,hrf); +ppi = ppi(k); + +% similarly for psychological effect +%--------------------------------------------------------------------- +PSYHRF = conv(PSY,hrf); +PSYHRF = PSYHRF(k); + +% save psychological variables +%--------------------------------------------------------------------- +PPI.psy = U; +PPI.P = PSYHRF; +PPI.xn = xn; +PPI.ppi = spm_detrend(ppi); + +return + +function [myPPI] = spm_regions_PPI(SPM,xSPM,xyz) +xY.xyz = xyz; + +global Session_filter scandir psyconvar namevar rad_sphere +%-Get adjustment options and VOI name +%----------------------------------------------------------------------- +spm_input(sprintf('at [%3.0f %3.0f %3.0f]',xY.xyz),1,'d',... + 'VOI time-series extraction') + +if ~isfield(xY,'name') + tmp = deblank(num2str(xyz')); + xY.name = strrep(tmp, ' ',''); +end + +xY.Ic = 1; + +xY.def = 'sphere'; +Q = ones(1,size(xSPM.XYZmm,2)); + +if ~isfield(xY,'spec') + xY.spec = rad_sphere; % radius of the sphere +end +d = [xSPM.XYZmm(1,:) - xyz(1); + xSPM.XYZmm(2,:) - xyz(2); + xSPM.XYZmm(3,:) - xyz(3)]; +Q = find(sum(d.^2) <= xY.spec^2); + +%% Get raw data, whiten and filter +y = spm_get_data(SPM.xY.VY,xSPM.XYZ(:,Q)); +y = spm_filter(SPM.xX.K,SPM.xX.W*y); +xY.XYZmm = xSPM.XYZmm(:,Q); + +%-Parameter estimates: beta = xX.pKX*xX.K*y +%--------------------------------------------------------------- +beta = spm_get_data(SPM.Vbeta,xSPM.XYZ(:,Q)); + +%-subtract Y0 = XO*beta, Y = Yc + Y0 + e +%--------------------------------------------------------------- +y = y - spm_FcUtil('Y0',SPM.xCon(xY.Ic),SPM.xX.xKXs,beta); + +% extract session-specific rows from data and confounds +%----------------------------------------------------------------------- +Session=get_directories(fullfile(scandir,Session_filter)); +nses = size(Session,2); + +for ses=1:nses + clear xY.X0 y2 + xY.X0 = SPM.xX.xKXs.X(:,[SPM.xX.iB SPM.xX.iG]); + xY.Sess = ses; + + try + i = SPM.Sess(xY.Sess).row; + y2 = y(i,:); + xY.X0 = xY.X0(i,:); + end + + % and add session-specific filter confounds + %----------------------------------------------------------------------- + try + xY.X0 = [xY.X0 SPM.xX.K(xY.Sess).X0]; + end + + %======================================================================= + try + xY.X0 = [xY.X0 SPM.xX.K(xY.Sess).KH]; % Compatibility check + end + + % compute regional response in terms of first eigenvariate + %----------------------------------------------------------------------- + [m n] = size(y2); + if m > n + [v s v] = svd(spm_atranspa(y2)); + s = diag(s); + v = v(:,1); + u = y2*v/sqrt(s(1)); + else + [u s u] = svd(spm_atranspa(y2')); + s = diag(s); + u = u(:,1); + v = y2'*u/sqrt(s(1)); + end + d = sign(sum(v)); + u = u*d; + v = v*d; + Y = u*sqrt(s(1)/n); + + % set in structure + %----------------------------------------------------------------------- + xY.y = y2; + xY.u = Y; + xY.v = v; + xY.s = s; + + str = ['VOI_' xY.name]; + if isfield(xY,'Sess') + if length(xY.Sess) == 1 + str = sprintf('VOI_%s_%i',xY.name,xY.Sess); + end + end + myvoi= [fullfile(SPM.swd,str) '.mat']; + save(fullfile(SPM.swd,str),'Y','xY') + myPPI{ses} = Labnic_extract_PPI(SPM,myvoi,psyconvar,namevar); +end +return + +function [SPM,xSPM] = spm_getSPM_PPI(swd,Ic) +SCCSid = '2.51'; + +%-GUI setup +%----------------------------------------------------------------------- +SPMid = spm('SFnBanner',mfilename,SCCSid); +spm_help('!ContextHelp',mfilename) + +%-Select SPM.mat & note SPM results directory +%----------------------------------------------------------------------- +% swd = spm_str_manip(spm_get(1,'SPM.mat','Select SPM.mat'),'H'); + +%-Preliminaries... +%======================================================================= + +%-Load SPM.mat +%----------------------------------------------------------------------- +load(fullfile(swd,'SPM.mat')); +SPM.swd = swd; + +%-Get volumetric data from SPM.mat +%----------------------------------------------------------------------- +try + xX = SPM.xX; %-Design definition structure + XYZ = SPM.xVol.XYZ; %-XYZ coordinates + S = SPM.xVol.S; %-search Volume {voxels} + R = SPM.xVol.R; %-search Volume {resels} + M = SPM.xVol.M(1:3,1:3); %-voxels to mm matrix + VOX = sqrt(diag(M'*M))'; %-voxel dimensions +catch + + % check the model has been estimated + %--------------------------------------------------------------- + str = { 'This model has not been estimated.';... + 'Would you like to estimate it now?'}; + if spm_input(str,1,'bd','yes|no',[1,0],1) + [SPM] = spm_spm(SPM); + else + return + end +end + + +%-Contrast definitions +%======================================================================= + +%-Load contrast definitions (if available) +%----------------------------------------------------------------------- +try + xCon = SPM.xCon; +catch + xCon = {}; +end + +nc = length(Ic); % Number of contrasts + +n = 1; + +%-Enforce orthogonality of multiple contrasts for conjunction +% (Orthogonality within subspace spanned by contrasts) +%----------------------------------------------------------------------- +Im = []; +pm = []; +Ex = []; + +%-Create/Get title string for comparison +%----------------------------------------------------------------------- +str = xCon(Ic).name; +if Ex + mstr = 'masked [excl.] by'; +else + mstr = 'masked [incl.] by'; +end +if length(Im) == 1 + str = sprintf('%s (%s %s at p=%g)',str,mstr,xCon(Im).name,pm); + +elseif ~isempty(Im) + str = [sprintf('%s (%s {%d',str,mstr,Im(1)),... + sprintf(',%d',Im(2:end)),... + sprintf('} at p=%g)',pm)]; +end +titlestr = str; + +%-Compute & store contrast parameters, contrast/ESS images, & SPM images +%======================================================================= +SPM.xCon = xCon; +SPM = spm_contrasts(SPM,unique([Ic,Im])); +xCon = SPM.xCon; +VspmSv = cat(1,xCon(Ic).Vspm); +STAT = xCon(Ic(1)).STAT; + +%-Check conjunctions - Must be same STAT w/ same df +%----------------------------------------------------------------------- +if (nc > 1) && (any(diff(double(cat(1,xCon(Ic).STAT)))) || ... + any(abs(diff(cat(1,xCon(Ic).eidf))) > 1)) + error('illegal conjunction: can only conjoin SPMs of same STAT & df') +end + +%-Degrees of Freedom and STAT string describing marginal distribution +%----------------------------------------------------------------------- +df = [xCon(Ic(1)).eidf xX.erdf]; +if nc>1 + if n>1 + str = sprintf('^{%d \\{Ha:k\\geq%d\\}}',nc,(nc-n)+1); + else + str = sprintf('^{%d \\{Ha:k=%d\\}}',nc,(nc-n)+1); + end +else + str = ''; +end + +switch STAT + case 'T' + STATstr = sprintf('%c%s_{%.0f}','T',str,df(2)); + case 'F' + STATstr = sprintf('%c%s_{%.0f,%.0f}','F',str,df(1),df(2)); + case 'P' + STATstr = sprintf('%s^{%0.2f}','PPM',df(1)); +end + +%-Compute (unfiltered) SPM pointlist for masked conjunction requested +%======================================================================= +fprintf('\t%-32s: %30s\n','SPM computation','...initialising') %-# + +%-Compute conjunction as minimum of SPMs +%----------------------------------------------------------------------- +Z = Inf; +for i = Ic + Z = min(Z,spm_get_data(xCon(i).Vspm,XYZ)); +end + +% P values for False Discovery FDR rate computation (all search voxels) +%======================================================================= +switch STAT + case 'T' + Ps = (1 - spm_Tcdf(Z,df(2))).^n; + case 'P' + Ps = (1 - Z).^n; + case 'F' + Ps = (1 - spm_Fcdf(Z,df)).^n; +end + +%-Compute mask and eliminate masked voxels +%----------------------------------------------------------------------- +for i = Im + fprintf('%s%30s',sprintf('\b')*ones(1,30),'...masking') + + Mask = spm_get_data(xCon(i).Vspm,XYZ); + um = spm_u(pm,[xCon(i).eidf,xX.erdf],xCon(i).STAT); + if Ex + Q = Mask <= um; + else + Q = Mask > um; + end + XYZ = XYZ(:,Q); + Z = Z(Q); + if isempty(Q) + fprintf('\n') %-# + warning(sprintf('No voxels survive masking at p=%4.2f',pm)) + break + end +end + +%-clean up interface +%----------------------------------------------------------------------- +fprintf('\t%-32s: %30s\n','SPM computation','...done') %-# +spm('Pointer','Arrow') + + + +%======================================================================= +% - H E I G H T & E X T E N T T H R E S H O L D S +%======================================================================= + +%-Height threshold - classical inference +%----------------------------------------------------------------------- +u = -Inf; +k = 0; +u = 1; +if u <= 1; u = spm_u(u^(1/n),df,STAT); end + + + +%-Calculate height threshold filtering +%------------------------------------------------------------------- +Q = find(Z > u); + +%-Apply height threshold +%------------------------------------------------------------------- +Z = Z(:,Q); +XYZ = XYZ(:,Q); +if isempty(Q) + warning(sprintf('No voxels survive height threshold u=%0.2g',u)) +end + + +%-Extent threshold (disallowed for conjunctions) +%----------------------------------------------------------------------- +if ~isempty(XYZ) + + %-Get extent threshold [default = 0] + %------------------------------------------------------------------- + k = 0; + + %-Calculate extent threshold filtering + %------------------------------------------------------------------- + A = spm_clusters(XYZ); + Q = []; + for i = 1:max(A) + j = find(A == i); + if length(j) >= k; Q = [Q j]; end + end + + % ...eliminate voxels + %------------------------------------------------------------------- + Z = Z(:,Q); + XYZ = XYZ(:,Q); + if isempty(Q) + warning(sprintf('No voxels survive extent threshold k=%0.2g',k)) + end + +else + + k = 0; + +end % (if ~isempty(XYZ)) + + +%======================================================================= +% - E N D +%======================================================================= +fprintf('\t%-32s: %30s\n','SPM computation','...done') %-# + +%-Assemble output structures of unfiltered data +%======================================================================= +xSPM = struct('swd', swd,... + 'title', titlestr,... + 'Z', Z,... + 'n', n,... + 'STAT', STAT,... + 'df', df,... + 'STATstr', STATstr,... + 'Ic', Ic,... + 'Im', Im,... + 'pm', pm,... + 'Ex', Ex,... + 'u', u,... + 'k', k,... + 'XYZ', XYZ,... + 'XYZmm', SPM.xVol.M(1:3,:)*[XYZ; ones(1,size(XYZ,2))],... + 'S', SPM.xVol.S,... + 'R', SPM.xVol.R,... + 'FWHM', SPM.xVol.FWHM,... + 'M', SPM.xVol.M,... + 'iM', SPM.xVol.iM,... + 'DIM', SPM.xVol.DIM,... + 'VOX', VOX,... + 'Vspm', VspmSv,... + 'Ps', Ps); + +% RESELS per voxel (density) if it exists +%----------------------------------------------------------------------- +if isfield(SPM,'VRpv'), xSPM.VRpv = SPM.VRpv; end +return + +function [] = Labnic_analyse_PPI(su,myPPI,region) + +global Session_filter scandir TR nslices + +anatype = ['analyse' filesep 'PPI' filesep]; +% select the sessions of interest +Session=get_directories(fullfile(scandir,Session_filter)); +nses = size(Session,2); + +myana= ['PPI_' region filesep]; %ROI + +% specify data: matrix of filenames and TR +%=========================================================================== +nscans=[]; +SPM.xY.P=[]; +clear tmp + +for ses= 1:nses + smoothfolder = fullfile(scandir,deblank(Session{ses}),[filesep 'swf' filesep]); + tmp{ses} = spm_select('List',smoothfolder,'^sw.*nii'); + nscans = [nscans size(tmp{ses},1)]; + SPM.xY.P = strvcat(SPM.xY.P,[repmat(smoothfolder,nscans(ses),1),tmp{ses}]); +end + +% basis functions and timing parameters +%--------------------------------------------------------------------------- +ref_slice=floor(nslices/2); % middle slice in time +SPM.xBF.T = nslices; % number of time bins per scan % useless? cf. defaults above +SPM.xBF.T0 = ref_slice; % middle slice/timebin % useless? cf. defaults above +SPM.xBF.UNITS = 'scans'; % OPTIONS: 'scans'|'secs' for onsets +SPM.xBF.Volterra = 1; +SPM.xBF.name = 'hrf'; +SPM.xBF.order = 1; %2= hrf + time deriv --> 'hrf (with time derivative)'; +SPM.xBF.length = 32; % length in seconds + +SPM.xY.RT = TR; + +% number of scans and sessions +%--------------------------------------------------------------------------- +SPM.nscan = nscans; + +for ses=1:nses + SPM.Sess(ses).U=[]; +end + +rnam = {'PPI' 'contrast' region 'X','Y','Z','x','y','z'}; +for ses=1:nses + fn = spm_select('List',fullfile(scandir,deblank(Session{ses})),'^rp_.*txt'); + [r1,r2,r3,r4,r5,r6] = textread(fullfile(scandir,deblank(Session{ses}),fn(1,:)),'%f%f%f%f%f%f'); + SPM.Sess(ses).C.C = [myPPI{ses}.ppi myPPI{ses}.P myPPI{ses}.Y r1 r2 r3 r4 r5 r6]; + SPM.Sess(ses).C.name = rnam; +end + +defaults.stats.fmri.t = nslices; +defaults.stats.fmri.t0 = ref_slice; + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% subject specific bit +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +wkdir = [su anatype myana]; +if ~exist(wkdir, 'dir'); + mkdir(wkdir); +end +eval(['cd ' (wkdir)]); + +% global normalization: OPTIONS:'Scaling'|'None' +%--------------------------------------------------------------------------- +SPM.xGX.iGXcalc = 'None'; + +% low frequency confound: high-pass cutoff (secs) [Inf = no filtering] +%--------------------------------------------------------------------------- +SPM.xX.K(1).HParam = 128; + +% intrinsic autocorrelations: OPTIONS: 'none'|'AR(1) + w' +%----------------------------------------------------------------------- +SPM.xVi.form = 'AR(1)'; %AR(0.2)???? SOSART + +% specify SPM working dir for this sub +%=========================================================================== +SPM.swd = wkdir; + +% Configure design matrix +%=========================================================================== +SPM = spm_fmri_spm_ui(SPM); + +% Estimate parameters +%=========================================================================== +SPM = spm_spm(SPM); +return + + +function [] = Labnic_contrast_PPI(su,region) + +cd (fullfile(su,['analyse' filesep 'PPI'],['PPI_' region])) +load('SPM.mat'); + +SPM.xCon =[]; % reset xCon + +for i = 1:length(SPM.xX.name)%adding 6 for mvt regressors that are in SPM.xX.name + connam{i} = SPM.xX.name{i}(7:end); +end +PPI = double(strcmp(connam,'PPI')); +contrast = double(strcmp(connam,'contrast')); +vox = double(strcmp(connam,region)); +%---------- +%MAIN EFFECTS +%---------- +cn = 1; +Cnames{cn} = 'PPI'; +cwgt{cn} = PPI; +ctyp{cn} = 'F'; +cn = cn+1; +Cnames{cn} = 'contrast'; +cwgt{cn} = contrast; +ctyp{cn} = 'T'; +cn = cn+1; +Cnames{cn} = region; +cwgt{cn} = vox; +ctyp{cn} = 'T'; +cn = cn+1; +Cnames{cn} = 'PPI'; +cwgt{cn} = PPI; +ctyp{cn} = 'T'; +cn = cn+1; +Cnames{cn} = '-PPI'; +cwgt{cn} = -PPI; +ctyp{cn} = 'T'; + +save contrasts_name Cnames +for c = 1:cn + cw = [cwgt{c}]'; % pad with zero for constant + tmp(c) = spm_FcUtil('Set',Cnames{c},ctyp{c},'c',cw,SPM.xX.xKXs); +end +SPM.xCon = tmp; + +SPM = spm_contrasts(SPM); +return \ No newline at end of file diff --git a/labnic/Labnic_rfx_ones_spm8.m b/labnic/Labnic_rfx_ones_spm8.m new file mode 100644 index 0000000..8496cf3 --- /dev/null +++ b/labnic/Labnic_rfx_ones_spm8.m @@ -0,0 +1,196 @@ +function [] = Labnic_rfx_ones_spm8 +% create the second level analyis (random effect analysis RFX) +% create a one sample t-test with one group of subjects + +% you must speficied the 3 first line of the script +% dirname is the direcotry for the main RFX analysis +% it create a dir for each contrast in this main dir +% this code take into account that some of the subjects do not have all the +% contrasts +% Analysis of each subjects must be in: +% **...\data_fMRI\populations\subjects\analyse\ana1\** +% At the begining, the script create a mean strucutral image to display the +% results of the group analysis + +% this function has been adapted by Virginie Sterpenich (Virginie.Sterpenich@unige.ch) +% and optimised by Yann Cojan (yann.cojan@unige.ch) + +global name_ana; + +%% parameters to specify for each study +root_path = 'C:\experiments\FLANKER\'; % directory where are the populations, main directory +name_ana = 'ana_woST\'; %or 'ana2'; etc +dirname = 'RFX\RFX_ones_hypnosis_subjective_31s'; % name for the directory of the random analysis + +%% defining the pop_style +cd ([root_path filesep 'controls']) +Subject{1}= spm_select(Inf,'dir','which subjects do you want to process?'); +Subject = char(Subject); +nsuj = size(Subject,1); + +%% Creation of directory for the rfx +dirstr = strcat(root_path,filesep,dirname); +if ~exist(strcat(root_path,filesep,dirname)) + mkdir(strcat(root_path,filesep,dirname)) +end +cd(dirstr) + +%% starting analysing +% load the names of the contrasts from the first subject +% it use the contrasts_name matrix created in the Labnic_contrast script +path_suj1 =[deblank(Subject(1,:)) 'analyse' filesep name_ana]; + +% loop over contrasts + +% defaults.modality='FMRI'; +% disp(['Processing contrast ' num2str(icon) ' : ' char(Cnames(icon))]); +% disp('-----------------------------'); +%initialise variables +P = {}; scans = {}; + +% % Create the dir for the contrast of interest +% mkdir(char(Cnames(icon))); +Cnames = {'slatency_Cf' 'slatency_If' 'slatency_Cs' 'slatency_Is' 'slatency_ECf' 'slatency_EIf' 'slatency_ECs' 'slatency_EIs'}; +nb_con = size(Cnames,2); +% loop over subjects +count = 0; +for sub = 1:nsuj + count = count+1; + %go to the subject's analyse folder + path_ana =[deblank(Subject(sub,:)) 'analyse' filesep name_ana]; + + % check the name of the contrast + % Find for this subject the con.img corresponding to the contrast of + % interest (icon) + con = 0; + for icon = 1:nb_con %for td %10:19 %for hrf % 20:26 %for tdd % depending on F and T contrast at the first level + con = con+1; + if sub == 1 %for initialising the struct + matlabbatch{con}.spm.stats.factorial_design.des.t1.scans = {}; + matlabbatch{con}.spm.stats.factorial_design.dir = {char(strcat(dirstr,filesep,Cnames{icon}))}; + if ~exist(matlabbatch{con}.spm.stats.factorial_design.dir{1}) + mkdir(matlabbatch{con}.spm.stats.factorial_design.dir{1}) + end + end + %% mask + matlabbatch{con}.spm.stats.factorial_design.masking.im = 0; + matlabbatch{con}.spm.stats.factorial_design.masking.em = {'C:\experiments\FLANKER\RFX\RFX_anova_woST\mask.img,1'}; + % Take the adequate 'latency' img + str = Cnames{icon}; + files = spm_select('ExtFPList',path_ana,str); + % put the image con in the appropriate group + matlabbatch{con}.spm.stats.factorial_design.des.t1.scans{end+1} = files; + + + % %matlabbatch{con}.spm.stats.factorial_design.cov(1).c = [139 198 + % 178 184 161 172 123 142 125 174 214 177 102 145 121 136 174 109 105 139 175 131 148 130 142 183 ]'; + matlabbatch{con}.spm.stats.factorial_design.cov(1).c = [10,9,11,7,10,9,3,7,11,10,10,8,6,10,7,10,2,5,7,3,10,4,6,7,2,1,11,9,8,6,4,8;]'; %hypnosis subj + matlabbatch{con}.spm.stats.factorial_design.cov(1).cname = 'hypnosis subjective'; + matlabbatch{con}.spm.stats.factorial_design.cov(1).iCFI = 1; + matlabbatch{con}.spm.stats.factorial_design.cov(1).iCC = 1; + + % matlabbatch{con}.spm.stats.factorial_design.cov(1).c = [527.93,430.56,544.31,437.01,418.69,606.21,516.66,373.88,612.50,499.77,430.23,577.45,462.00,454.70,460.17,521.39,395.60,429.86,531.31,484.48,465.62,426.93,420.84,681.16,638.34,447.86,407.31,461.15,370.47,473.02]'; + % matlabbatch{con}.spm.stats.factorial_design.cov(1).cname = 'reaction time'; + % matlabbatch{con}.spm.stats.factorial_design.cov(1).iCFI = 1; + % matlabbatch{con}.spm.stats.factorial_design.cov(1).iCC = 1; + % + % matlabbatch{con}.spm.stats.factorial_design.cov(2).c = [23.18,27.08,80.97,25.36,17.07,73.28,36.61,21.03,22.30,42.92,34.49,116.21,57.44,54.08,40.86,24.04,33.38,58.98,33.16,83.69,58.14,15.97,34.38,-22.34,10.28,78.25,19.23,19.67,49.42,10.67,52.88]'; + % matlabbatch{con}.spm.stats.factorial_design.cov(2).cname = 'incongruency effect'; + % matlabbatch{con}.spm.stats.factorial_design.cov(2).iCFI = 1; + % matlabbatch{con}.spm.stats.factorial_design.cov(2).iCC = 1; + % + % matlabbatch{con}.spm.stats.factorial_design.cov(2).c = [-7.2105,1.6933,-10.3191,-5.9564,12.103,-27.4546,11.0383,-8.5667,-9.85,4.7017,-4.4777,5.3444,-17.0412,-7.1307,4.0314,7.9951,-5.6508,14.6891,-17.6318,10.7182,-2.3345,4.404,9.8678,6.1827,5.8837,-1.9921,8.745,-5.7231,-2.1239,-3.0548;]'; + % matlabbatch{con}.spm.stats.factorial_design.cov(2).cname = 'TR_F-S'; + % matlabbatch{con}.spm.stats.factorial_design.cov(2).iCFI = 1; + % matlabbatch{con}.spm.stats.factorial_design.cov(2).iCC = 1; + + % matlabbatch{con}.spm.stats.factorial_design.cov(2).c = [-18.9881,-24.9904,-85.9272,-28.1663,-7.4509,-69.0327,-33.1592,-21.4825,-22.5177,-31.5774,-30.9564,-119.0013,-63.5359,-54.963,-36.2025,-19.6949,-36.909,-57.6552,-38.0087,-91.712,-55.8303,-16.1885,-24.5821,23.7739,-69.5856,-16.0828,-14.7308,-50.3663,-10.876,-47.7222;]'; + % matlabbatch{con}.spm.stats.factorial_design.cov(2).cname = 'TR_Cf-If'; + % matlabbatch{con}.spm.stats.factorial_design.cov(2).iCFI = 1; + % matlabbatch{con}.spm.stats.factorial_design.cov(2).iCC = 1; + % + % matlabbatch{con}.spm.stats.factorial_design.cov(2).c = [20.0982,31.4695,72.5183,18.2599,27.5145,81.5441,52.4209,16.9066,7.8373,56.2911,37.8267,129.6121,59.0775,51.9855,40.9023,30.59,33.1506,63.7293,33.5807,88.6858,59.559,17.5099,38.3411,-39.5562,90.8655,21.9729,24.6094,44.8306,14.6509,53.9644;]'; + % matlabbatch{con}.spm.stats.factorial_design.cov(2).cname = 'TR_Cs-Is'; + % matlabbatch{con}.spm.stats.factorial_design.cov(2).iCFI = 1; + % matlabbatch{con}.spm.stats.factorial_design.cov(2).iCC = 1; + + % matlabbatch{con}.spm.stats.factorial_design.cov(2).c = [-3.0502,4.0862,-11.864,-7.9314,16.0833,-7.4716,15.15,-6.5713,-12.2652,14.7077,1.1963,7.9776,-10.7498,-5.0541,4.3656,9.4451,-4.7046,10.3816,-11.0299,3.846,0.6971,2.8627,11.8134,-4.7998,13.5818,1.949,9.3118,-5.6294,0.8255,1.5937;]'; + % matlabbatch{con}.spm.stats.factorial_design.cov(2).cname = 'TR_Cf-Cs'; + % matlabbatch{con}.spm.stats.factorial_design.cov(2).iCFI = 1; + % matlabbatch{con}.spm.stats.factorial_design.cov(2).iCC = 1; + + % matlabbatch{con}.spm.stats.factorial_design.cov(2).c = [-4.1603,-2.6332,-1.9456,-7.6981,-3.9411,-0.5668,-0.0937,-2.9494,-4.6485,-2.3929,1.5449,1.975,-3.9803,-19.983,-4.1117,-1.9954,2.4152,-10.006,-5.674,-6.2914,-2.0766,-0.3342,-1.45,-0.9462,4.3075,-6.6019,6.8722,-3.0316,1.5413,10.9825;]'; + % matlabbatch{con}.spm.stats.factorial_design.cov(2).cname = 'TR_If-Is'; + % matlabbatch{con}.spm.stats.factorial_design.cov(2).iCFI = 1; + % matlabbatch{con}.spm.stats.factorial_design.cov(2).iCC = 1; + + if sub == 1 %for initialising the struct + matlabbatch{con}.spm.stats.factorial_design.masking.tm.tm_none = []; + matlabbatch{con}.spm.stats.factorial_design.masking.im = 1; + matlabbatch{con}.spm.stats.factorial_design.masking.em = {''}; + matlabbatch{con}.spm.stats.factorial_design.globalc.g_omit = []; + matlabbatch{con}.spm.stats.factorial_design.globalm.gmsca.gmsca_no = []; + matlabbatch{con}.spm.stats.factorial_design.globalm.glonorm = 1;% precise the contrasts between both groups + end + %% for estimation + con = con+1; + matlabbatch{con}.spm.stats.fmri_est.spmmat = {char(strcat(dirstr,filesep,Cnames{icon},filesep,'SPM.mat'))}; + matlabbatch{con}.spm.stats.fmri_est.method = 1; + %% for creating contrasts + con = con+1; + matlabbatch{con}.spm.stats.con.spmmat = {char(strcat(dirstr,filesep,Cnames{icon},filesep,'SPM.mat'))}; + matlabbatch{con}.spm.stats.con.consess{1}.tcon.name = Cnames{icon}; + matlabbatch{con}.spm.stats.con.consess{1}.tcon.convec = 1; + matlabbatch{con}.spm.stats.con.consess{1}.tcon.sessrep = 'none'; + % matlabbatch{con}.spm.stats.con.consess{2}.tcon.name = ['-' Cnames{icon}]; + % matlabbatch{con}.spm.stats.con.consess{2}.tcon.convec = -1; + % matlabbatch{con}.spm.stats.con.consess{2}.tcon.sessrep = 'none'; + matlabbatch{con}.spm.stats.con.consess{2}.tcon.name = matlabbatch{con-2}.spm.stats.factorial_design.cov(1).cname; + matlabbatch{con}.spm.stats.con.consess{2}.tcon.convec = [0 1]; + matlabbatch{con}.spm.stats.con.consess{2}.tcon.sessrep = 'none'; + matlabbatch{con}.spm.stats.con.consess{3}.tcon.name = ['-' matlabbatch{con-2}.spm.stats.factorial_design.cov(1).cname]; + matlabbatch{con}.spm.stats.con.consess{3}.tcon.convec = [0 -1]; + matlabbatch{con}.spm.stats.con.consess{3}.tcon.sessrep = 'none'; + % matlabbatch{con}.spm.stats.con.consess{4}.tcon.name = [matlabbatch{con-2}.spm.stats.factorial_design.cov(2).cname]; + % matlabbatch{con}.spm.stats.con.consess{4}.tcon.convec = [0 0 1]; + % matlabbatch{con}.spm.stats.con.consess{4}.tcon.sessrep = 'none'; + % matlabbatch{con}.spm.stats.con.consess{5}.tcon.name = ['-' matlabbatch{con-2}.spm.stats.factorial_design.cov(2).cname]; + % matlabbatch{con}.spm.stats.con.consess{5}.tcon.convec = [0 0 -1]; + % matlabbatch{con}.spm.stats.con.consess{5}.tcon.sessrep = 'none'; + % matlabbatch{con}.spm.stats.con.consess{6}.tcon.name = ['-' matlabbatch{con-2}.spm.stats.factorial_design.cov(2).cname]; + % matlabbatch{con}.spm.stats.con.consess{6}.tcon.convec = [0 0 -1]; + % matlabbatch{con}.spm.stats.con.consess{6}.tcon.sessrep = 'none'; + % matlabbatch{con}.spm.stats.con.consess{5}.tcon.name = matlabbatch{con}.spm.stats{con-2}.factorial_design.cov(2).cname; + % matlabbatch{con}.spm.stats.con.consess{5}.tcon.convec = [0 0 1]; + % matlabbatch{con}.spm.stats.con.consess{5}.tcon.sessrep = 'none'; + % matlabbatch{con}.spm.stats.con.consess{6}.tcon.name = ['-' matlabbatch{con}.spm.stats{con-2}.factorial_design.cov(2).cname]; + % matlabbatch{con}.spm.stats.con.consess{6}.tcon.convec = [0 0 -1]; + % matlabbatch{con}.spm.stats.con.consess{6}.tcon.sessrep = 'none'; + % + % matlabbatch{con}.spm.stats.con.consess{7}.tcon.name = matlabbatch{con}.spm.stats{con-2}.factorial_design.cov(3).cname; + % matlabbatch{con}.spm.stats.con.consess{7}.tcon.convec = [0 0 0 1]; + % matlabbatch{con}.spm.stats.con.consess{7}.tcon.sessrep = 'none'; + % matlabbatch{con}.spm.stats.con.consess{8}.tcon.name = ['-' matlabbatch{con}.spm.stats{con-2}.factorial_design.cov(3).cname]; + % matlabbatch{con}.spm.stats.con.consess{8}.tcon.convec = [0 0 0 -1]; + % matlabbatch{con}.spm.stats.con.consess{8}.tcon.sessrep = 'none'; + % + % matlabbatch{con}.spm.stats.con.consess{9}.tcon.name = matlabbatch{con}.spm.stats{con-2}.factorial_design.cov(4).cname; + % matlabbatch{con}.spm.stats.con.consess{9}.tcon.convec = [0 0 0 0 1]; + % matlabbatch{con}.spm.stats.con.consess{9}.tcon.sessrep = 'none'; + % matlabbatch{con}.spm.stats.con.consess{10}.tcon.name = ['-' matlabbatch{con}.spm.stats{con-2}.factorial_design.cov(4).cname]; + % matlabbatch{con}.spm.stats.con.consess{10}.tcon.convec = [0 0 0 0 -1]; + % matlabbatch{con}.spm.stats.con.consess{10}.tcon.sessrep = 'none'; + % + % matlabbatch{con}.spm.stats.con.consess{11}.tcon.name = 'impulsivity'; + % matlabbatch{con}.spm.stats.con.consess{11}.tcon.convec = [0 1 1 1 1]; + % matlabbatch{con}.spm.stats.con.consess{11}.tcon.sessrep = 'none'; + % matlabbatch{con}.spm.stats.con.consess{12}.tcon.name = '-impulsivity'; + % matlabbatch{con}.spm.stats.con.consess{12}.tcon.convec = [0 -1 -1 -1 -1]; + % matlabbatch{con}.spm.stats.con.consess{12}.tcon.sessrep = 'none'; + end % end icon loop +end % end isub loop +save test_latency matlabbatch +% run the job for the design specification +spm_jobman('run',matlabbatch) + +return \ No newline at end of file diff --git a/labnic/Labnic_rfx_twos_spm5.m b/labnic/Labnic_rfx_twos_spm5.m new file mode 100644 index 0000000..74a346b --- /dev/null +++ b/labnic/Labnic_rfx_twos_spm5.m @@ -0,0 +1,205 @@ +function [] = Labnic_rfx_twos_spm5 + +% create the second level analyis (random effect analysis RFX) +% create a two sample t-test between 2 populations of subjects + +% you must speficied the 3 first line of the script +% dirname is the direcotry for the main RFX analysis +% it create a dir for each contrast in this main dir +% this code take into account that some of the subjects do not have all the +% contrasts +% Analysis of each subjects must be in: +% **...\data_fMRI\populations\subjects\analyse\ana1\** +% At the beginning, the script create a mean structural image to display the +% results of the group analysis + +% this function has been adapted by Virginie Sterpenich (Virginie.Sterpenich@unige.ch) + +global name_ana; + +%% parameters to specify for each study +root_path = 'D:\vs1'; % directory where are the populations, main directory +name_ana = 'ana1'; %or 'ana2'; etc +dirname = ['RFX_ana_2s']; % name for the directory of the random analysis + +%% defining the pop_style +cd (root_path) +Population = spm_select(Inf,'dir','which population do you want to process?'); +npop = size(Population,1); + +%% Definition of subjects to process +for pop=1:npop + cd(Population(pop,1:end)) + Subject{pop}= spm_select(Inf,'dir','which subjects do you want to process?'); +end +Subject = char(Subject); +nsuj = size(Subject,1); + +%% Creation of directory for the rfx +dirstr = strcat(root_path,filesep,dirname); +if ~exist(strcat(root_path,filesep,dirname)) + mkdir(strcat(root_path,filesep,dirname)) +end + +%% create mean structural image to display results of the group +disp('Creating mean structural image'); +PM = cell(nsuj,1); +ii=0; +for sub = 1:nsuj + %go to the subject's analyse folder + sufolder= deblank(Subject(sub,:)); + path_ana =[sufolder 'scans' filesep 'struct']; + eval(['cd ' path_ana]); + % select the struct img + struct_img = spm_select('List',path_ana,'^ws*'); + PM{sub} = [path_ana filesep struct_img]; + ii=ii+1; +end +% create the expression for the mean +expression = '(i'; +for jj = [1:ii] + expression = [expression num2str(jj) '+i']; +end +expression = [expression(1:end-2) ')/' num2str(ii)]; +% fil the job and calculate +jobs{1}.util{1}.imcalc.input = PM; +jobs{1}.util{1}.imcalc.output = strcat([dirstr filesep 'meanstruct.nii']); +jobs{1}.util{1}.imcalc.expression = expression; + +jobs{1}.util{1}.imcalc.options.dmtx = 0; +jobs{1}.util{1}.imcalc.options.mask = 0; +jobs{1}.util{1}.imcalc.options.interp = 0; +jobs{1}.util{1}.imcalc.options.dtype = 4; +% run the job +spm_jobman('run',jobs) +clear jobs + +%% starting analysing + +cd(dirstr) +% load the names of the contrasts from the first subject +% it use the contrasts_name matrix created in the Labnic_contrast script +path_suj1 =[Subject(1,:) 'analyse' filesep name_ana]; +load(strcat(path_suj1,filesep,'contrasts_name')); +% Cnames = Cnames(:,[2:7]);% if you don't want analyse all the contrasts (ex: only 2 to 7) +nb_con = size(Cnames,2); + +% loop over contrasts +for icon = 1:nb_con + defaults.modality='FMRI'; + disp(['Processing contrast ' num2str(icon) ' : ' char(Cnames(icon))]); + disp('-----------------------------'); + %initialise variables + P = {}; scans1 = {}; scans2 = {}; + % go to the main dir + cd(dirstr) + % Create the dir for the contrast of interest + mkdir(char(Cnames(icon))); + + % loop over subjects + for sub = 1:nsuj + %go to the subject's analyse folder + path_ana =[deblank(Subject(sub,:)) 'analyse' filesep name_ana]; + eval(['cd ' path_ana]); + %select SPM.mat + load SPM + % check the name of the contrast + % Find for this subject the con.img corresponding to the contrast of interest (icon) + % This works even if the name of the con.img differs from one subject to the other !watch out for multiple matches! + contrastindice = []; + for icontrast = 1:size(SPM.xCon,2) + contrastmatch = findstr(SPM.xCon(icontrast).name,Cnames{icon}); + if ~isempty(contrastmatch) + contrastindice = icontrast; + break + end + end + %%alternative to loop above, adding some error messages + %contrastmatch = find(~cellfun('isempty',regexp(cellstr(char(SPM.xCon.name)),Cnames{icon}))); + % if isempty(contrastmatch) ...no contrast was found + % error('No matching contrast found - check names'); + % elseif length(contrastmatch)>1 ... more than 1 contrasts found + % error('Multiple matches found - check names'); + % else + % contrastindice = icontrast; ...found exactly 1 match + % end + + % Take the adequate 'con' img + if ~isempty(contrastindice)% au cas ou le contraste n'existe pas chez un sujet + if contrastindice < 10 + str = ['^con_000+' num2str(contrastindice) filesep '.img$']; + elseif contrastindice < 100 + str = ['^con_00+' num2str(contrastindice) filesep '.img$']; + else + str = ['^con_0+' num2str(contrastindice) filesep '.img$']; + end + files = spm_select('List',pwd,str); + P{end+1,1} = strcat(path_ana,filesep,files,',1'); + % put the image con in the appropriate group + if any(strcmp(P{end}(1:size(deblank(Population(1,:)),2)),deblank(Population(1,:)))); + scans1{end+1,1} = P{end,:}; + elseif any(strcmp(P{end}(1:size(deblank(Population(2,:)),2)),deblank(Population(2,:)))); + scans2{end+1,1} = P{end,:}; + end + end + clear SPM + end % end isub loop + %affiche les img con sélectionnées + scans1 + scans2 + % fil the job + jobs{1}.stats{1}.factorial_design.des.t2.scans1 = scans1; + jobs{1}.stats{1}.factorial_design.des.t2.scans2 = scans2; + jobs{1}.stats{1}.factorial_design.dir = {char(strcat(dirstr,filesep,Cnames{icon}))}; + % normal parameters + jobs{1}.stats{1}.factorial_design.des.t2.dept = 0; + jobs{1}.stats{1}.factorial_design.des.t2.variance = 1; + jobs{1}.stats{1}.factorial_design.des.t2.gmsca = 0; + jobs{1}.stats{1}.factorial_design.des.t2.ancova = 0; + jobs{1}.stats{1}.factorial_design.masking.tm.tm_none = []; + jobs{1}.stats{1}.factorial_design.masking.im = 1; + jobs{1}.stats{1}.factorial_design.masking.em = {''}; + jobs{1}.stats{1}.factorial_design.globalc.g_omit = []; + jobs{1}.stats{1}.factorial_design.globalm.gmsca.gmsca_no = []; + jobs{1}.stats{1}.factorial_design.globalm.glonorm = 1; + + % run the job for the design specification + spm_jobman('run',jobs) + clear jobs + + %% Estimate parameters + cd(strcat(dirstr,filesep,Cnames{icon})) + load SPM + SPM = spm_spm(SPM); + + %% Create contrasts + % select the SPM.mat + jobs{1}.stats{1}.con.spmmat = {char(strcat(dirstr,filesep,Cnames{icon},filesep,'SPM.mat'))}; + + % precise the contrasts between both groups + jobs{1}.stats{1}.con.consess{1}.tcon.name = 'Gr1 vs Gr2'; + jobs{1}.stats{1}.con.consess{1}.tcon.convec = [1 -1]; + jobs{1}.stats{1}.con.consess{1}.tcon.sessrep = 'none'; + + jobs{1}.stats{1}.con.consess{2}.tcon.name = 'Gr2 vs Gr1'; + jobs{1}.stats{1}.con.consess{2}.tcon.convec = [-1 1]; + jobs{1}.stats{1}.con.consess{2}.tcon.sessrep = 'none'; + + jobs{1}.stats{1}.con.consess{3}.tcon.name = 'Gr1'; + jobs{1}.stats{1}.con.consess{3}.tcon.convec = [1 0]; + jobs{1}.stats{1}.con.consess{3}.tcon.sessrep = 'none'; + + jobs{1}.stats{1}.con.consess{4}.tcon.name = 'Gr2'; + jobs{1}.stats{1}.con.consess{4}.tcon.convec = [0 1]; + jobs{1}.stats{1}.con.consess{4}.tcon.sessrep = 'none'; + + jobs{1}.stats{1}.con.consess{5}.tcon.name = 'Gr1_and_Gr2'; + jobs{1}.stats{1}.con.consess{5}.tcon.convec = [1 1]; + jobs{1}.stats{1}.con.consess{5}.tcon.sessrep = 'none'; + % run the job + spm_jobman('run',jobs) + clear jobs + cd(dirstr) + +end % end icond loop over contrasts +return \ No newline at end of file diff --git a/labnic/extract_clubetas.m b/labnic/extract_clubetas.m new file mode 100644 index 0000000..9025142 --- /dev/null +++ b/labnic/extract_clubetas.m @@ -0,0 +1,292 @@ +function [Data] = extract_clubetas(SPM,xSPM) +sub2pr=[1:SPM.nscan]; +clear Data +[xyzmm,i] = spm_XYZreg('NearestXYZ',spm_results_ui('GetCoords'),xSPM.XYZmm); +spm_results_ui('SetCoords',xSPM.XYZmm(:,i)); +A = spm_clusters(xSPM.XYZ); +j = find(A == A(i)); +xyz = xSPM.XYZmm(:,j)'; + +pathlist = SPM.xY.P; + +allbetas= []; +Allbetas = []; +allxyz = []; + +%--- to write +XYZ = SPM.xVol.XYZ; +XYZmm = SPM.xVol.M(1:3,:)*[XYZ; ones(1,size(XYZ,2))]; +bob = spm_input('nature of extraction','+1','b','VOI|cluster',[0,1]); + +if ~bob + [xyz xyzmm] = spm_VOI_yann(SPM,xSPM); + xyz = xyzmm'; +end + +cond_list = []; +for sub = 1:length(sub2pr) + mydir = fileparts(pathlist{sub}); + sSPM = load([mydir filesep 'SPM.mat']); + nses = length(sSPM.SPM.Sess);%num of session + convec = zeros(1,length(sSPM.SPM.Vbeta)); + for ses = 1:nses + for j = 1:length(sSPM.SPM.Sess(ses).Fc) + index = sSPM.SPM.Sess(ses).col(sSPM.SPM.Sess(ses).Fc(1,j).i); + convec(ses,index) = 1; + end + end + real = sSPM.SPM.xX.name(any(convec,1)); + for j = 1:length(real) + b = strfind(real{j},'*bf'); + list_cond{j} = real{j}(7:b-1); + end + [b,c,d] = unique(list_cond,'first'); + [m n] = sort(c); + cond_list = [cond_list b(n)]; +end +[b,c,d] = unique(cond_list,'first'); +[m n] = sort(c); +thelist = b(n); + +Allbetas = NaN(length(sub2pr),length(thelist)); + + +messages = {'hot baby... hot...' 'what are you thinking about?...' 'wait and see...' 'wait for reaching heaven...' 'Please wait... (no luck! the basic)'}; +colors = {[1,0.4,0.6] [1,0.6,0.4] [0.4,1,0.6] [0.4,0.6,1] [1,0.4,1]}; +[m n] = sort(rand(1,5)); +h = waitbar(0,messages{n(1)},'Color',colors{n(1)}); + +for sub = 1:length(sub2pr) + mydir = fileparts(pathlist{sub}); + sSPM = load([mydir filesep 'SPM.mat']); + nses = length(sSPM.SPM.Sess);%num of session + for i = 1:length(sSPM.SPM.Vbeta) + sSPM.SPM.Vbeta(i).fname = [mydir filesep sSPM.SPM.Vbeta(i).fname]; + end + tmpallbetas= []; + tmpallxyz = []; + Tmpallbetas= []; + list_cond = []; + for cluvox = 1:size(xyz,1) + [nxyz,i] = spm_XYZreg('NearestXYZ',xyz(cluvox,:),XYZmm); + govox=1; + if sqrt(sum((nxyz'-xyz(cluvox,:)).^2))>=1.7321 %one voxel in each dimZ + govox= spm_input({'No data stored for this voxel','Closest voxels with data are:',... + num2str(xyz(cluvox,:)),... + 'Continue anyway?'},... + 1,'bd',{'OK','NO'},[1,0]); + end + vXYZ = XYZ(:,i) ; % coordinates in voxels + + %-Get parameter and hyperparameter estimates + beta= spm_get_data(sSPM.SPM.Vbeta, vXYZ); + + convec = zeros(1,length(sSPM.SPM.Vbeta)); + for ses = 1:nses + for j = 1:length(sSPM.SPM.Sess(ses).Fc) + index = sSPM.SPM.Sess(ses).col(sSPM.SPM.Sess(ses).Fc(1,j).i); + convec(ses,index) = 1; + end + + end + real = sSPM.SPM.xX.name(any(convec,1)); + for j = 1:length(real) + b = strfind(real{j},'*bf'); + list_cond{j} = real{j}(7:b-1); + end + tmpallbetas = [tmpallbetas; beta(any(convec,1))']; + end + + for c = 1:length(thelist) + C = strcmp(thelist{c},list_cond); + Tmpallbetas = [Tmpallbetas mean(sum(tmpallbetas(:,C)))]; + end + + Allbetas(sub,:) = Tmpallbetas; + %allxyz= [allxyz;mean(tmpallxyz,1)]; + waitbar(sub/length(sub2pr),h) +end +close(h) + +ctitle = [num2str(nxyz') ' nvox = ' num2str(size(xyz,1))]; +tmpaxe = thelist(1:length(thelist)); + +npop = size(SPM.xX.X,2); +for pop = 1:npop + Data{pop}(:,:) = reshape(Allbetas(logical(repmat(SPM.xX.X(:,pop),1,size(Allbetas,2)))),sum(SPM.xX.X(:,pop)),size(Allbetas,2)); +end +[Maj,ST,SE] = yann_bar(Data); +switch spm_input('errorbar?',2,'b','yes|no'); + case 'yes' + figure + h = bar(Maj'); + for i = 1:size(Maj,1) + if i == 1 & npop > 1 + N = -0.08; + elseif i == 1 + N = 0; + else + N = 0.08; + end + for j = 1:size(Maj,2) + coef = j+npop*N; + line([coef coef],([SE(i,j) 0 - SE(i,j)] + Maj(i,j)),... + 'LineWidth',1.5,'Color','k') + line ([coef-0.05 coef+0.05],([SE(i,j)+Maj(i,j) SE(i,j)+Maj(i,j)]),... + 'LineWidth',1.5,'Color','k') + line ([coef-0.05 coef+0.05],([-SE(i,j)+Maj(i,j) -SE(i,j)+Maj(i,j)]),... + 'LineWidth',1.5,'Color','k') + end + end + case 'no' + figure + h = bar(Maj'); + set(h,'FaceColor',[1 1 1]*.8) +end +set(gca, 'XTickLabel',tmpaxe, 'XTick' , [1:length(tmpaxe)],'FontSize',6); +title(ctitle,'FontSize',16) + +%% Functions +function [Maj,ST,SE] = yann_bar(Data) +% function [Maj,ST,SE] = yann_bar(Data) +% calculate Mean, stdev and std error for Data +% developed by Yann Cojan (yann.cojan@unige.ch) + +npop = length(Data) + +switch spm_input('Reset lowest value to:?',1,'b','zero|mean|unchanged'); + case 'zero' + for pop = 1:npop + if isempty(find(mean(Data{pop})<0)) + Maj(pop,:) = mean(Data{pop},1)- min(mean(Data{pop},1)); + else + Maj(pop,:) = mean(Data{pop},1) + abs(min(mean(Data{pop},1))); + end + end + case 'mean' + for pop = 1:npop + Maj(pop,:) = mean(Data{pop},1) - mean(mean(Data{pop},1)); + end + case 'unchanged' + for pop = 1:npop + if any(any(isnan(Data{pop}))) + count = 1; + for hu = 1:size(Data{pop},2) + Maj(pop,hu) = mean(Data{pop}(~isnan(Data{pop}(:,hu)),hu)); + end + else + Maj(pop,:) = mean(Data{pop},1); + end + end +end +for pop = 1:npop + if size(Data{pop},1)>1 + if any(any(isnan(Data{pop}))) + for hu = 1:size(Data{pop},2) + SE(pop,hu) = std(Data{pop}(~isnan(Data{pop}(:,hu)),hu))/sqrt(size(Data{pop}(~isnan(Data{pop}(:,hu))),1)); + ST(pop,hu) = std(Data{pop}(~isnan(Data{pop}(:,hu)),hu)); + end + else + SE(pop,:) = std(Data{pop})/sqrt(size(Data{pop},1)); + ST(pop,:) = std(Data{pop}); + end + else + if any(any(isnan(Data{pop}))) + for hu = 1:size(Data{pop},2) + SE(pop,hu) = std(Data{pop}(~isnan(Data{pop}(:,hu)),hu))/sqrt(size(Data{pop}(~isnan(Data{pop}(:,hu))),1)); + ST(pop,hu) = std(Data{pop}(~isnan(Data{pop}(:,hu)),hu)); + end + else + SE(pop,:) = zeros(1,size(Data{pop},2)); + ST(pop,:) = zeros(1,size(Data{pop},2)); + end + end +end + +function [XYZ XYZmm] = spm_VOI_yann(SPM,xSPM,hReg) +% see spm_VOI for help + +%-Parse arguments +%----------------------------------------------------------------------- +if nargin < 2, error('insufficient arguments'), end +if nargin < 3, hReg = []; end + +Num = 16; % maxima per cluster +Dis = 04; % distance among maxima (mm) + +%-Title +%----------------------------------------------------------------------- +spm('FigName',['SPM{',xSPM.STAT,'}: Small Volume Correction']); + +%-Get current location {mm} +%----------------------------------------------------------------------- +xyzmm = spm_results_ui('GetCoords'); + +%-Specify search volume +%----------------------------------------------------------------------- +str = sprintf(' at [%.0f,%.0f,%.0f]',xyzmm(1),xyzmm(2),xyzmm(3)); +SPACE = spm_input('Search volume...',-1,'m',... + {['Sphere',str],['Box',str],'Image'},['S','B','I']); + +% voxels in entire search volume {mm} +%----------------------------------------------------------------------- +XYZmm = SPM.xVol.M(1:3,:)*[SPM.xVol.XYZ; ones(1, SPM.xVol.S)]; +Q = ones(1,size(xSPM.XYZmm,2)); +O = ones(1,size( XYZmm,2)); +FWHM = xSPM.FWHM; + + +switch SPACE + + case 'S' %-Sphere + %--------------------------------------------------------------- + D = spm_input('radius of VOI {mm}',-2); + str = sprintf('%0.1fmm sphere',D); + j = find(sum((xSPM.XYZmm - xyzmm*Q).^2) <= D^2); + k = find(sum(( XYZmm - xyzmm*O).^2) <= D^2); + D = D./xSPM.VOX; + + + case 'B' %-Box + %--------------------------------------------------------------- + D = spm_input('box dimensions [k l m] {mm}',-2); + str = sprintf('%0.1f x %0.1f x %0.1f mm box',D(1),D(2),D(3)); + j = find(all(abs(xSPM.XYZmm - xyzmm*Q) <= D(:)*Q/2)); + k = find(all(abs( XYZmm - xyzmm*O) <= D(:)*O/2)); + D = D./xSPM.VOX; + + + case 'I' %-Mask Image + %--------------------------------------------------------------- + Msk = spm_select(1,'image','Image defining search volume'); + D = spm_vol(Msk); + str = sprintf('image mask: %s',spm_str_manip(Msk,'a30')); + VOX = sqrt(sum(D.mat(1:3,1:3).^2)); + FWHM = FWHM.*(xSPM.VOX./VOX); + XYZ = D.mat \ [xSPM.XYZmm; ones(1, size(xSPM.XYZmm, 2))]; + j = find(spm_sample_vol(D, XYZ(1,:), XYZ(2,:), XYZ(3,:),0) > 0); + XYZ = D.mat \ [ XYZmm; ones(1, size( XYZmm, 2))]; + k = find(spm_sample_vol(D, XYZ(1,:), XYZ(2,:), XYZ(3,:),0) > 0); + +end + +xSPM.S = length(k); +xSPM.R = spm_resels(FWHM,D,SPACE); +xSPM.Z = xSPM.Z(j); +xSPM.XYZ = xSPM.XYZ(:,j); +xSPM.XYZmm = xSPM.XYZmm(:,j); +xSPM.Ps = xSPM.Ps(k); +XYZ = xSPM.XYZ; +XYZmm = xSPM.XYZmm; +%-Tabulate p values +%----------------------------------------------------------------------- +str = sprintf('search volume: %s',str); +if any(strcmp(SPACE,{'S','B'})) + str = sprintf('%s at [%.0f,%.0f,%.0f]',str,xyzmm(1),xyzmm(2),xyzmm(3)); +end + +TabDat = spm_list('List',xSPM,hReg,Num,Dis,str); + +%-Reset title +%----------------------------------------------------------------------- +spm('FigName',['SPM{',xSPM.STAT,'}: Results']); \ No newline at end of file diff --git a/labnic/nic_spm_explorer.m b/labnic/nic_spm_explorer.m new file mode 100644 index 0000000..eab8e05 --- /dev/null +++ b/labnic/nic_spm_explorer.m @@ -0,0 +1,80 @@ +function [varargout] = nic_spm_explorer(input,varargin) +%NIC_SPM_EXPLORER - SPM results explorer +% [] = nic_spm_explorer() will open a new window asking for a SPM file +% +% +% [] = nic_spm_explorer(SPM) +% [] = nic_spm_explorer('/some/path/SPM.mat') +% [] = nic_spm_explorer('/some/path/SPM.mat','option1', value1, ...) +% [] = nic_spm_explorer('/some/path/SPM.mat','option1', value1, ...) +% +% Example +% >> nic_spm_explorer +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2008 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2008-10-16 Creation +% +% ----------------------------- Script History --------------------------------- + +% TO DO +% Everything! based on xjview.m plus: +% - no conflict with the spm_figure (Graphics) window +% - yoke windows "à la" MRICro(n) +% - remove uicontrol for cleaner export via "copy figure" +% - + +if nargin<1 + action='start'; +elseif nargin==1 || isnumeric(action) + varargin=[{action} varargin ]; + action='start'; +end + +% equivalent to +varargout={eval(sprintf('action_%s(varargin{:});',action))}; + +%% ACTION CALLS =========================================================== + +function [hf]=action_start(varargin) +OPTIONS = load_options(varargin) + + +%% PRIVATE FUNCTIONS ====================================================== +% +function OPTIONS=load_options(varargin) +% ------------------------------------------------------------------------- +% Check OPTIONS and default values when optional arguments are not +% specified +Def_OPTIONS = struct(... + 'option1',[],... + ); +if nargin < 2 + OPTIONS = Def_OPTIONS; +else + if length(varargin)>1 + OPTIONS = cell2struct(varargin(2:2:end),varargin(1:2:end),2); + % struct(varargin{:}) bugs with something like {'string1' 'string2'} in the inputs! + else + OPTIONS = varargin{1}; + end + % Check field names of passed OPTIONS and fill missing ones with default values + DefFieldNames = fieldnames(Def_OPTIONS); + for k = 1:length(DefFieldNames) + if ~isfield(OPTIONS,DefFieldNames{k}) + OPTIONS = setfield(OPTIONS,DefFieldNames{k},getfield(Def_OPTIONS,DefFieldNames{k})); + end + end + clear DefFieldNames +end +clear Def_OPTIONS +return; % load_options +% ------------------------------------------------------------------------- \ No newline at end of file diff --git a/labnic/nifti2img.m b/labnic/nifti2img.m new file mode 100644 index 0000000..c5b4987 --- /dev/null +++ b/labnic/nifti2img.m @@ -0,0 +1,860 @@ +function varargout = nifti2ana(P) +% NIFTI2ANA converts images from nifti to analyze format readable in SPM2 +% V = NIFTI2ANA(P) converts files that are in SPM5's nifti format to the +% analyze format that was readable by SPM2. This allows one to use files +% processed in SPM5 to be analyzed in SPM2. +% +% P are the filenames to be converted. If P is empty the user is +% prompted to select the files. +% +% V is a structure array of mapped volumes. This can be used to read the +% new files or discarded. +% +% SPM5 must be in the Matlab path. +% +% The converted files are prepended with 'n2a_' +% +% The program REQUIRES SPM5 image files (either .nii or .img) as input, +% and outputs SPM2 compatible analyze-type files. This program is not +% for converting SPM5 .nii to SPM5 .img files. To do that simply read +% in the data, change the filename to have a .img extension, and write +% out the new file. For example: +% v = spm_vol('my_file_name.nii'); +% img = spm_read_vols(v); +% vnew = v; +% vnew.fname = [vnew.fname(1:end-3), 'img']; +% vnew = spm_create_vol(vnew); +% vnew = spm_write_vol(vnew,img); +% +% Author: Darren Gitelman +% $Id: nifti2ana.m,v 1.3 2008-07-22 13:06:43-05 drg Exp drg $ +% updated 2010-07-14 by drg. + +% Get image filenames if none are provided +if nargin < 1 + P = spm_select(Inf,'image','Select images to convert'); +end +if isempty(P) + disp('No files selected. Aborting conversion.') + return; +end + +% Map the volumes using SPM5 file handling tools +fprintf('Reading SPM5 NIFTI volume headers..'); +Vin = spm_vol(deblank(P)); +fprintf('Done\n'); +fprintf('There are %i files to be converted.\n',numel(Vin)); + +Vout = struct('fname' , '',... + 'dim' , [],... + 'mat' , [],... + 'pinfo' , [],... + 'descrip', '',... + 'n' , [],... + 'private', []); +V = Vout; +% Convert the volume structures to be analyze compatible +fprintf('Creating SPM2-compatible headers..') + +for i = 1:numel(Vin) + fprintf('%5i',i); + [tmppth tmpfn tmpext] = fileparts(Vin(i).fname); + dt = Vin(i).dt(1); + tmpfn = ['n2a_',tmpfn]; + Vtmp.fname = fullfile(tmppth,[tmpfn,tmpext]); + Vtmp.dim = [Vin(i).dim dt]; + Vtmp.mat = Vin(i).mat; + if spm_flip_analyze_images + fprintf(' : Flipping image L/R\n') + Vtmp.mat = spm_matrix([0 0 0 0 0 0 -1 1 1 0 0 0])*Vtmp.mat; + fprintf('Creating SPM2-compatible headers..%5i',i) + end + Vtmp.pinfo = Vin(i).pinfo; + Vtmp.descrip = ['nifti2analyze converted: ',Vin(i).descrip]; + + dtype = Vin(i).private.dat.dtype; + + % must pass the dtype to figure out if endianness is swapped. + Vout(i)= my_spm2_create_vol(Vtmp,'noopen',dtype); + fprintf(sprintf(repmat('\b',1,5))) +end +fprintf('Done\n') + +fprintf('Writing SPM2-compatible image files..') +for i = 1:numel(Vout) + fprintf('%5i',i); + y = spm_read_vols(Vin(i)); + V(i) = my_spm2_write_vol(Vout(i),y,dtype); + fprintf(sprintf(repmat('\b',1,5))) +end +fprintf('All Done!\n') +if nargout == 1 + varargout{1} = V; +end + +%_______________________________________________________________________ +%_______________________________________________________________________ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% THESE FUNCTIONS COME FROM THE SPM2 VERSION OF SPM_CREATE_VOL +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +function V = my_spm2_create_vol(V,varargin) +% Create an image file. +% FORMAT Vo = spm_create_vol(Vi,['noopen']) +% Vi - data structure containing image information. +% - see spm_vol for a description. +% 'noopen' - optional flag to say "don't open/create the image file". +% Vo - data structure after modification for writing. +% varargin can now have 2 arguments, 'noopen' and the dtype. +%_______________________________________________________________________ +% @(#)spm_create_vol.m 2.14 John Ashburner 03/07/31 +for i=1:numel(V) + if nargin>1, + v = my_spm2_internal_create_vol(V(i),varargin{:}); + else + v = my_spm2_internal_create_vol(V(i)); + end; + f = fieldnames(v); + for j=1:size(f,1), + %eval(['V(i).' f{j} ' = v.' f{j} ';']); + V = setfield(V,{i},f{j},getfield(v,f{j})); + end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function V = my_spm2_internal_create_vol(V,varargin) +if ~isfield(V,'n') || isempty(V.n) + V.n = 1; +end; + +if ~isfield(V,'descrip') || isempty(V.descrip) + V.descrip = 'SPM2 compatible'; +end; +V.private = struct('hdr',[]); + +% Orientation etc... +M = V.mat; +if spm_flip_analyze_images, M = diag([-1 1 1 1])*M; end; +vx = sqrt(sum(M(1:3,1:3).^2)); +if det(M(1:3,1:3))<0, vx(1) = -vx(1); end; +origin = M\[0 0 0 1]'; +origin = round(origin(1:3)); + +[pth nam] = fileparts(V.fname); +fname = fullfile(pth,[nam, '.hdr']); +try + [hdr swapped] = my_spm2_spm_read_hdr(fname); +catch + warning(['Could not read "' fname '"']); + swapped = 0; + hdr = []; +end; + +if ~isempty(hdr) && (hdr.dime.dim(5)>1 || V.n>1), + % cannot simply overwrite the header + + hdr.dime.dim(5) = max(V.n,hdr.dime.dim(5)); + if any(V.dim(1:3) ~= hdr.dime.dim(2:4)) + error('Incompatible image dimensions'); + end; + + if sum((vx-hdr.dime.pixdim(2:4)).^2)>1e-6, + error('Incompatible voxel sizes'); + end; + + V.dim(4) = spm_type(spm_type(hdr.dime.datatype)); + mach = 'native'; + if swapped, + V.dim(4) = V.dim(4)*256; + if spm_platform('bigend'), + mach = 'ieee-le'; + else + mach = 'ieee-be'; + end; + end; + + if isfinite(hdr.dime.funused1) && hdr.dime.funused1 + scal = hdr.dime.funused1; + if isfinite(hdr.dime.funused2), + dcoff = hdr.dime.funused2; + else + dcoff = 0; + end; + else + if hdr.dime.glmax-hdr.dime.glmin && hdr.dime.cal_max-hdr.dime.cal_min + scal = (hdr.dime.cal_max-hdr.dime.cal_min)/(hdr.dime.glmax-hdr.dime.glmin); + dcoff = hdr.dime.cal_min - scal*hdr.dime.glmin; + else + scal = 1; + dcoff = 0; + warning(['Assuming a scalefactor of 1 for "' V.fname '".']); + end; + end; + V.pinfo(1:2) = [scal dcoff]'; + V.private.hdr = hdr; +else + + V.private.hdr = my_spm2_create_defaults; + + my_endianess = spm_platform('bigend'); + if nargin == 3 + dtype = varargin{2}; + elseif nargin ==2 + dtype = varargin{1}; + end + + if my_endianess && ~isempty(findstr(dtype,'BE')) || ... + ~my_endianess && isempty(findstr(dtype,'BE')) + swapped = 0; + else + swapped = 1; + end + dt = spm_type(spm_type(V.dim(4))); + if any(dt == [128+2 128+4 128+8]), + % Convert to a form that Analyze will support + dt = dt - 128; + end; + V.dim(4) = dt; + mach = 'native'; + if swapped + V.dim(4) = V.dim(4)*256; + if spm_platform('bigend'), + mach = 'ieee-le'; + else + mach = 'ieee-be'; + end; + end; + V.private.hdr.dime.datatype = dt; + V.private.hdr.dime.bitpix = spm_type(dt,'bits'); + + if spm_type(dt,'intt'), + + V.private.hdr.dime.glmax = spm_type(dt,'maxval'); + V.private.hdr.dime.glmin = spm_type(dt,'minval'); + + if 0, % Allow DC offset + V.private.hdr.dime.cal_max = max(V.private.hdr.dime.glmax*V.pinfo(1,:) + V.pinfo(2,:)); + V.private.hdr.dime.cal_min = min(V.private.hdr.dime.glmin*V.pinfo(1,:) + V.pinfo(2,:)); + V.private.hdr.dime.funused1 = 0; + scal = (V.private.hdr.dime.cal_max - V.private.hdr.dime.cal_min)/... + (V.private.hdr.dime.glmax - V.private.hdr.dime.glmin); + dcoff = V.private.hdr.dime.cal_min - V.private.hdr.dime.glmin*scal; + V.pinfo = [scal dcoff 0]'; + else % Don't allow DC offset + cal_max = max(V.private.hdr.dime.glmax*V.pinfo(1,:) + V.pinfo(2,:)); + cal_min = min(V.private.hdr.dime.glmin*V.pinfo(1,:) + V.pinfo(2,:)); + V.private.hdr.dime.funused1 = cal_max/V.private.hdr.dime.glmax; + if V.private.hdr.dime.glmin, + V.private.hdr.dime.funused1 = max(V.private.hdr.dime.funused1,... + cal_min/V.private.hdr.dime.glmin); + end; + V.private.hdr.dime.cal_max = V.private.hdr.dime.glmax*V.private.hdr.dime.funused1; + V.private.hdr.dime.cal_min = V.private.hdr.dime.glmin*V.private.hdr.dime.funused1; + V.pinfo = [V.private.hdr.dime.funused1 0 0]'; + end; + else + V.private.hdr.dime.glmax = 1; + V.private.hdr.dime.glmin = 0; + V.private.hdr.dime.cal_max = 1; + V.private.hdr.dime.cal_min = 0; + V.private.hdr.dime.funused1 = 1; + end; + + V.private.hdr.dime.pixdim(2:4) = vx; + V.private.hdr.dime.dim(2:4) = V.dim(1:3); + V.private.hdr.dime.dim(5) = V.n; + V.private.hdr.hist.origin(1:3) = origin; + + d = 1:min([length(V.descrip) 79]); + V.private.hdr.hist.descrip = char(zeros(1,80)); + V.private.hdr.hist.descrip(d) = V.descrip(d); + V.private.hdr.hk.db_name = char(zeros(1,18)); + [pth, nam] = fileparts(V.fname); + d = 1:min([length(nam) 17]); + V.private.hdr.hk.db_name(d) = nam(d); +end; + +V.pinfo(3) = prod(V.private.hdr.dime.dim(2:4))*V.private.hdr.dime.bitpix/8*(V.n-1); + +fid = fopen(fname,'w',mach); +if (fid == -1), + error(['Error opening ' fname '. Check that you have write permission.']); +end; + +my_spm2_write_hk(fid,V.private.hdr.hk); +my_spm2_write_dime(fid,V.private.hdr.dime); +my_spm2_write_hist(fid,V.private.hdr.hist); +fclose(fid); + +fname = fullfile(pth,[nam, '.mat']); +off = -vx'.*origin; +mt = [vx(1) 0 0 off(1) ; 0 vx(2) 0 off(2) ; 0 0 vx(3) off(3) ; 0 0 0 1]; +if spm_flip_analyze_images, mt = diag([-1 1 1 1])*mt; end; + +if sum((V.mat(:) - mt(:)).*(V.mat(:) - mt(:))) > eps*eps*12 || exist(fname,'file')==2 + if exist(fname,'file')==2, + clear mat + str = load(fname); + if isfield(str,'mat'), + mat = str.mat; + elseif isfield(str,'M'), + mat = str.M; + if spm_flip_analyze_images, + for i=1:size(mat,3), + mat(:,:,i) = diag([-1 1 1 1])*mat(:,:,i); + end; + end; + end; + mat(:,:,V.n) = V.mat; + mat = my_spm2_fill_empty(mat,mt); + M = mat(:,:,1); + if spm_flip_analyze_images + M = diag([-1 1 1 1])*M; + end; + try + save(fname,'mat','M','-append'); + catch % Mat-file was probably Matlab 4 + save(fname,'mat','M'); + end; + else + clear mat + mat(:,:,V.n) = V.mat; + mat = my_spm2_fill_empty(mat,mt); + M = mat(:,:,1); + if spm_flip_analyze_images + M = diag([-1 1 1 1])*M; + end; + save(fname,'mat','M'); + end; +end; + +if nargin==1 || ~strcmp(varargin{1},'noopen') + fname = fullfile(pth,[nam, '.img']); + V.private.fid = fopen(fname,'r+',mach); + if (V.private.fid == -1), + V.private.fid = fopen(fname,'w',mach); + if (V.private.fid == -1), + error(['Error opening ' fname '. Check that you have write permission.']); + end; + end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function Mo = my_spm2_fill_empty(Mo,Mfill) +todo = []; +for i=1:size(Mo,3) + if ~any(any(Mo(:,:,i))), + todo = [todo i]; + end; +end; +if ~isempty(todo) + for i=1:length(todo), + Mo(:,:,todo(i)) = Mfill; + end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function my_spm2_write_hk(fid,hk) +% write (struct) header_key +%----------------------------------------------------------------------- +fseek(fid,0,'bof'); +fwrite(fid,hk.sizeof_hdr, 'int32'); +fwrite(fid,hk.data_type, 'char' ); +fwrite(fid,hk.db_name, 'char' ); +fwrite(fid,hk.extents, 'int32'); +fwrite(fid,hk.session_error,'int16'); +fwrite(fid,hk.regular, 'char' ); +if fwrite(fid,hk.hkey_un0, 'char' )~= 1, + error(['Error writing ' fopen(fid) '. Check your disk space.']); +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function my_spm2_write_dime(fid,dime) +% write (struct) image_dimension +%----------------------------------------------------------------------- +fseek(fid,40,'bof'); +fwrite(fid,dime.dim, 'int16'); +fwrite(fid,dime.vox_units, 'uchar' ); +fwrite(fid,dime.cal_units, 'uchar' ); +fwrite(fid,dime.unused1, 'int16' ); +fwrite(fid,dime.datatype, 'int16'); +fwrite(fid,dime.bitpix, 'int16'); +fwrite(fid,dime.dim_un0, 'int16'); +fwrite(fid,dime.pixdim, 'float'); +fwrite(fid,dime.vox_offset, 'float'); +fwrite(fid,dime.funused1, 'float'); +fwrite(fid,dime.funused2, 'float'); +fwrite(fid,dime.funused2, 'float'); +fwrite(fid,dime.cal_max, 'float'); +fwrite(fid,dime.cal_min, 'float'); +fwrite(fid,dime.compressed, 'int32'); +fwrite(fid,dime.verified, 'int32'); +fwrite(fid,dime.glmax, 'int32'); +if fwrite(fid,dime.glmin, 'int32')~=1, + error(['Error writing ' fopen(fid) '. Check your disk space.']); +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function my_spm2_write_hist(fid,hist) +% write (struct) data_history +%----------------------------------------------------------------------- +fseek(fid,148,'bof'); +fwrite(fid,hist.descrip, 'uchar'); +fwrite(fid,hist.aux_file, 'uchar'); +fwrite(fid,hist.orient, 'uchar'); +fwrite(fid,hist.origin, 'int16'); +fwrite(fid,hist.generated, 'uchar'); +fwrite(fid,hist.scannum, 'uchar'); +fwrite(fid,hist.patient_id, 'uchar'); +fwrite(fid,hist.exp_date, 'uchar'); +fwrite(fid,hist.exp_time, 'uchar'); +fwrite(fid,hist.hist_un0, 'uchar'); +fwrite(fid,hist.views, 'int32'); +fwrite(fid,hist.vols_added, 'int32'); +fwrite(fid,hist.start_field,'int32'); +fwrite(fid,hist.field_skip, 'int32'); +fwrite(fid,hist.omax, 'int32'); +fwrite(fid,hist.omin, 'int32'); +fwrite(fid,hist.smax, 'int32'); +if fwrite(fid,hist.smin, 'int32')~=1, + error(['Error writing ' fopen(fid) '. Check your disk space.']); +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function hdr = my_spm2_create_defaults +hk.sizeof_hdr = 348; +hk.data_type = ['dsr ' 0]; +hk.db_name = char(zeros(1,18)); +hk.extents = 0; +hk.session_error= 0; +hk.regular = 'r'; +hk.hkey_un0 = 0; + +dime.dim = [4 0 0 0 1 0 0 0]; +dime.vox_units = ['mm ' 0]; +dime.cal_units = char(zeros(1,8)); +dime.unused1 = 0; +dime.datatype = -1; +dime.bitpix = 0; +dime.dim_un0 = 0; +dime.pixdim = [0 1 1 1 1 0 0 0]; +dime.vox_offset = 0; +dime.funused1 = 1; +dime.funused2 = 0; +dime.funused3 = 0; +dime.cal_max = 1; +dime.cal_min = 0; +dime.compressed = 0; +dime.verified = 0; +dime.glmax = 1; +dime.glmin = 0; + +hist.descrip = char(zeros(1,80)); +hist.descrip(1:length('SPM2 compatible')) = 'SPM2 compatible'; +hist.aux_file = char(zeros(1,24)); +hist.orient = char(0); +hist.origin = [0 0 0 0 0]; +hist.generated = char(zeros(1,10)); +hist.scannum = char(zeros(1,10)); +hist.patient_id = char(zeros(1,10)); +hist.exp_date = char(zeros(1,10)); +hist.exp_time = char(zeros(1,10)); +hist.hist_un0 = char(zeros(1,3)); +hist.generated(1:5) = 'today'; +hist.views = 0; +hist.vols_added = 0; +hist.start_field= 0; +hist.field_skip = 0; +hist.omax = 0; +hist.omin = 0; +hist.smax = 0; +hist.smin = 0; + +hdr.hk = hk; +hdr.dime = dime; +hdr.hist = hist; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% THESE FUNCTIONS COME FROM THE SPM2 VERSION OF SPM_READ_HDR +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +function [hdr,otherendian] = my_spm2_spm_read_hdr(fname) +% Read (SPM customised) Analyze header +% FORMAT [hdr,otherendian] = spm_read_hdr(fname) +% fname - .hdr filename +% hdr - structure containing Analyze header +% otherendian - byte swapping necessary flag +%_______________________________________________________________________ +% @(#)spm_read_hdr.m 2.2 John Ashburner 03/07/17 + +fid = fopen(fname,'r','native'); +otherendian = 0; +if (fid > 0) + dime = my_spm2_read_dime(fid); + if dime.dim(1)<0 || dime.dim(1)>15 % Appears to be other-endian + % Re-open other-endian + fclose(fid); + if spm_platform('bigend'), fid = fopen(fname,'r','ieee-le'); + else fid = fopen(fname,'r','ieee-be'); end; + otherendian = 1; + dime = my_spm2_read_dime(fid); + end; + hk = my_spm2_read_hk(fid); + hist = my_spm2_read_hist(fid); + hdr.hk = hk; + hdr.dime = dime; + hdr.hist = hist; + fclose(fid); +else + hdr = []; + otherendian = NaN; + %error(['Problem opening header file (' fopen(fid) ').']); +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function hk = my_spm2_read_hk(fid) +% read (struct) header_key +%----------------------------------------------------------------------- +fseek(fid,0,'bof'); +hk.sizeof_hdr = fread(fid,1,'int32'); +hk.data_type = my_spm2_mysetstr(fread(fid,10,'uchar'))'; +hk.db_name = my_spm2_mysetstr(fread(fid,18,'uchar'))'; +hk.extents = fread(fid,1,'int32'); +hk.session_error = fread(fid,1,'int16'); +hk.regular = my_spm2_mysetstr(fread(fid,1,'uchar'))'; +hk.hkey_un0 = my_spm2_mysetstr(fread(fid,1,'uchar'))'; +if isempty(hk.hkey_un0) + error(['Problem reading "hk" of header file (' fopen(fid) ').']); +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function dime = my_spm2_read_dime(fid) +% read (struct) image_dimension +%----------------------------------------------------------------------- +fseek(fid,40,'bof'); +dime.dim = fread(fid,8,'int16')'; +dime.vox_units = my_spm2_mysetstr(fread(fid,4,'uchar'))'; +dime.cal_units = my_spm2_mysetstr(fread(fid,8,'uchar'))'; +dime.unused1 = fread(fid,1,'int16'); +dime.datatype = fread(fid,1,'int16'); +dime.bitpix = fread(fid,1,'int16'); +dime.dim_un0 = fread(fid,1,'int16'); +dime.pixdim = fread(fid,8,'float')'; +dime.vox_offset = fread(fid,1,'float'); +dime.funused1 = fread(fid,1,'float'); +dime.funused2 = fread(fid,1,'float'); +dime.funused3 = fread(fid,1,'float'); +dime.cal_max = fread(fid,1,'float'); +dime.cal_min = fread(fid,1,'float'); +dime.compressed = fread(fid,1,'int32'); +dime.verified = fread(fid,1,'int32'); +dime.glmax = fread(fid,1,'int32'); +dime.glmin = fread(fid,1,'int32'); +if isempty(dime.glmin) + error(['Problem reading "dime" of header file (' fopen(fid) ').']); +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function hist = my_spm2_read_hist(fid) +% read (struct) data_history +%----------------------------------------------------------------------- +fseek(fid,148,'bof'); +hist.descrip = my_spm2_mysetstr(fread(fid,80,'uchar'))'; +hist.aux_file = my_spm2_mysetstr(fread(fid,24,'uchar'))'; +hist.orient = fread(fid,1,'uchar'); +hist.origin = fread(fid,5,'int16')'; +hist.generated = my_spm2_mysetstr(fread(fid,10,'uchar'))'; +hist.scannum = my_spm2_mysetstr(fread(fid,10,'uchar'))'; +hist.patient_id = my_spm2_mysetstr(fread(fid,10,'uchar'))'; +hist.exp_date = my_spm2_mysetstr(fread(fid,10,'uchar'))'; +hist.exp_time = my_spm2_mysetstr(fread(fid,10,'uchar'))'; +hist.hist_un0 = my_spm2_mysetstr(fread(fid,3,'uchar'))'; +hist.views = fread(fid,1,'int32'); +hist.vols_added = fread(fid,1,'int32'); +hist.start_field= fread(fid,1,'int32'); +hist.field_skip = fread(fid,1,'int32'); +hist.omax = fread(fid,1,'int32'); +hist.omin = fread(fid,1,'int32'); +hist.smax = fread(fid,1,'int32'); +hist.smin = fread(fid,1,'int32'); +if isempty(hist.smin) + error(['Problem reading "hist" of header file (' fopen(fid) ').']); +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function out = my_spm2_mysetstr(in) +tmp = find(in == 0); +tmp = min([min(tmp) length(in)]); +out = char([in(1:tmp)' zeros(1,length(in)-(tmp))])'; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% THESE FUNCTIONS COME FROM THE SPM2 VERSION OF SPM_WRITE_VOL +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +function V = my_spm2_write_vol(V,Y,dtype) +% Write an image volume to disk, setting scales and offsets as appropriate +% FORMAT V = my_spm2_write_vol(V,Y) +% V (input) - a structure containing image volume information (see spm_vol) +% Y - a one, two or three dimensional matrix containing the image voxels +% V (output) - data structure after modification for writing. +%_______________________________________________________________________ +% @(#)spm_write_vol.m 2.9 John Ashburner 03/02/26 + +if ndims(Y)>3, error('Can only handle a maximum of 3 dimensions.'), end + +if ~isfield(V,'pinfo'), V.pinfo = [1,0,0]'; end + +dim = [size(Y) 1 1 1]; +if ~all(dim(1:3) == V.dim(1:3)) || (size(V.pinfo,2)~=1 && size(V.pinfo,2)~=dim(3)), + error('Incompatible dimensions.'); +end + + +% Set scalefactors and offsets +%----------------------------------------------------------------------- +dt = V.dim(4); if dt>256, dt = dt/256; end; +if any(dt == [128+2 128+4 128+8]), + % Convert to a form that Analyze will support + dt = dt - 128; +end; +s = find(dt == [2 4 8 128+2 128+4 128+8]); +dmnmx = [0 -2^15 -2^31 -2^7 0 0 ; 2^8-1 2^15-1 2^31-1 2^7-1 2^16 2^32]; +dmnmx = dmnmx(:,s); +V.pinfo(1,:) = 1; +V.pinfo(2,:) = 0; +mxs = zeros(dim(3),1)+NaN; +mns = zeros(dim(3),1)+NaN; +if ~isempty(s), + for p=1:dim(3), + tmp = double(Y(:,:,p)); + tmp = tmp(isfinite(tmp)); + if ~isempty(tmp), + mxs(p) = max(tmp); + mns(p) = min(tmp); + end; + end; + + if size(V.pinfo,2) ~= 1 + for p=1:dim(3), + mx = mxs(p); + mn = mns(p); + if ~isfinite(mx), mx = 0; end; + if ~isfinite(mn), mn = 0; end; + if mx~=mn, + V.pinfo(1,p) = (mx-mn)/(dmnmx(2)-dmnmx(1)); + V.pinfo(2,p) = ... + (dmnmx(2)*mn-dmnmx(1)*mx)/(dmnmx(2)-dmnmx(1)); + else + V.pinfo(1,p) = 0; + V.pinfo(2,p) = mx; + end; + end; + else + mx = max(mxs(isfinite(mxs))); + mn = min(mns(isfinite(mns))); + if isempty(mx), mx = 0; end; + if isempty(mn), mn = 0; end; + if mx~=mn + V.pinfo(1,1) = (mx-mn)/(dmnmx(2)-dmnmx(1)); + V.pinfo(2,1) = (dmnmx(2)*mn-dmnmx(1)*mx)/(dmnmx(2)-dmnmx(1)); + else + V.pinfo(1,1) = 0; + V.pinfo(2,1) = mx; + end; + end; +end; + +%-Create and write image +%----------------------------------------------------------------------- +V = my_spm2_create_vol(V,dtype); + +for p=1:V.dim(3), + V = my_spm2_write_plane(V,Y(:,:,p),p); +end; +V = my_spm2_close_vol(V); +%_______________________________________________________________________ +%_______________________________________________________________________ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% THESE FUNCTIONS COME FROM THE SPM2 VERSION OF SPM_WRITE_PLANE +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +function V = my_spm2_write_plane(V,A,p) +% Write a transverse plane of image data. +% FORMAT V = my_spm2_write_plane(V,A,p) +% V - data structure containing image information. +% - see spm_vol for a description. +% A - the two dimensional image to write. +% p - the plane number (beginning from 1). +% +% VO - (possibly) modified data structure containing image information. +% It is possible that future versions of spm_write_plane may +% modify scalefactors (for example). +% +%_______________________________________________________________________ +% @(#)spm_write_plane.m 2.19 John Ashburner 03/07/16 + +if any(V.dim(1:2) ~= size(A)), error('Incompatible image dimensions'); end; +if p>V.dim(3), error('Plane number too high'); end; + +% Write Analyze image by default +V = my_spm2_write_analyze_plane(V,A,p); +return; +%_______________________________________________________________________ + +%_______________________________________________________________________ +function V = my_spm2_write_analyze_plane(V,A,p) + +types = [ 2 4 8 16 64 130 132 136, 512 1024 2048 4096 16384 33280 33792 34816]; +maxval = [2^8-1 2^15-1 2^31-1 Inf Inf 2^7-1 2^16-1 2^32-1, 2^8-1 2^15-1 2^31-1 Inf Inf 2^8-1 2^16-1 2^32-1]; +minval = [ 0 -2^15 -2^31 -Inf -Inf -2^7 0 0, 0 -2^15 -2^31 -Inf -Inf -2^7 0 0]; +intt = [ 1 1 1 0 0 1 1 1, 1 1 1 0 0 1 1 1]; +prec = str2mat('uint8','int16','int32','float','double','int8','uint16','uint32','uint8','int16','int32','float','double','int8','uint16','uint32'); +swapped = [ 0 0 0 0 0 0 0 0, 1 1 1 1 1 1 1 1]; +bits = [ 8 16 32 32 64 8 16 32, 8 16 32 32 64 8 16 32]; + +dt = find(types==V.dim(4)); +if isempty(dt), error('Unknown datatype'); end; + +A = double(A); + +% Rescale to fit appropriate range +if intt(dt), + A(isnan(A)) = 0; + mxv = maxval(dt); + mnv = minval(dt); + A = round(A*(1/V.pinfo(1)) - V.pinfo(2)); + A(A > mxv) = mxv; + A(A < mnv) = mnv; +end; + +[pth,nam] = fileparts(V.fname); +fname = fullfile(pth,[nam, '.img']); +if ~isfield(V,'private') || ~isfield(V.private,'fid') || isempty(V.private.fid) + mach = 'native'; + if swapped(dt) + if spm_platform('bigend') + mach = 'ieee-le'; + else + mach = 'ieee-be'; + end; + end; + fid = fopen(fname,'r+',mach); + if fid == -1 + fid = fopen(fname,'w',mach); + if fid == -1 + error(['Error opening ' fname '. Check that you have write permission.']); + end; + end; +else + if isempty(fopen(V.private.fid)), + mach = 'native'; + if swapped(dt) + if spm_platform('bigend') + mach = 'ieee-le'; + else + mach = 'ieee-be'; + end; + end; + V.private.fid = fopen(fname,'r+',mach); + if V.private.fid == -1 + error(['Error opening ' fname '. Check that you have write permission.']); + end; + end; + fid = V.private.fid; +end; + +% Seek to the appropriate offset +datasize = bits(dt)/8; +off = (p-1)*datasize*prod(V.dim(1:2)) + V.pinfo(3,1); +fseek(fid,0,'bof'); % A bug in Matlab 6.5 means that a rewind is needed +if fseek(fid,off,'bof')==-1, + % Need this because fseek in Matlab does not seek past the EOF + fseek(fid,0,'bof'); % A bug in Matlab 6.5 means that a rewind is needed + fseek(fid,0,'eof'); + curr_off = ftell(fid); + blanks = zeros(off-curr_off,1); + if fwrite(fid,blanks,'uchar') ~= numel(blanks) + my_spm2_write_error_message(V.fname); + error(['Error writing ' V.fname '.']); + end; + fseek(fid,0,'bof'); % A bug in Matlab 6.5 means that a rewind is needed + if fseek(fid,off,'bof') == -1, + my_spm2_write_error_message(V.fname); + error(['Error writing ' V.fname '.']); + end; +end; + +if fwrite(fid,A,deblank(prec(dt,:))) ~= numel(A) + my_spm2_write_error_message(V.fname); + error(['Error writing ' V.fname '.']); +end; + +if ~isfield(V,'private') || ~isfield(V.private,'fid') || isempty(V.private.fid), fclose(fid); end; + +return; +%_______________________________________________________________________ + +%_______________________________________________________________________ +function my_spm2_write_error_message(q) +str = {... + 'Error writing:',... + ' ',... + [' ',spm_str_manip(q,'k40d')],... + ' ',... + 'Check disk space / disk quota.'}; +spm('alert*',str,mfilename,sqrt(-1)); + +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% THIS FUNCTION COMES FROM THE SPM2 VERSION OF SPM_CLOSE_VOL +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +function V = my_spm2_close_vol(V) +% Close image volume +% See: spm_create_vol and spm_write_plane. +%_______________________________________________________________________ +% @(#)spm_close_vol.m 2.4 John Ashburner 02/08/16 +for i=1:numel(V), + if isfield(V,'private') && isfield(V(i).private,'fid') && ~isempty(V(i).private.fid), + if ~isempty(fopen(V(i).private.fid)), + fclose(V(i).private.fid); + end; + V(i).private = rmfield(V(i).private,'fid'); + end; +end; \ No newline at end of file diff --git a/labnic/ps2pdf.m b/labnic/ps2pdf.m new file mode 100644 index 0000000..e64a8ca --- /dev/null +++ b/labnic/ps2pdf.m @@ -0,0 +1,385 @@ +function ps2pdf(varargin) +%PS2PDF Function to convert a PostScript file to PDF using Ghostscript +% +% Converts a postscript file into PDF. The resulting PDF file will contain +% one page for each page defined in the postscript files, so a multi-page +% postscript file, like those generated by using the '-append' option of +% MATLAB's print command, can be used to generate a multi-page PDF file. +% +% Ghostscript is a third-party application currently supplied with +% MATLAB. The caller may also specify a different version of Ghostscript +% to use. +% +% PS2PDF expects to be called with a set of parameter-value pairs. The +% order of these is unimportant, but required parameters MUST be +% specified +% +% In the list below, required parameters are marked with an asterisk * +% Parameter: Value: +% * psfile full or relative path to the postscript file to convert +% +% * pdffile full or relative path to the pdf file to create +% +% gscommand path to Ghostscript executable to use; this will try +% to default to the version of Ghostscript shipped with +% MATLAB, if any. If this value is specified you should +% also specify the gsfontpath and gslibpath values. +% +% gsfontpath full path to the Ghostscript font files +% If a gscommand is specified then this path should +% also be specified and reference the same Ghostscript +% version +% +% If gscommand is NOT specified and we can determine +% the version of Ghostscript, if any, shipped with +% MATLAB, then this value will be overridden to use the +% path that references MATLAB's version of Ghostscript +% +% gslibpath full path to the Ghostscript library (.ps) files. +% If a gscommand is specified then this path should +% also be specified and reference the same Ghostscript +% version +% +% If gscommand is NOT specified and we can determine +% the version of Ghostscript, if any, shipped with +% MATLAB, then this value will be overridden to use the +% path that references MATLAB's version of Ghostscript +% +% gspapersize paper size to use in the created .pdf file. If not +% specified or the specified value is not recognized +% it will use whatever default paper size is +% built into the version of Ghostscript being run +% +% NOTE: no scaling of the input occurs - it's simply +% placed on a page with the specified paper size. +% +% Valid values for gspapersize are: +% 'letter', 'ledger', 'legal', '11x17', +% 'archA', 'archB', 'archC', 'archD', 'archE', +% 'a0', 'a1', 'a2', 'a3','a4', 'a5', +% 'a6', 'a7', 'a8', 'a9', 'a10' +% +% deletepsfile 0 to keep the input ps file after creating pdf +% non-zero to delete the input ps file after creating pdf +% Default is 0: keep the input ps file (do NOT delete it) +% NOTE: if the pdf creation process fails, the input +% PS file will be kept regardless of this setting +% +% verbose 0 to suppress display of status/progress info; +% non-zero to allow display of status/progress info +% Default is 0 (no display) +% +% Example usage: +% use MATLAB's version of Ghostscript to generate an A4 pdf file +% ps2pdf('psfile', 'input.ps', 'pdffile', 'output.pdf', 'gspapersize', 'a4') +% +% use a local copy of Ghostcript to generate a file, and display some +% status/progress info while doing so. +% ps2pdf('psfile', '../reports/input.ps', 'pdffile', 'c:\temp\output3.pdf', ... +% 'gspapersize', 'a4', 'verbose', 1, ...) +% 'gscommand', 'C:\Program Files\GhostScript\bin\gswin32c.exe', ... +% 'gsfontpath', 'C:\Program Files\GhostScript\fonts', ... +% 'gslibpath', 'C:\Program Files\GhostScript\lib') +% +% use MATLAB's version of Ghostscript to generate a pdf file and delete +% the input.ps file when done +% ps2pdf('psfile', 'input.ps', 'pdffile', 'output.pdf', 'gspapersize', 'a4', 'deletepsfile', 1) + +% Update log: +% Aug 15, 2008: fixed bug where embedded space in location of +% MATLAB's version of Ghostscript caused ps2pdf to fail. +% Apr 16, 2008: added deletepsfile option + +% Copyright 2008 The MathWorks, Inc. + + if nargin < 1 + error('ps2pdf:parameters', 'No parameters specified. Type ''help ps2pdf'' for details on how to use this function.'); + end + + % parse input args + gsData = LocalParseArgs(varargin{:}); + + % setup the file that tells GS what we want it to do + gsData = LocalCreateResponseFile(gsData); + + gsDebug = 0; + if gsData.verbose + fprintf('ps2pdf: input settings are:\n'); + if isfield(gsData, 'paperSizes') + gsData = rmfield(gsData, 'paperSizes'); + end + gsData %#ok + fprintf('ps2pdf: response file for Ghostscript is:\n'); + type(gsData.responseFile); + gsDebug = 1; + end + + %to hold results/status from system call + s = 0; %#ok + r = ''; %#ok + + % run Ghostscript to convert the file + if gsData.useBuiltin + [s, r] = gsData.cmd(['@' gsData.responseFile], gsData.psFile, gsDebug); + else + [s, r] = system([gsData.cmd ' @' gsData.responseFile ' ' gsData.psFile]); + end + + if gsData.verbose + disp( ['Ghostscript STDOUT: ' num2str(s) ] ); + disp( ['Ghostscript STDERR: ' r ] ); + else + delete(gsData.responseFile) + end + + if s && ~isempty(r) + error('ps2pdf:ghostscript', ['Problem converting PostScript. System returned error: ' num2str(s) '.' r]) + elseif s + error('ps2pdf:ghostscript', ['Problem calling GhostScript. System returned error: ' num2str(s)]) + end + + %if after all this we still couldn't create the file, report the error + fid = fopen( gsData.pdfFile, 'r'); + if ( fid == -1 ) + error('ps2pdf:ghostscript', '%s', [ 'Ghostscript could not create ''' gsData.pfdFile '''.' ]) + else + fclose( fid ); + end + + % if we get here, we successfully created pdf file; delete ps file if + % requested to do so + if gsData.deletePSFile + delete(gsData.psFile); + end + +end + +%local function to parse arguments and fill in the gsData structure +% .psFile - postscript file to convert +% .pdfFile - pdf file to create +% .cmd - path/name of Ghostscript command to run or handle to gscript +% builtin +% .useBuiltin - 1 if using builtin gs command +% .fontPath - path to the Ghostscript fonts, if any +% .libPath - path to the Ghostscript libs (Ghostscript .ps files), if any) +% .paperSize - paper size to set for resulting .pdf file +% .deletePSFile - 0 to keep (not delete) the input ps file if pdf created ok +% .verbose - if non-zero, display some status/progress info to command window +function gsData = LocalParseArgs(varargin) + gsData.paperSizes = {'letter', 'ledger', 'legal', '11x17', 'archA', 'archB', ... + 'archC', 'archD', 'archE', 'a0', 'a1', 'a2', 'a3','a4', 'a5', ... + 'a6', 'a7', 'a8', 'a9', 'a10'}; + + %default values for some settings + gsData.verbose = 0; + gsData.useBuiltin = 0; + gsData.deletePSFile = 0; + + for i = 1 : 2 : length(varargin)-1 + param_arg = varargin{i}; + param_value = varargin{i+1}; + switch(lower(param_arg)) + % path to ps file to conver + case 'psfile' + if ~exist(param_value, 'file') + error('print:ghostscript', ... + 'Can not find postscript file <%s> to convert', ... + param_value) + end + gsData.psFile = param_value; + + % path to pdf file to create + case 'pdffile' + %verify we can create file at that location + pdf_fid = fopen(param_value,'w'); + if pdf_fid < 0 + error('ps2pdf:invalidPDFFIle', ... + 'Can not open <%s> for writing', ... + param_value); + end + fclose(pdf_fid); + %delete temp file we created + delete(param_value); + gsData.pdfFile = param_value; + + % full path to gs executable + case 'gscommand' + if ~exist(param_value, 'file') + error('ps2pdf:ghostscriptCommand', ... + 'Can not find Ghostscript executable (''gscommand'') <%s>',... + param_value) + end + if ispc && ~isempty(findstr(param_value, ' ')) + param_value = ['"' param_value '"']; %#ok + end + gsData.cmd = param_value; + + % full path to gs font dir + case 'gsfontpath' + if ~exist(param_value, 'dir') + error('ps2pdf:ghostscriptFontPath', ... + 'Can not find the directory <%s> for Ghostscript fonts (''gsfontpath'')', ... + param_value) + end + gsData.fontPath = param_value; + + % full path to gs lib dir + case 'gslibpath' + if ~exist(param_value, 'dir') + error('ps2pdf:ghostscriptLibPath', ... + 'Can not find the directory <%s> for Ghostscript library files (''gslibpath'')', ... + param_value) + end + gsData.libPath = param_value; + + % paper size + case 'gspapersize' + idx = strcmpi(param_value, gsData.paperSizes); + if ~any(idx) + warning('ps2pdf:papersize', ... + '''gspapersize'' value <%s> not found in the list of known sizes, ignoring it.', param_value); + else + gsData.paperSize = gsData.paperSizes{idx}; + end + + % deletePSFile + case 'deletepsfile' + if isnumeric(param_value) + gsData.deletePSFile = param_value; + else + warning('ps2pdf:deletepsfile', ... + '''deletepsfile'' value <%s> class <%s> should be numeric, defaulting to 0', ... + param_value, class(param_value)); + end + + % verbose + case 'verbose' + if isnumeric(param_value) + gsData.verbose = param_value; + else + warning('ps2pdf:verbose', ... + '''verbose'' value <%s> class <%s> should be numeric, defaulting to 0', ... + param_value, class(param_value)); + end + + otherwise + if isnumeric(param_value) + param_value = num2str(param_value); + end + warning('ps2pdf:unknown', ... + 'ignoring unknown parameter <%s> with value <%s>.', param_arg, param_value); + end + end + + if ~isfield(gsData, 'psFile') + error('ps2pdf:noInputFile', ... + 'No input (psfile) file specified'); + end + + if ~isfield(gsData, 'pdfFile') + error('ps2pdf:noOutputFile', ... + 'No output (pdffile) file specified'); + end + + if ~isfield(gsData, 'cmd') + + v = ver('MATLAB'); + if str2double(v.Version) < 7.4 + [gsCmd, ghostDir] = Local_GetOldGhostscript(); + gsData.cmd = gsCmd; + else + % version 7.4 or later + % the path to Ghostscript changed in 7a + ghostDir = fullfile( matlabroot, 'sys', 'gs8x' ); + if ~exist(ghostDir, 'dir') + error('ps2pdf:ghostscriptCommand', ... + 'Can not find Ghostscript installed with MATLAB in <%s>',... + ghostDir); + end + gsData.cmd = Local_GetGscriptFcnHandle; + if ~isempty(gsData.cmd) + gsData.useBuiltin = 1; % use builtin Ghostscript + end + end + + if ~isempty(gsData.cmd) + % if using MATLAB's version of GhostScript, use same set of fonts and library files + if isfield(gsData, 'fontPath') || isfield(gsData, 'libPath') + warning('ps2pdf:ghostscriptPathOverride', ... + 'Using MATLAB''s version of Ghostscript; overriding ''gsfontpath'' and ''gslibpath'' to use builtin MATLAB version'); + end + gsData.fontPath = fullfile( ghostDir, 'fonts', ''); + gsData.libPath = fullfile( ghostDir, 'ps_files', ''); + else + error('ps2pdf:noGhostscriptCommand', ... + 'Can not find Ghostscript program in MATLAB'); + end + else + % if gscommandpath was specified, + if ~isfield(gsData, 'fontPath') || ~isfield(gsData, 'libPath') + warning('ps2pdf:ghostscriptCommandSuggestion', ... + ['When specifying a Ghostscript executable (''gscommand'') you should also '... + 'specify both the ''gsfontpath'' and ''gslibpath'' locations']); + + end + end +end + +%local function to create the input file needed for Ghostscript +function gsData = LocalCreateResponseFile(gsData) + % open a response file to write out Ghostscript commands + rsp_file = [tempname '.rsp']; + rsp_fid = fopen (rsp_file, 'w'); + + if (rsp_fid < 0) + error('ps2pdf:responseFileCreate', 'Unable to create response file') + end + + fprintf(rsp_fid, '-dBATCH -dNOPAUSE\n'); + if ~gsData.verbose + fprintf(rsp_fid, '-q\n'); + end + if isfield(gsData, 'libPath') + fprintf(rsp_fid, '-I"%s"\n', gsData.libPath); + end + if isfield(gsData, 'fontPath') + fprintf(rsp_fid, '-I"%s"\n', gsData.fontPath); + end + if isfield(gsData, 'paperSize') + fprintf( rsp_fid, '-sPAPERSIZE=%s\n', gsData.paperSize ); + end + fprintf(rsp_fid, '-sOutputFile="%s"\n', gsData.pdfFile); + fprintf(rsp_fid, '-sDEVICE=%s\n', 'pdfwrite'); + fclose(rsp_fid); + gsData.responseFile = rsp_file; +end + +%local function to get a handle to MATLAB's Ghostscript implementation +%NOTE: this may change or be removed in future releases +function gs = Local_GetGscriptFcnHandle() + gs = ''; + p = which('-all', 'gscript'); + if ~isempty(p) + p = p{1}; + fpath = fileparts(p); + olddir = cd(fpath); + gs = @gscript; + cd(olddir); + end +end + +% local function to try and get location of Ghostscript in older MATLAB +function [gsCmd, ghostDir] = Local_GetOldGhostscript + ghostDir = fullfile( matlabroot, 'sys', 'ghostscript' ); + gsCmd = ''; + if ispc + if exist(fullfile(ghostDir,'bin','win32','gs.exe'), 'file') + gsCmd = fullfile(ghostDir,'bin','win32','gs.exe'); + end + else + if exist(fullfile(ghostDir,'bin',lower(computer),'gs'), 'file') + gsCmd = fullfile(ghostDir,'bin',lower(computer), 'gs'); + end + end + gsCmd = ['"' gsCmd '"']; +end \ No newline at end of file diff --git a/labnic/read_eprimetxt.m b/labnic/read_eprimetxt.m new file mode 100644 index 0000000..b7204ae --- /dev/null +++ b/labnic/read_eprimetxt.m @@ -0,0 +1,252 @@ +function [hnames,A,B,tvals,rowlevel] = read_eprimetxt(filename, varnames) +% read_eprimetxt - reads E-prime text files as edat +% V = read_eprimetxt('FILENAME') returns in V, the names of variables found in +% the E-Prime text file FILENAME (in alphabetical order) +% +% [V, A, B] = read_eprimetxt('FILENAME') also returns numeric data, stored in A, and text +% data, stored in B. +% +% See also: xlsread + +txt=textread(filename, '%s', 1, 'delimiter', '\n'); +if isequal(double(textread(filename, '%c', 3))', [65535 65534 42]) + % Null separated characters... + fid=fopen(filename,'rt'); + txt=char(fread(fid, getfield(dir(filename), 'bytes'), 'char'))'; + fclose(fid); + txt(txt==255)=[]; + txt(txt==254)=[]; + txt(txt==0)=[]; + txt=strread(txt, '%s', 'delimiter', '\n'); +elseif isequal(txt, {'*** Header Start ***'}) + txt=textread(filename, '%s', 'delimiter', '\n'); +else + hnames{1} = 'impossible'; + A = []; + B = []; + tvals = []; + rowlevel = []; + return + error('Wrong type of file, it should be an E-prime generated text file, starting with a header'); +end +% Header.nvars=strmatch('*** Header End ***', txt)-strmatch('*** Header Start ***', txt)-1; + + +%% Vars +v=txt; +v(strmatch('*** ', txt))=[]; +if datenum(version('-date')) > 733044 %732892 % 732687 % 732341 + [vnames, vvals]=strread(v,'%s%s',1,'delimiter', ': '); + % elseif str2num(version('-release'))>=14 + % %it still bugs with Matlab 7.0.4.365 (R14) Service Pack 2 !! + % for i=1:size(v,1) + % [vnames(i), vvals(i)]=strread(v{i},'%s%s',1,'delimiter', ': '); + % end +else + vnames=cell(length(v),1); + vvals=vnames; + for i=1:length(v) + [vnames{i},b]= strtok(v{i}, ': '); + vvals{i}=b(3:end); + end +end + +%% LevelNames +LevelNames=vvals(strmatch('LevelName', vnames)); +vvals(strmatch('LevelName', vnames))=[]; +vnames(strmatch('LevelName', vnames))=[]; + +% Identical variable names across levels should be specified using [] +ilevels=strmatch('Level', vnames, 'exact'); +levels=str2num(strvcat(vvals(ilevels))); +vlevels=zeros(length(vnames),1); +for i=1:length(ilevels) + vlevels(ilevels(i):end)=levels(i); +end +% vvals(strmatch('Level', vnames))=[]; +% vlevels(strmatch('Level', vnames))=[]; +% vnames(strmatch('Level', vnames))=[]; + + +% List of column headers +[hnames,i,j]=unique(vnames); +%j( strmatch('Level', hnames, 'exact'))=NaN; + + +for i=1:length(hnames) + if ~isequal(hnames{i},'Level') + l=unique(vlevels(j==i)); + l=nonzeros(l); + if length(l)>1 + %hnames(i) + for k=2:length(l) + hnames{end+1}=[ hnames{i} '[' LevelNames{l(k)} ']']; + vnames(intersect(find(vlevels==l(k)),strmatch(hnames{i},vnames,'exact')))=hnames(end); + end + hnames{i} = [ hnames{i} '[' LevelNames{l(1)} ']']; + vnames(intersect(find(vlevels==l(1)),strmatch(hnames{i},vnames,'exact')))=hnames(i); + end + end +end +hnames(strmatch('Level', hnames, 'exact'))=[]; +hnames=[hnames ; LevelNames(2:max(levels)) ]; +[i,i]=sort(lower(hnames)); +hnames=hnames(i)'; +if nargout <= 1 + %returns headers only + % k=strmatch('Level',hnames, 'exact'); + % hnames(k)=[]; + % k=strmatch('LevelName',hnames, 'exact'); + % hnames(k)=[]; + k=strmatch('VersionPersist',hnames, 'exact'); + hnames(k)=[]; + return +end + + +%% Converts text to numerical values +% To tease apart numerical/text values, we create a new array containing +% mixed data types +% try +% % With version > 7.0 +% xvals=cellfun(str2func('str2num'), vvals, 'UniformOutput', 0); +% +% %Where non numerical data were... +% tvals=cellfun('isempty', xvals); +% % We also need to check for a strange behavior of str2num +% tvals=tvals | cellfun('length',xvals) > 1; +% +% % we put them back in the mixed type array xvals +% xvals(tvals)=vvals(tvals); +% catch +% xvals=cell(length(vvals),1); +% tvals=logical(zeros(length(vvals),1)); +% for i=1:length(vvals) +% xvals{i}=str2num(vvals{i}); +% if isempty(xvals{i}) & ~isempty(vvals{i}) +% xvals{i}=vvals{i}; +% tvals(i)=1; +% end +% end +% end + +nrows=sum(diff(levels)==0)+sum(diff(levels)>0)+1; +nvars= length(hnames); +%table of values +tvals=cell(nrows,length(hnames)); + +rowlevel=zeros(nrows,1); +row=0; +for i=1:length(levels)-1 + if i==1 || levels(i)>=levels(i-1) + row=row(end)+1; + else + row=find(rowlevel>levels(i)); + end + rowlevel(row)=levels(i); + for j=ilevels(i)+1:ilevels(i+1)-1 + % Fills with values for the corresponding level + k=strmatch(vnames(j),hnames, 'exact'); + if strcmp(vvals{j},'i') + v = NaN; + else + v=str2double(vvals{j}); + end + if isnan(v) && ~isempty(vvals{j}) + v=vvals{j}; + end + tvals(row,k)={v}; + end + k=strmatch(LevelNames(levels(i)),hnames, 'exact'); + if row(1)>1 + tvals(row,k)={tvals{row(1)-1,k}+1}; + else + tvals(row,k)={1}; + end +end + +for j=ilevels(end)+1:length(vvals) + % Fills with values for the corresponding level + k=strmatch(vnames(j),hnames, 'exact'); + if strcmp(vvals{j},'i') + v = NaN; + else + v=str2double(vvals{j}); + end + if isnan(v) && ~isempty(vvals{j}) + v=vvals{j}; + end + tvals(:,k)={v}; +end + + +%% Converts to Number and Text arrays +B = cell(nrows,nvars); +B(:) = {''}; +vIsNaN = false(nrows,nvars); +% find non-numeric entries in data cell array +vIsText = cellfun('isclass',tvals,'char'); +% place text cells in text array +% then replace them with NaN +if any(vIsText(:)) + vIsText=any(vIsText); + B(:,vIsText) = deblank(tvals(:,vIsText)); + % Replace [] in char array... + B(cellfun('isempty',B))={''}; + tvals(:,vIsText)={NaN}; +end +% place NaN in empty numeric cells +vIsNaN = ... + cellfun('isempty',tvals)|... + cellfun('isclass',tvals,'char'); +if any(vIsNaN(:)) + tvals(vIsNaN)={NaN}; +end +A = cell2mat(tvals); +B=reshape(B,[nrows,nvars]); + +%% Ouputs requested variables +if nargin>1 + if ~iscell(varnames) + varnames={varnames}; + end + k=zeros(1,length(varnames)); + for i=1:length(varnames) + if ismember(varnames(i),hnames) + k(i)=strmatch(varnames{i},hnames,'exact'); + else + %k(i)=strmatch(varnames{i},hnames,'exact'); + end + end + if any(k==0) + error('Variable name not found'); + end + hnames=hnames(k); + A=A(:,k); + B=B(:,k); + return +end + +% add: Procedure[Block] Procedure[Trial] Running[Block] Running[Trial] +k=strmatch('Level',hnames, 'exact'); +A(:,k)=[];B(:,k)=[];hnames(k)=[]; +k=strmatch('LevelName',hnames, 'exact'); +A(:,k)=[];B(:,k)=[];hnames(k)=[]; +k=strmatch('VersionPersist',hnames, 'exact'); +A(:,k)=[];B(:,k)=[];hnames(k)=[]; +k=strmatch('Procedure',hnames, 'exact'); +A(:,k)=[];B(:,k)=[];hnames(k)=[]; +k=strmatch('Running',hnames, 'exact'); +A(:,k)=[];B(:,k)=[];hnames(k)=[]; + +return + +function [tvals,rowlevel,varlevel]=filllevels(tvals,rowlevel,varlevel,curlevel,row,nlevels) +% fill lower levels in the hierarchy +for j=nlevels:-1:curlevel + k=(varlevel0; + tvals(row-[0:rowlevel(j)-1],k)=repmat(tvals(row,k),rowlevel(j),1); +end +varlevel(varlevel>=curlevel)=0; +rowlevel(curlevel:end)=0; +return \ No newline at end of file diff --git a/labnic/references_batches_spm8 [WikiNIC].html b/labnic/references_batches_spm8 [WikiNIC].html new file mode 100644 index 0000000..d9d4d2b --- /dev/null +++ b/labnic/references_batches_spm8 [WikiNIC].html @@ -0,0 +1,6432 @@ + + + + + references:batches_spm8 [WikiNIC] + + + + + + + + + + + + + + + + + + + + + + + + + + +

    + +
    + +
    + + + +
    +
    + + +
    +
    +
    + +
    +
      +
    + +
    +
    + + + + +
    + + +
    + + + + + +

    Import your data

    +
    +

    use this function for converting your data and save it as swr_import_DICOM.m

    +

    +It will open a first window to catch the folder where the original data +are and a second window to ask you where to save your converted data. +

    + +
    +
    +

    Convert NIFTI to old Analyze format

    +
    +

    As backward +compatibility is not insured in SPM for image formats, this fonction is +sometimes useful ; use this function for converting your NIFTI files +(.nii or nifti .img/.hdr) to ANALYSE format (.img/.hdr) and save it as +nifti2img.m ; Requires at least SPM5 to run

    +
    +
    +

    Preprocessing

    +
    +

    SPM5: save this function as spm5_preprocessing_labnic.m

    +

    +updated by YC on the 10th of October 2011 +

    +

    SPM8: Save this function as spm8_preprocessing_labnic.m to preprocess with spm8

    +

    + +You must have a structure like …main_dir\populations\subjects\scans\sessions\f*img +

    + +

    +If you have a structural image for the subject, it should be in a structure like: …main_dir\populations\subjects\scans\struct\s*img
    + +If you have no structural image, do not create the dir 'struct' and the coregistration will not be processed. +

    + +

    +It will create \af \waf and \swaf folders with the processed images.
    + +

    + +

    +Processing include: +

    +
      +
    1. realignment (with option to realign on mean or first scan of session)
      +
      +
    2. +
    3. coregistration (if struct image exists)
      +
      +
    4. +
    5. slice timing (suggested for TR > 2.5s)
      +
      +
    6. +
    7. normalization (on EPI template with +voxel size of [3 3 3]mm for functional images and on T1 template with +voxel size of [1 1 1]mm for struct image)
      +
      +
    8. +
    9. smoothing (Gaussian filter of 8 mm).
      +
      +
    10. +
    + +

    + +These steps can be now specified line 28 and options for each line 29 to 33. +

    + +

    +You must specify some details of your study in lines 23 to 33 of the +function. If you have specific names or specific order for sessions, +specify it at the end of this function.
    + +

    + +

    +If you notice some problems or need some help, please contact Yann Cojan (yann.cojan@unige.ch) or Virginie Sterpenich (virginie.sterpenich@unige.ch). +

    + +
    +
    +

    First Level Analysis

    +
    + +

    + +updated by YC on 17th Dec 2009 +

    + +

    +Works with spm8 without adaptation +

    +

    SPM5 or SPM8 : save this function as Labnic_1st_analysis.m

    +

    +updated by YC on 24th Jan 2012 +

    + +

    +if you want to be able to launch the batch in spm8 use this one: + +

    +

    SPM8 : save this function as Labnic_1stlevel_spm8.m

    +

    +You must specify some details of your study in lines 19 to 24 of the +function. If you have specific names or specific order for sessions, +specify it at the end of this function. +

    + +

    +Between lines 58-68, you have to specify how to load onsets and +conditions of your experiments. Several choices are possible (reading +eprime files, creating ons.mat and loading them, etc…). For help on +this topic, please contact anyone who has already the script running, +Yann Cojan (yann.cojan@unige.ch) or Virginie Sterpenich +(virginie.sterpenich@unige.ch). +

    + +

    +If you notice some problems or need some help, please contact Yann Cojan (yann.cojan@unige.ch) or Virginie Sterpenich (virginie.sterpenich@unige.ch). +

    + +

    +for connectivity fans… PPIs +

    +

    SPM8 : save this function as Labnic_PPI_spm8.m

    +

    +You have to specify the first lines of the script, then it should work +correctly… As contrasts are consistent across studies (PPI, psycho, +physio factors), they are set and computed by default. If you have +comments, suggestions, bugs reports, feel free to contact Yann Cojan (yann.cojan@unige.ch) +

    + +
    +
    +

    First Level Contrasts

    +
    + +

    + +Updated by YC on July 17th 2009 +

    +

    SPM5 or SPM8 : save this function as Labnic_1st_contrast_spm5.m

    +

    + +You must specify some details of your study in lines 17-18 of the function. +

    + +

    +You have to specify your contrast from line 68 to as many contrasts you +want. For help on this topic, please contact anyone who has already the +script running, Yann Cojan (yann.cojan@unige.ch) or Virginie Sterpenich +(virginie.sterpenich@unige.ch). +

    + +

    +If you notice some problems or need some help, please contact Yann Cojan (yann.cojan@unige.ch) or Virginie Sterpenich (virginie.sterpenich@unige.ch). +

    + +
    +
    +

    Second Level Analysis

    +
    + +

    + +Updated by YC on Dec 17th 2009 +

    + +
    + +

    1 sample ttest

    +
    +

    SPM5 : save this function as Labnic_rfx_ones_spm5.m

    SPM8 : save this function as Labnic_rfx_ones_spm8.m

    +

    +You must specify some details of your study in lines 21-23 of the function. +

    + +

    +It's taking the first subject analysis and conditions and reproduce this + analysis at the group level. Be sure that every subjects have the +contrasts in the same order… For help on this topic, please contact +anyone who has already the script running, Yann Cojan (yann.cojan@unige.ch) or Virginie Sterpenich (virginie.sterpenich@unige.ch). +

    + +

    +If you notice some problems or need some help, please contact Yann Cojan (yann.cojan@unige.ch) or Virginie Sterpenich (virginie.sterpenich@unige.ch). +

    + +
    + +

    2 sample ttest

    +
    +

    SPM5 : save this function as Labnic_rfx_twos_spm5.m

    +

    +You must specify some details of your study in lines 21-23 of the function. +

    + +

    +This function is taking the first subject analysis and conditions and +reproduce this analysis at the group level. Be sure that every subjects +have the contrasts in the same order… A 2 sample ttest is computed for +each contrast of interest made at the first level. +

    + +

    +For help please contact anyone who has already the script running, Yann Cojan (yann.cojan@unige.ch) or Virginie Sterpenich (virginie.sterpenich@unige.ch). +

    + +

    +If you notice some problems or need some help, please contact Yann Cojan (yann.cojan@unige.ch) or Virginie Sterpenich (virginie.sterpenich@unige.ch). +

    + +
    + +

    Anovas

    +
    +

    SPM5 : save this function as Labnic_Anova_spm5.m

    +

    +Updated by SP March 7th 2011 +

    + +

    +Note that if you want to correct for potential violations of sphericity +(ie what you usually do when looking at the p-value in the +Greenhouse-Geiser line), you need to set the variance of the condition +factor 'condition' to unequal (==1). The following code asks you this +question using a popup window. +

    +

    SPM8: Save this function as Labnic_Anova_spm8.m

    +

    +This function is functioning by taking the contrasts (define in cons +line 10) for simple HRF modeling (like Face_emotional, Face_neutral) and + make the anova directly at the second level. Furthermore it is doing +some contrasts at line 146 that you should define. +

    + +

    +For help please contact anyone who has already the script running, Yann Cojan (yann.cojan@unige.ch) or Virginie Sterpenich (virginie.sterpenich@unige.ch). +

    + +

    +If you notice some problems or need some help, please contact Yann Cojan (yann.cojan@unige.ch) or Virginie Sterpenich (virginie.sterpenich@unige.ch). +

    +

    SPM5 : or Labnic_Anova_2groups_spm5.m

    SPM8: or Labnic_Anova_2groups_spm8.m

    +

    +You must specify some details of your study in lines 7-10 of the +functions. +As contrasts as not as easy to enter than for more classical analysis, +main contrasts are set by default by feel free to contact Yann Cojan (yann.cojan@unige.ch) for problems or suggestions… +

    + +
    +
    +

    Note on sphericity correction in SPM 2nd level analyses

    +
    + +

    + +Updated by SP on June 22th 2011 +

    + +

    +Very often, when doing ANOVAs with SPM, people assume that the error +variance between experimental conditions is equal, ie it is spheric. +Sphericity is an extension of the homoscedasticity of variance for the +population being sampled. It is one of the prerequisites to perform an +ANOVA. Concretely, sphericity is an assumption about the structure of +the covariance matrix - between experimental conditions -, namely that +covariance values should be roughly equal. +

    + +

    +In SPSS, when sphericity is violated, you generally read the p values +from the GreenHouse Geiser or Huynh and Feldt correction lines. This +correction works by decreasing the degree of freedom of your analysis +and p values are adjusted as a function of how much the variance differs + between conditions. +

    + +

    +In SPM, sphericity correction is achieved by answering YES to the +question 'unequal variance between conditions' (or setting this +parameter value to 1). +

    + +

    +Be aware that by default, this parameter is set to 0 in many labnic +scripts, meaning that you don't correct for sphericity violation, if +any. Be also aware that in a lot of example you find on the web, many do + not correct for sphericity. +

    + +

    +So you wonder, “well, how do I notice whether or not my dataset is spheric †? +

    + +

    +You can check this by looking at your ANOVA GLM matrix, once estimated.
    + +- If your dataset is spheric, you should see an nice-looking eye matrix, ie a white diagonal on a greyish background.
    + +- If your dataset is not spheric, the matrix will look 'messy' and the diagonal will be hardly distinguishable. +

    + +

    +Once estimated, SPM displays the whitened (ie scaled) design matrix. +This scaling will be adjusted if sphericity correction is required and +enabled in your script by setting unequal variance of your condition +factor to 1. Since sphericity correction rescales your design matrix, it + will also change and improve the design orthogonality. +

    + +

    +You may also wonder, “what in my experimental design, renders my dataset non spheric ?â€. +

    + +

    +Well if you have, in the same model, conditions that have been estimated + using different numbers of trials (n versus 2n for instance), you will +probably end up with a problem of sphericity. Concretely, you can be +faced with such a problem in designs where you analyse errors or when +using a condition with rare events (e.g. oddballs). +

    + +
    +
    +

    Plotting spheres instead of voxels

    +
    + +

    + +Updated by SP on Sep 28th 2010 +

    + +

    +This snippet adds a button to SPM GUI + in the plot function that will make SPM plot the beta for one / several + voxels contained into a sphere. +The step added to the plot function concerns the specification of the +sphere radius. Choose 0 and you will plot the beta for 1 voxel, as +usual. Choose N and you will plot the mean beta for all voxels located +into a sphere of N mm radious. +It has been tested for SPM8 only and might require adaptations for +earlier versions of SPM. +1. You need to edit spm_graph.m +3. Replace the following code (around line 219) +

    +

    Code to remove / comment

    +
        beta  = spm_get_data(SPM.Vbeta, XYZ);
    +    ResMS = spm_get_data(SPM.VResMS,XYZ);
    +    Bcov  = ResMS*SPM.xX.Bcov;
    +
    +

    +and add the following lines +

    +

    Code to add

    +
        % BEGINNING OF MODIFICATION : plots the mean/SE of a voxel / a bunch of voxels contained into a sphere of Nmm radius
    +    % This code replaces the following SPM initial code
    +    %     beta  = spm_get_data(SPM.Vbeta, XYZ);
    +    %     ResMS = spm_get_data(SPM.VResMS,XYZ);
    +    %     Bcov  = ResMS*SPM.xX.Bcov;
    + 
    +    rad = spm_input(['Plot voxel (0) or sphere (radius mm)'],'+0','r',0,1);
    +    d     = [xSPM.XYZ(1,:) - XYZ(1); xSPM.XYZ(2,:) - XYZ(2); xSPM.XYZ(3,:) - XYZ(3)];  % Center the contrast on the voxel / sphere coordinates
    +    Q     = find(sum(d.^2) <= rad^2);                                                  % Find surrounding voxels if rad > 0
    +    XYZ = xSPM.XYZ(:,Q);                                                               % Get their coords
    +    beta  = spm_get_data(SPM.Vbeta, XYZ);                                              % Get their beta
    +    ResMS = spm_get_data(SPM.VResMS,XYZ);                                              % Get their VResMS values
    + 
    +    if size(beta, 2)>1                                                                 % If more than 1 voxel, then average the stuff before calculating Bcov, SE ..
    +        beta = mean(beta')';
    +        ResMS = mean(ResMS);
    +    end
    +    Bcov  = ResMS*SPM.xX.Bcov;
    + 
    +    % END OF MODIFICATION
    +
    +

    +then when opening spm, you will have a new fuschia button on the left bottom panel of spm.
    + +

    + +
    +
    +

    Beta Extraction

    +
    + +

    +Updated by YC +

    + +
    + +

    Beta Extraction for SPM

    +
    + +

    + +Before using the following function, you may change one little thing in spm_results_ui.m to activate a new button in the spm GUI.
    + +

    + +

    +1. search for the %-Not currently used sentence (around line 570) and replace the old paragraph by this: + +

    +

    this little part

    +

    + +then when opening spm, you will have a new fuschia button on the left bottom panel of spm.
    + +

    + +

    +2. save the following function as +

    +

    extract_clubetas.m

    +
    + +

    Beta Extraction for xjview

    +
    +

    First add this little part (around line 353) in xjview.m

    +

    + between 'callback', @CallBack_commonRegionPush); and handles.displayPush +

    +

    Then add the following part at the end of xjview.m just before the last return

    +
    function CallBack_extract_clubetas(hObject, eventdata, readdata)
    +if nargin<3
    +    readdata=1;
    +end
    + 
    + handles = guidata(hObject);
    +[img.p, img.fname, img.ext]=fileparts(handles.imageFileName{1});
    +spmfile=fullfile(img.p,'SPM.mat');
    + 
    +if exist(spmfile(3:end), 'file')
    +    warning('Possible disk swapping in xjview.m');
    +    spmfile=spmfile(3:end);
    +end
    +if ~exist(spmfile)
    +    if findstr('SPM2',spm('ver'))
    +        spmfile = spm_get([0 1],'SPM.mat','locate the RFX SPM.mat');
    +    elseif findstr('SPM5',spm('ver'))
    +        spmfile = spm_select([0:1],'SPM.mat','locate the RFX SPM.mat');
    +    end
    +end
    +if ~exist(spmfile)
    +    return
    +end
    +xSPM=load(spmfile, 'SPM');
    +[glmpath, glmpath]=fileparts(fileparts(fileparts(xSPM.SPM.swd)));
    +clu = handles.currentDisplayMNI{1};
    +center = handles.currentxyz;
    +mni = cell2mat(handles.currentDisplayMNI');
    +cor = mni2cor(mni, handles.M{1});
    +[xyzmm,i] = spm_XYZreg('NearestXYZ',center,mni');
    +A = spm_clusters(cor');
    +j = find(A == A(i));
    +xyz = mni(j,:);
    +sub2pr=[1:xSPM.SPM.nscan];
    +clear Data
    +pathlist = xSPM.SPM.xY.P;
    + 
    +allbetas= [];
    +Allbetas = [];
    +allxyz = [];
    + 
    +%--- to write
    +XYZ  = xSPM.SPM.xVol.XYZ;
    +XYZmm = xSPM.SPM.xVol.M(1:3,:)*[XYZ; ones(1,size(XYZ,2))];
    +bob = spm_input('nature of extraction','+1','b','VOI|cluster',[0,1]);
    + 
    +if ~bob
    +    radius=inputdlg('Radius of the sphere?');
    +    if isempty(radius)
    +        return
    +    end
    +    radius=str2num(radius{1});
    +    [d] = spm_XYZreg('Edist',center,XYZmm);
    +    i=find(d<=radius);
    +    clus=XYZmm(:,i);
    +    xyz = clus';
    +end
    + 
    +cond_list = [];
    +for sub = 1:length(sub2pr)
    +    mydir = fileparts(pathlist{sub});
    +    %% initiation of matrix Allbetas and Tmpallbetas with the max
    +    %% number of conditions
    +    sSPM = load([mydir filesep 'SPM.mat']);
    +    nses = length(sSPM.SPM.Sess);%num of session
    +    convec = zeros(1,length(sSPM.SPM.Vbeta));
    +    for ses = 1:nses
    +        for j = 1:length(sSPM.SPM.Sess(ses).Fc)
    +            index = sSPM.SPM.Sess(ses).col(sSPM.SPM.Sess(ses).Fc(1,j).i);
    +            convec(ses,index) = 1;
    +        end
    +    end
    +    real = sSPM.SPM.xX.name(any(convec));
    +    for j = 1:length(real)
    +        b = strfind(real{j},'*bf');
    +        list_cond{j} = real{j}(7:b-1);
    +    end
    +    cond_list = [cond_list unique(list_cond)];
    +end
    +thelist = unique(cond_list);
    +Allbetas = NaN(length(sub2pr),length(thelist));
    + 
    +h = waitbar(0,'Please Wait...');
    +for sub = 1:length(sub2pr)
    +    mydir = fileparts(pathlist{sub});
    +    sSPM = load([mydir filesep 'SPM.mat']);
    +    for i = 1:length(sSPM.SPM.Vbeta)
    +        sSPM.SPM.Vbeta(i).fname = [mydir filesep sSPM.SPM.Vbeta(i).fname];
    +    end
    +    tmpallbetas= [];
    +    tmpallxyz = [];
    +    Tmpallbetas= [];
    +    list_cond = [];
    +    for cluvox = 1:size(xyz,1)
    +        [nxyz,i] = spm_XYZreg('NearestXYZ',xyz(cluvox,:),XYZmm);
    +        govox=1;
    +        if sqrt(sum((nxyz'-xyz(cluvox,:)).^2))>=1.7321 %one voxel in each dimZ
    +            govox= spm_input({'No data stored for this voxel','Closest voxels with data are:',...
    +                num2str(xyz(cluvox,:)),...
    +                'Continue anyway?'},...
    +                1,'bd',{'OK','NO'},[1,0]);
    +        end
    +        vXYZ     = XYZ(:,i)  ;         % coordinates in voxels
    + 
    +        %-Get parameter and hyperparameter estimates
    +        beta= spm_get_data(sSPM.SPM.Vbeta, vXYZ);
    + 
    +        convec = zeros(1,length(sSPM.SPM.Vbeta));
    +        for ses = 1:nses
    +            for j = 1:length(sSPM.SPM.Sess(ses).Fc)
    +                index = sSPM.SPM.Sess(ses).col(sSPM.SPM.Sess(ses).Fc(1,j).i);
    +                convec(ses,index) = 1;
    +            end
    + 
    +        end
    +        real = sSPM.SPM.xX.name(any(convec));
    +        for j = 1:length(real)
    +            b = strfind(real{j},'*bf');
    +            list_cond{j} = real{j}(7:b-1);
    +        end
    +        tmpallbetas  = [tmpallbetas; beta(any(convec))'];
    +    end
    + 
    +    for c = 1:length(thelist)
    +        C = strcmp(thelist{c},list_cond);
    +        Tmpallbetas = [Tmpallbetas mean(mean(tmpallbetas(:,C)))];
    +    end
    + 
    +    Allbetas(sub,:) = Tmpallbetas;
    +    waitbar(sub/length(sub2pr),h)
    +end
    +close(h)
    + 
    +ctitle = [num2str(nxyz') '  nvox = ' num2str(size(xyz,1))];
    +tmpaxe = thelist(1:length(thelist));
    + 
    +npop = size(xSPM.SPM.xX.X,2);
    +for pop = 1:npop
    +    Data{pop}(:,:) = reshape(Allbetas(logical(repmat(xSPM.SPM.xX.X(:,pop),1,size(Allbetas,2)))),sum(xSPM.SPM.xX.X(:,pop)),size(Allbetas,2));
    +end
    +[Maj,ST,SE] = yann_bar(Data);
    +switch spm_input('errorbar?',2,'b','yes|no');
    +    case 'yes'
    +        figure
    +        h  = bar(Maj');
    +        for  i = 1:size(Maj,1)
    +            if i == 1 & npop > 1
    +                N = -0.08;
    +            elseif i == 1
    +                N = 0;
    +            else
    +                N = 0.08;
    +            end
    +            for j = 1:size(Maj,2)
    +                coef = j+npop*N;
    +                line([coef coef],([SE(i,j) 0 - SE(i,j)] + Maj(i,j)),...
    +                    'LineWidth',1.5,'Color','k')
    +                line ([coef-0.05 coef+0.05],([SE(i,j)+Maj(i,j) SE(i,j)+Maj(i,j)]),...
    +                    'LineWidth',1.5,'Color','k')
    +                line ([coef-0.05 coef+0.05],([-SE(i,j)+Maj(i,j) -SE(i,j)+Maj(i,j)]),...
    +                    'LineWidth',1.5,'Color','k')
    +            end
    +        end
    +    case 'no'
    +        figure
    +        h  = bar(Maj');
    +        set(h,'FaceColor',[1 1 1]*.8)
    +end
    +set(gca, 'XTickLabel',tmpaxe, 'XTick' , [1:length(tmpaxe)],'FontSize',6);
    +title(ctitle,'FontSize',16)
    + 
    +function [Maj,ST,SE] = yann_bar(Data)
    +% function [Maj,ST,SE] = yann_bar(Data)
    +% calculate Mean, stdev and std error for Data
    +% adapted by Yann Cojan (yann.cojan@unige.ch)
    + 
    +npop = length(Data)
    + 
    +switch spm_input('Reset lowest value to:?',1,'b','zero|mean|unchanged');
    +    case 'zero'
    +        for pop = 1:npop
    +            if isempty(find(mean(Data{pop})<0))
    +                Maj(pop,:) = mean(Data{pop},1)- min(mean(Data{pop},1));
    +            else
    +                Maj(pop,:) = mean(Data{pop},1) + abs(min(mean(Data{pop},1)));
    +            end
    +        end
    +    case 'mean'
    +        for pop = 1:npop
    +            Maj(pop,:) = mean(Data{pop},1) - mean(mean(Data{pop},1));
    +        end
    +    case 'unchanged'
    +        for pop = 1:npop
    +            if any(any(isnan(Data{pop})))
    +                count = 1;
    +                for hu = 1:size(Data{pop},2)
    +                    Maj(pop,hu) = mean(Data{pop}(~isnan(Data{pop}(:,hu)),hu));
    +                end
    +            else
    +                Maj(pop,:) = mean(Data{pop},1);
    +            end
    +        end
    +end
    +for pop = 1:npop
    +    if size(Data{pop},1)>1
    +        if any(any(isnan(Data{pop})))
    +            for hu = 1:size(Data{pop},2)
    +                SE(pop,hu) = std(Data{pop}(~isnan(Data{pop}(:,hu)),hu))/sqrt(size(Data{pop}(~isnan(Data{pop}(:,hu))),1));
    +                ST(pop,hu) = std(Data{pop}(~isnan(Data{pop}(:,hu)),hu));
    +            end
    +        else
    +            SE(pop,:) = std(Data{pop})/sqrt(size(Data{pop},1));
    +            ST(pop,:) = std(Data{pop});
    +        end
    +    else
    +        if any(any(isnan(Data{pop})))
    +            for hu = 1:size(Data{pop},2)
    +                 SE(pop,hu) = std(Data{pop}(~isnan(Data{pop}(:,hu)),hu))/sqrt(size(Data{pop}(~isnan(Data{pop}(:,hu))),1));
    +                ST(pop,hu) = std(Data{pop}(~isnan(Data{pop}(:,hu)),hu));
    +            end
    +        else
    +            SE(pop,:) = zeros(1,size(Data{pop},2));
    +            ST(pop,:) = zeros(1,size(Data{pop},2));
    +        end
    +    end
    +end
    +
    +
    + +

    Remark concerning the standard error estimated by SPM

    +
    + +

    +Updated by SP June 22th 2011 +

    + +

    +You will notice that if you estimate the SEM of your extracted beta - +using std(beta)/sqrt(n) - you will find a very different estimate than +the one calculated by SPM. +This is because SPM derives the SE from the unexplained variance in the +model, ie the variance of the residual. This is calculated in spm_graph, + line ~328. +SE = sqrt(diag(SPM.xCon(Ic).c'*Bcov*SPM.xCon(Ic).c)); +% knowing that Bcov = ResMS*SPM.xX.Bcov +

    + +

    +In a traditional ANOVA language, this is equivalent to the root mean +square error (RMSE = sqrt(MSE) = SQRT(STD/SQRT(n))^2 = SQRT(SE)^2) aka +the standard error. +http://en.wikipedia.org/wiki/Root_mean_square_deviation +http://en.wikipedia.org/wiki/Mean_squared_error +

    + +

    +You can find some useful SPM posts here:
    + +https://www.jiscmail.ac.uk/cgi-bin/webadmin?A2=ind0708&L=SPM&P=R20753&1=SPM&9=A&I=-3&J=on&d=No+Match%3BMatch%3BMatches&z=4
    + +https://www.jiscmail.ac.uk/cgi-bin/webadmin?A2=ind0708&L=SPM&P=R27896&1=SPM&9=A&I=-3&J=on&d=No+Match%3BMatch%3BMatches&z=4 +

    + +
    +
    +

    Code Snippets

    +
    + +

    + +Need help with SPM? Want to program something? +

    + +

    +First, have a look at the SPM mailing-list (Search page). +

    + +

    +Second, hasn't John Ashburner already done it in his SPM99, SPM2, SPM5 Gems? +

    + +

    +Third, you may find it below… +

    + +
    + +

    Image Calculator

    +
    + +

    + +This is an example how to batch the Image Calculator function (spm_imcalc) for several subjects (SPM2): + +

    +

    How to Batch a Image Calculator function for several subjects

    +
    + +

    Moving Data or SPM.mat

    +
    + +

    + +In case your data have “moved hose†from one computer to another or within the computer, SPM.mat needs to know the new adress. +This is an example how to move house for SPM.mat: + +

    +

    Moving house script for SPM.mat

    + +
    function MG_moveSPM
    + 
    +if spm('ver')=='SPM2'
    +    mypath = spm_get(-1,pwd,'Select parent directory with all SPM.mat to move in its childs!');
    +else
    +    mypath = spm_select(1,'dir','Select parent directory with all SPM.mat to move in its childs!','',pwd)
    +end
    +mypathlist = genpath(mypath);
    +mypathlist = strread(mypathlist,'%s','delimiter',';');
    + 
    +for d=2:size(mypathlist)
    +    cd(mypathlist{d})
    +    if exist('SPM.mat')
    +        load SPM;
    +        xSPM=SPM;
    +        xSPM.swd = pwd;
    +        SPM=xSPM;
    +        warning('SPM.mat is changed!!')
    +        save SPM.mat SPM
    +        disp(xSPM.swd)
    +    end
    +end
    +
    +
    + +

    Read Eprime text files

    +
    +

    save this function as read_eprimetxt.m

    +
    + +

    Several SPM Versions

    +
    + +

    + +In case you must use several SPM versions in the same MATLAB you'll have to change the path and clear the global variables. +For that purpose you can create a function that does all this (example +from SPM2 to SPM5). Copy and paste this into your matlab-batches +directory (name p_spm5) and edit the SPM path according to your PC: + + +

    +

    Change between SPM2 and SPM5

    +

    +Loading the data contained in a MR volume in SPM (I always forget that) + +

    +
    IMAGE=spm_read_vols(spm_vol(spm_select('List',your_file_directory,'your_filename.img'))
    + +
    + +

    Matlab figure in PDF

    +
    +

    Save your Matlab figure as a PDF in exactly the present appearance and size (needs GHOSTSCRIPT installed)

    +

    +Here a handy function to convert PS files to PDF (found here). + +You can use it for example to convert the PS files created by SPM during + preprocessing to verify realignment, normalisation, etc. Works straight + out of the box in Matlab. +

    +

    ps2pdf.m

    +
    +
    ps2pdf.m
    +
    function ps2pdf(varargin)
    +%PS2PDF Function to convert a PostScript file to PDF using Ghostscript
    +%
    +%  Converts a postscript file into PDF. The resulting PDF file will contain
    +%  one page for each page defined in the postscript files, so a multi-page
    +%  postscript file, like those generated by using the '-append' option of 
    +%  MATLAB's print command, can be used to generate a multi-page PDF file. 
    +%
    +%   Ghostscript is a third-party application currently supplied with 
    +%   MATLAB. The caller may also specify a different version of Ghostscript
    +%   to use.
    +%
    +%   PS2PDF expects to be called with a set of parameter-value pairs. The
    +%   order of these is unimportant, but required parameters MUST be
    +%   specified
    +%
    +%   In the list below, required parameters are marked with an asterisk *
    +%   Parameter:        Value:  
    +%   *  psfile         full or relative path to the postscript file to convert
    +%
    +%   *  pdffile        full or relative path to the pdf file to create
    +%
    +%      gscommand      path to Ghostscript executable to use; this will try
    +%                     to default to the version of Ghostscript shipped with 
    +%                     MATLAB, if any. If this value is specified you should 
    +%                     also specify the gsfontpath and gslibpath values.
    +%
    +%      gsfontpath     full path to the Ghostscript font files
    +%                     If a gscommand is specified then this path should
    +%                     also be specified and reference the same Ghostscript
    +%                     version
    +%
    +%                     If gscommand is NOT specified and we can determine
    +%                     the version of Ghostscript, if any, shipped with
    +%                     MATLAB, then this value will be overridden to use the
    +%                     path that references MATLAB's version of Ghostscript
    +%
    +%      gslibpath      full path to the Ghostscript library (.ps) files. 
    +%                     If a gscommand is specified then this path should
    +%                     also be specified and reference the same Ghostscript
    +%                     version
    +%
    +%                     If gscommand is NOT specified and we can determine
    +%                     the version of Ghostscript, if any, shipped with
    +%                     MATLAB, then this value will be overridden to use the
    +%                     path that references MATLAB's version of Ghostscript
    +%
    +%      gspapersize    paper size to use in the created .pdf file. If not 
    +%                     specified or the specified value is not recognized 
    +%                     it will use whatever default paper size is 
    +%                     built into the version of Ghostscript being run
    +%
    +%                         NOTE: no scaling of the input occurs - it's simply
    +%                         placed on a page with the specified paper size. 
    +%
    +%                         Valid values for gspapersize are: 
    +%                              'letter', 'ledger', 'legal', '11x17', 
    +%                              'archA', 'archB', 'archC', 'archD', 'archE', 
    +%                              'a0', 'a1', 'a2', 'a3','a4', 'a5',
    +%                              'a6', 'a7', 'a8', 'a9', 'a10'
    +%                         
    +%      deletepsfile   0 to keep the input ps file after creating pdf
    +%                     non-zero to delete the input ps file after creating pdf
    +%                     Default is 0: keep the input ps file (do NOT delete it)
    +%                        NOTE: if the pdf creation process fails, the input
    +%                        PS file will be kept regardless of this setting
    +%
    +%      verbose        0 to suppress display of status/progress info; 
    +%                     non-zero to allow display of status/progress info
    +%                     Default is 0 (no display)
    +%
    +% Example usage: 
    +%    use MATLAB's version of Ghostscript to generate an A4 pdf file
    +%      ps2pdf('psfile', 'input.ps', 'pdffile', 'output.pdf', 'gspapersize', 'a4')
    +%
    +%    use a local copy of Ghostcript to generate a file, and display some 
    +%    status/progress info while doing so.
    +%      ps2pdf('psfile', '../reports/input.ps', 'pdffile', 'c:\temp\output3.pdf', ...
    +%            'gspapersize', 'a4', 'verbose', 1, ...)
    +%            'gscommand', 'C:\Program Files\GhostScript\bin\gswin32c.exe', ...
    +%            'gsfontpath', 'C:\Program Files\GhostScript\fonts', ...
    +%            'gslibpath', 'C:\Program Files\GhostScript\lib')
    +%
    +%    use MATLAB's version of Ghostscript to generate a pdf file and delete
    +%    the input.ps file when done 
    +%      ps2pdf('psfile', 'input.ps', 'pdffile', 'output.pdf', 'gspapersize', 'a4', 'deletepsfile', 1)
    + 
    +%   Update log: 
    +%      Aug 15, 2008: fixed bug where embedded space in location of 
    +%                    MATLAB's version of Ghostscript caused ps2pdf to fail.
    +%      Apr 16, 2008: added deletepsfile option
    + 
    +%   Copyright 2008 The MathWorks, Inc.
    + 
    +   if nargin < 1 
    +      error('ps2pdf:parameters', 'No parameters specified. Type ''help ps2pdf'' for details on how to use this function.');
    +   end
    + 
    +   % parse input args
    +   gsData = LocalParseArgs(varargin{:});
    + 
    +   % setup the file that tells GS what we want it to do
    +   gsData = LocalCreateResponseFile(gsData); 
    + 
    +   gsDebug = 0;
    +   if gsData.verbose 
    +      fprintf('ps2pdf: input settings are:\n');
    +      if isfield(gsData, 'paperSizes')
    +          gsData = rmfield(gsData, 'paperSizes');
    +      end
    +      gsData  %#ok<NOPRT>
    +      fprintf('ps2pdf: response file for Ghostscript is:\n');
    +      type(gsData.responseFile);
    +      gsDebug = 1;
    +   end
    + 
    +   %to hold results/status from system call
    +   s = 0; %#ok<NASGU>
    +   r = ''; %#ok<NASGU>
    + 
    +   % run Ghostscript to convert the file
    +   if gsData.useBuiltin 
    +      [s, r] = gsData.cmd(['@' gsData.responseFile], gsData.psFile, gsDebug);
    +   else 
    +      [s, r] = system([gsData.cmd ' @' gsData.responseFile ' ' gsData.psFile]);
    +   end
    + 
    +   if gsData.verbose
    +      disp( ['Ghostscript STDOUT: ' num2str(s) ] );
    +      disp( ['Ghostscript STDERR: ' r ] );
    +   else
    +      delete(gsData.responseFile)
    +   end
    + 
    +   if s && ~isempty(r)
    +      error('ps2pdf:ghostscript',  ['Problem converting PostScript. System returned error: ' num2str(s) '.' r]) 
    +   elseif s
    +      error('ps2pdf:ghostscript',  ['Problem calling GhostScript. System returned error: ' num2str(s)]) 
    +   end
    + 
    +   %if after all this we still couldn't create the file, report the error
    +   fid = fopen( gsData.pdfFile, 'r');
    +   if ( fid == -1 )
    +      error('ps2pdf:ghostscript', '%s', [ 'Ghostscript could not create ''' gsData.pfdFile '''.' ])
    +   else
    +      fclose( fid );
    +   end
    + 
    +   % if we get here, we successfully created pdf file; delete ps file if
    +   % requested to do so
    +   if gsData.deletePSFile 
    +       delete(gsData.psFile);
    +   end
    + 
    +end
    + 
    +%local function to parse arguments and fill in the gsData structure
    +%  .psFile - postscript file to convert
    +%  .pdfFile - pdf file to create
    +%  .cmd     - path/name of Ghostscript command to run or handle to gscript
    +%             builtin
    +%  .useBuiltin - 1 if using builtin gs command 
    +%  .fontPath - path to the Ghostscript fonts, if any
    +%  .libPath - path to the Ghostscript libs (Ghostscript .ps files), if any) 
    +%  .paperSize - paper size to set for resulting .pdf file 
    +%  .deletePSFile - 0 to keep (not delete) the input ps file if pdf created ok
    +%  .verbose - if non-zero, display some status/progress info to command window
    +function gsData = LocalParseArgs(varargin) 
    +    gsData.paperSizes = {'letter', 'ledger', 'legal', '11x17', 'archA', 'archB', ... 
    +                   'archC', 'archD', 'archE', 'a0', 'a1', 'a2', 'a3','a4', 'a5', ...
    +                   'a6', 'a7', 'a8', 'a9', 'a10'};
    + 
    +    %default values for some settings
    +    gsData.verbose      = 0; 
    +    gsData.useBuiltin   = 0;
    +    gsData.deletePSFile = 0; 
    + 
    +    for i.html">i = 1 : 2 : length(varargin)-1 
    +        param_arg = varargin{i.html">i};
    +        param_value = varargin{i.html">i+1};        
    +        switch(lower(param_arg)) 
    +            % path to ps file to conver
    +            case 'psfile'
    +               if ~exist(param_value, 'file')
    +                  error('print:ghostscript', ...
    +                      'Can not find postscript file <%s> to convert', ...
    +                      param_value)
    +               end
    +               gsData.psFile = param_value;
    + 
    +            % path to pdf file to create
    +            case 'pdffile'
    +                %verify we can create file at that location
    +                pdf_fid = fopen(param_value,'w');
    +                if pdf_fid < 0 
    +                    error('ps2pdf:invalidPDFFIle', ... 
    +                        'Can not open <%s> for writing', ...
    +                        param_value);
    +                end
    +                fclose(pdf_fid); 
    +                %delete temp file we created
    +                delete(param_value); 
    +                gsData.pdfFile = param_value;
    + 
    +            % full path to gs executable
    +            case 'gscommand' 
    +               if ~exist(param_value, 'file')
    +                  error('ps2pdf:ghostscriptCommand', ...
    +                      'Can not find Ghostscript executable (''gscommand'') <%s>',...
    +                      param_value)
    +               end
    +               if ispc && ~isempty(findstr(param_value, ' '))
    +                   param_value = ['"' param_value '"']; %#ok<AGROW>
    +               end
    +               gsData.cmd = param_value; 
    + 
    +            % full path to gs font dir
    +            case 'gsfontpath' 
    +               if ~exist(param_value, 'dir')
    +                   error('ps2pdf:ghostscriptFontPath', ...
    +                         'Can not find the directory <%s> for Ghostscript fonts (''gsfontpath'')', ...
    +                         param_value)
    +               end
    +               gsData.fontPath = param_value;
    + 
    +            % full path to gs lib dir
    +            case 'gslibpath' 
    +               if ~exist(param_value, 'dir')
    +                   error('ps2pdf:ghostscriptLibPath', ...
    +                         'Can not find the directory <%s> for Ghostscript library files (''gslibpath'')', ...
    +                         param_value)
    +               end
    +               gsData.libPath = param_value;
    + 
    +            % paper size 
    +            case 'gspapersize'
    +               idx = strcmpi(param_value, gsData.paperSizes);
    +               if ~any(idx)
    +                  warning('ps2pdf:papersize', ...
    +                        '''gspapersize'' value <%s> not found in the list of known sizes, ignoring it.', param_value);
    +               else
    +                  gsData.paperSize = gsData.paperSizes{idx};
    +               end
    + 
    +            % deletePSFile
    +            case 'deletepsfile'
    +               if isnumeric(param_value) 
    +                  gsData.deletePSFile = param_value; 
    +               else
    +                   warning('ps2pdf:deletepsfile', ...
    +                         '''deletepsfile'' value <%s> class <%s> should be numeric, defaulting to 0', ...
    +                         param_value, class(param_value));
    +               end
    + 
    +            % verbose
    +            case 'verbose'
    +               if isnumeric(param_value) 
    +                  gsData.verbose = param_value; 
    +               else
    +                   warning('ps2pdf:verbose', ...
    +                         '''verbose'' value <%s> class <%s> should be numeric, defaulting to 0', ...
    +                         param_value, class(param_value));
    +               end
    + 
    +            otherwise
    +               if isnumeric(param_value)
    +                   param_value = num2str(param_value);
    +               end
    +                  warning('ps2pdf:unknown', ...
    +                     'ignoring unknown parameter <%s> with value <%s>.', param_arg, param_value);
    +        end
    +    end    
    + 
    +    if ~isfield(gsData, 'psFile') 
    +        error('ps2pdf:noInputFile', ...
    +               'No input (psfile) file specified');
    +    end
    + 
    +    if ~isfield(gsData, 'pdfFile') 
    +        error('ps2pdf:noOutputFile', ...
    +               'No output (pdffile) file specified');
    +    end
    + 
    +    if ~isfield(gsData, 'cmd') 
    + 
    +        v = ver('MATLAB');
    +        if str2double(v.Version) < 7.4
    +            [gsCmd, ghostDir] = Local_GetOldGhostscript();
    +            gsData.cmd = gsCmd;
    +        else 
    +            % version 7.4 or later
    +           % the path to Ghostscript changed in 7a
    +           ghostDir = fullfile( matlabroot, 'sys', 'gs8x' );
    +           if ~exist(ghostDir, 'dir')
    +              error('ps2pdf:ghostscriptCommand', ...
    +                    'Can not find Ghostscript installed with MATLAB in <%s>',...
    +                    ghostDir);
    +           end
    +           gsData.cmd = Local_GetGscriptFcnHandle;
    +           if ~isempty(gsData.cmd)
    +              gsData.useBuiltin = 1; % use builtin Ghostscript
    +           end
    +        end
    + 
    +        if ~isempty(gsData.cmd)
    +           % if using MATLAB's version of GhostScript, use same set of fonts and library files
    +           if isfield(gsData, 'fontPath') || isfield(gsData, 'libPath')
    +              warning('ps2pdf:ghostscriptPathOverride', ...
    +                    'Using MATLAB''s version of Ghostscript; overriding ''gsfontpath'' and ''gslibpath'' to use builtin MATLAB version');
    +           end
    +           gsData.fontPath = fullfile( ghostDir, 'fonts', '');
    +           gsData.libPath = fullfile( ghostDir, 'ps_files', '');
    +        else 
    +            error('ps2pdf:noGhostscriptCommand', ...
    +                  'Can not find Ghostscript program in MATLAB');
    +        end
    +    else
    +        % if gscommandpath was specified, 
    +        if ~isfield(gsData, 'fontPath') || ~isfield(gsData, 'libPath')
    +           warning('ps2pdf:ghostscriptCommandSuggestion', ...
    +                 ['When specifying a Ghostscript executable (''gscommand'') you should also '...
    +                 'specify both the ''gsfontpath'' and ''gslibpath'' locations']);
    + 
    +        end
    +    end
    +end
    + 
    +%local function to create the input file needed for Ghostscript
    +function gsData = LocalCreateResponseFile(gsData) 
    +   % open a response file to write out Ghostscript commands
    +   rsp_file = [tempname '.rsp'];
    +   rsp_fid = fopen (rsp_file, 'w');
    + 
    +   if (rsp_fid < 0)
    +      error('ps2pdf:responseFileCreate', 'Unable to create response file')
    +   end
    + 
    +   fprintf(rsp_fid, '-dBATCH -dNOPAUSE\n');
    +   if ~gsData.verbose
    +       fprintf(rsp_fid, '-q\n');
    +   end
    +   if isfield(gsData, 'libPath')
    +      fprintf(rsp_fid, '-I"%s"\n', gsData.libPath);
    +   end
    +   if isfield(gsData, 'fontPath')
    +      fprintf(rsp_fid, '-I"%s"\n', gsData.fontPath);
    +   end
    +   if isfield(gsData, 'paperSize') 
    +      fprintf( rsp_fid, '-sPAPERSIZE=%s\n', gsData.paperSize );
    +   end
    +   fprintf(rsp_fid, '-sOutputFile="%s"\n', gsData.pdfFile);
    +   fprintf(rsp_fid, '-sDEVICE=%s\n', 'pdfwrite');
    +   fclose(rsp_fid);
    +   gsData.responseFile = rsp_file;
    +end
    + 
    +%local function to get a handle to MATLAB's Ghostscript implementation
    +%NOTE: this may change or be removed in future releases
    +function gs = Local_GetGscriptFcnHandle()
    +  gs = '';
    +  p = which('-all', 'gscript');
    +  if ~isempty(p) 
    +      p = p{1};
    +      fpath = fileparts(p);
    +      olddir = cd(fpath);
    +      gs = @gscript;
    +      cd(olddir);
    +  end
    +end
    + 
    +% local function to try and get location of Ghostscript in older MATLAB
    +function [gsCmd, ghostDir] = Local_GetOldGhostscript
    +   ghostDir = fullfile( matlabroot, 'sys', 'ghostscript' );
    +   gsCmd = '';
    +   if ispc
    +      if exist(fullfile(ghostDir,'bin','win32','gs.exe'), 'file')
    +         gsCmd = fullfile(ghostDir,'bin','win32','gs.exe');
    +      end
    +   else 
    +      if exist(fullfile(ghostDir,'bin',lower(computer),'gs'), 'file')
    +         gsCmd = fullfile(ghostDir,'bin',lower(computer), 'gs');
    +      end
    +   end
    +   gsCmd = ['"' gsCmd '"'];
    +end
    +
    +
    +
    +
    +

    + Discussion +

    +
    + +
    +
    +
    + + + + + + + + +
    + Enter your comment (wiki syntax is allowed):
    + +
    + + + +
    + +
    +
    +
    +
    +
    + + +
    + +
     
    + + +
    + +
    +
    + Logged in as: Karim N'Diaye (ndiayek)
    +
    + references/batches_spm8.txt · Last modified: 2012/05/04 16:42 by fabi1007
    +
    + + +
    +
    +
    +
    +
      +
    +
    +
    + +
    + +
    Except where otherwise noted, content on this wiki is licensed under the following license:CC Attribution-Noncommercial-Share Alike 3.0 Unported
    +
    + +
    + + Recent changes RSS feed + + + + Donate + + Powered by PHP + + Valid XHTML 1.0 + + Valid CSS + + Driven by DokuWiki + + + +
    + +
    + + + \ No newline at end of file diff --git a/labnic/references_batches_spm8 [WikiNIC]_fichiers/button-css.png b/labnic/references_batches_spm8 [WikiNIC]_fichiers/button-css.png new file mode 100644 index 0000000000000000000000000000000000000000..706325e1c1b6f7f666a2f655b6874be1d3ee7dd4 GIT binary patch literal 299 zcmV+`0o4A9P)X!LnL50z(5q>K~=a4CjcodXPV4VJ#(T~ivj_KbD4HB-<^MP-5Cc03hfj3 zvVX5FJDvqHaRJw=1q>jBMWOVj`<7e(ax^f4+{m?H71t^d!lF?8=k48-=1+3~dxi^Q zC>J(`%>SqV4Uqf_@ioxIRS<-!ka2(L-yrrM3@i>1H!^?`HiflJJ8S1&>*z}$;A;j; xhH}Z<0W~iK2q^@5moW%rGa-e<#Wk_12LM7iB(v#(0(AfY002ovPDHLkV1n}{eY*ev literal 0 HcmV?d00001 diff --git a/labnic/references_batches_spm8 [WikiNIC]_fichiers/button-donate.gif b/labnic/references_batches_spm8 [WikiNIC]_fichiers/button-donate.gif new file mode 100644 index 0000000000000000000000000000000000000000..bba284e214e3ff94657be76a4b399291bc55768d GIT binary patch literal 187 zcmV;s07U;sNk%w1VNd`M0HFc^iH>>y|NmUfD`sY9EC2ui08juA0007%oR6u??GI0w zwAzca-n^S2gyKk+VF0A+%C_zcyTNDHH5%vo&dvj9698>Ep7GeD5huZ-Y_fu;icTqj zGPNdu#GuI8!ilF@vYDy^Z-*vkGd8}S_aHg~6{$8|%|_hJ_)H|Ur} pc(vE&C4_W|>(N06SCBTRH#$ literal 0 HcmV?d00001 diff --git a/labnic/references_batches_spm8 [WikiNIC]_fichiers/button-dw.png b/labnic/references_batches_spm8 [WikiNIC]_fichiers/button-dw.png new file mode 100644 index 0000000000000000000000000000000000000000..39d5f56a9dff668881444dafbec313e2e89f2287 GIT binary patch literal 427 zcmV;c0aX5pP)IWd0001BP)t-sr?8+) zt3FdxQ-hYB|NsB1tVecyW=m^!`0>i-#IRj?gF9Vn^Jrp;+n%V5cKVxk%2zj`Sv0gI z0?f?Js>agB<<9%~=GfWf=JnuzDg)^0&*jnD`QqWh$g~Qf5^jWz%73Zc00009a7bBm z0000;0000;07l7cJ^%m!@kvBMR5;76liiYoAPj|r$%0@-{4HyxdH=U8tlf^TUz$@c76fljnPG5qklU~goqp9uC^FDcDD|h zQEPp&5(JTm7xG>o9{cL7)OftWWA-!gnzI>vYa z;!Q-N0P^(-ublqPGC?8=@L+QPVX|nZ<+jwK{t;|4CuP+H1NOtU79nGj|7Q7QJphRF V9*n(wPYeJ6002ovPDHLkV1k{Cz_0)S literal 0 HcmV?d00001 diff --git a/labnic/references_batches_spm8 [WikiNIC]_fichiers/button-php.gif b/labnic/references_batches_spm8 [WikiNIC]_fichiers/button-php.gif new file mode 100644 index 0000000000000000000000000000000000000000..19aefb08f5755ccddc1a20b5d352d5bfd33c864a GIT binary patch literal 207 zcmV;=05JbYNk%w1VNd`M0HFc^W@ctFWt{*2|A~%yEC2ui08juA00080gpaAq?GKNb zwAzca-n<(TgyKk+p)jQC%C_zcyTWJIH5%vo&dva63yN^OqOnIiO@=2WaJZsGiBy$z zaK%ni*_Q7K3^xN}sFjPxqEXee`kf{sf5W9SY&Uba@iqLK1(@cQL`G8fG$JSWhQsz3 z1?k3URhVdqd3h4qvk0f<7{}K&rzuoId8C^2bt;s)8cRs$Y6B~ai}lgL+w1#_5g|NG JT&z$406VZOU`hZ0 literal 0 HcmV?d00001 diff --git a/labnic/references_batches_spm8 [WikiNIC]_fichiers/button-rss.png b/labnic/references_batches_spm8 [WikiNIC]_fichiers/button-rss.png new file mode 100644 index 0000000000000000000000000000000000000000..b036f7152bd705536c7e3a7eb2d46d8bbb426102 GIT binary patch literal 280 zcmV+z0q6dSP)3;JraZqAwWxb03f2f+qeis z^y|EuW(-CNGQN_eYI4MLP)}Ka@FqLgV#JiyjD8 zF--H&ccYwR4=k2%kLTH2swXN^uQW#|meA=it&-{0K07HEP@$E}8ZrqhGdAj6_}{(e zJlFTPE8!YahGF$Au0&+4Vi2D-k#b5k-K#6hWfB)cAfiUT!-)8Nmj0b}4R39^mF(R! eugA6axAX>X!J?o2lz+_t0000X!LnL50z(5q>K~=a4CjcodXPV4VJ#(T~ivj_KbD4HB-<^MP-5Cc03hfj3 zvVX5FJDvqHk!uy#0w7|5Fo1vof|+yC=<`<^b^w*Qx~!Acb697z&yHPyZVr`4!@8xQQ^UkrXoS5B(d&{)2(V0q#bq zLa_U|xL|H%tYz9+JNH^gUjhMNGgvZ|OWqEsc_BbZA<(;wK_Ht6DI_kgiA_BK@PQUn TFoEJQ00000NkvXXu0mjfr-Fwn literal 0 HcmV?d00001 diff --git a/labnic/references_batches_spm8 [WikiNIC]_fichiers/cc-by-nc-sa.png b/labnic/references_batches_spm8 [WikiNIC]_fichiers/cc-by-nc-sa.png new file mode 100644 index 0000000000000000000000000000000000000000..1c54f994df06341ae0d3f460eda0c55fa0f92801 GIT binary patch literal 686 zcmV;f0#W^mP)Px#24YJ`L;$M*u>h(JE6XVW000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iOV> z5GxDMTNG*l00J&aL_t(Y$K_YQZxb;Ten#DBhD(NYtq(?EaFjow+^v8F+(4vmwZ+y- zD5qp6@db$+fZ=A4f&!HaBY}S)iiZZ#P*FJ(DwSv~(J~){rQ_85k#-~G=?0(uefInO z?Dy^vr4$y)mH;3Kf(22_7+cb;r4|7C{eHDteda%(#~`N&?!J0mDwVR-YU7;mf8F2N ze)m5J9_@}8V*nt8FijI7WZSk<%C>EUkZGDi2ttTuS$kjha?X!_;>Ri3PVJ1dQo%W^ za@CB~&UaZVuaq|)ZZsN=cDtR*S-ZJ59*;L4ZO-NAC8ac67^9S;Mf+R&0|N03 UY>){zJpcdz07*qoM6N<$f&;NFn*aa+ literal 0 HcmV?d00001 diff --git a/labnic/references_batches_spm8 [WikiNIC]_fichiers/css.css b/labnic/references_batches_spm8 [WikiNIC]_fichiers/css.css new file mode 100644 index 0000000..79aaee2 --- /dev/null +++ b/labnic/references_batches_spm8 [WikiNIC]_fichiers/css.css @@ -0,0 +1 @@ +a.interwiki{background:transparent url(/wiki/lib/images/interwiki.png) 0px 1px no-repeat;padding-left:16px;}a.iw_wp{background-image:url(/wiki/lib/images/interwiki/wp.gif)}a.iw_wpfr{background-image:url(/wiki/lib/images/interwiki/wpfr.gif)}a.iw_wpde{background-image:url(/wiki/lib/images/interwiki/wpde.gif)}a.iw_wpes{background-image:url(/wiki/lib/images/interwiki/wpes.gif)}a.iw_wppl{background-image:url(/wiki/lib/images/interwiki/wppl.gif)}a.iw_wpjp{background-image:url(/wiki/lib/images/interwiki/wpjp.gif)}a.iw_wpmeta{background-image:url(/wiki/lib/images/interwiki/wpmeta.gif)}a.iw_doku{background-image:url(/wiki/lib/images/interwiki/doku.gif)}a.iw_dokubug{background-image:url(/wiki/lib/images/interwiki/dokubug.gif)}a.iw_amazon{background-image:url(/wiki/lib/images/interwiki/amazon.gif)}a.iw_amazon_de{background-image:url(/wiki/lib/images/interwiki/amazon.de.gif)}a.iw_amazon_uk{background-image:url(/wiki/lib/images/interwiki/amazon.uk.gif)}a.iw_phpfn{background-image:url(/wiki/lib/images/interwiki/phpfn.gif)}a.iw_coral{background-image:url(/wiki/lib/images/interwiki/coral.gif)}a.iw_sb{background-image:url(/wiki/lib/images/interwiki/sb.gif)}a.iw_google{background-image:url(/wiki/lib/images/interwiki/google.gif)}a.iw_meatball{background-image:url(/wiki/lib/images/interwiki/meatball.gif)}a.iw_wiki{background-image:url(/wiki/lib/images/interwiki/wiki.gif)}a.mediafile{background:transparent url(/wiki/lib/images/fileicons/file.png) 0px 1px no-repeat;padding-left:18px;padding-bottom:1px;}a.mf_deb{background-image:url(/wiki/lib/images/fileicons/deb.png)}a.mf_rar{background-image:url(/wiki/lib/images/fileicons/rar.png)}a.mf_cpp{background-image:url(/wiki/lib/images/fileicons/cpp.png)}a.mf_jpg{background-image:url(/wiki/lib/images/fileicons/jpg.png)}a.mf_xls{background-image:url(/wiki/lib/images/fileicons/xls.png)}a.mf_xml{background-image:url(/wiki/lib/images/fileicons/xml.png)}a.mf_css{background-image:url(/wiki/lib/images/fileicons/css.png)}a.mf_csv{background-image:url(/wiki/lib/images/fileicons/csv.png)}a.mf_c{background-image:url(/wiki/lib/images/fileicons/c.png)}a.mf_doc{background-image:url(/wiki/lib/images/fileicons/doc.png)}a.mf_html{background-image:url(/wiki/lib/images/fileicons/html.png)}a.mf_rpm{background-image:url(/wiki/lib/images/fileicons/rpm.png)}a.mf_rtf{background-image:url(/wiki/lib/images/fileicons/rtf.png)}a.mf_zip{background-image:url(/wiki/lib/images/fileicons/zip.png)}a.mf_tar{background-image:url(/wiki/lib/images/fileicons/tar.png)}a.mf_audio{background-image:url(/wiki/lib/images/fileicons/audio.png)}a.mf_sql{background-image:url(/wiki/lib/images/fileicons/sql.png)}a.mf_lua{background-image:url(/wiki/lib/images/fileicons/lua.png)}a.mf_tgz{background-image:url(/wiki/lib/images/fileicons/tgz.png)}a.mf_mp3{background-image:url(/wiki/lib/images/fileicons/mp3.png)}a.mf_cs{background-image:url(/wiki/lib/images/fileicons/cs.png)}a.mf_swf{background-image:url(/wiki/lib/images/fileicons/swf.png)}a.mf_conf{background-image:url(/wiki/lib/images/fileicons/conf.png)}a.mf_sxc{background-image:url(/wiki/lib/images/fileicons/sxc.png)}a.mf_sxd{background-image:url(/wiki/lib/images/fileicons/sxd.png)}a.mf_sxi{background-image:url(/wiki/lib/images/fileicons/sxi.png)}a.mf_sxw{background-image:url(/wiki/lib/images/fileicons/sxw.png)}a.mf_gz{background-image:url(/wiki/lib/images/fileicons/gz.png)}a.mf_gif{background-image:url(/wiki/lib/images/fileicons/gif.png)}a.mf_js{background-image:url(/wiki/lib/images/fileicons/js.png)}a.mf_pptx{background-image:url(/wiki/lib/images/fileicons/pptx.png)}a.mf_pl{background-image:url(/wiki/lib/images/fileicons/pl.png)}a.mf_odc{background-image:url(/wiki/lib/images/fileicons/odc.png)}a.mf_odf{background-image:url(/wiki/lib/images/fileicons/odf.png)}a.mf_odg{background-image:url(/wiki/lib/images/fileicons/odg.png)}a.mf_odi{background-image:url(/wiki/lib/images/fileicons/odi.png)}a.mf_ps{background-image:url(/wiki/lib/images/fileicons/ps.png)}a.mf_rb{background-image:url(/wiki/lib/images/fileicons/rb.png)}a.mf_py{background-image:url(/wiki/lib/images/fileicons/py.png)}a.mf_odp{background-image:url(/wiki/lib/images/fileicons/odp.png)}a.mf_ods{background-image:url(/wiki/lib/images/fileicons/ods.png)}a.mf_txt{background-image:url(/wiki/lib/images/fileicons/txt.png)}a.mf_odt{background-image:url(/wiki/lib/images/fileicons/odt.png)}a.mf_ogg{background-image:url(/wiki/lib/images/fileicons/ogg.png)}a.mf_jpeg{background-image:url(/wiki/lib/images/fileicons/jpeg.png)}a.mf_java{background-image:url(/wiki/lib/images/fileicons/java.png)}a.mf_xlsx{background-image:url(/wiki/lib/images/fileicons/xlsx.png)}a.mf_wav{background-image:url(/wiki/lib/images/fileicons/wav.png)}a.mf_pdf{background-image:url(/wiki/lib/images/fileicons/pdf.png)}a.mf_htm{background-image:url(/wiki/lib/images/fileicons/htm.png)}a.mf_php{background-image:url(/wiki/lib/images/fileicons/php.png)}a.mf_7z{background-image:url(/wiki/lib/images/fileicons/7z.png)}a.mf_png{background-image:url(/wiki/lib/images/fileicons/png.png)}a.mf_docx{background-image:url(/wiki/lib/images/fileicons/docx.png)}a.mf_ppt{background-image:url(/wiki/lib/images/fileicons/ppt.png)}a.mf_bz2{background-image:url(/wiki/lib/images/fileicons/bz2.png)}div.clearer{clear:both;line-height:0;height:0;overflow:hidden;}div.no{display:inline;margin:0;padding:0;}.hidden{display:none;}div.error{background:#fcc url(/wiki/lib/styles/../images/error.png) 0.5em 0px no-repeat;color:#000;border-bottom:1px solid #faa;font-size:90%;margin:0;padding-left:3em;overflow:hidden;}div.info{background:#ccf url(/wiki/lib/styles/../images/info.png) 0.5em 0px no-repeat;color:#000;border-bottom:1px solid #aaf;font-size:90%;margin:0;padding-left:3em;overflow:hidden;}div.success{background:#cfc url(/wiki/lib/styles/../images/success.png) 0.5em 0px no-repeat;color:#000;border-bottom:1px solid #afa;font-size:90%;margin:0;padding-left:3em;overflow:hidden;}div.notify{background:#ffc url(/wiki/lib/styles/../images/notify.png) 0.5em 0px no-repeat;color:#000;border-bottom:1px solid #ffa;font-size:90%;margin:0;padding-left:3em;overflow:hidden;}.medialeft{float:left;}.mediaright{float:right;}.mediacenter{display:block;margin-left:auto;margin-right:auto;}.leftalign{text-align:left;}.centeralign{text-align:center;}.rightalign{text-align:right;}em.u{font-style:normal;text-decoration:underline;}em em.u{font-style:italic;}.code .br0{color:#6c6;}.code .co0{color:#808080;font-style:italic;}.code .co1{color:#808080;font-style:italic;}.code .co2{color:#808080;font-style:italic;}.code .co3{color:#808080;}.code .coMULTI{color:#808080;font-style:italic;}.code .es0{color:#009;font-weight:bold;}.code .kw1{color:#b1b100;}.code .kw2{color:#000;font-weight:bold;}.code .kw3{color:#006;}.code .kw4{color:#933;}.code .kw5{color:#00f;}.code .me1{color:#060;}.code .me2{color:#060;}.code .nu0{color:#c6c;}.code .re0{color:#00f;}.code .re1{color:#00f;}.code .re2{color:#00f;}.code .re3{color:#f33;font-weight:bold;}.code .re4{color:#099;}.code .st0{color:#f00;}.code .sy0{color:#6c6;}div#acl_manager div#acl__tree{font-size:90%;width:25%;height:300px;float:left;overflow:auto;border:1px solid #8cacbb;text-align:left;}div#acl_manager div#acl__tree a.cur{background-color:#ff9;font-weight:bold;}div#acl_manager div#acl__tree ul{list-style-type:none;margin:0;padding:0;}div#acl_manager div#acl__tree li{padding-left:1em;}div#acl_manager div#acl__tree ul img{margin-right:0.25em;cursor:pointer;}div#acl_manager div#acl__detail{width:73%;height:300px;float:right;overflow:auto;}div#acl_manager div#acl__detail fieldset{width:90%;}div#acl_manager div#acl__detail div#acl__user{border:1px solid #8cacbb;padding:0.5em;margin-bottom:0.6em;}div#acl_manager table.inline{width:100%;margin:0;}div#acl_manager .aclgroup{background:transparent url(/wiki/lib/plugins/acl/pix/group.png) 0px 1px no-repeat;padding:1px 0px 1px 18px;}div#acl_manager .acluser{background:transparent url(/wiki/lib/plugins/acl/pix/user.png) 0px 1px no-repeat;padding:1px 0px 1px 18px;}div#acl_manager .aclpage{background:transparent url(/wiki/lib/plugins/acl/pix/page.png) 0px 1px no-repeat;padding:1px 0px 1px 18px;}div#acl_manager .aclns{background:transparent url(/wiki/lib/plugins/acl/pix/ns.png) 0px 1px no-repeat;padding:1px 0px 1px 18px;}div#acl_manager label.disabled{color:#666!important;}#acl_manager label{text-align:left;font-weight:normal;display:inline;}#acl_manager table{margin-left:10%;width:80%;}#acl_manager table tr{background-color:inherit;}#acl_manager table tr:hover{background-color:#dee7ec;}div.dokuwiki div.comment_wrapper{background-color:#dee7ec;margin:1em -1em;padding:0.5em 1em;clear:both;}div.dokuwiki div.comment_form{margin-top:2em;clear:both;}div.dokuwiki #discussion__comment_form label input.edit{width:75%;}div.dokuwiki .comment_head{font-size:80%;color:#666;padding-top:0.5em;margin-top:1em;clear:both;}div.dokuwiki .comment_head abbr{border-bottom:0;}div.dokuwiki .comment_head span.author{background:transparent url(/wiki/lib/plugins/discussion/images/user.gif) 0px 1px no-repeat;padding:1px 0px 1px 16px;}div.dokuwiki .comment_head abbr.published{background:transparent url(/wiki/lib/plugins/discussion/images/date.gif) 0px 1px no-repeat;padding:1px 0px 1px 16px;}div.dokuwiki .comment_body{padding-top:0.5em;padding-bottom:0.5em;border-bottom:1px dotted #8cacbb;}div.dokuwiki div.comment_replies{}div.dokuwiki div.comment_subscribe{float:left;display:inline;padding:0.6em;}div.dokuwiki div.comment_subscribe input{float:left;display:inline;}div.dokuwiki div.comment_subscribe label{float:left;padding-left:0.8em;}div.dokuwiki input.comment_submit{float:left;display:inline;}div.dokuwiki div.comment_hidden{opacity:0.5;}div.dokuwiki div.comment_buttons{float:right;font-size:10px;cursor:pointer;margin-top:-21px;padding-bottom:1em;}div.dokuwiki div.comment_buttons input.button{border:1px solid #8cacbb;color:#000;background-color:#fff;vertical-align:middle;text-decoration:none;padding:0;margin:0 0 0 0.5em;}div.dokuwiki div.newthread_form{clear:both;text-align:center;margin-bottom:1em;}div.dokuwiki #discussion__newthread_form input.edit{width:95%;}div.dokuwiki ul.admin_discussion li.hidden{display:list-item;opacity:0.5;}div.dokuwiki ul.admin_discussion span.abstract{color:#666;}#config__manager div.success,#config__manager div.error,#config__manager div.info{background-position:0.5em;padding:0.5em;text-align:center;}#config__manager fieldset{margin:1em;width:auto;margin-bottom:2em;background-color:#dee7ec;color:#000;padding:0 1em;}#config__manager legend{font-size:1.25em;}#config__manager form{}#config__manager table{margin:1em 0;width:100%;}#config__manager fieldset td{text-align:left;}#config__manager fieldset td.value{width:31em;}#config__manager td.label{padding:0.8em 0 0.6em 1em;vertical-align:top;}#config__manager td.label label{clear:left;display:block;}#config__manager td.label img{padding:0 10px;vertical-align:middle;float:right;}#config__manager td.label span.outkey{font-size:70%;margin-top:-1.7em;margin-left:-1em;display:block;background-color:#fff;color:#666;float:left;padding:0 0.1em;position:relative;z-index:1;}#config__manager td input.edit{width:30em;}#config__manager td .input{width:30.8em;}#config__manager td select.edit{}#config__manager td textarea.edit{width:27.5em;height:4em;}#config__manager tr .input,#config__manager tr input,#config__manager tr textarea,#config__manager tr select{background-color:#fff;color:#000;}#config__manager tr.default .input,#config__manager tr.default input,#config__manager tr.default textarea,#config__manager tr.default select,#config__manager .selectiondefault{background-color:#cdf;color:#000;}#config__manager tr.protected .input,#config__manager tr.protected input,#config__manager tr.protected textarea,#config__manager tr.protected select,#config__manager tr.protected .selection{background-color:#fcc!important;color:#000 !important;}#config__manager td.error{background-color:red;color:#000;}#config__manager .selection{width:14.8em;float:left;margin:0 0.3em 2px 0;}#config__manager .selection label{float:right;width:14em;font-size:90%;}* html #config__manager .selection label{padding-top:2px;}#config__manager .selection input.checkbox{padding-left:0.7em;}#config__manager .other{clear:both;padding-top:0.5em;}#config__manager .other label{padding-left:2px;font-size:90%;}.authorstats-table{border-collapse:collapse;border:1px solid #8CACBB;}.authorstats-table td{padding:10px;text-align:center;border:1px solid #8CACBB;}.authorstats-table th{background-color:#DEE7EC;color:black;border:1px solid #8CACBB;padding:2px 15px 2px 15px;}.folder{padding-left:2px;padding-right:9px;background:url(/wiki/lib/plugins/folded/closed.gif) no-repeat right center;}.folder.open{background:url(/wiki/lib/plugins/folded/open.gif) no-repeat right center;}div.folded{padding:0.5em;border:1px dotted #dee7ec;}span.folded{border:1px dotted #dee7ec;}span.indicator{visibility:hidden;}#user__manager tr.disabled{color:#6f6f6f;background:#e4e4e4;}#user__manager tr.user_info{vertical-align:top;}#user__manager div.edit_user{width:46%;float:left;}#user__manager table{margin-bottom:1em;}#user__manager input.button[disabled]{color:#ccc!important;border-color:#ccc!important;}#plugin__manager h2{margin-left:0;}#plugin__manager form{display:block;margin:0;padding:0;}#plugin__manager legend{display:none;}#plugin__manager fieldset{width:auto;}#plugin__manager .button{margin:0;}#plugin__manager p,#plugin__manager label{text-align:left;}#plugin__manager .hidden{display:none;}#plugin__manager .new{background:#dee7ec;}#plugin__manager input[disabled]{color:#ccc;border-color:#ccc;}#plugin__manager .pm_menu,#plugin__manager .pm_info{margin-left:0;text-align:left;}#plugin__manager .pm_menu{float:left;width:48%;}#plugin__manager .pm_info{float:right;width:50%;}#plugin__manager .common fieldset{margin:0;padding:0 0 1.0em 0;text-align:left;border:none;}#plugin__manager .common label{padding:0 0 0.5em 0;}#plugin__manager .common input.edit{width:24em;margin:0.5em;}#plugin__manager .plugins fieldset{color:#000;background:#fff;text-align:right;border-top:none;border-right:none;border-left:none;}#plugin__manager .plugins fieldset.protected{background:#fdd;color:#000;}#plugin__manager .plugins fieldset.disabled{background:#e0e0e0;color:#a8a8a8;}#plugin__manager .plugins .legend{color:#000;background:inherit;display:block;margin:0;padding:0;font-size:1em;line-height:1.4em;font-weight:normal;text-align:left;float:left;padding:0;clear:none;}#plugin__manager .plugins .button{font-size:95%;}#plugin__manager .plugins fieldset.buttons{border:none;}#plugin__manager .plugins fieldset.buttons .button{float:left;}#plugin__manager .pm_info h3{margin-left:0;}#plugin__manager .pm_info dl{margin:1em 0;padding:0;}#plugin__manager .pm_info dt{width:6em;float:left;clear:left;margin:0;padding:0;}#plugin__manager .pm_info dd{margin:0 0 0 7em;padding:0;background:none;}#plugin__manager .plugins .enable{float:left;width:auto;margin-right:0.5em;}div.dokuwiki div.include div.secedit{float:right;margin-left:1em;margin-top:-18px;}div.dokuwiki div.inclmeta{border-top:1px dotted #8cacbb;padding-top:0.2em;color:#666;font-size:80%;line-height:1.25;margin-top:0.5em;margin-bottom:2em;}div.dokuwiki div.inclmeta a.permalink{background:transparent url(/wiki/lib/plugins/include/images/link.gif) 0px 1px no-repeat;padding:1px 0px 1px 16px;}div.dokuwiki div.inclmeta abbr.published{background:transparent url(/wiki/lib/plugins/include/images/date.gif) 0px 1px no-repeat;padding:1px 0px 1px 16px;border-bottom:0;}div.dokuwiki div.inclmeta span.author{background:transparent url(/wiki/lib/plugins/include/images/user.gif) 0px 1px no-repeat;padding:1px 0px 1px 16px;}div.dokuwiki div.inclmeta span.comment{background:transparent url(/wiki/lib/plugins/include/images/comment.gif) 0px 1px no-repeat;padding:1px 0px 1px 16px;}div.dokuwiki div.inclmeta div.tags{border-top:0;font-size:100%;float:right;clear:none;}div.dokuwiki .header{padding:3px 0 0 2px;}div.dokuwiki .pagename{float:left;font-size:200%;font-weight:bolder;color:#dee7ec;text-align:left;vertical-align:middle;}div.dokuwiki .pagename a{color:#436976 !important;text-decoration:none !important;}div.dokuwiki .logo{float:right;font-size:220%;font-weight:bolder;text-align:right;vertical-align:middle;}div.dokuwiki .logo a{color:#dee7ec !important;text-decoration:none !important;font-variant:small-caps;letter-spacing:2pt;}div.dokuwiki .bar{border-top:1px solid #8cacbb;border-bottom:1px solid #8cacbb;background:#dee7ec;padding:0.1em 0.15em;clear:both;}div.dokuwiki .bar-left{float:left;}div.dokuwiki .bar-right{float:right;text-align:right;}div.dokuwiki #bar__bottom{margin-bottom:3px;}div.dokuwiki div.meta{clear:both;margin-top:1em;color:#638c9c;font-size:70%;}div.dokuwiki div.meta div.user{float:left;}div.dokuwiki div.meta div.doc{text-align:right;}*{padding:0;margin:0;}body{font:80% "Lucida Grande",Verdana,Lucida,Helvetica,Arial,sans-serif;background-color:#fff;color:#000;}div.dokuwiki div.page{margin:4px 2em 0 1em;text-align:justify;}div.dokuwiki table{font-size:100%;}div.dokuwiki tr,div.dokuwiki td,div.dokuwiki th{}div.dokuwiki img{border:0;}div.dokuwiki p,div.dokuwiki blockquote,div.dokuwiki table,div.dokuwiki pre{margin:0 0 1.0em 0;}div.dokuwiki hr{border:0px;border-top:1px solid #8cacbb;text-align:center;height:0px;}div.dokuwiki div.nothing{text-align:center;margin:2em;}div.dokuwiki form{border:none;display:inline;}div.dokuwiki label.block{display:block;text-align:right;font-weight:bold;}div.dokuwiki label.simple{display:block;text-align:left;font-weight:normal;}div.dokuwiki label.block input.edit{width:50%;}div.dokuwiki fieldset{width:300px;text-align:center;border:1px solid #8cacbb;padding:0.5em;margin:auto;}div.dokuwiki textarea.edit{font-family:monospace;font-size:14px;color:#000;background-color:#fff;border:1px solid #8cacbb;padding:0.3em 0 0 0.3em;width:700px;min-width:100%;max-width:100%;}html>body div.dokuwiki textarea.edit{background:#fff url(/wiki/lib/tpl/default/images/inputshadow.png) repeat-x top;}div.dokuwiki input.edit,div.dokuwiki select.edit{font-size:100%;border:1px solid #8cacbb;color:#000;background-color:#fff;vertical-align:middle;margin:1px;padding:0.20em 0.3em;display:inline;}html>body div.dokuwiki input.edit,html>body div.dokuwiki select.edit{background:#fff url(/wiki/lib/tpl/default/images/inputshadow.png) repeat-x top;}div.dokuwiki select.edit{padding:0.1em 0;}div.dokuwiki input.missing{font-size:100%;border:1px solid #8cacbb;color:#000;background-color:#fcc;vertical-align:middle;margin:1px;padding:0.20em 0.3em;display:inline;}div.dokuwiki textarea.edit[disabled],div.dokuwiki textarea.edit[readonly],div.dokuwiki input.edit[disabled],div.dokuwiki input.edit[readonly],div.dokuwiki input.button[disabled],div.dokuwiki select.edit[disabled]{background-color:#f5f5f5!important;color:#666!important;}div.dokuwiki div.toolbar,div.dokuwiki div#wiki__editbar{margin:2px 0;text-align:left;}div.dokuwiki div#size__ctl{float:right;width:60px;height:2.7em;}div.dokuwiki #size__ctl img{cursor:pointer;}div.dokuwiki div#wiki__editbar div.editButtons{float:left;padding:0 1.0em 0.7em 0;}div.dokuwiki div#wiki__editbar div.summary{float:left;}div.dokuwiki .nowrap{white-space:nowrap;}div.dokuwiki div#draft__status{float:right;color:#638c9c;}div.dokuwiki div.license{padding:0.5em;font-size:90%;text-align:center;}div.dokuwiki form#dw__editform div.license{clear:left;font-size:90%;}div.dokuwiki input.button,div.dokuwiki button.button{border:1px solid #8cacbb;color:#000;background-color:#fff;vertical-align:middle;text-decoration:none;font-size:100%;cursor:pointer;margin:1px;padding:0.125em 0.4em;}html>body div.dokuwiki input.button,html>body div.dokuwiki button.button{background:#fff url(/wiki/lib/tpl/default/images/buttonshadow.png) repeat-x bottom;}* html div.dokuwiki input.button,* html div.dokuwiki button.button{height:1.8em;}div.dokuwiki div.secedit input.button{border:1px solid #8cacbb;color:#000;background-color:#fff;vertical-align:middle;text-decoration:none;margin:0;padding:0;font-size:10px;cursor:pointer;float:right;display:inline;}div.dokuwiki div.pagenav{margin:1em 0 0 0;}div.dokuwiki div.pagenav-prev{text-align:right;float:left;width:49%}div.dokuwiki div.pagenav-next{text-align:left;float:right;width:49%}div.dokuwiki a:link,div.dokuwiki a:visited{color:#436976;text-decoration:none;}div.dokuwiki a:hover,div.dokuwiki a:active{color:#000;text-decoration:underline;}div.dokuwiki h1 a,div.dokuwiki h2 a,div.dokuwiki h3 a,div.dokuwiki h4 a,div.dokuwiki h5 a,div.dokuwiki a.nolink{color:#000 !important;text-decoration:none !important;}div.dokuwiki a.urlextern{background:transparent url(/wiki/lib/tpl/default/images/link_icon.gif) 0px 1px no-repeat;padding:1px 0px 1px 16px;}div.dokuwiki a.windows{background:transparent url(/wiki/lib/tpl/default/images/windows.gif) 0px 1px no-repeat;padding:1px 0px 1px 16px;}div.dokuwiki a.interwiki{}div.dokuwiki a.media{}div.dokuwiki a.urlextern:link,div.dokuwiki a.windows:link,div.dokuwiki a.interwiki:link{color:#436976;}div.dokuwiki a.urlextern:visited,div.dokuwiki a.windows:visited,div.dokuwiki a.interwiki:visited{color:purple;}div.dokuwiki a.urlextern:hover,div.dokuwiki a.urlextern:active,div.dokuwiki a.windows:hover,div.dokuwiki a.windows:active,div.dokuwiki a.interwiki:hover,div.dokuwiki a.interwiki:active{color:#000;}div.dokuwiki a.mail{background:transparent url(/wiki/lib/tpl/default/images/mail_icon.gif) 0px 1px no-repeat;padding:1px 0px 1px 16px;}div.dokuwiki a.wikilink1{color:#090 !important;}div.dokuwiki a.wikilink2{color:#f30 !important;text-decoration:none !important;border-bottom:dashed 1px #f30 !important;}div.dokuwiki div.preview{background-color:#f5f5f5;margin:0 0 0 2em;padding:4px;border:1px dashed #000;}div.dokuwiki div.breadcrumbs{background-color:#f5f5f5;color:#666;font-size:80%;padding:0 0 0 4px;}div.dokuwiki span.user{color:#ccc;font-size:90%;}div.dokuwiki li.minor{color:#666;font-style:italic;}div.dokuwiki img.media{margin:3px;}div.dokuwiki img.medialeft{border:0;float:left;margin:0 1.5em 0 0;}div.dokuwiki img.mediaright{border:0;float:right;margin:0 0 0 1.5em;}div.dokuwiki img.mediacenter{border:0;display:block;margin:0 auto;}div.dokuwiki img.middle{vertical-align:middle;}div.dokuwiki acronym{cursor:help;border-bottom:1px dotted #000;}div.dokuwiki h1,div.dokuwiki h2,div.dokuwiki h3,div.dokuwiki h4,div.dokuwiki h5{color:#000;background-color:inherit;font-size:100%;font-weight:normal;margin:0 0 1em 0;padding:0.5em 0 0 0;border-bottom:1px solid #8cacbb;clear:left;}div.dokuwiki h1{font-size:160%;margin-left:0px;font-weight:bold;}div.dokuwiki h2{font-size:150%;margin-left:20px;}div.dokuwiki h3{font-size:140%;margin-left:40px;border-bottom:none;font-weight:bold;}div.dokuwiki h4{font-size:120%;margin-left:60px;border-bottom:none;font-weight:bold;}div.dokuwiki h5{font-size:100%;margin-left:80px;border-bottom:none;font-weight:bold;}div.dokuwiki div.level1{margin-left:3px;}div.dokuwiki div.level2{margin-left:23px;}div.dokuwiki div.level3{margin-left:43px;}div.dokuwiki div.level4{margin-left:63px;}div.dokuwiki div.level5{margin-left:83px;}div.dokuwiki ul{line-height:1.5em;list-style-type:square;list-style-image:none;margin:0 0 1em 3.5em;color:#638c9c;}div.dokuwiki ol{line-height:1.5em;list-style-image:none;margin:0 0 1em 3.5em;color:#638c9c;font-weight:bold;}div.dokuwiki li ul,div.dokuwiki li ol{margin:0 0 0 1.5em;}div.dokuwiki .li{color:#000;font-weight:normal;}div.dokuwiki ol{list-style-type:decimal;}div.dokuwiki ol ol{list-style-type:upper-roman;}div.dokuwiki ol ol ol{list-style-type:lower-alpha;}div.dokuwiki ol ol ol ol{list-style-type:lower-greek;}div.dokuwiki li.open{list-style-image:url(/wiki/lib/tpl/default/images/open.gif);}div.dokuwiki li.closed{list-style-image:url(/wiki/lib/tpl/default/images/closed.gif);}div.dokuwiki blockquote{border-left:2px solid #8cacbb;padding-left:3px;}div.dokuwiki pre{font-family:monospace;font-size:120%;padding:0.5em;border:1px dashed #8cacbb;color:#000;overflow:auto;}div.dokuwiki pre.pre{background-color:#f7f9fa;}div.dokuwiki pre.code{background-color:#f7f9fa;}div.dokuwiki pre.file{background-color:#dee7ec;}div.dokuwiki dl.file,div.dokuwiki dl.code{margin-top:2em;margin-bottom:2.5em;}div.dokuwiki dl.file dt,div.dokuwiki dl.code dt{border:1px dashed #8cacbb;display:inline;padding:0.1em 1em;margin-left:2em;}div.dokuwiki dl.code dt a,div.dokuwiki dl.file dt a{color:#000;}div.dokuwiki dl.code dt{background-color:#f7f9fa;border-bottom:1px solid #f7f9fa;}div.dokuwiki dl.file dt{background-color:#dee7ec;border-bottom:1px solid #dee7ec;}div.dokuwiki code{font-size:120%;}div.dokuwiki table.inline{background-color:#fff;border-spacing:0px;border-collapse:collapse;}div.dokuwiki table.inline th{padding:3px;border:1px solid #8cacbb;background-color:#dee7ec;}div.dokuwiki table.inline td{padding:3px;border:1px solid #8cacbb;}div.dokuwiki div.toc{margin:1.2em 0 0 2em;float:right;width:200px;font-size:80%;clear:both;}div.dokuwiki div.tocheader{border:1px solid #8cacbb;background-color:#dee7ec;text-align:left;font-weight:bold;padding:3px;margin-bottom:2px;}div.dokuwiki span.toc_open,div.dokuwiki span.toc_close{border:0.4em solid #dee7ec;float:right;display:block;margin:0.4em 3px 0 0;}div.dokuwiki span.toc_open span,div.dokuwiki span.toc_close span{display:none;}div.dokuwiki span.toc_open{margin-top:0.4em;border-top:0.4em solid #000;}div.dokuwiki span.toc_close{margin-top:0;border-bottom:0.4em solid #000;}div.dokuwiki #toc__inside{border:1px solid #8cacbb;background-color:#fff;text-align:left;padding:0.5em 0 0.7em 0;}div.dokuwiki ul.toc{list-style-type:none;list-style-image:none;line-height:1.2em;padding-left:1em;margin:0;}div.dokuwiki ul.toc li{background:transparent url(/wiki/lib/tpl/default/images/tocdot2.gif) 0 0.6em no-repeat;padding-left:0.4em;}div.dokuwiki ul.toc li.clear{background-image:none;padding-left:0.4em;}div.dokuwiki a.toc:link,div.dokuwiki a.toc:visited{color:#436976;}div.dokuwiki a.toc:hover,div.dokuwiki a.toc:active{color:#000;}div.dokuwiki table.diff{background-color:#fff;width:100%;}div.dokuwiki td.diff-blockheader{font-weight:bold;}div.dokuwiki table.diff th{border-bottom:1px solid #8cacbb;font-size:110%;width:50%;font-weight:normal;text-align:left;}div.dokuwiki table.diff th a{font-weight:bold;}div.dokuwiki table.diff th span.user{color:#000;font-size:80%;}div.dokuwiki table.diff th span.sum{font-size:80%;font-weight:bold;}div.dokuwiki table.diff th.minor{font-style:italic;}div.dokuwiki table.diff td{font-family:monospace;font-size:100%;}div.dokuwiki td.diff-addedline{background-color:#dfd;}div.dokuwiki td.diff-deletedline{background-color:#ffb;}div.dokuwiki td.diff-context{background-color:#f5f5f5;}div.dokuwiki table.diff td.diff-addedline strong,div.dokuwiki table.diff td.diff-deletedline strong{color:red;}div.dokuwiki div.footnotes{clear:both;border-top:1px solid #8cacbb;padding-left:1em;margin-top:1em;}div.dokuwiki div.fn{font-size:90%;}div.dokuwiki a.fn_bot{font-weight:bold;}div.insitu-footnote{font-size:80%;line-height:1.2em;border:1px solid #8cacbb;background-color:#f7f9fa;text-align:left;padding:4px;max-width:40%;min-width:5em;}* html .insitu-footnote pre.code,* html .insitu-footnote pre.file{padding-bottom:18px;}div.dokuwiki .search_result{margin-bottom:6px;padding:0 10px 0 30px;}div.dokuwiki .search_snippet{color:#ccc;font-size:12px;margin-left:20px;}div.dokuwiki .search_sep{color:#000;}div.dokuwiki .search_hit{color:#000;background-color:#ff9;}div.dokuwiki strong.search_hit{font-weight:normal;}div.dokuwiki div.search_quickresult{margin:0 0 15px 30px;padding:0 10px 10px 0;border-bottom:1px dashed #8cacbb;}div.dokuwiki div.search_quickresult h3{margin:0 0 1.0em 0;font-size:1em;font-weight:bold;}div.dokuwiki ul.search_quickhits{margin:0 0 0.5em 1.0em;}div.dokuwiki ul.search_quickhits li{margin:0 1.0em 0 1.0em;float:left;width:30%;}div.dokuwiki .section_highlight{background-color:#dee7ec !important;}div.footerinc{text-align:center;}.footerinc a img{opacity:0.5;border:0;}.footerinc a:hover img{opacity:1;}div.dokuwiki div.ajax_qsearch{position:absolute;right:237px;;width:200px;opacity:0.9;display:none;font-size:80%;line-height:1.2em;border:1px solid #8cacbb;background-color:#f7f9fa;text-align:left;padding:4px;}button.toolbutton{background-color:#fff;padding:0px;margin:0 1px 0 0;border:1px solid #8cacbb;cursor:pointer;}html>body button.toolbutton{background:#fff url(/wiki/lib/tpl/default/images/buttonshadow.png) repeat-x bottom;}div.picker{width:250px;border:1px solid #8cacbb;background-color:#dee7ec;}div.pk_hl{width:125px;}button.pickerbutton{padding:0px;margin:0 1px 1px 0;border:0;background-color:transparent;font-size:80%;cursor:pointer;}div.dokuwiki div.img_big{float:left;margin-right:0.5em;}div.dokuwiki dl.img_tags dt{font-weight:bold;background-color:#dee7ec;}div.dokuwiki dl.img_tags dd{background-color:#f5f5f5;}div.dokuwiki div.imagemeta{color:#666;font-size:70%;line-height:95%;}div.dokuwiki div.imagemeta img.thumb{float:left;margin-right:0.1em;}#media__manager{height:100%;overflow:hidden;}#media__left{width:30%;border-right:solid 1px #8cacbb;height:100%;overflow:auto;position:absolute;left:0;}#media__right{width:69.7%;height:100%;overflow:auto;position:absolute;right:0;}#media__manager h1{margin:0;padding:0;margin-bottom:0.5em;}#media__tree img{float:left;padding:0.5em 0.3em 0 0;}#media__tree ul{list-style-type:none;list-style-image:none;margin-left:1.5em;}#media__tree li{clear:left;list-style-type:none;list-style-image:none;}*+html #media__tree li,* html #media__tree li{border:1px solid #fff;}#media__opts{padding-left:1em;margin-bottom:0.5em;}#media__opts input{float:left;display:block;margin-top:4px;position:absolute;}*+html #media__opts input,* html #media__opts input{position:static;}#media__opts label{display:block;float:left;margin-left:20px;margin-bottom:4px;}*+html #media__opts label,* html #media__opts label{margin-left:10px;}#media__opts br{clear:left;}#media__content img.load{margin:1em auto;}#media__content #scroll__here{border:1px dashed #8cacbb;}#media__content .odd{background-color:#f7f9fa;padding:0.4em;}#media__content .even{padding:0.4em;}#media__content a.mediafile{margin-right:1.5em;font-weight:bold;}#media__content div.detail{padding:0.3em 0 0.3em 2em;}#media__content div.detail div.thumb{float:left;width:130px;text-align:center;margin-right:0.4em;}#media__content img.btn{vertical-align:text-bottom;}#media__content div.example{color:#666;margin-left:1em;}#media__content div.upload{font-size:90%;padding:0 0.5em 0.5em 0.5em;}#media__content form#dw__upload,#media__content div#dw__flashupload{display:block;border-bottom:solid 1px #8cacbb;padding:0 0.5em 1em 0.5em;}#media__content form#dw__upload fieldset{padding:0;margin:0;border:none;width:auto;}#media__content form#dw__upload p{text-align:left;padding:0.25em 0;margin:0;line-height:1.0em;}#media__content form#dw__upload label.check{float:none;width:auto;margin-left:11.5em;}#media__content form.meta{display:block;padding:0 0 1em 0;}#media__content form.meta label{display:block;width:25%;float:left;font-weight:bold;margin-left:1em;clear:left;}#media__content form.meta .edit{font:100% "Lucida Grande",Verdana,Lucida,Helvetica,Arial,sans-serif;float:left;width:70%;padding-right:0;padding-left:0.2em;margin:2px;}#media__content form.meta textarea.edit{height:8em;}#media__content form.meta div.metafield{clear:left;}#media__content form.meta div.buttons{clear:left;margin-left:20%;padding-left:1em;}#media__popup{background-color:#fff;display:none;border:1px solid #8cacbb;position:absolute;width:270px;}#media__popup h1{text-align:center;font-weight:normal;background-color:#dee7ec;height:16px;margin-bottom:5px;font-size:12px;border-bottom:0;}#media__popup p{display:block;line-height:14pt;margin:0.5em;}#media_nolink{padding:4px 0;}#media__popup label{float:left;width:9em;}#media__popup .button{margin-left:auto;margin-right:auto;}#media__popup .btnlbl{text-align:center;}#media__popup .btnlbl input{margin:0 1em;}#media__closeimg{float:right;}#media__linkopts label,#media__nolnk{width:80px;float:left;margin-left:10px;}#media__linkopts label{line-height:20px;}#media__nolnk,#media__linkopts label.long{margin-bottom:8px;line-height:12px;}#media__linkopts label.long{width:150px;float:none;}#media__linkopts br{clear:both;}#media__linkopts select{width:60px;margin-left:10px;}#media__linkopts input.edit{width:50px;margin-left:10px;}#media__linkopts #media__title{width:150px;}#admin__version{clear:left;float:right;color:#666;}.dokuwiki ul.admin_tasks{font-size:115%;float:left;width:40%;list-style-type:none;}.dokuwiki ul.admin_tasks li{line-height:22px;padding-left:35px;margin:1em 0;background:transparent none no-repeat scroll 0 0;text-align:left;}.dokuwiki ul.admin_tasks li div.li{font-weight:bold;}.dokuwiki ul.admin_tasks li.admin_acl{background-image:url(/wiki/lib/tpl/default/../../images/admin/acl.png);}.dokuwiki ul.admin_tasks li.admin_usermanager{background-image:url(/wiki/lib/tpl/default/../../images/admin/usermanager.png);}.dokuwiki ul.admin_tasks li.admin_plugin{background-image:url(/wiki/lib/tpl/default/../../images/admin/plugin.png);}.dokuwiki ul.admin_tasks li.admin_config{background-image:url(/wiki/lib/tpl/default/../../images/admin/config.png);}.dokuwiki ul.admin_tasks li.admin_revert{background-image:url(/wiki/lib/tpl/default/../../images/admin/revert.png);}.dokuwiki ul.admin_tasks li.admin_popularity{background-image:url(/wiki/lib/tpl/default/../../images/admin/popularity.png);}#link__wiz{position:absolute;display:block;z-index:99;width:300px;height:250px;padding:0;margin:0;overflow:hidden;border:1px solid #8cacbb;background-color:#f5f5f5;text-align:center;}#link__wiz_header{background-color:#dee7ec;height:16px;margin-bottom:5px;}#link__wiz_close{cursor:pointer;margin:0;}#link__wiz_result{background-color:#fff;width:293px;height:193px;overflow:auto;border:1px solid #8cacbb;margin:3px auto;text-align:left;}#link__wiz_result div.type_u{padding:3px 3px 3px 22px;background:transparent url(/wiki/lib/tpl/default/../../images/up.png) 3px 3px no-repeat;}#link__wiz_result div.type_f{padding:3px 3px 3px 22px;background:transparent url(/wiki/lib/tpl/default/../../images/page.png) 3px 3px no-repeat;}#link__wiz_result div.type_d{padding:3px 3px 3px 22px;background:transparent url(/wiki/lib/tpl/default/../../images/ns.png) 3px 3px no-repeat;}#link__wiz_result div.even{background-color:#f5f5f5;}#link__wiz_result div.selected{background-color:#dee7ec;}#link__wiz_result span{display:block;color:#666;}.ondrag{cursor:move;opacity:0.8;}form#subscribe__form{display:block;width:300px;text-align:center;}form#subscribe__form fieldset{text-align:left;margin:0.5em 0;}form#subscribe__form label{display:block;margin:0 0.5em 0.5em;}div.dokuwiki a.li{color:#FFF000;}div.dokuwiki a.li:hover{color:#000;} \ No newline at end of file diff --git a/labnic/references_batches_spm8 [WikiNIC]_fichiers/css_002.css b/labnic/references_batches_spm8 [WikiNIC]_fichiers/css_002.css new file mode 100644 index 0000000..5905b24 --- /dev/null +++ b/labnic/references_batches_spm8 [WikiNIC]_fichiers/css_002.css @@ -0,0 +1 @@ +a.interwiki{background:transparent url(/wiki/lib/images/interwiki.png) 0px 1px no-repeat;padding-left:16px;}a.iw_wp{background-image:url(/wiki/lib/images/interwiki/wp.gif)}a.iw_wpfr{background-image:url(/wiki/lib/images/interwiki/wpfr.gif)}a.iw_wpde{background-image:url(/wiki/lib/images/interwiki/wpde.gif)}a.iw_wpes{background-image:url(/wiki/lib/images/interwiki/wpes.gif)}a.iw_wppl{background-image:url(/wiki/lib/images/interwiki/wppl.gif)}a.iw_wpjp{background-image:url(/wiki/lib/images/interwiki/wpjp.gif)}a.iw_wpmeta{background-image:url(/wiki/lib/images/interwiki/wpmeta.gif)}a.iw_doku{background-image:url(/wiki/lib/images/interwiki/doku.gif)}a.iw_dokubug{background-image:url(/wiki/lib/images/interwiki/dokubug.gif)}a.iw_amazon{background-image:url(/wiki/lib/images/interwiki/amazon.gif)}a.iw_amazon_de{background-image:url(/wiki/lib/images/interwiki/amazon.de.gif)}a.iw_amazon_uk{background-image:url(/wiki/lib/images/interwiki/amazon.uk.gif)}a.iw_phpfn{background-image:url(/wiki/lib/images/interwiki/phpfn.gif)}a.iw_coral{background-image:url(/wiki/lib/images/interwiki/coral.gif)}a.iw_sb{background-image:url(/wiki/lib/images/interwiki/sb.gif)}a.iw_google{background-image:url(/wiki/lib/images/interwiki/google.gif)}a.iw_meatball{background-image:url(/wiki/lib/images/interwiki/meatball.gif)}a.iw_wiki{background-image:url(/wiki/lib/images/interwiki/wiki.gif)}a.mediafile{background:transparent url(/wiki/lib/images/fileicons/file.png) 0px 1px no-repeat;padding-left:18px;padding-bottom:1px;}a.mf_deb{background-image:url(/wiki/lib/images/fileicons/deb.png)}a.mf_rar{background-image:url(/wiki/lib/images/fileicons/rar.png)}a.mf_cpp{background-image:url(/wiki/lib/images/fileicons/cpp.png)}a.mf_jpg{background-image:url(/wiki/lib/images/fileicons/jpg.png)}a.mf_xls{background-image:url(/wiki/lib/images/fileicons/xls.png)}a.mf_xml{background-image:url(/wiki/lib/images/fileicons/xml.png)}a.mf_css{background-image:url(/wiki/lib/images/fileicons/css.png)}a.mf_csv{background-image:url(/wiki/lib/images/fileicons/csv.png)}a.mf_c{background-image:url(/wiki/lib/images/fileicons/c.png)}a.mf_doc{background-image:url(/wiki/lib/images/fileicons/doc.png)}a.mf_html{background-image:url(/wiki/lib/images/fileicons/html.png)}a.mf_rpm{background-image:url(/wiki/lib/images/fileicons/rpm.png)}a.mf_rtf{background-image:url(/wiki/lib/images/fileicons/rtf.png)}a.mf_zip{background-image:url(/wiki/lib/images/fileicons/zip.png)}a.mf_tar{background-image:url(/wiki/lib/images/fileicons/tar.png)}a.mf_audio{background-image:url(/wiki/lib/images/fileicons/audio.png)}a.mf_sql{background-image:url(/wiki/lib/images/fileicons/sql.png)}a.mf_lua{background-image:url(/wiki/lib/images/fileicons/lua.png)}a.mf_tgz{background-image:url(/wiki/lib/images/fileicons/tgz.png)}a.mf_mp3{background-image:url(/wiki/lib/images/fileicons/mp3.png)}a.mf_cs{background-image:url(/wiki/lib/images/fileicons/cs.png)}a.mf_swf{background-image:url(/wiki/lib/images/fileicons/swf.png)}a.mf_conf{background-image:url(/wiki/lib/images/fileicons/conf.png)}a.mf_sxc{background-image:url(/wiki/lib/images/fileicons/sxc.png)}a.mf_sxd{background-image:url(/wiki/lib/images/fileicons/sxd.png)}a.mf_sxi{background-image:url(/wiki/lib/images/fileicons/sxi.png)}a.mf_sxw{background-image:url(/wiki/lib/images/fileicons/sxw.png)}a.mf_gz{background-image:url(/wiki/lib/images/fileicons/gz.png)}a.mf_gif{background-image:url(/wiki/lib/images/fileicons/gif.png)}a.mf_js{background-image:url(/wiki/lib/images/fileicons/js.png)}a.mf_pptx{background-image:url(/wiki/lib/images/fileicons/pptx.png)}a.mf_pl{background-image:url(/wiki/lib/images/fileicons/pl.png)}a.mf_odc{background-image:url(/wiki/lib/images/fileicons/odc.png)}a.mf_odf{background-image:url(/wiki/lib/images/fileicons/odf.png)}a.mf_odg{background-image:url(/wiki/lib/images/fileicons/odg.png)}a.mf_odi{background-image:url(/wiki/lib/images/fileicons/odi.png)}a.mf_ps{background-image:url(/wiki/lib/images/fileicons/ps.png)}a.mf_rb{background-image:url(/wiki/lib/images/fileicons/rb.png)}a.mf_py{background-image:url(/wiki/lib/images/fileicons/py.png)}a.mf_odp{background-image:url(/wiki/lib/images/fileicons/odp.png)}a.mf_ods{background-image:url(/wiki/lib/images/fileicons/ods.png)}a.mf_txt{background-image:url(/wiki/lib/images/fileicons/txt.png)}a.mf_odt{background-image:url(/wiki/lib/images/fileicons/odt.png)}a.mf_ogg{background-image:url(/wiki/lib/images/fileicons/ogg.png)}a.mf_jpeg{background-image:url(/wiki/lib/images/fileicons/jpeg.png)}a.mf_java{background-image:url(/wiki/lib/images/fileicons/java.png)}a.mf_xlsx{background-image:url(/wiki/lib/images/fileicons/xlsx.png)}a.mf_wav{background-image:url(/wiki/lib/images/fileicons/wav.png)}a.mf_pdf{background-image:url(/wiki/lib/images/fileicons/pdf.png)}a.mf_htm{background-image:url(/wiki/lib/images/fileicons/htm.png)}a.mf_php{background-image:url(/wiki/lib/images/fileicons/php.png)}a.mf_7z{background-image:url(/wiki/lib/images/fileicons/7z.png)}a.mf_png{background-image:url(/wiki/lib/images/fileicons/png.png)}a.mf_docx{background-image:url(/wiki/lib/images/fileicons/docx.png)}a.mf_ppt{background-image:url(/wiki/lib/images/fileicons/ppt.png)}a.mf_bz2{background-image:url(/wiki/lib/images/fileicons/bz2.png)}div.dokuwiki #discussion__comment_form,div.dokuwiki #discussion__newthread_form,div.dokuwiki div.comment_buttons,div.dokuwiki div.comment_hidden{display:none;}body{font:10pt "Lucida Grande",Verdana,Lucida,Helvetica,Arial,sans-serif;background-color:White;color:Black;}table{font-size:100%;padding:0;margin:0;}tr,td,th{padding:0;margin:0;}img{border:0;}a{color:#000;text-decoration:none;background:none !important;}a.interwiki{padding-left:0px !important;}div.meta{clear:both;margin-top:1em;font-size:70%;text-align:right;}div.notify,div.info,div.success,div.error,div.breadcrumbs,div.secedit{display:none;}a.urlextern:after{content:" [" attr(href) "]";font-size:90%;}a.interwiki:after{content:" [" attr(href) "]";font-size:90%;}a.mail:after{content:" [" attr(href) "]";font-size:90%;}a.wikilink1{text-decoration:underline;}div.page{text-align:justify;}h1,h2,h3,h4,h5{color:Black;background-color:transparent;font-family:"Lucida Grande",Verdana,Lucida,Helvetica,Arial,sans-serif;font-size:100%;font-weight:normal;margin-left:0;margin-right:0;margin-top:0;margin-bottom:1em;padding-left:0;padding-right:0;padding-top:0.5em;padding-bottom:0;border-bottom:1px solid #000;clear:left;}h1{font-size:160%;font-weight:bold;}h2{font-size:150%;}h3{font-size:140%;border-bottom:none;}h4{font-size:120%;border-bottom:none;}h5{font-size:100%;border-bottom:none;}img.media{margin:3px;}img.medialeft{border:0;float:left;margin:0 1.5em 0 0;}img.mediaright{border:0;float:right;margin:0 0 0 1.5em;}ul{line-height:1.5em;list-style-type:square;margin:0 0 1em 3.5em;padding:0;}ol{line-height:1.5em;margin:0 0 1em 3.5em;padding:0;font-weight:normal;}div.dokuwiki li ul{margin-bottom:0;}div.dokuwiki li ol{margin-bottom:0;}div.dokuwiki ol{list-style-type:decimal;}div.dokuwiki ol ol{list-style-type:upper-roman;}div.dokuwiki ol ol ol{list-style-type:lower-alpha;}div.dokuwiki ol ol ol ol{list-style-type:lower-greek;}span.li{font-weight:normal;}pre{font-family:monospace;}pre.pre{font-size:8pt;padding:0.5em;border:1px dashed #000;color:Black;overflow:visible;}pre.code{font-size:8pt;padding:0.5em;border:1px dashed #000;color:Black;overflow:visible;}code{font-size:120%;}pre.file{font-size:8pt;padding:0.5em;border:1px dotted #000;color:Black;overflow:visible;}div.footnotes{clear:both;border-top:1px solid #000;padding-left:1em;margin-top:1em;}div.fn{font-size:90%;}a.fn_top{vertical-align:super;font-size:80%;}a.fn_bot{vertical-align:super;font-size:80%;font-weight:bold;}acronym{border:0;}table.inline{font-size:80%;background-color:#fff;border-spacing:0px;border-collapse:collapse;}table.inline th{padding:3px;border:1px solid #000;border-bottom:2px solid #000;}table.inline td{padding:3px;border:1px solid #000;}.leftalign{text-align:left;}.centeralign{text-align:center;}.rightalign{text-align:right;}.toc,.footerinc,.header,.bar,.user{display:none;} \ No newline at end of file diff --git a/labnic/references_batches_spm8 [WikiNIC]_fichiers/css_003.css b/labnic/references_batches_spm8 [WikiNIC]_fichiers/css_003.css new file mode 100644 index 0000000..888861c --- /dev/null +++ b/labnic/references_batches_spm8 [WikiNIC]_fichiers/css_003.css @@ -0,0 +1 @@ +a.interwiki{background:transparent url(/wiki/lib/images/interwiki.png) 0px 1px no-repeat;padding-left:16px;}a.iw_wp{background-image:url(/wiki/lib/images/interwiki/wp.gif)}a.iw_wpfr{background-image:url(/wiki/lib/images/interwiki/wpfr.gif)}a.iw_wpde{background-image:url(/wiki/lib/images/interwiki/wpde.gif)}a.iw_wpes{background-image:url(/wiki/lib/images/interwiki/wpes.gif)}a.iw_wppl{background-image:url(/wiki/lib/images/interwiki/wppl.gif)}a.iw_wpjp{background-image:url(/wiki/lib/images/interwiki/wpjp.gif)}a.iw_wpmeta{background-image:url(/wiki/lib/images/interwiki/wpmeta.gif)}a.iw_doku{background-image:url(/wiki/lib/images/interwiki/doku.gif)}a.iw_dokubug{background-image:url(/wiki/lib/images/interwiki/dokubug.gif)}a.iw_amazon{background-image:url(/wiki/lib/images/interwiki/amazon.gif)}a.iw_amazon_de{background-image:url(/wiki/lib/images/interwiki/amazon.de.gif)}a.iw_amazon_uk{background-image:url(/wiki/lib/images/interwiki/amazon.uk.gif)}a.iw_phpfn{background-image:url(/wiki/lib/images/interwiki/phpfn.gif)}a.iw_coral{background-image:url(/wiki/lib/images/interwiki/coral.gif)}a.iw_sb{background-image:url(/wiki/lib/images/interwiki/sb.gif)}a.iw_google{background-image:url(/wiki/lib/images/interwiki/google.gif)}a.iw_meatball{background-image:url(/wiki/lib/images/interwiki/meatball.gif)}a.iw_wiki{background-image:url(/wiki/lib/images/interwiki/wiki.gif)}a.mediafile{background:transparent url(/wiki/lib/images/fileicons/file.png) 0px 1px no-repeat;padding-left:18px;padding-bottom:1px;}a.mf_deb{background-image:url(/wiki/lib/images/fileicons/deb.png)}a.mf_rar{background-image:url(/wiki/lib/images/fileicons/rar.png)}a.mf_cpp{background-image:url(/wiki/lib/images/fileicons/cpp.png)}a.mf_jpg{background-image:url(/wiki/lib/images/fileicons/jpg.png)}a.mf_xls{background-image:url(/wiki/lib/images/fileicons/xls.png)}a.mf_xml{background-image:url(/wiki/lib/images/fileicons/xml.png)}a.mf_css{background-image:url(/wiki/lib/images/fileicons/css.png)}a.mf_csv{background-image:url(/wiki/lib/images/fileicons/csv.png)}a.mf_c{background-image:url(/wiki/lib/images/fileicons/c.png)}a.mf_doc{background-image:url(/wiki/lib/images/fileicons/doc.png)}a.mf_html{background-image:url(/wiki/lib/images/fileicons/html.png)}a.mf_rpm{background-image:url(/wiki/lib/images/fileicons/rpm.png)}a.mf_rtf{background-image:url(/wiki/lib/images/fileicons/rtf.png)}a.mf_zip{background-image:url(/wiki/lib/images/fileicons/zip.png)}a.mf_tar{background-image:url(/wiki/lib/images/fileicons/tar.png)}a.mf_audio{background-image:url(/wiki/lib/images/fileicons/audio.png)}a.mf_sql{background-image:url(/wiki/lib/images/fileicons/sql.png)}a.mf_lua{background-image:url(/wiki/lib/images/fileicons/lua.png)}a.mf_tgz{background-image:url(/wiki/lib/images/fileicons/tgz.png)}a.mf_mp3{background-image:url(/wiki/lib/images/fileicons/mp3.png)}a.mf_cs{background-image:url(/wiki/lib/images/fileicons/cs.png)}a.mf_swf{background-image:url(/wiki/lib/images/fileicons/swf.png)}a.mf_conf{background-image:url(/wiki/lib/images/fileicons/conf.png)}a.mf_sxc{background-image:url(/wiki/lib/images/fileicons/sxc.png)}a.mf_sxd{background-image:url(/wiki/lib/images/fileicons/sxd.png)}a.mf_sxi{background-image:url(/wiki/lib/images/fileicons/sxi.png)}a.mf_sxw{background-image:url(/wiki/lib/images/fileicons/sxw.png)}a.mf_gz{background-image:url(/wiki/lib/images/fileicons/gz.png)}a.mf_gif{background-image:url(/wiki/lib/images/fileicons/gif.png)}a.mf_js{background-image:url(/wiki/lib/images/fileicons/js.png)}a.mf_pptx{background-image:url(/wiki/lib/images/fileicons/pptx.png)}a.mf_pl{background-image:url(/wiki/lib/images/fileicons/pl.png)}a.mf_odc{background-image:url(/wiki/lib/images/fileicons/odc.png)}a.mf_odf{background-image:url(/wiki/lib/images/fileicons/odf.png)}a.mf_odg{background-image:url(/wiki/lib/images/fileicons/odg.png)}a.mf_odi{background-image:url(/wiki/lib/images/fileicons/odi.png)}a.mf_ps{background-image:url(/wiki/lib/images/fileicons/ps.png)}a.mf_rb{background-image:url(/wiki/lib/images/fileicons/rb.png)}a.mf_py{background-image:url(/wiki/lib/images/fileicons/py.png)}a.mf_odp{background-image:url(/wiki/lib/images/fileicons/odp.png)}a.mf_ods{background-image:url(/wiki/lib/images/fileicons/ods.png)}a.mf_txt{background-image:url(/wiki/lib/images/fileicons/txt.png)}a.mf_odt{background-image:url(/wiki/lib/images/fileicons/odt.png)}a.mf_ogg{background-image:url(/wiki/lib/images/fileicons/ogg.png)}a.mf_jpeg{background-image:url(/wiki/lib/images/fileicons/jpeg.png)}a.mf_java{background-image:url(/wiki/lib/images/fileicons/java.png)}a.mf_xlsx{background-image:url(/wiki/lib/images/fileicons/xlsx.png)}a.mf_wav{background-image:url(/wiki/lib/images/fileicons/wav.png)}a.mf_pdf{background-image:url(/wiki/lib/images/fileicons/pdf.png)}a.mf_htm{background-image:url(/wiki/lib/images/fileicons/htm.png)}a.mf_php{background-image:url(/wiki/lib/images/fileicons/php.png)}a.mf_7z{background-image:url(/wiki/lib/images/fileicons/7z.png)}a.mf_png{background-image:url(/wiki/lib/images/fileicons/png.png)}a.mf_docx{background-image:url(/wiki/lib/images/fileicons/docx.png)}a.mf_ppt{background-image:url(/wiki/lib/images/fileicons/ppt.png)}a.mf_bz2{background-image:url(/wiki/lib/images/fileicons/bz2.png)} \ No newline at end of file diff --git a/labnic/references_batches_spm8 [WikiNIC]_fichiers/indexer.gif b/labnic/references_batches_spm8 [WikiNIC]_fichiers/indexer.gif new file mode 100644 index 0000000000000000000000000000000000000000..9935f82104a336a22da8f8bac84db0749a4bd27b GIT binary patch literal 42 pcmZ?wbhEHbWMp7uXkY+=|Ns9h{$ycf01D`U_#hbuCPp6yYXG?z2LJ#7 literal 0 HcmV?d00001 diff --git a/labnic/references_batches_spm8 [WikiNIC]_fichiers/js.js b/labnic/references_batches_spm8 [WikiNIC]_fichiers/js.js new file mode 100644 index 0000000..bb2ea43 --- /dev/null +++ b/labnic/references_batches_spm8 [WikiNIC]_fichiers/js.js @@ -0,0 +1 @@ +var DOKU_BASE='/wiki/';var DOKU_TPL='/wiki/lib/tpl/default/';var DOKU_UHN=0;var DOKU_UHC=0;LANG={"notsavedyet":"Unsaved changes will be lost.","searchmedia":"Search for files","keepopen":"Keep window open on selection","hidedetails":"Hide Details","mediatitle":"Link settings","mediadisplay":"Link type","mediaalign":"Alignment","mediasize":"Image size","mediatarget":"Link target","mediaclose":"Close","mediainsert":"Insert","mediadisplayimg":"Show the image.","mediadisplaylnk":"Show only the link.","mediasmall":"Small version","mediamedium":"Medium version","medialarge":"Large version","mediaoriginal":"Original version","medialnk":"Link to detail page","mediadirect":"Direct link to original","medianolnk":"No link","medianolink":"Do not link the image","medialeft":"Align the image on the left.","mediaright":"Align the image on the right.","mediacenter":"Align the image in the middle.","medianoalign":"Use no align.","nosmblinks":"Linking to Windows shares only works in Microsoft Internet Explorer.\nYou still can copy and paste the link.","linkwiz":"Link Wizard","linkto":"Link to:","del_confirm":"Really delete selected item(s)?","mu_btn":"Upload multiple files at once","plugins":[]};var toolbar=[{"type":"format","title":"Bold Text","icon":"bold.png","key":"b","open":"**","close":"**","block":false},{"type":"format","title":"Italic Text","icon":"italic.png","key":"i","open":"\/\/","close":"\/\/","block":false},{"type":"format","title":"Underlined Text","icon":"underline.png","key":"u","open":"__","close":"__","block":false},{"type":"format","title":"Code Text","icon":"mono.png","key":"c","open":"''","close":"''","block":false},{"type":"format","title":"Strike-through Text","icon":"strike.png","key":"d","open":"","close":"<\/del>","block":false},{"type":"autohead","title":"Same Level Headline","icon":"hequal.png","key":"8","text":"Headline","mod":0,"block":true},{"type":"autohead","title":"Lower Headline","icon":"hminus.png","key":"9","text":"Headline","mod":1,"block":true},{"type":"autohead","title":"Higher Headline","icon":"hplus.png","key":"0","text":"Headline","mod":-1,"block":true},{"type":"picker","title":"Select Headline","icon":"h.png","class":"pk_hl","list":[{"type":"format","title":"Level 1 Headline","icon":"h1.png","key":"1","open":"====== ","close":" ======\\n"},{"type":"format","title":"Level 2 Headline","icon":"h2.png","key":"2","open":"===== ","close":" =====\\n"},{"type":"format","title":"Level 3 Headline","icon":"h3.png","key":"3","open":"==== ","close":" ====\\n"},{"type":"format","title":"Level 4 Headline","icon":"h4.png","key":"4","open":"=== ","close":" ===\\n"},{"type":"format","title":"Level 5 Headline","icon":"h5.png","key":"5","open":"== ","close":" ==\\n"}],"block":true},{"type":"linkwiz","title":"Internal Link","icon":"link.png","key":"l","open":"[[","close":"]]","block":false},{"type":"format","title":"External Link","icon":"linkextern.png","open":"[[","close":"]]","sample":"http:\/\/example.com|External Link","block":false},{"type":"formatln","title":"Ordered List Item","icon":"ol.png","open":" - ","close":"","key":"-","block":true},{"type":"formatln","title":"Unordered List Item","icon":"ul.png","open":" * ","close":"","key":".","block":true},{"type":"insert","title":"Horizontal Rule","icon":"hr.png","insert":"\\n----\\n","block":true},{"type":"mediapopup","title":"Add Images and other files","icon":"image.png","url":"lib\/exe\/mediamanager.php?ns=","name":"mediaselect","options":"width=750,height=500,left=20,top=20,scrollbars=yes,resizable=yes","block":false},{"type":"picker","title":"Smileys","icon":"smiley.png","list":{"8-)":"icon_cool.gif","8-O":"icon_eek.gif","8-o":"icon_eek.gif",":-(":"icon_sad.gif",":-)":"icon_smile.gif","=)":"icon_smile2.gif",":-\/":"icon_doubt.gif",":-\\":"icon_doubt2.gif",":-?":"icon_confused.gif",":-D":"icon_biggrin.gif",":-P":"icon_razz.gif",":-o":"icon_surprised.gif",":-O":"icon_surprised.gif",":-x":"icon_silenced.gif",":-X":"icon_silenced.gif",":-|":"icon_neutral.gif",";-)":"icon_wink.gif","^_^":"icon_fun.gif",":?:":"icon_question.gif",":!:":"icon_exclaim.gif","LOL":"icon_lol.gif","FIXME":"fixme.gif","DELETEME":"delete.gif"},"icobase":"smileys","block":false},{"type":"picker","title":"Special Chars","icon":"chars.png","list":["\u00c0","\u00e0","\u00c1","\u00e1","\u00c2","\u00e2","\u00c3","\u00e3","\u00c4","\u00e4","\u01cd","\u01ce","\u0102","\u0103","\u00c5","\u00e5","\u0100","\u0101","\u0104","\u0105","\u00c6","\u00e6","\u0106","\u0107","\u00c7","\u00e7","\u010c","\u010d","\u0108","\u0109","\u010a","\u010b","\u00d0","\u0111","\u00f0","\u010e","\u010f","\u00c8","\u00e8","\u00c9","\u00e9","\u00ca","\u00ea","\u00cb","\u00eb","\u011a","\u011b","\u0112","\u0113","\u0116","\u0117","\u0118","\u0119","\u0122","\u0123","\u011c","\u011d","\u011e","\u011f","\u0120","\u0121","\u0124","\u0125","\u00cc","\u00ec","\u00cd","\u00ed","\u00ce","\u00ee","\u00cf","\u00ef","\u01cf","\u01d0","\u012a","\u012b","\u0130","\u0131","\u012e","\u012f","\u0134","\u0135","\u0136","\u0137","\u0139","\u013a","\u013b","\u013c","\u013d","\u013e","\u0141","\u0142","\u013f","\u0140","\u0143","\u0144","\u00d1","\u00f1","\u0145","\u0146","\u0147","\u0148","\u00d2","\u00f2","\u00d3","\u00f3","\u00d4","\u00f4","\u00d5","\u00f5","\u00d6","\u00f6","\u01d1","\u01d2","\u014c","\u014d","\u0150","\u0151","\u0152","\u0153","\u00d8","\u00f8","\u0154","\u0155","\u0156","\u0157","\u0158","\u0159","\u015a","\u015b","\u015e","\u015f","\u0160","\u0161","\u015c","\u015d","\u0162","\u0163","\u0164","\u0165","\u00d9","\u00f9","\u00da","\u00fa","\u00db","\u00fb","\u00dc","\u00fc","\u01d3","\u01d4","\u016c","\u016d","\u016a","\u016b","\u016e","\u016f","\u01d6","\u01d8","\u01da","\u01dc","\u0172","\u0173","\u0170","\u0171","\u0174","\u0175","\u00dd","\u00fd","\u0178","\u00ff","\u0176","\u0177","\u0179","\u017a","\u017d","\u017e","\u017b","\u017c","\u00de","\u00fe","\u00df","\u0126","\u0127","\u00bf","\u00a1","\u00a2","\u00a3","\u00a4","\u00a5","\u20ac","\u00a6","\u00a7","\u00aa","\u00ac","\u00af","\u00b0","\u00b1","\u00f7","\u2030","\u00bc","\u00bd","\u00be","\u00b9","\u00b2","\u00b3","\u00b5","\u00b6","\u2020","\u2021","\u00b7","\u2022","\u00ba","\u2200","\u2202","\u2203","\u018f","\u0259","\u2205","\u2207","\u2208","\u2209","\u220b","\u220f","\u2211","\u203e","\u2212","\u2217","\u221a","\u221d","\u221e","\u2220","\u2227","\u2228","\u2229","\u222a","\u222b","\u2234","\u223c","\u2245","\u2248","\u2260","\u2261","\u2264","\u2265","\u2282","\u2283","\u2284","\u2286","\u2287","\u2295","\u2297","\u22a5","\u22c5","\u25ca","\u2118","\u2111","\u211c","\u2135","\u2660","\u2663","\u2665","\u2666","\u03b1","\u03b2","\u0393","\u03b3","\u0394","\u03b4","\u03b5","\u03b6","\u03b7","\u0398","\u03b8","\u03b9","\u03ba","\u039b","\u03bb","\u03bc","\u039e","\u03be","\u03a0","\u03c0","\u03c1","\u03a3","\u03c3","\u03a4","\u03c4","\u03c5","\u03a6","\u03c6","\u03c7","\u03a8","\u03c8","\u03a9","\u03c9","\u2605","\u2606","\u260e","\u261a","\u261b","\u261c","\u261d","\u261e","\u261f","\u2639","\u263a","\u2714","\u2718","\u00d7","\u201e","\u201c","\u201d","\u201a","\u2018","\u2019","\u00ab","\u00bb","\u2039","\u203a","\u2014","\u2013","\u2026","\u2190","\u2191","\u2192","\u2193","\u2194","\u21d0","\u21d1","\u21d2","\u21d3","\u21d4","\u00a9","\u2122","\u00ae","\u2032","\u2033","[","]","{","}","~","(",")","%","\u00a7","$","#","|","@"],"block":false},{"type":"signature","title":"Insert Signature","icon":"sig.png","key":"y","block":false}];function isUndefined(prop){return(typeof prop=='undefined');}function isFunction(prop){return(typeof prop=='function');}function isString(prop){return(typeof prop=='string');}function isNumber(prop){return(typeof prop=='number');}function isNumeric(prop){return isNumber(prop)&&!isNaN(prop)&&isFinite(prop);}function isArray(prop){return(prop instanceof Array);}function isRegExp(prop){return(prop instanceof RegExp);}function isBoolean(prop){return('boolean'==typeof prop);}function isScalar(prop){return isNumeric(prop)||isString(prop);}function isEmpty(prop){if(isBoolean(prop))return false;if(isRegExp(prop)&&new RegExp("").toString()==prop.toString())return true;if(isString(prop)||isNumber(prop))return!prop;if(Boolean(prop)&&false!=prop){for(var i in prop)if(prop.hasOwnProperty(i))return false;}return true;}if('undefined'==typeof Object.hasOwnProperty){Object.prototype.hasOwnProperty=function(prop){return!('undefined'==typeof this[prop]||this.constructor&&this.constructor.prototype[prop]&&this[prop]===this.constructor.prototype[prop]);};}function hasFlash(version){var ver=0;try{if(navigator.plugins!=null&&navigator.plugins.length>0){ver=navigator.plugins["Shockwave Flash"].description.split(' ')[2].split('.')[0];}else{var axo=new ActiveXObject("ShockwaveFlash.ShockwaveFlash");ver=axo.GetVariable("$version").split(' ')[1].split(',')[0];}}catch(e){}if(ver>=version)return true;return false;}function addEvent(element,type,handler){if(!handler.$$guid)handler.$$guid=addEvent.guid++;if(!element.events)element.events={};var handlers=element.events[type];if(!handlers){handlers=element.events[type]={};if(element["on"+type]){handlers[0]=element["on"+type];}}handlers[handler.$$guid]=handler;element["on"+type]=handleEvent;};addEvent.guid=1;function removeEvent(element,type,handler){if(element.events&&element.events[type]){delete element.events[type][handler.$$guid];}};function handleEvent(event){var returnValue=true;event=event||fixEvent(window.event,this);var handlers=this.events[event.type];for(var i in handlers){if(!handlers.hasOwnProperty(i))continue;if(handlers[i].call(this,event)===false){returnValue=false;}}return returnValue;};function fixEvent(event,_this){event.preventDefault=fixEvent.preventDefault;event.stopPropagation=fixEvent.stopPropagation;event.target=event.srcElement;event.currentTarget=_this;var base=(document.documentElement.scrollTop?document.documentElement:document.body);event.pageX=(typeof event.pageX!=='undefined')?event.pageX:event.clientX+base.scrollLeft;event.pageY=(typeof event.pageY!=='undefined')?event.pageY:event.clientY+base.scrollTop;return event;};fixEvent.preventDefault=function(){this.returnValue=false;};fixEvent.stopPropagation=function(){this.cancelBubble=true;};window.fireoninit=function(){if(arguments.callee.done)return;arguments.callee.done=true;if(_timer){clearInterval(_timer);_timer=null;}if(typeof window.oninit=='function'){window.oninit();}};if(document.addEventListener){document.addEventListener("DOMContentLoaded",window.fireoninit,null);}/*@cc_on @if(@_win32)document.write("<\/script>");var script=document.getElementById("__ie_init");script.onreadystatechange=function(){if(this.readyState=="complete"){window.fireoninit();}};@end @*/if(/WebKit/i.test(navigator.userAgent)){var _timer=setInterval(function(){if(/loaded|complete/.test(document.readyState)){window.fireoninit();}},10);}window.onload=window.fireoninit;window.oninit=function(){};function addInitEvent(func){var oldoninit=window.oninit;if(typeof window.oninit!='function'){window.oninit=func;}else{window.oninit=function(){oldoninit();func();};}}function bind(fnc){var args=Array.prototype.slice.call(arguments,1);return function(){return fnc.apply(this,args);};}var timer={_cur_id:0,_handlers:{},execDispatch:function(id){timer._handlers[id]();},add:function(func,timeout){var id=++timer._cur_id;timer._handlers[id]=func;return window.setTimeout('timer.execDispatch('+id+')',timeout);}};function Delay(func,timeout){this.func=func;if(timeout){this.timeout=timeout;}}Delay.prototype={func:null,timeout:500,delTimer:function(){if(this.timer!==null){window.clearTimeout(this.timer);this.timer=null;}},start:function(){this.delTimer();var _this=this;this.timer=timer.add(function(){_this.exec.call(_this);},this.timeout);this._data={_this:arguments[0],_params:Array.prototype.slice.call(arguments,2)};},exec:function(){this.delTimer();this.func.call(this._data._this,this._data._params);}};DokuCookie={data:Array(),name:'DOKU_PREFS',setValue:function(key,val){DokuCookie.init();DokuCookie.data[key]=val;var now=new Date();DokuCookie.fixDate(now);now.setTime(now.getTime()+365*24*60*60*1000);var text='';for(var key in DokuCookie.data){if(!DokuCookie.data.hasOwnProperty(key))continue;text+='#'+escape(key)+'#'+DokuCookie.data[key];}DokuCookie.setCookie(DokuCookie.name,text.substr(1),now,DOKU_BASE);},getValue:function(key){DokuCookie.init();return DokuCookie.data[key];},init:function(){if(DokuCookie.data.length)return;var text=DokuCookie.getCookie(DokuCookie.name);if(text){var parts=text.split('#');for(var i=0;i0){date.setTime(date.getTime()-skew);}}};var clientPC=navigator.userAgent.toLowerCase();var is_macos=navigator.appVersion.indexOf('Mac')!=-1;var is_gecko=((clientPC.indexOf('gecko')!=-1)&&(clientPC.indexOf('spoofer')==-1)&&(clientPC.indexOf('khtml')==-1)&&(clientPC.indexOf('netscape/7.0')==-1));var is_safari=((clientPC.indexOf('applewebkit')!=-1)&&(clientPC.indexOf('spoofer')==-1));var is_khtml=(navigator.vendor=='KDE'||(document.childNodes&&!document.all&&!navigator.taintEnabled));if(clientPC.indexOf('opera')!=-1){var is_opera=true;var is_opera_preseven=(window.opera&&!document.childNodes);var is_opera_seven=(window.opera&&document.childNodes);}function $(){var elements=new Array();for(var i=0;i');}function hideLoadBar(id){obj=$(id);if(obj)obj.style.display="none";}function addTocToggle(){if(!document.getElementById)return;var header=$('toc__header');if(!header)return;var toc=$('toc__inside');var obj=document.createElement('span');obj.id='toc__toggle';obj.style.cursor='pointer';if(toc&&toc.style.display=='none'){obj.innerHTML='+';obj.className='toc_open';}else{obj.innerHTML='';obj.className='toc_close';}prependChild(header,obj);obj.parentNode.onclick=toggleToc;obj.parentNode.style.cursor='pointer';}function toggleToc(){var toc=$('toc__inside');var obj=$('toc__toggle');if(toc.style.display=='none'){toc.style.display='';obj.innerHTML='';obj.className='toc_close';}else{toc.style.display='none';obj.innerHTML='+';obj.className='toc_open';}}function insitu_popup(target,popup_id){var fndiv=$(popup_id);if(!fndiv){fndiv=document.createElement('div');fndiv.id=popup_id;fndiv.className='insitu-footnote JSpopup dokuwiki';addEvent(fndiv,'mouseout',function(e){var p=e.relatedTarget||e.toElement;while(p&&p!==this){p=p.parentNode;}if(p===this){return;}this.style.display='none';});getElementsByClass('dokuwiki',document.body,'div')[0].appendChild(fndiv);}fndiv.style.position='absolute';fndiv.style.left=findPosX(target)+'px';fndiv.style.top=(findPosY(target)+target.offsetHeight*1.5)+'px';fndiv.style.display='';return fndiv;}function footnote(e){var fndiv=insitu_popup(e.target,'insitu__fn');var a=$("fn__"+e.target.id.substr(5));if(!a){return;}var content=new String(a.parentNode.parentNode.innerHTML);content=content.replace(/.*<\/sup>/gi,'');content=content.replace(/^\s+(,\s+)+/,'');content=content.replace(/\bid=(['"])([^"']+)\1/gi,'id="insitu__$2');fndiv.innerHTML=content;}addInitEvent(function(){var elems=getElementsByClass('fn_top',null,'a');for(var i=0;i=2)input2.disabled=(input2.type=='checkbox'&&!input2.checked);else input2.disabled=(input2.type!='checkbox');}});input1.checked=false;}else if(input1.type=='submit'){input1.disabled=true;}}});addInitEvent(function(){var selector=$('action__selector');if(!selector)return;addEvent(selector,'change',function(e){this.form.submit();});$('action__selectorbtn').style.display='none';});function checkWindowsShares(){if(!LANG['nosmblinks'])return true;if(document.all!=null)return true;var elems=getElementsByClass('windows',document,'a');if(elems){for(var i=0;iloading...
  • ';listitem.appendChild(ul);listitem.className='open';}},this.throbber_delay);ajax.elementObj=ul;ajax.afterCompletion=function(){window.clearTimeout(timeout);index.treeattach(ul);if(listitem.className!='open'){if(!listitem.open){ul.style.display='none';}listitem.appendChild(ul);if(listitem.open){listitem.className='open';}}};ajax.runAJAX(clicky.search.substr(1)+'&call=index');e.preventDefault();return false;}};addInitEvent(function(){index.treeattach($('index__tree'));});var drag={obj:null,handle:null,oX:0,oY:0,eX:0,eY:0,attach:function(obj,handle){if(handle){handle.dragobject=obj;}else{handle=obj;}var _this=this;addEvent($(handle),'mousedown',function(e){return _this.start(e);});},start:function(e){this.handle=e.target;if(this.handle.dragobject){this.obj=this.handle.dragobject;}else{this.obj=this.handle;}this.handle.className+=' ondrag';this.obj.className+=' ondrag';this.oX=parseInt(this.obj.style.left);this.oY=parseInt(this.obj.style.top);this.eX=e.pageX;this.eY=e.pageY;var _this=this;this.mousehandlers=[function(e){return _this.drag(e);},function(e){return _this.stop(e);}];addEvent(document,'mousemove',this.mousehandlers[0]);addEvent(document,'mouseup',this.mousehandlers[1]);return false;},stop:function(){this.handle.className=this.handle.className.replace(/ ?ondrag/,'');this.obj.className=this.obj.className.replace(/ ?ondrag/,'');removeEvent(document,'mousemove',this.mousehandlers[0]);removeEvent(document,'mouseup',this.mousehandlers[1]);this.obj=null;this.handle=null;},drag:function(e){if(this.obj){this.obj.style.top=(e.pageY+this.oY-this.eY+'px');this.obj.style.left=(e.pageX+this.oX-this.eX+'px');}}};function selection_class(){this.start=0;this.end=0;this.obj=null;this.rangeCopy=null;this.scroll=0;this.fix=0;this.getLength=function(){return this.end-this.start;};this.getText=function(){if(!this.obj)return'';return this.obj.value.substring(this.start,this.end);};}function getSelection(textArea){var sel=new selection_class();sel.obj=textArea;sel.start=textArea.value.length;sel.end=textArea.value.length;textArea.focus();if(document.getSelection){sel.start=textArea.selectionStart;sel.end=textArea.selectionEnd;sel.scroll=textArea.scrollTop;}else if(document.selection){sel.rangeCopy=document.selection.createRange().duplicate();if(textArea.tagName==='INPUT'){var before_range=textArea.createTextRange();before_range.expand('textedit');}else{var before_range=document.body.createTextRange();before_range.moveToElementText(textArea);}before_range.setEndPoint("EndToStart",sel.rangeCopy);var before_finished=false,selection_finished=false;var before_text,selection_text;before_text=before_range.text;selection_text=sel.rangeCopy.text;sel.start=before_text.length;sel.end=sel.start+selection_text.length;do{if(!before_finished){if(before_range.compareEndPoints("StartToEnd",before_range)==0){before_finished=true;}else{before_range.moveEnd("character",-1);if(before_range.text==before_text){sel.start+=2;sel.end+=2;}else{before_finished=true;}}}if(!selection_finished){if(sel.rangeCopy.compareEndPoints("StartToEnd",sel.rangeCopy)==0){selection_finished=true;}else{sel.rangeCopy.moveEnd("character",-1);if(sel.rangeCopy.text==selection_text){sel.end+=2;}else{selection_finished=true;}}}}while((!before_finished||!selection_finished));var countNL=function(str){var m=str.split("\r\n");if(!m||!m.length)return 0;return m.length-1;};sel.fix=countNL(sel.obj.value.substring(0,sel.start));}return sel;}function setSelection(selection){if(document.getSelection){selection.obj.setSelectionRange(selection.start,selection.end);if(selection.scroll)selection.obj.scrollTop=selection.scroll;}else if(document.selection){selection.rangeCopy.collapse(true);selection.rangeCopy.moveStart('character',selection.start-selection.fix);selection.rangeCopy.moveEnd('character',selection.end-selection.start);selection.rangeCopy.select();}}function pasteText(selection,text,opts){if(!opts)opts={};selection.obj.value=selection.obj.value.substring(0,selection.start)+text+selection.obj.value.substring(selection.end,selection.obj.value.length);if(is_opera){selection.end=selection.start+text.replace(/\r?\n/g,'\r\n').length;}else{selection.end=selection.start+text.length;}if(opts.startofs)selection.start+=opts.startofs;if(opts.endofs)selection.end-=opts.endofs;if(opts.nosel)selection.start=selection.end;setSelection(selection);}function insertTags(textAreaID,tagOpen,tagClose,sampleText){var txtarea=$(textAreaID);var selection=getSelection(txtarea);var text=selection.getText();var opts;if(text.charAt(text.length-1)==' '){selection.end--;text=selection.getText();}if(!text){text=sampleText;opts={startofs:tagOpen.length,endofs:tagClose.length};}else{opts={nosel:true};}text=tagOpen+text+tagClose;pasteText(selection,text,opts);}function insertAtCarret(textAreaID,text){var txtarea=$(textAreaID);var selection=getSelection(txtarea);pasteText(selection,text,{nosel:true});}var pickercounter=0;function initToolbar(tbid,edid,tb,allowblock){var toolbar=$(tbid);if(!toolbar)return;var edit=$(edid);if(!edit)return;if(edit.readOnly)return;if(typeof allowblock==='undefined'){allowblock=true;}toolbar.innerHTML='';var cnt=tb.length;for(var i=0;i5)lvl=5;var tags='=';for(var i=0;i<=5-lvl;i++)tags+='=';insertTags(edid,tags+' ',' '+tags+"\n",props['text']);pickerClose();return false;}function addBtnActionPicker(btn,props,edid){var pickerid='picker'+(pickercounter++);createPicker(pickerid,props,edid);addEvent(btn,'click',function(){pickerToggle(pickerid,btn);return false;});return true;}function addBtnActionLinkwiz(btn,props,edid){linkwiz.init($(edid));addEvent(btn,'click',function(){linkwiz.toggle();return false;});return true;}function pickerToggle(pickerid,btn){var picker=$(pickerid);if(picker.style.marginLeft=='-10000px'){var x=findPosX(btn);var y=findPosY(btn);picker.style.left=(x+3)+'px';picker.style.top=(y+btn.offsetHeight+3)+'px';picker.style.marginLeft='0px';picker.style.marginTop='0px';}else{picker.style.marginLeft='-10000px';picker.style.marginTop='-10000px';}}function pickerClose(){var pobjs=getElementsByClass('picker');for(var i=0;i3){field.value=field.value.substr(0,linestart)+field.value.substr(linestart+2);selection.start=selection.start-2;selection.end=selection.start;}else{field.value=field.value.substr(0,linestart)+field.value.substr(selection.start);selection.start=linestart;selection.end=linestart;}setSelection(selection);e.preventDefault();return false;}}else if(e.keyCode==32){var match=search.match(/(\n +)([*-] )$/);if(match){field.value=field.value.substr(0,linestart)+' '+field.value.substr(linestart);selection.start=selection.start+2;selection.end=selection.start;setSelection(selection);e.preventDefault();return false;}}}addInitEvent(function(){var field=$('wiki__text');if(!field)return;if(is_opera){addEvent(field,'keypress',keyHandler);}else{addEvent(field,'keydown',keyHandler);}});function currentHeadlineLevel(textboxId){var field=$(textboxId);var selection=getSelection(field);var search="\n"+field.value.substr(0,selection.start);var lasthl=search.lastIndexOf("\n==");if(lasthl==-1&&field.form.prefix){search=field.form.prefix.value;lasthl=search.lastIndexOf("\n==");}search=search.substr(lasthl+1,6);if(search=='======')return 1;if(search.substr(0,5)=='=====')return 2;if(search.substr(0,4)=='====')return 3;if(search.substr(0,3)=='===')return 4;if(search.substr(0,2)=='==')return 5;return 0;}var textChanged=false;function deleteDraft(){if(is_opera)return;var dwform=$('dw__editform');if(dwform){var params='call=draftdel';params+='&id='+encodeURIComponent(dwform.elements.id.value);var sackobj=new sack(DOKU_BASE+'lib/exe/ajax.php');sackobj.asynchronous=false;sackobj.method='GET';sackobj.AjaxFailedAlert='';sackobj.encodeURIString=false;sackobj.runAJAX(params);}}addInitEvent(function(){var editform=$('dw__editform');if(!editform)return;var edit_text=$('wiki__text');if(edit_text){if(edit_text.readOnly)return;edit_text.focus();}var checkfunc=function(){textChanged=true;summaryCheck();};addEvent(editform,'change',checkfunc);addEvent(editform,'keydown',checkfunc);window.onbeforeunload=function(){if(textChanged){return LANG.notsavedyet;}};window.onunload=deleteDraft;addEvent($('edbtn__save'),'click',function(){textChanged=false;});addEvent($('edbtn__preview'),'click',function(){textChanged=false;});var summary=$('edit__summary');addEvent(summary,'change',summaryCheck);addEvent(summary,'keyup',summaryCheck);if(textChanged)summaryCheck();});function summaryCheck(){var sum=document.getElementById('edit__summary');if(sum.value===''){sum.className='missing';}else{sum.className='edit';}}function locktimer_class(){this.sack=null;this.timeout=0;this.timerID=null;this.lasttime=null;this.msg='';this.pageid='';};var locktimer=new locktimer_class();locktimer.init=function(timeout,msg,draft){locktimer.timeout=timeout*1000;locktimer.msg=msg;locktimer.draft=draft;locktimer.lasttime=new Date();if(!$('dw__editform'))return;locktimer.pageid=$('dw__editform').elements.id.value;if(!locktimer.pageid)return;locktimer.sack=new sack(DOKU_BASE+'lib/exe/ajax.php');locktimer.sack.AjaxFailedAlert='';locktimer.sack.encodeURIString=false;locktimer.sack.onCompletion=locktimer.refreshed;addEvent($('dw__editform'),'keypress',function(){locktimer.refresh();});locktimer.reset();};locktimer.reset=function(){locktimer.clear();locktimer.timerID=window.setTimeout("locktimer.warning()",locktimer.timeout);};locktimer.warning=function(){locktimer.clear();alert(locktimer.msg);};locktimer.clear=function(){if(locktimer.timerID!==null){window.clearTimeout(locktimer.timerID);locktimer.timerID=null;}};locktimer.refresh=function(){var now=new Date();if(now.getTime()-locktimer.lasttime.getTime()>30*1000){var params='call=lock&id='+encodeURIComponent(locktimer.pageid);var dwform=$('dw__editform');if(locktimer.draft&&dwform.elements.wikitext){params+='&prefix='+encodeURIComponent(dwform.elements.prefix.value);params+='&wikitext='+encodeURIComponent(dwform.elements.wikitext.value);params+='&suffix='+encodeURIComponent(dwform.elements.suffix.value);if(dwform.elements.date){params+='&date='+encodeURIComponent(dwform.elements.date.value);}}locktimer.sack.runAJAX(params);locktimer.lasttime=now;}};locktimer.refreshed=function(){var data=this.response;var error=data.charAt(0);data=data.substring(1);$('draft__status').innerHTML=data;if(error!='1')return;locktimer.reset();};var linkwiz={wiz:null,entry:null,result:null,timer:null,sack:null,textArea:null,selected:-1,selection:null,init:function(textArea){linkwiz.sack=new sack(DOKU_BASE+'lib/exe/ajax.php');linkwiz.sack.AjaxFailedAlert='';linkwiz.sack.encodeURIString=false;linkwiz.wiz=document.createElement('div');linkwiz.wiz.id='link__wiz';linkwiz.wiz.className='picker';linkwiz.wiz.style.top=(findPosY(textArea)+20)+'px';linkwiz.wiz.style.left=(findPosX(textArea)+80)+'px';linkwiz.wiz.style.marginLeft='-10000px';linkwiz.wiz.style.marginTop='-10000px';linkwiz.wiz.style.position='absolute';linkwiz.wiz.innerHTML=''+'
    '+LANG['linkto']+'
    '+'';$('dw__editform').parentNode.appendChild(linkwiz.wiz);linkwiz.textArea=textArea;linkwiz.result=$('link__wiz_result');linkwiz.entry=$('link__wiz_entry');var obj;obj=$('link__wiz_close');obj.onclick=linkwiz.hide;linkwiz.sack.elementObj=linkwiz.result;addEvent(linkwiz.entry,'keyup',linkwiz.onEntry);addEvent(linkwiz.result,'click',linkwiz.onResultClick);drag.attach(linkwiz.wiz,$('link__wiz_header'));},onEntry:function(e){if(e.keyCode==37||e.keyCode==39){return true;}if(e.keyCode==27){linkwiz.hide();e.preventDefault();e.stopPropagation();return false;}if(e.keyCode==38){linkwiz.select(linkwiz.selected-1);e.preventDefault();e.stopPropagation();return false;}if(e.keyCode==40){linkwiz.select(linkwiz.selected+1);e.preventDefault();e.stopPropagation();return false;}if(e.keyCode==13){if(linkwiz.selected>-1){var obj=linkwiz.getResult(linkwiz.selected);if(obj){var a=obj.getElementsByTagName('A')[0];linkwiz.resultClick(a);}}else if(linkwiz.entry.value){linkwiz.insertLink(linkwiz.entry.value);}e.preventDefault();e.stopPropagation();return false;}linkwiz.autocomplete();},getResult:function(num){var obj;var childs=linkwiz.result.getElementsByTagName('DIV');obj=childs[num];if(obj){return obj;}else{return null;}},select:function(num){if(num<0){linkwiz.deselect();return;}var obj=linkwiz.getResult(num);if(obj){linkwiz.deselect();obj.className+=' selected';if(obj.offsetTop>linkwiz.result.scrollTop+linkwiz.result.clientHeight){linkwiz.result.scrollTop+=obj.clientHeight;}else if(obj.offsetTop-linkwiz.result.clientHeightlinkwiz.result.scrollTop+linkwiz.result.clientHeight)||(obj.offsetTop-1){var obj=linkwiz.getResult(linkwiz.selected);if(obj){obj.className=obj.className.replace(/ ?selected/,'');}}linkwiz.selected=-1;},onResultClick:function(e){if(e.target.tagName!='A')return;e.stopPropagation();e.preventDefault();linkwiz.resultClick(e.target);return false;},resultClick:function(a){var id=a.title;if(id==''||id.substr(id.length-1)==':'){linkwiz.entry.value=id;linkwiz.autocomplete_exec();}else{linkwiz.entry.value=id;if(a.nextSibling&&a.nextSibling.tagName=='SPAN'){linkwiz.insertLink(a.nextSibling.innerHTML);}else{linkwiz.insertLink('');}}},insertLink:function(title){if(!linkwiz.entry.value)return;var sel=getSelection(linkwiz.textArea);if(sel.start==0&&sel.end==0)sel=linkwiz.selection;var stxt=sel.getText();if(stxt.charAt(stxt.length-1)==' '){sel.end--;stxt=sel.getText();}if(!stxt&&!DOKU_UHC)stxt=title;if(linkwiz.textArea.form['id'].value.indexOf(':')!=-1&&linkwiz.entry.value.indexOf(':')==-1){linkwiz.entry.value=':'+linkwiz.entry.value;}var link='[['+linkwiz.entry.value+'|';if(stxt)link+=stxt;link+=']]';var so=linkwiz.entry.value.length+3;var eo=2;pasteText(sel,link,{startofs:so,endofs:eo});linkwiz.hide();linkwiz.entry.value=linkwiz.entry.value.replace(/(^:)?[^:]*$/,'');},autocomplete:function(){if(linkwiz.timer!==null){window.clearTimeout(linkwiz.timer);linkwiz.timer=null;}linkwiz.timer=window.setTimeout(linkwiz.autocomplete_exec,350);},autocomplete_exec:function(){linkwiz.deselect();linkwiz.result.innerHTML='';linkwiz.sack.runAJAX('call=linkwiz&q='+encodeURI(linkwiz.entry.value));},clear:function(){linkwiz.result.innerHTML='Search for a matching page name above, or browse through the pages on the right';linkwiz.entry.value='';},show:function(){linkwiz.selection=getSelection(linkwiz.textArea);linkwiz.wiz.style.marginLeft='0px';linkwiz.wiz.style.marginTop='0px';linkwiz.entry.focus();linkwiz.autocomplete();},hide:function(){linkwiz.wiz.style.marginLeft='-10000px';linkwiz.wiz.style.marginTop='-10000px';linkwiz.textArea.focus();},toggle:function(){if(linkwiz.wiz.style.marginLeft=='-10000px'){linkwiz.show();}else{linkwiz.hide();}}};var media_manager={keepopen:false,hide:false,align:false,popup:false,id:false,display:false,link:false,size:false,ext:false,treeattach:function(obj){if(!obj)return;var items=obj.getElementsByTagName('li');for(var i=0;i=1){opts+=(optsstart)?'&':'?';if(s=="1"){opts+='100';if(media_manager.ext=='swf'){opts+='x62';}}else if(s=="2"){opts+='200';if(media_manager.ext=='swf'){opts+='x123';}}else if(s=="3"){opts+='300';if(media_manager.ext=='swf'){opts+='x185';}}}if(media_manager.align=='1'){alignleft='';alignright=' ';}if(media_manager.align=='2'){alignleft=' ';alignright=' ';}if(media_manager.align=='3'){alignleft=' ';alignright='';}}}opener.insertTags('wiki__text','{{'+alignleft+id+opts+alignright+'|','}}','');if(!media_manager.keepopen)window.close();opener.focus();return false;},list:function(event,link){var ajax=new sack(DOKU_BASE+'lib/exe/ajax.php');ajax.AjaxFailedAlert='';ajax.encodeURIString=false;if(ajax.failed)return true;cleanMsgArea();var content=$('media__content');content.innerHTML='...';ajax.elementObj=content;ajax.afterCompletion=function(){media_manager.selectorattach(content);media_manager.confirmattach(content);media_manager.updatehide();media_manager.initFlashUpload();};ajax.runAJAX(link.search.substr(1)+'&call=medialist');return false;},toggle:function(event,clicky){var listitem=clicky.parentNode;var sublists=listitem.getElementsByTagName('ul');if(sublists.length){listitem.removeChild(sublists[0]);clicky.src=DOKU_BASE+'lib/images/plus.gif';return false;}var link=listitem.getElementsByTagName('a')[0];var ajax=new sack(DOKU_BASE+'lib/exe/ajax.php');ajax.AjaxFailedAlert='';ajax.encodeURIString=false;if(ajax.failed)return true;var ul=document.createElement('ul');listitem.appendChild(ul);ajax.elementObj=ul;ajax.afterCompletion=function(){media_manager.treeattach(ul);};ajax.runAJAX(link.search.substr(1)+'&call=medians');clicky.src=DOKU_BASE+'lib/images/minus.gif';return false;},suggest:function(){var file=$('upload__file');var name=$('upload__name');if(!file||!name)return;var text=file.value;text=text.substr(text.lastIndexOf('/')+1);text=text.substr(text.lastIndexOf('\\')+1);name.value=text;},initFlashUpload:function(){if(!hasFlash(8))return;var oform=$('dw__upload');var oflash=$('dw__flashupload');if(!oform||!oflash)return;var clicky=document.createElement('img');clicky.src=DOKU_BASE+'lib/images/multiupload.png';clicky.title=LANG['mu_btn'];clicky.alt=LANG['mu_btn'];clicky.style.cursor='pointer';clicky.onclick=function(){oform.style.display='none';oflash.style.display='';};oform.appendChild(clicky);},closePopup:function(event){$('media__popup').style.display='none';},setalign:function(event,cb){if(cb.value){DokuCookie.setValue('align',cb.value);media_manager.align=cb.value;media_manager.outSet("media__alignbtn0");media_manager.outSet("media__alignbtn1");media_manager.outSet("media__alignbtn2");media_manager.outSet("media__alignbtn3");media_manager.inSet("media__alignbtn"+cb.value);}else{DokuCookie.setValue('align','');media_manager.align=false;}},setlink:function(event,cb){if(cb.value){DokuCookie.setValue('link',cb.value);media_manager.link=cb.value;media_manager.outSet("media__linkbtn1");media_manager.outSet("media__linkbtn2");media_manager.outSet("media__linkbtn3");media_manager.outSet("media__linkbtn4");media_manager.inSet("media__linkbtn"+cb.value);var size=document.getElementById("media__size");var align=document.getElementById("media__align");if(cb.value!='4'){size.style.display="block";align.style.display="block";}else{size.style.display="none";align.style.display="none";}}else{DokuCookie.setValue('link','');media_manager.link=false;}},setdisplay:function(event,cb){if(cb.value){DokuCookie.setValue('display',cb.value);media_manager.display=cb.value;media_manager.outSet("media__displaybtn1");media_manager.outSet("media__displaybtn2");media_manager.inSet("media__displaybtn"+cb.value);}else{DokuCookie.setValue('display','');media_manager.align=false;}},outSet:function(id){var ele=document.getElementById(id);if(ele==null)return;ele.style.borderStyle="outset";},inSet:function(id){var ele=document.getElementById(id);if(ele==null)return;ele.style.borderStyle="inset";},setsize:function(event,cb){if(cb.value){DokuCookie.setValue('size',cb.value);media_manager.size=cb.value;for(var i=1;i<=4;++i){media_manager.outSet("media__sizebtn"+i);}media_manager.inSet("media__sizebtn"+cb.value);}else{DokuCookie.setValue('size','');media_manager.width=false;}}};addInitEvent(function(){media_manager.treeattach($('media__tree'));media_manager.selectorattach($('media__content'));media_manager.confirmattach($('media__content'));media_manager.attachoptions($('media__opts'));media_manager.initpopup();media_manager.initFlashUpload();});addInitEvent(function(){var form=$('subscribe__form');if(!form){return;}var styleradios={};function update_state(){if(!this.checked){return;}if(this.value.match(/:$/)){styleradios.list.parentNode.style.display='';}else{styleradios.list.parentNode.style.display='none';if(styleradios.list.checked){styleradios.digest.checked='checked';}}}var cur_sel=null;var inputs=form.getElementsByTagName('input');for(var i=0;i/g,">");return str;},treetoggle:function(clicky){var listitem=clicky.parentNode.parentNode;var sublists=listitem.getElementsByTagName('ul');if(sublists.length){listitem.removeChild(sublists[0]);clicky.src=DOKU_BASE+'lib/images/plus.gif';clicky.alt='+';return false;}var link=listitem.getElementsByTagName('a')[0];var ajax=new sack(DOKU_BASE+'lib/plugins/acl/ajax.php');ajax.AjaxFailedAlert='';ajax.encodeURIString=false;if(ajax.failed)return true;var ul=document.createElement('ul');listitem.appendChild(ul);ajax.elementObj=ul;ajax.setVar('ajax','tree');var frm=$('acl__detail').getElementsByTagName('form')[0];ajax.setVar('current_ns',encodeURIComponent(frm.elements['ns'].value));ajax.setVar('current_id',encodeURIComponent(frm.elements['id'].value));ajax.runAJAX(link.search.substr(1));clicky.src=DOKU_BASE+'lib/images/minus.gif';return false;},treehandler:function(e){if(e.target.src){acl.treetoggle(e.target);}else if(e.target.href){var obj=getElementsByClass('cur',$('acl__tree'),'a');for(var i=0;i-1){frm.elements['ns'].value='';frm.elements['id'].value=acl.hsc(acl.parseatt(e.target.search)['id']);}else if(e.target.className.search(/idx_dir/)>-1){frm.elements['ns'].value=acl.hsc(acl.parseatt(e.target.search)['ns']);frm.elements['id'].value='';}acl.loadinfo();}e.stopPropagation();e.preventDefault();return false;}};addInitEvent(acl.init);function isBlank(s){if((s===null)||(s.length===0)){return true;}for(var i=0;i$/)[1];folded_hide=eStrings[1].innerHTML.match(/^$/)[1];var folds=getElementsByClass('folder');for(var i=0;i');function usrmgr_delconfirm(){if($('usrmgr__del')){addEvent($('usrmgr__del'),'click',function(){return confirm(reallyDel);});}};addInitEvent(usrmgr_delconfirm);addInitEvent(function(){var btns=getElementsByClass('btn_incledit',document,'form');for(var i=0;i + + + + references:software [WikiNIC] + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    + +
    + + + +
    +
    + + +
    +
    +
    + +
    +
      +
    + +
    +
    + + + + +
    + + +
    + + + + + +

    Intro

    +
    + +

    +This site contains information related to specific programs. For general + information on how to install programs, back up your system etc., see Computers. +

    + +
    +
    +

    Directories

    +
    + + +
    +
    +

    Main Programs by Topic

    +
    + + +
    +
    +

    BESA

    +
    + +

    + +This is a program for analysing EEG data, with its main focus on dipole source localization (hence the name: Brain Electrical Source Analysis; see Source Analysis). It allows EEG preprocessing and waveshape analysis, + although not in a very straightforward way. In newer versions, it +apparently includes distributed source localization algorithms as well. +Supposedly it communicates directly with BrainVoyager + (another commercial program…), but since few people in the lab use +either of the two, this hasn't been tested yet. Installation CDs are +somewhere in the lab, but the program works with a USB hardlock, of +which we have 2 (one at the CISA, one at the CMU). +

    + +
    +
    +

    BrainStorm

    +
    + +

    +BrainStorm is an integrated free Matlab toolkit dedicated to +Magnetoencephalography (MEG) and Electroencephalography (EEG) data +visualization and processing. Our intention is to make a comprehensive +set of tools available to the scientific community involved in MEG/EEG +experimental research. +

    + +

    +http://neuroimage.usc.edu/brainstorm/ +

    + +
    +
    +

    BrainVISA / Anatomist

    +
    + +

    + +BrainVISA is a signal processing factory and database manager while +Anatomist is its companion visualization software. BrainVisa is mainly +oriented towards MR data though it now includes MEG-EEG processing +through add-ons. +

    + +

    +BrainVISA is distributed with a toolbox of building blocks dedicated to +the segmentation of T1-weighted MR images. The product of the main +assembly ine made up from this toolbox is the following: grey/white +classification for Voxel Based Morphometry, Meshes of each hemisphere +surface for visualization purpose, Spherical meshes of each hemisphere +white matter surface, a graph of the cortical folds, a labeling of the +cortical folds according to a nomenclature of the main sulci. + +

    + + +
    +
    +

    BrainVision Analyzer

    +
    + +

    + +This is standard software for many types of EEG analyses and can be found on the BrainProducts website under Downloads. Use the following codes in order to download the program or any add-ons provided on the website: + +

    +
      +
    • User: N20022
      +
    • +
    • Password: 100442
      +
    • +
    + +

    + +Both the older (1.05) and newer (2.0) versions can be run using a +network licence that is currently located in Sophie's office at the CMU +(it belongs to the Functional Brain Mapping Lab, + though). If no local USB licence is found, the program checks the local + network and should generally work fine - if the network and Sophie's +computer are working, and if no more than one other person is working +with the program at the same time (there's only 2 network licences for +the time being). +

    + +

    +The manual for version 1.05 can be found here, but it's also included in the program help. Here's a workshop tutorial, also for the old version. +

    + +
    +
    +

    BrainVoyager

    +
    + +

    +This is a commercial brain analysis software which gives nice images. +The lab has 4 licences (see Markus or Karsten), and we are still waiting + for somebody to try making it work!! +

    + +
    +
    +

    Caret and Surface-Based Atlases

    +
    + +

    +Caret (Computerized Anatomical Reconstruction and Editing Toolkit) is +designed for interactively viewing, manipulating, and analyzing surface +reconstructions of the cerebral and cerebellar cortex: http://brainvis.wustl.edu/caret/ +

    + +
    +
    +

    Cartool

    +
    + +
    +
    +

    Cogent

    +
    + +

    + +A Matlab toolbox for designing experiments, containing commands useful +for presenting scanner-synchronised visual stimuli (Cogent Graphics), +auditory stimuli, mechanical stimuli, and taste and smell stimuli. It is + also used in monitoring key presses and other physiological recordings +from the subject +

    + + +

    + +Many of us use Cogent because it is very flexible in presenting and +creating various stimuli (except for video). If you are looking for a +script of a task that you think someone in the lab has used before, just + send an e-mail to labnic@unige.ch . + +

    + +
    +
    +

    DTIStudio

    +
    + +

    + +This software is a more easygoing tool for Diffusion-MR analysis. It comes form Johns-Hopkins Hospital (Prof. Susumu Mori)
    + +

    + + +

    + +For download you'll need a registration with username and password (use: gschwind and soscmu).
    +It then gives you just an exe-file which you can place in your programs folder and add a link to it. +

    + +

    +How to work with DTIStudio +

    + +
    +
    +

    E-Prime

    +
    + +
    +
    +

    Finding existing experiments

    +
    + +

    + +Additionnaly to those you can find in the lab, ~100 different scripts are available here: http://step.psy.cmu.edu/ +You need to browse the 4 different scripts categories +

    + +
    +
    +

    Installing E-Prime 2.0 Professional

    +
    + +

    +It is possible to download the new version of E-Prime directly from the Internet (Build 2.0.8.22). To do so, you have to go to pstnet's website and log in with the following informations so you'll be able to enter the downloading page: +

    +
      +
    • E-Mail : Sophie.Schwartz@medecine.unige.ch
      +
    • +
    • Password : soscmu
      +
    • +
    + +

    + +Once logged in, you have to click on the “download†tab on the left of +the page. Click then “E-Prime†and “E-Prime 2 release candidateâ€. Now +you reach the last step of the download, by clicking on “E-Prime 2.0 +Professional Release Candidate Installation (78MB) Build 2.0.8.22†and +saving it on your computer. +

    + +

    +Once the program installer is saved on your computer, launch it and +connect the appropriate USB Eprime key. Once installed, you are asked +information such as username, institution & serial. For the serial, +enter: +-the Beta-users serial given by the installer in round brackets. +-or (Eprime2) simply the serial# linked to the USB key +

    + +

    +Upgrade Note: If you upgrade to a new version of Eprime, you will be asked to remove the old version before getting the new Mac Coy. +

    + +

    +-If you have problem installing E-prime when running the file â€.exe†+with a message of the installation program saying “you need update you +.NET frameworkâ€, that mean that you maybe not have in your pc the +appropriate program .NET, you can downalod this program going Microsoft .NET Framework and after retry to run the .exe file of eprime. +

    + +

    + Compatibility: You can have installed both versions (1.1 et 2.0) at the same time on your Computer. The compatibility works from new to old, but not from old to new, i.e. you can open eprime.es scripts with Eprime 2.0, but not eprime.es2 with Eprime 1.1. +

    + +
    +
    +

    Sending Trigger Signals with E-Prime 2.0 Professional

    +
    + +

    + +There seem to be different ways of sending triggers through a parallel port, but all of them require an inline object. Here a quick overview: +

    + +
    + +

    WritePort

    +
    + +

    + +Using this function you can write a value (1-255) to the parallel port (e.g. address: &H378). For example you can have an inline object before your stimulus is presented: + +

    +
    WritePort &H378, 4               'write value '4' to address '&H378'
    +sleep 5                          'wait 5 ms, so the target device can capture the trigger
    +WritePort &H378, 0               'reset port value to '0'
    + +

    +Be aware that:
    + +

    +
      +
    1. The sleep function pauses your script and therefore introduces a delay.
      +
      +
    2. +
    3. The trigger should be wide enough compared to the sampling frequency of the target device.
      +
      +
    4. +
    + +

    +Alternatively, use two inline objects, one before stimulus onset (write value to port) and the other after stimulus offset (reset). +

    + +
    + +

    Object.OnsetSignalEnabled .OffsetSignalEnabled

    +
    + +

    + +Here you specify port address and value for a certain object (e.g. +TextDisplay1). Then the trigger is sent automatically at object onset +and/or at object offset. +

    +
    'enable the signal data
    +TextDisplay1.OnsetSignalEnabled = True
    +TextDisplay1.OffsetSignalEnabled = True
    + 
    +'assign the port values 
    +TextDisplay1.OnsetSignalPort = &H378
    +TextDisplay1.OffsetSignalPort = &H378
    + 
    +'set the offset and onset data values
    +TextDisplay1.OnsetSignalData = &HFF 
    +TextDisplay1.OffsetSignalData = &H00
    + +

    +See also http://www.unicog.org/pmwiki/pmwiki.php/Main/EPrime. +

    + +
    + +

    help for beginner with Eprime

    +
    + +

    + +-In this zip_eprime_help}, + you can find some good introduction for e-prime, e-basic script and +many useful kind of tips, by exemple how to load picture without add +delay to your experiment or create internal drawings with e-basic codes. + Some of them are in french. +

    + +

    +In those wepage, you will find some interesting forum where you can have a look to resolve your problem: +

    + +

    +-the official one: http://www.pstnet.com/forum/ +if you are logged in, you can also send an email to the supervisor, who sometimes have great solutions. +

    + +

    +-the google group: +http://groups.google.com/group/e-prime/topics +

    + +
    +
    +

    Excel

    +
    +
      +
    • There is always another way to do what you do with it. See the many tips on this page: http://www.mvps.org/dmcritchie/excel/excel.htm
      +
    • +
    • Excel 2003 is unable to open the .xlsx files produced with Excel 2007. You can download a patch here that lets you do the trick.
      +
    • +
    + +
    +
    +

    FreeSurfer

    +
    + +

    +FreeSurfer is an MRI brain imaging software package developed by the +Athinoula A. Martinos Center for Biomedical Imaging at Massachusetts +General Hospital with support from CorTechs Labs, Inc, La Jolla, CA. https://surfer.nmr.mgh.harvard.edu/ +

    + +

    +Its specialty is the surface based analysis. Best segmentation ever +(takes 30 hours) with sophisticated quantification tools, and surface +based corregistration and visualisation of activations on the inflated +hemisphere. +

    + +

    +Freesurfer works only under LINUX and is installed on our machine Montblanc. +

    + +
    +
    +

    FSL

    +
    + +

    +FMRIB Software Library (FSL) is a software library providing a serious +offer for all kind of brain image analyis and statistical tools for: + +

    +
      +
    • functional (FEAT = GLM analyis, MELODIC = ICA analyis)
      +
    • +
    • structural (FAST, SIENAx, FSL_VBM)
      +
    • +
    • and diffusion MRI (FDT, probabilistic tractography, TBSS = tract based spacial statistics).
      +
    • +
    + +

    + +In LabNIC it has so far been used to do DTI analysis and probabilistic tractography. + +

    + + + +

    + +Installation instructions : Windows Installation +

    + +
    +
    +

    ImageJ

    +
    + +

    +ImageJ is a very powerful (but light thus fast) image processing +freeware that runs on Linux, Mac & Windows. besides converting, +resizing, changing contrast, luminosities and these kinds of tricks, +ImageJ can work as calculator to compute various properties (mean luminance, spatial frequencies…) in your images and save them in a spreadsheet. +

    + +

    + +http://rsbweb.nih.gov/ij/ +

    + +
    +
    +

    MATLAB

    +
    + +
    +
    +

    Intro

    +
    + +

    + +No neuroscientist without MATLAB ;-). + It's a programming language and environment that allows you to do +almost anything, probably including making coffee. An important strength + of MATLAB is the availability of numerous so-called toolboxes for data +acquisition, processing and analysis, many of them available free of +charge (as opposed to MATLAB itself). For example, Cogent, (parts of) mrVista, SPM and others are not stand-alone programs but MATLAB toolboxes. + +

    + + +
    +
    +

    Installation

    +
    + +

    + +All of the newer MATLAB versions (i.e. 7.0 upwards) use a licence server and during installation will ask you for this file detailing the address of the server. Note + that this file is NOT a zip file, so don't unpack it, but just rename +it to licence.dat, put it wherever you want, and tell the MATLAB +installer where you put it. +

    +
      +
    • in our experience, the a-versions of + newer MATLAB releases (e.g. 2006a, 2007a) contain more bugs than the +b-versions, which are usually released in late summer/early autumn.
      +
    • +
    • A good version is the Version 2007b which is not anymore available on GDL but which is on labnic ftp.
      +
      +
    • +
    • Here is how to install it matlab-inst.pdf.
      +
    • +
    • After you did everything carefully it still does not run; this is a bug and you will have to add a system environement variable in order to make it work: Desktop + > right click on My Computer > Properties > Advanced > +Environement Variables > New > name: MATLAB_RESERVE_LO, value: 0
      +
    • +
    • Then click OK.
      +
    • +
    • Now Matlab should do all you want!
      +
    • +
    + +
    +
    +

    Toolboxes

    +
    + +

    + +This is a list of useful Matlab toolboxes. It doesn't include SPM-based toolboxes (xjView, etc) which are listed below. + +

    +
      +
    • Cogent: + A toolbox dedicated to stimulus presentation (images, sounds, movies,…) + with high temporal control. Some would see this as a hurdle, but other +will appreciate that being a MATLAB toolbox, you can take profit of the +full range of commands within your script (randomization, computation, +etc.) Don't be put off by the website, which is not very well designed +and really doesn't contain a lot of information. Note that only the +latest version runs under MATLAB 7! Thus, if you have older scripts, you + will either have to use an old MATLAB or you may have to adapt them a +bit. If accurate response times (keyboard responses) are important make +sure that config_keyboard is set in mode 'exclusive'. However in this +mode the Cogent script can not be interrupted (see Cogent manual for +config_keyboard).
      +
    • +
    • EEGLAB: + EEG preprocessing, ERPs and, most importantly, time-frequency analyses - + if you can get it to read in your files without complaining about lack +of RAM
      +
    • +
    • GIFT (=Group ICA of fMRI Toolbox) and EEGIFT, as part of the ICA Toolbox. Note that it includes some of the SPM functions.
      +
    • +
    • ILAB Toolbox for eye-tracker analysis. Ask Amal…
      +
    • +
    • mrVista is another MATLAB toolbox for brain imaging analysis, coming form Stanford. Try it out!
      +
    • +
    • PsychToolbox: similar to Cogent, but with an even worse website
      +
    • +
    • SPM is also a MATLAB Toolbox, but as it's so important, it got an extra section (including sub-sections and sub-sub-sections).
      +
    • +
    + +
    +
    +

    "OUT OF MEMORY"

    +
    + +

    +It can occur that you experience an out of memory error. +We are dealing with heavy data (the record is reached by NewSegement of +spm8). It is thus maybe necessary to increase your allocated memory for +MATLAB and the SWAP space of your OS. + +

    + + +

    + This is how the maximum memory looks like on a WinXP machine (type feature memstats) +

    +
     
    +  Physical Memory (RAM):
    +      In Use:                             2697 MB (a8950000)
    +      Free:                                884 MB (374c1000)
    +      Total:                              3582 MB (dfe11000)
    +  Page File (Swap space):
    +      In Use:                             3070 MB (bfea2000)
    +      Free:                               5720 MB (165834000)
    +      Total:                              8790 MB (2256d6000)
    +  Virtual Memory (Address Space):
    +      In Use:                              724 MB (2d46f000)
    +      Free:                               1323 MB (52b71000)
    +      Total:                              2047 MB (7ffe0000)
    +  Largest Contiguous Free Blocks:
    +       1. [at 3e1b8000]                    299 MB (12b48000)
    +       2. [at 30010000]                    217 MB ( d920000)
    +       3. [at 5d36e000]                    142 MB ( 8ea2000)
    +       4. [at 50e10000]                    115 MB ( 73f0000)
    +       5. [at 66308000]                     69 MB ( 45f8000)
    +       6. [at 18bc0000]                     52 MB ( 3440000)
    +       7. [at 6a983000]                     42 MB ( 2abd000)
    +       8. [at 583bd000]                     41 MB ( 29b3000)
    +       9. [at 6f477000]                     37 MB ( 25d9000)
    +      10. [at 16b75000]                     24 MB ( 184b000)
    +                                          ======= ==========
    +                                          1043 MB (413c6000)
    +      ans =
    +      313819136
    + + +
    +
    +

    STATISTIC TOOLBOX LICENCE PROBLEMS

    +
    + +

    + +When running imaging functions you might get the following error: + +

    +
      ??? License checkout failed.
    +  License Manager Error -4
    +  Maximum number of users for Statistics_Toolbox reached.
    +  Try again later.
    +  To see a list of current users use the lmstat utility or contact your License Administrator.
    +  .
    +  Troubleshoot this issue by visiting:
    +  http://www.mathworks.com/support/lme4b
    +  .
    +  Diagnostic Information:
    +  Feature: Statistics_Toolbox
    +  License path: /usr/local/MATLAB2007b/etc/license.dat:/usr/local/MATLAB2007b/etc/*.lic:
    +  FLEXnet Licensing error: -4,132.
    + +

    + +Why? This happens because the UNIGE licence server hosts 23 licences of the MATLAB Statistic Toolbox and more than 23 users want to use this toolbox. +

    + +

    +Information about the licence server is here: +

    + +

    +https://aton.unige.ch/show_licenses.cgi + +

    +
      +
    • Choose MATLAB and click “verifierâ€.
      +
    • +
    • Scroll down to the Statistic Toolbox and see which users use it currently.
      +
    • +
    + +

    + +Sometimes it happens that people from the CUI http://www.cui.unige.ch/LeCUI.html have submitted big computations (jobs) to the cluster, which then uses a licence per node. +

    + +

    +You might want to contact the users and ask them if she/he would like to free some of the licenses… +

    + +
    +
    +

    Notepad++

    +
    + +

    +Notepad++ is a powerful code editor that handles most of programming languages including Matlab. +It is one of the best free advanced text editor and must be included in +you top applications to install. +It is very convenient to use, some of its functions include a great +syntax analyser / run function / auto-completion / string search & +replace in a directory (grep like) / FTP server / script recorder etc … +Supported languages include C, C++, C#, Java, Javascript, XML, HTML, PHP, SQL, Objective-C, CSS, Pascal, Perl, Python, Lua, RC resource, Makefile, ASCII art, Doxygen, INI, batch, ASP and VB/VBS +

    + +
    +
    +

    Morpheus Photo Morpher

    +
    + +

    + +This program helps you to morph two images together (for example, one +face progressively transformed in another). It is very easy to use. You +just have to place some points at strategic places on the two pictures. +You will then obtain several pictures with progressive morphing of the +first image to the second. +

    + +

    +Activation code: NTPPWWYPELJJHHBL + +

    +
      +
    • +
    • +
    • Run Morpheus Photo Morpher and select Help/Activate from the menu bar.
      +
    • +
    • If your trial has completely expired, you may instead see a screen with a button saying Activate.
      +
    • +
    • Once at the activation window, enter your activation code into the box and click the Activate button.
      +
    • +
    • An Internet connection is required when activating.
      +
    • +
    + +
    +
    +

    Psychomorph

    +
    + +

    + +In New Age terminology, a psychomorph is one of three types of +supernormal entities (the others are eximorphs and paramorphs). In our +context, it's also a very useful program to do facial prototyping as a +means of systematically deriving the average feature shape and +colouration from a group of faces. +This information can be used in various ways, including: +-Facilitating facial recognition +-Investigating facial attractiveness +-Investigating asymmetries in facial perception +-Investigating facial ageing +-Manipulating facial characteristics (like masculinity) +

    + +

    +The process of facial prototyping can be broken down into 3 stages: +deliniation of the face shape, calculation of the average shape for a +group of faces , and warping the images into the average shape. These +steps are illustrated below: +(1) Deliniation : The coordinates of standard facial features are marked +(2) Calculation of the average face shape :The average x,y coordinates +of the points marking each feature are found for a group of facial +stimuli. The average of each of these points is used to make our +“average†face. The coordinates of standard facial features are marked +(3) Warping the faces into the average face shape +

    + +

    +References: http://www.perceptionlab.com/WEBPAGE/Transforms/PrototypingDemo/Prototyping.html +Download it here: http://users.aber.ac.uk/bpt/jpsychomorph/ +(edited by Indrit Sinanaj) +

    + +
    +
    +

    Fantamorph

    +
    + +

    +Abrosoft FantaMorph is powerful and easy-to-use morphing software for +the creation of photo morphing pictures and animation (video morphs). +What you can do? Many many things, like create “Average Face†or various + face composites with multiple face photo, improve morphing with the +advanced morphing features: Track Curve, Camera, Filter, Sequence +Effect, etc.Fun trivia: Did you know that Fantamorph was used to morph the age of Rose, in Titanic? +

    + +

    +Also Fantamorph has a smarter 'face localizer' algorithm with improved automatic accuracy. +

    + +

    +To use Fantamorph v 5.2.2, and for other information regarding latest version, please contact indrit.sinanaj@unige.ch. +The software can be used for 30 days (evaluation period). +

    + +

    +Here are a few licences you can use: +

    + +

    +Abrosoft FantaMorph Deluxe v5.2.2 +

    + +

    +Name: Kaizer Soze / CORE +

    + +

    +FGEKFQHJ-U42HR4-8APQMEUJ +

    + +

    +FGA9RCWK-EKG4UW-PPAEK3A9 +

    + +

    +FGMKYR4D-UN7PGG-9MNN8X9T +

    + +

    +Name: Personne +

    + +

    +FGK68M8J-D86WR9-MHPGK7W2 +

    + +

    +FG3T94D8-3FMYPA-W3TNRAG7 +

    + +

    +FGSKE8SS-6WFKWT-D24HRWNC +

    + +

    + +Name: Utilisateur +

    + +

    +FGWA9JE8-8UUAUC-5QJDY4PE +

    + +

    +FGHAUS9K-3KLCU4-FD23ABW8 +

    + +

    +FGUMJ3XC-EA3Q9F-LSTEPC9A +

    + +

    +(edited by Indrit Sinanaj) +

    + +
    +
    +

    MRIcro/MRIcron

    +
    + +

    + +MRIcro +View and manipulate MRI images (supports Analyze file format, no support for NIfTI). +

    + +

    +http://www.sph.sc.edu/comd/rorden/mricro.html +

    + +

    + +MRIcron +MRIcro successor with new features and NIfTI support. +

    + +

    +http://www.sph.sc.edu/comd/rorden/mricron/ +

    + +

    +Especially:
    + +

    +
      +
    • dcm2nii a very fast DICOM_2_Nifti converter tool.
      +
      +
    • +
    • NPM, non-parametric-Lesion-Mapping
      +
      +
    • +
    • Also quite performing visualisations like 3D rendering, multislice images etc.
      +
    • +
    + +
    +
    +

    PDF Modification

    +
    + +

    + +To be able to modify a PDF: +

    + + +
    +
    +

    Presentation

    +
    + +

    +A commercial, stand-alone software for stimulus presentation and logging + responses that runs on Windows. +Main advantages: easier to use than cogent, and more flexible than +E-prime (possesses advanced scripting and programming languages). +The true onsets of events are known with a sub-millisecond temporal +precision. +

    + +

    +http://www.neurobs.com/presentation +

    + +
    +
    +

    SPM

    +
    + +
    +
    + +

    Intro

    +
    + +

    + +SPM 2011 video lectures now available online : http://www.fil.ion.ucl.ac.uk/spm/course/video/ +

    + +

    +SPM is THE software developed in London for the analysis of brain imaging data including fMRI, PET, SPECT, EEG and MEG. Runs under MATLAB. + +

    + + +

    + +Third-party resources: +

    + + +
    +
    +

    Installation

    +
    + +
    + +

    The MEX files issue

    +
    + +

    + +SPM uses some C-programed functions (called MEX files) that make it run faster.
    +The drawback of this is that each of these needs to be compiled + before SPM can run and, this compilation is Operating System-dependant +(the files produced by the compiler will be different under Mac OS, Windows, Linux etc. – even between the different versions of an OS family). +

    + +

    +SPM is provided with some already compiled functions for the most common OS (like Windows XP, MacOS X) but on other OS's (like on a 64-bit Linux/SuSE as the one run on Montblanc) one will have to recompile these functions. +

    + +

    +See the SPMwiki to see how to compile the MEX-files for LINUX! +

    + +

    +See the SPMwiki to get the lastest version of MEX-files for windows and spm2/5/8 +

    + +
    +
    +

    SPM batches

    +
    + +

    +batches_spm8 + +

    + +
    +
    +

    Toolboxes

    +
    + +

    + +SPM comes with many add-ons called toolboxes. These are generally added in the SPM path under the toolbox folder. + +

    +
      +
    • Xjview makes it easy to navigate between contrasts, and play with thresholds) See also: xjViewBrowser by Christoph Hofstetter or xjview_karim
      +
    • +
    • Anatomy Toolbox + is an on-going project mapping the cytoarchitectonic maps of Brodmann +in the MNI/Talairach space with some quantification of the beteen +subject variability.
      +
    • +
    • WFU_Pickatlas + is a software developed in the Functional MRI Laboratory at the Wake +Forest University School of Medicine. It provides a method for +generating ROI masks and to display SPM results only within these ROIs. +Pre-defined ROIs are included (including those from AAL database) but user-specified masks can be used as well.
      +
    • +
    • SnPM + Wonderful plugin for permutation-based non parametrical SPM analysis. +Appropriate (ehm.. necessary) every time you are not sure whether your +volumes comply with the assumptions of standard SPM analysis (too few +subjects, lesion masks, unconventional data, etc.). Allows combined voxel-cluster correction for multiple comparisons. Now works also on SPM8.
      +
    • +
    • IBASPM + is a nice toolbox for automatic labelling/atlassing. You feed in +T1/T2/EPI/PD/PET or SPECT etc., it segments and normalizes it and then +applies different atlasses to the individual subjects brain and you can +extract one of 116 areas in the anatomy of the subject.
      +
      +
        +
      • It is meant to be used under SPM2 and SPM5. However there some adaptaional problems to solve first.
        +
        +
      • +
      • under SPM5: delete/rename the spm_select.m as it will interfere with your version (it comes with an old version from 2005)
        +
      • +
      • under SPM5: ANALYZE-files are not treated properly (l/r-flip during segmentation.
        +
        +
      • +
      • under both: control your atlas +labelling. the odd numbers are left, the even numbers are right. If this + is not the case (spm_display) you must flip the atlas (spm_display and +change the sign of x to -x, then reorient image.)
        +
      • +
      • under SPM2: The use of SPM2 is recommended if you want to treat ANALYZE-files.
        +
      • +
      +
    • +
    • SurfRend + is a nice tool to obtain a Freesurfer-like visualization on inflated +brain-hemispheres of your SPM-created activation maps. It is thus a link + between SPM and Freesurfer.
      +NB: You need to work with Freesurfer afterwards!
      +
    • +
    + +
    +
    +

    Courses

    +
    + +

    +SPM 2011 video lectures are now available online : http://www.fil.ion.ucl.ac.uk/spm/course/video/ + +

    + +
    +
    +

    SPM structures

    +
    + +

    +Updated by SP on Oct 7th 2010 +The following tables detail the content of various SPM structures (SPM2). +

    + +
    + +
    SPM fields in SPM2
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Variable Name Variable Description Originating function
    SPMoverarching catchall-
    SPM.nscan1 x s vector specifying n of scans in session sspm_fMRI_design
    SPM.PPM.Cconditional covariances of parametersspm_spm_Bayes
    SPM.PPM.Cbempirical prior parameter covariancesspm_spm_Bayes
    SPM.PPM.dCcell array of derivative of C w/ respect to l (I think)spm_spm_Bayes
    SPM.PPM.ddCcell array of 2nd derivative of C w/ respect to l (I think)spm_spm_Bayes
    SPM.PPM.lsession-specific hyperparameter meansspm_spm_Bayes
    SPM.Sessstructure array for each session's infospm_fMRI_design
    SPM.Sess.Cuser-specified regressor structurespm_fMRI_design
    SPM.Sess.C.Cregressor values, each in a columnspm_fMRI_design
    SPM.Sess.C.namecell array of regressor namesspm_fMRI_design
    SPM.Sess.colindices into design matrix for effects in session sspm_fMRI_design
    SPM.Sess.Fcarray of F-contrast info for Volterra correctionsspm_Volterra
    SPM.Sess.Fc.iindex (indices) for input j and interactionsspm_Volterra
    SPM.Sess.Fc.namenames pertaining to input j and interactionsspm_Volterra
    SPM.Sess.rowindices into design matrix for scans in session sspm_fMRI_design
    SPM.Sess.Utrial types structure array - each U is a conditionspm_fMRI_design
    SPM.Sess.U.dttime bin length in secs - same as xBF.dtspm_get_ons
    SPM.Sess.U.durduration of trials (scalar or as long as ons) (in UNITS)spm_get_ons
    SPM.Sess.U.namestring: name of trial typespm_get_ons
    SPM.Sess.U.onsvector of onsets (in UNITS)spm_get_ons
    SPM.Sess.U.Puser-defined parametric modulation struct arrayspm_get_ons
    SPM.Sess.U.P.horder of polynomial expansion of parameterspm_get_ons
    SPM.Sess.U.P.isubindices of u corresponding to given parameter polynomial expansions (I don't even know).spm_get_ons
    SPM.Sess.U.P.namestring: 'none', 'time', or user-definedspm_get_ons
    SPM.Sess.U.P.Pvalues of parameters (1 for each ons time)spm_get_ons
    SPM.Sess.U.pstperi-stim vector - n scans long, but values are in seconds (like 0, 2.5, 5, 7.5, 0, 2.5, 5, 7.5, …)spm_get_ons
    SPM.Sess.U.uactual “stick function†matrix - sparsespm_get_ons
    SPM.SPMidstring describing version of SPM and spm_spmspm_spm
    SPM.swdresults directory that SPM.mat sits inspm_spm
    SPM.Vbetafilehandle array of beta images (relative) - spm_vol-stylespm_spm
    SPM.VCbetafilehandles (relative) of Cbeta images)spm_spm_Bayes
    SPM.VHpfilehandles (relative) of hyperparameter imagesspm_spm_Bayes
    SPM.VMfilehandle (relative) of mask imagespm_spm
    SPM.VResMSfilehandle (relative) of Res Mean Square imagespm_spm
    SPM.xBFbasis function structurespm_fMRI_design
    SPM.xBF.bfactual basic function matrix/vector (of values to convolve)spm_get_bf
    SPM.xBF.dtRT / # of bins = time bin length in seconds spm_fMRI_design
    SPM.xBF.lengthwindow length of basis fns (in secs) (i.e., duration of basis function kernel)spm_get_bf
    SPM.xBF.namestring: 'hrf', 'hrf with time deriv…' 'fourier,'… 'finite impulse response', etc.spm_get_bf
    SPM.xBF.orderorder of basis functionsspm_get_bf
    SPM.xBF.T# of time bins per scanspm_fMRI_design
    SPM.xBF.T0reference slice numberspm_fMRI_design
    SPM.xBF.UNITSstring: 'scans' or 'seconds'spm_fMRI_design
    SPM.xBF.VolterraVolterra flag: 1 = no Volterra (1st order expansion); 2 = Volterra modeled (2nd order expansion)spm_fMRI_design
    SPM.xConcontrast structure arrayspm_spm
    SPM.xCon.ccontrast weightsspm_FcUtil
    SPM.xCon.eidfeffective interest degrees of freedom (numerator df)spm_FcUtil
    SPM.xCon.iX0indicator: how was con specified? “c†for t-contrast in con manager, or columns for reduced design matrixspm_FcUtil
    SPM.xCon.namename of contrastspm_FcUtil
    SPM.xCon.STATstatistic type - F, T, or P for prior probability mapspm_FcUtil
    SPM.xCon.Vconfilehandle (relative) for con_*.imgspm_contrasts
    SPM.xCon.Vspmfilehandle (relative) for statistic .imgspm_contrasts
    SPM.xCon.X0reduced design matrix structurespm_FcUtil
    SPM.xCon.X0.ukX0the reduced design matrix itselfspm_FcUtil
    SPM.xCon.X10remaining design space structure (orthogonal to SPM.xCon.X0.ukX0spm_FcUtil
    SPM.xCon.X10.ukX10remainining design space itselfspm_FcUtil
    SPM.xGXglobal variate structspm_fmri_spm_ui
    SPM.xGX.GMgrand mean to scale to (pegged to 100 for individuals)spm_fmri_spm_ui
    SPM.xGX.gSFglobal scaling factor (pegged to GM./rg for individuals)spm_fmri_spm_ui
    SPM.xGX.iGXcalcglobal normalization option ('none' or 'scaling')spm_fmri_spm_ui
    SPM.xGX.rgraw global estimatespm_fmri_spm_ui
    SPM.xGX.sGMscagrand mean scaling method ('scaling of overall grand mean' for individs, usually none for group)spm_fmri_spm_ui
    SPM.xGX.sGXcalcglobal calculation method ('mean vox value' for individual subjects, 'user specified or 'omit' for group analysis)spm_fmri_spm_ui
    SPM.xMmasking structurespm_fmri_spm_ui
    SPM.xM.Iimplicit masking flag (0 = none, 1 = implicit zero/NaN mask, default is zero)spm_fmri_spm_ui
    SPM.xM.Tthreshold masking value (-Inf = none, real = absolute, complex = proportional; default is all ones for individual)
    SPM.xM.THthreshold for masking out voxels - default is 80% of meanspm_fmri_spm_ui
    SPM.xM.VMfilehandles (from spm_vol) for explicit mask imagespm_fmri_spm_ui
    SPM.xM.xsstring description of mask typespm_fmri_spm_ui
    SPM.xsDesdesign description structure - stringsspm_fmri_spm_ui (line 426)
    SPM.xVitemporal non-sphericity structurespm_fmri_spm_ui
    SPM.xVi.Cyspatially whitened Y*Y'spm_spm
    SPM.xVi.CYsome modification of that above used by spm_spm_Bayesspm_spm
    SPM.xVi.formstring description of xVI ('i.i.d.' or 'AR(1)')spm_fmri_spm_ui
    SPM.xVi.hnon-sphericity hyperparametersspm_spm
    SPM.xVi.Vestimated non-sphericity - second pass through spm_spmspm_spm
    SPM.xVi.Vicell array of sparse n x n covariance components (default, i.i.d., assumption is identity matrix)spm_fmri_spm_ui
    SPM.xVol.DIManalysis image dimensions (in voxels)spm_spm
    SPM.xVol.FWHMsmoothness of components - full-width half-max in voxels - 3-element vector (X/Y/Z)spm_spm
    SPM.xVol.iM4×4 mm → voxel matrix for analysis imagesspm_spm
    SPM.xVol.M4×4 voxel → mm matrix for analysis imagesspm_spm
    SPM.xVol.R# of resels in volume + - four-element vector, where R(1) is Euler chars of volume, R(2) is +resel diameter, R(3) is resel surface area, R(4) is volume in resel +spacespm_spm, but see spm_resels_vol
    SPM.xVol.SLebesgue measure or volume - number of voxels in analysisspm_spm
    SPM.xVol.VRpvfilehandle (relative) of resels per voxel imagespm_spm
    SPM.xVol.XYZlist of coordinates in mask (3 by numvoxels), in voxelsspm_spm
    SPM.xXdesign matrix structurespm_fMRI_design
    SPM.xX.Bcovncons x ncons; variance-covariance matrix of paramtere estimates - xX.pKX*xX.V*xX.pKXspm_spm
    SPM.xX.erdfeffective residual degrees of freedom (trRV*trRV) / trRVRVspm_spm
    SPM.xX.iBcolumns of B partition (block effects)spm_fMRI_design
    SPM.xX.iCcolumns of C partition (conditions - all non-block-effect columns in fMRI)spm_fMRI_design
    SPM.xX.iGcolumns of G partition (nuisance covariates - empty for fMRI)spm_fMRI_design
    SPM.xX.iHcolumns of H partition (indicators - empty for fMRI)spm_fMRI_design
    SPM.xX.Kfilter structure array (or matrix; session-specific)spm_fmri_spm_ui
    SPM.xX.K.Hparamhigh-pass cutoff period in secsspm_fmri_spm_ui
    SPM.xX.K.rowrows of appropriate session for this filter - same as SPM.Sess(i).rowspm_fmri_spm_ui
    SPM.xX.K.RTexperiment TR - same as SPM.xY.RTspm_fmri_spm_ui
    SPM.xX.K.X0frequencies to be removed (basis functions from discrete cosine transform of number from Hparam)spm_filter
    SPM.xX.namecell array of column names for every columnspm_fMRI_design
    SPM.xX.nKXdesign matrix scaled for displayspm_spm
    SPM.xX.pKXpseudoinverse of K*W*X, the filtered/whitened design matrixspm_spm
    SPM.xX.trRVtrace of R*V (don't know what R is here…residuals?)spm_spm
    SPM.xX.trRVRVtrace of RVRV, obviouslyspm_spm
    SPM.xX.Vcorrelations after filtering & whitening - K*W*Vi*W'*K', supposedly; nscans x nscansspm_spm
    SPM.xX.Wwhitening/weighting matrix to give WLS estimates (W*W') = inv(xVi.V); nscans x nscansspm_spm
    SPM.xX.Xactual design matrix - valuesspm_fMRI_design
    SPM.xX.xKXsspace structure for filtered/whitened design matrix (K*W*X)spm_spm
    SPM.xX.xKXs.dsvectors for singular valuesspm_sp
    SPM.xX.xKXs.oPorthogonal projector on X (optional)spm_sp
    SPM.xX.xKXs.oPporthogonal projector on X' (optional)spm_sp
    SPM.xX.xKXs.rkrank of matrixspm_sp
    SPM.xX.xKXs.sussubspace of this space (optional)spm_sp
    SPM.xX.xKXs.toltolerance for errorspm_sp
    SPM.xX.xKXs.uu in X = u*diag(ds)*v'spm_sp
    SPM.xX.xKXs.ups“space in which this one is embedded†- all right. (optional)spm_sp
    SPM.xX.xKXs.vv in abovespm_sp
    SPM.xX.xKXs.Xdesign matrix againspm_sp
    SPM.xYimage files data structspm_fmri_spm_ui
    SPM.xY.Pimage files filenames (including volumes in 4D files)spm_fmri_spm_ui
    SPM.xY.RTexperimental RTspm_fmri_spm_ui
    SPM.xY.VYimage files filehandles (from spm_vol)spm_fmri_spm_ui
    +
    +
    + +
    Multivariate analyses with SPM5
    +
    + +

    + +Mulitvariate analyses with n-variate response variables are supported +and automatically invoke a ManCova and CVA in spm_spm. Multivariate +designs are, at the moment limited to Basic and PET designs.
    + +MG; 05/2011 (c.f. help spm_spm_ui) +

    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Variable Name Variable Description
    SPM.xY.P image files filenames (including volumes in 4D files)
    SPM.xY.VY nScan x 1 struct array of memory mapped images (see spm_vol for definition of the map structure)
    SPM.xX structure describing design matrix
    SPM.xX.D design definition structure (See definition in main body of spm_spm_ui.m)
    SPM.xX.I nScan x 4 matrix of factor level indicators I(n,i) is the level of factor i corresponding to image n
    SPM.xX.sF 1×4 cellstr containing the names of the four factors xX.sF{i} is the name of factor i
    SPM.xX.X design matrix
    SPM.xX.xVi correlation constraints for non-spericity correction
    SPM.xX.iH vector of H partition (condition effects) indices, identifying columns of X correspoding to H
    SPM.xX.iC vector of C partition (covariates of interest) indices
    SPM.xX.iB vector of B partition (block effects) indices
    SPM.xX.iG vector of G partition (nuisance variables) indices
    SPM.xX.name p x 1 cellstr of effect names corresponding to columns of the design matrix
    SPM.xC structure array of covariate details
    SPM.xC(i).rc raw (as entered) i-th covariate
    SPM.xC(i).rcname name of this covariate (string)
    SPM.xC(i).c covariate as appears in design matrix (after any scaling, centering of interactions)
    SPM.xC(i).cname cellstr containing names for effects corresponding to columns of xC(i).c
    SPM.xC(i).iCC covariate centering option
    SPM.xC(i).iCFI covariate by factor interaction option
    SPM.xC(i).type covariate type: 1=interest, 2=nuisance, 3=global
    SPM.xC(i).cols columns of design matrix corresponding to xC(i).c
    SPM.xC(i).descrip cellstr containing a description of the covariate
    SPM.xGX structure describing global options and values
    SPM.xGX.iGXcalc global calculation option used
    SPM.xGX.sGXcalc string describing global calculation used
    SPM.xGX.rg raw globals (before scaling and such like)
    SPM.xGX.iGMsca grand mean scaling option
    SPM.xGX.sGMsca string describing grand mean scaling
    SPM.xGX.GM value for grand mean (/proportional) scaling
    SPM.xGX.gSF global scaling factor (applied to xGX.rg)
    SPM.xGX.iGC global covariate centering option
    SPM.xGX.sGC string describing global covariate centering option
    SPM.xGX.gc center for global covariate
    SPM.xGX.iGloNorm Global normalisation option
    SPM.xGX.sGloNorm string describing global normalisation option
    SPM.xM structure describing masking options
    SPM.xM.T Threshold masking value (-Inf⇒None, real⇒absolute, complex⇒proportional (i.e. times global))
    SPM.xM.TH nScan x 1 vector of analysis thresholds, one per image
    SPM.xM.I Implicit masking (0⇒none, 1⇒implicit zero/NaN mask)
    SPM.xM.VM struct array of explicit mask images (empty if no explicit masks)
    SPM.xM.xs structure describing masking options (format is same as for xsDes described below)
    SPM.xsDes +structure of strings describing the design: Fieldnames are essentially +topic strings (use “_â€'s for spaces), and the field values should be +strings or cellstr's of information regarding that topic. spm_DesRep.m +uses this structure to produce a printed description of the design, +displaying the fieldnames (with “_â€'s converted to spaces) in bold as +topics, with the corresponding text to the right
    SPM.SPMid String identifying SPM and program versions
    +
    +
    + +
    xSPM fields in SPM2
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Variable Name Variable Description Originating function
    xSPMmain results structure containing SPM, distribution, filtering detailsspm_getSPM
    xSPM.dftwo elements: degrees of freedom of interest, degrees of freedom of residualspm_getSPM
    xSPM.DIManalysis image dimensions in voxelsspm_getSPM
    xSPM.Exflag for inclusive/exclusive masking (exclusive=1, inclusive = 0)spm_getSPM
    xSPM.FWHMsmoothness, in voxels, same as SPM.xVol.FWHMspm_getSPM
    xSPM.Iccontrast index in SPM.xConspm_getSPM
    xSPM.Imindex of masking contrastspm_getSPM
    xSPM.iMmm → voxels matrixspm_getSPM
    xSPM.kextent threshold (in voxels)spm_getSPM
    xSPM.Mvoxels → mm matrix (SPM.xVol.M)spm_getSPM
    xSPM.nnumber of conjoint testsspm_getSPM
    xSPM.pmuncorrected p-value for maskingspm_getSPM
    xSPM.Pslist of P values for every voxel - co-indexed with Z and XYZ and XYZmmspm_getSPM
    xSPM.Rsearch volume (resels, elements as SPM.xVol.R)spm_getSPM
    xSPM.Ssearch volume (voxels)spm_getSPM
    xSPM.STATtype of statistic (Z, T, X, F, or P)spm_getSPM
    xSPM.STATstrstatistic description - usually â€<STAT>_<number of df residual>“spm_getSPM
    xSPM.swdresults working directoryspm_getSPM
    xSPM.titlestring: title of comparisonspm_getSPM
    xSPM.uheight threshold (in STAT)spm_getSPM
    xSPM.VOXvoxel dimensions in mmspm_getSPM
    xSPM.Vspmfilehandle (relative) of statistic imagespm_getSPM
    xSPM.XYZlocation of voxels in voxel coords; co-indexed with Zspm_getSPM
    xSPM.XYZmmlocation of voxels in mm coords; co-indexed with Zspm_getSPM
    xSPM.Zcomplete list of stat values for every voxel in SPM (type of stat in STAT)spm_getSPM
    +
    +
    + +
    PPI fields in SPM2
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Variable Name Variable Description Originating function
    PPIoutput structure of PPI codespm_peb_ppi
    PPI.dtlength of time bin in secondsspm_peb_ppi
    PPI.namename of PPIspm_peb_ppi
    PPI.Pthe value of psy.u convolved with HRF for psychophysiologic, or eigenvariate of second region in physiophysiologic interactionsspm_peb_ppi
    PPI.ppivalue of the PPI - psy.u*xn convolved with HRF, or xn1*xn2 convolved with HRFspm_peb_ppi
    PPI.psypsychological variable structurespm_peb_ppi
    PPI.psy.namecell array of names of included variablesspm_peb_ppi
    PPI.psy.uvalues of +psychological variable - essentially same as SPM.Sess.U.u, with 32-bin +offset taken off front; one column for each included varspm_peb_ppi
    PPI.psy.wweights of included variablesspm_peb_ppi
    PPI.xndeconvolved neural signal (multi-column if more than one region)spm_peb_ppi
    PPI.xYoriginal VOI informationspm_peb_ppi
    PPI.Yoriginal bold signal - same as VOI's xY.uspm_peb_ppi
    +
    +
    + +
    xY fields in SPM2
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Variable Name Variable Description Originating function
    xYVolume of interest structure from results “VOI†buttonspm_regions
    xY.defstring: 'sphere', 'box', or 'cluster'spm_regions
    xY.Icindex of contrast used to adjust data (0 = none)spm_regions
    xY.namename of VOIspm_regions
    xY.seigenvalues of yspm_regions
    xY.Sesssession index VOI is extracted fromspm_regions
    xY.specif def = sphere, spec = VOI radius in mm; if def = vox, spec = box xyz dims in mm; if def = cluster, this field doesn’t existspm_regions
    xY.uscaled first eigenvariate of y = approx. mean response of VOIspm_regions
    xY.vfirst eigenimage of yspm_regions
    xY.X0nscans long matrix of whitened confounds, including drift termsspm_regions
    xY.xyzcenter of VOI (mm)spm_regions
    xY.XYZmmcoordinates of voxels (mm)spm_regions
    xY.yfiltered/whitened data, voxel-by-voxelspm_regions
    +
    +
    +
    +

    Filtering with regexp

    +
    + +

    +SP on Oct 7th 2010 +The function and dialog for selecting files in SPM - spm_select - uses +regular expression for filtering. So for example when prompted to select + an SPM.mat file, the filter field already contains 'SPM.mat$' otherwise + the default is usually '.*'. This filter function can be pretty handy, +however, the syntax of regexp is not always intuitive. Here is a short +list of regexp features commonly used. +

    + +

    +For the examples let's assume we have the following files in our directory: + +

    +
    con_0001.hdr
    +con_0001.img
    +T1_my_structural_image.hdr
    +T1_my_structural_image.img
    +T1_my_structural_image.nii
    +wT1_my_structural_image.nii
    +
    + + + + + + + + + + + + + + + + + + + + + +
    Expression Meaning Example Selected Example NOT Selected
    .*
    +
    any character (.) from 0 to infinite times, in short, anythingallnone
    ^T1
    +
    all files starting with 'T1'
    T1_my_structural_image.hdr
    +T1_my_structural_image.img
    +T1_my_structural_image.nii
    +
    con_0001.hdr
    +con_0001.img
    +wT1_my_structural_image.nii
    +
    nii$
    +
    all files ending with 'nii'
    T1_my_structural_image.nii
    +wT1_my_structural_image.nii
    +
    con_0001.hdr
    +con_0001.img
    +T1_my_structural_image.hdr
    +T1_my_structural_image.img
    +
    ^T1.*nii$
    +
    all files starting (^) with 'T1' and ending ($) with 'nii' with anything (.*) in between
    T1_my_structural_image.nii
    +
    con_0001.hdr
    +con_0001.img
    +T1_my_structural_image.hdr
    +T1_my_structural_image.img
    +wT1_my_structural_image.nii
    +
    ^T1.*img$|^T1.*hdr$
    +
    all files starting with 'T1' and ending with 'img' or (|)
    +all files starting with 'T1' and ending with 'hdr'
    T1_my_structural_image.hdr
    +T1_my_structural_image.img
    +
    con_0001.hdr
    +con_0001.img
    +T1_my_structural_image.nii
    +wT1_my_structural_image.nii
    +
    ^T1.*(img|hdr)$
    +
    same as above but in a more compact form due to use of parenthesessame as abovesame as above
    +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    +

    SPSS

    +
    + +

    + +The Statistical Package for the Social Sciences + has been around long enough to bear its weird name: Apparently the +first versions consisted of packages of punched cards which had to be +fed to the ancestors of our PCs. Some of the program's components +(especially the graphics) seem to have remained unchanged since that +era, but it does almost any statistical analysis you might think of, and + then some more. The same applies to the output of most analysis: People + have reportedly starved to death while searching for a particular +t-value. Some hints and remarks: + +

    +
      +
    • The most frequently used modules in our setting would probably be General Linear Model and Regression, although any statistician will tell you that you should first Explore your data before analysing them.
      +
    • +
    • An interesting but frequently under-used feature of the program is the possibility to Paste + your analysis settings to a syntax file, rather than just executing an +analysis by clicking OK. This way, you can re-run the same analysis +without having to go through a thousand clicks, which is especially +useful if you have lots of factors. Also, the syntax is not very +complex, so sometimes it may be easier to change your analysis settings +in the syntax file rather than via the graphical interface.
      +
    • +
    • Another advantage of syntax files is + that it may be easier to recap previous analysis based on the syntax +than on the output. This is probably why newer versions of the program +paste the syntax into the output window by default.
      +
    • +
    • I will add some course material here soon.
      +
    • +
    + +
    +
    +

    Installation

    +
    + +

    + +Usually, at least the current and one previous version of SPSS are +available on GDL. When downloading one of these, you will also receive +an authorization code on your university e-mail account. Using this, you + can licence your version directly via the internet (although this +sometimes causes problems with version 16). Licences are valid for +around one year. If you're version suddenly stops working and displays a + message about missing licences, go to GDL and check for an SPSS licence + text-file, in which new authorization codes are noted, which you can +then use to re-licence your version (just as you did when you first +installed it). Note that version 16 seems to have been adapted for +Windows Vista, particularly in terms of speed - it is terribly slow. +

    +
      +
    • note further that most of SPSS versions ar not compatible with older ones. That means you need that particular version in wich your old data have been calculated… or at least the viewer SPSS Legacy
      +
        +
      • comment by Karsten: I think this is +only the case for version 16 to earlier ones; 13 through 15 interacted +quite nicely; note that you can have several versions on your computer +at the same time.
        +
      • +
      +
    • +
    + +
    +
    +

    STATISTICA

    +
    +
    Changement du No de code licence
    +----------------------------------------------------
    +Pour changer ce code, allez  dans
    +Help --> About Statistica --> License
    +Management --> Enter Code
    +
    +Tapez : 
    +XNVJDFHH6QZXNWLQ8W
    +(nouveau code valable jusqu'au 31.10.2009)
    +
    +Attention !
    +Ce code est valable pour la version 8 uniquement.
    +Si vous possédez une version antérieure, il vaut mieux
    +désinstaller et re-installer la version 8.0.
    + +
    +
    +

    Windows XP

    +
    + +
    +
    +

    Windows addons

    +
    + +

    +Some useful +

    +
    + + + + + + + + + + + + + + + +
    Name Download Description
    Folder Size http://foldersize.sourceforge.net Allows you to display the size of the content of folder in the windows Explorer
    NetDrive http://www.netdrive.net Makes your FTP servers appears as if they were just folders/disks on your PC, for easy copy-paste etc.
    TortoiseSVN http://tortoisesvn.tigris.org Integrates SVN in the right click menu of the Explorer
    Unlocker http://ccollomb.free.fr/unlocker Tell you which program is using the file(s) you want to delete, and let you force the deletion
    + +
    +
    +

    xjview_karim

    +
    + +

    + +It is a Karim-made variant of the xjview toolbox for SPM. + Although the original one already does a very good job, displaying your + results with a cursor that allows you to easily change the p-value +threshold without having to go through the open contrast box again and +again. Plus, it tells you were you are in the brain according to the Brodmann & AAL + atlases. I tweaked it so that you can now also naviguate between +contrasts, and between subjects (if your at the 2nd level). I also added + a feature that can extract betas at a voxels, in a cluster or in +sphere, from each subject of the group. It can also plot correlation +graph in a BOLD-questionnaires study. +

    + +

    +However, although I'm willing to share it, it wasn't meant for public +release: so use it if you want but be aware that it may bug (or show +stupid labels such as “averted gaze†etc. Ask me for the latest version. + Karim +

    + +
    +
    +

    XnView(img batch conversion)

    +
    + +

    + +XnView is THE picture conversion tool, free to use, that let you convert + a bunch of pictures into a very large range of pictures formats +(several 100s). +You can also resize, convert from color to black & white, & +perform several basic operations in one clic. +http://www.xnview.com/fr/index.html + +

    + +
    +
    +

    + Discussion +

    +
    +
    +
    + + , 2009/03/24 10:16 +
    +
    + +

    +Could the Windows addons be moved to Tools & Utilities? + +

    + +
    +
    +
    +
    + + + + + +
    +
    +
    +
    +
    +
    +
    + + , 2009/06/17 00:21 +
    +
    + +

    +Yes, of course! + +

    + +
    +
    +
    +
    + + + + + +
    +
    +
    +
    +
    +
    +
    + + , 2009/03/24 10:16 +
    +
    + +

    +Could xjview_karim be moved to SPM Toolboxes? + +

    + +
    +
    +
    +
    + + + + + +
    +
    +
    +
    +
    +
    +
    + + , 2009/06/17 00:21 +
    +
    + +

    +yes, of course! + +

    + +
    +
    +
    +
    + + + + + +
    +
    +
    +
    +
    +
    +
    + + , 2011/01/08 01:13 +
    +
    + +

    +Could some please updae the licences? + +

    + +
    +
    +
    +
    + + + + + +
    +
    +
    +
    + +
    +
    +
    + + + + + + + + +
    + Enter your comment (wiki syntax is allowed):
    + +
    + + + +
    + +
    +
    +
    +
    +
    + + +
    + +
     
    + + +
    + +
    +
    + Logged in as: Karim N'Diaye (ndiayek)
    +
    + references/software.txt · Last modified: 2012/12/13 11:19 by sinanaj
    +
    + + +
    +
    +
    +
    +
      +
    +
    +
    + +
    + +
    Except where otherwise noted, content on this wiki is licensed under the following license:CC Attribution-Noncommercial-Share Alike 3.0 Unported
    +
    + +
    + + Recent changes RSS feed + + + + Donate + + Powered by PHP + + Valid XHTML 1.0 + + Valid CSS + + Driven by DokuWiki + + + +
    + +
    + + + \ No newline at end of file diff --git a/labnic/references_software [WikiNIC]_fichiers/button-css.png b/labnic/references_software [WikiNIC]_fichiers/button-css.png new file mode 100644 index 0000000000000000000000000000000000000000..706325e1c1b6f7f666a2f655b6874be1d3ee7dd4 GIT binary patch literal 299 zcmV+`0o4A9P)X!LnL50z(5q>K~=a4CjcodXPV4VJ#(T~ivj_KbD4HB-<^MP-5Cc03hfj3 zvVX5FJDvqHaRJw=1q>jBMWOVj`<7e(ax^f4+{m?H71t^d!lF?8=k48-=1+3~dxi^Q zC>J(`%>SqV4Uqf_@ioxIRS<-!ka2(L-yrrM3@i>1H!^?`HiflJJ8S1&>*z}$;A;j; xhH}Z<0W~iK2q^@5moW%rGa-e<#Wk_12LM7iB(v#(0(AfY002ovPDHLkV1n}{eY*ev literal 0 HcmV?d00001 diff --git a/labnic/references_software [WikiNIC]_fichiers/button-donate.gif b/labnic/references_software [WikiNIC]_fichiers/button-donate.gif new file mode 100644 index 0000000000000000000000000000000000000000..bba284e214e3ff94657be76a4b399291bc55768d GIT binary patch literal 187 zcmV;s07U;sNk%w1VNd`M0HFc^iH>>y|NmUfD`sY9EC2ui08juA0007%oR6u??GI0w zwAzca-n^S2gyKk+VF0A+%C_zcyTNDHH5%vo&dvj9698>Ep7GeD5huZ-Y_fu;icTqj zGPNdu#GuI8!ilF@vYDy^Z-*vkGd8}S_aHg~6{$8|%|_hJ_)H|Ur} pc(vE&C4_W|>(N06SCBTRH#$ literal 0 HcmV?d00001 diff --git a/labnic/references_software [WikiNIC]_fichiers/button-dw.png b/labnic/references_software [WikiNIC]_fichiers/button-dw.png new file mode 100644 index 0000000000000000000000000000000000000000..39d5f56a9dff668881444dafbec313e2e89f2287 GIT binary patch literal 427 zcmV;c0aX5pP)IWd0001BP)t-sr?8+) zt3FdxQ-hYB|NsB1tVecyW=m^!`0>i-#IRj?gF9Vn^Jrp;+n%V5cKVxk%2zj`Sv0gI z0?f?Js>agB<<9%~=GfWf=JnuzDg)^0&*jnD`QqWh$g~Qf5^jWz%73Zc00009a7bBm z0000;0000;07l7cJ^%m!@kvBMR5;76liiYoAPj|r$%0@-{4HyxdH=U8tlf^TUz$@c76fljnPG5qklU~goqp9uC^FDcDD|h zQEPp&5(JTm7xG>o9{cL7)OftWWA-!gnzI>vYa z;!Q-N0P^(-ublqPGC?8=@L+QPVX|nZ<+jwK{t;|4CuP+H1NOtU79nGj|7Q7QJphRF V9*n(wPYeJ6002ovPDHLkV1k{Cz_0)S literal 0 HcmV?d00001 diff --git a/labnic/references_software [WikiNIC]_fichiers/button-php.gif b/labnic/references_software [WikiNIC]_fichiers/button-php.gif new file mode 100644 index 0000000000000000000000000000000000000000..19aefb08f5755ccddc1a20b5d352d5bfd33c864a GIT binary patch literal 207 zcmV;=05JbYNk%w1VNd`M0HFc^W@ctFWt{*2|A~%yEC2ui08juA00080gpaAq?GKNb zwAzca-n<(TgyKk+p)jQC%C_zcyTWJIH5%vo&dva63yN^OqOnIiO@=2WaJZsGiBy$z zaK%ni*_Q7K3^xN}sFjPxqEXee`kf{sf5W9SY&Uba@iqLK1(@cQL`G8fG$JSWhQsz3 z1?k3URhVdqd3h4qvk0f<7{}K&rzuoId8C^2bt;s)8cRs$Y6B~ai}lgL+w1#_5g|NG JT&z$406VZOU`hZ0 literal 0 HcmV?d00001 diff --git a/labnic/references_software [WikiNIC]_fichiers/button-rss.png b/labnic/references_software [WikiNIC]_fichiers/button-rss.png new file mode 100644 index 0000000000000000000000000000000000000000..b036f7152bd705536c7e3a7eb2d46d8bbb426102 GIT binary patch literal 280 zcmV+z0q6dSP)3;JraZqAwWxb03f2f+qeis z^y|EuW(-CNGQN_eYI4MLP)}Ka@FqLgV#JiyjD8 zF--H&ccYwR4=k2%kLTH2swXN^uQW#|meA=it&-{0K07HEP@$E}8ZrqhGdAj6_}{(e zJlFTPE8!YahGF$Au0&+4Vi2D-k#b5k-K#6hWfB)cAfiUT!-)8Nmj0b}4R39^mF(R! eugA6axAX>X!J?o2lz+_t0000X!LnL50z(5q>K~=a4CjcodXPV4VJ#(T~ivj_KbD4HB-<^MP-5Cc03hfj3 zvVX5FJDvqHk!uy#0w7|5Fo1vof|+yC=<`<^b^w*Qx~!Acb697z&yHPyZVr`4!@8xQQ^UkrXoS5B(d&{)2(V0q#bq zLa_U|xL|H%tYz9+JNH^gUjhMNGgvZ|OWqEsc_BbZA<(;wK_Ht6DI_kgiA_BK@PQUn TFoEJQ00000NkvXXu0mjfr-Fwn literal 0 HcmV?d00001 diff --git a/labnic/references_software [WikiNIC]_fichiers/cc-by-nc-sa.png b/labnic/references_software [WikiNIC]_fichiers/cc-by-nc-sa.png new file mode 100644 index 0000000000000000000000000000000000000000..1c54f994df06341ae0d3f460eda0c55fa0f92801 GIT binary patch literal 686 zcmV;f0#W^mP)Px#24YJ`L;$M*u>h(JE6XVW000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2iOV> z5GxDMTNG*l00J&aL_t(Y$K_YQZxb;Ten#DBhD(NYtq(?EaFjow+^v8F+(4vmwZ+y- zD5qp6@db$+fZ=A4f&!HaBY}S)iiZZ#P*FJ(DwSv~(J~){rQ_85k#-~G=?0(uefInO z?Dy^vr4$y)mH;3Kf(22_7+cb;r4|7C{eHDteda%(#~`N&?!J0mDwVR-YU7;mf8F2N ze)m5J9_@}8V*nt8FijI7WZSk<%C>EUkZGDi2ttTuS$kjha?X!_;>Ri3PVJ1dQo%W^ za@CB~&UaZVuaq|)ZZsN=cDtR*S-ZJ59*;L4ZO-NAC8ac67^9S;Mf+R&0|N03 UY>){zJpcdz07*qoM6N<$f&;NFn*aa+ literal 0 HcmV?d00001 diff --git a/labnic/references_software [WikiNIC]_fichiers/css.css b/labnic/references_software [WikiNIC]_fichiers/css.css new file mode 100644 index 0000000..79aaee2 --- /dev/null +++ b/labnic/references_software [WikiNIC]_fichiers/css.css @@ -0,0 +1 @@ +a.interwiki{background:transparent url(/wiki/lib/images/interwiki.png) 0px 1px no-repeat;padding-left:16px;}a.iw_wp{background-image:url(/wiki/lib/images/interwiki/wp.gif)}a.iw_wpfr{background-image:url(/wiki/lib/images/interwiki/wpfr.gif)}a.iw_wpde{background-image:url(/wiki/lib/images/interwiki/wpde.gif)}a.iw_wpes{background-image:url(/wiki/lib/images/interwiki/wpes.gif)}a.iw_wppl{background-image:url(/wiki/lib/images/interwiki/wppl.gif)}a.iw_wpjp{background-image:url(/wiki/lib/images/interwiki/wpjp.gif)}a.iw_wpmeta{background-image:url(/wiki/lib/images/interwiki/wpmeta.gif)}a.iw_doku{background-image:url(/wiki/lib/images/interwiki/doku.gif)}a.iw_dokubug{background-image:url(/wiki/lib/images/interwiki/dokubug.gif)}a.iw_amazon{background-image:url(/wiki/lib/images/interwiki/amazon.gif)}a.iw_amazon_de{background-image:url(/wiki/lib/images/interwiki/amazon.de.gif)}a.iw_amazon_uk{background-image:url(/wiki/lib/images/interwiki/amazon.uk.gif)}a.iw_phpfn{background-image:url(/wiki/lib/images/interwiki/phpfn.gif)}a.iw_coral{background-image:url(/wiki/lib/images/interwiki/coral.gif)}a.iw_sb{background-image:url(/wiki/lib/images/interwiki/sb.gif)}a.iw_google{background-image:url(/wiki/lib/images/interwiki/google.gif)}a.iw_meatball{background-image:url(/wiki/lib/images/interwiki/meatball.gif)}a.iw_wiki{background-image:url(/wiki/lib/images/interwiki/wiki.gif)}a.mediafile{background:transparent url(/wiki/lib/images/fileicons/file.png) 0px 1px no-repeat;padding-left:18px;padding-bottom:1px;}a.mf_deb{background-image:url(/wiki/lib/images/fileicons/deb.png)}a.mf_rar{background-image:url(/wiki/lib/images/fileicons/rar.png)}a.mf_cpp{background-image:url(/wiki/lib/images/fileicons/cpp.png)}a.mf_jpg{background-image:url(/wiki/lib/images/fileicons/jpg.png)}a.mf_xls{background-image:url(/wiki/lib/images/fileicons/xls.png)}a.mf_xml{background-image:url(/wiki/lib/images/fileicons/xml.png)}a.mf_css{background-image:url(/wiki/lib/images/fileicons/css.png)}a.mf_csv{background-image:url(/wiki/lib/images/fileicons/csv.png)}a.mf_c{background-image:url(/wiki/lib/images/fileicons/c.png)}a.mf_doc{background-image:url(/wiki/lib/images/fileicons/doc.png)}a.mf_html{background-image:url(/wiki/lib/images/fileicons/html.png)}a.mf_rpm{background-image:url(/wiki/lib/images/fileicons/rpm.png)}a.mf_rtf{background-image:url(/wiki/lib/images/fileicons/rtf.png)}a.mf_zip{background-image:url(/wiki/lib/images/fileicons/zip.png)}a.mf_tar{background-image:url(/wiki/lib/images/fileicons/tar.png)}a.mf_audio{background-image:url(/wiki/lib/images/fileicons/audio.png)}a.mf_sql{background-image:url(/wiki/lib/images/fileicons/sql.png)}a.mf_lua{background-image:url(/wiki/lib/images/fileicons/lua.png)}a.mf_tgz{background-image:url(/wiki/lib/images/fileicons/tgz.png)}a.mf_mp3{background-image:url(/wiki/lib/images/fileicons/mp3.png)}a.mf_cs{background-image:url(/wiki/lib/images/fileicons/cs.png)}a.mf_swf{background-image:url(/wiki/lib/images/fileicons/swf.png)}a.mf_conf{background-image:url(/wiki/lib/images/fileicons/conf.png)}a.mf_sxc{background-image:url(/wiki/lib/images/fileicons/sxc.png)}a.mf_sxd{background-image:url(/wiki/lib/images/fileicons/sxd.png)}a.mf_sxi{background-image:url(/wiki/lib/images/fileicons/sxi.png)}a.mf_sxw{background-image:url(/wiki/lib/images/fileicons/sxw.png)}a.mf_gz{background-image:url(/wiki/lib/images/fileicons/gz.png)}a.mf_gif{background-image:url(/wiki/lib/images/fileicons/gif.png)}a.mf_js{background-image:url(/wiki/lib/images/fileicons/js.png)}a.mf_pptx{background-image:url(/wiki/lib/images/fileicons/pptx.png)}a.mf_pl{background-image:url(/wiki/lib/images/fileicons/pl.png)}a.mf_odc{background-image:url(/wiki/lib/images/fileicons/odc.png)}a.mf_odf{background-image:url(/wiki/lib/images/fileicons/odf.png)}a.mf_odg{background-image:url(/wiki/lib/images/fileicons/odg.png)}a.mf_odi{background-image:url(/wiki/lib/images/fileicons/odi.png)}a.mf_ps{background-image:url(/wiki/lib/images/fileicons/ps.png)}a.mf_rb{background-image:url(/wiki/lib/images/fileicons/rb.png)}a.mf_py{background-image:url(/wiki/lib/images/fileicons/py.png)}a.mf_odp{background-image:url(/wiki/lib/images/fileicons/odp.png)}a.mf_ods{background-image:url(/wiki/lib/images/fileicons/ods.png)}a.mf_txt{background-image:url(/wiki/lib/images/fileicons/txt.png)}a.mf_odt{background-image:url(/wiki/lib/images/fileicons/odt.png)}a.mf_ogg{background-image:url(/wiki/lib/images/fileicons/ogg.png)}a.mf_jpeg{background-image:url(/wiki/lib/images/fileicons/jpeg.png)}a.mf_java{background-image:url(/wiki/lib/images/fileicons/java.png)}a.mf_xlsx{background-image:url(/wiki/lib/images/fileicons/xlsx.png)}a.mf_wav{background-image:url(/wiki/lib/images/fileicons/wav.png)}a.mf_pdf{background-image:url(/wiki/lib/images/fileicons/pdf.png)}a.mf_htm{background-image:url(/wiki/lib/images/fileicons/htm.png)}a.mf_php{background-image:url(/wiki/lib/images/fileicons/php.png)}a.mf_7z{background-image:url(/wiki/lib/images/fileicons/7z.png)}a.mf_png{background-image:url(/wiki/lib/images/fileicons/png.png)}a.mf_docx{background-image:url(/wiki/lib/images/fileicons/docx.png)}a.mf_ppt{background-image:url(/wiki/lib/images/fileicons/ppt.png)}a.mf_bz2{background-image:url(/wiki/lib/images/fileicons/bz2.png)}div.clearer{clear:both;line-height:0;height:0;overflow:hidden;}div.no{display:inline;margin:0;padding:0;}.hidden{display:none;}div.error{background:#fcc url(/wiki/lib/styles/../images/error.png) 0.5em 0px no-repeat;color:#000;border-bottom:1px solid #faa;font-size:90%;margin:0;padding-left:3em;overflow:hidden;}div.info{background:#ccf url(/wiki/lib/styles/../images/info.png) 0.5em 0px no-repeat;color:#000;border-bottom:1px solid #aaf;font-size:90%;margin:0;padding-left:3em;overflow:hidden;}div.success{background:#cfc url(/wiki/lib/styles/../images/success.png) 0.5em 0px no-repeat;color:#000;border-bottom:1px solid #afa;font-size:90%;margin:0;padding-left:3em;overflow:hidden;}div.notify{background:#ffc url(/wiki/lib/styles/../images/notify.png) 0.5em 0px no-repeat;color:#000;border-bottom:1px solid #ffa;font-size:90%;margin:0;padding-left:3em;overflow:hidden;}.medialeft{float:left;}.mediaright{float:right;}.mediacenter{display:block;margin-left:auto;margin-right:auto;}.leftalign{text-align:left;}.centeralign{text-align:center;}.rightalign{text-align:right;}em.u{font-style:normal;text-decoration:underline;}em em.u{font-style:italic;}.code .br0{color:#6c6;}.code .co0{color:#808080;font-style:italic;}.code .co1{color:#808080;font-style:italic;}.code .co2{color:#808080;font-style:italic;}.code .co3{color:#808080;}.code .coMULTI{color:#808080;font-style:italic;}.code .es0{color:#009;font-weight:bold;}.code .kw1{color:#b1b100;}.code .kw2{color:#000;font-weight:bold;}.code .kw3{color:#006;}.code .kw4{color:#933;}.code .kw5{color:#00f;}.code .me1{color:#060;}.code .me2{color:#060;}.code .nu0{color:#c6c;}.code .re0{color:#00f;}.code .re1{color:#00f;}.code .re2{color:#00f;}.code .re3{color:#f33;font-weight:bold;}.code .re4{color:#099;}.code .st0{color:#f00;}.code .sy0{color:#6c6;}div#acl_manager div#acl__tree{font-size:90%;width:25%;height:300px;float:left;overflow:auto;border:1px solid #8cacbb;text-align:left;}div#acl_manager div#acl__tree a.cur{background-color:#ff9;font-weight:bold;}div#acl_manager div#acl__tree ul{list-style-type:none;margin:0;padding:0;}div#acl_manager div#acl__tree li{padding-left:1em;}div#acl_manager div#acl__tree ul img{margin-right:0.25em;cursor:pointer;}div#acl_manager div#acl__detail{width:73%;height:300px;float:right;overflow:auto;}div#acl_manager div#acl__detail fieldset{width:90%;}div#acl_manager div#acl__detail div#acl__user{border:1px solid #8cacbb;padding:0.5em;margin-bottom:0.6em;}div#acl_manager table.inline{width:100%;margin:0;}div#acl_manager .aclgroup{background:transparent url(/wiki/lib/plugins/acl/pix/group.png) 0px 1px no-repeat;padding:1px 0px 1px 18px;}div#acl_manager .acluser{background:transparent url(/wiki/lib/plugins/acl/pix/user.png) 0px 1px no-repeat;padding:1px 0px 1px 18px;}div#acl_manager .aclpage{background:transparent url(/wiki/lib/plugins/acl/pix/page.png) 0px 1px no-repeat;padding:1px 0px 1px 18px;}div#acl_manager .aclns{background:transparent url(/wiki/lib/plugins/acl/pix/ns.png) 0px 1px no-repeat;padding:1px 0px 1px 18px;}div#acl_manager label.disabled{color:#666!important;}#acl_manager label{text-align:left;font-weight:normal;display:inline;}#acl_manager table{margin-left:10%;width:80%;}#acl_manager table tr{background-color:inherit;}#acl_manager table tr:hover{background-color:#dee7ec;}div.dokuwiki div.comment_wrapper{background-color:#dee7ec;margin:1em -1em;padding:0.5em 1em;clear:both;}div.dokuwiki div.comment_form{margin-top:2em;clear:both;}div.dokuwiki #discussion__comment_form label input.edit{width:75%;}div.dokuwiki .comment_head{font-size:80%;color:#666;padding-top:0.5em;margin-top:1em;clear:both;}div.dokuwiki .comment_head abbr{border-bottom:0;}div.dokuwiki .comment_head span.author{background:transparent url(/wiki/lib/plugins/discussion/images/user.gif) 0px 1px no-repeat;padding:1px 0px 1px 16px;}div.dokuwiki .comment_head abbr.published{background:transparent url(/wiki/lib/plugins/discussion/images/date.gif) 0px 1px no-repeat;padding:1px 0px 1px 16px;}div.dokuwiki .comment_body{padding-top:0.5em;padding-bottom:0.5em;border-bottom:1px dotted #8cacbb;}div.dokuwiki div.comment_replies{}div.dokuwiki div.comment_subscribe{float:left;display:inline;padding:0.6em;}div.dokuwiki div.comment_subscribe input{float:left;display:inline;}div.dokuwiki div.comment_subscribe label{float:left;padding-left:0.8em;}div.dokuwiki input.comment_submit{float:left;display:inline;}div.dokuwiki div.comment_hidden{opacity:0.5;}div.dokuwiki div.comment_buttons{float:right;font-size:10px;cursor:pointer;margin-top:-21px;padding-bottom:1em;}div.dokuwiki div.comment_buttons input.button{border:1px solid #8cacbb;color:#000;background-color:#fff;vertical-align:middle;text-decoration:none;padding:0;margin:0 0 0 0.5em;}div.dokuwiki div.newthread_form{clear:both;text-align:center;margin-bottom:1em;}div.dokuwiki #discussion__newthread_form input.edit{width:95%;}div.dokuwiki ul.admin_discussion li.hidden{display:list-item;opacity:0.5;}div.dokuwiki ul.admin_discussion span.abstract{color:#666;}#config__manager div.success,#config__manager div.error,#config__manager div.info{background-position:0.5em;padding:0.5em;text-align:center;}#config__manager fieldset{margin:1em;width:auto;margin-bottom:2em;background-color:#dee7ec;color:#000;padding:0 1em;}#config__manager legend{font-size:1.25em;}#config__manager form{}#config__manager table{margin:1em 0;width:100%;}#config__manager fieldset td{text-align:left;}#config__manager fieldset td.value{width:31em;}#config__manager td.label{padding:0.8em 0 0.6em 1em;vertical-align:top;}#config__manager td.label label{clear:left;display:block;}#config__manager td.label img{padding:0 10px;vertical-align:middle;float:right;}#config__manager td.label span.outkey{font-size:70%;margin-top:-1.7em;margin-left:-1em;display:block;background-color:#fff;color:#666;float:left;padding:0 0.1em;position:relative;z-index:1;}#config__manager td input.edit{width:30em;}#config__manager td .input{width:30.8em;}#config__manager td select.edit{}#config__manager td textarea.edit{width:27.5em;height:4em;}#config__manager tr .input,#config__manager tr input,#config__manager tr textarea,#config__manager tr select{background-color:#fff;color:#000;}#config__manager tr.default .input,#config__manager tr.default input,#config__manager tr.default textarea,#config__manager tr.default select,#config__manager .selectiondefault{background-color:#cdf;color:#000;}#config__manager tr.protected .input,#config__manager tr.protected input,#config__manager tr.protected textarea,#config__manager tr.protected select,#config__manager tr.protected .selection{background-color:#fcc!important;color:#000 !important;}#config__manager td.error{background-color:red;color:#000;}#config__manager .selection{width:14.8em;float:left;margin:0 0.3em 2px 0;}#config__manager .selection label{float:right;width:14em;font-size:90%;}* html #config__manager .selection label{padding-top:2px;}#config__manager .selection input.checkbox{padding-left:0.7em;}#config__manager .other{clear:both;padding-top:0.5em;}#config__manager .other label{padding-left:2px;font-size:90%;}.authorstats-table{border-collapse:collapse;border:1px solid #8CACBB;}.authorstats-table td{padding:10px;text-align:center;border:1px solid #8CACBB;}.authorstats-table th{background-color:#DEE7EC;color:black;border:1px solid #8CACBB;padding:2px 15px 2px 15px;}.folder{padding-left:2px;padding-right:9px;background:url(/wiki/lib/plugins/folded/closed.gif) no-repeat right center;}.folder.open{background:url(/wiki/lib/plugins/folded/open.gif) no-repeat right center;}div.folded{padding:0.5em;border:1px dotted #dee7ec;}span.folded{border:1px dotted #dee7ec;}span.indicator{visibility:hidden;}#user__manager tr.disabled{color:#6f6f6f;background:#e4e4e4;}#user__manager tr.user_info{vertical-align:top;}#user__manager div.edit_user{width:46%;float:left;}#user__manager table{margin-bottom:1em;}#user__manager input.button[disabled]{color:#ccc!important;border-color:#ccc!important;}#plugin__manager h2{margin-left:0;}#plugin__manager form{display:block;margin:0;padding:0;}#plugin__manager legend{display:none;}#plugin__manager fieldset{width:auto;}#plugin__manager .button{margin:0;}#plugin__manager p,#plugin__manager label{text-align:left;}#plugin__manager .hidden{display:none;}#plugin__manager .new{background:#dee7ec;}#plugin__manager input[disabled]{color:#ccc;border-color:#ccc;}#plugin__manager .pm_menu,#plugin__manager .pm_info{margin-left:0;text-align:left;}#plugin__manager .pm_menu{float:left;width:48%;}#plugin__manager .pm_info{float:right;width:50%;}#plugin__manager .common fieldset{margin:0;padding:0 0 1.0em 0;text-align:left;border:none;}#plugin__manager .common label{padding:0 0 0.5em 0;}#plugin__manager .common input.edit{width:24em;margin:0.5em;}#plugin__manager .plugins fieldset{color:#000;background:#fff;text-align:right;border-top:none;border-right:none;border-left:none;}#plugin__manager .plugins fieldset.protected{background:#fdd;color:#000;}#plugin__manager .plugins fieldset.disabled{background:#e0e0e0;color:#a8a8a8;}#plugin__manager .plugins .legend{color:#000;background:inherit;display:block;margin:0;padding:0;font-size:1em;line-height:1.4em;font-weight:normal;text-align:left;float:left;padding:0;clear:none;}#plugin__manager .plugins .button{font-size:95%;}#plugin__manager .plugins fieldset.buttons{border:none;}#plugin__manager .plugins fieldset.buttons .button{float:left;}#plugin__manager .pm_info h3{margin-left:0;}#plugin__manager .pm_info dl{margin:1em 0;padding:0;}#plugin__manager .pm_info dt{width:6em;float:left;clear:left;margin:0;padding:0;}#plugin__manager .pm_info dd{margin:0 0 0 7em;padding:0;background:none;}#plugin__manager .plugins .enable{float:left;width:auto;margin-right:0.5em;}div.dokuwiki div.include div.secedit{float:right;margin-left:1em;margin-top:-18px;}div.dokuwiki div.inclmeta{border-top:1px dotted #8cacbb;padding-top:0.2em;color:#666;font-size:80%;line-height:1.25;margin-top:0.5em;margin-bottom:2em;}div.dokuwiki div.inclmeta a.permalink{background:transparent url(/wiki/lib/plugins/include/images/link.gif) 0px 1px no-repeat;padding:1px 0px 1px 16px;}div.dokuwiki div.inclmeta abbr.published{background:transparent url(/wiki/lib/plugins/include/images/date.gif) 0px 1px no-repeat;padding:1px 0px 1px 16px;border-bottom:0;}div.dokuwiki div.inclmeta span.author{background:transparent url(/wiki/lib/plugins/include/images/user.gif) 0px 1px no-repeat;padding:1px 0px 1px 16px;}div.dokuwiki div.inclmeta span.comment{background:transparent url(/wiki/lib/plugins/include/images/comment.gif) 0px 1px no-repeat;padding:1px 0px 1px 16px;}div.dokuwiki div.inclmeta div.tags{border-top:0;font-size:100%;float:right;clear:none;}div.dokuwiki .header{padding:3px 0 0 2px;}div.dokuwiki .pagename{float:left;font-size:200%;font-weight:bolder;color:#dee7ec;text-align:left;vertical-align:middle;}div.dokuwiki .pagename a{color:#436976 !important;text-decoration:none !important;}div.dokuwiki .logo{float:right;font-size:220%;font-weight:bolder;text-align:right;vertical-align:middle;}div.dokuwiki .logo a{color:#dee7ec !important;text-decoration:none !important;font-variant:small-caps;letter-spacing:2pt;}div.dokuwiki .bar{border-top:1px solid #8cacbb;border-bottom:1px solid #8cacbb;background:#dee7ec;padding:0.1em 0.15em;clear:both;}div.dokuwiki .bar-left{float:left;}div.dokuwiki .bar-right{float:right;text-align:right;}div.dokuwiki #bar__bottom{margin-bottom:3px;}div.dokuwiki div.meta{clear:both;margin-top:1em;color:#638c9c;font-size:70%;}div.dokuwiki div.meta div.user{float:left;}div.dokuwiki div.meta div.doc{text-align:right;}*{padding:0;margin:0;}body{font:80% "Lucida Grande",Verdana,Lucida,Helvetica,Arial,sans-serif;background-color:#fff;color:#000;}div.dokuwiki div.page{margin:4px 2em 0 1em;text-align:justify;}div.dokuwiki table{font-size:100%;}div.dokuwiki tr,div.dokuwiki td,div.dokuwiki th{}div.dokuwiki img{border:0;}div.dokuwiki p,div.dokuwiki blockquote,div.dokuwiki table,div.dokuwiki pre{margin:0 0 1.0em 0;}div.dokuwiki hr{border:0px;border-top:1px solid #8cacbb;text-align:center;height:0px;}div.dokuwiki div.nothing{text-align:center;margin:2em;}div.dokuwiki form{border:none;display:inline;}div.dokuwiki label.block{display:block;text-align:right;font-weight:bold;}div.dokuwiki label.simple{display:block;text-align:left;font-weight:normal;}div.dokuwiki label.block input.edit{width:50%;}div.dokuwiki fieldset{width:300px;text-align:center;border:1px solid #8cacbb;padding:0.5em;margin:auto;}div.dokuwiki textarea.edit{font-family:monospace;font-size:14px;color:#000;background-color:#fff;border:1px solid #8cacbb;padding:0.3em 0 0 0.3em;width:700px;min-width:100%;max-width:100%;}html>body div.dokuwiki textarea.edit{background:#fff url(/wiki/lib/tpl/default/images/inputshadow.png) repeat-x top;}div.dokuwiki input.edit,div.dokuwiki select.edit{font-size:100%;border:1px solid #8cacbb;color:#000;background-color:#fff;vertical-align:middle;margin:1px;padding:0.20em 0.3em;display:inline;}html>body div.dokuwiki input.edit,html>body div.dokuwiki select.edit{background:#fff url(/wiki/lib/tpl/default/images/inputshadow.png) repeat-x top;}div.dokuwiki select.edit{padding:0.1em 0;}div.dokuwiki input.missing{font-size:100%;border:1px solid #8cacbb;color:#000;background-color:#fcc;vertical-align:middle;margin:1px;padding:0.20em 0.3em;display:inline;}div.dokuwiki textarea.edit[disabled],div.dokuwiki textarea.edit[readonly],div.dokuwiki input.edit[disabled],div.dokuwiki input.edit[readonly],div.dokuwiki input.button[disabled],div.dokuwiki select.edit[disabled]{background-color:#f5f5f5!important;color:#666!important;}div.dokuwiki div.toolbar,div.dokuwiki div#wiki__editbar{margin:2px 0;text-align:left;}div.dokuwiki div#size__ctl{float:right;width:60px;height:2.7em;}div.dokuwiki #size__ctl img{cursor:pointer;}div.dokuwiki div#wiki__editbar div.editButtons{float:left;padding:0 1.0em 0.7em 0;}div.dokuwiki div#wiki__editbar div.summary{float:left;}div.dokuwiki .nowrap{white-space:nowrap;}div.dokuwiki div#draft__status{float:right;color:#638c9c;}div.dokuwiki div.license{padding:0.5em;font-size:90%;text-align:center;}div.dokuwiki form#dw__editform div.license{clear:left;font-size:90%;}div.dokuwiki input.button,div.dokuwiki button.button{border:1px solid #8cacbb;color:#000;background-color:#fff;vertical-align:middle;text-decoration:none;font-size:100%;cursor:pointer;margin:1px;padding:0.125em 0.4em;}html>body div.dokuwiki input.button,html>body div.dokuwiki button.button{background:#fff url(/wiki/lib/tpl/default/images/buttonshadow.png) repeat-x bottom;}* html div.dokuwiki input.button,* html div.dokuwiki button.button{height:1.8em;}div.dokuwiki div.secedit input.button{border:1px solid #8cacbb;color:#000;background-color:#fff;vertical-align:middle;text-decoration:none;margin:0;padding:0;font-size:10px;cursor:pointer;float:right;display:inline;}div.dokuwiki div.pagenav{margin:1em 0 0 0;}div.dokuwiki div.pagenav-prev{text-align:right;float:left;width:49%}div.dokuwiki div.pagenav-next{text-align:left;float:right;width:49%}div.dokuwiki a:link,div.dokuwiki a:visited{color:#436976;text-decoration:none;}div.dokuwiki a:hover,div.dokuwiki a:active{color:#000;text-decoration:underline;}div.dokuwiki h1 a,div.dokuwiki h2 a,div.dokuwiki h3 a,div.dokuwiki h4 a,div.dokuwiki h5 a,div.dokuwiki a.nolink{color:#000 !important;text-decoration:none !important;}div.dokuwiki a.urlextern{background:transparent url(/wiki/lib/tpl/default/images/link_icon.gif) 0px 1px no-repeat;padding:1px 0px 1px 16px;}div.dokuwiki a.windows{background:transparent url(/wiki/lib/tpl/default/images/windows.gif) 0px 1px no-repeat;padding:1px 0px 1px 16px;}div.dokuwiki a.interwiki{}div.dokuwiki a.media{}div.dokuwiki a.urlextern:link,div.dokuwiki a.windows:link,div.dokuwiki a.interwiki:link{color:#436976;}div.dokuwiki a.urlextern:visited,div.dokuwiki a.windows:visited,div.dokuwiki a.interwiki:visited{color:purple;}div.dokuwiki a.urlextern:hover,div.dokuwiki a.urlextern:active,div.dokuwiki a.windows:hover,div.dokuwiki a.windows:active,div.dokuwiki a.interwiki:hover,div.dokuwiki a.interwiki:active{color:#000;}div.dokuwiki a.mail{background:transparent url(/wiki/lib/tpl/default/images/mail_icon.gif) 0px 1px no-repeat;padding:1px 0px 1px 16px;}div.dokuwiki a.wikilink1{color:#090 !important;}div.dokuwiki a.wikilink2{color:#f30 !important;text-decoration:none !important;border-bottom:dashed 1px #f30 !important;}div.dokuwiki div.preview{background-color:#f5f5f5;margin:0 0 0 2em;padding:4px;border:1px dashed #000;}div.dokuwiki div.breadcrumbs{background-color:#f5f5f5;color:#666;font-size:80%;padding:0 0 0 4px;}div.dokuwiki span.user{color:#ccc;font-size:90%;}div.dokuwiki li.minor{color:#666;font-style:italic;}div.dokuwiki img.media{margin:3px;}div.dokuwiki img.medialeft{border:0;float:left;margin:0 1.5em 0 0;}div.dokuwiki img.mediaright{border:0;float:right;margin:0 0 0 1.5em;}div.dokuwiki img.mediacenter{border:0;display:block;margin:0 auto;}div.dokuwiki img.middle{vertical-align:middle;}div.dokuwiki acronym{cursor:help;border-bottom:1px dotted #000;}div.dokuwiki h1,div.dokuwiki h2,div.dokuwiki h3,div.dokuwiki h4,div.dokuwiki h5{color:#000;background-color:inherit;font-size:100%;font-weight:normal;margin:0 0 1em 0;padding:0.5em 0 0 0;border-bottom:1px solid #8cacbb;clear:left;}div.dokuwiki h1{font-size:160%;margin-left:0px;font-weight:bold;}div.dokuwiki h2{font-size:150%;margin-left:20px;}div.dokuwiki h3{font-size:140%;margin-left:40px;border-bottom:none;font-weight:bold;}div.dokuwiki h4{font-size:120%;margin-left:60px;border-bottom:none;font-weight:bold;}div.dokuwiki h5{font-size:100%;margin-left:80px;border-bottom:none;font-weight:bold;}div.dokuwiki div.level1{margin-left:3px;}div.dokuwiki div.level2{margin-left:23px;}div.dokuwiki div.level3{margin-left:43px;}div.dokuwiki div.level4{margin-left:63px;}div.dokuwiki div.level5{margin-left:83px;}div.dokuwiki ul{line-height:1.5em;list-style-type:square;list-style-image:none;margin:0 0 1em 3.5em;color:#638c9c;}div.dokuwiki ol{line-height:1.5em;list-style-image:none;margin:0 0 1em 3.5em;color:#638c9c;font-weight:bold;}div.dokuwiki li ul,div.dokuwiki li ol{margin:0 0 0 1.5em;}div.dokuwiki .li{color:#000;font-weight:normal;}div.dokuwiki ol{list-style-type:decimal;}div.dokuwiki ol ol{list-style-type:upper-roman;}div.dokuwiki ol ol ol{list-style-type:lower-alpha;}div.dokuwiki ol ol ol ol{list-style-type:lower-greek;}div.dokuwiki li.open{list-style-image:url(/wiki/lib/tpl/default/images/open.gif);}div.dokuwiki li.closed{list-style-image:url(/wiki/lib/tpl/default/images/closed.gif);}div.dokuwiki blockquote{border-left:2px solid #8cacbb;padding-left:3px;}div.dokuwiki pre{font-family:monospace;font-size:120%;padding:0.5em;border:1px dashed #8cacbb;color:#000;overflow:auto;}div.dokuwiki pre.pre{background-color:#f7f9fa;}div.dokuwiki pre.code{background-color:#f7f9fa;}div.dokuwiki pre.file{background-color:#dee7ec;}div.dokuwiki dl.file,div.dokuwiki dl.code{margin-top:2em;margin-bottom:2.5em;}div.dokuwiki dl.file dt,div.dokuwiki dl.code dt{border:1px dashed #8cacbb;display:inline;padding:0.1em 1em;margin-left:2em;}div.dokuwiki dl.code dt a,div.dokuwiki dl.file dt a{color:#000;}div.dokuwiki dl.code dt{background-color:#f7f9fa;border-bottom:1px solid #f7f9fa;}div.dokuwiki dl.file dt{background-color:#dee7ec;border-bottom:1px solid #dee7ec;}div.dokuwiki code{font-size:120%;}div.dokuwiki table.inline{background-color:#fff;border-spacing:0px;border-collapse:collapse;}div.dokuwiki table.inline th{padding:3px;border:1px solid #8cacbb;background-color:#dee7ec;}div.dokuwiki table.inline td{padding:3px;border:1px solid #8cacbb;}div.dokuwiki div.toc{margin:1.2em 0 0 2em;float:right;width:200px;font-size:80%;clear:both;}div.dokuwiki div.tocheader{border:1px solid #8cacbb;background-color:#dee7ec;text-align:left;font-weight:bold;padding:3px;margin-bottom:2px;}div.dokuwiki span.toc_open,div.dokuwiki span.toc_close{border:0.4em solid #dee7ec;float:right;display:block;margin:0.4em 3px 0 0;}div.dokuwiki span.toc_open span,div.dokuwiki span.toc_close span{display:none;}div.dokuwiki span.toc_open{margin-top:0.4em;border-top:0.4em solid #000;}div.dokuwiki span.toc_close{margin-top:0;border-bottom:0.4em solid #000;}div.dokuwiki #toc__inside{border:1px solid #8cacbb;background-color:#fff;text-align:left;padding:0.5em 0 0.7em 0;}div.dokuwiki ul.toc{list-style-type:none;list-style-image:none;line-height:1.2em;padding-left:1em;margin:0;}div.dokuwiki ul.toc li{background:transparent url(/wiki/lib/tpl/default/images/tocdot2.gif) 0 0.6em no-repeat;padding-left:0.4em;}div.dokuwiki ul.toc li.clear{background-image:none;padding-left:0.4em;}div.dokuwiki a.toc:link,div.dokuwiki a.toc:visited{color:#436976;}div.dokuwiki a.toc:hover,div.dokuwiki a.toc:active{color:#000;}div.dokuwiki table.diff{background-color:#fff;width:100%;}div.dokuwiki td.diff-blockheader{font-weight:bold;}div.dokuwiki table.diff th{border-bottom:1px solid #8cacbb;font-size:110%;width:50%;font-weight:normal;text-align:left;}div.dokuwiki table.diff th a{font-weight:bold;}div.dokuwiki table.diff th span.user{color:#000;font-size:80%;}div.dokuwiki table.diff th span.sum{font-size:80%;font-weight:bold;}div.dokuwiki table.diff th.minor{font-style:italic;}div.dokuwiki table.diff td{font-family:monospace;font-size:100%;}div.dokuwiki td.diff-addedline{background-color:#dfd;}div.dokuwiki td.diff-deletedline{background-color:#ffb;}div.dokuwiki td.diff-context{background-color:#f5f5f5;}div.dokuwiki table.diff td.diff-addedline strong,div.dokuwiki table.diff td.diff-deletedline strong{color:red;}div.dokuwiki div.footnotes{clear:both;border-top:1px solid #8cacbb;padding-left:1em;margin-top:1em;}div.dokuwiki div.fn{font-size:90%;}div.dokuwiki a.fn_bot{font-weight:bold;}div.insitu-footnote{font-size:80%;line-height:1.2em;border:1px solid #8cacbb;background-color:#f7f9fa;text-align:left;padding:4px;max-width:40%;min-width:5em;}* html .insitu-footnote pre.code,* html .insitu-footnote pre.file{padding-bottom:18px;}div.dokuwiki .search_result{margin-bottom:6px;padding:0 10px 0 30px;}div.dokuwiki .search_snippet{color:#ccc;font-size:12px;margin-left:20px;}div.dokuwiki .search_sep{color:#000;}div.dokuwiki .search_hit{color:#000;background-color:#ff9;}div.dokuwiki strong.search_hit{font-weight:normal;}div.dokuwiki div.search_quickresult{margin:0 0 15px 30px;padding:0 10px 10px 0;border-bottom:1px dashed #8cacbb;}div.dokuwiki div.search_quickresult h3{margin:0 0 1.0em 0;font-size:1em;font-weight:bold;}div.dokuwiki ul.search_quickhits{margin:0 0 0.5em 1.0em;}div.dokuwiki ul.search_quickhits li{margin:0 1.0em 0 1.0em;float:left;width:30%;}div.dokuwiki .section_highlight{background-color:#dee7ec !important;}div.footerinc{text-align:center;}.footerinc a img{opacity:0.5;border:0;}.footerinc a:hover img{opacity:1;}div.dokuwiki div.ajax_qsearch{position:absolute;right:237px;;width:200px;opacity:0.9;display:none;font-size:80%;line-height:1.2em;border:1px solid #8cacbb;background-color:#f7f9fa;text-align:left;padding:4px;}button.toolbutton{background-color:#fff;padding:0px;margin:0 1px 0 0;border:1px solid #8cacbb;cursor:pointer;}html>body button.toolbutton{background:#fff url(/wiki/lib/tpl/default/images/buttonshadow.png) repeat-x bottom;}div.picker{width:250px;border:1px solid #8cacbb;background-color:#dee7ec;}div.pk_hl{width:125px;}button.pickerbutton{padding:0px;margin:0 1px 1px 0;border:0;background-color:transparent;font-size:80%;cursor:pointer;}div.dokuwiki div.img_big{float:left;margin-right:0.5em;}div.dokuwiki dl.img_tags dt{font-weight:bold;background-color:#dee7ec;}div.dokuwiki dl.img_tags dd{background-color:#f5f5f5;}div.dokuwiki div.imagemeta{color:#666;font-size:70%;line-height:95%;}div.dokuwiki div.imagemeta img.thumb{float:left;margin-right:0.1em;}#media__manager{height:100%;overflow:hidden;}#media__left{width:30%;border-right:solid 1px #8cacbb;height:100%;overflow:auto;position:absolute;left:0;}#media__right{width:69.7%;height:100%;overflow:auto;position:absolute;right:0;}#media__manager h1{margin:0;padding:0;margin-bottom:0.5em;}#media__tree img{float:left;padding:0.5em 0.3em 0 0;}#media__tree ul{list-style-type:none;list-style-image:none;margin-left:1.5em;}#media__tree li{clear:left;list-style-type:none;list-style-image:none;}*+html #media__tree li,* html #media__tree li{border:1px solid #fff;}#media__opts{padding-left:1em;margin-bottom:0.5em;}#media__opts input{float:left;display:block;margin-top:4px;position:absolute;}*+html #media__opts input,* html #media__opts input{position:static;}#media__opts label{display:block;float:left;margin-left:20px;margin-bottom:4px;}*+html #media__opts label,* html #media__opts label{margin-left:10px;}#media__opts br{clear:left;}#media__content img.load{margin:1em auto;}#media__content #scroll__here{border:1px dashed #8cacbb;}#media__content .odd{background-color:#f7f9fa;padding:0.4em;}#media__content .even{padding:0.4em;}#media__content a.mediafile{margin-right:1.5em;font-weight:bold;}#media__content div.detail{padding:0.3em 0 0.3em 2em;}#media__content div.detail div.thumb{float:left;width:130px;text-align:center;margin-right:0.4em;}#media__content img.btn{vertical-align:text-bottom;}#media__content div.example{color:#666;margin-left:1em;}#media__content div.upload{font-size:90%;padding:0 0.5em 0.5em 0.5em;}#media__content form#dw__upload,#media__content div#dw__flashupload{display:block;border-bottom:solid 1px #8cacbb;padding:0 0.5em 1em 0.5em;}#media__content form#dw__upload fieldset{padding:0;margin:0;border:none;width:auto;}#media__content form#dw__upload p{text-align:left;padding:0.25em 0;margin:0;line-height:1.0em;}#media__content form#dw__upload label.check{float:none;width:auto;margin-left:11.5em;}#media__content form.meta{display:block;padding:0 0 1em 0;}#media__content form.meta label{display:block;width:25%;float:left;font-weight:bold;margin-left:1em;clear:left;}#media__content form.meta .edit{font:100% "Lucida Grande",Verdana,Lucida,Helvetica,Arial,sans-serif;float:left;width:70%;padding-right:0;padding-left:0.2em;margin:2px;}#media__content form.meta textarea.edit{height:8em;}#media__content form.meta div.metafield{clear:left;}#media__content form.meta div.buttons{clear:left;margin-left:20%;padding-left:1em;}#media__popup{background-color:#fff;display:none;border:1px solid #8cacbb;position:absolute;width:270px;}#media__popup h1{text-align:center;font-weight:normal;background-color:#dee7ec;height:16px;margin-bottom:5px;font-size:12px;border-bottom:0;}#media__popup p{display:block;line-height:14pt;margin:0.5em;}#media_nolink{padding:4px 0;}#media__popup label{float:left;width:9em;}#media__popup .button{margin-left:auto;margin-right:auto;}#media__popup .btnlbl{text-align:center;}#media__popup .btnlbl input{margin:0 1em;}#media__closeimg{float:right;}#media__linkopts label,#media__nolnk{width:80px;float:left;margin-left:10px;}#media__linkopts label{line-height:20px;}#media__nolnk,#media__linkopts label.long{margin-bottom:8px;line-height:12px;}#media__linkopts label.long{width:150px;float:none;}#media__linkopts br{clear:both;}#media__linkopts select{width:60px;margin-left:10px;}#media__linkopts input.edit{width:50px;margin-left:10px;}#media__linkopts #media__title{width:150px;}#admin__version{clear:left;float:right;color:#666;}.dokuwiki ul.admin_tasks{font-size:115%;float:left;width:40%;list-style-type:none;}.dokuwiki ul.admin_tasks li{line-height:22px;padding-left:35px;margin:1em 0;background:transparent none no-repeat scroll 0 0;text-align:left;}.dokuwiki ul.admin_tasks li div.li{font-weight:bold;}.dokuwiki ul.admin_tasks li.admin_acl{background-image:url(/wiki/lib/tpl/default/../../images/admin/acl.png);}.dokuwiki ul.admin_tasks li.admin_usermanager{background-image:url(/wiki/lib/tpl/default/../../images/admin/usermanager.png);}.dokuwiki ul.admin_tasks li.admin_plugin{background-image:url(/wiki/lib/tpl/default/../../images/admin/plugin.png);}.dokuwiki ul.admin_tasks li.admin_config{background-image:url(/wiki/lib/tpl/default/../../images/admin/config.png);}.dokuwiki ul.admin_tasks li.admin_revert{background-image:url(/wiki/lib/tpl/default/../../images/admin/revert.png);}.dokuwiki ul.admin_tasks li.admin_popularity{background-image:url(/wiki/lib/tpl/default/../../images/admin/popularity.png);}#link__wiz{position:absolute;display:block;z-index:99;width:300px;height:250px;padding:0;margin:0;overflow:hidden;border:1px solid #8cacbb;background-color:#f5f5f5;text-align:center;}#link__wiz_header{background-color:#dee7ec;height:16px;margin-bottom:5px;}#link__wiz_close{cursor:pointer;margin:0;}#link__wiz_result{background-color:#fff;width:293px;height:193px;overflow:auto;border:1px solid #8cacbb;margin:3px auto;text-align:left;}#link__wiz_result div.type_u{padding:3px 3px 3px 22px;background:transparent url(/wiki/lib/tpl/default/../../images/up.png) 3px 3px no-repeat;}#link__wiz_result div.type_f{padding:3px 3px 3px 22px;background:transparent url(/wiki/lib/tpl/default/../../images/page.png) 3px 3px no-repeat;}#link__wiz_result div.type_d{padding:3px 3px 3px 22px;background:transparent url(/wiki/lib/tpl/default/../../images/ns.png) 3px 3px no-repeat;}#link__wiz_result div.even{background-color:#f5f5f5;}#link__wiz_result div.selected{background-color:#dee7ec;}#link__wiz_result span{display:block;color:#666;}.ondrag{cursor:move;opacity:0.8;}form#subscribe__form{display:block;width:300px;text-align:center;}form#subscribe__form fieldset{text-align:left;margin:0.5em 0;}form#subscribe__form label{display:block;margin:0 0.5em 0.5em;}div.dokuwiki a.li{color:#FFF000;}div.dokuwiki a.li:hover{color:#000;} \ No newline at end of file diff --git a/labnic/references_software [WikiNIC]_fichiers/css_002.css b/labnic/references_software [WikiNIC]_fichiers/css_002.css new file mode 100644 index 0000000..5905b24 --- /dev/null +++ b/labnic/references_software [WikiNIC]_fichiers/css_002.css @@ -0,0 +1 @@ +a.interwiki{background:transparent url(/wiki/lib/images/interwiki.png) 0px 1px no-repeat;padding-left:16px;}a.iw_wp{background-image:url(/wiki/lib/images/interwiki/wp.gif)}a.iw_wpfr{background-image:url(/wiki/lib/images/interwiki/wpfr.gif)}a.iw_wpde{background-image:url(/wiki/lib/images/interwiki/wpde.gif)}a.iw_wpes{background-image:url(/wiki/lib/images/interwiki/wpes.gif)}a.iw_wppl{background-image:url(/wiki/lib/images/interwiki/wppl.gif)}a.iw_wpjp{background-image:url(/wiki/lib/images/interwiki/wpjp.gif)}a.iw_wpmeta{background-image:url(/wiki/lib/images/interwiki/wpmeta.gif)}a.iw_doku{background-image:url(/wiki/lib/images/interwiki/doku.gif)}a.iw_dokubug{background-image:url(/wiki/lib/images/interwiki/dokubug.gif)}a.iw_amazon{background-image:url(/wiki/lib/images/interwiki/amazon.gif)}a.iw_amazon_de{background-image:url(/wiki/lib/images/interwiki/amazon.de.gif)}a.iw_amazon_uk{background-image:url(/wiki/lib/images/interwiki/amazon.uk.gif)}a.iw_phpfn{background-image:url(/wiki/lib/images/interwiki/phpfn.gif)}a.iw_coral{background-image:url(/wiki/lib/images/interwiki/coral.gif)}a.iw_sb{background-image:url(/wiki/lib/images/interwiki/sb.gif)}a.iw_google{background-image:url(/wiki/lib/images/interwiki/google.gif)}a.iw_meatball{background-image:url(/wiki/lib/images/interwiki/meatball.gif)}a.iw_wiki{background-image:url(/wiki/lib/images/interwiki/wiki.gif)}a.mediafile{background:transparent url(/wiki/lib/images/fileicons/file.png) 0px 1px no-repeat;padding-left:18px;padding-bottom:1px;}a.mf_deb{background-image:url(/wiki/lib/images/fileicons/deb.png)}a.mf_rar{background-image:url(/wiki/lib/images/fileicons/rar.png)}a.mf_cpp{background-image:url(/wiki/lib/images/fileicons/cpp.png)}a.mf_jpg{background-image:url(/wiki/lib/images/fileicons/jpg.png)}a.mf_xls{background-image:url(/wiki/lib/images/fileicons/xls.png)}a.mf_xml{background-image:url(/wiki/lib/images/fileicons/xml.png)}a.mf_css{background-image:url(/wiki/lib/images/fileicons/css.png)}a.mf_csv{background-image:url(/wiki/lib/images/fileicons/csv.png)}a.mf_c{background-image:url(/wiki/lib/images/fileicons/c.png)}a.mf_doc{background-image:url(/wiki/lib/images/fileicons/doc.png)}a.mf_html{background-image:url(/wiki/lib/images/fileicons/html.png)}a.mf_rpm{background-image:url(/wiki/lib/images/fileicons/rpm.png)}a.mf_rtf{background-image:url(/wiki/lib/images/fileicons/rtf.png)}a.mf_zip{background-image:url(/wiki/lib/images/fileicons/zip.png)}a.mf_tar{background-image:url(/wiki/lib/images/fileicons/tar.png)}a.mf_audio{background-image:url(/wiki/lib/images/fileicons/audio.png)}a.mf_sql{background-image:url(/wiki/lib/images/fileicons/sql.png)}a.mf_lua{background-image:url(/wiki/lib/images/fileicons/lua.png)}a.mf_tgz{background-image:url(/wiki/lib/images/fileicons/tgz.png)}a.mf_mp3{background-image:url(/wiki/lib/images/fileicons/mp3.png)}a.mf_cs{background-image:url(/wiki/lib/images/fileicons/cs.png)}a.mf_swf{background-image:url(/wiki/lib/images/fileicons/swf.png)}a.mf_conf{background-image:url(/wiki/lib/images/fileicons/conf.png)}a.mf_sxc{background-image:url(/wiki/lib/images/fileicons/sxc.png)}a.mf_sxd{background-image:url(/wiki/lib/images/fileicons/sxd.png)}a.mf_sxi{background-image:url(/wiki/lib/images/fileicons/sxi.png)}a.mf_sxw{background-image:url(/wiki/lib/images/fileicons/sxw.png)}a.mf_gz{background-image:url(/wiki/lib/images/fileicons/gz.png)}a.mf_gif{background-image:url(/wiki/lib/images/fileicons/gif.png)}a.mf_js{background-image:url(/wiki/lib/images/fileicons/js.png)}a.mf_pptx{background-image:url(/wiki/lib/images/fileicons/pptx.png)}a.mf_pl{background-image:url(/wiki/lib/images/fileicons/pl.png)}a.mf_odc{background-image:url(/wiki/lib/images/fileicons/odc.png)}a.mf_odf{background-image:url(/wiki/lib/images/fileicons/odf.png)}a.mf_odg{background-image:url(/wiki/lib/images/fileicons/odg.png)}a.mf_odi{background-image:url(/wiki/lib/images/fileicons/odi.png)}a.mf_ps{background-image:url(/wiki/lib/images/fileicons/ps.png)}a.mf_rb{background-image:url(/wiki/lib/images/fileicons/rb.png)}a.mf_py{background-image:url(/wiki/lib/images/fileicons/py.png)}a.mf_odp{background-image:url(/wiki/lib/images/fileicons/odp.png)}a.mf_ods{background-image:url(/wiki/lib/images/fileicons/ods.png)}a.mf_txt{background-image:url(/wiki/lib/images/fileicons/txt.png)}a.mf_odt{background-image:url(/wiki/lib/images/fileicons/odt.png)}a.mf_ogg{background-image:url(/wiki/lib/images/fileicons/ogg.png)}a.mf_jpeg{background-image:url(/wiki/lib/images/fileicons/jpeg.png)}a.mf_java{background-image:url(/wiki/lib/images/fileicons/java.png)}a.mf_xlsx{background-image:url(/wiki/lib/images/fileicons/xlsx.png)}a.mf_wav{background-image:url(/wiki/lib/images/fileicons/wav.png)}a.mf_pdf{background-image:url(/wiki/lib/images/fileicons/pdf.png)}a.mf_htm{background-image:url(/wiki/lib/images/fileicons/htm.png)}a.mf_php{background-image:url(/wiki/lib/images/fileicons/php.png)}a.mf_7z{background-image:url(/wiki/lib/images/fileicons/7z.png)}a.mf_png{background-image:url(/wiki/lib/images/fileicons/png.png)}a.mf_docx{background-image:url(/wiki/lib/images/fileicons/docx.png)}a.mf_ppt{background-image:url(/wiki/lib/images/fileicons/ppt.png)}a.mf_bz2{background-image:url(/wiki/lib/images/fileicons/bz2.png)}div.dokuwiki #discussion__comment_form,div.dokuwiki #discussion__newthread_form,div.dokuwiki div.comment_buttons,div.dokuwiki div.comment_hidden{display:none;}body{font:10pt "Lucida Grande",Verdana,Lucida,Helvetica,Arial,sans-serif;background-color:White;color:Black;}table{font-size:100%;padding:0;margin:0;}tr,td,th{padding:0;margin:0;}img{border:0;}a{color:#000;text-decoration:none;background:none !important;}a.interwiki{padding-left:0px !important;}div.meta{clear:both;margin-top:1em;font-size:70%;text-align:right;}div.notify,div.info,div.success,div.error,div.breadcrumbs,div.secedit{display:none;}a.urlextern:after{content:" [" attr(href) "]";font-size:90%;}a.interwiki:after{content:" [" attr(href) "]";font-size:90%;}a.mail:after{content:" [" attr(href) "]";font-size:90%;}a.wikilink1{text-decoration:underline;}div.page{text-align:justify;}h1,h2,h3,h4,h5{color:Black;background-color:transparent;font-family:"Lucida Grande",Verdana,Lucida,Helvetica,Arial,sans-serif;font-size:100%;font-weight:normal;margin-left:0;margin-right:0;margin-top:0;margin-bottom:1em;padding-left:0;padding-right:0;padding-top:0.5em;padding-bottom:0;border-bottom:1px solid #000;clear:left;}h1{font-size:160%;font-weight:bold;}h2{font-size:150%;}h3{font-size:140%;border-bottom:none;}h4{font-size:120%;border-bottom:none;}h5{font-size:100%;border-bottom:none;}img.media{margin:3px;}img.medialeft{border:0;float:left;margin:0 1.5em 0 0;}img.mediaright{border:0;float:right;margin:0 0 0 1.5em;}ul{line-height:1.5em;list-style-type:square;margin:0 0 1em 3.5em;padding:0;}ol{line-height:1.5em;margin:0 0 1em 3.5em;padding:0;font-weight:normal;}div.dokuwiki li ul{margin-bottom:0;}div.dokuwiki li ol{margin-bottom:0;}div.dokuwiki ol{list-style-type:decimal;}div.dokuwiki ol ol{list-style-type:upper-roman;}div.dokuwiki ol ol ol{list-style-type:lower-alpha;}div.dokuwiki ol ol ol ol{list-style-type:lower-greek;}span.li{font-weight:normal;}pre{font-family:monospace;}pre.pre{font-size:8pt;padding:0.5em;border:1px dashed #000;color:Black;overflow:visible;}pre.code{font-size:8pt;padding:0.5em;border:1px dashed #000;color:Black;overflow:visible;}code{font-size:120%;}pre.file{font-size:8pt;padding:0.5em;border:1px dotted #000;color:Black;overflow:visible;}div.footnotes{clear:both;border-top:1px solid #000;padding-left:1em;margin-top:1em;}div.fn{font-size:90%;}a.fn_top{vertical-align:super;font-size:80%;}a.fn_bot{vertical-align:super;font-size:80%;font-weight:bold;}acronym{border:0;}table.inline{font-size:80%;background-color:#fff;border-spacing:0px;border-collapse:collapse;}table.inline th{padding:3px;border:1px solid #000;border-bottom:2px solid #000;}table.inline td{padding:3px;border:1px solid #000;}.leftalign{text-align:left;}.centeralign{text-align:center;}.rightalign{text-align:right;}.toc,.footerinc,.header,.bar,.user{display:none;} \ No newline at end of file diff --git a/labnic/references_software [WikiNIC]_fichiers/css_003.css b/labnic/references_software [WikiNIC]_fichiers/css_003.css new file mode 100644 index 0000000..888861c --- /dev/null +++ b/labnic/references_software [WikiNIC]_fichiers/css_003.css @@ -0,0 +1 @@ +a.interwiki{background:transparent url(/wiki/lib/images/interwiki.png) 0px 1px no-repeat;padding-left:16px;}a.iw_wp{background-image:url(/wiki/lib/images/interwiki/wp.gif)}a.iw_wpfr{background-image:url(/wiki/lib/images/interwiki/wpfr.gif)}a.iw_wpde{background-image:url(/wiki/lib/images/interwiki/wpde.gif)}a.iw_wpes{background-image:url(/wiki/lib/images/interwiki/wpes.gif)}a.iw_wppl{background-image:url(/wiki/lib/images/interwiki/wppl.gif)}a.iw_wpjp{background-image:url(/wiki/lib/images/interwiki/wpjp.gif)}a.iw_wpmeta{background-image:url(/wiki/lib/images/interwiki/wpmeta.gif)}a.iw_doku{background-image:url(/wiki/lib/images/interwiki/doku.gif)}a.iw_dokubug{background-image:url(/wiki/lib/images/interwiki/dokubug.gif)}a.iw_amazon{background-image:url(/wiki/lib/images/interwiki/amazon.gif)}a.iw_amazon_de{background-image:url(/wiki/lib/images/interwiki/amazon.de.gif)}a.iw_amazon_uk{background-image:url(/wiki/lib/images/interwiki/amazon.uk.gif)}a.iw_phpfn{background-image:url(/wiki/lib/images/interwiki/phpfn.gif)}a.iw_coral{background-image:url(/wiki/lib/images/interwiki/coral.gif)}a.iw_sb{background-image:url(/wiki/lib/images/interwiki/sb.gif)}a.iw_google{background-image:url(/wiki/lib/images/interwiki/google.gif)}a.iw_meatball{background-image:url(/wiki/lib/images/interwiki/meatball.gif)}a.iw_wiki{background-image:url(/wiki/lib/images/interwiki/wiki.gif)}a.mediafile{background:transparent url(/wiki/lib/images/fileicons/file.png) 0px 1px no-repeat;padding-left:18px;padding-bottom:1px;}a.mf_deb{background-image:url(/wiki/lib/images/fileicons/deb.png)}a.mf_rar{background-image:url(/wiki/lib/images/fileicons/rar.png)}a.mf_cpp{background-image:url(/wiki/lib/images/fileicons/cpp.png)}a.mf_jpg{background-image:url(/wiki/lib/images/fileicons/jpg.png)}a.mf_xls{background-image:url(/wiki/lib/images/fileicons/xls.png)}a.mf_xml{background-image:url(/wiki/lib/images/fileicons/xml.png)}a.mf_css{background-image:url(/wiki/lib/images/fileicons/css.png)}a.mf_csv{background-image:url(/wiki/lib/images/fileicons/csv.png)}a.mf_c{background-image:url(/wiki/lib/images/fileicons/c.png)}a.mf_doc{background-image:url(/wiki/lib/images/fileicons/doc.png)}a.mf_html{background-image:url(/wiki/lib/images/fileicons/html.png)}a.mf_rpm{background-image:url(/wiki/lib/images/fileicons/rpm.png)}a.mf_rtf{background-image:url(/wiki/lib/images/fileicons/rtf.png)}a.mf_zip{background-image:url(/wiki/lib/images/fileicons/zip.png)}a.mf_tar{background-image:url(/wiki/lib/images/fileicons/tar.png)}a.mf_audio{background-image:url(/wiki/lib/images/fileicons/audio.png)}a.mf_sql{background-image:url(/wiki/lib/images/fileicons/sql.png)}a.mf_lua{background-image:url(/wiki/lib/images/fileicons/lua.png)}a.mf_tgz{background-image:url(/wiki/lib/images/fileicons/tgz.png)}a.mf_mp3{background-image:url(/wiki/lib/images/fileicons/mp3.png)}a.mf_cs{background-image:url(/wiki/lib/images/fileicons/cs.png)}a.mf_swf{background-image:url(/wiki/lib/images/fileicons/swf.png)}a.mf_conf{background-image:url(/wiki/lib/images/fileicons/conf.png)}a.mf_sxc{background-image:url(/wiki/lib/images/fileicons/sxc.png)}a.mf_sxd{background-image:url(/wiki/lib/images/fileicons/sxd.png)}a.mf_sxi{background-image:url(/wiki/lib/images/fileicons/sxi.png)}a.mf_sxw{background-image:url(/wiki/lib/images/fileicons/sxw.png)}a.mf_gz{background-image:url(/wiki/lib/images/fileicons/gz.png)}a.mf_gif{background-image:url(/wiki/lib/images/fileicons/gif.png)}a.mf_js{background-image:url(/wiki/lib/images/fileicons/js.png)}a.mf_pptx{background-image:url(/wiki/lib/images/fileicons/pptx.png)}a.mf_pl{background-image:url(/wiki/lib/images/fileicons/pl.png)}a.mf_odc{background-image:url(/wiki/lib/images/fileicons/odc.png)}a.mf_odf{background-image:url(/wiki/lib/images/fileicons/odf.png)}a.mf_odg{background-image:url(/wiki/lib/images/fileicons/odg.png)}a.mf_odi{background-image:url(/wiki/lib/images/fileicons/odi.png)}a.mf_ps{background-image:url(/wiki/lib/images/fileicons/ps.png)}a.mf_rb{background-image:url(/wiki/lib/images/fileicons/rb.png)}a.mf_py{background-image:url(/wiki/lib/images/fileicons/py.png)}a.mf_odp{background-image:url(/wiki/lib/images/fileicons/odp.png)}a.mf_ods{background-image:url(/wiki/lib/images/fileicons/ods.png)}a.mf_txt{background-image:url(/wiki/lib/images/fileicons/txt.png)}a.mf_odt{background-image:url(/wiki/lib/images/fileicons/odt.png)}a.mf_ogg{background-image:url(/wiki/lib/images/fileicons/ogg.png)}a.mf_jpeg{background-image:url(/wiki/lib/images/fileicons/jpeg.png)}a.mf_java{background-image:url(/wiki/lib/images/fileicons/java.png)}a.mf_xlsx{background-image:url(/wiki/lib/images/fileicons/xlsx.png)}a.mf_wav{background-image:url(/wiki/lib/images/fileicons/wav.png)}a.mf_pdf{background-image:url(/wiki/lib/images/fileicons/pdf.png)}a.mf_htm{background-image:url(/wiki/lib/images/fileicons/htm.png)}a.mf_php{background-image:url(/wiki/lib/images/fileicons/php.png)}a.mf_7z{background-image:url(/wiki/lib/images/fileicons/7z.png)}a.mf_png{background-image:url(/wiki/lib/images/fileicons/png.png)}a.mf_docx{background-image:url(/wiki/lib/images/fileicons/docx.png)}a.mf_ppt{background-image:url(/wiki/lib/images/fileicons/ppt.png)}a.mf_bz2{background-image:url(/wiki/lib/images/fileicons/bz2.png)} \ No newline at end of file diff --git a/labnic/references_software [WikiNIC]_fichiers/icon_wink.gif b/labnic/references_software [WikiNIC]_fichiers/icon_wink.gif new file mode 100644 index 0000000000000000000000000000000000000000..d1482880421dde677d3302940aa875ff22a11b06 GIT binary patch literal 170 zcmZ?wbhEHba9D@n#Zhbsv zutHU>;J1F0R>6zE2`tV}8xE^#?@bGklrZsUYAp!;dbG*vY{CM88Jk-qvwbqQUtQFY Pm?x1IA$CiJiNP8Goi0B( literal 0 HcmV?d00001 diff --git a/labnic/references_software [WikiNIC]_fichiers/indexer.gif b/labnic/references_software [WikiNIC]_fichiers/indexer.gif new file mode 100644 index 0000000000000000000000000000000000000000..9935f82104a336a22da8f8bac84db0749a4bd27b GIT binary patch literal 42 pcmZ?wbhEHbWMp7uXkY+=|Ns9h{$ycf01D`U_#hbuCPp6yYXG?z2LJ#7 literal 0 HcmV?d00001 diff --git a/labnic/references_software [WikiNIC]_fichiers/js.js b/labnic/references_software [WikiNIC]_fichiers/js.js new file mode 100644 index 0000000..bb2ea43 --- /dev/null +++ b/labnic/references_software [WikiNIC]_fichiers/js.js @@ -0,0 +1 @@ +var DOKU_BASE='/wiki/';var DOKU_TPL='/wiki/lib/tpl/default/';var DOKU_UHN=0;var DOKU_UHC=0;LANG={"notsavedyet":"Unsaved changes will be lost.","searchmedia":"Search for files","keepopen":"Keep window open on selection","hidedetails":"Hide Details","mediatitle":"Link settings","mediadisplay":"Link type","mediaalign":"Alignment","mediasize":"Image size","mediatarget":"Link target","mediaclose":"Close","mediainsert":"Insert","mediadisplayimg":"Show the image.","mediadisplaylnk":"Show only the link.","mediasmall":"Small version","mediamedium":"Medium version","medialarge":"Large version","mediaoriginal":"Original version","medialnk":"Link to detail page","mediadirect":"Direct link to original","medianolnk":"No link","medianolink":"Do not link the image","medialeft":"Align the image on the left.","mediaright":"Align the image on the right.","mediacenter":"Align the image in the middle.","medianoalign":"Use no align.","nosmblinks":"Linking to Windows shares only works in Microsoft Internet Explorer.\nYou still can copy and paste the link.","linkwiz":"Link Wizard","linkto":"Link to:","del_confirm":"Really delete selected item(s)?","mu_btn":"Upload multiple files at once","plugins":[]};var toolbar=[{"type":"format","title":"Bold Text","icon":"bold.png","key":"b","open":"**","close":"**","block":false},{"type":"format","title":"Italic Text","icon":"italic.png","key":"i","open":"\/\/","close":"\/\/","block":false},{"type":"format","title":"Underlined Text","icon":"underline.png","key":"u","open":"__","close":"__","block":false},{"type":"format","title":"Code Text","icon":"mono.png","key":"c","open":"''","close":"''","block":false},{"type":"format","title":"Strike-through Text","icon":"strike.png","key":"d","open":"","close":"<\/del>","block":false},{"type":"autohead","title":"Same Level Headline","icon":"hequal.png","key":"8","text":"Headline","mod":0,"block":true},{"type":"autohead","title":"Lower Headline","icon":"hminus.png","key":"9","text":"Headline","mod":1,"block":true},{"type":"autohead","title":"Higher Headline","icon":"hplus.png","key":"0","text":"Headline","mod":-1,"block":true},{"type":"picker","title":"Select Headline","icon":"h.png","class":"pk_hl","list":[{"type":"format","title":"Level 1 Headline","icon":"h1.png","key":"1","open":"====== ","close":" ======\\n"},{"type":"format","title":"Level 2 Headline","icon":"h2.png","key":"2","open":"===== ","close":" =====\\n"},{"type":"format","title":"Level 3 Headline","icon":"h3.png","key":"3","open":"==== ","close":" ====\\n"},{"type":"format","title":"Level 4 Headline","icon":"h4.png","key":"4","open":"=== ","close":" ===\\n"},{"type":"format","title":"Level 5 Headline","icon":"h5.png","key":"5","open":"== ","close":" ==\\n"}],"block":true},{"type":"linkwiz","title":"Internal Link","icon":"link.png","key":"l","open":"[[","close":"]]","block":false},{"type":"format","title":"External Link","icon":"linkextern.png","open":"[[","close":"]]","sample":"http:\/\/example.com|External Link","block":false},{"type":"formatln","title":"Ordered List Item","icon":"ol.png","open":" - ","close":"","key":"-","block":true},{"type":"formatln","title":"Unordered List Item","icon":"ul.png","open":" * ","close":"","key":".","block":true},{"type":"insert","title":"Horizontal Rule","icon":"hr.png","insert":"\\n----\\n","block":true},{"type":"mediapopup","title":"Add Images and other files","icon":"image.png","url":"lib\/exe\/mediamanager.php?ns=","name":"mediaselect","options":"width=750,height=500,left=20,top=20,scrollbars=yes,resizable=yes","block":false},{"type":"picker","title":"Smileys","icon":"smiley.png","list":{"8-)":"icon_cool.gif","8-O":"icon_eek.gif","8-o":"icon_eek.gif",":-(":"icon_sad.gif",":-)":"icon_smile.gif","=)":"icon_smile2.gif",":-\/":"icon_doubt.gif",":-\\":"icon_doubt2.gif",":-?":"icon_confused.gif",":-D":"icon_biggrin.gif",":-P":"icon_razz.gif",":-o":"icon_surprised.gif",":-O":"icon_surprised.gif",":-x":"icon_silenced.gif",":-X":"icon_silenced.gif",":-|":"icon_neutral.gif",";-)":"icon_wink.gif","^_^":"icon_fun.gif",":?:":"icon_question.gif",":!:":"icon_exclaim.gif","LOL":"icon_lol.gif","FIXME":"fixme.gif","DELETEME":"delete.gif"},"icobase":"smileys","block":false},{"type":"picker","title":"Special Chars","icon":"chars.png","list":["\u00c0","\u00e0","\u00c1","\u00e1","\u00c2","\u00e2","\u00c3","\u00e3","\u00c4","\u00e4","\u01cd","\u01ce","\u0102","\u0103","\u00c5","\u00e5","\u0100","\u0101","\u0104","\u0105","\u00c6","\u00e6","\u0106","\u0107","\u00c7","\u00e7","\u010c","\u010d","\u0108","\u0109","\u010a","\u010b","\u00d0","\u0111","\u00f0","\u010e","\u010f","\u00c8","\u00e8","\u00c9","\u00e9","\u00ca","\u00ea","\u00cb","\u00eb","\u011a","\u011b","\u0112","\u0113","\u0116","\u0117","\u0118","\u0119","\u0122","\u0123","\u011c","\u011d","\u011e","\u011f","\u0120","\u0121","\u0124","\u0125","\u00cc","\u00ec","\u00cd","\u00ed","\u00ce","\u00ee","\u00cf","\u00ef","\u01cf","\u01d0","\u012a","\u012b","\u0130","\u0131","\u012e","\u012f","\u0134","\u0135","\u0136","\u0137","\u0139","\u013a","\u013b","\u013c","\u013d","\u013e","\u0141","\u0142","\u013f","\u0140","\u0143","\u0144","\u00d1","\u00f1","\u0145","\u0146","\u0147","\u0148","\u00d2","\u00f2","\u00d3","\u00f3","\u00d4","\u00f4","\u00d5","\u00f5","\u00d6","\u00f6","\u01d1","\u01d2","\u014c","\u014d","\u0150","\u0151","\u0152","\u0153","\u00d8","\u00f8","\u0154","\u0155","\u0156","\u0157","\u0158","\u0159","\u015a","\u015b","\u015e","\u015f","\u0160","\u0161","\u015c","\u015d","\u0162","\u0163","\u0164","\u0165","\u00d9","\u00f9","\u00da","\u00fa","\u00db","\u00fb","\u00dc","\u00fc","\u01d3","\u01d4","\u016c","\u016d","\u016a","\u016b","\u016e","\u016f","\u01d6","\u01d8","\u01da","\u01dc","\u0172","\u0173","\u0170","\u0171","\u0174","\u0175","\u00dd","\u00fd","\u0178","\u00ff","\u0176","\u0177","\u0179","\u017a","\u017d","\u017e","\u017b","\u017c","\u00de","\u00fe","\u00df","\u0126","\u0127","\u00bf","\u00a1","\u00a2","\u00a3","\u00a4","\u00a5","\u20ac","\u00a6","\u00a7","\u00aa","\u00ac","\u00af","\u00b0","\u00b1","\u00f7","\u2030","\u00bc","\u00bd","\u00be","\u00b9","\u00b2","\u00b3","\u00b5","\u00b6","\u2020","\u2021","\u00b7","\u2022","\u00ba","\u2200","\u2202","\u2203","\u018f","\u0259","\u2205","\u2207","\u2208","\u2209","\u220b","\u220f","\u2211","\u203e","\u2212","\u2217","\u221a","\u221d","\u221e","\u2220","\u2227","\u2228","\u2229","\u222a","\u222b","\u2234","\u223c","\u2245","\u2248","\u2260","\u2261","\u2264","\u2265","\u2282","\u2283","\u2284","\u2286","\u2287","\u2295","\u2297","\u22a5","\u22c5","\u25ca","\u2118","\u2111","\u211c","\u2135","\u2660","\u2663","\u2665","\u2666","\u03b1","\u03b2","\u0393","\u03b3","\u0394","\u03b4","\u03b5","\u03b6","\u03b7","\u0398","\u03b8","\u03b9","\u03ba","\u039b","\u03bb","\u03bc","\u039e","\u03be","\u03a0","\u03c0","\u03c1","\u03a3","\u03c3","\u03a4","\u03c4","\u03c5","\u03a6","\u03c6","\u03c7","\u03a8","\u03c8","\u03a9","\u03c9","\u2605","\u2606","\u260e","\u261a","\u261b","\u261c","\u261d","\u261e","\u261f","\u2639","\u263a","\u2714","\u2718","\u00d7","\u201e","\u201c","\u201d","\u201a","\u2018","\u2019","\u00ab","\u00bb","\u2039","\u203a","\u2014","\u2013","\u2026","\u2190","\u2191","\u2192","\u2193","\u2194","\u21d0","\u21d1","\u21d2","\u21d3","\u21d4","\u00a9","\u2122","\u00ae","\u2032","\u2033","[","]","{","}","~","(",")","%","\u00a7","$","#","|","@"],"block":false},{"type":"signature","title":"Insert Signature","icon":"sig.png","key":"y","block":false}];function isUndefined(prop){return(typeof prop=='undefined');}function isFunction(prop){return(typeof prop=='function');}function isString(prop){return(typeof prop=='string');}function isNumber(prop){return(typeof prop=='number');}function isNumeric(prop){return isNumber(prop)&&!isNaN(prop)&&isFinite(prop);}function isArray(prop){return(prop instanceof Array);}function isRegExp(prop){return(prop instanceof RegExp);}function isBoolean(prop){return('boolean'==typeof prop);}function isScalar(prop){return isNumeric(prop)||isString(prop);}function isEmpty(prop){if(isBoolean(prop))return false;if(isRegExp(prop)&&new RegExp("").toString()==prop.toString())return true;if(isString(prop)||isNumber(prop))return!prop;if(Boolean(prop)&&false!=prop){for(var i in prop)if(prop.hasOwnProperty(i))return false;}return true;}if('undefined'==typeof Object.hasOwnProperty){Object.prototype.hasOwnProperty=function(prop){return!('undefined'==typeof this[prop]||this.constructor&&this.constructor.prototype[prop]&&this[prop]===this.constructor.prototype[prop]);};}function hasFlash(version){var ver=0;try{if(navigator.plugins!=null&&navigator.plugins.length>0){ver=navigator.plugins["Shockwave Flash"].description.split(' ')[2].split('.')[0];}else{var axo=new ActiveXObject("ShockwaveFlash.ShockwaveFlash");ver=axo.GetVariable("$version").split(' ')[1].split(',')[0];}}catch(e){}if(ver>=version)return true;return false;}function addEvent(element,type,handler){if(!handler.$$guid)handler.$$guid=addEvent.guid++;if(!element.events)element.events={};var handlers=element.events[type];if(!handlers){handlers=element.events[type]={};if(element["on"+type]){handlers[0]=element["on"+type];}}handlers[handler.$$guid]=handler;element["on"+type]=handleEvent;};addEvent.guid=1;function removeEvent(element,type,handler){if(element.events&&element.events[type]){delete element.events[type][handler.$$guid];}};function handleEvent(event){var returnValue=true;event=event||fixEvent(window.event,this);var handlers=this.events[event.type];for(var i in handlers){if(!handlers.hasOwnProperty(i))continue;if(handlers[i].call(this,event)===false){returnValue=false;}}return returnValue;};function fixEvent(event,_this){event.preventDefault=fixEvent.preventDefault;event.stopPropagation=fixEvent.stopPropagation;event.target=event.srcElement;event.currentTarget=_this;var base=(document.documentElement.scrollTop?document.documentElement:document.body);event.pageX=(typeof event.pageX!=='undefined')?event.pageX:event.clientX+base.scrollLeft;event.pageY=(typeof event.pageY!=='undefined')?event.pageY:event.clientY+base.scrollTop;return event;};fixEvent.preventDefault=function(){this.returnValue=false;};fixEvent.stopPropagation=function(){this.cancelBubble=true;};window.fireoninit=function(){if(arguments.callee.done)return;arguments.callee.done=true;if(_timer){clearInterval(_timer);_timer=null;}if(typeof window.oninit=='function'){window.oninit();}};if(document.addEventListener){document.addEventListener("DOMContentLoaded",window.fireoninit,null);}/*@cc_on @if(@_win32)document.write("<\/script>");var script=document.getElementById("__ie_init");script.onreadystatechange=function(){if(this.readyState=="complete"){window.fireoninit();}};@end @*/if(/WebKit/i.test(navigator.userAgent)){var _timer=setInterval(function(){if(/loaded|complete/.test(document.readyState)){window.fireoninit();}},10);}window.onload=window.fireoninit;window.oninit=function(){};function addInitEvent(func){var oldoninit=window.oninit;if(typeof window.oninit!='function'){window.oninit=func;}else{window.oninit=function(){oldoninit();func();};}}function bind(fnc){var args=Array.prototype.slice.call(arguments,1);return function(){return fnc.apply(this,args);};}var timer={_cur_id:0,_handlers:{},execDispatch:function(id){timer._handlers[id]();},add:function(func,timeout){var id=++timer._cur_id;timer._handlers[id]=func;return window.setTimeout('timer.execDispatch('+id+')',timeout);}};function Delay(func,timeout){this.func=func;if(timeout){this.timeout=timeout;}}Delay.prototype={func:null,timeout:500,delTimer:function(){if(this.timer!==null){window.clearTimeout(this.timer);this.timer=null;}},start:function(){this.delTimer();var _this=this;this.timer=timer.add(function(){_this.exec.call(_this);},this.timeout);this._data={_this:arguments[0],_params:Array.prototype.slice.call(arguments,2)};},exec:function(){this.delTimer();this.func.call(this._data._this,this._data._params);}};DokuCookie={data:Array(),name:'DOKU_PREFS',setValue:function(key,val){DokuCookie.init();DokuCookie.data[key]=val;var now=new Date();DokuCookie.fixDate(now);now.setTime(now.getTime()+365*24*60*60*1000);var text='';for(var key in DokuCookie.data){if(!DokuCookie.data.hasOwnProperty(key))continue;text+='#'+escape(key)+'#'+DokuCookie.data[key];}DokuCookie.setCookie(DokuCookie.name,text.substr(1),now,DOKU_BASE);},getValue:function(key){DokuCookie.init();return DokuCookie.data[key];},init:function(){if(DokuCookie.data.length)return;var text=DokuCookie.getCookie(DokuCookie.name);if(text){var parts=text.split('#');for(var i=0;i0){date.setTime(date.getTime()-skew);}}};var clientPC=navigator.userAgent.toLowerCase();var is_macos=navigator.appVersion.indexOf('Mac')!=-1;var is_gecko=((clientPC.indexOf('gecko')!=-1)&&(clientPC.indexOf('spoofer')==-1)&&(clientPC.indexOf('khtml')==-1)&&(clientPC.indexOf('netscape/7.0')==-1));var is_safari=((clientPC.indexOf('applewebkit')!=-1)&&(clientPC.indexOf('spoofer')==-1));var is_khtml=(navigator.vendor=='KDE'||(document.childNodes&&!document.all&&!navigator.taintEnabled));if(clientPC.indexOf('opera')!=-1){var is_opera=true;var is_opera_preseven=(window.opera&&!document.childNodes);var is_opera_seven=(window.opera&&document.childNodes);}function $(){var elements=new Array();for(var i=0;i');}function hideLoadBar(id){obj=$(id);if(obj)obj.style.display="none";}function addTocToggle(){if(!document.getElementById)return;var header=$('toc__header');if(!header)return;var toc=$('toc__inside');var obj=document.createElement('span');obj.id='toc__toggle';obj.style.cursor='pointer';if(toc&&toc.style.display=='none'){obj.innerHTML='+';obj.className='toc_open';}else{obj.innerHTML='';obj.className='toc_close';}prependChild(header,obj);obj.parentNode.onclick=toggleToc;obj.parentNode.style.cursor='pointer';}function toggleToc(){var toc=$('toc__inside');var obj=$('toc__toggle');if(toc.style.display=='none'){toc.style.display='';obj.innerHTML='';obj.className='toc_close';}else{toc.style.display='none';obj.innerHTML='+';obj.className='toc_open';}}function insitu_popup(target,popup_id){var fndiv=$(popup_id);if(!fndiv){fndiv=document.createElement('div');fndiv.id=popup_id;fndiv.className='insitu-footnote JSpopup dokuwiki';addEvent(fndiv,'mouseout',function(e){var p=e.relatedTarget||e.toElement;while(p&&p!==this){p=p.parentNode;}if(p===this){return;}this.style.display='none';});getElementsByClass('dokuwiki',document.body,'div')[0].appendChild(fndiv);}fndiv.style.position='absolute';fndiv.style.left=findPosX(target)+'px';fndiv.style.top=(findPosY(target)+target.offsetHeight*1.5)+'px';fndiv.style.display='';return fndiv;}function footnote(e){var fndiv=insitu_popup(e.target,'insitu__fn');var a=$("fn__"+e.target.id.substr(5));if(!a){return;}var content=new String(a.parentNode.parentNode.innerHTML);content=content.replace(/.*<\/sup>/gi,'');content=content.replace(/^\s+(,\s+)+/,'');content=content.replace(/\bid=(['"])([^"']+)\1/gi,'id="insitu__$2');fndiv.innerHTML=content;}addInitEvent(function(){var elems=getElementsByClass('fn_top',null,'a');for(var i=0;i=2)input2.disabled=(input2.type=='checkbox'&&!input2.checked);else input2.disabled=(input2.type!='checkbox');}});input1.checked=false;}else if(input1.type=='submit'){input1.disabled=true;}}});addInitEvent(function(){var selector=$('action__selector');if(!selector)return;addEvent(selector,'change',function(e){this.form.submit();});$('action__selectorbtn').style.display='none';});function checkWindowsShares(){if(!LANG['nosmblinks'])return true;if(document.all!=null)return true;var elems=getElementsByClass('windows',document,'a');if(elems){for(var i=0;iloading...';listitem.appendChild(ul);listitem.className='open';}},this.throbber_delay);ajax.elementObj=ul;ajax.afterCompletion=function(){window.clearTimeout(timeout);index.treeattach(ul);if(listitem.className!='open'){if(!listitem.open){ul.style.display='none';}listitem.appendChild(ul);if(listitem.open){listitem.className='open';}}};ajax.runAJAX(clicky.search.substr(1)+'&call=index');e.preventDefault();return false;}};addInitEvent(function(){index.treeattach($('index__tree'));});var drag={obj:null,handle:null,oX:0,oY:0,eX:0,eY:0,attach:function(obj,handle){if(handle){handle.dragobject=obj;}else{handle=obj;}var _this=this;addEvent($(handle),'mousedown',function(e){return _this.start(e);});},start:function(e){this.handle=e.target;if(this.handle.dragobject){this.obj=this.handle.dragobject;}else{this.obj=this.handle;}this.handle.className+=' ondrag';this.obj.className+=' ondrag';this.oX=parseInt(this.obj.style.left);this.oY=parseInt(this.obj.style.top);this.eX=e.pageX;this.eY=e.pageY;var _this=this;this.mousehandlers=[function(e){return _this.drag(e);},function(e){return _this.stop(e);}];addEvent(document,'mousemove',this.mousehandlers[0]);addEvent(document,'mouseup',this.mousehandlers[1]);return false;},stop:function(){this.handle.className=this.handle.className.replace(/ ?ondrag/,'');this.obj.className=this.obj.className.replace(/ ?ondrag/,'');removeEvent(document,'mousemove',this.mousehandlers[0]);removeEvent(document,'mouseup',this.mousehandlers[1]);this.obj=null;this.handle=null;},drag:function(e){if(this.obj){this.obj.style.top=(e.pageY+this.oY-this.eY+'px');this.obj.style.left=(e.pageX+this.oX-this.eX+'px');}}};function selection_class(){this.start=0;this.end=0;this.obj=null;this.rangeCopy=null;this.scroll=0;this.fix=0;this.getLength=function(){return this.end-this.start;};this.getText=function(){if(!this.obj)return'';return this.obj.value.substring(this.start,this.end);};}function getSelection(textArea){var sel=new selection_class();sel.obj=textArea;sel.start=textArea.value.length;sel.end=textArea.value.length;textArea.focus();if(document.getSelection){sel.start=textArea.selectionStart;sel.end=textArea.selectionEnd;sel.scroll=textArea.scrollTop;}else if(document.selection){sel.rangeCopy=document.selection.createRange().duplicate();if(textArea.tagName==='INPUT'){var before_range=textArea.createTextRange();before_range.expand('textedit');}else{var before_range=document.body.createTextRange();before_range.moveToElementText(textArea);}before_range.setEndPoint("EndToStart",sel.rangeCopy);var before_finished=false,selection_finished=false;var before_text,selection_text;before_text=before_range.text;selection_text=sel.rangeCopy.text;sel.start=before_text.length;sel.end=sel.start+selection_text.length;do{if(!before_finished){if(before_range.compareEndPoints("StartToEnd",before_range)==0){before_finished=true;}else{before_range.moveEnd("character",-1);if(before_range.text==before_text){sel.start+=2;sel.end+=2;}else{before_finished=true;}}}if(!selection_finished){if(sel.rangeCopy.compareEndPoints("StartToEnd",sel.rangeCopy)==0){selection_finished=true;}else{sel.rangeCopy.moveEnd("character",-1);if(sel.rangeCopy.text==selection_text){sel.end+=2;}else{selection_finished=true;}}}}while((!before_finished||!selection_finished));var countNL=function(str){var m=str.split("\r\n");if(!m||!m.length)return 0;return m.length-1;};sel.fix=countNL(sel.obj.value.substring(0,sel.start));}return sel;}function setSelection(selection){if(document.getSelection){selection.obj.setSelectionRange(selection.start,selection.end);if(selection.scroll)selection.obj.scrollTop=selection.scroll;}else if(document.selection){selection.rangeCopy.collapse(true);selection.rangeCopy.moveStart('character',selection.start-selection.fix);selection.rangeCopy.moveEnd('character',selection.end-selection.start);selection.rangeCopy.select();}}function pasteText(selection,text,opts){if(!opts)opts={};selection.obj.value=selection.obj.value.substring(0,selection.start)+text+selection.obj.value.substring(selection.end,selection.obj.value.length);if(is_opera){selection.end=selection.start+text.replace(/\r?\n/g,'\r\n').length;}else{selection.end=selection.start+text.length;}if(opts.startofs)selection.start+=opts.startofs;if(opts.endofs)selection.end-=opts.endofs;if(opts.nosel)selection.start=selection.end;setSelection(selection);}function insertTags(textAreaID,tagOpen,tagClose,sampleText){var txtarea=$(textAreaID);var selection=getSelection(txtarea);var text=selection.getText();var opts;if(text.charAt(text.length-1)==' '){selection.end--;text=selection.getText();}if(!text){text=sampleText;opts={startofs:tagOpen.length,endofs:tagClose.length};}else{opts={nosel:true};}text=tagOpen+text+tagClose;pasteText(selection,text,opts);}function insertAtCarret(textAreaID,text){var txtarea=$(textAreaID);var selection=getSelection(txtarea);pasteText(selection,text,{nosel:true});}var pickercounter=0;function initToolbar(tbid,edid,tb,allowblock){var toolbar=$(tbid);if(!toolbar)return;var edit=$(edid);if(!edit)return;if(edit.readOnly)return;if(typeof allowblock==='undefined'){allowblock=true;}toolbar.innerHTML='';var cnt=tb.length;for(var i=0;i5)lvl=5;var tags='=';for(var i=0;i<=5-lvl;i++)tags+='=';insertTags(edid,tags+' ',' '+tags+"\n",props['text']);pickerClose();return false;}function addBtnActionPicker(btn,props,edid){var pickerid='picker'+(pickercounter++);createPicker(pickerid,props,edid);addEvent(btn,'click',function(){pickerToggle(pickerid,btn);return false;});return true;}function addBtnActionLinkwiz(btn,props,edid){linkwiz.init($(edid));addEvent(btn,'click',function(){linkwiz.toggle();return false;});return true;}function pickerToggle(pickerid,btn){var picker=$(pickerid);if(picker.style.marginLeft=='-10000px'){var x=findPosX(btn);var y=findPosY(btn);picker.style.left=(x+3)+'px';picker.style.top=(y+btn.offsetHeight+3)+'px';picker.style.marginLeft='0px';picker.style.marginTop='0px';}else{picker.style.marginLeft='-10000px';picker.style.marginTop='-10000px';}}function pickerClose(){var pobjs=getElementsByClass('picker');for(var i=0;i3){field.value=field.value.substr(0,linestart)+field.value.substr(linestart+2);selection.start=selection.start-2;selection.end=selection.start;}else{field.value=field.value.substr(0,linestart)+field.value.substr(selection.start);selection.start=linestart;selection.end=linestart;}setSelection(selection);e.preventDefault();return false;}}else if(e.keyCode==32){var match=search.match(/(\n +)([*-] )$/);if(match){field.value=field.value.substr(0,linestart)+' '+field.value.substr(linestart);selection.start=selection.start+2;selection.end=selection.start;setSelection(selection);e.preventDefault();return false;}}}addInitEvent(function(){var field=$('wiki__text');if(!field)return;if(is_opera){addEvent(field,'keypress',keyHandler);}else{addEvent(field,'keydown',keyHandler);}});function currentHeadlineLevel(textboxId){var field=$(textboxId);var selection=getSelection(field);var search="\n"+field.value.substr(0,selection.start);var lasthl=search.lastIndexOf("\n==");if(lasthl==-1&&field.form.prefix){search=field.form.prefix.value;lasthl=search.lastIndexOf("\n==");}search=search.substr(lasthl+1,6);if(search=='======')return 1;if(search.substr(0,5)=='=====')return 2;if(search.substr(0,4)=='====')return 3;if(search.substr(0,3)=='===')return 4;if(search.substr(0,2)=='==')return 5;return 0;}var textChanged=false;function deleteDraft(){if(is_opera)return;var dwform=$('dw__editform');if(dwform){var params='call=draftdel';params+='&id='+encodeURIComponent(dwform.elements.id.value);var sackobj=new sack(DOKU_BASE+'lib/exe/ajax.php');sackobj.asynchronous=false;sackobj.method='GET';sackobj.AjaxFailedAlert='';sackobj.encodeURIString=false;sackobj.runAJAX(params);}}addInitEvent(function(){var editform=$('dw__editform');if(!editform)return;var edit_text=$('wiki__text');if(edit_text){if(edit_text.readOnly)return;edit_text.focus();}var checkfunc=function(){textChanged=true;summaryCheck();};addEvent(editform,'change',checkfunc);addEvent(editform,'keydown',checkfunc);window.onbeforeunload=function(){if(textChanged){return LANG.notsavedyet;}};window.onunload=deleteDraft;addEvent($('edbtn__save'),'click',function(){textChanged=false;});addEvent($('edbtn__preview'),'click',function(){textChanged=false;});var summary=$('edit__summary');addEvent(summary,'change',summaryCheck);addEvent(summary,'keyup',summaryCheck);if(textChanged)summaryCheck();});function summaryCheck(){var sum=document.getElementById('edit__summary');if(sum.value===''){sum.className='missing';}else{sum.className='edit';}}function locktimer_class(){this.sack=null;this.timeout=0;this.timerID=null;this.lasttime=null;this.msg='';this.pageid='';};var locktimer=new locktimer_class();locktimer.init=function(timeout,msg,draft){locktimer.timeout=timeout*1000;locktimer.msg=msg;locktimer.draft=draft;locktimer.lasttime=new Date();if(!$('dw__editform'))return;locktimer.pageid=$('dw__editform').elements.id.value;if(!locktimer.pageid)return;locktimer.sack=new sack(DOKU_BASE+'lib/exe/ajax.php');locktimer.sack.AjaxFailedAlert='';locktimer.sack.encodeURIString=false;locktimer.sack.onCompletion=locktimer.refreshed;addEvent($('dw__editform'),'keypress',function(){locktimer.refresh();});locktimer.reset();};locktimer.reset=function(){locktimer.clear();locktimer.timerID=window.setTimeout("locktimer.warning()",locktimer.timeout);};locktimer.warning=function(){locktimer.clear();alert(locktimer.msg);};locktimer.clear=function(){if(locktimer.timerID!==null){window.clearTimeout(locktimer.timerID);locktimer.timerID=null;}};locktimer.refresh=function(){var now=new Date();if(now.getTime()-locktimer.lasttime.getTime()>30*1000){var params='call=lock&id='+encodeURIComponent(locktimer.pageid);var dwform=$('dw__editform');if(locktimer.draft&&dwform.elements.wikitext){params+='&prefix='+encodeURIComponent(dwform.elements.prefix.value);params+='&wikitext='+encodeURIComponent(dwform.elements.wikitext.value);params+='&suffix='+encodeURIComponent(dwform.elements.suffix.value);if(dwform.elements.date){params+='&date='+encodeURIComponent(dwform.elements.date.value);}}locktimer.sack.runAJAX(params);locktimer.lasttime=now;}};locktimer.refreshed=function(){var data=this.response;var error=data.charAt(0);data=data.substring(1);$('draft__status').innerHTML=data;if(error!='1')return;locktimer.reset();};var linkwiz={wiz:null,entry:null,result:null,timer:null,sack:null,textArea:null,selected:-1,selection:null,init:function(textArea){linkwiz.sack=new sack(DOKU_BASE+'lib/exe/ajax.php');linkwiz.sack.AjaxFailedAlert='';linkwiz.sack.encodeURIString=false;linkwiz.wiz=document.createElement('div');linkwiz.wiz.id='link__wiz';linkwiz.wiz.className='picker';linkwiz.wiz.style.top=(findPosY(textArea)+20)+'px';linkwiz.wiz.style.left=(findPosX(textArea)+80)+'px';linkwiz.wiz.style.marginLeft='-10000px';linkwiz.wiz.style.marginTop='-10000px';linkwiz.wiz.style.position='absolute';linkwiz.wiz.innerHTML=''+'
    '+LANG['linkto']+'
    '+'';$('dw__editform').parentNode.appendChild(linkwiz.wiz);linkwiz.textArea=textArea;linkwiz.result=$('link__wiz_result');linkwiz.entry=$('link__wiz_entry');var obj;obj=$('link__wiz_close');obj.onclick=linkwiz.hide;linkwiz.sack.elementObj=linkwiz.result;addEvent(linkwiz.entry,'keyup',linkwiz.onEntry);addEvent(linkwiz.result,'click',linkwiz.onResultClick);drag.attach(linkwiz.wiz,$('link__wiz_header'));},onEntry:function(e){if(e.keyCode==37||e.keyCode==39){return true;}if(e.keyCode==27){linkwiz.hide();e.preventDefault();e.stopPropagation();return false;}if(e.keyCode==38){linkwiz.select(linkwiz.selected-1);e.preventDefault();e.stopPropagation();return false;}if(e.keyCode==40){linkwiz.select(linkwiz.selected+1);e.preventDefault();e.stopPropagation();return false;}if(e.keyCode==13){if(linkwiz.selected>-1){var obj=linkwiz.getResult(linkwiz.selected);if(obj){var a=obj.getElementsByTagName('A')[0];linkwiz.resultClick(a);}}else if(linkwiz.entry.value){linkwiz.insertLink(linkwiz.entry.value);}e.preventDefault();e.stopPropagation();return false;}linkwiz.autocomplete();},getResult:function(num){var obj;var childs=linkwiz.result.getElementsByTagName('DIV');obj=childs[num];if(obj){return obj;}else{return null;}},select:function(num){if(num<0){linkwiz.deselect();return;}var obj=linkwiz.getResult(num);if(obj){linkwiz.deselect();obj.className+=' selected';if(obj.offsetTop>linkwiz.result.scrollTop+linkwiz.result.clientHeight){linkwiz.result.scrollTop+=obj.clientHeight;}else if(obj.offsetTop-linkwiz.result.clientHeightlinkwiz.result.scrollTop+linkwiz.result.clientHeight)||(obj.offsetTop-1){var obj=linkwiz.getResult(linkwiz.selected);if(obj){obj.className=obj.className.replace(/ ?selected/,'');}}linkwiz.selected=-1;},onResultClick:function(e){if(e.target.tagName!='A')return;e.stopPropagation();e.preventDefault();linkwiz.resultClick(e.target);return false;},resultClick:function(a){var id=a.title;if(id==''||id.substr(id.length-1)==':'){linkwiz.entry.value=id;linkwiz.autocomplete_exec();}else{linkwiz.entry.value=id;if(a.nextSibling&&a.nextSibling.tagName=='SPAN'){linkwiz.insertLink(a.nextSibling.innerHTML);}else{linkwiz.insertLink('');}}},insertLink:function(title){if(!linkwiz.entry.value)return;var sel=getSelection(linkwiz.textArea);if(sel.start==0&&sel.end==0)sel=linkwiz.selection;var stxt=sel.getText();if(stxt.charAt(stxt.length-1)==' '){sel.end--;stxt=sel.getText();}if(!stxt&&!DOKU_UHC)stxt=title;if(linkwiz.textArea.form['id'].value.indexOf(':')!=-1&&linkwiz.entry.value.indexOf(':')==-1){linkwiz.entry.value=':'+linkwiz.entry.value;}var link='[['+linkwiz.entry.value+'|';if(stxt)link+=stxt;link+=']]';var so=linkwiz.entry.value.length+3;var eo=2;pasteText(sel,link,{startofs:so,endofs:eo});linkwiz.hide();linkwiz.entry.value=linkwiz.entry.value.replace(/(^:)?[^:]*$/,'');},autocomplete:function(){if(linkwiz.timer!==null){window.clearTimeout(linkwiz.timer);linkwiz.timer=null;}linkwiz.timer=window.setTimeout(linkwiz.autocomplete_exec,350);},autocomplete_exec:function(){linkwiz.deselect();linkwiz.result.innerHTML='';linkwiz.sack.runAJAX('call=linkwiz&q='+encodeURI(linkwiz.entry.value));},clear:function(){linkwiz.result.innerHTML='Search for a matching page name above, or browse through the pages on the right';linkwiz.entry.value='';},show:function(){linkwiz.selection=getSelection(linkwiz.textArea);linkwiz.wiz.style.marginLeft='0px';linkwiz.wiz.style.marginTop='0px';linkwiz.entry.focus();linkwiz.autocomplete();},hide:function(){linkwiz.wiz.style.marginLeft='-10000px';linkwiz.wiz.style.marginTop='-10000px';linkwiz.textArea.focus();},toggle:function(){if(linkwiz.wiz.style.marginLeft=='-10000px'){linkwiz.show();}else{linkwiz.hide();}}};var media_manager={keepopen:false,hide:false,align:false,popup:false,id:false,display:false,link:false,size:false,ext:false,treeattach:function(obj){if(!obj)return;var items=obj.getElementsByTagName('li');for(var i=0;i=1){opts+=(optsstart)?'&':'?';if(s=="1"){opts+='100';if(media_manager.ext=='swf'){opts+='x62';}}else if(s=="2"){opts+='200';if(media_manager.ext=='swf'){opts+='x123';}}else if(s=="3"){opts+='300';if(media_manager.ext=='swf'){opts+='x185';}}}if(media_manager.align=='1'){alignleft='';alignright=' ';}if(media_manager.align=='2'){alignleft=' ';alignright=' ';}if(media_manager.align=='3'){alignleft=' ';alignright='';}}}opener.insertTags('wiki__text','{{'+alignleft+id+opts+alignright+'|','}}','');if(!media_manager.keepopen)window.close();opener.focus();return false;},list:function(event,link){var ajax=new sack(DOKU_BASE+'lib/exe/ajax.php');ajax.AjaxFailedAlert='';ajax.encodeURIString=false;if(ajax.failed)return true;cleanMsgArea();var content=$('media__content');content.innerHTML='...';ajax.elementObj=content;ajax.afterCompletion=function(){media_manager.selectorattach(content);media_manager.confirmattach(content);media_manager.updatehide();media_manager.initFlashUpload();};ajax.runAJAX(link.search.substr(1)+'&call=medialist');return false;},toggle:function(event,clicky){var listitem=clicky.parentNode;var sublists=listitem.getElementsByTagName('ul');if(sublists.length){listitem.removeChild(sublists[0]);clicky.src=DOKU_BASE+'lib/images/plus.gif';return false;}var link=listitem.getElementsByTagName('a')[0];var ajax=new sack(DOKU_BASE+'lib/exe/ajax.php');ajax.AjaxFailedAlert='';ajax.encodeURIString=false;if(ajax.failed)return true;var ul=document.createElement('ul');listitem.appendChild(ul);ajax.elementObj=ul;ajax.afterCompletion=function(){media_manager.treeattach(ul);};ajax.runAJAX(link.search.substr(1)+'&call=medians');clicky.src=DOKU_BASE+'lib/images/minus.gif';return false;},suggest:function(){var file=$('upload__file');var name=$('upload__name');if(!file||!name)return;var text=file.value;text=text.substr(text.lastIndexOf('/')+1);text=text.substr(text.lastIndexOf('\\')+1);name.value=text;},initFlashUpload:function(){if(!hasFlash(8))return;var oform=$('dw__upload');var oflash=$('dw__flashupload');if(!oform||!oflash)return;var clicky=document.createElement('img');clicky.src=DOKU_BASE+'lib/images/multiupload.png';clicky.title=LANG['mu_btn'];clicky.alt=LANG['mu_btn'];clicky.style.cursor='pointer';clicky.onclick=function(){oform.style.display='none';oflash.style.display='';};oform.appendChild(clicky);},closePopup:function(event){$('media__popup').style.display='none';},setalign:function(event,cb){if(cb.value){DokuCookie.setValue('align',cb.value);media_manager.align=cb.value;media_manager.outSet("media__alignbtn0");media_manager.outSet("media__alignbtn1");media_manager.outSet("media__alignbtn2");media_manager.outSet("media__alignbtn3");media_manager.inSet("media__alignbtn"+cb.value);}else{DokuCookie.setValue('align','');media_manager.align=false;}},setlink:function(event,cb){if(cb.value){DokuCookie.setValue('link',cb.value);media_manager.link=cb.value;media_manager.outSet("media__linkbtn1");media_manager.outSet("media__linkbtn2");media_manager.outSet("media__linkbtn3");media_manager.outSet("media__linkbtn4");media_manager.inSet("media__linkbtn"+cb.value);var size=document.getElementById("media__size");var align=document.getElementById("media__align");if(cb.value!='4'){size.style.display="block";align.style.display="block";}else{size.style.display="none";align.style.display="none";}}else{DokuCookie.setValue('link','');media_manager.link=false;}},setdisplay:function(event,cb){if(cb.value){DokuCookie.setValue('display',cb.value);media_manager.display=cb.value;media_manager.outSet("media__displaybtn1");media_manager.outSet("media__displaybtn2");media_manager.inSet("media__displaybtn"+cb.value);}else{DokuCookie.setValue('display','');media_manager.align=false;}},outSet:function(id){var ele=document.getElementById(id);if(ele==null)return;ele.style.borderStyle="outset";},inSet:function(id){var ele=document.getElementById(id);if(ele==null)return;ele.style.borderStyle="inset";},setsize:function(event,cb){if(cb.value){DokuCookie.setValue('size',cb.value);media_manager.size=cb.value;for(var i=1;i<=4;++i){media_manager.outSet("media__sizebtn"+i);}media_manager.inSet("media__sizebtn"+cb.value);}else{DokuCookie.setValue('size','');media_manager.width=false;}}};addInitEvent(function(){media_manager.treeattach($('media__tree'));media_manager.selectorattach($('media__content'));media_manager.confirmattach($('media__content'));media_manager.attachoptions($('media__opts'));media_manager.initpopup();media_manager.initFlashUpload();});addInitEvent(function(){var form=$('subscribe__form');if(!form){return;}var styleradios={};function update_state(){if(!this.checked){return;}if(this.value.match(/:$/)){styleradios.list.parentNode.style.display='';}else{styleradios.list.parentNode.style.display='none';if(styleradios.list.checked){styleradios.digest.checked='checked';}}}var cur_sel=null;var inputs=form.getElementsByTagName('input');for(var i=0;i/g,">");return str;},treetoggle:function(clicky){var listitem=clicky.parentNode.parentNode;var sublists=listitem.getElementsByTagName('ul');if(sublists.length){listitem.removeChild(sublists[0]);clicky.src=DOKU_BASE+'lib/images/plus.gif';clicky.alt='+';return false;}var link=listitem.getElementsByTagName('a')[0];var ajax=new sack(DOKU_BASE+'lib/plugins/acl/ajax.php');ajax.AjaxFailedAlert='';ajax.encodeURIString=false;if(ajax.failed)return true;var ul=document.createElement('ul');listitem.appendChild(ul);ajax.elementObj=ul;ajax.setVar('ajax','tree');var frm=$('acl__detail').getElementsByTagName('form')[0];ajax.setVar('current_ns',encodeURIComponent(frm.elements['ns'].value));ajax.setVar('current_id',encodeURIComponent(frm.elements['id'].value));ajax.runAJAX(link.search.substr(1));clicky.src=DOKU_BASE+'lib/images/minus.gif';return false;},treehandler:function(e){if(e.target.src){acl.treetoggle(e.target);}else if(e.target.href){var obj=getElementsByClass('cur',$('acl__tree'),'a');for(var i=0;i-1){frm.elements['ns'].value='';frm.elements['id'].value=acl.hsc(acl.parseatt(e.target.search)['id']);}else if(e.target.className.search(/idx_dir/)>-1){frm.elements['ns'].value=acl.hsc(acl.parseatt(e.target.search)['ns']);frm.elements['id'].value='';}acl.loadinfo();}e.stopPropagation();e.preventDefault();return false;}};addInitEvent(acl.init);function isBlank(s){if((s===null)||(s.length===0)){return true;}for(var i=0;i$/)[1];folded_hide=eStrings[1].innerHTML.match(/^$/)[1];var folds=getElementsByClass('folder');for(var i=0;i');function usrmgr_delconfirm(){if($('usrmgr__del')){addEvent($('usrmgr__del'),'click',function(){return confirm(reallyDel);});}};addInitEvent(usrmgr_delconfirm);addInitEvent(function(){var btns=getElementsByClass('btn_incledit',document,'form');for(var i=0;i ouput = EEG struct +% 'eeglab.data' -> output EEGLAB event structure +% 'eeglab.chanlocs' -> output EEGLAB channel structure +% 'eeglab.event' -> output EEGLAB event structure +% 'eeglab.[...]' +% +% 'fieldtrip' -> output FieldTrip structure +% +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2010 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2010-04-22 Creation +% +% ----------------------------- Script History --------------------------------- +if nargin == 1 + format = 'eeglab'; +end +[format,subformat]=strtok(format, '.'); +switch lower(format) + case 'eeglab' + if exist('eeg_emptyset')==2 + EEG = eeg_emptyset; + else % if no eeglab in the path, use local version... + EEG = emptyset(); + end + [EEG.nbchan EEG.trials EEG.pnts] = size(dh.F); + [EEG.filepath EEG.filename] = fileparts(dh.file.path); + eeg.chanlocs = chanlocs(data) + + if isempty(subformat) || isequal(subformat,'data') + EEG.data = data(dh); + if exist('eeg_checkset')==2 + EEG = eeg_checkset(EEG); + else + warning('Datahandler:Convert:NoEEGLABCheckset', 'EEGLAB''s eeg_checkset function missing.') + end + end + if isempty(subformat) + varargout = {EEG}; + else + varargout = {EEG.(subformat)}; + end + return; +end + +function [EEG] = eeglab_emptyset() +EEG.data = [] ; + +EEG.setname = ''; +EEG.filename = ''; +EEG.filepath = ''; +EEG.trials = NaN; % number of epochs (or trials) in the dataset. +% If data are continuous, this number is 1. +EEG.pnts = NaN; % number of time points (or data frames) per trial (epoch). +% If data are continuous (trials=1), the total number +% of time points (frames) in the dataset +EEG.nbchan = NaN; % number of channels +EEG.srate = NaN; % data sampling rate (in Hz) +EEG.xmin = NaN; % epoch start latency|time (in sec. relative to the +% time-locking event at time 0) +EEG.xmax = NaN; % epoch end latency|time (in seconds) +EEG.times = NaN; % vector of latencies|times in seconds (one per time point) +EEG.ref = NaN; % ['common'|'averef'|integer] reference channel type or number +EEG.history = {}; % cell array of ascii pop-window commands that created +% or modified the dataset +EEG.comments = NaN; % comments about the nature of the dataset (edit this via +% menu selection Edit > About this dataset) +EEG.etc = NaN; % miscellaneous (technical or temporary) dataset information +EEG.saved = 'no'; % ['yes'|'no'] 'no' flags need to save dataset changes before exit +% +% EEG.chanlocs - structure array containing names and locations of the channels on the scalp +% EEG.urchanlocs - original (ur) dataset chanlocs structure containing all channels originally collected with these data (before channel rejection) +% EEG.chaninfo - structure containing additional channel info +% EEG.ref - type of channel reference ('common'|'averef'|+/-int] +% EEG.splinefile - location of the spline file used by headplot() to plot data scalp maps in 3-D + +function eeglab_chanlocs(dh) + +for index = 1:length( eloc ) + eloc(index).X = tmp(1); x(index) = tmp(1); + eloc(index).Y = tmp(2); y(index) = tmp(2); + eloc(index).Z = tmp(3); z(index) = tmp(3); + eloc(index).type = 'EEG'; +end; diff --git a/lena/@datahandler/data.m b/lena/@datahandler/data.m new file mode 100644 index 0000000..7392a4c --- /dev/null +++ b/lena/@datahandler/data.m @@ -0,0 +1,17 @@ +function F = data(dh) +% data (datahandler) - Display/output data +% + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2010 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2010-04-22 Creation +% +% ----------------------------- Script History --------------------------------- + +F=dh.F; \ No newline at end of file diff --git a/lena/@datahandler/datahandler.m b/lena/@datahandler/datahandler.m new file mode 100644 index 0000000..281aa51 --- /dev/null +++ b/lena/@datahandler/datahandler.m @@ -0,0 +1,86 @@ +function dh = datahandler(varargin) +% datahandler - A class to handle MEEG data +% +% dh = datahandler(varargin) +% +% KND + +% properties +% F = []; +% file = struct('path', '', 'header', []); +% dimensions = [] +% time +% channel +% trial +% marker +% class +% badchannel +% end + +% +DATA_DIMENSIONS = {'supersensors','time','trial'}; + +dh.F = []; +dh.file = struct('path',[],'header',[]); + +% Data dimensions +% DIMENSION_TYPES = {'double','nominal','logical','struct'} +dh.dimensions = struct('names',[],'size',[],'synonyms',[]); +% dh.dimensions.names = {'supersensors','time','trial'}; +dh.supersensor = []; +dh.time = []; +dh.trial = []; + +% Extra info +dh.marker = []; +dh.class = []; +dh.badchannel = []; + +if nargin==0 + + dh = class(dh, 'datahandler'); +else + v = varargin{1}; + if isa(v,'datahandler') + dh = v; + elseif ischar(v) + % if (exist(v, 'filename') || exist(v,'dir')) + dh.file.path = v; + try + dh = import(dh.file.path,varargin{2:end}); + + catch ME + rethrow(ME); + end + dh = class(dh, 'datahandler'); + elseif isnumeric(v) + dh.F = v ; + dh = class(dh, 'datahandler'); + else + error('[datahandler] Bad input argument'); + end +end + +function dh = import(filename,varargin) + +% Import data from file +[dh.file.header,dh.F,dh.time] = read_lena(filename,varargin{:}); +dh.file.path = filename; +% classes = read_lena_classes +% markers = read_lena_events +dh.badchannel = []; + +%% Sensors and supersensors +sensor_range = dh.file.header.description.sensor_range; +sensornames = {sensor_range.sensor_list.sensor.CONTENT}; +n = length(sensor_range.sensor_samples.supersensor); +supersensor = sensor_range.sensor_samples.supersensor; +dh.supersensor = struct([]); +for i=1:n + dh.supersensor(i).name = supersensor(i).sensor; + dh.supersensor(i).idx = strmatch(dh.supersensor(i).name, sensornames); + dh.supersensor(i).unit = supersensor(i).ATTRIBUTE.unit ; + dh.supersensor(i).scale= supersensor(i).ATTRIBUTE.scale; + dh.supersensor(i).bad = ismember(dh.supersensor(i).idx, dh.badchannel); +end + diff --git a/lena/@datahandler/datahandler_classdef.m b/lena/@datahandler/datahandler_classdef.m new file mode 100644 index 0000000..0383ad8 --- /dev/null +++ b/lena/@datahandler/datahandler_classdef.m @@ -0,0 +1,62 @@ +classdef datahandler + % datahandler - A class to handle MEEG data + % + % dh = datahandler(varargin) + % + % KND + + properties + F = []; + file = struct('path', '', 'header', []); + dimensions = [] + time + channels + trials + markers + classes + badchannels + end + + methods + function dh = datahandler(varargin) + if nargin>0 + v = varargin{1}; + if isa(v,'datahandler') + dh = v; + elseif ischar(v) + % if (exist(v, 'file') || exist(v,'dir')) + dh.file.path = v; + try + [dh.file.header,dh.F,dh.time] = read_lena(dh.file.path,varargin{2:end}); + catch ME + rethrow(ME); + end + elseif isnumeric(v) + dh.F = v ; + else + error('[datahandler] Bad input argument'); + end + end + %obj = obj@uint8(data); + %dh = datahandler(data); + + end + + function s = sensornames(dh) + s = [dh.file.header.description.sensor_range.sensor_list.sensor]; + s = {s.CONTENT}; + end + + function plot(td,varargin) + plot(td.Strain,td.Stress,varargin{:}) + title(['Stress/Strain plot for Sample',... + num2str(td.SampleNumber)]) + ylabel('Stress (psi)') + xlabel('Strain %') + end % plot + + + end + + +end \ No newline at end of file diff --git a/lena/@datahandler/file.m b/lena/@datahandler/file.m new file mode 100644 index 0000000..e69de29 diff --git a/lena/@datahandler/get.m b/lena/@datahandler/get.m new file mode 100644 index 0000000..b84958a --- /dev/null +++ b/lena/@datahandler/get.m @@ -0,0 +1,13 @@ +function val = get(dh, propName, options) +% GET - Get property from datahandler object + +% default output data +if nargin<2 + propName = 'data'; +end +switch lower(propName) + case {'data', 'f'} + val = dh.F; + otherwise + error([propName,' is not a datahandler property']) +end \ No newline at end of file diff --git a/lena/@datahandler/sensornames.m b/lena/@datahandler/sensornames.m new file mode 100644 index 0000000..79010c7 --- /dev/null +++ b/lena/@datahandler/sensornames.m @@ -0,0 +1,22 @@ +%SENSORNAMES - Retrieves the name of the sensors in the data +% [] = sensornames(dh) +% +% Example +% >> sensornames +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2010 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2010-04-22 Creation +% +% ----------------------------- Script History --------------------------------- +function s = sensornames(dh) +s = {dh.supersensor.name}; +end \ No newline at end of file diff --git a/lena/@datahandler/set.m b/lena/@datahandler/set.m new file mode 100644 index 0000000..7768d9f --- /dev/null +++ b/lena/@datahandler/set.m @@ -0,0 +1,9 @@ +function dh = set(dh,varargin) +% SET Set properties and return the updated object +propertyArgIn = varargin; +while length(propertyArgIn) >= 2, + prop = propertyArgIn{1}; + val = propertyArgIn{2}; + propertyArgIn = propertyArgIn(3:end); + dh = set(dh, prop, val); +end \ No newline at end of file diff --git a/lena/@lena/lena.m b/lena/@lena/lena.m new file mode 100644 index 0000000..d29b297 --- /dev/null +++ b/lena/@lena/lena.m @@ -0,0 +1,48 @@ +function obj = lena(varargin) +% LENA - Constructor of the LENA data class +% +% obj = lena(varargin) +% +% KND +if nargin==0 + obj.header = []; + obj.F = []; + obj.Time = []; + obj.filename = ''; + obj.events = []; + obj.trialclass = []; + obj.badchannels = []; + + obj = class(obj,'lena'); +else + v = varargin{1}; + if isa(v,'lena') + obj = v; + elseif ischar(v) + % if (exist(v, 'file') || exist(v,'dir')) + obj.filename = v; + try + [obj.header,obj.F,obj.Time] = read_lena(obj.filename,varargin{2:end}); + catch ME + rethrow(ME); + end + obj = class(obj,'lena'); + elseif isnumeric(v) + obj.F = v ; + obj = class(obj,'lena'); + else + error('[LENA] Bad input argument'); + end + +end + +function [names] = sensornames +names = 1; + +function [pos] = sensorpositions + + +function [F] = data(o) +F = o.F; + + diff --git a/lena/create_events.m b/lena/create_events.m new file mode 100644 index 0000000..a2a3c00 --- /dev/null +++ b/lena/create_events.m @@ -0,0 +1,16 @@ +function events = create_events(onsets,names,trials,durations,offsets) +% create_events() - Create EEGLAB events +% Usage: +% >> events = create_events(onsets,names,trials,durations,offsets) +% +% Author: Karim N'Diaye, CNRS-UPR640, 01 Jan 2010 +% +% See also: read_ptx + +events = struct('type',[],'position',[],'latency',[],'urevent',[0]); +n=length(onsets); +events = repmat(events,n,1); +for i=1:n + events(i) = setfield(events(i),'latency', onsets(i)); + events(i) = setfield(events(i),'type', names{i}); +end \ No newline at end of file diff --git a/lena/ctf_folder.m b/lena/ctf_folder.m new file mode 100644 index 0000000..33c3ee9 --- /dev/null +++ b/lena/ctf_folder.m @@ -0,0 +1,106 @@ +function [ctf] = ctf_folder(folder,ctf); + +% ctf_folder - get and check CTF .ds folder name +% +% [ctf] = ctf_folder( [folder], [ctf] ); +% +% folder: The .ds directory of the dataset. It should be a complete path +% or given relative to the current working directory (given by pwd). The +% returned value will ensure the complete path is identified. If this +% argument is empty or not given, a graphical prompt for the folder +% appears. +% +% eg, +% ctf = ctf_folder; +% +% ctf.folder is returned (as a complete path). +% +% <>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> % +% < > % +% < DISCLAIMER: > % +% < > % +% < THIS PROGRAM IS INTENDED FOR RESEARCH PURPOSES ONLY. > % +% < THIS PROGRAM IS IN NO WAY INTENDED FOR CLINICAL OR > % +% < OFFICIAL USE. > % +% < > % +% <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<> % +% + + +% $Revision: 1.1 $ $Date: 2009-01-30 03:49:27 $ + +% Copyright (C) 2004 Darren L. Weber +% +% This program is free software; you can redistribute it and/or +% modify it under the terms of the GNU General Public License +% as published by the Free Software Foundation; either version 2 +% of the License, or (at your option) any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with this program; if not, write to the Free Software +% Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +% Modified: 01/2004, Darren.Weber_at_radiology.ucsf.edu +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + +if ~exist('folder','var'), folder = []; end +if ~exist('ctf','var'), ctf = []; end + +if isempty(folder), + if isfield(ctf,'folder'), + folder = ctf.folder; + else + folder = []; + end +end + +if exist(folder) ~= 7, + fprintf('...folder inputs are invalid\n'); + folder = getfolder; +end + +ctf.folder = folder; + +% ensure we get the folder path +current_dir = pwd; +cd(ctf.folder); +cd .. +folderPath = pwd; +cd(current_dir); + +% check whether the folder path is in the folder already +[path,file] = fileparts(ctf.folder); + +% if findstr(folderPath,ctf.folder), + % OK the path is already in the folder +% else +if isempty(path) + % Add the folderPath + ctf.folder = fullfile(folderPath,ctf.folder); +end + + +return + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function folder = getfolder, + +% This does not work on linux systems (only windows) +folder = uigetdir(pwd,'locate CTF .ds folder'); + +if ~folder, + % try to get it this way + [filename, folder, filterindex] = uigetfile('*.res4', 'Pick a .res4 file'); + if ~folder, + error('invalid folder'); + end +end + +return diff --git a/lena/ctf_read_classfile.m b/lena/ctf_read_classfile.m new file mode 100644 index 0000000..89261d1 --- /dev/null +++ b/lena/ctf_read_classfile.m @@ -0,0 +1,254 @@ +function class = ctf_read_classfile(ctf,clsFile); + +% class = ctf_read_classfile(ctf,clsFile); +% +% This function reads the ClassFile.cls created by CTF preprocessing +% software. +% +% ctf - a struct returned by ctf_read, it contains ctf.folder, which is a +% .ds path where a ClassFile.cls can be read; eg: +% ctf = ctf_read +% ctf.class = ctf_read_classfile(ctf) +% +% clsFile - this can be used to override the default behavior of reading +% *.ds/ClassFile.cls, it should be a complete path to a .cls file; eg: +% ctf.class = ctf_read_classfile([],clsFile) +% ctf.class = ctf_read_classfile([],'//ClassFile.cls') +% +% It returns a class struct, eg: +% +% class.path: '/data/dnl3/HighN_cuebase/allTrialData/CJ_HiN_alltrials.ds' +% class.number: 4 +% class.data: {[1x1 struct] [1x1 struct] [1x1 struct] [1x1 struct]} +% +% where class.data is a 1xN struct array, eg: +% +% >> class.data(1) +% +% ans = +% +% classgroupid: 3 +% name: 'EYEBLINKa' +% comment: [1x63 char] +% color: '' +% editable: 'Yes' +% classid: 2 +% Ntrials: 67 +% trials: [1x67 double] +% +% To get all the class names, +% [classNames{1:class.number}] = deal(class.data.name); +% classNames = {ctf.class.data(:).name}; +% +% To scan all the class names, use strmatch, eg: +% strmatch('BAD',{ctf.class.data(:).name}); +% +% Note that the class.data(n).trials contains numbers beginning with 1, +% whereas the CTF datafile has values beginning with 0 (this is based on +% differences in array indexing between c/c++ and matlab). +% +% <>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +% < > +% < DISCLAIMER: > +% < > +% < THIS PROGRAM IS INTENDED FOR RESEARCH PURPOSES ONLY. > +% < THIS PROGRAM IS IN NO WAY INTENDED FOR CLINICAL OR > +% < OFFICIAL USE. > +% < > +% <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<> +% + + +% $Revision: 1.1 $ $Date: 2009-01-30 03:49:27 $ + +% Copyright (C) 2005 Darren L. Weber +% +% This program is free software; you can redistribute it and/or +% modify it under the terms of the GNU General Public License +% as published by the Free Software Foundation; either version 2 +% of the License, or (at your option) any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with this program; if not, write to the Free Software +% Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +% Created: 12/2005, Darren.Weber_at_radiology.ucsf.edu +% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + +if ~exist('clsFile','var'), clsFile = ''; end +if isempty(clsFile), clsFile = ''; end +if clsFile, + fid = fopen(clsFile,'r'); +else + fid = -1; +end + +if fid < 0, + % only use the ctf.folder if the above has failed + if isfield(ctf,'folder'), + if isempty(ctf.folder), + error('ctf.folder is empty and no clsFile specified'); + else + clsFile = fullfile(ctf.folder,'ClassFile.cls'); + fid = fopen(clsFile,'r'); + end + end +end + +if fid < 0, + error('failed to open any ClassFile.cls'); +end + + +% Read the dataset path from the classfile +class.path = []; +while isempty(class.path), + curLine = fgetl(fid); + if strcmp(curLine,'PATH OF DATASET:'), + curLine = fgetl(fid); + class.path = curLine; + end +end +frewind(fid); + +% Find the number of classes to read +class.number = []; +while isempty(class.number), + curLine = fgetl(fid); + if strcmp(curLine,'NUMBER OF CLASSES:'), + curLine = fgetl(fid); + class.number = str2num(curLine); + end +end +frewind(fid) + +if isempty(class.number), + warning('ClassFile.cls is empty of classes'); + fclose(fid); + return +end + + +% read the classes, assuming this format: +% +% CLASSGROUPID: +% 3 +% NAME: +% EYEBLINK +% COMMENT: +% Ampl(MEG)=0T; Deriv(MEG)=0T/s; Ampl(EEG)=80uV; Deriv(EEG)=0V/s. +% COLOR: +% +% EDITABLE: +% Yes +% CLASSID: +% 2 +% NUMBER OF TRIALS: +% 3 +% LIST OF TRIALS: +% TRIAL NUMBER +% +527 +% +612 +% +624 + +% initialize a class.data array of structs +class.data(1:class.number) = struct(... + 'classgroupid', '',... + 'name', '',... + 'comment', '',... + 'color', '',... + 'editable', '',... + 'classid', [],... + 'Ntrials', [],... + 'trials', []); + +classCurrent = 0; +while classCurrent < class.number, + + curLine = fgetl(fid); + if strcmp(curLine,'CLASSGROUPID:'); + % update current class no. here only + classCurrent = classCurrent + 1; + curLine = fgetl(fid); + curLine = str2num(curLine); + class.data(classCurrent).classgroupid = curLine; + curLine = fgetl(fid); + end + + if strcmp(curLine,'NAME:'); + curLine = fgetl(fid); + class.data(classCurrent).name = curLine; + curLine = fgetl(fid); + end + + if strcmp(curLine,'COMMENT:'); + curLine = fgetl(fid); + class.data(classCurrent).comment = curLine; + curLine = fgetl(fid); + end + + if strcmp(curLine,'COLOR:'); + curLine = fgetl(fid); + class.data(classCurrent).color = curLine; + curLine = fgetl(fid); + end + + if strcmp(curLine,'EDITABLE:'); + curLine = fgetl(fid); + class.data(classCurrent).editable = curLine; + curLine = fgetl(fid); + end + + if strcmp(curLine,'CLASSID:'); + curLine = fgetl(fid); + curLine = str2num(curLine); + class.data(classCurrent).classid = curLine; + if curLine ~= classCurrent, + error('curLine ~= classCurrent'); + end + curLine = fgetl(fid); + end + + if strcmp(curLine,'NUMBER OF TRIALS:'); + curLine = fgetl(fid); + Ntrials = str2num(curLine); + class.data(classCurrent).Ntrials = Ntrials; + + % read past "LIST OF TRIALS:" + curLine = fgetl(fid); + if strcmp(curLine,'LIST OF TRIALS:'), + % that's good, we expect it + else + warning('Did not find ''LIST OF TRIALS:'' where expected') + end + + % read past "TRIAL NUMBER" + curLine = fgetl(fid); + if strcmp(curLine,'TRIAL NUMBER'), + % that's good, we expect it + else + warning('Did not find ''TRIAL NUMBER'' where expected') + end + + class.data(classCurrent).trials = []; + if class.data(classCurrent).Ntrials > 0, + class.data(classCurrent).trials = zeros(1,Ntrials); + for n = 1:Ntrials, + curLine = fgetl(fid); + % add 1 to trial numbers for matlab compatibility + class.data(classCurrent).trials(n) = str2num(curLine) + 1; + end + end + + end + +end + +return diff --git a/lena/ctf_read_markerfile.m b/lena/ctf_read_markerfile.m new file mode 100644 index 0000000..df92823 --- /dev/null +++ b/lena/ctf_read_markerfile.m @@ -0,0 +1,188 @@ +function [markers] = ctf_read_markerfile(ctf,mrkFile); + +% ctf_read_markerfile - load data from MarkerFile.mrk in .ds folder +% +% [markers] = ctf_read_markerfile([ctf],[mrkFile]); +% +% ctf.folder is a .ds path, if it is missing or invalid a gui prompts +% for the folder. ctf is a struct generated by this and other +% ctf_read_*.m functions. +% +% mrkFile - this can be used to override the default behavior of reading +% *.ds/MarkerFile.cls, it should be a complete path to a .mrk file; eg: +% ctf = ctf_read_markerfile([],mrkFile) +% ctf = ctf_read_markerfile([],'//MarkerFile.mrk') +% +% Returns the 'markers' struct: +% +% markers.number_markers - scalar +% markers.number_samples - column array, number_markers x 1 +% markers.marker_names - cell array of strings, number_markers x 1 +% markers.trial_times - cell array of matrices, number_markers x 1 each +% containing number_samples x 2 matrix, where column 1 indicates the trial +% number containing the marker and column 2 indicates the offset (in sec). +% +% <>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +% < > +% < DISCLAIMER: > +% < > +% < THIS PROGRAM IS INTENDED FOR RESEARCH PURPOSES ONLY. > +% < THIS PROGRAM IS IN NO WAY INTENDED FOR CLINICAL OR > +% < OFFICIAL USE. > +% < > +% <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<> +% + +% $Revision: 1.1 $ $Date: 2009-01-30 03:49:27 $ + +% Copyright (C) 2004 Darren L. Weber +% +% This program is free software; you can redistribute it and/or +% modify it under the terms of the GNU General Public License +% as published by the Free Software Foundation; either version 2 +% of the License, or (at your option) any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with this program; if not, write to the Free Software +% Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +% Modified: 12/2003, Darren.Weber_at_radiology.ucsf.edu +% - modified from NIH code readmarkerfile.m +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +ver = '$Revision: 1.1 $'; +fprintf('\nCTF_READ_MARKERFILE [v %s]\n',ver(11:15)); tic; + + + +if ~exist('mrkFile','var'), mrkFile = ''; end +if isempty(mrkFile), mrkFile = ''; end +if isstruct(mrkFile), mrkFile = ''; end +if mrkFile, + fid = fopen(mrkFile,'r'); +else + fid = -1; +end + +if fid < 0, + + % check the ctf.folder + if ~exist('ctf','var'), + ctf = ctf_folder; + else + ctf = ctf_folder([],ctf); + end + %fprintf('...reading from %s\n',ctf.folder); + + % use the ctf.folder to find .mrk file + if isfield(ctf,'folder'), + if isempty(ctf.folder), + error('ctf.folder is empty and no mrkFile specified'); + else + % check for a marker file + mrkFile = findmrkfile( ctf.folder ); + if exist(mrkFile) == 2, + fid = fopen(mrkFile,'r'); + else, + warning('...MarkerFile.mrk does not exist in this .ds folder.'); + ctf.markers = []; + return + end + end + else + error('ctf.folder is not a field of the ctf struct'); + end +end + + + + +% read the marker file, delimited by carriage returns. This new array is +% used below to extract relevant information +fprintf('...reading marker file:\n...%s\n', mrkFile); +fprintf('...reading file text, using ''\\n'' delimiter\n'); +mrkFileText = textread(mrkFile,'%s','delimiter','\n'); + +% extract the number of markers +number_markers_index = strmatch('NUMBER OF MARKERS:',mrkFileText,'exact'); +number_markers = str2num(mrkFileText{number_markers_index + 1}); +fprintf('...found %d marker types:\n',number_markers); + +% extract marker names (this can be an array, if number_markers > 1) +marker_names_index = strmatch('NAME:',mrkFileText,'exact'); +marker_names = mrkFileText(marker_names_index + 1); +for m = 1:length(marker_names), + fprintf(' %40s\n',marker_names{m}); +end + +% replace any '-' with '_' symbols in the marker names +marker_names = strrep(marker_names,'-','_'); +fprintf('...replaced any - with _ symbol in marker names\n'); + +% extract number of marker samples +% (this can be an array, if number_markers > 1) +number_samples_index = strmatch('NUMBER OF SAMPLES:',mrkFileText,'exact'); +number_samples = str2num(char(mrkFileText(number_samples_index + 1))); + +% check that all the marker samples are defined +if ~all(number_samples), + disp('One of the markers has no samples and was ignored.'); +end + + +% extract marker trial number and time (sec), eg, find & read: +% LIST OF SAMPLES: +% TRIAL NUMBER TIME FROM SYNC POINT (in seconds) +% +0 +0 +% +1 +0 +% +2 +0 + +fprintf('...reading trial numbers and offset time (sec)\n'); + +HEADER_LINES = 2; +trial = strmatch('LIST OF SAMPLES:',mrkFileText,'exact') + HEADER_LINES; + +for i = find(number_samples)' + % extract the lines of the samples, where each line contains a trial + % number in the first column and a time offset (sec) in the 2nd column + sample_text_rows = mrkFileText( trial(i):[trial(i) + number_samples(i)] ); + trials{i} = str2num(char(sample_text_rows)); + % add 1 to the trial numbers (which start at 0 for CTF software) + trials{i}(:,1) = trials{i}(:,1) + 1; +end +fprintf('...added 1 to trial numbers for matlab compatibility\n'); + +% allocate marker information into the marker stucture +markers = struct(... + 'file',mrkFile,... + 'number_markers',number_markers,... + 'number_samples',number_samples,... + 'marker_names',marker_names,... + 'trial_times',trials'); + +t = toc; fprintf('...done (%6.2f sec)\n\n',t); + +return + + + +% ------------------------------------------------------- +% find file name if truncated or with uppercase extension +% added by Arnaud Delorme, June 15, 2004 +function mrkname = findmrkfile( folder ) + mrkname = dir([ folder filesep '*.mrk' ]); + if isempty(mrkname) + mrkname = dir([ folder filesep '*.MRK' ]); + end; + if isempty(mrkname) + fprintf('...no file with extension .mrk or .MRK in .ds folder\n'); + mrkname = []; + else + mrkname = [ folder filesep mrkname.name ]; + end; +return diff --git a/lena/extractBINblocksRevised4SensorsBis.m b/lena/extractBINblocksRevised4SensorsBis.m new file mode 100644 index 0000000..497c8ac --- /dev/null +++ b/lena/extractBINblocksRevised4SensorsBis.m @@ -0,0 +1,427 @@ +function echantillons = extractBINblocks(fid,dim_to_read,dim_names,level,data_type,data_size) +% function echantillons = extractBINblocks(fid,mat,data_type,data_size) +% +% Function to read only parts of a bin file ( cf LENA data format ) +% +% Inputs : +% - fid ( integer ) : fid of the bin file +% - mat ( double array ) : matrix of samples to read. Size must be N x 2, +% ie first column for samples to read, second for samples to skip +% - data_type ( string ) : data type and size ( example : int16 ) +% - data_size ( integer ) : data format size +%dim +% Ouputs : +% - echantillons ( cells ) : read datas stored in cells + +size_dim=length(dim_to_read); +trial_dim=[]; +frequency_dim=[]; +sensor_dim=[]; +time_dim=[]; + +first_pos_trial=0; +first_pos_frequency=0; +first_pos_sensor=0; +first_pos_time=0; + +[trial_dim, trial_level,frequency_dim, frequency_level, sensor_dim, sensor_level, time_dim, time_level ]=GetDimensionsValues(dim_to_read, dim_names, level); + +if ~isempty(trial_dim) + trial_dim=cell2mat(trial_dim); + first_pos_trial=Get_First_Pos_Trial(trial_dim, frequency_level, sensor_level,time_level); +end +if ~isempty(sensor_dim) + sensor_dim=cell2mat(sensor_dim); + first_pos_sensor=Get_First_Pos_Sensor(sensor_dim, time_level); +end +if ~isempty(frequency_dim) + if isempty(sensor_dim) + sensor_dim=1; + end + frequency_dim=cell2mat(frequency_dim); + [first_pos_frequency, first_pos_sensor]=Get_First_Pos_Frequency_Sensor(frequency_dim,sensor_dim,frequency_level,sensor_level,time_level, dim_names); +end +if ~isempty(time_dim) + time_dim=cell2mat(time_dim); + first_pos_time=time_dim(1);%-1; +end +first_pos=(first_pos_time+first_pos_sensor+first_pos_frequency+first_pos_trial); +fseek(fid,data_size*first_pos,'bof'); +if ~isempty(trial_dim) + first_trial=trial_dim(1); + last_trial=trial_dim(end); +else + first_trial=1; + last_trial=1; +end +if ~isempty(frequency_dim) + first_frequency=frequency_dim(1); + last_frequency=frequency_dim(end); +else + first_frequency=1; + last_frequency=1; +end +if ~isempty(sensor_dim) + first_sensor=sensor_dim(1); + last_sensor=sensor_dim(end); +else + first_sensor=1; + last_sensor=1; +end +if ~isempty(time_dim) + first_time=time_dim(1); + last_time=time_dim(end); +else + first_time=1; + last_time=1; +end +if exist('time_level') && ~ isempty(time_dim) && (time_level-time_dim(end))>0 + remainder_time=time_level-time_dim(end)-1; +else + remainder_time=0; +end +k=1; +echantillons=[]; + +%% Nouvelle tentative +if (isempty(trial_dim) && isempty(trial_level)) + first_pos_trial=0; + if ~isempty(frequency_level) & ~isempty(sensor_level) & ~isempty(time_level) + [rank_sensor, rank_frequency]=getRankSensorFrequency(dim_names); + if rank_sensor > rank_frequency + [k, echantillon]= Process_Frequency_Sensor(fid,data_size, data_type,first_pos_trial, first_pos_sensor,frequency_dim,sensor_dim, time_dim, k,frequency_level, time_level, sensor_level); % frequence/temps + echantillons= [echantillons echantillon]; + else + [k, echantillon]= Process_Sensor_Frequency(fid,data_size, data_type,first_pos_trial, first_pos_frequency, frequency_dim,sensor_dim, time_dim, k,frequency_level, time_level, sensor_level); % frequence/temps + echantillons= [echantillons echantillon]; + end + else + if ~isempty(sensor_level) & ~isempty(time_level) + first_pos_frequency=0;%1; + [k, echantillon]= Process_Sensor(fid,data_size, data_type,first_pos_trial, first_pos_frequency,sensor_dim, time_dim,sensor_level,time_level,k); + echantillons= [echantillons echantillon]; + else + if ~isempty(frequency_level) & ~isempty(time_level) + [k, echantillon]= Process_Frequency(fid,data_size, data_type,first_pos_trial, frequency_dim,time_dim, k,frequency_level, time_level); % frequence/temps + echantillons= [echantillons echantillon]; + else + if ~isempty(sensor_level) & isempty(frequency_level) & isempty(time_level) + [k, echantillon]= Process_Sensor(fid,data_size, data_type,first_pos_trial, first_pos_frequency,sensor_dim, time_dim,sensor_level,time_level,k); + echantillons= [echantillons echantillon]; + else + if isempty(sensor_level) & ~isempty(frequency_level) & isempty(time_level) + [k, echantillon]= Process_Frequency(fid,data_size, data_type,first_pos_trial, frequency_dim,time_dim, k,frequency_level, time_level); % frequence/temps + echantillons= [echantillons echantillon]; + else + if isempty(sensor_level) & isempty(frequency_level) & ~isempty(time_level) + fseek(fid,data_size*first_pos,'bof'); + echantillons{k}= fread(fid,length(time_dim),data_type); + else + fseek(fid,0,'bof'); + echantillons{k}= fread(fid,1,data_type); + end + end + end + end + end + end +else + if ~isempty(trial_dim) + limit_trial=length(trial_dim); + else + if ~isempty(trial_level) + limit_trial=trial_level; + end + end + for i_dim_trial=1:limit_trial + if ~isempty(frequency_level) & ~isempty(sensor_level) & ~isempty(time_level) + first_pos_trial=(trial_dim(i_dim_trial)-1)*(frequency_level*sensor_level*time_level); + [rank_sensor, rank_frequency]=getRankSensorFrequency(dim_names); + if rank_sensor > rank_frequency + [k, echantillon]= Process_Frequency_Sensor(fid,data_size, data_type,first_pos_trial, first_pos_sensor,frequency_dim,sensor_dim, time_dim, k,frequency_level, time_level, sensor_level); % frequence/temps + echantillons= [echantillons echantillon]; + else + [k, echantillon]= Process_Sensor_Frequency(fid,data_size, data_type,first_pos_trial, first_pos_frequency, frequency_dim,sensor_dim, time_dim, k,frequency_level, time_level, sensor_level); % frequence/temps + echantillons= [echantillons echantillon]; + end + else + if ~isempty(sensor_level) & ~isempty(time_level) + first_pos_trial=(trial_dim(i_dim_trial)-1)*(sensor_level*time_level); + first_pos_frequency=0; + [k, echantillon]= Process_Sensor(fid,data_size, data_type,first_pos_trial, first_pos_frequency,sensor_dim, time_dim,sensor_level,time_level,k); + echantillons= [echantillons echantillon]; + else + if ~isempty(frequency_level) & ~isempty(time_level) + first_pos_trial=(trial_dim(i_dim_trial)-1)*(frequency_level*time_level); + [k, echantillons]= Process_Frequency(fid,data_size, data_type,first_pos_trial, frequency_dim,time_dim, k,frequency_level, time_level); % frequence/temps + else + if ~isempty(sensor_level) & isempty(frequency_level) & isempty(time_level) + [k, echantillons]= Process_Sensor(fid,data_size, data_type,first_pos_trial, first_pos_frequency,sensor_dim, time_dim,sensor_level,time_level,k); + end + end + end + end + end +end +end +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +function [trial_dim, trial_level,frequency_dim, frequency_level, sensor_dim, sensor_level, time_dim, time_level ]=GetDimensionsValues(dim_to_read, dim_names, level) + +trial_dim=[]; +frequency_dim=[]; +sensor_dim=[]; +time_dim=[]; + +trial_level=[]; +frequency_level=[]; +sensor_level=[]; +time_level=[]; + +size= length(dim_to_read); +if size==1 + switch dim_names + case 'datablock_range' + trial_dim=dim_to_read; trial_level=level; + case 'frequency_range' + frequency_dim=dim_to_read; frequency_level=level; + case 'sensor_range' + sensor_dim=dim_to_read; sensor_level=level; + case 'time_range' + time_dim=dim_to_read; time_level=level; + end +else + for i=1:length(dim_to_read) + switch dim_names{i} + case 'datablock_range' + trial_dim=dim_to_read(i); trial_level=level(i); + case 'frequency_range' + frequency_dim=dim_to_read(i); frequency_level=level(i); + case 'sensor_range' + sensor_dim=dim_to_read(i); sensor_level=level(i); + case 'time_range' + time_dim=dim_to_read(i); time_level=level(i); + end + end +end +if iscell (dim_names) + for i=1:length(level) + switch dim_names{i} + case 'datablock_range' + if isempty(trial_dim) & isempty(trial_level) + trial_dim=num2cell(level(i)); trial_level=level(i); + end + case 'frequency_range' + if isempty(frequency_dim) & isempty(frequency_level) + frequency_dim=num2cell(level(i)); frequency_level=level(i); + end + case 'sensor_range' + if isempty(sensor_dim) & isempty(sensor_level) + sensor_dim=num2cell(level(i)); sensor_level=level(i); + end + case 'time_range' + if isempty(time_dim) & isempty(time_level) + time_dim=num2cell(level(i)-1); time_level=level(i); + end + end + end +else + if ~ isempty( dim_names) + switch dim_names + case 'datablock_range' + if isempty(trial_dim) & isempty(trial_level) + trial_dim=num2cell(level); trial_level=level; + end + case 'frequency_range' + if isempty(frequency_dim) & isempty(frequency_level) + frequency_dim=num2cell(level); frequency_level=level; + end + case 'sensor_range' + if isempty(sensor_dim) & isempty(sensor_level) + sensor_dim=num2cell(level); sensor_level=level; + end + case 'time_range' + if isempty(time_dim) & isempty(time_level) + time_dim=num2cell(level-1); time_level=level; + end + end + end +end +return + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function [k, echantillons]= Process_Frequency_Sensor(fid,data_size, data_type,first_pos_trial, first_pos_frequency, frequency_dim,sensor_dim, time_dim, k,frequency_level, time_level, sensor_level) +% frequence/temps +echantillons=[]; +if ~isempty(frequency_dim) + limit_frequency=length(frequency_dim); +else limit_frequency=frequency_level; +end +for i_dim_frequency=1:limit_frequency + first_pos_frequency=(frequency_dim(i_dim_frequency)-1)*(sensor_level*time_level); + [k, echantillon]= Process_Sensor(fid,data_size, data_type,first_pos_trial,first_pos_frequency, sensor_dim, time_dim,sensor_level,time_level,k); + echantillons=[echantillons echantillon]; +end +return + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function [k, echantillons]= Process_Sensor_Frequency(fid,data_size, data_type,first_pos_trial, first_pos_frequency, frequency_dim,sensor_dim, time_dim, k,frequency_level, time_level, sensor_level); % frequence/temps +echantillons=[]; +if ~isempty(sensor_dim) + limit_sensor=length(sensor_dim); +else limit_sensor=sensor_level; +end +for i_dim_sensor=1:limit_sensor + + first_pos_sensor=(sensor_dim(i_dim_sensor)-1)*(frequency_level*time_level); + [k, echantillon]= Process_Frequency(fid,data_size, data_type,first_pos_trial,first_pos_sensor,frequency_dim,time_dim, k,frequency_level, time_level); + echantillons=[echantillons echantillon]; +end +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function [k, echantillon]= Process_Frequency(fid,data_size, data_type,first_pos_trial, first_pos_sensor, frequency_dim,time_dim, k,frequency_level, time_level) +% function Process_Frequency(first_pos_trial) +if ( isempty(frequency_dim) & isempty(frequency_level) & isempty(time_dim) & isempty(time_level) ) + fseek(fid,0,'bof'); + echantillons{k}= fread(fid,1,data_type); + %k=1; + %echantillons=[]; + return; +end +if (~ isempty(time_dim) & ~isempty(time_level)) + first_pos_time=time_dim(1);%-1; + if time_level==time_dim(end) + remainder_time=0; + else + remainder_time=time_level-time_dim(end)-1; + end + dim_time=length(time_dim); +else + first_pos_time=0; + remainder_time=0; + time_level=1; + dim_time=1; +end +if ~isempty(frequency_dim) + limit_frequency=length(frequency_dim); +else + limit_frequency=frequency_level; +end +k1=1; +for i_dim_frequency=1:limit_frequency + first_pos=first_pos_trial+first_pos_sensor+ (frequency_dim(i_dim_frequency)-1)*(time_level) + first_pos_time; + %Lecture + fseek(fid,data_size*first_pos,'bof'); + echantillon{k1}= fread(fid,dim_time,data_type); + k1=k1+1; + k=k+1; +end +return + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%function Process_Sensor(first_pos_trial, first_pos_frequency) +function [k, echantillons]= Process_Sensor(fid,data_size, data_type,first_pos_trial, first_pos_frequency,sensor_dim, time_dim,sensor_level,time_level,k) +if ( isempty(sensor_dim) & isempty(sensor_level) & isempty(time_dim) & isempty(time_level) ) + fseek(fid,0,'bof'); + echantillons{k}= fread(fid,1,data_type); + return; +end +if (~ isempty(time_dim) & ~isempty(time_level)) + first_pos_time=time_dim(1); + if time_level==time_dim(end)+1; + remainder_time=0; + else + remainder_time=time_level-time_dim(end)-1; + end + dim_time=length(time_dim); +else + first_pos_time=0; + remainder_time=0; + time_level=1; + dim_time=1; +end +if ~isempty(sensor_dim) + limit_sensor=length(sensor_dim); +else + limit_sensor=sensor_level; +end +k1=1; +for i_dim_sensor=1:limit_sensor + first_pos_sensor=(sensor_dim(i_dim_sensor)-1)*(time_level); + first_pos=first_pos_trial+first_pos_frequency+ first_pos_sensor + first_pos_time; + %Lecture + fseek(fid,data_size*first_pos,'bof'); + echantillons{k1}= fread(fid,dim_time,data_type); + k1=k1+1; + k=k+1; +end +return + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +function first_pos_trial=Get_First_Pos_Trial(trial_dim, frequency_level, sensor_level,time_level) +if isempty(frequency_level) + frequency_level=1; +end +if isempty(sensor_level) + sensor_level=1; +end +if isempty(time_level) + time_level=1; +end + +first_pos_trial=(trial_dim(1)-1)*(frequency_level*sensor_level*time_level); + +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +function [rank_sensor, rank_frequency]=getRankSensorFrequency(dim_names) +rank_sensor=0; +rank_frequency=0; +for i=1:length(dim_names) + if strcmp (dim_names{i}, 'frequency_range') + rank_frequency=i; + else + if strcmp (dim_names{i}, 'sensor_range') + rank_sensor=i; + end + end +end +return + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function [first_pos_frequency,first_pos_sensor] =Get_First_Pos_Frequency_Sensor(frequency_dim, sensor_dim, frequency_level,sensor_level,time_level, dim_names) +[rank_sensor, rank_frequency]=getRankSensorFrequency(dim_names); +if isempty(time_level) + time_level=1; +end +if isempty(sensor_level) + sensor_level=1; +end +if rank_frequency ~= 0 & rank_frequency > rank_sensor & rank_sensor ~= 0 + % rajouter le cas ou frequency est apres sensors + first_pos_frequency=(frequency_dim(1)-1)*(time_level); + first_pos_sensor=(sensor_dim(1)-1)*(frequency_level*time_level); +else if rank_frequency ~= 0 & rank_sensor > rank_frequency & rank_sensor ~= 0 + first_pos_sensor=(sensor_dim(1)-1)*(time_level); + first_pos_frequency=(frequency_dim(1)-1)*(sensor_level*time_level); + else if rank_frequency == 0 + first_pos_sensor=(sensor_dim(1)-1)*(time_level); + else if rank_sensor == 0 + first_pos_frequency=(frequency_dim(1)-1)*(time_level); + end + end + end +end +return + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function first_pos_sensor=Get_First_Pos_Sensor(sensor_dim, time_level) +if isempty(time_level) + time_level=1; +end +first_pos_sensor=(sensor_dim(1)-1)*(time_level); +return diff --git a/lena/lena2brainstorm.m b/lena/lena2brainstorm.m new file mode 100644 index 0000000..ba80402 --- /dev/null +++ b/lena/lena2brainstorm.m @@ -0,0 +1,48 @@ +function [Channel,Time,F,h] = lena2brainstorm(lenafile,varargin) +%LENA2BRAINSTORM - Reads LENA file into brainstorm data format +% [Channel,Time,F,header] = lena2brainstorm(lenafile) +% +% Example +% >> lena2brainstorm +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2010 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2010-04-02 Creation +% +% ----------------------------- Script History --------------------------------- + +%% Reads .lena file/folder +warning off +if nargout < 3 + % No need to read the full data info + [h] = read_lena(lenafile,varargin{:}); +else + [h,F] = read_lena(lenafile,varargin{:}); +end +warning on +%% Brainstorm's Channel structure +Channel = []; +if nargout < 2 + return +end +%% Time array +Time = [0:(str2num(h.description.time_range.time_samples)-1)]./... + str2num(h.description.time_range.sample_rate) - ... + str2num(h.description.time_range.pre_trigger); +if nargout < 3 + return +end +%% Process data into F +canonical_dimensions = {'sensor_range' 'time_range' 'datablock_range' }; +data_dimensions = fieldnames(h.description); +[ign,candim] = intersect(canonical_dimensions(:), data_dimensions); +icandim(candim)=1:numel(candim); +F = permute(F, icandim); diff --git a/lena/ptx2events.m b/lena/ptx2events.m new file mode 100644 index 0000000..7b1449d --- /dev/null +++ b/lena/ptx2events.m @@ -0,0 +1,27 @@ +function [t,m] = ptx2events(input) +%PTS2EVENTS - One line description goes here. +% [] = ptx2events(input) +% +% Example +% >> ptx2events +% +% See also: eeglab + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2009 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2009-04-24 Creation +% +% ----------------------------- Script History --------------------------------- + + +[onsets,names,trials,durations,offsets] = read_ptx(input); +e = create_events(onsets,names,trials,durations,offsets); + + + diff --git a/lena/readLENA.m b/lena/readLENA.m new file mode 100644 index 0000000..d997be3 --- /dev/null +++ b/lena/readLENA.m @@ -0,0 +1,150 @@ +% read_lena() - read LENA header & data +% +% Usage: +% >> [header data lena_tree] = read_lena(lenafile) +% +% Required Input: +% lenafile = LENA header file +% Available options: +% device -> 'ALL' (default)|'MEG'|'EEG'|'EEG+MEG'|'DC' +% trials -> Trials to import as list of indices (default: [] = 'all') +% timewindow -> Default: [] = 'all' +% +% Outputs: +% header = header info (from reading the xml heder file) +% data = cell array of data (one cell by trial) +% +% Author: Karim N'Diaye, CNRS-UPR640, 01 Jan 2004 +% +% See also: +% POP_READLENA, EEGLAB + +%123456789012345678901234567890123456789012345678901234567890123456789012 + +% Copyright (C) 2004, CNRS - UPR640, N'Diaye Karim, +% karim.ndiaye@chups.jussieu.Fr +% +% This program is free software; you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation; either version 2 of the License, or +% (at your option) any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with this program; if not, write to the Free Software +% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +% $Log: readegi.m,v $ +% Revision 0.1 2004/01/01 +% First alpha version for EEGLAB release 4.301 + +function [header data lena_tree] = readLENA(lenafile,varargin) + +if nargin < 1 + help(mfilename); + return; +end; +binary_extensions = {'.bin' '.data'}; + +lena_tree = xmltree(lenafile); +warning('off','MATLAB:warn_r14_stucture_assignment') +header = convert(lena_tree); +warning('on','MATLAB:warn_r14_stucture_assignment') + +if nargout<2 + return +end + +if nargin==2 + data_filename = varargin{1}; + if isfield(header, 'data_filename') + warning('Overloading binary data filname from header (%s) to: %s', header.data_filename, data_filename); + end + header.data_filename = data_filename; +end + + +if ~isfield(header, 'data_filename') + header.data_filename =''; + for i=1:length(binary_extensions) + data_filename = [lenafile(1:end-5) binary_extensions{i} ]; + if exist(data_filename, 'file') + header.data_filename = data_filename; + warning('Assumed binary data file not specified in header... Found possible match: %s', data_filename); + break; + end + end +end +if isempty(header.data_filename) + error('No datafile') +end + +fprintf('Importing data from binary file...\n') + +if strcmp(header.data_format,'LittleEndian') + data_fid = fopen(header.data_filename,'r','l'); +elseif strcmp(header.data_format,'BigEndian') + data_fid = fopen(header.data_filename,'r','b'); +else + warning('No data_format provided'); + data_fid = fopen(header.data_filename,'r'); +end + +% Read the offset because this info is not provided in header! +header.data_offset = getData_offset(lena_tree); +fseek(data_fid,header.data_offset,-1); + +% Get data type : +switch(header.data_type) + case 'unsigned fixed' + data_type='uint'; + case 'fixed' + data_type='int'; + case 'floating' + data_type='float'; + otherwise + error('Error : data_type wasn t found, which is required.') + return +end +% Get the data size : +header.data_size = getData_size(lena_tree); +data = fread(data_fid,str2num(header.description.time_range.time_samples)*... + length(header.description.sensor_range.sensor_list.sensor),data_type); +data=reshape(data,str2num(header.description.time_range.time_samples), []); +header.Time=[1:str2num(header.description.time_range.time_samples)]... + ./str2num(header.description.time_range.sample_rate) ... + - str2num(header.description.time_range.pre_trigger); + +% first sensor 1, from sample 1 to last (537264) + + +% +% fprintf('Importing channel location information') +% Channel=getfield(load([studyname '_channel']), 'Channel'); +% chanlocs=LENAchannels2chanlocs( Channel ); +% +% i=1; +% %datafilename=sprintf('%s%d.mat', database , i); +% f=dir(sprintf('%s*.mat', database)); +% f=strvcat({f.name}); +% f=f(:,length(database)+1:end); +% trials=str2num(char(strrep(cellstr(f), '.mat', ''))); +% trials=sort(trials); +% +% h = waitbar(0,'Importing Datafiles. Please wait...'); +% for i=1:length(trials) +% waitbar(i/length(trials),h) +% datafilename=sprintf('%s%d.mat', database , trials(i)); +% fprintf('Importing %s\n', datafilename) +% Data(i)=load(datafilename); +% %channelflag=channelflags & Data(i).ChannelFlag; +% channelflag=channelflags; +% Data(i).F=Data(i).F(find(channelflag),:); +% end +%close(h) + +return diff --git a/lena/readLENA.m~ b/lena/readLENA.m~ new file mode 100644 index 0000000..583168e --- /dev/null +++ b/lena/readLENA.m~ @@ -0,0 +1,147 @@ +% read_lena() - read LENA header & data +% +% Usage: +% >> [header data lena_tree] = read_lena(lenafile) +% +% Required Input: +% lenafile = LENA header file +% Available options: +% device -> 'ALL' (default)|'MEG'|'EEG'|'EEG+MEG'|'DC' +% trials -> Trials to import as list of indices (default: [] = 'all') +% timewindow -> Default: [] = 'all' +% +% Outputs: +% header = header info (from reading the xml heder file) +% data = cell array of data (one cell by trial) +% +% Author: Karim N'Diaye, CNRS-UPR640, 01 Jan 2004 +% +% See also: +% POP_READLENA, EEGLAB + +%123456789012345678901234567890123456789012345678901234567890123456789012 + +% Copyright (C) 2004, CNRS - UPR640, N'Diaye Karim, +% karim.ndiaye@chups.jussieu.Fr +% +% This program is free software; you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation; either version 2 of the License, or +% (at your option) any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with this program; if not, write to the Free Software +% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +% $Log: readegi.m,v $ +% Revision 0.1 2004/01/01 +% First alpha version for EEGLAB release 4.301 + +function [header data lena_tree] = readLENA(lenafile,varargin) + +if nargin < 1 + help(mfilename); + return; +end; +binary_extensions = {'.bin' '.data'}; + +lena_tree = xmltree(lenafile); +warning('off','MATLAB:warn_r14_stucture_assignment') +header = convert(lena_tree); +warning('on','MATLAB:warn_r14_stucture_assignment') + +if nargout<2 + return +end + +if nargin==2 + data_filename = varargin{1}; + if isfield(header, 'data_filename') + warning('Overloading binary data filname from header (%s) to: %s', header.data_filename, data_filename); + end + header.data_filename = data_filename; +end + + +if ~isfield(header, 'data_filename') + header.data_filename =''; + for i=1:length(binary_extensions) + data_filename = [lenafile(1:end-5) binary_extensions{i} ]; + if exist(data_filename, 'file') + header.data_filename = data_filename; + warning('Assumed binary data file not specified in header... Found possible match: %s', data_filename); + break; + end + end +end +if isempty(header.data_filename) + error('No datafile') +end + +fprintf('Importing data from binary file...\n') + +if strcmp(header.data_format,'LittleEndian') + data_fid = fopen(header.data_filename,'r','l'); +elseif strcmp(header.data_format,'BigEndian') + data_fid = fopen(header.data_filename,'r','b'); +else + warning('No data_format provided'); + data_fid = fopen(header.data_filename,'r'); +end + +% Read the offset because this info is not provided in header! +header.data_offset = getData_offset(lena_tree); +fseek(data_fid,header.data_offset,-1); + +% Get data type : +switch(header.data_type) + case 'unsigned fixed' + data_type='uint'; + case 'fixed' + data_type='int'; + case 'floating' + data_type='float'; + otherwise + error('Error : data_type wasn t found, which is required.') + return +end +% Get the data size : +header.data_size = getData_size(lena_tree); +data = fread(data_fid,str2num(header.description.time_range.time_samples)*... + length(header.description.sensor_range.sensor_list.sensor),data_type); +data=reshape(data,str2num(header.description.time_range.time_samples), []); +h.Time= +% first sensor 1, from sample 1 to last (537264) + + +% +% fprintf('Importing channel location information') +% Channel=getfield(load([studyname '_channel']), 'Channel'); +% chanlocs=LENAchannels2chanlocs( Channel ); +% +% i=1; +% %datafilename=sprintf('%s%d.mat', database , i); +% f=dir(sprintf('%s*.mat', database)); +% f=strvcat({f.name}); +% f=f(:,length(database)+1:end); +% trials=str2num(char(strrep(cellstr(f), '.mat', ''))); +% trials=sort(trials); +% +% h = waitbar(0,'Importing Datafiles. Please wait...'); +% for i=1:length(trials) +% waitbar(i/length(trials),h) +% datafilename=sprintf('%s%d.mat', database , trials(i)); +% fprintf('Importing %s\n', datafilename) +% Data(i)=load(datafilename); +% %channelflag=channelflags & Data(i).ChannelFlag; +% channelflag=channelflags; +% Data(i).F=Data(i).F(find(channelflag),:); +% end +%close(h) + +return diff --git a/lena/readLENAHead.m b/lena/readLENAHead.m new file mode 100644 index 0000000..a59e7b6 --- /dev/null +++ b/lena/readLENAHead.m @@ -0,0 +1,568 @@ +function [Header lena_tree] = fastReadLENAHead(lena_file,dim_to_read) + +% This function reads a lena format file. +% +% Parameters : +% Input : +% - lena_file ( String ) : name of LENA file, or lenaTree object if +% file as already been read +% Optional +% - dim_to_read ( cell ) : sample to read indices for each dimension +% +% +% +% Output : +% - Header : a structure containing the header information that can be +% manipulated by the user: Only the dimensions specified inside +% dim_to_read parameter will be found in the Header structure. If dim_to_read is +% not used, all the dimensions will be found in Header structure +% +% Usage : +% H=fastReadLENA('/path/to/file/test.lena'); +% will return a matrix containing all data +% +% H=fastReadLENA('/path/to/file/test.lena',{ 2 , [ ] , [5:12] }); +% will return a matrix containing data for : +% - 2nd element in first dimension +% - all elements in second dimension +% - elements 5 to 12 in third dimension +% +% CNRS LENA UPR 640 + +% Check argument number : + if nargin <3 + smart = 1; + end + +level=[]; + +% First check lena_file argument, and read the header : + +if strcmp(class(lena_file),'xmltree') + lena_tree = lena_file; + lena_file = getfilename(lena_tree); +elseif exist(lena_file)==2 + lena_tree = lenaTree( lena_file ); +else + error(lena_file,'Provided file name doesn t seem to exist, or is not a valid object') + return +end + +% Get the header location : +[lena_path,lena_name]=fileparts(lena_file); + + +% Check present dimensions : + + +history=getHistory(lena_tree); + + + + +dimensions_names = getDimensions_names(lena_tree); +if iscell(dimensions_names) +length_dimensions_names=length(dimensions_names); +else if ischar(dimensions_names) + length_dimensions_names=1; + else if isempty(dimensions_names) + length_dimensions_names=0; + end + end +end + + + +if iscell(dimensions_names) +for i=1:length_dimensions_names + if strcmp(dimensions_names{i},'frequency_range') + frequency_dim=i; + elseif strcmp(dimensions_names{i},'sensor_range') + sensor_dim=i; + elseif strcmp(dimensions_names{i},'time_range') + time_dim=i; + elseif strcmp(dimensions_names{i},'datablock_range') + datablock_dim=i; + else + error(strcat('Found unexpected dimension : ',dimensions_names{i})) + end +end + +else if ischar(dimensions_names) + if strcmp(dimensions_names,'frequency_range') + frequency_dim=1; + elseif strcmp(dimensions_names,'sensor_range') + sensor_dim=1; + elseif strcmp(dimensions_names,'time_range') + time_dim=1; + elseif strcmp(dimensions_names,'datablock_range') + datablock_dim=1; + else + error(strcat('Found unexpected dimension : ',dimensions_names{i})) + end + else +% to do + end + +end + +% If no dimension to read is provided, read all : +if nargin == 1 + for i = 1:length_dimensions_names + dim_to_read{i}=''; + end +else + if length( dim_to_read) dim_to_read{i}(1)&Time1) +% F=convertBlocs2matrix(samples, size_to_read); +% else F=cell2mat (samples); +% end + +fclose (data_fid); +% Search Supersensor scales to apply : + +% if exist('sensor_dim') +% coef = getSupersensor_scale(lena_tree,dim_to_read{sensor_dim}); +% % Apply gains : +% if sensor_dim==1 +% size_to_read(sensor_dim)=1; +% coef_mat = repmat(coef',size_to_read); +% else +% s_t_r_temp=[1,size_to_read(1:sensor_dim-1),size_to_read(sensor_dim+1:end)]; +% test=repmat(coef',s_t_r_temp); +% coef_mat=permute(test,[2:sensor_dim,1,sensor_dim+1:length(size_to_read)]); +% end +% +% if (length(size_to_read)>1) +% F=coef_mat.*F; +% else F=coef.*F; +% end +% end +%if smart + % F=squeeze(F); +%end + +return +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +function [leveldim, sensorList, sensorSample]=ProcessSensorDim(lena_tree, sensor_dim,dimensions_names, dim_to_read) + + +sensors_number = getSensors_number(lena_tree); + + + + Supersensors_number = getSensor_sample_number(lena_tree); + leveldim=Supersensors_number; + + + for i=1: sensors_number + list_sensor_name{i} = getSensor_name(lena_tree,i); + end + + + for i=1: Supersensors_number +% list_Supersensor_name{i} = getSupersensor_name(lena_tree,i); +coef{i} = getSupersensor_scale(lena_tree,i); + + end + + +list_Supersensor_name= getSupersensor_list(lena_tree); + +s=[]; + dim=(dim_to_read{sensor_dim}); + if ~ isempty(dim) + s_list= list_sensor_name(dim(1):dim(end)); + + + + for i=1:length(s_list) +% s(i)= rank_sensor(a_listsensor, sensorList(i)); + s1= rank_sensor(list_Supersensor_name, s_list(i)); + s=[s s1]; + + end + + else + + s= [1:length(list_Supersensor_name)]; + + + end + + + + + + +if (~ isempty(list_sensor_name)) + + for i=1:length(list_sensor_name) + sensor_categories{i} = getSensor_category(lena_tree,list_sensor_name{i} ); + sensor_coils{i}= getSensor_coil_geometry(lena_tree,list_sensor_name{i} ); + end + end + + sensorList.name=list_sensor_name; + sensorList.category=sensor_categories; + sensorList.coils=sensor_coils; + + %sensorSample=select_sensors_in(list_sensor_name, lena_tree); + + sensorSample=Supersensors_in(s, list_Supersensor_name ); +end + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function sample_sensors=Supersensors_in(sel_sensor, Supersensor_list ) + +dim=size(Supersensor_list,2); +res=1; +if dim >1 + + for i=1: length(sel_sensor) + for i1=1:dim + rank=sel_sensor(i); + Superlist_sensors{i,i1}=Supersensor_list{rank,i1}; + end + Superlist_unit{i,:}='unknown unit'; %getSupersensor_unit(lenafile,i); + Superlist_scale{i,:}=1;%getSupersensor_scale(lenafile,i); + %res=res+1; + end + +else + + +% + for i=1: length(sel_sensor) + rank=sel_sensor(i); + Superlist_sensors{i,:}=(Supersensor_list{rank,:}); + Superlist_unit{i,:}=''; %getSupersensor_unit(lenafile,i); + Superlist_scale{i,:}=1;%getSupersensor_scale(lenafile,i); + %res=res+1; + end + +end + +if exist('Superlist_sensors') +dim=size(Superlist_sensors,2); +dim1=size(Superlist_sensors,1); +if dim>1 +sample_sensors.list_sensors=reshape (Superlist_sensors,dim1,dim); + + +else +sample_sensors.list_sensors=reshape (Superlist_sensors,length(Superlist_sensors),1); +end +sample_sensors.unit=reshape (Superlist_unit,length(Superlist_unit),1); +sample_sensors.scale=reshape (Superlist_scale,length(Superlist_scale),1); +end + +end + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function [leveldim, time_pretrigger,sample_rate, timesamples_toread]=ProcessTimeDim(lena_tree, time_dim,dimensions_names, dim_to_read) + + +time_samples = getTime_samples(lena_tree); + sample_rate=getSample_rate(lena_tree); + leveldim=time_samples; + time_pretrigger=getPre_trigger(lena_tree); + + + if iscell(dimensions_names) + + for i=1: length( dimensions_names) + if strcmp(dimensions_names{i}, 'time_range') + if isempty(cell2mat(dim_to_read(i))) + timesamples_toread=time_samples; + else + timesamples_toread=cell2mat(dim_to_read(i)); + + end + break; + end + end + else if strcmp(dimensions_names,'time_range') + if isempty(cell2mat(dim_to_read(1))) + timesamples_toread=time_samples; + else + timesamples_toread=cell2mat(dim_to_read(1)); + + end + end + end + +end +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function s= rank_sensor(a_listsensor, sensor) + +found=false; +s=-1; +i=1; +k=1; +while (i< length(a_listsensor)) & ~found + tab=((strmatch(char(sensor),char(a_listsensor{i,:})))); + if ~isempty(tab) + % found =true; + s(k)= i; + k=k+1; + + end + i=i+1; +end +end +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/lena/read_ClassFile.m b/lena/read_ClassFile.m new file mode 100644 index 0000000..a3c10af --- /dev/null +++ b/lena/read_ClassFile.m @@ -0,0 +1,53 @@ +% readClassFile() - Reads Classes from CTF's ClassFile +% +% [cls]=readClassFile(dsfolder) +% +% Input : +% dsfolder: the folder containing the ClassFile to be read +% or the ClassFile filename itself +% +% Output: +% cls: the classes +% +% Classrs have the following fields: +% .name: their label e.g. 'Tr17' +% .e: the trial in which they occur (indexed from 1) +% + +% Not yet! +% Optional fields: +% .color +% .comment + +function [cls] = readClassFile(dsfolder) + +if exist(dsfolder, 'dir') + [dspath, dsname]=fileparts(dsfolder); + clsfile=fullfile(dsfolder, 'ClassFile.cls'); +elseif exist(dsfolder, 'file') + clsfile=dsfolder; +else + error([mfilename ': .cls file not found!']); +end + +fprintf('Reading File: %s\n', clsfile) +txt=textread(clsfile,'%s','delimiter','\n','whitespace',''); +n=0; + +for i=1:size(txt,1) + if strcmp(txt{i}, 'NAME:') + n=n+1; + cls(n).name=char(txt{i+1}); + end + + if strcmp(txt{i}, 'NUMBER OF TRIALS:') + nb_events(n)=str2num(txt{i+1}); + end + + if strmatch('TRIAL NUMBER', txt{i}) + for j=1:nb_events(n) + [a]= strread(txt{i+j}); + cls(n).e(j)=a+1; + end + end +end diff --git a/lena/read_events.m b/lena/read_events.m new file mode 100755 index 0000000..762f72b --- /dev/null +++ b/lena/read_events.m @@ -0,0 +1,32 @@ + + +error('read_events is deprecated, use: read_lena_events()') + +% function [e,txt] = read_event(eventfile) +% if nargin < 1,help(mfilename);return;end; +% e = struct(... +% 'trial',[],... +% 'time', [],... +% 'name', [],... +% 'duration', [],... +% 'offset', []); +% fprintf('Reading File: %s\n', eventfile) +% txt=textread(eventfile,'%s','delimiter','\n','whitespace',''); +% NL = length(txt); +% i=1; +% % TRIAL TIME NAME DURATION OFFSET +% while(i<=NL && (txt{i}(1) == '#')) +% i=i+1; +% end +% n=0; +% while i<=NL +% n=n+1; +% try +% [e(n).trial,e(n).time,e(n).name,e(n).duration,e(n).offset]=strread(txt{i}, '%d%f%s%f%f'); +% catch +% [e(n).trial,e(n).time,e(n).name]=strread(txt{i}, '%d%f%s'); +% end +% e(n).trial=e(n).trial+1; +% e(n).name = e(n).name{1}; +% i=i+1; +% end diff --git a/lena/read_lena.m b/lena/read_lena.m new file mode 100755 index 0000000..a1ad7c1 --- /dev/null +++ b/lena/read_lena.m @@ -0,0 +1,475 @@ +% read_lena() - read LENA header & data +% +% Usage: +% [header] = read_lena(lenafile) reads only the header found in the LENA folder +% >> [header data] = read_lena(lenafile) also reads the whole data +% >> [header data] = read_lena(lenafile,DataSelection) +% >> [header data] = read_lena(lenafile,OPTIONS) +% >> [header data Time OPTIONS extras] = read_lena(lenafile,...) +% +% Required input: +% lenafile = LENA header file +% Optional inputs: +% The subset of data to read may be (more quickly) specified as a cell array: +% DataSelection = { 1:4 [] -2} to read the first four elemnts of the +% first dimension (e.g. the first 4 channels), all the 2nd dimension, +% excluding the second sample of the third etc. +% +% Alternatively, one may use an OPTIONS structure with any of those fields: +% OPTIONS.DataSelection = (see above) +% OPTIONS.SensorCategory = 'ALL' (default)|'MEG'|'EEG'|'EEG+MEG'|'DC'|'ADC' +% OPTIONS.SensorName = '' (default) or any regexp string or a cell array +% OPTIONS.Trials = Trials to read (default: [] = 'all') +% CAUTION: First Trial is #0 +% OPTIONS.TimeWindow = Start and end time in seconds (todo) +% OPTIONS.XMLDepth -> Depth of the XML parsing (min. 3, default: 5) +% This latter structure (OPTIONS) may also be provided as a list: +% >> [header data] = read_lena(lenafile,'Trials',[1:N],'SensorName','FC'...) +% +% Outputs: +% header = header info (from reading the xml heder file) +% data = cell array of data (one cell by trial) +% Time = Time vector for each sample +% OPTIONS = struct (see above) +% extras = structure with fields: +% * classes +% * events +% * badchannels +% +% Requires: xml_read() +% See also: +% +% Author: Karim N'Diaye, CNRS-UPR640, 21 Apr 2010 + +% Copyright (C) 2004, CNRS - UPR640, N'Diaye Karim, +% karim.ndiaye@chups.jussieu.Fr +% +% This program is free software; you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation; either version 2 of the License, or +% (at your option) any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with this program; if not, write to the Free Software +% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +% $Log: readegi.m,v $ +% Revision 0.1 2004/01/01 +% First alpha version for EEGLAB release 4.301 + +function [header data Time options extras] = read_lena(lenafile,varargin) +if nargin < 1 + help(mfilename); + return; +end +header = []; +data=[]; +Time = []; +options = {}; +extras = struct('classes',[] ,'events', [], 'badchannels', []); +if nargin<2 + options.DataSelection = { } ; % reads everything +end +%warning('Oh oh! There is a lot to read! It may take a while...'); +data_selection = {}; +data = []; + +options.XMLDepth = 5; +if nargin>2 + if isstruct(varargin{1}) + fn = fieldnames(varargin{1}); + for i=1:numel(fn) + options = setfield(options,fn{i},getfield(varargin{1},fn{i})); + end + varargin(1)=[]; + end + if iscell(varargin) && numel(varargin) == 1 + options.DataSelection = varargin{1}; + else + if mod(length(varargin),2) ~= 0 + error('Options should be given in pairs: ''Field1'', value1, etc.') + end + for i=1:2:length(varargin) + options = setfield(options,varargin{i}, varargin{i+1}); + end + end +end + +if iscell(lenafile) + header=cell(size(lenafile)); + for i=1:numel(lenafile) + fprintf('Reading: %s\n', lenafile{i}); + switch nargout + case 0; + case 1 + [header{i}] = read_lena(lenafile{i},varargin{:}); + case 2 + data=cell(size(lenafile)); + [header{i} data{i}] = read_lena(lenafile{i},varargin{:}); + case 3 + data=header; + Time=header; + [header{i} data{i} Time{i}] = read_lena(lenafile{i},varargin{:}); + end + end + + return +end +if not(exist(lenafile, 'file')) + error('File/folder does not exist: %s', lenafile); +end + +binary_extensions = {'.bin' '.data'}; + +canonical_dimensions = { + 'Sensor' 'sensor_range' ; + 'Time' 'time_range' ; + 'Trial' 'datablock_range' }; + +if exist(lenafile, 'dir') == 7 + %Folder = 2.0 format and above + header_file = fullfile(lenafile, 'data.header'); +else + [p,r,e]=fileparts(lenafile); + header_file = fullfile(p,[r, '.header']); + if ~exist(header_file, 'file') + header_file = lenafile; + end +end + +% The old version used 'xmltree' which is buggy with +% lena_tree = xmltree(header_file); +% warning('off', 'MATLAB:warn_r14_stucture_assignment') +% header = convert(lena_tree); +% warning('on', 'MATLAB:warn_r14_stucture_assignment') +% +% Indeed sensor info is lost: +% header.description.sensor_range.sensor_list(1).sensor{1}.coil +% is empty! +fprintf('Reading the header: %s...\n', header_file); + +if isfield(options, 'XMLDepth') + fprintf('Reading the XML to depth %d...\n', options.XMLDepth); + header = xml_read(header_file, struct('NumLevels',options.XMLDepth)); +else + header = xml_read(header_file); +end + +%% Exit if enough... +if nargout<2 + return +end +% Otherwise, it means that the user wants the binary data... +%% Read XML +if options.XMLDepth<4 + warning('read_lena:MinimalXMLDepth','The XML header must be read to a depth of at least 4 so as to import data'); + options.XMLDepth = 4; + header = xml_read(header_file, struct('NumLevels',options.XMLDepth)); +end + +%% Time vector +if isfield(header.description, 'time_range') +Time = [1:header.description.time_range.time_samples]... + ./header.description.time_range.sample_rate ... + - header.description.time_range.pre_trigger; +else + warning('read_lena:NoTimeRange', 'Data have no time range.'); + Time = []; +end +%% Data filename +if ~isfield(header, 'data_filename') + header.data_filename =''; + if exist(lenafile, 'dir') + %new format + data_filename = fullfile(lenafile, 'data.data'); + else + for i=1:length(binary_extensions) + [p,fn,ext]=fileparts(lenafile); + fn = fullfile(p,fn); + data_filename = [fn binary_extensions{i} ]; + if exist(data_filename, 'file') + header.data_filename = data_filename; + warning('I had to guess the binary data file since it was not specified in header...\nFound a possible match: %s\n', data_filename); + break; + end + end + end +else + data_filename = header.data_filename; +end +if isempty(fileparts(data_filename)) + % If specified without path, assumed it is in the same folder as the + % lena file + data_filename = fullfile(fileparts(lenafile), data_filename); +end +if ~exist(data_filename, 'file') + error('No binary data file found!') +end +fprintf('Data will be read from binary file: %s ...\n', data_filename) + +%% Data format + +if isfield(header, 'data_format') && strcmp(header.data_format,'LittleEndian') + data_fid = fopen(data_filename,'r','l'); +elseif isfield(header, 'data_format') && strcmp(header.data_format,'BigEndian') + data_fid = fopen(data_filename,'r','b'); +else + warning('No data_format provided'); + data_fid = fopen(data_filename,'r'); +end +if data_fid < 1 + error('Can''t open binary file'); +end +% Read the offset because this info is not provided in header! +if isfield(header, 'data_offset') + data_offset = header.data_offset; +else + data_offset = 0 ; +end +% Get data type : +switch(header.data_type) + case 'unsigned fixed' + data_type='uint'; + case 'fixed' + data_type='int'; + case 'floating' + data_type='float'; + otherwise + error('Error : data_type wasn t found, which is required.') + return +end +% Get the data precision size : +data_precision = []; +switch header.data_type + case 'unsigned fixed' + data_type='uint'; + case 'fixed' + data_type='int'; + case 'floating' + data_type='float'; + if isfield(header, 'data_size') && header.data_size == 4 + data_precision = 'float32'; + end + otherwise + error('Error : data_type wasn t found, which is required.') + return +end +if isempty(data_precision) + error('Unknonw data precision/size/type'); +end + +%% Data dimensions +data_dimensions = fieldnames(header.description); +ndim = length(data_dimensions); +[ign,candim,dimcan] = intersect(canonical_dimensions(:,2), data_dimensions); +if any(dimcan(1:2) == 0) || any((candim-dimcan)~=0) || any(candim == 0) + warning('read_lena:NonBrainstormDimensions',... + ['Dimensions are not "brainstorm/eeglab" like (i.e. Sensor * Time * Epochs) but: ', ... + sprintf('<%s> ',data_dimensions{:})]) +end +% the nth canonical dim is the p-th one in the data +icandim(candim)=dimcan; +data_size=zeros(1,ndim); +for i=1:ndim + switch (data_dimensions{i}) + case 'datablock_range' + data_size(i) = numel(header.description.datablock_range.datablock_samples.trial); + case 'sensor_range' + data_size(i) = numel(header.description.sensor_range.sensor_samples.supersensor); + case 'time_range' + data_size(i) = header.description.time_range.time_samples; + case 'frequency_range' + data_size(i) = numel(header.description.frequency_range.frequency_samples.superfrequency); + otherwise + error('Wrong dimension') + end +end + +%% Data selection +% Expand (if needed) data_selection to the actual size of the data +if numel(data_selection)header.description.time_range.sample_rate) + error('Selection Time window goes by the Time range [%g .. %g] of the data!', Time([1 end])) + end + else + error('Wrong Time selection (should be a 2x1 cell)') + end + + data_selection{icandim(2)} = options.Time; + case 'Trials' + data_selection{icandim(3)} = options.Trials; + end +end +for i_dim = 1:ndim + if islogical(data_selection{i_dim}) + data_selection{i_dim} = find(data_selection{i_dim}); + end +end +if any(cellfun('isempty',data_selection)) + warning('read_lena:EmptySelection','According to options, data selection reduces to nill!') + return +end +for i_dim = 1:ndim + if any(abs(data_selection{i_dim})>data_size(i_dim)) + error('read_lena:SelectionBeyondRange','Selection of <%s> is beyond authorized range [ %g .. %g ]', ... + data_dimensions{i_dim}, 1, data_size(i_dim)) + end +end + +%% Data fread +% the fastest increasing dimension is the first and also the first one in +% the header, that is the 1st one unfolding in the binary data + +h.data.filename = data_filename; +h.data.selection = data_selection; + +[fread_size fread_precision fread_skip fread_offset fread_selection] = fread_options(data_size,data_selection,data_precision); +% Now reads the data +fprintf('... reading %d bytes of data ...\n',prod(fread_size)); +fseek(data_fid,data_offset+fread_offset, 'bof'); +data = fread(data_fid,prod(fread_size),fread_precision,fread_skip); +% Reshape data read into a matrix +data=reshape(data,fread_size); +% Extract the data if needed +for i=1:ndim + data=subarray(data,fread_selection{i},i); +end +% Now permute so as to match the "natural" order of data_dimensions +data=permute(data,ndim:-1:1); + + +%% Read extra info (classes, etc.) +extras.classes = read_lena_classes(lenafile); +extras.events = read_lena_events(lenafile); +%badchannels = read_lena_badchannels(lenafile); + +return + + +function [fread_size fread_precision fread_skip fread_offset fread_selection] = fread_options(data_size,data_selection,data_precision) +% Optimal precision & skip to use in fread based on the shape of data +% The 'quick and dirty' way is to read all the data and select in the matrix afterwards... +fread_size = fliplr(data_size); +fread_precision = data_precision; +fread_skip = 0; +fread_offset = 0; +fread_selection = fliplr(data_selection); +return; +switch(data_precision) + case {'uint8','int8','uchar','schar','char'} + fread_bytes = 2; + case {'uint16','int32','short','ushort'} + fread_bytes = 2; + case {'float32','single', 'float','uint32','int32','int','uint','long'} + fread_bytes = 4; + case {'float64','double','uint64','int64'} + fread_bytes = 8; + otherwise + warning('read_lena:UnknownPrecision','Unknown precision: %s -- Go through the Full monthy', data_precision); + return +end +ndim = numel(fread_size); +% Squeeze successive dimensions (beyond the first one) with only one sample to read +% Find the last squeezable dimension +i = 1; +while i1) + break; + end +end +if i==ndim + skip = 0; +else + +end +return + + +function [i dist] = findclosest(x,y) +x=x(:)'; +y=y(:); +[dist i] = min(abs(repmat(x,length(y),1) - repmat(y,1,length(x)))); + diff --git a/lena/read_lena.m~ b/lena/read_lena.m~ new file mode 100644 index 0000000..fd8e7ea --- /dev/null +++ b/lena/read_lena.m~ @@ -0,0 +1,474 @@ +% read_lena() - read LENA header & data +% +% Usage: +% [header] = read_lena(lenafile) reads only the header found in the LENA folder +% >> [header data] = read_lena(lenafile) also reads the whole data +% >> [header data] = read_lena(lenafile,DataSelection) +% >> [header data] = read_lena(lenafile,OPTIONS) +% >> [header data Time OPTIONS extras] = read_lena(lenafile,...) +% +% Required input: +% lenafile = LENA header file +% Optional inputs: +% The subset of data to read may be (more quickly) specified as a cell array: +% DataSelection = { 1:4 [] -2} to read the first four elemnts of the +% first dimension (e.g. the first 4 channels), all the 2nd dimension, +% excluding the second sample of the third etc. +% +% Alternatively, one may use an OPTIONS structure with any of those fields: +% OPTIONS.DataSelection = (see above) +% OPTIONS.SensorCategory = 'ALL' (default)|'MEG'|'EEG'|'EEG+MEG'|'DC'|'ADC' +% OPTIONS.SensorName = '' (default) or any regexp string or a cell array +% OPTIONS.Trials = Trials to read (default: [] = 'all') +% CAUTION: First Trial is #0 +% OPTIONS.TimeWindow = Start and end time in seconds (todo) +% OPTIONS.XMLDepth -> Depth of the XML parsing (min. 3, default: 5) +% This latter structure (OPTIONS) may also be provided as a list: +% >> [header data] = read_lena(lenafile,'Trials',[1:N],'SensorName','FC'...) +% +% Outputs: +% header = header info (from reading the xml heder file) +% data = cell array of data (one cell by trial) +% Time = Time vector for each sample +% OPTIONS = struct (see above) +% extras = structure with fields: +% * classes +% * events +% * badchannels +% +% Requires: xml_read() +% See also: +% +% Author: Karim N'Diaye, CNRS-UPR640, 21 Apr 2010 + +% Copyright (C) 2004, CNRS - UPR640, N'Diaye Karim, +% karim.ndiaye@chups.jussieu.Fr +% +% This program is free software; you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation; either version 2 of the License, or +% (at your option) any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with this program; if not, write to the Free Software +% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +% $Log: readegi.m,v $ +% Revision 0.1 2004/01/01 +% First alpha version for EEGLAB release 4.301 + +function [header data Time options extras] = read_lena(lenafile,varargin) +if nargin < 1 + help(mfilename); + return; +end +header = []; +data=[]; +Time = []; +options = {}; +extras = struct('classes',[] ,'events', [], 'badchannels', []); +if nargin<2 + options.DataSelection = { } ; % reads everything +end +%warning('Oh oh! There is a lot to read! It may take a while...'); +data_selection = {}; +data = []; + +options.XMLDepth = 5; +if nargin>2 + if isstruct(varargin{1}) + fn = fieldnames(varargin{1}); + for i=1:numel(fn) + options = setfield(options,fn{i},getfield(varargin{1},fn{i})); + end + varargin(1)=[]; + end + if iscell(varargin) && numel(varargin) == 1 + options.DataSelection = varargin{1}; + else + if mod(length(varargin),2) ~= 0 + error('Options should be given in pairs: ''Field1'', value1, etc.') + end + for i=1:2:length(varargin) + options = setfield(options,varargin{i}, varargin{i+1}); + end + end +end + +if iscell(lenafile) + header=cell(size(lenafile)); + for i=1:numel(lenafile) + fprintf('Reading: %s\n', lenafile{i}); + switch nargout + case 0; + case 1 + [header{i}] = read_lena(lenafile{i},varargin{:}); + case 2 + data=cell(size(lenafile)); + [header{i} data{i}] = read_lena(lenafile{i},varargin{:}); + case 3 + data=header; + Time=header; + [header{i} data{i} Time{i}] = read_lena(lenafile{i},varargin{:}); + end + end + + return +end +if not(exist(lenafile, 'file')) + error('File/folder does not exist: %s', lenafile); +end + +binary_extensions = {'.bin' '.data'}; + +canonical_dimensions = { + 'Sensor' 'sensor_range' ; + 'Time' 'time_range' ; + 'Trial' 'datablock_range' }; + +if exist(lenafile, 'dir') == 7 + %Folder = 2.0 format and above + header_file = fullfile(lenafile, 'data.header'); +else + [p,r,e]=fileparts(lenafile); + header_file = fullfile(p,[r, '.header']); + if ~exist(header_file, 'file') + header_file = lenafile; + end +end + +% The old version used 'xmltree' which is buggy with +% lena_tree = xmltree(header_file); +% warning('off', 'MATLAB:warn_r14_stucture_assignment') +% header = convert(lena_tree); +% warning('on', 'MATLAB:warn_r14_stucture_assignment') +% +% Indeed sensor info is lost: +% header.description.sensor_range.sensor_list(1).sensor{1}.coil +% is empty! +fprintf('Reading the header: %s...\n', header_file); + +if isfield(options, 'XMLDepth') + fprintf('Reading the XML to depth %d...\n', options.XMLDepth); + header = xml_read(header_file, struct('NumLevels',options.XMLDepth)); +else + header = xml_read(header_file); +end + +%% Exit if enough... +if nargout<2 + return +end +% Otherwise, it means that the user wants the binary data... +%% Read XML +if options.XMLDepth<4 + warning('read_lena:MinimalXMLDepth','The XML header must be read to a depth of at least 4 so as to import data'); + options.XMLDepth = 4; + header = xml_read(header_file, struct('NumLevels',options.XMLDepth)); +end + +%% Time vector +if isfield(header.description, 'time_range') +Time = [1:header.description.time_range.time_samples]... + ./header.description.time_range.sample_rate ... + - header.description.time_range.pre_trigger; +else + warning('read_lena:NoTimeRange', 'Data have no time range.'); + Time = []; +end +%% Data filename +if ~isfield(header, 'data_filename') + header.data_filename =''; + if exist(lenafile, 'dir') + %new format + data_filename = fullfile(lenafile, 'data.data'); + else + for i=1:length(binary_extensions) + [p,fn,ext]=fileparts(lenafile); + fn = fullfile(p,fn); + data_filename = [fn binary_extensions{i} ]; + if exist(data_filename, 'file') + header.data_filename = data_filename; + warning('I had to guess the binary data file since it was not specified in header...\nFound a possible match: %s\n', data_filename); + break; + end + end + end +else + data_filename = header.data_filename; +end +if isempty(fileparts(data_filename)) + % If specified without path, assumed it is in the same folder as the + % lena file + data_filename = fullfile(fileparts(lenafile), data_filename); +end +if ~exist(data_filename, 'file') + error('No binary data file found!') +end +fprintf('Data will be read from binary file: %s ...\n', data_filename) + +%% Data format + +if isfield(header, 'data_format') && strcmp(header.data_format,'LittleEndian') + data_fid = fopen(data_filename,'r','l'); +elseif isfield(header, 'data_format') && strcmp(header.data_format,'BigEndian') + data_fid = fopen(data_filename,'r','b'); +else + warning('No data_format provided'); + data_fid = fopen(data_filename,'r'); +end +if data_fid < 1 + error('Can''t open binary file'); +end +% Read the offset because this info is not provided in header! +if isfield(header, 'data_offset') + data_offset = header.data_offset; +else + data_offset = 0 ; +end +% Get data type : +switch(header.data_type) + case 'unsigned fixed' + data_type='uint'; + case 'fixed' + data_type='int'; + case 'floating' + data_type='float'; + otherwise + error('Error : data_type wasn t found, which is required.') + return +end +% Get the data precision size : +data_precision = []; +switch header.data_type + case 'unsigned fixed' + data_type='uint'; + case 'fixed' + data_type='int'; + case 'floating' + data_type='float'; + if isfield(header, 'data_size') && header.data_size == 4 + data_precision = 'float32'; + end + otherwise + error('Error : data_type wasn t found, which is required.') + return +end +if isempty(data_precision) + error('Unknonw data precision/size/type'); +end + +%% Data dimensions +data_dimensions = fieldnames(header.description); +ndim = length(data_dimensions); +[ign,candim,dimcan] = intersect(canonical_dimensions(:,2), data_dimensions); +if any(dimcan(1:2) == 0) || any((candim-dimcan)~=0) || any(candim == 0) + warning('read_lena:NonBrainstormDimensions',... + ['Dimensions are not "brainstorm/eeglab" like (i.e. Sensor * Time * Epochs) but: ', ... + sprintf('<%s> ',data_dimensions{:})]) +end +% the nth canonical dim is the p-th one in the data +icandim(candim)=dimcan; +data_size=zeros(1,ndim); +for i=1:ndim + switch (data_dimensions{i}) + case 'datablock_range' + data_size(i) = numel(header.description.datablock_range.datablock_samples.trial); + case 'sensor_range' + data_size(i) = numel(header.description.sensor_range.sensor_samples.supersensor); + case 'time_range' + data_size(i) = header.description.time_range.time_samples; + case 'frequency_range' + data_size(i) = numel(header.description.frequency_range.frequency_samples.superfrequency); + otherwise + error('Wrong dimension') + end +end + +%% Data selection +% Expand (if needed) data_selection to the actual size of the data +if numel(data_selection)header.description.time_range.sample_rate) + error('Selection Time window goes by the Time range [%g .. %g] of the data!', Time([1 end])) + end + else + error('Wrong Time selection (should be a 2x1 cell)') + end + + data_selection{icandim(2)} = options.Time; + case 'Trials' + data_selection{icandim(3)} = options.Trials; + end +end +for i_dim = 1:ndim + if islogical(data_selection{i_dim}) + data_selection{i_dim} = find(data_selection{i_dim}); + end +end +if any(cellfun('isempty',data_selection)) + warning('read_lena:EmptySelection','According to options, data selection reduces to nill!') + return +end +for i_dim = 1:ndim + if any(abs(data_selection{i_dim})>data_size(i_dim)) + error('read_lena:SelectionBeyondRange','Selection of <%s> is beyond authorized range [ %g .. %g ]', ... + data_dimensions{i_dim}, 1, data_size(i_dim)) + end +end + +%% Data fread +% the fastest increasing dimension is the first and also the first one in +% the header, that is the 1st one unfolding in the binary data + +h.data.filename = data_filename; +h.data.selection = data_selection; + +[fread_size fread_precision fread_skip fread_offset fread_selection] = fread_options(data_size,data_selection,data_precision); +% Now reads the data +fprintf('... reading %d bytes of data ...\n',prod(fread_size)); +fseek(data_fid,data_offset+fread_offset, 'bof'); +data = fread(data_fid,prod(fread_size),fread_precision,fread_skip); +% Reshape data read into a matrix +data=reshape(data,fread_size); +% Extract the data if needed +for i=1:ndim + data=subarray(data,fread_selection{i},i); +end +% Now permute so as to match the "natural" order of data_dimensions +data=permute(data,ndim:-1:1); + + +%% Read extra info (classes, etc.) +c = read_lena_classes(lenafile); + + +return + + +function [fread_size fread_precision fread_skip fread_offset fread_selection] = fread_options(data_size,data_selection,data_precision) +% Optimal precision & skip to use in fread based on the shape of data +% The 'quick and dirty' way is to read all the data and select in the matrix afterwards... +fread_size = fliplr(data_size); +fread_precision = data_precision; +fread_skip = 0; +fread_offset = 0; +fread_selection = fliplr(data_selection); +return; +switch(data_precision) + case {'uint8','int8','uchar','schar','char'} + fread_bytes = 2; + case {'uint16','int32','short','ushort'} + fread_bytes = 2; + case {'float32','single', 'float','uint32','int32','int','uint','long'} + fread_bytes = 4; + case {'float64','double','uint64','int64'} + fread_bytes = 8; + otherwise + warning('read_lena:UnknownPrecision','Unknown precision: %s -- Go through the Full monthy', data_precision); + return +end +ndim = numel(fread_size); +% Squeeze successive dimensions (beyond the first one) with only one sample to read +% Find the last squeezable dimension +i = 1; +while i1) + break; + end +end +if i==ndim + skip = 0; +else + +end +return + + +function [i dist] = findclosest(x,y) +x=x(:)'; +y=y(:); +[dist i] = min(abs(repmat(x,length(y),1) - repmat(y,1,length(x)))); + diff --git a/lena/read_lena_classes.m b/lena/read_lena_classes.m new file mode 100644 index 0000000..8038710 --- /dev/null +++ b/lena/read_lena_classes.m @@ -0,0 +1,37 @@ +function cls = read_lena_class(classfile) +%READ_LENA_CLASS - One line description goes here. +% [cls] = read_lena_class(classfile) +% [cls] = read_lena_class(lenafolder) +% +% Example +% >> read_lena_class +% +% See also: + +% Author: K. N'Diaye (kndiaye01gmail.com) +% Copyright (C) 2011 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2011-06-08 Creation +% +% ----------------------------- Script History --------------------------------- +cls=repmat(struct('name',[],'trials',[]),0); +if nargin < 1,help(mfilename);return;end; +if exist(classfile,'dir') + classfile = fullfile(classfile,'data.class'); +end +if ~exist(classfile,'file') + error('read_lena_class:BadLenaFolder','File not found: %s',classfile) +end +e = read_lena_events(classfile); +[names i,j] =unique({e.name}); +n=numel(names); +cls=repmat(cls,1,n); +for i=1:n + cls(i).name = names{i}; + cls(i).trials = [e(j==i).trial]; +end diff --git a/lena/read_lena_events.m b/lena/read_lena_events.m new file mode 100644 index 0000000..28f97a2 --- /dev/null +++ b/lena/read_lena_events.m @@ -0,0 +1,79 @@ +function [events,filename] = read_lena_events(filename) +%READ_LENA_EVENTS - Reads events in a LENA file +% [events] = read_lena_events(filename) +% events is a struct array with fields: +% .name +% .comments +% .color +% .epoch +% .time +% .duration +% .offset +% +% Example +% >> read_lena_events('run1.lena') +% >> read_lena_events('data.ptx') +% +% See also: read_ptx + +% Author: K. N'Diaye (kndiaye01gmail.com) +% Copyright (C) 2010 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2010-10-18 Creation +% +% ----------------------------- Script History --------------------------------- +events = repmat(struct(... + 'name',[],... + 'comment',[],... + 'color',[],... + 'trial',[],... + 'time',[],... + 'duration',[],... + 'offset',[]),0); + +folder = NaN; +if exist(filename, 'dir') + folder = filename; + % Default names for event files + EVENTFILES = { 'data.event' 'data.events' }; + while ~isempty(EVENTFILES) + filename = fullfile(folder,EVENTFILES{1}); + EVENTFILES(1)=[]; + if exist(filename,'file') + break + end + end +end + +if ~exist(filename,'file') + if ~isnan(folder) + return + end + error('File not found: %s must be a LENA folder or a ptx-formatted file',filename); +end + +[onsets,names,epochs,durations,offsets,header] = read_ptx(filename); +if ~isempty(onsets) + events = struct(... + 'name',names,... + 'comment',[],... + 'color',[],... + 'trial',num2cell(epochs),... + 'time',num2cell(onsets),... + 'duration',num2cell(durations),... + 'offset',num2cell(offsets)); +end +if ~isempty(header) + specs = regexp(header(strmatch('#TRIGGER COMMENTS',header)+1:end),... + '#\s+(?\w*)\s+(?.*)\s+#[cC][oO][lL][oO][rR]=(?[#\w]*)','names'); + for i=1:numel(specs) + if ~isempty(specs{i}) + [events(ismember({events.name},specs{i}.name)).color]=deal(specs{i}.color); + end + end +end diff --git a/lena/read_ptx.m b/lena/read_ptx.m new file mode 100644 index 0000000..0a5eb07 --- /dev/null +++ b/lena/read_ptx.m @@ -0,0 +1,45 @@ +function [onsets,names,epochs,durations,offsets,header] = read_ptx(filename) +% read_ptx() - read PTX-formatted file +% Usage: +% [onsets,names,epochs] = read_ptx(filename) reads a PTX formatted file +% [onsets,names,epochs,durations,offsets,header] = ... +% also reads extra info (if provided in PTX file) +% +%Nota bene: +% onsets are in seconds (even for PTX v.1 files) +% epochs start at 1, if not specified in the file, epochs(:) = NaN +% +% See also: read_lena(), read_lena_events() + +% Author: Karim N'Diaye, CRICM, CNRS, 01 Jan 2010 + +if ~exist(filename, 'file') + error('read_ptx:FileNotFound','File not found: %s',filename) +end +% version info: +version = textread(filename,'%s',1); +version = version{1}; +if ~isequal(regexp(version,'#'),1) + version = 'PTX_V1.0 (assumed)'; +end +header = {}; +switch version + case '#PTX_V2.0' + content = textread(filename,'%s','delimiter','\n'); + header = content(strmatch('#',content)); + try + [epochs,onsets,names,durations,offsets]=textread(filename, '%d%f%s%f%f', 'commentstyle', 'shell'); + catch + [onsets,names,durations,offsets]=textread(filename, '%f%s%f%f', 'commentstyle', 'shell'); + epochs = onsets.*NaN; + end + case { '#PTX_V1.0' 'PTX_V1.0 (assumed)' } + [onsets,names]=textread(filename, '%f%s', 'commentstyle', 'shell'); + onsets = onsets/1000; + epochs = onsets.*NaN; + durations = onsets.*NaN; + offsets = onsets.*NaN; + otherwise + error('Unknown PTX version: %s', version); +end +epochs = epochs+1; diff --git a/lena/read_trc.m b/lena/read_trc.m new file mode 100644 index 0000000..7b858b8 --- /dev/null +++ b/lena/read_trc.m @@ -0,0 +1,119 @@ +%TRC import +% after: +% http://neuralensemble.org/trac/OpenElectrophy/browser/trunk/pyssdh/OpenElectrophy/filereader/generic/read_trc.py?rev=77c char string of length 1 + +%Python struct alias read_f format: +% b signed char integer schar +% B unsigned char integer uchar +% h short integer int16 +% H unsigned short integer uint16 +% i int / integer int32 +% I unsigned integer or long uint32 +% http://docs.python.org/library/struct.html + + +clear TRC +TRC.Filename='\\Serveur_meg\datalinks\CONFINUM\intra\micromed\patients\PAT_2\EEG_3.TRC'; +fid=fopen(TRC.Filename); +%% Read Patient info +fseek(fid,64,-1); +TRC.PatientName = deblank(fread(fid, 22, 'int8=>char')'); +TRC.PatientSurname = deblank(fread(fid, 20, 'uint8=>char')'); + +%% Read date +fseek(fid,128,-1); +TRC.Datenum = datenum(fliplr(fread(fid,3,'int8')')+[1900 0 0]); +TRC.Date = datestr(TRC.Datenum); + +%% Header +fseek(fid,175,-1); +TRC.HeaderVersion = fread(fid,1,'int8'); +if TRC.HeaderVersion ~= 4 + error('*.trc file is not Micromed System98 Header type 4') +end + +%% Data description +fseek(fid,138,-1); +TRC.DataStartOffset = fread(fid,1,'uint32') +TRC.NumberOfChannels = fread(fid,1,'uint16') +TRC.Multiplexer = fread(fid,1,'uint16') +TRC.SamplingRate = fread(fid,1,'uint16') +TRC.Bytes = fread(fid,1,'uint16') +TRC.Precision = sprintf('uint%d', 8*TRC.Bytes); +fseek(fid,176+8,-1); +TRC.Code_Area = fread(fid,1,'uint32') +TRC.Code_Area_Length = fread(fid,1,'uint32') +fseek(fid,192+8,-1) +TRC.Electrode_Area = fread(fid,1,'uint32') +TRC.Electrode_Area_Length = fread(fid,1,'uint32') +fseek(fid,400+8,-1) +TRC.Trigger_Area =fread(fid,1,'uint32') +TRC.Trigger_Area_Length=fread(fid,1,'uint32') + +%% Read Code Info +fseek(fid,TRC.Code_Area,-1) +TRC.Code = fread(fid, TRC.NumberOfChannels, 'uint16') +units = { ... + -1 1e-9 + 0 1e-6 + 1 1e-3 + 2 1 + 100 'percent' + 101 'bpm' + 102 'Adim'}; + +%units = {-1:1e-9, 0:1e-6, 1:1e-3, 2:1, 100:'percent', 101:'bpm', 102:'Adim'} +for c=1:TRC.NumberOfChannels + Channel.chan_record = TRC.Code(c) + fseek(fid, TRC.Electrode_Area+TRC.Code(c)*128+2,-1) + Channel.positive_input = sprintf('%02d-%s', c, char(fread(fid, 6, 'uchar'))); + Channel.negative_input = sprintf('%02d-%s', c, char(fread(fid, 6, 'uchar'))); + Channel.logical_min = fread(fid,1,'int32'); + Channel.logical_max = fread(fid,1,'int32'); + Channel.logical_ground = fread(fid,1,'int32'); + Channel.physical_min = fread(fid,1,'int32'); + Channel.physical_max = fread(fid,1,'int32'); + try + Channel.measurement_unit = units{fread(fid,1,'int16')==[units{:,1}],2}; + catch + Channel.measurement_unit = 10e-6; + end + fseek(fid, 8,0); + Channel.rate_coef = fread(fid,1,'uint16'); + Channel + TRC.Channel(c) = Channel; + +end + +%% Read Raw data +fseek(fid,TRC.DataStartOffset,-1); +% Skip 1h40 minutes of data +% + +if 0 % read all channels +%TRC.Data = fread(fid,70000*TRC.NumberOfChannels,'uint16'); +%TRC.Data = reshape(TRC.Data,64, []); +end +% Skip ... min of data +fseek(fid,TRC.Bytes*TRC.NumberOfChannels*TRC.SamplingRate*(0*60),0) +% Skip 24 Channels to read Channel 25 +fseek(fid,TRC.Bytes*24,0) +TRC.Data =fread(fid,Inf,'uint16', 63*TRC.Bytes + TRC.Bytes*TRC.NumberOfChannels*TRC.SamplingRate )'; +plot(TRC.Data) + +%% RAWDATA rescaling matrix +factor = ([TRC.Channel.physical_max]-[TRC.Channel.physical_min])./([TRC.Channel.logical_max]-[TRC.Channel.logical_min]); +if ~all(cell2mat({TRC.Channel.measurement_unit})==units{[units{:,1}]==0,2}) + error('Don''t know how to read multiple measurement units in a single file') +end + +for i=1:size(TRC.Data,1) + TRC.F(i,:) = (TRC.Data(i,:)-TRC.Channel(i).logical_ground)*factor(i); +end + +%% + +TRC + +%% Close fid +%fclose(fid); diff --git a/lena/readds.m b/lena/readds.m new file mode 100644 index 0000000..d8acbea --- /dev/null +++ b/lena/readds.m @@ -0,0 +1,33 @@ +if ~ exist('fic_ds') + fic_ds=input('Fichier DS ?\n ''/pclxserver/home/ndiaye/DAV/rawdata/S7/aud-avTr22.ds'' '); +end + +if ~ exist('F') + [F,Channel,imegsens,ieegsens,iothersens,irefsens,grad_order_no,no_trials,filter,Time, RunTitle] = ds2brainstorm(fic_ds,0); +end + +t=textread('ClassFile.cls','%s'); +n=strmatch('BAD',t) +n=strmatch('NUMBER',t(n:end))+n-1 + +nbad=str2num(t{n(1)+3}); +badtrials=[]; +for i=1:nbad + j=i+n(2); +badtrials=[badtrials str2num(t{j})]; +end + +MARQUEURS={'Tr22' 'Tr23' 'Tr24' 'Tr25' 'Tr26'} +for i=1:length(MARQUEURS) + t=textread('MarkerFile.mrk','%s'); + mark(i).name=MARQUEURS{i} + n=strmatch(MARQUEURS{i},t); + n=strmatch('SAMPLES',t(n:end))+n-1; + mark(i).nb=str2num(t{n(1)+1}) + + for j=1:mark(i).nb + k=2*j+n(2)+7; + mark(i).tr(j)=str2num(t{k}); + end + +end diff --git a/lena/xml_read.m b/lena/xml_read.m new file mode 100644 index 0000000..9abd471 --- /dev/null +++ b/lena/xml_read.m @@ -0,0 +1,514 @@ +function [tree, RootName, DOMnode] = xml_read(xmlfile, Pref) +%XML_READ reads xml files and converts them into Matlab's struct tree. +% +% DESCRIPTION +% tree = xml_read(xmlfile) reads 'xmlfile' into data structure 'tree' +% +% tree = xml_read(xmlfile, Pref) reads 'xmlfile' into data structure 'tree' +% according to your preferences +% +% [tree, RootName, DOMnode] = xml_read(xmlfile) get additional information +% about XML file +% +% INPUT: +% xmlfile URL or filename of xml file to read +% Pref Preferences: +% Pref.ItemName - default 'item' - name of a special tag used to itemize +% cell arrays +% Pref.ReadAttr - default true - allow reading attributes +% Pref.ReadSpec - default true - allow reading special nodes +% Pref.Str2Num - default 'smart' - convert strings that look like numbers +% to numbers. Options: "always", "never", and "smart" +% Pref.KeepNS - default true - keep or strip namespace info +% Pref.NoCells - default true - force output to have no cell arrays +% Pref.Debug - default false - show mode specific error messages +% Pref.NumLevels- default infinity - how many recursive levels are +% allowed. Can be used to speed up the function by prunning the tree. +% Pref.RootOnly - default true - output variable 'tree' corresponds to +% xml file root element, otherwise it correspond to the whole file. +% Pref.CellItem - default 'true' - leave 'item' nodes in cell notation. +% OUTPUT: +% tree tree of structs and/or cell arrays corresponding to xml file +% RootName XML tag name used for root (top level) node. +% Optionally it can be a string cell array storing: Name of +% root node, document "Processing Instructions" data and +% document "comment" string +% DOMnode output of xmlread +% +% DETAILS: +% Function xml_read first calls MATLAB's xmlread function and than +% converts its output ('Document Object Model' tree of Java objects) +% to tree of MATLAB struct's. The output is in format of nested structs +% and cells. In the output data structure field names are based on +% XML tags, except in cases when tags produce illegal variable names. +% +% Several special xml node types result in special tags for fields of +% 'tree' nodes: +% - node.CONTENT - stores data section of the node if other fields are +% present. Usually data section is stored directly in 'node'. +% - node.ATTRIBUTE.name - stores node's attribute called 'name'. +% - node.COMMENT - stores node's comment section (string). For global +% comments see "RootName" output variable. +% - node.CDATA_SECTION - stores node's CDATA section (string). +% - node.PROCESSING_INSTRUCTIONS - stores "processing instruction" child +% node. For global "processing instructions" see "RootName" output variable. +% - other special node types like: document fragment nodes, document type +% nodes, entity nodes, notation nodes and processing instruction nodes +% will be treated like regular nodes +% +% EXAMPLES: +% MyTree=[]; +% MyTree.MyNumber = 13; +% MyTree.MyString = 'Hello World'; +% xml_write('test.xml', MyTree); +% [tree treeName] = xml_read ('test.xml'); +% disp(treeName) +% gen_object_display() +% % See also xml_examples.m +% +% See also: +% xml_write, xmlread, xmlwrite +% +% Written by Jarek Tuszynski, SAIC, jaroslaw.w.tuszynski_at_saic.com +% References: +% - Function inspired by Example 3 found in xmlread function. +% - Output data structures inspired by xml_toolbox structures. + +%% default preferences +DPref.ItemName = 'item'; % name of a special tag used to itemize cell arrays +DPref.CellItem = false; % leave 'item' nodes in cell notation +DPref.ReadAttr = true; % allow reading attributes +DPref.ReadSpec = true; % allow reading special nodes: comments, CData, etc. +DPref.KeepNS = true; % Keep or strip namespace info +DPref.Str2Num = 'smart';% convert strings that look like numbers to numbers +DPref.NoCells = true; % force output to have no cell arrays +DPref.NumLevels = 1e10; % number of recurence levels +DPref.PreserveSpace = false; % Preserve or delete spaces at the beggining and the end of stings? +RootOnly = true; % return root node with no top level special nodes +Debug = false; % show specific errors (true) or general (false)? +tree = []; +RootName = []; + +%% Check Matlab Version +v = ver('MATLAB'); +version = str2double(regexp(v.Version, '\d.\d','match','once')); +if (version<7.1) + error('Your MATLAB version is too old. You need version 7.1 or newer.'); +end + +%% read user preferences +if (nargin>1) + if (isfield(Pref, 'ItemName' )), DPref.ItemName = Pref.ItemName; end + if (isfield(Pref, 'CellItem' )), DPref.CellItem = Pref.CellItem; end + if (isfield(Pref, 'Str2Num' )), DPref.Str2Num = Pref.Str2Num ; end + if (isfield(Pref, 'NoCells' )), DPref.NoCells = Pref.NoCells ; end + if (isfield(Pref, 'NumLevels')), DPref.NumLevels = Pref.NumLevels; end + if (isfield(Pref, 'ReadAttr' )), DPref.ReadAttr = Pref.ReadAttr; end + if (isfield(Pref, 'ReadSpec' )), DPref.ReadSpec = Pref.ReadSpec; end + if (isfield(Pref, 'KeepNS' )), DPref.KeepNS = Pref.KeepNS; end + if (isfield(Pref, 'RootOnly' )), RootOnly = Pref.RootOnly; end + if (isfield(Pref, 'Debug' )), Debug = Pref.Debug ; end + if (isfield(Pref, 'PreserveSpace')), DPref.PreserveSpace = Pref.PreserveSpace; end +end +if ischar(DPref.Str2Num), % convert from character description to numbers + DPref.Str2Num = find(strcmpi(DPref.Str2Num, {'never', 'smart', 'always'}))-1; + if isempty(DPref.Str2Num), DPref.Str2Num=1; end % 1-smart by default +end + +%% read xml file using Matlab function +if isa(xmlfile, 'org.apache.xerces.dom.DeferredDocumentImpl'); + % if xmlfile is a DOMnode than skip the call to xmlread + try + try + DOMnode = xmlfile; + catch ME + error('Invalid DOM node: \n%s.', getReport(ME)); + end + catch %#ok catch for mablab versions prior to 7.5 + error('Invalid DOM node. \n'); + end +else % we assume xmlfile is a filename + if (Debug) % in debuging mode crashes are allowed + DOMnode = xmlread(xmlfile); + else % in normal mode crashes are not allowed + try + try + DOMnode = xmlread(xmlfile); + catch ME + error('Failed to read XML file %s: \n%s',xmlfile, getReport(ME)); + end + catch %#ok catch for mablab versions prior to 7.5 + error('Failed to read XML file %s\n',xmlfile); + end + end +end +Node = DOMnode.getFirstChild; + +%% Find the Root node. Also store data from Global Comment and Processing +% Instruction nodes, if any. +GlobalTextNodes = cell(1,3); +GlobalProcInst = []; +GlobalComment = []; +GlobalDocType = []; +while (~isempty(Node)) + if (Node.getNodeType==Node.ELEMENT_NODE) + RootNode=Node; + elseif (Node.getNodeType==Node.PROCESSING_INSTRUCTION_NODE) + data = strtrim(char(Node.getData)); + target = strtrim(char(Node.getTarget)); + GlobalProcInst = [target, ' ', data]; + GlobalTextNodes{2} = GlobalProcInst; + elseif (Node.getNodeType==Node.COMMENT_NODE) + GlobalComment = strtrim(char(Node.getData)); + GlobalTextNodes{3} = GlobalComment; + % elseif (Node.getNodeType==Node.DOCUMENT_TYPE_NODE) + % GlobalTextNodes{4} = GlobalDocType; + end + Node = Node.getNextSibling; +end + +%% parse xml file through calls to recursive DOMnode2struct function +if (Debug) % in debuging mode crashes are allowed + [tree RootName] = DOMnode2struct(RootNode, DPref, 1); +else % in normal mode crashes are not allowed + try + try + [tree RootName] = DOMnode2struct(RootNode, DPref, 1); + catch ME + error('Unable to parse XML file %s: \n %s.',xmlfile, getReport(ME)); + end + catch %#ok catch for mablab versions prior to 7.5 + error('Unable to parse XML file %s.',xmlfile); + end +end + +%% If there were any Global Text nodes than return them +if (~RootOnly) + if (~isempty(GlobalProcInst) && DPref.ReadSpec) + t.PROCESSING_INSTRUCTION = GlobalProcInst; + end + if (~isempty(GlobalComment) && DPref.ReadSpec) + t.COMMENT = GlobalComment; + end + if (~isempty(GlobalDocType) && DPref.ReadSpec) + t.DOCUMENT_TYPE = GlobalDocType; + end + t.(RootName) = tree; + tree=t; +end +if (~isempty(GlobalTextNodes)) + GlobalTextNodes{1} = RootName; + RootName = GlobalTextNodes; +end + + +%% ======================================================================= +% === DOMnode2struct Function =========================================== +% ======================================================================= +function [s TagName LeafNode] = DOMnode2struct(node, Pref, level) + +%% === Step 1: Get node name and check if it is a leaf node ============== +[TagName LeafNode] = NodeName(node, Pref.KeepNS); +s = []; % initialize output structure + +%% === Step 2: Process Leaf Nodes (nodes with no children) =============== +if (LeafNode) + if (LeafNode>1 && ~Pref.ReadSpec), LeafNode=-1; end % tags only so ignore special nodes + if (LeafNode>0) % supported leaf node types + try + try % use try-catch: errors here are often due to VERY large fields (like images) that overflow java memory + s = char(node.getData); + if (isempty(s)), s = ' '; end % make it a string + % for some reason current xmlread 'creates' a lot of empty text + % fields with first chatacter=10 - those will be deleted. + if (~Pref.PreserveSpace || s(1)==10) + if (isspace(s(1)) || isspace(s(end))), s = strtrim(s); end % trim speces is any + end + if (LeafNode==1), s=str2var(s, Pref.Str2Num, 0); end % convert to number(s) if needed + catch ME % catch for mablab versions 7.5 and higher + warning('xml_io_tools:read:LeafRead', ... + 'This leaf node could not be read and was ignored. '); + getReport(ME) + end + catch %#ok catch for mablab versions prior to 7.5 + warning('xml_io_tools:read:LeafRead', ... + 'This leaf node could not be read and was ignored. '); + end + end + if (LeafNode==3) % ProcessingInstructions need special treatment + target = strtrim(char(node.getTarget)); + s = [target, ' ', s]; + end + return % We are done the rest of the function deals with nodes with children +end + +if (level>Pref.NumLevels+1), return; end % if Pref.NumLevels is reached than we are done + +%% === Step 3: Process nodes with children =============================== +if (node.hasChildNodes) % children present + Child = node.getChildNodes; % create array of children nodes + nChild = Child.getLength; % number of children + + % --- pass 1: how many children with each name ----------------------- + f = []; + for iChild = 1:nChild % read in each child + [cname cLeaf] = NodeName(Child.item(iChild-1), Pref.KeepNS); + if (cLeaf<0), continue; end % unsupported leaf node types + if (~isfield(f,cname)), + f.(cname)=0; % initialize first time I see this name + end + f.(cname) = f.(cname)+1; % add to the counter + end % end for iChild + % text_nodes become CONTENT & for some reason current xmlread 'creates' a + % lot of empty text fields so f.CONTENT value should not be trusted + if (isfield(f,'CONTENT') && f.CONTENT>2), f.CONTENT=2; end + + % --- pass 2: store all the children as struct of cell arrays ---------- + for iChild = 1:nChild % read in each child + [c cname cLeaf] = DOMnode2struct(Child.item(iChild-1), Pref, level+1); + if (cLeaf && isempty(c)) % if empty leaf node than skip + continue; % usually empty text node or one of unhandled node types + elseif (nChild==1 && cLeaf==1) + s=c; % shortcut for a common case + else % if normal node + if (level>Pref.NumLevels), continue; end + n = f.(cname); % how many of them in the array so far? + if (~isfield(s,cname)) % encountered this name for the first time + if (n==1) % if there will be only one of them ... + s.(cname) = c; % than save it in format it came in + else % if there will be many of them ... + s.(cname) = cell(1,n); + s.(cname){1} = c; % than save as cell array + end + f.(cname) = 1; % initialize the counter + else % already have seen this name + s.(cname){n+1} = c; % add to the array + f.(cname) = n+1; % add to the array counter + end + end + end % for iChild +end % end if (node.hasChildNodes) + +%% === Step 4: Post-process struct's created for nodes with children ===== +if (isstruct(s)) + fields = fieldnames(s); + nField = length(fields); + + % --- Post-processing: convert 'struct of cell-arrays' to 'array of structs' + % Example: let say s has 3 fields s.a, s.b & s.c and each field is an + % cell-array with more than one cell-element and all 3 have the same length. + % Then change it to array of structs, each with single cell. + % This way element s.a{1} will be now accessed through s(1).a + vec = zeros(size(fields)); + for i=1:nField, vec(i) = f.(fields{i}); end + if (numel(vec)>1 && vec(1)>1 && var(vec)==0) % convert from struct of + s = cell2struct(struct2cell(s), fields, 1); % arrays to array of struct + end % if anyone knows better way to do above conversion please let me know. + +end + +%% === Step 5: Process nodes with attributes ============================= +if (node.hasAttributes && Pref.ReadAttr) + if (~isstruct(s)), % make into struct if is not already + ss.CONTENT=s; + s=ss; + end + Attr = node.getAttributes; % list of all attributes + for iAttr = 1:Attr.getLength % for each attribute + name = char(Attr.item(iAttr-1).getName); % attribute name + name = str2varName(name, Pref.KeepNS); % fix name if needed + value = char(Attr.item(iAttr-1).getValue); % attribute value + value = str2var(value, Pref.Str2Num, 1); % convert to number if possible + s.ATTRIBUTE.(name) = value; % save again + end % end iAttr loop +end % done with attributes +if (~isstruct(s)), return; end %The rest of the code deals with struct's + +%% === Post-processing: fields of "s" +% convert 'cell-array of structs' to 'arrays of structs' +fields = fieldnames(s); % get field names +nField = length(fields); +for iItem=1:length(s) % for each struct in the array - usually one + for iField=1:length(fields) + field = fields{iField}; % get field name + % if this is an 'item' field and user want to leave those as cells + % than skip this one + if (strcmpi(field, Pref.ItemName) && Pref.CellItem), continue; end + x = s(iItem).(field); + if (iscell(x) && all(cellfun(@isstruct,x)) && numel(x)>1) % it's cell-array of structs + % numel(x)>1 check is to keep 1 cell-arrays created when Pref.CellItem=1 + try % this operation fails sometimes + % example: change s(1).a{1}.b='jack'; s(1).a{2}.b='john'; to + % more convinient s(1).a(1).b='jack'; s(1).a(2).b='john'; + s(iItem).(field) = [x{:}]'; % converted to arrays of structs + catch %#ok + % above operation will fail if s(1).a{1} and s(1).a{2} have + % different fields. If desired, function forceCell2Struct can force + % them to the same field structure by adding empty fields. + if (Pref.NoCells) + s(iItem).(field) = forceCell2Struct(x); + end + end % end catch + end + end +end + +%% === Step 4: Post-process struct's created for nodes with children ===== + +% --- Post-processing: remove special 'item' tags --------------------- +% many xml writes (including xml_write) use a special keyword to mark +% arrays of nodes (see xml_write for examples). The code below converts +% s.item to s.CONTENT +ItemContent = false; +if (isfield(s,Pref.ItemName)) + s.CONTENT = s.(Pref.ItemName); + s = rmfield(s,Pref.ItemName); + ItemContent = Pref.CellItem; % if CellItem than keep s.CONTENT as cells +end + +% --- Post-processing: clean up CONTENT tags --------------------- +% if s.CONTENT is a cell-array with empty elements at the end than trim +% the length of this cell-array. Also if s.CONTENT is the only field than +% remove .CONTENT part and store it as s. +if (isfield(s,'CONTENT')) + if (iscell(s.CONTENT)) + x = s.CONTENT; + for i=length(x):-1:1, if ~isempty(x{i}), break; end; end + if (i==1 && ~ItemContent) + s.CONTENT = x{1}; % delete cell structure + else + s.CONTENT = x(1:i); % delete empty cells + end + end + if (nField==1) + if (ItemContent) + ss = s.CONTENT; % only child: remove a level but ensure output is a cell-array + s=[]; s{1}=ss; + else + s = s.CONTENT; % only child: remove a level + end + end +end + + + +%% ======================================================================= +% === forceCell2Struct Function ========================================= +% ======================================================================= +function s = forceCell2Struct(x) +% Convert cell-array of structs, where not all of structs have the same +% fields, to a single array of structs + +%% Convert 1D cell array of structs to 2D cell array, where each row +% represents item in original array and each column corresponds to a unique +% field name. Array "AllFields" store fieldnames for each column +AllFields = fieldnames(x{1}); % get field names of the first struct +CellMat = cell(length(x), length(AllFields)); +for iItem=1:length(x) + fields = fieldnames(x{iItem}); % get field names of the next struct + for iField=1:length(fields) % inspect all fieldnames and find those + field = fields{iField}; % get field name + col = find(strcmp(field,AllFields),1); + if isempty(col) % no column for such fieldname yet + AllFields = [AllFields; field]; + col = length(AllFields); % create a new column for it + end + CellMat{iItem,col} = x{iItem}.(field); % store rearanged data + end +end +%% Convert 2D cell array to array of structs +s = cell2struct(CellMat, AllFields, 2); + +%% ======================================================================= +% === str2var Function ================================================== +% ======================================================================= +function val=str2var(str, option, attribute) +% Can this string 'str' be converted to a number? if so than do it. +val = str; +len = numel(str); +if (len==0 || option==0), return; end % Str2Num="never" of empty string -> do not do enything +if (len>10000 && option==1), return; end % Str2Num="smart" and string is very long -> probably base64 encoded binary +%digits = '[Inf,NaN,pi,\t,\n,\d,\+,\-,\*,\.,e,i, ,E,I,\[,\],\;,\,]'; +digits = '(Inf)|(NaN)|(pi)|[\t\n\d\+\-\*\.ei EI\[\]\;\,]'; +s = regexprep(str, digits, ''); % remove all the digits and other allowed characters +if (~all(~isempty(s))) % if nothing left than this is probably a number + if (~isempty(strfind(str, ' '))), option=2; end %if str has white-spaces assume by default that it is not a date string + if (~isempty(strfind(str, '['))), option=2; end % same with brackets + str(strfind(str, '\n')) = ';';% parse data tables into 2D arrays, if any + if (option==1) % the 'smart' option + try % try to convert to a date, like 2007-12-05 + datenum(str); % if successful than leave it as string + catch %#ok % if this is not a date than ... + option=2; % ... try converting to a number + end + end + if (option==2) + if (attribute) + num = str2double(str); % try converting to a single number using sscanf function + else + num = str2num(str); %#ok % try converting to a single number or array using eval function + end + if(isnumeric(num) && numel(num)>0), val=num; end % if convertion to a single was succesful than save + end +end + +%% ======================================================================= +% === str2varName Function ============================================== +% ======================================================================= +function str = str2varName(str, KeepNS) +% convert a sting to a valid matlab variable name +if(KeepNS) + str = regexprep(str,':','_COLON_', 'once', 'ignorecase'); +else + k = strfind(str,':'); + if (~isempty(k)) + str = str(k+1:end); + end +end +str = regexprep(str,'-','_DASH_' ,'once', 'ignorecase'); +if (~isvarname(str)) + str = genvarname(str); +end + +%% ======================================================================= +% === NodeName Function ================================================= +% ======================================================================= +function [Name LeafNode] = NodeName(node, KeepNS) +% get node name and make sure it is a valid variable name in Matlab. +% also get node type: +% LeafNode=0 - normal element node, +% LeafNode=1 - text node +% LeafNode=2 - supported non-text leaf node, +% LeafNode=3 - supported processing instructions leaf node, +% LeafNode=-1 - unsupported non-text leaf node +switch (node.getNodeType) + case node.ELEMENT_NODE + Name = char(node.getNodeName);% capture name of the node + Name = str2varName(Name, KeepNS); % if Name is not a good variable name - fix it + LeafNode = 0; + case node.TEXT_NODE + Name = 'CONTENT'; + LeafNode = 1; + case node.COMMENT_NODE + Name = 'COMMENT'; + LeafNode = 2; + case node.CDATA_SECTION_NODE + Name = 'CDATA_SECTION'; + LeafNode = 2; + case node.DOCUMENT_TYPE_NODE + Name = 'DOCUMENT_TYPE'; + LeafNode = 2; + case node.PROCESSING_INSTRUCTION_NODE + Name = 'PROCESSING_INSTRUCTION'; + LeafNode = 3; + otherwise + NodeType = {'ELEMENT','ATTRIBUTE','TEXT','CDATA_SECTION', ... + 'ENTITY_REFERENCE', 'ENTITY', 'PROCESSING_INSTRUCTION', 'COMMENT',... + 'DOCUMENT', 'DOCUMENT_TYPE', 'DOCUMENT_FRAGMENT', 'NOTATION'}; + Name = char(node.getNodeName);% capture name of the node + warning('xml_io_tools:read:unkNode', ... + 'Unknown node type encountered: %s_NODE (%s)', NodeType{node.getNodeType}, Name); + LeafNode = -1; +end + + diff --git a/linearfit.m b/linearfit.m new file mode 100644 index 0000000..8ca879c --- /dev/null +++ b/linearfit.m @@ -0,0 +1,67 @@ +function [slope, offset,R2,SCE,P,T] = linearfit(y,x) +%LINEARFIT - One line description goes here. +% [slope,offset,R2,SCE,P,T] = linearfit(Y,X) +% Solves the equation: +% Y = slope * X + offset +% Y: Data to fit. Should be a N-element vector or a 2D matrix with N lines +% X: Fitting data. Should be a N-element vector. If missing: X=1:length(Y) +% R2: Explained variance, also known as the square of the Pearson +% Product-Moment Correlation Coefficient (R=sqrt(R2)) +% SCE: Sum of of the squared distance between Y and the linear fit +% P: Non directional probability assuming normal distribution. +% When a specific hypothesis is made on the direction of the correlation +% (slope > 0 OR slope < 0) you should divide P by 2 to get the +% directional p-value +% T: Approximate T-value corresponding to the R + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2006 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2006-02-20 Creation +% KND 2008-07-01 Check X, Y order in inputs +% +% ----------------------------- Script History --------------------------------- +if nargin>1 && isvector(y) && ~isvector(x) + warning('X and Y were inverted as inputs!') + tmp=y; + y=x; + x=tmp; + clear tmp; +end +if numel(y)==max(size(y)) + y=y(:); +end +if nargin==1 + x=1:size(y,1); +end +x=x(:); +x=[x ones(size(x))]; +if(size(y,1) ~= size(x,1)) + error('Length of x and y must be the same'); +end +m=(y'/x')'; +slope=m(1,:); +offset=m(2,:); +RES = (x*m-y); +SCE = sum(RES.^2,1); +my=x(:,2)*mean(y,1); +SCY = sum((y-my).^2,1); +SCER= sum((x*m-my).^2); +if SCY>0 + R2 = SCER./SCY; +else + R2=NaN; +end +if nargout>3 + df= length(x)-2; + T = sqrt(R2)./sqrt((1-R2)./(df)); + P = betainc( df ./ (df + T.*T), df/2, 0.5); +end + + +return \ No newline at end of file diff --git a/ll.m b/ll.m new file mode 100644 index 0000000..0be196a --- /dev/null +++ b/ll.m @@ -0,0 +1,88 @@ +function [varargout]=ll(varargin) +% ll - synonym of DIR command (display bytes & date info) +% ll(pat) list file according to pattern and display output +% +% f=ll(pat) ouputs a file structure. +% Note that it uses 'ls' utility in Unix which allow advanced regexp +% e.g. f=ll('~/.c*/*') + +if nargout==0 + if isunix + if ismac && nargin==0 + varargin={pwd}; + end + system(['ls -l ' sprintf('%s ', varargin{:})]); + else + f=dir(varargin{:}); + nf=numel(f); + %sep = repmat(sprintf('\t'), [nf,1]); + %filedates = datevec([f.datenum]); + %if any(strmatch('-t', varargin(:))) + [i,i]=sort([f.datenum]); + %end + f=f(i); + for i=1:numel(f) + mfb = max([f.bytes]); + if isequal(mfb,0) + mfb = 0; + else + mfb = 1+ceil(log10(mfb)); + end + + fprintf(['%s\t%' sprintf(' %d',mfb) 'd\t%s\n'], datestr(f(i).datenum,31), f(i).bytes, f(i).name); + end + % end + % try + % disp([ ... + % datestr(datevec([f.datenum]),31) ... %strvcat({f.date}) ... + % sep ... + % num2str([f.bytes]') ... %strvcat(regexprep(cellstr(num2str([f.bytes]')), '^(\s*)0$','')) + % sep ... + % strvcat({f.name}) ... %names ... + % ]); + % catch + % disp([ ... + % datestr(datevec({f.date}),31) ... %strvcat({f.date}) ... + % sep ... + % num2str([f.bytes]') ... %strvcat(regexprep(cellstr(num2str([f.bytes]')), '^(\s*)0$','')) + % sep ... + % strvcat({f.name}) ... %names ... + % ]); + % end + end +else + if isunix + if ismac + if nargin==0 + varargin={[pwd]}; + end + if isdir(varargin{end}) + varargin{end}=[varargin{end} filesep '*'] + end + [status,s]=system(['\ls -paUogd ' sprintf('%s ', varargin{:})]) + + [attr types bytes days months times names]=strread(s,'%s%d%d%s%s%s%s%*[^\n]'); + dates = [strvcat(days) repmat(' ', numel(days),1) strvcat(months) repmat(' ', numel(days),1) strvcat(times)] + datenums = datenum(dates,31); + else + [status,s]=system(['\ls -paUogd --time-style="long-iso" --block-size=1 ' sprintf('%s ', varargin{:})]); + [attr types bytes days times names] =strread(s,'%s%d%d%s%s%s%*[^\n]'); + dates = [strvcat(days) repmat(' ', numel(days),1) strvcat(times)]; + datenums = datenum(dates,31); + end + if status + varargout={[]}; + return + end + + varargout = {struct(... + 'name', names,... + 'date', cellstr(datestr(datenums)),... + 'bytes',num2cell(bytes),... + 'isdir',num2cell(types==2),... + 'datenum',num2cell(datenums)... + )}; + else + varargout={dir(varargin{:})}; + end +end diff --git a/loadallmat.m b/loadallmat.m new file mode 100644 index 0000000..eba1498 --- /dev/null +++ b/loadallmat.m @@ -0,0 +1,22 @@ +function [varargout]=loadallmat(directory) +%loadallmat - load all MAT files from a directory +% +if nargin<1 + directory=pwd; +end + +f=dir(fullfile(directory,'*.mat')); +for i=1:length(f) + [ignore,vname]=fileparts(f(i).name); + disp(sprintf('Loading %s...', vname)); + eval(sprintf('vars.%s=load(''%s'');',vname,fullfile(directory,f(i).name))); +end +if nargout==1 + varargout={vars}; +else + vnames=fieldnames(vars); + + for i=1:length(vnames) + assignin('base', vnames{i}, getfield(vars, vnames{i})); + end +end diff --git a/loadfield.m b/loadfield.m new file mode 100644 index 0000000..ef987f8 --- /dev/null +++ b/loadfield.m @@ -0,0 +1,25 @@ +function X = loadfield(filename,fieldname) +%LOADFIELD - Load a given variable from a .mat file +% [X] = loadfield(filename,fieldname) +% +% Example +% >> a=loadfield('test.mat', 'a') +% +% See also: load, getfield + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2006 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2006-01-11 Creation +% +% ----------------------------- Script History --------------------------------- + +if nargin<2 + error('No field/vraible given!') +end +X=getfield(load(filename, fieldname), fieldname); \ No newline at end of file diff --git a/localmax.m b/localmax.m new file mode 100644 index 0000000..3451024 --- /dev/null +++ b/localmax.m @@ -0,0 +1,84 @@ +function [Y,I] = localmax(X,N,dim) +%LOCALMAX - Finds local maxima +% [Y,I] = localmax(X) +% [Y,I] = localmax(X,N) +% Looks for local maxima in vector X. +% Search window can be enlarged using input N. Default: N=1, ie. +% each local maximum must be greater than or equal to its immediate +% neighbours +% +% Example +% >> localmax +% +% See also: max + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2006 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2006-01-27 Creation +% KND 2008-09-18 Only the first value of a constant plateau is returned +% KND 2009-03-25 try/catch in case there is no license for the stats toolbox +% +% ----------------------------- Script History --------------------------------- + +if nargin<1 + error('No data!') +end +if nargin<2 + N=1; +end +if numel(X)~=max(size(X)) + error('X should be unidimensional') +end + +X=X(:)'; +n=length(X); + +if N>=n + N=n-1; + error(sprintf('Not enough elements in X (of length %d)\nSearch window should be reduced to: N=%d', n, N)); +end +t=1:n; +try + XX=[buffer(X,2*N+1,2*N,repmat(X(1),1,2*N)) ... + fliplr(flipud(buffer(fliplr(X(end-2*N+1:end)),2*N+1,2*N, repmat(X(end),1,2*N))))]; + XX=XX(:,N+1:end-N); +catch + XX=X; + for i=1:N; + XX=[X(1,[repmat(1,1,i) t(1:end-i)]); ... + XX; ... + X(1,[t(i+1:end) repmat(t(end),1,i)])]; + end +end + +YY=repmat(X,[2*N+1 1]); + +I=find(all(YY>=XX)); +dI=diff(I)==1; +I(logical([0 dI])) = []; +Y=X(I); + +return + +Y=reshape(Y,[length(Y)/prod(sX(3:end)) sX(3:end)]); +Y=ipermute(Y,pX); +I=mod(I-1,prod(sX(2)))+1; +I=reshape(I,[length(I)/prod(sX(3:end)) sX(3:end)]); +I=ipermute(I,pX); + +return + + + +N = length(x); % N: Number of elements in time series +x_prev = [x(1) x(1:(N-1))]; % x_prev: Previous element in time series +x_next = [x(2:N) x(N)]; % x_next: Next element in time series +f = ( (x<=x_prev) & (x<=x_next) ); % f: True where x takes on a local +% minimum value + diff --git a/localmin.m b/localmin.m new file mode 100644 index 0000000..f838cd6 --- /dev/null +++ b/localmin.m @@ -0,0 +1,8 @@ +function [values,ix] = localmin(x) +N = length(x); % N: Number of elements in time series +x_prev = [x(1) x(1:(N-1))]; % x_prev: Previous element in time series +x_next = [x(2:N) x(N)]; % x_next: Next element in time series +f = ( (x<=x_prev) & (x<=x_next) ); % f: True where x takes on a local + % minimum value + + diff --git a/locmax.m b/locmax.m new file mode 100644 index 0000000..6be7ee7 --- /dev/null +++ b/locmax.m @@ -0,0 +1,81 @@ +function varargout = localMaximum(x,minDist, exculdeEqualPoints) +% function varargout = localMaximum(x,minDist, exculdeEqualPoints) +% +% This function returns the indexes\subscripts of local maximum in the data x. +% x can be a vector or a matrix of any dimension +% +% minDist is the minimum distance between two peaks (local maxima) +% minDist should be a vector in which each argument corresponds to it's +% relevant dimension OR a number which is the minimum distance for all +% dimensions +% +% exculdeEqualPoints - is a boolean definning either to recognize points with the same value as peaks or not +% x = [1 2 3 4 4 4 4 4 4 3 3 3 2 1]; +% will the program return all the '4' as peaks or not - defined by the 'exculdeEqualPoints' +% localMaximum(x,3) +% ans = +% 4 5 6 7 8 9 11 12 +% +% localMaximum(x,3,true) +% ans = +% 4 7 12 +% +% +% Example: +% a = randn(100,30,10); +% minDist = [10 3 5]; +% peaks = localMaximum(a,minDist); +% +% To recieve the subscript instead of the index use: +% [xIn yIn zIn] = localMaximum(a,minDist); +% +% To find local minimum call the function with minus the variable: +% valleys = localMaximum(-a,minDist); + + if nargin < 3 + exculdeEqualPoints = false; + if nargin < 2 + minDist = size(x)/10; + end + end + + if isempty(minDist) + minDist = size(x)/10; + end + + dimX = length ( size(x) ); + if length(minDist) ~= dimX + % In case minimum distance isn't defined for all of x dimensions + % I use the first value as the default for all of the dimensions + minDist = minDist( ones(dimX,1) ); + end + + % validity checks + minDist = ceil(minDist); + minDist = max( [minDist(:)' ; ones(1,length(minDist))] ); + minDist = min( [minDist ; size(x)] ); + + % --------------------------------------------------------------------- + if exculdeEqualPoints + % this section comes to solve the problem of a plato + % without this code, points with the same hight will be recognized as peaks + y = sort(x(:)); + dY = diff(y); + % finding the minimum step in the data + minimumDiff = min( dY(dY ~= 0) ); + %adding noise which won't affect the peaks + x = x + rand(size(x))*minimumDiff; + end + % --------------------------------------------------------------------- + + + se = ones(minDist); + X = imdilate(x,se); + f = find(x == X); + + + if nargout + [varargout{1:nargout}] = ind2sub( size(x), f ); + else + varargout{1} = f; + end diff --git a/lowerfields.m b/lowerfields.m new file mode 100644 index 0000000..8134359 --- /dev/null +++ b/lowerfields.m @@ -0,0 +1,23 @@ +function [t]=lowerfields(s,force) +% lowerfields - lower case the fields of a struct +if nargin<2 + force=0; +end + +fn=fieldnames(s); +t=[]; +for i=1:length(fn) + if force + t=setfield(t, lower(fn{i}), getfield(s, fn{i})); + else + if length(strmatch(lower(fn{i}), lower(fn)))>1 & ~isequal(lower(fn{i}), fn{i}) + if nargin<2 + warning(sprintf('Field %s is not lower-cased (use option force=1 to override)', fn{i})) + end + t=setfield(t, fn{i}, getfield(s, fn{i})); + else + t=setfield(t, lower(fn{i}), getfield(s, fn{i})); + end + end +end + diff --git a/lowpass.m b/lowpass.m new file mode 100644 index 0000000..afeb19b --- /dev/null +++ b/lowpass.m @@ -0,0 +1,147 @@ +function [xf] = lowpass(x,rf,tdim,df) +% lowpass - easy to use low pass filter (zero phase) +% [xf]=lowpass(x,rf); +% [xf]=lowpass(x,rf,tdim); +% [xf]=lowpass(x,rf,tdim,df); +% lowpass data in x under frequency relative frequency: +% rf=Fc/FS (default: 0.01) +% where Fc is the cutting frequency, and FS is the sampling rate +% Filtering is done along dimension tdim (default: the longest dimension) +% df is the relative width of the attenuation: +% df=(Fc-Fc2)/FS (default: 0.001) +% A causal fft algorithm is applied (i.e. no phase shift). The filter +% functions is constructed from a Hamming window. +% ___________________________ +% :\ +% : \ +% : \ +% : \ +% : \ +% : \________ +% : : +% Fc/FS=rf (rf+df)=Fs/FS +% +% Based on Mario Chavez's LowPassFilter (used as sub-fuction) + +if nargin<2 % Default: ~6Hz/625Hz ~ 100ms sliding window + rf=.01; +end +if nargin<3 % Default: 6Hz/625Hz par défaut ~=~ 100ms sliding window + [ignore,tdim]=max(size(x)); +end +if nargin<4 % Default: ~0.5Hz/625Hz + df=.001; +end + +sx=size(x); +px=[tdim setdiff(1:ndims(x), tdim)]; +if tdim>1 + x=permute(x,px); +end +%Ensure x has an even number of data point +if rem(sx(tdim),2) + x = [x ; zeros([1 sx(px(2:end))])]; +end + + +% Construct the filter function H(f) using frequencies in Nyquist units +N = sx(tdim)+rem(sx(tdim),2); +B = fir2(N-1,[0 rf*2 (rf+df)*2 1],[1 1 0 0]); +H = abs(fft(B)); % Make zero-phase filter function +H=repmat(H',[ 1 sx(px(2:end))] ); +% Pre-allocate xf +%xf=x*0; +k=1; +xf = real(ifft(fft(x) .* H)); + +xf = reshape(ipermute(xf(1:sx(tdim),:),px),sx); + +return + + +function xf = lowpassFilter(x,Fs,Fp2) +% function XF = lowpassFilter(X,FS,FP2) +% +% Bandpass filter for the signal x. An causal fft algorithm +% is applied (i.e. no phase shift). The filter functions is +% constructed from a Hamming window. +% +% Fs : sampling frequency +% +% The passband (Fp2) and stop band (Fs2) are defined as +% +% --------------------------- +% |\ +% | \ +% | \ +% | \ +% | ----------------- +% | | +% Fp2 Fs2 = Fp2 + 0.5 (Hz) +% +% +% +% If NO OUTPUTS arguments are assigned the filter function H(f) and +% impulse response are plotted. +% +% NOTE: for long data traces the filter is very slow. +% +% EXEMPLE +% x= sin(2*pi*12*[0:1/200:10])+sin(2*pi*30*[0:1/200:10]) +% y=lowpassFilter(x,200,10); lowpass filter between 0 and 10 Hz +%------------------------------------------------------------------------ +% Originally produced by the Helsinki University of Technology, +% Adapted by Mariecito SCHMUCKEN 2001 +%------------------------------------------------------------------------ +Fs2 = Fp2 + 0.5; + +if size(x,1) == 1 + x = x'; +end +% Make x even +Norig = size(x,1); +if rem(Norig,2) + x = [x' zeros(size(x,2),1)]'; +end + +% Normalize frequencies +Ns2 = Fs2/(Fs/2); +Np2 = Fp2/(Fs/2); + +% Construct the filter function H(f) +N = size(x,1); +Nh = N/2; + +% B = fir2(N-1,[0 Ns1 Np1 Np2 Ns2 1],[0 0 1 1 0 0]); +B = fir2(N-1,[0 Np2 Ns2 1],[1 1 0 0]); +H = abs(fft(B)); % Make zero-phase filter function +IPR = real(ifft(H)); + +% Visual display if off +% if nargout == 0 +% figure, +% subplot(2,1,1) +% f = Fs*(0:Nh-1)/(N); +% plot(f,H(1:Nh)); +% xlim([0 2*Fs2]) +% ylim([0 1]); +% title('Filter function H(f)') +% xlabel('Frequency (Hz)') +% subplot(2,1,2) +% plot((1:Nh)/Fs,IPR(1:Nh)) +% xlim([0 2/Fp2]) +% xlabel('Time (sec)') +% ylim([min(IPR) max(IPR)]) +% title('Impulse response') +% end + + +if size(x,2) > 1 + for k=1:size(x,2) + xf(:,k) = real(ifft(fft(x(:,k)) .* H')); + end + xf = xf(1:Norig,:); +else + xf = real(ifft(fft(x') .* H)); + xf = xf(1:Norig); +end diff --git a/lowpassfilter.m b/lowpassfilter.m new file mode 100644 index 0000000..4fb49a8 --- /dev/null +++ b/lowpassfilter.m @@ -0,0 +1,156 @@ +function [xf] = lowpassfilter(x,rf,tdim,df) +% lowpassfilter - easy to use low pass filter (zero phase) +% [xf]=lowpassfilter(x,rf); +% [xf]=lowpassfilter(x,rf,tdim); +% [xf]=lowpassfilter(x,rf,tdim,df); +% Low-pass filtering of data x below relative frequency: +% rf=Fc/FS (default: 0.01) +% where Fc is the cutting frequency and FS is the sampling rate +% Filtering is done along dimension tdim (default: the longest dimension) +% df is the relative width of the attenuation: +% df=(Fc-Fc2)/FS (default: 0.001) +% ___________________________ +% :\ +% : \ +% : \ +% : \ +% : \ +% : \________ +% : : +% Fc/FS=rf (rf+df)=Fc2/FS +% +% A causal fft algorithm is applied (i.e. no phase shift). The filter +% functions is constructed from a Hamming window. + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Based on Mario Chavez's LowPassFilter & EEGLAB eegfiltfft +% Copyright (C) 2006 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2006-04-19 Creation +% +% ----------------------------- Script History --------------------------------- + +if nargin<2 % Default: ~6Hz/625Hz ~ 100ms sliding window + rf=.05; +end +if nargin<3 % Default longest dimension + [ignore,tdim]=max(size(x)); +end +if nargin<4 % Default: ~0.5Hz/625Hz + df=.001; +end + +sx=size(x); +px=[tdim setdiff(1:ndims(x), tdim)]; +if tdim>1 + x=permute(x,px); +end +%Ensure x has an even number of data point +if rem(sx(tdim),2) + x = [x ; zeros([1 sx(px(2:end))])]; +end + +% Construct the filter function H(f) using frequencies in Nyquist units +N = sx(tdim)+rem(sx(tdim),2); +B = fir2(N-1,[0 rf*2 (rf+df)*2 1],[1 1 0 0]); +H = abs(fft(B)); % Make zero-phase filter function +H=repmat(H',[ 1 sx(px(2:end))] ); +k=1; +xf = real(ifft(fft(x) .* H)); +xf = ipermute(reshape(xf(1:sx(tdim),:),sx(px)),px); + +return + +% This is the original function by Mario: + +function xf = lowpassFilter(x,Fs,Fp2) +% function XF = lowpassFilter(X,FS,FP2) +% +% Bandpass filter for the signal x. An causal fft algorithm +% is applied (i.e. no phase shift). The filter functions is +% constructed from a Hamming window. +% +% Fs : sampling frequency +% +% The passband (Fp2) and stop band (Fs2) are defined as +% +% --------------------------- +% |\ +% | \ +% | \ +% | \ +% | ----------------- +% | | +% Fp2 Fs2 = Fp2 + 0.5 (Hz) +% +% +% +% If NO OUTPUTS arguments are assigned the filter function H(f) and +% impulse response are plotted. +% +% NOTE: for long data traces the filter is very slow. +% +% EXEMPLE +% x= sin(2*pi*12*[0:1/200:10])+sin(2*pi*30*[0:1/200:10]) +% y=lowpassFilter(x,200,10); lowpass filter between 0 and 10 Hz +%------------------------------------------------------------------------ +% Originally produced by the Helsinki University of Technology, +% Adapted by Mariecito SCHMUCKEN 2001 +%------------------------------------------------------------------------ +Fs2 = Fp2 + 0.5; + +if size(x,1) == 1 + x = x'; +end +% Make x even +Norig = size(x,1); +if rem(Norig,2) + x = [x' zeros(size(x,2),1)]'; +end + +% Normalize frequencies +Ns2 = Fs2/(Fs/2); +Np2 = Fp2/(Fs/2); + +% Construct the filter function H(f) +N = size(x,1); +Nh = N/2; + +% B = fir2(N-1,[0 Ns1 Np1 Np2 Ns2 1],[0 0 1 1 0 0]); +B = fir2(N-1,[0 Np2 Ns2 1],[1 1 0 0]); +H = abs(fft(B)); % Make zero-phase filter function +IPR = real(ifft(H)); + +% Visual display if off +% if nargout == 0 +% figure, +% subplot(2,1,1) +% f = Fs*(0:Nh-1)/(N); +% plot(f,H(1:Nh)); +% xlim([0 2*Fs2]) +% ylim([0 1]); +% title('Filter function H(f)') +% xlabel('Frequency (Hz)') +% subplot(2,1,2) +% plot((1:Nh)/Fs,IPR(1:Nh)) +% xlim([0 2/Fp2]) +% xlabel('Time (sec)') +% ylim([min(IPR) max(IPR)]) +% title('Impulse response') +% end + + +if size(x,2) > 1 + for k=1:size(x,2) + xf(:,k) = real(ifft(fft(x(:,k)) .* H')); + end + xf = xf(1:Norig,:); +else + xf = real(ifft(fft(x') .* H)); + xf = xf(1:Norig); +end diff --git a/lpass.m b/lpass.m new file mode 100644 index 0000000..b6634cd --- /dev/null +++ b/lpass.m @@ -0,0 +1,61 @@ +function [z] = lpass(x, f, FS,tdim) +% lpass - easy to use lw pass filter (zero phase) +% [xf]=lpass(x,[f, FS, tdim]) +% Low-passes data in x ... +% with a cutting frequency f [default=5Hz] +% assuming x in sampled at FS Hz [def=625]; +% on the tdim dimension. If not given: +% - x should be a [ 6 conditions x Nchannels x Tsamples ] +% - or: tdim is ssumed to be the longest dimension... don't laugh +% +% Based on Mario Chavez's LowPassFilter +if not(exist('lowpassFilter')) + global HOMEDIR + addpath(fullfile(HOMEDIR, 'matlab', 'mario')) + addpath(fullfile(HOMEDIR, 'matlab', 'mario')) +end + +if nargin<2 % 6Hz par défaut ~=~ 100ms sliding window + f=6; +end + +if nargin < 3 + FS=625; +end + +if nargin > 3 + s1=size(x); + n1=ndims(x); + t=tdim; + s2=[prod(s1(1:t-1)) s1(t) prod(s1(t+1:end))]; + z=reshape(x,s2); + for i=1:size(z,1) + for j=1:size(z,3) + z(i,:,j)=lowpassFilter(squeeze(z(i,:,j)),FS,f); + end + end + + z=reshape(z, s1); + return +end +if ndims(x)==3 & size(x,3)<6 + for i=1:size(x,3); + z(:,:,i)=lowpassFilter(shiftdim(permute(x(:,:,i), [3 2 1])),FS,f)'; + end + if size(z, 2)==1 + z=permute(z, [2 1 3]); + end +else + s1=size(x); + n1=ndims(x); + [ignore,t]=max(s1); + s2=[prod(s1(1:t-1)) s1(t) prod(s1(t+1:end))]; + z=reshape(x,s2); + for i=1:size(z,1) + for j=1:size(z,3) + z(i,:,j)=lowpassFilter(squeeze(z(i,:,j)),FS,f); + end + end + + z=reshape(z, s1); +end \ No newline at end of file diff --git a/match.m b/match.m new file mode 100644 index 0000000..e10d1b1 --- /dev/null +++ b/match.m @@ -0,0 +1,14 @@ +function [I]=match(A,B,varargin) +% match() - find indices of matching items from an array +if iscell(A) && iscell(B) + if all(vertvec(cellfun(@ischar,A))) && ... + all(vertvec(cellfun(@ischar,B))) + I=cellfun2(@strmatch,A,B,'exact'); + v=cellfun(@isempty,I); + if any(v(:)) + warning('match:Nonmatchs','Some values from A are not in B, these indices were set to 0'); + I(v)={0}; + end + I=cell2mat(I); + end +end diff --git a/matlabupdate.m b/matlabupdate.m new file mode 100644 index 0000000..16c8bb7 --- /dev/null +++ b/matlabupdate.m @@ -0,0 +1,35 @@ +function []=matlabupdate(mpath) +% matlabupdate() - Add matlab functions in the path to attempt forward compatibility +% +% matlabupdate(mpath) add the subdirectories found in the specified path +% If a directory matches a date pattern (e.g., 2007-12-31), then it +% is included only if the running version of matlab is older. +% All other subdirectories will be unconditionally added +% +% default: mpath = folder of 'matlabupdate.m'/matlabupdate', +% e.g., g:\ndiayek\matlab\matlabupdate +% +if nargin<1 + mpath=mfilename('fullpath'); +end +dat=0; +try + dat=datenum(version('-date')); +end +f=dir(mpath); +f([f.isdir]==0)=[]; +f(strmatch('.', {f.name}, 'exact'))=[]; +f(strmatch('..', {f.name}, 'exact'))=[]; +f(strmatch('.svn', {f.name}, 'exact'))=[]; + +for dats={f.name} + t=1; %unconditionnlly add "non-calendar" directories + try + t = datenum(dats{1},'yyyy-mm-dd') >= dat; + end + if t + fprintf('Adding to the path: %s\n', fullfile(mpath, dats{1})) + addpath(fullfile(mpath, dats{1})) + end +end + diff --git a/matlabupdate/2000-11-02/strtrim.m b/matlabupdate/2000-11-02/strtrim.m new file mode 100644 index 0000000..faf03d7 --- /dev/null +++ b/matlabupdate/2000-11-02/strtrim.m @@ -0,0 +1,44 @@ +function S=strtrim(A) +%STRTRIM Remove insignificant whitespace on both sides. +% S = STRTRIM(M) removes insignificant whitespace from string M. +if iscell(A) + S=cell(size(A)); + for i=1:numel(A) + S{i}=strtrim(A{i}); + end +else + S=deblank(fliplr(deblank(fliplr(strvcat(A))))); +end +return + + + +%STRTRIM Remove insignificant whitespace. +% S = STRTRIM(M) removes insignificant whitespace from string M. +% +% Whitespace characters are the following: V = char([9 10 11 12 13 32]), which +% return true from ISSPACE(V). Per definition, insignificant leading +% whitespace leads the first non-whitespace character, and insignificant +% trailing whitespace follows the last non-whitespace character in a string. +% +% B = STRTRIM(A) removes insignificant whitespace from the char array. +% +% D = STRTRIM(C), when C is a cell array of strings, removes insignificant +% whitespace from each element of C. +% +% INPUT PARAMETERS: +% M: any one of a char row vector, 2D char array, or an N-D cell array of +% strings. +% +% RETURN PARAMETERS: +% M: any one of a char row vector, 2D char array or an N-D cell array of +% strings. +% +% EXAMPLES: +% M = STRTRIM(M) removes whitespace from the front and rear of M. +% +% See also ISSPACE, CELLSTR, DEBLANK. + +% Copyright 1984-2003 The MathWorks, Inc. +% $Revision: 1.1.6.5 $ $Date: 2004/04/10 23:33:02 $ +%============================================================================== diff --git a/matlabupdate/2004-10-13/isscalar.m b/matlabupdate/2004-10-13/isscalar.m new file mode 100644 index 0000000..c09238c --- /dev/null +++ b/matlabupdate/2004-10-13/isscalar.m @@ -0,0 +1,12 @@ +function t = isscalar(x) +%ISSCALAR True for scalar input. +% +% ISSCALAR(X) returns 1 if it's argument is a scalar and 0 otherwise. +% Note that this function does not consider the empty matrix a scalar. + +% Author: Peter J. Acklam +% Time-stamp: 2002-03-03 13:50:54 +0100 +% E-mail: pjacklam@online.no +% URL: http://home.online.no/~pjacklam +error(nargchk(1, 1, nargin)); +t = all(size(x) == 1); diff --git a/matlabupdate/2004-10-13/isvector.m b/matlabupdate/2004-10-13/isvector.m new file mode 100644 index 0000000..ef7fea7 --- /dev/null +++ b/matlabupdate/2004-10-13/isvector.m @@ -0,0 +1,4 @@ +function [tf]=isvector(X) +%isvector - Test whether X is a vector (i.e. unidimensional) +%NB Returns 1 for empty +tf=isequal(prod(size(X)), max(size(X))); diff --git a/matlabupdate/2006-08-03/cast.m b/matlabupdate/2006-08-03/cast.m new file mode 100644 index 0000000..a2a81fe --- /dev/null +++ b/matlabupdate/2006-08-03/cast.m @@ -0,0 +1,13 @@ +%CAST Cast a variable to a different data type or class. +% B = CAST(A,NEWCLASS) casts A to class NEWCLASS. A must be convertible to +% class NEWCLASS. NEWCLASS must be the name of one of the builtin data types. +% +% Example: +% a = int8(5); +% b = cast(a,'uint8'); +% +% See also CLASS. + +% Copyright 1984-2005 The MathWorks, Inc. +% $Revision: 1.1.6.3 $ $Date: 2005/06/21 19:23:23 $ +% Built-in function. diff --git a/matlabupdate/2006-08-03/edist.dll b/matlabupdate/2006-08-03/edist.dll new file mode 100644 index 0000000000000000000000000000000000000000..fe9838d0cdd54c9cd356e442832b2ee9e96553b6 GIT binary patch literal 18432 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 bBoOJhowS{@y=oh_UALhjo3aS}1`zmPQo_qk literal 0 HcmV?d00001 diff --git a/matlabupdate/overloaded/str2num.m b/matlabupdate/overloaded/str2num.m new file mode 100644 index 0000000..30470b2 --- /dev/null +++ b/matlabupdate/overloaded/str2num.m @@ -0,0 +1,85 @@ +function [x,ok] = str2num(s) +%STR2NUM Convert string matrix to numeric array. +% X = STR2NUM(S) converts a character array representation of a matrix of +% numbers to a numeric matrix. For example, +% +% S = ['1 2' str2num(S) => [1 2;3 4] +% '3 4'] +% +% The numbers in the string matrix S should be ASCII character +% representations of a numeric values. Each number may contain digits, +% a decimal point, a leading + or - sign, an 'e' or 'd' preceding a +% power of 10 scale factor, and an 'i' or 'j' for a complex unit. +% +% If the string S does not represent a valid number or matrix, +% STR2NUM(S) returns the empty matrix. [X,OK]=STR2NUM(S) will +% return OK=0 if the conversion failed. +% +% CAUTION: STR2NUM uses EVAL to convert the input argument, so side +% effects can occur if the string contains calls to functions. Use +% STR2DOUBLE to avoid such side effects or when S contains a single +% number. +% +% Also spaces can be significant. For instance, str2num('1+2i') and +% str2num('1 + 2i') produce x = 1+2i while str2num('1 +2i') produces +% x = [1 2i]. These problems are also avoided when you use STR2DOUBLE. +% +% See also STR2DOUBLE, NUM2STR, HEX2NUM, CHAR. + +% Copyright 1984-2003 The MathWorks, Inc. +% $Revision: 5.31.4.3 $ $Date: 2004/04/10 23:32:52 $ + +if isempty(s) + x = []; + ok=false; + return +end +if ~ischar(s) || ndims(s)>2 + error('Requires string or character array input.') +end +[m,n] = size(s); +if m==1, + % Replace any char(0) characters with spaces + s(s==char(0)) = ' '; + [x,ok] = protected_conversion(['[' s ']']); % Always add brackets +else + semi = ';'; + space = ' '; + if ~any(any(s == '[' | s == ']')), % String does not contain brackets + o = ones(m-1,1); + s = [['[';space(o)] s [semi(o) space(o);' ]']]'; + elseif ~any(any(s(1:m-1,:) == semi)), % No ;'s in non-last rows + s = [s,[semi(ones(m-1,1));space]]'; + else % Put ;'s where appropriate + spost = space(ones(m,1)); + for i = 1:m-1, + last = find(s(i,:) ~= space,1,'last'); + if s(i,n-last+1) ~= semi, + spost(i) = semi; + end + end + s = [s,spost]'; + end + [x,ok] = protected_conversion(s); +end +if isnumeric(x) + return +end +if ischar(x) || iscell(x) + x = []; + ok = false; +end + +function [STR2NUM_VaR,ok] = protected_conversion(STR2NUM_StR) +% Try to convert the string into a number. If this fails, return [] and ok=0 +% Protects variables in STR2NUM from "variables" in s. + +STR2NUM_LaSTeRR = lasterr; +try + STR2NUM_VaR = eval(STR2NUM_StR); + ok = true; +catch + STR2NUM_VaR = []; + ok = false; + lasterr(STR2NUM_LaSTeRR) % Preseve lasterr +end diff --git a/max2.m b/max2.m new file mode 100644 index 0000000..fb25626 --- /dev/null +++ b/max2.m @@ -0,0 +1,40 @@ +function [V,I] = max2(X,ignore,dim) +%MAX2 - Maximum across multiple dimensions +% [V,I] = max2(X,[],dim) +% V is the maximum value across given dimension (dim) +% I is the index in the given dimensions +% (Note that [] must be here for compatibility reasons with max function) +% +% Example +% >> max2(magic(3), [], [1 2]) +% 9 +% Finds the maximum in rows and columns of the magic square +% +% See also: max, imax + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2006 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2006-02-20 Creation +% +% ----------------------------- Script History --------------------------------- + +if nargin<2 + [V,I]=max(X); +elseif length(dim)==1 + [V,I]=max(X,[],dim); +else + sX=size(X); + X=nd2array(X,dim); + [V,I]=max(X,[],1); + V=nd2array(V,-dim,sX); + I=ind2sub2(sX(dim),I); + sX(dim)=1; + sX(sX==1)=[]; +% I=reshape(I,[sX,length(dim)]); +end \ No newline at end of file diff --git a/maxabs.m b/maxabs.m new file mode 100644 index 0000000..595643a --- /dev/null +++ b/maxabs.m @@ -0,0 +1,28 @@ +function [Y,I] = maxabs(X,varargin) +%MAXABS - Signed maximum of absolute value +% [Y,I] = maxabs(X,varargin) +% +% Example +% >> maxabs([-3 2 1]) ouputs: -3 +% +% See also: max, max2 + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2006 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2006-02-26 Creation +% +% ----------------------------- Script History --------------------------------- + +[Y,I]=max(abs(X),varargin{:}); +[Z,J]=max(X,varargin{:}); +if ~isequal(size(Y),size(X)) + Y(I~=J)=-Y(I~=J); +else + Y=X; +end diff --git a/maxnan.m b/maxnan.m new file mode 100644 index 0000000..a9ce407 --- /dev/null +++ b/maxnan.m @@ -0,0 +1,36 @@ +function [y,i,nans]=maxnan(x,dim) +%MAXNAN Find maximum dismissing NaN values +% [y,i,nans]=maxnan(x) +% [y,i,nans]=maxnan(x,dim) +% returns an array +% If dim is negative +% Do not include data if any NaN found along the dimension +% +% Example: +% a = cat(3,magic(5), cumsum(ones(5))); +% a([3 35])=NaN % arbitrarily set some elements to NaN +% maxnan(a,1) +% maxnan(a,-2) +% +% See also: max() + +if nargin==1, + dim = min(find(size(x)~=1)); + if isempty(dim), dim = 1; end +end +nans=isnan(x); +if dim < 0 + dim=-dim; + otherdims = setdiff(1:ndims(x),dim); + permdims = [dim otherdims]; + nans=permute(nans,permdims); + nans(any(nans(:,:),2),:)=1; + nans=ipermute(nans,permdims); +end +x(nans)=-Inf; + +%nans=double(nans); + +[y,i] = max(x,[],dim); +y(all(nans,dim)) = NaN; + \ No newline at end of file diff --git a/mcorr.m b/mcorr.m new file mode 100644 index 0000000..dcf3e57 --- /dev/null +++ b/mcorr.m @@ -0,0 +1,74 @@ +function varargout= mcorr(varargin) +%MCORR Multi-plot of all correlations between columns of a matrix +% MCORR (X) plots correlations between all possible combinations of the +% columns of array X, in a single figure. If the first argument is the +% name of a file present in the current directory, mcorr reads +% it (including variable names in the first row) as X. Otherwise, MCORR assumes +% its first argument is the array to plot, in which case consecutive +% numbers will be used as variable names. If there is a second (numeric) +% argument, mcorr will plot only the columns indicated in the second argument. +% mcorr does not plot self-correlations (each variable with itself). It +% is unpractical to try to plot more than, say, 8 variables, since each +% individual plot becomes too small. +% +% OUTPUT= MCORR (X,'sig') calculates the Pearson correlation coefficient between +% each pair of columns and, if the correlation is sgnificant at the 95% +% level, points are plotted in red and the column numbers, Pearson coefficient +% and p-value are returned in the OUTPUT array. Requires Statistical Toolbox +% +% EXAMPLES: +% mcorr ('myfile') +% mcorr ('myfile',[1:5]) +% mcorr (X,[3:6]) +% output= mcorr (X,'sig') +% +% Last modified: Feb. 2008 + + +if nargin == 0 error ('mcorr needs at least 1 argument: name of file or array'); end +pearson= 0; +color= 'b'; +output= []; + +%Its a file => Read. Else is matrix +if ischar(varargin{1}) & ~isempty(dir(varargin{1})) + [A,varnames] = tblread(varargin{1}); +else + A= varargin{1}; + varnames= num2cell([1:size(A,2)]); +end + +%Select columns +if nargin > 1 & isnumeric(varargin{2}) + A= A(:,varargin{2}); + varnames= varnames(varargin{2}); +end +ncol= size(A,2); + +%Pearson corr. coef. +for j= 1:length(varargin) + if ischar(varargin{j}) & ~isempty(findstr(lower(varargin{j}),'sig')) + pearson= 1 + end +end + +%Function +for j= 2:ncol +for k= 1:j-1 + subplot(ncol-1,ncol-1,(j-2)*(ncol-1)+k); + if pearson [rho,pval]= corr(A(:,k),A(:,j)); + if pval < 0.05 + color= 'r'; + output= [output;k,j,rho,pval]; + else + color= 'b'; + end + end + plot(A(:,k),A(:,j),'.','MarkerSize',5,'MarkerEdgeColor',color); + set (gca,'FontSize',6); + if k == 1 ylabel(varnames(j),'FontSize',7); end + if j == ncol xlabel(varnames(k),'FontSize',7); end +end +end + +varargout{1}= output; diff --git a/meannan.m b/meannan.m new file mode 100644 index 0000000..1ad068c --- /dev/null +++ b/meannan.m @@ -0,0 +1,42 @@ +function [y,nans]=meannan(x,dim,rep) +%MEANNAN Average or mean value once NaN values have been removed +% [y,nans]=meannan(x,dim) +% [y,nans]=meannan(x,dim,rep) +% +% If dim is negative +% Do not include data if any NaN found along the complementary +% dimensions (e.g., -1 will exclude from the averag any row +% containing a NaN) +% +% Example: +% a = cat(3,magic(5), cumsum(ones(5)));a([3 35])=NaN +% meannan(a,1) +% meannan(a,-2) +% see mean() for details + +if nargin==1, + dim = min(find(size(x)~=1)); + if isempty(dim), dim = 1; end +end +nans=isnan(x); +if dim < 0 + dim=-dim; + otherdims = setdiff(1:ndims(x),dim); + permdims = [dim otherdims]; + nans=permute(nans,permdims); + nans(any(nans(:,:),2),:)=1; + nans=ipermute(nans,permdims); +end +if nargin<3 + rep=0; +end +x(nans)=rep; +nans=double(nans); +if isequal(rep,0) + y = sum(x,dim)./(size(x,dim)-sum(nans,dim)); +else + y = sum(x,dim)./size(x,dim); +end +if nargin>1 + nans=logical(nans); +end diff --git a/mediansplit.m b/mediansplit.m new file mode 100644 index 0000000..9285c07 --- /dev/null +++ b/mediansplit.m @@ -0,0 +1,107 @@ +function varargout = mediansplit(X,D,Y) +%MEDIANSPLIT - Split data in two equal groups on each side of the median +% [A,M] = mediansplit(X) +% A are logical arrays such that with M is the median of X +% A=+1 for X(A) > M; A=-1 for X(A) < M, and A=0 for X(A) = M +% +% [A,M] = mediansplit(X,D) works along dimension D in X (default D=1) +% +% [Z,A,M] = mediansplit(X,[],Y) +% [Z,A,M] = mediansplit(X,D ,Y) +% will actually computes the median split of the data in Y according to X +% X must be a vector of length L = size(Y,D) +% Z is a 3-by-... matrix with such that: +% Z(1,...)=mean(Y(A==+1)); Z(2,..)=mean(Y(A==-1)); Z(3,..)=mean(Y(A==0)) +% +% Example +% >> mediansplit(rand(1,10)) +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2008 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History ---------------------------- +% KND 2008-10-15 Creation +% +% ----------------------------- Script History ---------------------------- + +if nargin<1 + error('No data!') +elseif nargin<2 + D=[]; + Y=[]; +elseif nargin<3 + Y=[]; +elseif nargin>3 + error('%s accepts 1 to 3 inputs',mfilename); +else + if ~isequal(size(X),size(Y)) + if prod(size(X))~=max(size(X)) + error('X should be a vector or of the same size as Y'); + end + X=X(:); + if isempty(D) + if length(X) == size(Y,1) + D=1; + else + error('X should be a vector matching size(Y,1) or you need to specify D'); + end + elseif length(X) ~= size(Y,D) + error('the vector X should be have a same length of size(Y,D)'); + end + rm=[ones(1,D) 1]; + rm(D)=length(X); + X=reshape(X,rm); + end +end + +if isempty(D) + M=median(X); +else + M=median(X,D); +end + +if numel(M)>1 + if isempty(D) + % the median is squeezed along one dimension but we don't know + % which one, so we need to find out. + % We cannot compare directly the sizes of X and M because if D + % happens to be the last dimension of X, the size of M may be + % shorter by one dimension, and the ~= would raise an error... + % Thus this trick: + ndX=ndims(X); + ndM=ndims(M); + nd=max(ndM,ndX); + D = [size(X) zeros(1,nd-ndX)]~=[size(M) zeros(1,nd-ndM)]; + end + rm=[ones(1,D) 1]; + rm(D)=size(X,D); + M=repmat(M,rm); +end +A=(X > M) - (X < M); +if ~isempty(Y) && ~isequal(size(X),size(Y)) + rm=[size(Y)]; + rm(D)=1; + A=repmat(A,rm); +end +sA=sum(A>0,D); +sB=sum(A<0,D); +if(~isequal(sA,sB)) + warning('Distribution(s) above and below median is/are not symmetrical!') +end +varargout={A,M}; +if isempty(Y) + return +end +if isempty(D) + D=1; %It also works if Y is horizontal +end +Z=cat(D,sum(Y.*(A>0),D),sum(Y.*(A<0),D),sum(Y.*(A==0),D)); +Z=Z./cat(D,sA,sB,sum(A==0,D)); +varargout=[Z varargout]; +return diff --git a/mergestructs.m b/mergestructs.m new file mode 100644 index 0000000..fe341c6 --- /dev/null +++ b/mergestructs.m @@ -0,0 +1,59 @@ +function [s1]=mergestructs(s1,s2,ignore) +% mergestructs - merge two structures into one +% [S3]=MERGESTRUCTS(S1,S2) creates a structure s3 whose fields are those +% of structures S1 and S2. +% +% Structures are recursively processed. If some fields (and/or subfields) +% are common to both S1 and S2, values from S1 are replaced by those +% found in S2. +% Common fields/subfields in S1 and S2 MUST be of the same size (or one +% must be of size 1 and will be expanded to match the size of the other). + +% KND + +if isempty(s1) + s1=s2; + return +end +if isempty(s2) + return +end +if nargin<3 + ignore=0; +end + +if ( ~isstruct(s1) || ~isstruct(s2) ) + if ignore && ( ~isstruct(s1) && ~isstruct(s2) ) + % 'ignore == 1' accepts input that aren't structures. + s1=s2; + return; + else + error('KND:MERGESTRUCTS:NotStructures', 'Inputs must be struct arrays!'); + return + end +end + +if ~isequal(size(s1),size(s2)) + if all(size(s1)==1) + s1=repmat(s1, size(s2)); + elseif all(size(s2)==1) + s2=repmat(s2, size(s1)); + else + error('KND:MERGESTRUCTS:NonmatchingSizes', ... + 'Input structures have incompatibles size.'); + end +end +f=fieldnames(s2); +for i=1:length(f) + do_merge = isfield(s1, f{i}); + for j=1:length(s1(:)) + if do_merge + x=mergestructs(getfield(s1(j),f{i}), getfield(s2(j),f{i}),1); + else + x=getfield(s2(j),f{i}); + s1(j).(f{i})=[]; + end + s1(j)=setfield(s1(j), f{i}, x); + end +end +s1=reshape(s1, size(s2)); diff --git a/merror.deprecated.m b/merror.deprecated.m new file mode 100644 index 0000000..48ed399 --- /dev/null +++ b/merror.deprecated.m @@ -0,0 +1,16 @@ +function [varargout]=merror(msg) +% marche pas ! +% on voit 'error in merror' appraitre sur l'écran... +[x,idb]=dbstack +if length(x)>1 + idb=2; +end +a=strread(msg, '%s'); +msg=[]; +for i=1:length(a) + msg=[msg x(idb).name sprintf(': %s\n', a{i})] +end +error(msg) +if nargout>0 +varargout={msg}; +end \ No newline at end of file diff --git a/mesh_smooth_gaussian.m b/mesh_smooth_gaussian.m new file mode 100644 index 0000000..0b79a5b --- /dev/null +++ b/mesh_smooth_gaussian.m @@ -0,0 +1,44 @@ +% Simple Gaussian Smoothing does NOT work on surface manifolds. As +% distance needs to be geodesic not Euclidian +function y=mesh_smooth_gaussian(x,v,vc,sigma,radius) +% mesh_smooth_gaussian - smooth data on a mesh with a Gaussian kernel +% y=mesh_smooth_gaussian(x,v,vc,sigma,radius) +% +% x: data, if empty, the smoothing matrix is returned. +% y: smoothed data / smooting matrix (if x empty) +% v: vertices positions ([X Y Z]) +% vc: vertices connectivity +% sigma: standard deviation of the gaussian kernel +% radius: radius of swelling (in sigma units), default: 4 +% The contribution of any given vertex is set to zero, when it +% lies further than the radius +% +% Note: Full Width at Half Maximum (FWHM) is related to sigma by: +% FWHM = sqrt(8*log(2)) * sigma ~ 2.35*sigma +if (nargin<5) + radius=4; +end +nv=size(v,1); +radius=radius*sigma; +sigma=sigma^2; % +W=sparse(1:nv,1:nv,1./sqrt(2*pi*sigma).*ones(1,nv),nv,nv,ceil(nv*radius/sqrt(sum(median(diff(v)).^2)))); +K=inline('1/sqrt(2*pi*sigma)*exp(-(x.^2/(2*sigma)))','sigma','x'); +for i=1:nv + waitbar(i/nv) + done=[i]; + nb=[vc{i}]; + while ~isempty(nb) + dist=sqrt(sum((v(nb,:)-repmat(v(i,:),length(nb),1)).^2,2)); + j=logical(dist&1` + version=`expr "$CCV" : '.*\([0-9][0-9]*\)\.'` + if [ "$version" = "4" ]; then + echo "SC5.0 or later C++ compiler is required" + fi + CXXFLAGS='-KPIC -dalign -xlibmieee -D__EXTENSIONS__ -D_POSIX_C_SOURCE=199506L -mt' + CXXLIBS="$MLIBS -lm -lCstd -lCrun" + CXXOPTIMFLAGS='-xO3 -xlibmil -DNDEBUG' + CXXDEBUGFLAGS='-g' +# +# f77 -V +# WorkShop Compilers 5.0 99/09/16 FORTRAN 77 5.0 patch 107596-03 + FC='f77' + FFLAGS='-KPIC -dalign -mt' + FLIBS="$MLIBS -lF77 -lM77 -lsunmath -lm -lcx -lc" + FOPTIMFLAGS='-O' + FDEBUGFLAGS='-g' +# + LD="$COMPILER" + LDFLAGS="-G -mt -M$TMW_ROOT/extern/lib/$Arch/$MAPFILE" + LDOPTIMFLAGS='-O' + LDDEBUGFLAGS='-g' +# + POSTLINK_CMDS=':' +#---------------------------------------------------------------------------- + ;; + mac) +#---------------------------------------------------------------------------- + CC='cc' + CFLAGS='-fno-common -traditional-cpp' + + CLIBS="$MLIBS" + + COPTIMFLAGS='-O3 -DNDEBUG' + CDEBUGFLAGS='-g' + + CXX=c++ + CXXFLAGS='-fno-common -traditional-cpp' + CXXLIBS="$MLIBS -lstdc++" + CXXOPTIMFLAGS='-O3 -DNDEBUG' + CXXDEBUGFLAGS='-g' +# + FC='f77' + FFLAGS='-f -N15 -N11 -s -Q51 -W' + ABSOFTLIBDIR=`which $FC | sed -n -e '1s|bin/'$FC'|lib|p'` + FLIBS="-L$ABSOFTLIBDIR -lfio -lf77math" + FOPTIMFLAGS='-O' + FDEBUGFLAGS='-g' +# + LD="$CC" + LDFLAGS="-bundle -Wl,-flat_namespace -undefined suppress" + LDOPTIMFLAGS='-O' + LDDEBUGFLAGS='-g' +# + POSTLINK_CMDS='nmedit -s $TMW_ROOT/extern/lib/$Arch/$MAPFILE $mex_file' +#---------------------------------------------------------------------------- + ;; + esac +############################################################################# +# +# Architecture independent lines: +# +# Set and uncomment any lines which will apply to all architectures. +# +#---------------------------------------------------------------------------- +# CC="$CC" +# CFLAGS="$CFLAGS" +# COPTIMFLAGS="$COPTIMFLAGS" +# CDEBUGFLAGS="$CDEBUGFLAGS" +# CLIBS="$CLIBS" +# +# FC="$FC" +# FFLAGS="$FFLAGS" +# FOPTIMFLAGS="$FOPTIMFLAGS" +# FDEBUGFLAGS="$FDEBUGFLAGS" +# FLIBS="$FLIBS" +# +# LD="$LD" +# LDFLAGS="$LDFLAGS" +# LDOPTIMFLAGS="$LDOPTIMFLAGS" +# LDDEBUGFLAGS="$LDDEBUGFLAGS" +#---------------------------------------------------------------------------- +############################################################################# diff --git a/mfiletemplate.m b/mfiletemplate.m new file mode 100644 index 0000000..a600fd7 --- /dev/null +++ b/mfiletemplate.m @@ -0,0 +1,56 @@ +function outStr = mfiletemplate(option,filename,description) +%MFILETEMPLATE Template for new M-files +% MFILETEMPLATE(OPTION,FILENAME,DESCRIPTION) +% OPTION = { full | description | copyright } + +% Copyright 1984-2003 The MathWorks, Inc. + +if nargin < 1 + option = 'full'; +end + +if nargin < 2 + filename = ''; +end + +if nargin < 3 + description = ''; +end + +if strcmp(option,'description') + s = {[upper(filename) ' ' description]}; + +else + f = textread(which('template.m'),'%s','delimiter','\n','whitespace',''); + s = f; + + copyrightLine = ''; + + for n = 1:length(f) + str = f{n}; + str = strrep(str,'$filename',filename); + str = strrep(str,'$FILENAME',upper(filename)); + str = strrep(str,'$description',description); + if ~isempty(strfind(str,'$date')) + match = regexp(str,'\$date\((.*?)\)','match','once'); + if ~isempty(match) + token = regexp(match,'\((.*?)\)','tokens','once'); + str = strrep(str,match,datestr(now,token{1})); + end + end + if ~isempty(strfind(lower(str),'copyright')) + copyrightLine = str; + end + s{n} = str; + end + + if strcmp(option,'copyright') + s = {copyrightLine}; + end +end + +str = sprintf('%s\n',s{:}); +% Pull off the last \n +str(end) = []; +outStr = str; + diff --git a/minf2struct.m b/minf2struct.m new file mode 100644 index 0000000..337354d --- /dev/null +++ b/minf2struct.m @@ -0,0 +1,64 @@ +function [x]=minf2struct(minf) +% minf2struct - reads .minf file (Brainvisa) and create a structure +% +% [s]=minf2struct(minf) +% +% Input: +% minf: filename to read (e.g. 'toto.tex.minf') +% +% Output: +% s : matlab structure +x=[]; +fid=fopen(minf, 'rt'); +t=[]; +% t=fgetl(fid); % skip 1st line +while not(feof(fid)) + t=fgetl(fid); + t=strrep(t,'attributes = {', ''); + t=strrep(t,'}', ''); + t=rmspaces(t); + [f,v]=strtok(t,':'); + while not(isempty(v)) + [f2,v2]=strtok(v,':'); + sp=findstr('''', f); + f=f(sp(1)+1:sp(2)-1); + sp=findstr('''', f2); + if not(isempty(v2)) + v=v(2:(sp(end-1)-1)); + else + v=v(2:end); + end + x=setfield(x, f, eval([ v ';'])); + v=v2; + if not(isempty(v2)) + f=f2(sp(end-1):end); + else + f=[]; + end + end + + +end +fclose(fid); + + +function [t]=rmspaces(t) +i=1; +if length(t)==0 + return +end +while t(1)==' ' + t(1)=[]; + if length(t)==0 + break + end +end +if length(t)==0 + return +end +while t(end)==' ' + t(end)=[]; + if length(t)==0 + break + end +end diff --git a/misc/biblio/pubmed1.m b/misc/biblio/pubmed1.m new file mode 100644 index 0000000..e000cb3 --- /dev/null +++ b/misc/biblio/pubmed1.m @@ -0,0 +1,30 @@ + +t=urlread('http://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?db=pubmed&term=Nature[ta]+brain[TIAB]+"Journal+Article"[PT]&retmax=500'); + +[a]=strread(t,'%s', 'delimiter','\n'); +a=a(strmatch('', a)); +a=strrep(a, '',''); +a=strrep(a, '',''); +%a=strvcat(a); +%a=strrep(vectvec(transpose(a))','', '');a=strrep(a,'', ','); + +au=[]; + +for i=1:length(a) + fprintf('%d/%d\n',i,length(a)) +% q=a{i}(5:end-5); +q=a{i}; +rs{i}=urlread(['http://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?db=pubmed&retmode=text&rettype=medline&id=' q]); +r=strread(rs{i}, '%s', 'delimiter','\n\n'); +b=strrep(r(strmatch('AU -',r)), 'AU - ',''); +au=[au, {b(:)'} ]; +%r=urlread(['http://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?db=pubmed&retmode=text&rettype=medline&id=' q]) +% b=findstr(r,char([13 10 13])); +% b=r(b(2)+4:b(3)-2) +% au=[au ', ' b]; +end +o=[au{:}] +[b,i,j]=unique(o) +[m,mm]=histk(j) +b(m(1:10)) +mm(1:10) diff --git a/misc/biblio/pubmed2.m b/misc/biblio/pubmed2.m new file mode 100644 index 0000000..63f4cd3 --- /dev/null +++ b/misc/biblio/pubmed2.m @@ -0,0 +1,45 @@ +f=textread('d:\ndiaye\labo\publis\these\update biblio.txt','%s', 'delimiter', '\n'); +f=f(21:end) + +for i=1:length(f) + f{i} + t=urlread(['http://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?db=pubmed&term=' strrep(f{i}, '] "', ']+"')]); + + [a]=strread(t,'%s', 'delimiter','\n'); + a=a(strmatch('', a)); + a=strrep(a, '',''); + a=strrep(a, '',''); + %a=strvcat(a); + %a=strrep(vectvec(transpose(a))','', '');a=strrep(a,'', ','); + au=[]; + if length(a)==1 + for j=1:length(a) + fprintf('%d/%d\n',j,length(a)) + q=a{j}; + rs{j}=urlread(['http://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?db=pubmed&retmode=text&rettype=medline&id=' q]); + return + end + end + +end + +au=[]; + +for i=1:length(a) + fprintf('%d/%d\n',i,length(a)) + % q=a{i}(5:end-5); + q=a{i}; + rs{i}=urlread(['http://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?db=pubmed&retmode=text&rettype=medline&id=' q]); + r=strread(rs{i}, '%s', 'delimiter','\n\n'); + b=strrep(r(strmatch('AU -',r)), 'AU - ',''); + au=[au, {b(:)'} ]; + %r=urlread(['http://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?db=pubmed&retmode=text&rettype=medline&id=' q]) + % b=findstr(r,char([13 10 13])); + % b=r(b(2)+4:b(3)-2) + % au=[au ', ' b]; +end +o=[au{:}] +[b,i,j]=unique(o) +[m,mm]=histk(j) +b(m(1:10)) +mm(1:10) diff --git a/misc/biblio/repairbib.m b/misc/biblio/repairbib.m new file mode 100644 index 0000000..99b8049 --- /dev/null +++ b/misc/biblio/repairbib.m @@ -0,0 +1,42 @@ +% re-fetch biblio data rom medline +t=textread('d:\ndiaye\labo\jabbib.bib', '%s', 'delimiter', '\n'); + +return +fid=fopen('d:\ndiaye\labo\jabbib.bib','rt') +t=textscan(fid, '%s'); +fclose(fid) + + + +return + + +t=urlread('http://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi?db=pubmed&term=Nature[ta]+brain[TIAB]+"Journal+Article"[PT]&retmax=500'); + +[a]=strread(t,'%s', 'delimiter','\n'); +a=a(strmatch('', a)); +a=strrep(a, '',''); +a=strrep(a, '',''); +%a=strvcat(a); +%a=strrep(vectvec(transpose(a))','', '');a=strrep(a,'', ','); + +au=[]; + +for i=1:length(a) + fprintf('%d/%d\n',i,length(a)) +% q=a{i}(5:end-5); +q=a{i}; +rs{i}=urlread(['http://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?db=pubmed&retmode=text&rettype=medline&id=' q]); +r=strread(rs{i}, '%s', 'delimiter','\n\n'); +b=strrep(r(strmatch('AU -',r)), 'AU - ',''); +au=[au, {b(:)'} ]; +%r=urlread(['http://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?db=pubmed&retmode=text&rettype=medline&id=' q]) +% b=findstr(r,char([13 10 13])); +% b=r(b(2)+4:b(3)-2) +% au=[au ', ' b]; +end +o=[au{:}] +[b,i,j]=unique(o) +[m,mm]=histk(j) +b(m(1:10)) +mm(1:10) diff --git a/misc/cdf.m b/misc/cdf.m new file mode 100644 index 0000000..4f921fa --- /dev/null +++ b/misc/cdf.m @@ -0,0 +1,49 @@ +function [t] = cdf(a) + +% Computes the cumulative distribution function of the histogram of image "a". +% How to use: +% (1) Read the image into the workspace: Ex, a = imread('image.tif'); +% (2) Define the variable to use for the cumm dist function: Ex, s = cdf(a); +% NOTE: The output is normalized to the range [0,1]. + +% Get histogram of image + + h=imhist(a); %This MATLAB function gives a 256X1 histogram if "a" is uint8. + + m=length(h); + +%First normalize the histogram to make sure that its area is unity (i.e., that it is a valid pdf). + + sum=0; + + for k=1:m; + sum=sum + h(k); + end + + coeff=1/sum; + +%Multiply each component of h by coeff + + h = h.*coeff; + +%Now compute the pdf + + for k=1:m; + sum=0; + for j=1:k; + sum=sum + h(j); + end + t(k)=sum; + end + +% Convert to a column vector + + t=t'; + +% End of function + + + + + + \ No newline at end of file diff --git a/misc/compute_ins.m b/misc/compute_ins.m new file mode 100644 index 0000000..e69de29 diff --git a/misc/docinfo.m b/misc/docinfo.m new file mode 100644 index 0000000..7d3919d --- /dev/null +++ b/misc/docinfo.m @@ -0,0 +1,25 @@ +function docinfo; +% Adds a user defined field to a figure that contains +% a text transcript of the m-file that called this command. +% The text will be stored when the figure is stored as a fig file +% and can be recovered using the 'recallfigmfile' command. +% +% To use this function, insert the command 'docinfo' after your plot command. +% The function operates only on the active figure, so if you have more than +% one figure, you will need to insert this command for each one. + +[st,I]=dbstack; % accesses the stack of called functions + +filestring=st(2).name; +% docinfo is first on the stack, so the second file is required. + +[pathstr,name,ext,versn] = fileparts(filestring); +% identifies the mfile + +mfiletext=textread([name,ext],'%s','delimiter','\r'); +%reads the mfile as a text structure. + +atext=char(mfiletext);%convert to a string + +set(gcf,'userdata',atext);%stores as a property of the figure + diff --git a/misc/filelabel.m b/misc/filelabel.m new file mode 100644 index 0000000..6a5c458 --- /dev/null +++ b/misc/filelabel.m @@ -0,0 +1,34 @@ +function filelabel; +% This program labels a figure with the name of the m.file +% which produced the plot as well as the date and time it +% was created. +% +% To use this function, insert the command 'filelabel' after your plot command. +% The function operates only on the active figure, so if you have more than +% one figure, you will need to insert this command for each figure. + +[st,I]=dbstack; % accesses the stack of called functions +filestring=st(2).name; % the first m file is this one - labelfig. +% the second item in the stack would refer to the function +% that called this program + +[pathstr,name,ext,versn] = fileparts(filestring);% allows elimination of the path +% string, since this is assumed to be either extraneous or transient information. +% If the full path name is desired, then the first 'tit' line should be commented out +% and the second one used. + +tit=[name,ext,' ',datestr(now)]; +%tit=[filestring,' ',datestr(now)]; + + +set(gca,'position',[0.1300 0.200 0.775 0.7250]); +% the set command raises the bottom of the axes to allow for labeling + +text(.5,-.2,tit,'interpreter','none','units','normalized','VerticalAlignment','top',... + 'HorizontalAlignment','center','clipping','off'); + + + + + + diff --git a/misc/gmail/gmailclient.m b/misc/gmail/gmailclient.m new file mode 100644 index 0000000..60bfe81 --- /dev/null +++ b/misc/gmail/gmailclient.m @@ -0,0 +1,4 @@ +function [c] = gmailclient(username, password) +url = 'https://www.google.com/accounts/ServiceLoginBoxAuth'; +postdata =sprintf( 'continue=https://gmail.google.com/gmail&service=mail&Email=%s&Passwd=%s&submit=null', username, password) + c = p.read() \ No newline at end of file diff --git a/misc/hex_to_uint64.m b/misc/hex_to_uint64.m new file mode 100644 index 0000000..0162eec --- /dev/null +++ b/misc/hex_to_uint64.m @@ -0,0 +1,56 @@ +function m = hex_to_uint64(h) +%HEX_TO_UINT64 Convert hexadecimal string to uint64 number. +% +% HEX_TO_UINT64(H) converts the hexadecimal string H and returns the +% corresponding uint64 numbers. Each row in H, representing one output +% value, must only contain characters in the set '0123456789abcdefABCDEF'. +% +% For example +% +% hex_to_uint64(['0000000000000000' +% '0000000000000001' +% '0000000000000002' +% '7ffffffffffffffd' +% '7ffffffffffffffe' +% '7fffffffffffffff']) +% +% returns +% +% [ 0 +% 1 +% 2 +% 9223372036854775805 +% 9223372036854775806 +% 9223372036854775807] + +% Author: Peter John Acklam +% Time-stamp: 2004-09-22 18:43:25 +0200 +% E-mail: pjacklam@online.no +% URL: http://home.online.no/~pjacklam + + % Check number of input arguments. + error(nargchk(1, 1, nargin)); + + % Check type and size of input argument. + if ~ischar(h) + error('Argument must be a character array.'); + end + + hs = size(h); + hd = ndims(h); + if (hd > 2) || (hs(2) ~= 16) + error('Input must be a 2D matrix with 16 columns.'); + end + + if any( ( (h(:) < '0') | ('9' < h(:)) ) ... + & ( (h(:) < 'A') | ('F' < h(:)) ) ... + & ( (h(:) < 'a') | ('f' < h(:)) ) ); + error('Invalid hexadecimal string.'); + end + + % Convert to the output data type. + n = uint64(reshape(sscanf(h, '%1x'), hs)) + m = uint64(n(:,1)); + for i = 2:16 + m = bitor(bitshift(m, 4), n(:,i)); + end diff --git a/misc/hypercube.m b/misc/hypercube.m new file mode 100644 index 0000000..ee4a67a --- /dev/null +++ b/misc/hypercube.m @@ -0,0 +1,91 @@ +function hypercube(d,p) +% Draws hypercube projections + +% Ref: +% http://www4.ncsu.edu/unity/lockers/users/f/felder/public/kenny/papers/4dplots.html + + +d = 4 +p = 3 +if p<1|d<1 + error +end +if p>3 + error 'cannot plot beyond 3d' +end + +%vertices +nv = 2^d +v = zeros(nv,d); +for i=1:nv + v(i,:)=bitget(i-1,1:d); +end +colv = rand(nv,3); +%edges +e=[]; +for i=1:nv + for j=i+1:nv + if sum(abs(diff([v(i,:);v(j,:)])))==1 + e=[e; i j]; + end + end +end +ne=size(e,1); +close all +hf = figure; +ha(1) = subplot(2,4,[3]); +ha(2) = subplot(2,4,[4]); +ha(3) = subplot(2,4,[7]); +ha(4) = subplot(2,4,[8]); +ha(5) = subplot(2,4,[1 2 5 6]); +set(ha,'XTick',[],'YTick',[],'Ztick', [], 'XLim',[-1 2], 'Ylim', [-1 2] , 'Zlim', [-1 2] ) +D='XYZU'; + +xy(:,1:p) = nchoosek(1:d,p); +xy(:,p+1:3)=0; +for j=1:d + axes(ha(j)) + xlabel(D(xy(j,1))) + ylabel(D(xy(j,2))) + view(2) + if xy(j,3)>0 + zlabel(D(xy(j,3))); + view(3) + end + axis square + grid on + box on + +end + + +for j=1:d + axes(ha(j)) + hold on + for i=1:nv% randperm(nv) + switch(p) + case 1 + case 2 + hp(j,i)=plot(v(:,xy(j,1)),v(:,xy(j,2)),'.','MarkerSize',12, 'color', colv(i,:)); + case 3 + hp(j,i)=plot3(v(:,xy(j,1)),v(:,xy(j,2)),v(:,xy(j,3)),'.','MarkerSize',12, 'color', colv(i,:)); + end + end + for i=1:ne + switch(p) + case 1 + case 2 + he(j,i)=line(v(e(i,:),xy(j,1)),v(e(i,:),xy(j,2))); + + case 3 + he(j,i)=line(v(e(i,:),xy(j,1)),v(e(i,:),xy(j,2)),v(e(i,:),xy(j,3))); + end + end + hold off +end +set(ha, 'XTick',[], 'YTick',[], 'XLim',[-1 2], 'Ylim', [-1 2] ) + + + +function v=rotate(v,r,a) + diff --git a/misc/ins_c.m b/misc/ins_c.m new file mode 100644 index 0000000..80f03bd --- /dev/null +++ b/misc/ins_c.m @@ -0,0 +1,34 @@ +function [I] = ins_c(prenoms, datedenaissance, nir) +% ins_c - Calcul de l'Identification National de Santé, calculé (INS-C) +if nargin<1 + if 1 + + prenoms = 'PHILIPPE '; + datedenaissance = '610217'; + nir = '1610275754289' + % Hashage: 73ba73767e7a53da3a85fba9929cf62b9e63d01758deb9bf11e14cc40c58ea81 + % Résultat: 0 833 910 461 279 517 589 8 + % Clé: 58 + else + prenoms = 'KARIMBABACARJOSEPH'; + datedenaissance = '790111'; + nir = '1790185191067'; + end +end + +c = [prenoms(1:10) , datedenaissance , nir]; +% SHA-256 hashing: +h=java.security.MessageDigest.getInstance('SHA-256'); +h.update(uint8(c)); +h = typecast(h.digest,'uint8'); + +h = h(1:8); +h = dec2bin(h)'; +b = h(:)'; +uint64(bin2dec(b(13:64))) +b = (uint64(bin2dec(b(1:12)) * 2^(64-12) + bin2dec(b(13:64)))) +%I = sprintf('%.1f',double(b)/10000) +%I = I(1:end-2) +%rem((b),10000) +%sprintf('%d',ans) + diff --git a/misc/isi/isi.bk.htm b/misc/isi/isi.bk.htm new file mode 100644 index 0000000..d0ca4fb --- /dev/null +++ b/misc/isi/isi.bk.htm @@ -0,0 +1,4928 @@ + + +

    Quelques facteurs d'impact (Impact Factor, ISI) + +

    En neurosciences cognitives

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Journal

    1992

    1993

    1994

    1995

    1996

    1997

    1998

    1999

    2000

    2001

    2002

    2003

    2004

    ANNU REV NEUROSCI20,42227,62917,95329,08333,62521,95223,02322,6526,67627,15224,09129.08323.143
    BEHAV BRAIN RES1,8541,4441,5631,8621,6132,342,7962,3182,2632,4732,7911.862
    BEHAV BRAIN SCI0,2970,4260,63915,62598,1188,811,27614,2517,3128,73015.6257.125
    BEHAV NEUROSCI2,7292,993,2632,9692,5282,6653,0262,7192,7512,862.7572.969
    BRAIN3,5893,8935,0354,8675,7395,3815,9527,3747,3037,477.1224.8678.201
    BRAIN COGNITION1,51,231,3731,111,0730,5660,5080,7360,630,7911.0931.110
    CEREB CORTEX5.322
    CLIN NEUROPHYSIOL1,6721,9222,120
    CLIN NEUROSCI0,6561,3331,3531,751,0141,7811.333
    COGNITIVE BRAIN RES0,882,2221,731,5762,7552,3332,7332,8842,4042.222
    COGNITIVE NEUROPSYCH2,92,4922,18
    COGN NEUROPSYCHOL3,3973,391
    COGNITIVE PSYCHOL3,7374,1673,4633,7114,059
    CURR OPIN NEUROBIOL5,3457,0797,3568,8549,2879,2779,99510,7185.3457.937
    ELECTROEN CLIN NEURO1,8721,6991,6171,8942,0272,42,452,8613,327N/AN/A1.894
    EUR J NEUROSCI4,824,564,9514,9214,3033,9473,823,8993,8623,9194,1634.921
    EXP BRAIN RES2,3442,4092,2271,9672,0491,8982,0182,2462,1372,362,3001.967
    HUM BRAIN MAPP10,0494,7385,285,1635,6885.076
    INT J NEUROSCI0,5270,6220.579
    J COGNITIVE NEUROSCI3,5714,0634,3833,6794,8445,225,7935,1156,7366.096
    J NEUROSCI7,168,0358,6578,2057,9557,9158,4038,9558,5028,1788.0458.317.907
    NAT NEUROSCI8,86312,63615,66814.85715.1416.980
    NAT REV NEUROSCI014,35324.04727.0121.225
    NEUROIMAGE2,7860,7781,6174,3115,6118,936,8577,8795.624
    NEURON15,58917,25618,34816,61916,95315,82116,50516,78215,08114,15313.84614.1114.439
    NEUROREPORT2,2773,0792,572,4872,2622,5912,6822,6962,3742.265
    NEUROSCI LETT2,4192,6452,7032,3182,091,7681,9342,0852,0912,212.100
    Q J EXP PSYCHOL-A1,9481,5611,4521,281.722
    Q J EXP PSYCHOL-B1,31,1321,2751,1281,3491,4191,221,33311,41.256
    TRENDS COGN SCI11,6608.1297.992
    TRENDS NEUROSCI15,42617,29920,19419,97217,75517,08418,46319,92517,41716,47514.47412.6314.794
    + + +


    +

    DIVERS

    +


    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/misc/isi/isi.htm b/misc/isi/isi.htm new file mode 100644 index 0000000..d0ca4fb --- /dev/null +++ b/misc/isi/isi.htm @@ -0,0 +1,4928 @@ + + +

    Quelques facteurs d'impact (Impact Factor, ISI) + +

    En neurosciences cognitives

    + +

    Journal

    1992

    1993

    1994

    1995

    1996

    1997

    1998

    1999

    2000

    2001

    2002

    NATURE22,13922,32625,46627,07428,41727,36828,83329,49125,81427,955
    SCIENCE20,96721,07422,06721,91123,60524,67624,38624,59523,87223,329
    P NATL ACAD SCI USA10,4810,32510,66710,5210,2449,049,82110,2610,7891,896
    P ROY SOC LOND B BIO3,0393,1372,792,5892,8672,8733,0332,7553,0373,192
    PHILOS T ROY SOC B2,1151,7682,192,2812,8322,5122,8862,653,5163,66
    ACTA NEUROBIOL EXP0,4130,5050,5510,480,560,7660,50,6310,767
    ACTA NEUROCHIR0,8220,7280,7480,6560,4980,6230,7481,040,8170,957
    ACTA NEUROL BELG0,3880,1630,2290,3330,3290,2530,2260,2750,521
    ACTA NEUROL SCAND1,1220,9771,1331,1421,0680,9021,1081,3251,3041,64
    ACTA NEUROPATHOL2,942,3952,1682,5462,9462,2992,3362,4022,4462,165
    ACTA NEUROPSYCHIATR0,0890,2980,0360,338
    ADV NEUROL0,9681,142
    AKTUEL NEUROL0,2930,1960,3230,2770,2350,240,2750,3140,2960,356
    AM J ELECTRONEUROD T0,0770,0940,0650,53
    AM J NEURORADIOL2,0591,8451,7331,5561,751,7071,8692,3582,1262,24
    ANN NEUROL6,327,0357,6246,6998,7159,5139,4558,3218,488,481
    ANNU REV NEUROSCI20,42227,62917,95329,08333,62521,95223,02322,6526,67627,152
    ARCH CLIN NEUROPSYCH0,750,8421,3950,988
    ARCH NEUROL-CHICAGO3,5173,1643,8514,263,7783,7793,3753,8634,3934,53
    ARQ NEURO-PSIQUIAT0,1410,1730,1970,228
    AUDIOL NEURO-OTOL2,0182,392,532
    AUDIT NEUROSCI2,461,929
    AUTON NEUROSCI-BASIC00,930
    BEHAV NEUROL0,250,2630,2560,333
    BEHAV NEUROSCI2,7292,993,2632,9692,5282,6653,0262,7192,7512,86
    BRIT J NEUROSURG0,9380,5340,5330,5460,5020,5380,5740,5390,563
    CAN J NEUROL SCI1,1811,531,7121,5441,0331,171,0741,2921,5041,317
    CELL MOL NEUROBIOL2,4122,441,6431,3491,6861,9681,961,7522,0932,12
    CESK SLOV NEUROL N0,0290,0650,0590,41
    CHILD NEUROPSYCHOL0,3140,9410,60,565
    CLIN NEUROL NEUROSUR0,3530,3260,4890,5940,6190,6130,4060,5640,6190,595
    CLIN NEUROPATHOL1,0491,3180,840,9350,7470,6360,9060,9681,0120,553
    CLIN NEUROPHARMACOL1,5682,1872,0711,5321,2511,6041,6362,0161,9431,533
    CLIN NEUROPHYSIOL1,6721,922
    CLIN NEUROPSYCHOL0,6740,5610,9480,551
    CLIN NEUROSCI0,6561,3331,3531,751,0141,781
    COGN NEUROPSYCHOL3,397
    COGNITIVE NEUROPSYCH2,92,4922,18
    CRIT REV NEUROBIOL7,7785,750
    CRIT REV NEUROSURG0,0940,2750,2330,227
    CURR OPIN NEUROBIOL5,3457,0797,3568,8549,2879,2779,995
    CURR OPIN NEUROL0,4730,9411,3951,8691,8552,4972,9933,1764,35
    DEV MED CHILD NEUROL1,1021,2081,171,1771,7781,5631,3161,4711,781,988
    DEV NEUROPSYCHOL1,361,0830,8710,914
    DEV NEUROSCI-BASEL0,7271,0380,6130,7561,2211,5961,672,1532,5231,93
    ELECTROEN CLIN NEURO1,8721,6991,6171,8942,0272,42,452,8613,327
    EUR J NEUROL0,6410,6080,670,9521,358
    EUR J NEUROSCI4,824,564,9514,9214,3033,9473,823,8993,8623,919
    EUR NEUROL1,3511,5630,8990,9151,2571,221,051,37911,179
    EUR NEUROPSYCHOPHARM1,9332,3281,6241,8521,7782,3872,0452,437
    EXP NEUROL3,1442,9582,8033,4513,6043,452,8633,263,8583,53
    FOLIA NEUROPATHOL0,2110,3380,2580,471
    FORTSCHR NEUROL PSYC0,430,5240,6170,670,5870,5170,4410,6020,6360,593
    FRONT NEUROENDOCRIN6,2616,0458,2387,875,7787,1117,257,8268,37515
    FUNCT NEUROL0,4120,5310,710,463
    GIORN NEUROPSI EVOL0,0310
    GIORN NEUROPSICOFARM000
    INT J DEV NEUROSCI1,7481,3391,5571,6481,3521,4061,4971,4251,5832,156
    INT J NEUROPSYCHOPH1,8331,3232,779
    INT J NEURORADIOL0,4150,3590,1390,238
    INT J NEUROSCI0,5270,622
    INT REV NEUROBIOL4,86,5881,8282,43,52,9441,78
    INTERV NEURORADIOL0,5850,441
    ITAL J NEUROL SCI0,1880,1980,3390,2160,2610,4180,4210,3490,1620,635
    J CHEM NEUROANAT3,3541,6171,9432,0921,5691,6672,7112,5522,1411,649
    J CHILD NEUROL1,3471,1320,920,9240,8970,9180,9291,0951,1341,393
    J CLIN EXP NEUROPSYC1,8142,8212,1561,5851,2011,3171,3671,2651,0671,14
    J CLIN NEUROPHYSIOL1,5831,5891,4251,1552,1641,4591,5841,9152,1732,538
    J CLIN NEUROSCI0,3850,320,2620,2920,1440,1780,392
    J COGNITIVE NEUROSCI3,5714,0634,3833,6794,8445,225,7935,1156,736
    J COMP NEUROL3,5553,6573,4683,5843,7483,7583,4763,7643,7723,515
    J COMPUT NEUROSCI2,6112,6222,2862,3462,596
    J INT NEUROPSYCH SOC2,3762,340
    J MOL NEUROSCI2,3512,4671,8751,4581,5222,0771,9121,6091,7651,688
    J NEUROBIOL2,8132,4094,1184,5684,8062,8142,8713,333,4653,34
    J NEUROCHEM4,2154,2234,5254,8524,2194,2344,6514,9064,94,834
    J NEUROCYTOL2,4912,3842,562,5362,6791,9732,1541,861,2311,776
    J NEUROENDOCRINOL2,3912,6162,5392,9892,8562,5632,8322,3772,5982,58
    J NEUROGENET1,9092,3642,51,6671,2350,9571,3571,6921,9381,125
    J NEUROIMAGING0,8350,6521,0440,9740,9420,926
    J NEUROIMMUNOL2,9642,9553,1883,6393,0832,8453,2853,2333,3553,342
    J NEUROL1,7361,791,8491,5811,9441,9762,32,8462,0612,653
    J NEUROL NEUROSUR PS2,6962,2612,5342,5042,933,0412,9382,7352,8463,24
    J NEUROL ORTH MED S0,0160,4
    J NEUROL REHABIL0,220,2980,385
    J NEUROL SCI2,3852,1651,7761,8671,7351,4531,8381,6851,6781,986
    J NEUROLINGUIST0,3780,9090,5140,722
    J NEURO-ONCOL1,3411,2771,0960,7771,1111,2321,6411,6551,5811,435
    J NEURO-OPHTHALMOL0,0240,6240,3890,2730,3740,2520,778
    J NEUROPATH EXP NEUR4,9044,2735,3675,8314,7844,2535,3665,595,5655,533
    J NEUROPHYSIOL3,8744,1344,0013,5783,8343,2633,4113,9353,8553,517
    J NEUROPSYCH CLIN N1,7372,0371,5731,7951,6973,2092,141,632
    J NEURORADIOLOGY0,3270,3330,4260,3040,3770,5880,3680,5220,62
    J NEUROSCI7,168,0358,6578,2057,9557,9158,4038,9558,5028,178
    J NEUROSCI METH1,5671,5231,4421,8151,4121,3351,431,3621,4771,659
    J NEUROSCI RES3,1973,0053,6373,3772,8432,4422,8743,1263,2073,378
    J NEUROSURG2,5152,6883,2963,0122,7692,9993,3723,3462,9182,748
    J NEUROSURG ANESTH0,6380,8311,4331,6521,2371,3331,1440,9340,9370,739
    J NEUROTRAUM2,1442,8023,0153,0863,0193,4042,8773,952
    J NEUROVIROL1,8332,4571,7292,0983,3972,71
    J PSYCHIATR NEUROSCI0,9661,6962,0392,516
    JNMS-J NEUROMUSC SYS0,1110,1250,1030,111
    KLIN NEUROPHYSIOL0,3210,2030,156
    MINIM INVAS NEUROSUR0,0770,5250,6490,6920,7440,667
    MOL CELL NEUROSCI3,3072,5293,2596,086,0895,6545,7465,446
    MOL CHEM NEUROPATHOL0,6361,0381,2451,1511,0640,780,881,0941,163
    MOL NEUROBIOL0,8112,2863,2551,62,3853,4834,3885,6234,3822,4
    NAT NEUROSCI8,86312,63615,668
    NAT REV NEUROSCI014,353
    NEUROBIOL AGING3,5273,4343,1012,112,1212,8723,5173,3994,1594,49
    NEUROBIOL DIS2,6114,2254,8695,0235,3334,487
    NEUROBIOL LEARN MEM1,5742,1221,8412,8053,041,83
    NEUROCASE1,0121,2681,8711,312
    NEUROCHEM INT1,8551,5181,4342,2361,9881,7051,7812,1752,6623,4
    NEUROCHEM RES1,4321,591,5761,5771,2921,311,6771,7551,8581,638
    NEUROCHIRURGIE0,4040,4290,5050,2810,2830,3880,3090,3360,390,346
    NEUROCIRUGIA0,0570,1850,1540,13
    NEUROCOMPUTING0,2430,6090,5770,4220,4530,360,6360,534
    NEUROENDOCRINOLOGY2,8412,7022,4222,6842,4452,44133,2142,7442,144
    NEUROEPIDEMIOLOGY0,9490,78611,0471,6541,3971,3531,8031,6541,39
    NEUROGASTROENT MOTIL1,3551,51,7211,6921,9362,0812,5
    NEUROGENETICS1,872,0942,5963,69
    NEUROIMAG CLIN N AM0,6770,831,0950,957
    NEUROIMAGE2,7860,7781,6174,3115,6118,936,8577,879
    NEUROIMMUNOMODULAT0,741,6092,7011,989
    NEUROL CLIN1,1551,0421,7610,8950,9441,331,3751,5660,8961,283
    NEUROL CROATICA0,09400,032
    NEUROL INDIA0,10,0570,0920,173
    NEUROL MED-CHIR0,386
    NEUROL PSYCHIAT BR0,2160,3330,278
    NEUROL RES0,6970,8521,1421,0271,1281,10,8661,176
    NEUROL SCI00,380
    NEUROL SURG TOKYO0,0920,1580,1560,12
    NEUROLOGIST0,3120,3970,2720,22
    NEUROLOGY4,3553,994,3474,6334,6124,5264,9725,2324,7815,212
    NEUROMODULATION2,0951,2161,96
    NEUROMUSCULAR DISORD1,4771,8182,7182,3512,5822,7492,7182,547
    NEURON15,58917,25618,34816,61916,95315,82116,50516,78215,08114,153
    NEURO-OPHTHALMOLOGY0,4570,2980,3720,2890,3740,3680,3610,450,3760,176
    NEURO-ORTHOPEDICS0,2730,222
    NEUROPATH APPL NEURO2,3262,3761,9831,8892,2042,1211,5912,5752,5232,963
    NEUROPATHOLOGY0,6910,4130,4870,575
    NEUROPEDIATRICS0,9261,20,7291,0081,3891,9851,5451,6691,5971,476
    NEUROPEPTIDES1,7972,1642,0261,6010,9640,9051,241,0751,4131,959
    NEUROPHARMACOLOGY2,2461,9972,4243,6344,4583,9093,244,1764,1253,854
    NEUROPHYSIOL CLIN0,4690,350,4330,4220,3690,5320,5161,33
    NEUROPHYSIOLOGY0,1140,1770
    NEUROPHYSIOLOGY+0,0270,870
    NEUROPSY NEUROPSY BE0,9740,6880,7930,6950,50,7580,8961,347
    NEUROPSYCHIATRIE0,1250,2350,3870,135
    NEUROPSYCHOBIOLOGY0,3680,3940,9861,050,8550,8210,8460,9531,561,64
    NEUROPSYCHOL REHABIL0,9470,6090,6670,759
    NEUROPSYCHOL REV0,7271,1821,250,92
    NEUROPSYCHOLOGIA1,8852,0531,9661,8282,0592,2672,9392,6872,7783,32
    NEUROPSYCHOLOGY2,6133,1132,7022,486
    NEUROPSYCHOPHARMACOL3,6612,5212,783,5673,9364,1054,3184,8584,5794,715
    NEURORADIOLOGY1,0510,9350,90,8580,9970,7541,0231,2870,9971,97
    NEUROREHAB NEURAL RE0,190,610
    NEUROREPORT2,2773,0792,572,4872,2622,5912,6822,6962,374
    NEUROSCI BIOBEHAV R2,4962,8573,7932,992,7863,3163,5953,3825,212
    NEUROSCI LETT2,4192,6452,7032,3182,091,7681,9342,0852,0912,21
    NEUROSCI RES1,9782,0592,1412,1652,1021,5231,7751,7261,8071,77
    NEUROSCI RES COMMUN0,9691,1910,9170,871,0740,8740,7420,9890,5380,67
    NEUROSCIENCE4,3244,5824,6264,2883,9083,5943,5913,9243,5633,219
    NEUROSCIENTIST2,1742,2331,9181,42
    NEUROSURG CLIN N AM0,8710,9220,9331,2650,783
    NEUROSURG QUART0,50,7670,50,217
    NEUROSURG REV0,3720,290,2560,1560,20,1610,3270,2860,3580,478
    NEUROSURGERY1,5541,4361,671,0060,7311,1132,42,8212,8992,783
    NEUROTOXICOL TERATOL1,5711,7031,8961,5451,9791,1871,321,8222,288
    NEUROTOXICOLOGY0,8951,3841,511,3631,2841,7281,2051,2821,741,576
    NEUROUROL URODYNAM0,7271,1920,7161,3432,1222,082,4182,4381,9682,266
    PEDIATR NEUROL0,9171,2561,0341,0780,8250,9180,9211,1191,0071,11
    PEDIATR NEUROSURG0,4490,3290,5320,7040,770,6560,7960,7350,8110,787
    PERSPECT DEV NEUROBI2,7231,2622,3171,581
    PROG NEUROBIOL7,8097,48186,1845,88855,6887,099,9339,377
    PROG NEURO-PSYCHOPH0,9460,8811,1410,9140,8870,8191,1141,3891,0781,58
    PSYCHIAT CLIN NEUROS0,1380,1910,3820,3480,4520,548
    PSYCHIAT RES-NEUROIM1,2861,9771,6671,7071,9322,1761,3292,191,9192,354
    PSYCHONEUROENDOCRINO1,5711,4861,4831,6992,2541,9921,8692,0453,0083,369
    RESTOR NEUROL NEUROS2,6091,4350,9150,561,1171,1960,50,9110,678
    REV ECUAT NEUROL0,17200,1060,4
    REV NEUROL0,8130,8820,6550,5140,5351,111,451,0130
    REV NEUROL-FRANCE0,6920,598
    REV NEUROLOGIA0,2650,2560,26
    REV NEUROPSYCHOL0,2110,1750,2860,262
    REV NEUROSCI1,7593,3873,43,794
    RIV NEURORADIOL0,0630,1060,0510,128
    SEMIN NEUROL0,6630,5650,4240,5640,6950,5110,671,3661,6351,49
    SEMIN NEUROSCI1,9892,1722,0833,3253,5962,71,667
    STEREOT FUNCT NEUROS0,2840,4460,6570,672
    SURG NEUROL0,7140,6850,6250,5290,3420,3280,7241,041,0180,936
    TECH NEUROSURG0,2150,290
    TRENDS NEUROSCI15,42617,29920,19419,97217,75517,08418,46319,92517,41716,475
    VISUAL NEUROSCI2,162,2122,2722,1372,491,8872,1862,2042,1492,351
    ZBL NEUROCHIR0,2220,40,9390,688
    ACTA NEUROPSYCHIATR0,0890,2980,0360,338
    ACTA PSYCHIAT SCAND1,31,1421,4951,4181,5951,5881,5541,6191,7742,17
    AM J GERIAT PSYCHIAT2,436
    AM J ORTHOPSYCHIAT0,921,0620,8591,0161,0541,7182,2671,4441,9391,55
    AM J PSYCHIAT4,8244,9544,575,126,0696,5015,9396,3416,5776,913
    ANN MED-PSYCHOL0,1050,0550,1120,1440,1920,210,2080,2380,236
    ANNU REV PSYCHOL5,7255,556,8816,8215,4394,8416,3957,5455,8515,979
    ARCH CLIN NEUROPSYCH0,750,8421,3950,988
    ARCH GEN PSYCHIAT8,2279,50511,41611,15511,50910,7519,39810,95211,77811,981
    AUST NZ J PSYCHIAT0,7840,6240,6350,6450,8570,5740,8471,1971,2650,858
    BIOL PSYCHIAT2,572,6012,1482,3522,4842,2542,4053,3194,2695,55
    BIOL PSYCHOL1,0261,0561,81,4741,51,778
    BRIT J MED PSYCHOL0,6710,4790,4590,5920,9390,6670,7020,8810,5620,674
    BRIT J PSYCHIAT2,3092,3192,862,9513,3933,2653,5034,0934,8274,143
    CAN J PSYCHIAT0,70,5430,5470,7180,8321,1511,0581,4621,6231,624
    CHILD NEUROPSYCHOL0,3140,9410,60,565
    CHILD PSYCHIAT HUM D0,2130,2340,3540,5320,3410,4880,4520,3780,4220,625
    CLIN NEUROPSYCHOL0,6740,5610,9480,551
    COGN NEUROPSYCHOL3,397
    COGNITIVE NEUROPSYCH2,92,4922,18
    COGNITIVE PSYCHOL3,7374,1673,4633,711
    COMPR PSYCHIAT1,4451,0711,2731,6221,521,2461,2341,6881,41,28
    DEV NEUROPSYCHOL1,361,0830,8710,914
    DEV PSYCHOBIOL1,1441,1881,4211,0411,2031,4351,5961,3121,3221,286
    EDUC PSYCHOL MEAS0,3240,2280,3680,3170,3160,4440,6180,6230,6080,789
    EUR NEUROPSYCHOPHARM1,9332,3281,6241,8521,7782,3872,0452,437
    EUR PSYCHIAT0,5440,4970,3940,5910,7481,72
    EXP CLIN PSYCHOPHARM1,511,251,7471,726
    GEN HOSP PSYCHIAT0,7390,921,1161,0281,3371,2981,1441,41,5122,65
    HUM PSYCHOPHARM CLIN0,8180,8540,8690,850,9410,5840,6091,1030,968
    INT CLIN PSYCHOPHARM0,750,8161,3642,2271,6381,9261,5571,0962,0762,295
    INT J GERIATR PSYCH1,5631,4951,778
    INT J NEUROPSYCHOPH1,8331,3232,779
    INT J PSYCHIAT MED0,7970,5520,7410,9181,0820,9440,631,2071,0330,714
    INT J PSYCHOPHYSIOL0,870,6840,8750,5851,0890,7031,1481,8291,4891,747
    INT J SPORT PSYCHOL0,2760,4790,8040,386
    J APPL SPORT PSYCHOL0,970
    J CHILD ADOL PSYCHOP1,3881,9331,6092,11,9821,754
    J CLIN PSYCHIAT2,5542,7693,133,3374,2934,0034,0734,1724,4544,735
    J CLIN PSYCHOPHARM3,6793,3663,4964,9635,35,0945,3385,7385,0524,13
    J COMP PSYCHOL1,2021,4041,5171,4091,6021,5631,7441,571,4771,663
    J EXP PSYCHOL ANIM B1,7752,562,5161,1591,9831,3971,2671,441,3651,95
    J EXP PSYCHOL HUMAN2,4062,3322,2472,498
    J EXP PSYCHOL LEARN2,8383,2052,7532,21
    J GERIATR PSYCH NEUR0,8021,2721,2220,8810,9091,263
    J GERONTOL B-PSYCHOL1,5542,3311,2241,5431,1021,594
    J INT NEUROPSYCH SOC2,3762,340
    J MATH PSYCHOL1,4210,8640,80,7660,780,7920,660,6921,0561,195
    J NEUROPSYCH CLIN N1,7372,0371,5731,7951,6973,2092,141,632
    J PSYCHIAT RES1,4031,0181,0851,5312,1591,6051,3622,1522,332,776
    J PSYCHIATR NEUROSCI0,9661,6962,0392,516
    J PSYCHOPHARMACOL1,3081,8881,4261,3811,4662,8272,3282,642
    J PSYCHOPHYSIOL0,5930,5370,6850,587
    J PSYCHOSOM OBST GYN0,4240,6480,3020,6270,5560,60,6060,7170,5290,67
    J PSYCHOSOM RES1,2220,9741,1021,1611,5841,0061,1291,5731,4461,717
    MOL PSYCHIATR2,1624,7567,9428,9276,25
    NEUROL PSYCHIAT BR0,2160,3330,278
    NEUROPSYCHIATRIE0,1250,2350,3870,135
    NEUROPSYCHOBIOLOGY0,3680,3940,9861,050,8550,8210,8460,9531,561,64
    NEUROPSYCHOL REHABIL0,9470,6090,6670,759
    NEUROPSYCHOL REV0,7271,1821,250,92
    NEUROPSYCHOLOGIA1,8852,0531,9661,8282,0592,2672,9392,6872,7783,32
    NEUROPSYCHOLOGY2,6133,1132,7022,486
    NEUROPSYCHOPHARMACOL3,6612,5212,783,5673,9364,1054,3184,8584,5794,715
    PERCEPT PSYCHOPHYS1,2521,492
    PHARMACOPSYCHIATRY1,1331,41,61,1171,2182,0532,3042,8032,39
    PROG NEURO-PSYCHOPH0,9460,8811,1410,9140,8870,8191,1141,3891,0781,58
    PSYCHIAT CLIN NEUROS0,1380,1910,3820,3480,4520,548
    PSYCHIAT RES1,8751,4741,6921,4731,4891,3271,4241,5511,5571,775
    PSYCHIAT RES-NEUROIM1,2861,9771,6671,7071,9322,1761,3292,191,9192,354
    PSYCHIATR GENET1,862,0162,6091,257
    PSYCHIATR SERV0,9351,4661,571,7471,7951,589
    PSYCHIATRY0,6230,6670,9031,260,7310,7681,2550,9440,8271,2
    PSYCHOBIOLOGY2,0711,7911,5380,9521,2321,0510,8151,2821,1941,652
    PSYCHOL BULL4,9585,1976,6976,9666,5916,0386,3467,796,9136,87
    PSYCHOL MED2,1772,6632,4332,7152,8153,0173,1243,3893,4123,119
    PSYCHOL REV6,4856,57,1875,0585,217,068,2396,8036,0695,756
    PSYCHOMETRIKA0,7870,6360,6230,7680,8330,6560,9330,8240,8980,85
    PSYCHONEUROENDOCRINO1,5711,4861,4831,6992,2541,9921,8692,0453,0083,369
    PSYCHO-ONCOL1,388
    PSYCHOPATHOLOGY0,4330,3270,4460,4510,7070,5750,4740,4250,5470,469
    PSYCHOPHARMACOL BULL1,6711,7291,4391,4311,5711,8162,592,2452,809
    PSYCHOPHARMACOLOGY2,7692,52,5492,8822,5992,9333,0322,9182,8043,145
    PSYCHOPHARMAKOTHERAP0,1970,3010,171
    PSYCHOPHYSIOLOGY2,6382,3492,6592,9482,8272,7742,4323,0063,1063,35
    PSYCHOSOM MED2,6932,3112,8082,9123,0313,0893,0462,6243,2462,815
    PSYCHOSOMATICS1,41,0711,1691,3212,1321,7071,5411,1741,5551,926
    PSYCHOTHER PSYCHOSOM0,3760,7521,0591,0471,5781,8052,1032,2592,3723,429
    Q J EXP PSYCHOL-A1,9481,5611,4521,28
    Q J EXP PSYCHOL-B1,31,1321,2751,1281,3491,4191,221,33311,4
    REV NEUROPSYCHOL0,2110,1750,2860,262
    SPORT PSYCHOL0,8730,9670,6490,76
    Z PSYCHOSOM MED PSYC0,3590,5760,460,4910,440,371,2090,4440,633
    BEHAV BRAIN RES1,8541,4441,5631,8621,6132,342,7962,3182,2632,473
    BEHAV BRAIN SCI0,2970,4260,63915,62598,1188,811,27614,2517,312
    BRAIN3,5893,8935,0354,8675,7395,3815,9527,3747,3037,47
    BRAIN BEHAV EVOLUT1,5491,9591,4171,5771,491,7861,5591,651,3811,635
    BRAIN BEHAV IMMUN2,7592,5242,3191,4841,951,752,1111,6882,1842,23
    BRAIN COGNITION1,51,231,3731,111,0730,5660,5080,7360,630,791
    BRAIN DEV-JPN0,7870,5760,6880,7090,5390,570,7611,3031,1551,49
    BRAIN INJURY0,880,8431,2561,0851,0170,9140,924
    BRAIN LANG1,1970,7130,9761,6581,41,5792,1161,9291,4731,38
    BRAIN PATHOL4,258,5676,4555,6634,8974,1546,4358,654
    BRAIN RES2,8652,8542,8692,6872,5262,1192,152,3022,5262,489
    BRAIN RES BULL1,6921,5991,8111,7941,6411,7081,651,9771,7581,783
    BRAIN RES PROTOC0,2820,7811,0671,284
    BRAIN RES REV9,7429,09410,20713,9627,61911,5536,256,6519,2127,72
    BRAIN TOPOGR0,5771,9061,5961,745
    COGNITIVE BRAIN RES0,882,2221,731,5762,7552,3332,7332,884
    DEV BRAIN DYSFUNCT0,1480,213
    DEV BRAIN RES2,3222,1772,2072,2461,9111,8231,6951,5351,8271,662
    EXP BRAIN RES2,3442,4092,2271,9672,0491,8982,0182,2462,1372,36
    HUM BRAIN MAPP10,0494,7385,285,1635,688
    J BRAIN RES0,3970,6710,2790,182
    METAB BRAIN DIS0,7321,1221,0290,631,751,311,53711,4111,91
    MOL BRAIN RES4,5223,8014,1343,483,4772,7422,4752,5392,6222,538
    PROG BRAIN RES1,8881,2611,4121,6821,4880,7721,951,4252,521,49
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    Journal

    1992

    1993

    1994

    1995

    1996

    1997

    1998

    1999

    2000

    2001

    2002

    2003

    2004

    ANNU REV NEUROSCI20,42227,62917,95329,08333,62521,95223,02322,6526,67627,15224,09129.08323.143
    BEHAV BRAIN RES1,8541,4441,5631,8621,6132,342,7962,3182,2632,4732,7911.862
    BEHAV BRAIN SCI0,2970,4260,63915,62598,1188,811,27614,2517,3128,73015.6257.125
    BEHAV NEUROSCI2,7292,993,2632,9692,5282,6653,0262,7192,7512,862.7572.969
    BRAIN3,5893,8935,0354,8675,7395,3815,9527,3747,3037,477.1224.8678.201
    BRAIN COGNITION1,51,231,3731,111,0730,5660,5080,7360,630,7911.0931.110
    CEREB CORTEX5.322
    CLIN NEUROPHYSIOL1,6721,9222,120
    CLIN NEUROSCI0,6561,3331,3531,751,0141,7811.333
    COGNITIVE BRAIN RES0,882,2221,731,5762,7552,3332,7332,8842,4042.222
    COGNITIVE NEUROPSYCH2,92,4922,18
    COGN NEUROPSYCHOL3,3973,391
    COGNITIVE PSYCHOL3,7374,1673,4633,7114,059
    CURR OPIN NEUROBIOL5,3457,0797,3568,8549,2879,2779,99510,7185.3457.937
    ELECTROEN CLIN NEURO1,8721,6991,6171,8942,0272,42,452,8613,327N/AN/A1.894
    EUR J NEUROSCI4,824,564,9514,9214,3033,9473,823,8993,8623,9194,1634.921
    EXP BRAIN RES2,3442,4092,2271,9672,0491,8982,0182,2462,1372,362,3001.967
    HUM BRAIN MAPP10,0494,7385,285,1635,6885.076
    INT J NEUROSCI0,5270,6220.579
    J COGNITIVE NEUROSCI3,5714,0634,3833,6794,8445,225,7935,1156,7366.096
    J NEUROSCI7,168,0358,6578,2057,9557,9158,4038,9558,5028,1788.0458.317.907
    NAT NEUROSCI8,86312,63615,66814.85715.1416.980
    NAT REV NEUROSCI014,35324.04727.0121.225
    NEUROIMAGE2,7860,7781,6174,3115,6118,936,8577,8795.624
    NEURON15,58917,25618,34816,61916,95315,82116,50516,78215,08114,15313.84614.1114.439
    NEUROREPORT2,2773,0792,572,4872,2622,5912,6822,6962,3742.265
    NEUROSCI LETT2,4192,6452,7032,3182,091,7681,9342,0852,0912,212.100
    Q J EXP PSYCHOL-A1,9481,5611,4521,281.722
    Q J EXP PSYCHOL-B1,31,1321,2751,1281,3491,4191,221,33311,41.256
    TRENDS COGN SCI11,6608.1297.992
    TRENDS NEUROSCI15,42617,29920,19419,97217,75517,08418,46319,92517,41716,47514.47412.6314.794
    + + +


    +

    DIVERS

    +


    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/misc/isi/isi.m b/misc/isi/isi.m new file mode 100644 index 0000000..48c775c --- /dev/null +++ b/misc/isi/isi.m @@ -0,0 +1,27 @@ +titles=textread('jcr_recs.txt', '%s%*[^\n]', 'delimiter', ';'); +address='http://jcr02.isiknowledge.com/JCR/JCR?RQ=IMPACT&rank=1&journal=' +%ic=zeros(length(titles),5)*NaN; +for i=i:length(titles) + titles{i} + [s,b,u] = web([address strrep(titles{i},' ', '+')]); + txt=get(b, 'Htmltext'); + p=findstr('Impact Factor', txt); + p=fliplr(p(4:end-2)); + for j=1:length(p) + z=0; + %z=findstr('Cites to recent articles', txt(p(j)+[1:3000])) + z=z+findstr('dataTable', txt(p(j)+z+[1:2000])); + ic(i,j)=str2num(strrep(strrep(strrep(txt(p(j)+z(6)+[14:19]),'<', ''), '/', ''), 'b', '')); + end + ic(i,:) + + +end + + + +return + + + +ANNU+REV+NEUROSCI \ No newline at end of file diff --git a/misc/isi/isi.mat b/misc/isi/isi.mat new file mode 100644 index 0000000000000000000000000000000000000000..c03300ec4fdd129721aaac7a8bdd8cd87bfe3794 GIT binary patch literal 10464 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 F{%`HWa!&vN literal 0 HcmV?d00001 diff --git a/misc/isi/jcr_recs.txt b/misc/isi/jcr_recs.txt new file mode 100644 index 0000000..2d87413 --- /dev/null +++ b/misc/isi/jcr_recs.txt @@ -0,0 +1,96 @@ +NATURE +SCIENCE +PLOS BIOL +ANNU REV NEUROSCI;0147-006X;8093;23.143;2.154;26;6.3; +NAT REV NEUROSCI;1471-0048;6449;21.225;3.230;74;3.0; +NAT NEUROSCI;1097-6256;16223;16.980;3.079;189;3.8; +TRENDS NEUROSCI;0166-2236;14615;14.794;1.564;110;6.9; +NEURON;0896-6273;43226;14.439;2.672;314;5.4; +ANNU REV PSYCHOL;0066-4308;3639;12.800;1.929;28;7.1; +PROG NEUROBIOL;0301-0082;7665;11.933;0.778;45;6.2; +BRAIN;0006-8950;20990;8.201;1.221;235;7.2; +ANN NEUROL;0364-5134;25960;8.097;1.827;237;7.5; +TRENDS COGN SCI;1364-6613;3580;7.992;1.012;84;4.3; +CURR OPIN NEUROBIOL;0959-4388;7660;7.937;1.069;102;5.0; +J NEUROSCI;0270-6474;93263;7.907;1.350;1233;5.6; +PSYCHOL BULL;0033-2909;15557;7.701;2.188;32;>10.0; +PSYCHOL REV;0033-295X;12753;7.145;1.356;45;>10.0; +BEHAV BRAIN SCI;0140-525X;3926;7.125;0.833;6;8.6; +MOL PSYCHIATR;1359-4184;4742;6.943;2.135;104;3.5; +NEUROSCI BIOBEHAV R;0149-7634;4707;6.346;0.788;66;6.4; +BIOL PSYCHIAT;0006-3223;17553;6.159;0.900;300;5.5; +J CEREBR BLOOD F MET;0271-678X;10182;5.673;0.826;144;6.6; +NEUROBIOL AGING;0197-4580;5501;5.516;1.240;129;5.4; +CEREB CORTEX;1047-3211;7096;5.322;1.097;134;5.0; +J COGNITIVE NEUROSCI;0898-929X;5578;5.275;0.466;148;5.1; +J NEUROPATH EXP NEUR;0022-3069;6612;5.037;0.518;112;6.9; +NEUROPSYCHOPHARMACOL;0893-133X;7448;4.941;0.839;236;4.5; +NEUROIMAGE;1053-8119;11323;4.869;0.601;531;3.5; +HUM BRAIN MAPP;1065-9471;3898;4.815;0.575;73;5.6; +BRAIN RES REV;0165-0173;5439;4.617;0.286;49;6.0; +HIPPOCAMPUS;1050-9631;3766;4.516;0.710;93;5.4; +NEUROBIOL LEARN MEM;1074-7427;1916;4.443;0.644;59;5.2; +CURR OPIN NEUROL;1350-7540;2261;4.017;0.551;78;3.9; +COGNITIVE PSYCHOL;0010-0285;3413;3.977;1.000;21;>10.0; +EUR J NEUROSCI;0953-816X;16736;3.820;0.543;727;4.7; +J NEUROPHYSIOL;0022-3077;34116;3.592;0.885;573;7.7; +NEUROSCIENCE;0306-4522;30136;3.456;0.622;662;7.0; +NEUROSCIENTIST;1073-8584;834;3.175;0.440;50;3.3; +BEHAV BRAIN RES;0166-4328;8530;2.992;0.336;307;6.3; +CLIN NEUROPHYSIOL;1388-2457;3828;2.538;0.370;311;3.6; +J EXP PSYCHOL HUMAN;0096-1523;7203;2.529;0.338;74;>10.0; +CORTEX;0010-9452;2484;2.472;0.905;42;>10.0; +MUSCLE NERVE;0148-639X;6635;2.432;0.319;182;6.9; +BRAIN RES BULL;0361-9230;7189;2.429;0.234;141;7.5; +NEUROREPORT;0959-4965;17280;2.351;0.274;566;5.8; +COGNITIVE BRAIN RES;0926-6410;2166;2.394;0.219;151;4.4; +EXP BRAIN RES;0014-4819;15613;2.304;0.328;344;9.6; +J NEUROSCI METH;0165-0270;5271;1.894;0.383;230;7.7; +NEUROPSYCHOLOGIA;0028-3932;9865;3.668;0.400;175;8.3; +EXP NEUROL;0014-4886;11916;3.369;0.598;249;6.8; +REV NEUROSCI;0334-1763;601;2.860;0.148;27;5.4; +SYNAPSE;0887-4476;4545;2.827;0.474;116;6.3; +BEHAV NEUROSCI;0735-7044;6335;2.819;0.481;154;8.4; +COGN NEUROPSYCHOL;0264-3294;1462;2.746;0.372;43;7.9; +BRAIN RES;0006-8993;58204;2.389;0.302;1037;>10.0; +J PSYCHOPHARMACOL;0269-8811;1454;2.336;0.227;66;5.3; +J PSYCHIATR NEUROSCI;1180-4882;689;2.333;0.467;30;4.8; +PSYCHOPHYSIOLOGY;0048-5772;5530;2.257;0.456;103;9.9; +EUR J NEUROL;1351-5101;1485;2.225;0.234;124;3.8; +J COMPUT NEUROSCI;0929-5313;695;2.200;0.216;37;4.8; +NEUROSCI RES;0168-0102;2988;2.155;0.336;149;6.0; +PROG NEURO-PSYCHOPH;0278-5846;2445;2.149;0.203;158;5.4; +NEUROSCI LETT;0304-3940;25138;2.019;0.248;1101;7.1; +J EXP PSYCHOL LEARN;0278-7393;5821;2.015;0.367;98;9.9; +J NEUROPSYCH CLIN N;0895-0172;1883;1.817;0.133;60;7.5; +VISION RES;0042-6989;12344;1.812;0.290;293;9.3; +BIOL PSYCHOL;0301-0511;1602;1.637;1.029;34;7.5; +BRAIN LANG;0093-934X;3625;1.614;0.431;153;8.5; +HEARING RES;0378-5955;5292;1.578;0.206;160;8.3; +INT J PSYCHOPHYSIOL;0167-8760;1860;1.563;0.333;66;5.9; +VISUAL NEUROSCI;0952-5238;2569;1.554;0.264;91;8.1; +J CLIN NEUROPHYSIOL;0736-0258;1775;1.535;0.188;48;6.9; +J PHYSIOL-PARIS;0928-4257;1079;1.505;0.150;20;7.1; +NEUROL CLIN;0733-8619;1250;1.489;0.057;53;8.5; +PERCEPT PSYCHOPHYS;0031-5117;5622;1.471;0.170;94;>10.0; +Q J EXP PSYCHOL-A;0272-4987;2248;1.426;0.197;61;9.9; +EUR NEUROL;0014-3022;2121;1.312;0.175;120;7.8; +Q J EXP PSYCHOL-B;0272-4995;578;1.289;0.706;17;9.4; +PERCEPTION;0301-0066;2805;1.271;0.155;103;9.8; +EXP AGING RES;0361-073X;516;1.250;0.167;24;9.6; +NETWORK-COMP NEURAL;0954-898X;597;1.227;0.133;15;7.7; +BRAIN RES PROTOC;1385-299X;607;1.224;0.154;39;4.8; +CLIN NEUROPSYCHOL;1385-4046;1100;1.162;0.000;41;7.8; +BRAIN COGNITION;0278-2626;2427;1.148;0.236;144;7.4; +CLIN NEUROSCI RES;1566-2772;167;1.076;0.000;37;3.1; +ACTA NEUROBIOL EXP;0065-1400;482;1.075;0.694;49;7.1; +J NEUROIMAGING;1051-2284;631;1.068;0.385;65;5.1; +NEUROCASE;1355-4794;572;0.978;0.061;49;5.2; +SOMATOSENS MOT RES;0899-0220;669;0.953;0.053;19;9.9; +SPATIAL VISION;0169-1015;644;0.905;0.120;25;7.7; +J CLIN NEUROSCI;0967-5868;671;0.834;0.057;229;3.2; +INT J NEUROSCI;0020-7454;1557;0.654;0.129;116;>10.0; +ENCEPHALE;0013-7006;582;0.413;0.013;79;8.2; +NEW ENGL J MED +LANCET +NAT MED diff --git a/misc/labnic_expe_forms_from_gmail.m b/misc/labnic_expe_forms_from_gmail.m new file mode 100644 index 0000000..e69de29 diff --git a/misc/physique1.m b/misc/physique1.m new file mode 100644 index 0000000..9c5c284 --- /dev/null +++ b/misc/physique1.m @@ -0,0 +1,90 @@ +% Cinématique du solide avec frottements + +% conditions initiales + +% solide +m=5; % Kg +V=0.1; % m^3 +alpha=0; % angle z par rapport à x (degrés) +betha=0; % angle x par rapport à y (degrés) +v0=40000; % Km/h; ratio pour exprimer en m/s= 10/36 +p=[0 0 500000]; % position du solide en metres +pinit=p; + +% environnement + w=[0 0 0]; % vitessse du vent + c=0;%10^-3; % coefficient de frottement de l'air + +% constantes +G=6.670*10^-11; % Gravitation universelle +M=6*10^24; % masse de la Terre (Kg) +R=6378*1000; % rayon de la Terre (m) + +% parametres d'intégration +dt=1; % secondes +Time=100000; % Temps total (secondes) +n_i=Time/dt; % Nombre d'itérations + +alpha=(alpha/360)*(2*pi); +betha=(betha/360)*(2*pi); +r=sqrt((R+p(3))^2+p(1)^2+p(2)^2); +g=G*M/r^2; +v0=sqrt(2*(R+p(3))*g); +v_v=[v0*cos(alpha)*(10/36) v0*sin(betha)*(10/36) v0*sin(alpha)*(10/36)]; +v_v_ref=v_v*Time; +Einit=m*g*(r-R)+0.5*m*sum((v_v+w).^2); % Energie totale du system à t0 + + +figure +[X,Y,Z]=sphere(100);surf(R*X,R*Y,R*Z-R) +shading interp +colormap(1-gray) +axis equal +%axis([-max(pinit) max(pinit) -max(pinit) max(pinit) -max(pinit) max(pinit)]) +%axis([0 v0*2*(10/36) 0 v0*(10/36) -5 v0*(10/36)]) +%view(0,0); +hold on + +for s_i=1:n_i +r=sqrt((R+p(3))^2+p(1)^2+p(2)^2); +% Accélération +% gravitationnelle +g=G*M/r^2; +v_g=[-g*p(1)/r -g*p(2)/r -g*(p(3)+R)/r]; +% frottements de l'air +v_c=-c*(v_v.^2)/m; +% if p(3)<=0 +% v_c(3)=-v_c(3); +% end +% Totale +v_a=v_g+v_c; + +% Energie mécanique +Ep=m*g*(r-R); % energie potentielle +Ec=0.5*m*sum(v_v.^2); % energie cinétique +E=Ep+Ec; % energie mécanique +T=Einit-E; % energie thermique dégagée + +% Vitesse +if r> exit.m +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2009 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2009-02-02 Creation +% +% ----------------------------- Script History --------------------------------- + +if isequal(questdlg('Sure you want to quit Matlab?', 'Exit', 'No'), 'Yes') +a=which('-all','exit'); +cd(fileparts(a{2}((findstr('(',a{2})+1):(findstr(')',a{2})-1)))) +exit +end \ No newline at end of file diff --git a/misc/worldhappiness.dat b/misc/worldhappiness.dat new file mode 100644 index 0000000..70dcf07 --- /dev/null +++ b/misc/worldhappiness.dat @@ -0,0 +1,244 @@ +The World Map of Happiness - http://www.le.ac.uk/pc/aw57/world/sample.html + +This is an interactive map - double click on a country to view in more detail. + +Key: Red = High Levels of Happiness + +Use of this map is subject to the credit line: Adrian White, Analytic Social Psychologist, University of Leicester. + +A high resolution image for downloading in .eps format is available by clicking here: .eps Download . Please feel free to download this map for publication, but use of the map is subject to the credit line: Adrian White, Analytic Social Psychologist, University of Leicester. + +To download a .bmp version click here : .bpm Download + +To download a .pdf version click here : .pdf Download + +To download an .fh11 version click here : .fh11 Download + +1. This is the first map to illustrate international differences in happiness. + +2. UK comes 41st out of 178 countries. + +3. UK doing better than most of our similar neighbours and competitors (France 62nd, Italy 50th, Spain 46th, Japan 90th, Chine 82nd, India 125th). However other counties did do better (Germany 35th, USA 23rd, Ireland 11th). A full copy of the league table is published below. + +4. Health is more important than wealth or education. Further analysis was performed to examine the links between satisfaction with life and measures of life expectancy (health), wealth (GDP per capita) and education (access to secondary level education).It was found that satisfaction with life correlated most closely with health (a correlation of .62), followed by wealth (.52) and then education (.51). (All pearson's r correlations were significant at the p<.001 level) + +5. The map is based on an analysis of the results from over 100 studies. It uses data published by by UNESCO, the CIA, the New Economics Foundation, the WHO, the Veenhoven Database, the Latinbarometer, the Afrobarometer, and the UNHDR. + +6. The map is being published in a psychology journal in September, and will be presented at a Psychology conference later in the year. + + + +Quotes + +Adrian White said “The concept of happiness, or satisfaction with life, is currently a major area of research in economics and psychology, most closely associated with new developments in positive psychology. It has also become a feature in the current political discourse in the UK.” + +"There is increasing political interest in using measures of happiness as a national indicator in conjuntion with measures of wealth. A recent BBC survey found that 81% of the population think the Government should focus on making us happier rather than wealthier." +“It is worth remembering that the UK is doing relatively well in this area, coming 41st out of 178 nations”. + +"Further analysis showed that a nation's level of happiness was most closely associated with health levels (correlation of .62), followed by wealth (.52), and then provision of education (.51)." + +"The three predictor variables of health, wealth and education were also very closely associated with each other, illustrating the interdependence of these factors." + +“There is a belief that capitalism leads to unhappy people. However, when people are asked if they are happy with their lives, people in countries with good healthcare, a higher GDP per captia, and access to education were much more likely to report being happy.” +“The frustrations of modern life, and the anxieties of the age, seem to be much less significant compared to the health, financial and educational needs in other parts of the World. The current concern with happiness levels in the UK may well be a case of the 'worried well'." + + + +World Happiness League Table + +SWL Ranking is each country's position in the international ranking of happiness +Country SWL Ranking (1) SWL Index (2) Life Expectancy (3) GDP per capita (4) Access to education score (5) +Albania 157 153.33 73.8 4.9 75.8 +Algeria 134 173.33 71.1 7.2 66.9 +Angola 149 160 40.8 3.2 NaN +Antigua And Barbuda 16 246.67 73.9 11 NaN +Argentina 56 226.67 74.5 13.1 93.7 +Armenia 172 123.33 71.5 4.5 NaN +Australia 26 243.33 80.3 31.9 NaN +Austria 3 260 79 32.7 99.1 +Azerbaijan 144 163.33 66.9 4.8 80.2 +Bahamas 5 256.67 69.7 20.2 NaN +Bahrain 33 240 74.3 23 102 +Bangladesh 104 190 62.8 2.1 53.7 +Barbados 27 243.33 75 17 101.1 +Belarus 170 133.33 68.1 6.9 94.2 +Belgium 28 243.33 78.9 31.4 145.4 +Belize 48 230 71.9 6.8 71.6 +Benin 122 180 54 1.1 21.8 +Bhutan 8 253.33 62.9 1.4 NaN +Bolivia 117 183.33 64.1 2.9 NaN +Bosnia & Herze 137 170 74.2 6.8 NaN +Botswana 123 180 36.3 10.5 81.8 +Brazil 81 210 70.5 8.4 103.2 +Brunei Darussalam 9 253.33 76.4 23.6 NaN +Bulgaria 164 143.33 72.2 9.6 92 +Burkina Faso 152 156.67 47.5 1.3 10 +Burma 130 176.67 60.2 1.7 NaN +Burundi 178 100 43.6 0.7 NaN +Cambodia 110 186.67 56.2 2.2 17.3 +Cameroon 138 170 45.8 2.4 NaN +Canada 10 253.33 80 34 102.6 +Cape Verdi 100 193.33 70.4 6.2 NaN +Central African Republic 145 163.33 39.3 1.1 NaN +Chad 159 150 43.6 1.5 11.5 +Chile 71 216.67 77.9 11.3 87.5 +China 82 210 71.6 6.8 62.8 +Columbia 34 240 72.4 7.9 70.9 +Comoros 97 196.67 63.2 0.6 NaN +Congo Democratic 176 110 43.1 0.7 18.4 +Congo Republic 105 190 52 1.3 NaN +Costa Rica 13 250 78.2 11.1 50.9 +Croatia 98 196.67 75 11.6 NaN +Cuba 83 210 77.3 3.5 NaN +Cyprus 49 230 78.6 7.14 NaN +Czech Republic 77 213.33 75.6 19.5 87.9 +Denmark 1 273.33 77.2 34.6 NaN +Dijbouti 150 160 52.8 1.3 14.7 +Dominica 29 243.33 75.6 5.5 NaN +Dominican Republic 42 233.33 67.2 7 NaN +Ecuador 111 186.67 74.3 4.3 56.7 +Egypt 151 160 69.8 3.9 NaN +El Salvador 61 220 70.9 4.7 49.8 +Equitorial Guinea 135 173.33 43.3 50.2 NaN +Eritrea 162 146.67 53.8 1 28.2 +Estonia 139 170 71.3 16.7 107 +Ethiopia 153 156.67 47.6 0.9 5.2 +Fiji 57 223.33 67.8 6 NaN +Finland 6 256.67 78.5 30.9 124.5 +France 62 220 79.5 29.9 108.7 +Gabon 88 206.67 54.5 6.8 54.4 +Gambia 106 190 55.7 1.9 27 +Georgia 169 136.67 70.5 3.3 77.7 +Germany 35 240 78.7 30.4 99 +Ghana 89 206.67 56.8 2.5 37.3 +Greece 84 210 78.3 22.2 94.6 +Grenada 72 216.67 65.3 5 NaN +Guatemala 43 233.33 67.3 4.7 32.7 +Guinea 140 170 53.7 2 NaN +Guinea-Bissau 124 180 44.7 0.8 20.4 +Guyana 36 240 63.1 4.6 81 +Haiti 118 183.33 51.6 1.7 NaN +Honduras 37 240 67.8 2.9 NaN +Hong Kong 63 220 81.6 32.9 NaN +Hungary 107 190 72.7 16.3 98.6 +Iceland 4 260 80.7 35.6 108.8 +India 125 180 63.3 3.3 49.9 +Indonesia 64 220 66.8 3.6 NaN +Iran 96 200 70.4 8.3 80 +Ireland 11 253.33 77.7 41 123.1 +Israel 58 223.33 79.7 24.6 93 +Italy 50 230 80.1 29.2 92.8 +Ivory Coast 160 150 45.9 1.6 21.7 +Jamaica 44 233.33 70.8 4.4 83.6 +Japan 90 206.67 82 31.5 102.1 +Jordan 141 170 71.3 4.7 87.7 +Kazakhstan 101 193.33 63.2 8.2 87 +Kenya 112 186.67 47.2 1.1 NaN +Kuwait 38 240 76.9 19.2 55.6 +Kyrgyzstan 65 220 66.8 2.1 83 +Laos 126 180 54.7 1.9 35.6 +Latvia 154 156.67 71.6 13.2 88.9 +Lebanon 113 186.67 72 6.2 78.2 +Lesotho 165 143.33 36.3 2.5 28 +Libya 108 190 73.6 11.4 NaN +Lithuania 155 156.67 72.3 13.7 93.4 +Luxembourg 12 253.33 78.5 55.6 95.3 +Macedonia 146 163.33 73.8 7.8 NaN +Madagascar 103 193.33 55.4 0.9 NaN +Malawi 158 153.33 39.7 0.6 NaN +Malaysia 17 246.67 73.2 12.1 98.8 +Maldives 66 220 66.6 3.9 42.7 +Mali 131 176.67 47.9 1.2 15 +Malta 14 250 78.4 19.9 90.4 +Mauritania 132 176.67 52.7 2.2 NaN +Mauritius 73 216.67 72.2 13.1 107.3 +Mexico 51 230 75.1 10 73.4 +Moldova 175 116.67 67.7 1.8 NaN +Mongolia 59 223.33 64 1.9 64.4 +Morocco 114 186.67 69.7 4.2 39.3 +Mozambique 127 180 41.9 1.3 13.9 +Namibia 74 216.67 48.3 7 59.8 +Nepal 119 183.33 61.6 1.4 53.9 +Netherlands 15 250 78.4 30.5 124.1 +New Zealand 18 246.67 79.1 25.2 112.9 +Nicaragua 85 210 69.7 2.9 NaN +Niger 161 150 44.4 0.9 NaN +Nigeria 120 183.33 43.4 1.4 NaN +Norway 19 246.67 79.4 42.3 117 +Oman 30 243.33 74.1 13.2 67.8 +Pakistan 166 143.33 63 2.4 39 +Palestine 128 180 72.5 5.8 80.7 +Panama 39 240 74.8 7.2 68.7 +Papua New Guinea 86 210 55.3 2.6 21.2 +Paraguay 75 216.67 71 4.9 56.9 +Peru 115 186.67 70 5.9 80.8 +Philippines 78 213.33 70.4 5.1 75.9 +Poland 99 196.67 74.3 13.3 NaN +Portugal 92 203.33 77.2 19.3 112 +Qatar 45 233.33 72.8 27.4 92.4 +Romania 136 173.33 71.3 8.2 80.2 +Russia 167 143.33 65.3 11.1 81.9 +Rwanda 163 146.67 43.9 1.5 12.1 +Samoa Western 52 230 70.2 5.8 76 +Sao Tome And Prinicpe 60 223.33 63 1.2 NaN +Saudi Arabia 31 243.33 71.8 12.8 68.5 +Senegal 116 186.67 55.7 1.8 19.5 +Seychelles 20 246.67 72.7 7.8 NaN +Sierra Leone 143 166.67 40.8 0.8 23.9 +Singapore 53 230 78.7 28.1 NaN +Slovakia 129 180 74 16.1 86.6 +Slovenia 67 220 76.4 21.6 98.8 +Soloman Islands 54 230 62.3 1.7 NaN +South Africa 109 190 48.4 12 90.2 +South Korea 102 193.33 77 20.4 97.4 +Spain 46 233.33 79.5 25.5 112.8 +Sri Lanka 93 203.33 74 4.3 NaN +St Kitts And Nevis 21 246.67 70 8.8 NaN +St Lucia 47 233.33 72.4 5.4 94.3 +St Vincent And The Grenadines 40 240 71.1 2.9 NaN +Sudan 173 120 56.4 2.1 28.8 +Suriname 32 243.33 69.1 4.1 50.7 +Swaziland 168 140 32.5 5 NaN +Sweden 7 256.67 80.2 29.8 152.8 +Switzerland 2 273.33 80.5 32.3 99.9 +Syria 142 170 73.3 3.9 42 +Taiwan 68 220 76.1 27.6 NaN +Tajikistan 94 203.33 63.6 1.2 76 +Tanzania 121 183.33 46 0.7 5.31 +Thailand 76 216.67 70 8.3 79 +Timor-Leste 69 220 65.5 0.4 NaN +Togo 147 163.33 54.3 1.7 NaN +Tonga 70 220 72.2 2.3 NaN +Trinidad And Tobago 55 230 69.9 16.7 78.4 +Tunisia 79 213.33 73.3 8.3 74.6 +Turkey 133 176.67 68.7 8.2 NaN +Turkmenistan 171 133.33 62.4 8 NaN +UAE 22 246.67 78 43.4 74.4 +Uganda 156 156.67 47.3 1.8 NaN +Ukraine 174 120 66.1 7.2 92.8 +United Kingdom 41 236.67 78.4 30.3 157.2 +Uruguay 87 210 75.4 9.6 91.6 +USA 23 246.67 77.4 41.8 94.6 +Uzbekistan 80 213.33 66.5 1.8 NaN +Vanuate 24 246.67 68.6 2.9 28.5 +Venzuela 25 246.67 72.9 6.1 NaN +Vietnam 95 203.33 70.5 2.8 64.6 +Yemen 91 206.67 60.6 0.9 NaN +Zambia 148 163.33 37.5 0.9 25.5 +Zimbabwe 177 110 36.9 2.3 45.3 + +Notes + +1. SWL (satisfaction with life) rating calculated from data published by New Economics Foundation (2006). + +2. SWL (satisfaction with life) index calculated from data published by New Economics Foundation (2006). + +3. Life Expectancy from UN Human Development Report (2003) + +4. GDP per capita from figure published by the CIA (2006), figure in US$. + +5. Access to secondary education rating from UNESCO (2002) + + + +If you are interested in the environment and personality, and live in the UK, please feel free to participate in our online study on the environment by following this link: NEPS diff --git a/misc/worldhappiness.m b/misc/worldhappiness.m new file mode 100644 index 0000000..e69de29 diff --git a/mkdirr.m b/mkdirr.m new file mode 100644 index 0000000..eacd424 --- /dev/null +++ b/mkdirr.m @@ -0,0 +1,28 @@ +function []=mkdirr(p) +% mkdirr - recursive mkdir +% +%Native MKDIR waits a relative path: +% >> mkdir('/usr/bin/newpath1/newpath2') +%will therefore create a usr/bin/newpath1... +%in the CURRENT directory whereas our +% >> mkdirr('/usr/bin/newpath1/newpath2') +%will make newpath1 and newpath2 in /usr/bin +i=0; +while not(exist(p, 'dir')) + i=i+1; + [p,np{i},e]=fileparts(p); + np{i}=[np{i},e]; + if isempty(np{i}) + break + end +end +lcd=pwd; +if isempty(p) + p='.'; +end +for j=i:-1:1 + cd(p) + mkdir(np{j}); + p=fullfile(p,np{j}); +end +cd(lcd) \ No newline at end of file diff --git a/mne.m b/mne.m new file mode 100644 index 0000000000000000000000000000000000000000..d09cd674a28b979f12eed1e834f618a7cc777641 GIT binary patch literal 59 zcmYc*%}XxH%+FI$i1vz4%Pi3;N-fSzDJ_mwuvN&-OVvouNX#wJ$xQ@Fq?hLCjyHPtTcq2NTe1ux+ji)dR{<8~tprZptn-A38$&L*>F*8I`Q zOsLw*LI_e~UE_56Of{%;^q6o z`@P?LzxR8;mk+s2W|EUHt`B@!@=n7jY33t5vu+GaPk6az0&x6;aSCrKC=uF9| ztFM{0S4^#<7PM-$W~ocMYT8xRsH#&hX4OiqsK>(Lz2iHjKMKtJ@S}08<7u!VIzPMq z@y;)7n~ld@KiG(OHrUVAo_1}p@l59@_A^GIB=>rqrjtY|WtejBj!k&64H}8=Qyw6S za3s--elO=zRo9QWbM_?%DW0W8c4Qul&mGxueb4Tci4vXU^>LLbE%Oq8v6Swv@ZwXu z&cB$bT>CERF-t#d@%o1f53)Z``}U|ri!rmPSsGDA=5fHyleSM1+h!{$wy?^ey_ zwkG(Dqi$NOYl(JPX3Tb8|Ks>=^P%=g>l27 za0{~GQ_|0JbG+w5GGvE1{^VRbyZPHT=iHHHhU~*KX+s9O_E0n#YMu{~wco29aBK7C zb~3b(e|espa}I@M$LGcaiv4hNLN-D6UdI~uJEMGE9_hUWJ{1B2AOHd&00JNY0w4ea zAOHd&00Q@cz%8{`Kac1N9x z19jkl00@8p2!H?xfB*=900@8p2!H?x+yeoAZe9K#-Hthd)) +axis image +if NewAxes + colormap(bone) + if slicing==3 + view(2) + else + view(60,30) + end +end + diff --git a/mseq.m b/mseq.m new file mode 100644 index 0000000..5b8cc46 --- /dev/null +++ b/mseq.m @@ -0,0 +1,422 @@ +function ms=mseq(baseVal,powerVal,shift,whichSeq) +% Maximum length sequence assuming 2,3,5 distinct values +% +% [ms]=MSEQ(baseVal,powerVal[,shift,whichSeq]) +% +% OUTPUT: +% ms = generated maximum length sequence, of length basisVal^powerVal-1 +% +% INPUT: +% baseVal -nuber of sequence levels (2,3, or 5 allowed) +% powerVal -power, so that sequence length is baseVal^powerVal-1 +% shift -cyclical shift of the sequence +% whichSeq -sequence istantiation to use +% (numer of sequences varies with powreVal - see the code) + +% (c) Giedrius T. Buracas, SNL-B, Salk Institute +% Register values are taken from: WDT Davies, System Identification +% for self-adaptive control. Wiley-Interscience, 1970 +% When using mseq code for design of FMRI experiments, please, cite: +% G.T.Buracas & G.M.Boynton (2002) Efficient Design of Event-Related fMRI +% Experiments Using M-sequences. NeuroImage, 16, 801-813. + +if nargin<4, whichSeq=1; end +if nargin<3, shift=1; end; + +bitNum=baseVal^powerVal-1; + +register=ones(powerVal,1); + +if baseVal==2, +switch powerVal, +case 2, tap(1).No=[1,2]; +case 3, tap(1).No=[1,3]; + tap(2).No=[2,3]; +case 4, tap(1).No=[1,4]; + tap(2).No=[3,4]; +case 5, tap(1).No=[2,5]; + tap(2).No=[3,5]; + tap(3).No=[1,2,3,5]; + tap(4).No=[2,3,4,5]; + tap(5).No=[1,2,4,5]; + tap(6).No=[1,3,4,5]; +case 6, tap(1).No=[1,6]; + tap(2).No=[5,6]; + tap(3).No=[1,2,5,6]; + tap(4).No=[1,4,5,6]; + tap(5).No=[1,3,4,6]; + tap(6).No=[2,3,5,6]; +case 7, tap(1).No=[1,7]; + tap(2).No=[6,7]; + tap(3).No=[3,7]; + tap(4).No=[4,7]; + tap(5).No=[1,2,3,7]; + tap(6).No=[4,5,6,7]; + tap(7).No=[1,2,5,7]; + tap(8).No=[2,5,6,7]; + tap(9).No=[2,3,4,7]; + tap(10).No=[3,4,5,7]; + tap(11).No=[1,3,5,7]; + tap(12).No=[2,4,6,7]; + tap(13).No=[1,3,6,7]; + tap(14).No=[1,4,6,7]; + tap(15).No=[2,3,4,5,6,7]; + tap(16).No=[1,2,3,4,5,7]; + tap(17).No=[1,2,4,5,6,7]; + tap(18).No=[1,2,3,5,6,7]; +case 8, tap(1).No=[1,2,7,8]; + tap(2).No=[1,6,7,8]; + tap(3).No=[1,3,5,8]; + tap(4).No=[3,5,7,8]; + tap(5).No=[2,3,4,8]; + tap(6).No=[4,5,6,8]; + tap(7).No=[2,3,5,8]; + tap(8).No=[3,5,6,8]; + tap(9).No=[2,3,6,8]; + tap(10).No=[2,5,6,8]; + tap(11).No=[2,3,7,8]; + tap(12).No=[1,5,6,8]; + tap(13).No=[1,2,3,4,6,8]; + tap(14).No=[2,4,5,6,7,8]; + tap(15).No=[1,2,3,6,7,8]; + tap(16).No=[1,2,5,6,7,8]; +case 9, tap(1).No=[4,9]; + tap(2).No=[5,9]; + tap(3).No=[3,4,6,9]; + tap(4).No=[3,5,6,9]; + tap(5).No=[4,5,8,9]; + tap(6).No=[1,4,5,9]; + tap(7).No=[1,4,8,9]; + tap(8).No=[1,5,8,9]; + tap(9).No=[2,3,5,9]; + tap(10).No=[4,6,7,9]; + tap(11).No=[5,6,8,9]; + tap(12).No=[1,3,4,9]; + tap(13).No=[2,7,8,9]; + tap(14).No=[1,2,7,9]; + tap(15).No=[2,4,7,9]; + tap(16).No=[2,5,7,9]; + tap(17).No=[2,4,8,9]; + tap(18).No=[1,5,7,9]; + tap(19).No=[1,2,4,5,6,9]; + tap(20).No=[3,4,5,7,8,9]; + tap(21).No=[1,3,4,6,7,9]; + tap(22).No=[2,3,5,6,8,9]; + tap(23).No=[3,5,6,7,8,9]; + tap(24).No=[1,2,3,4,6,9]; + tap(25).No=[1,5,6,7,8,9]; + tap(26).No=[1,2,3,4,8,9]; + tap(27).No=[1,2,3,7,8,9]; + tap(28).No=[1,2,6,7,8,9]; + tap(29).No=[1,3,5,6,8,9]; + tap(30).No=[1,3,4,6,8,9]; + tap(31).No=[1,2,3,5,6,9]; + tap(32).No=[3,4,6,7,8,9]; + tap(33).No=[2,3,6,7,8,9]; + tap(34).No=[1,2,3,6,7,9]; + tap(35).No=[1,4,5,6,8,9]; + tap(36).No=[1,3,4,5,8,9]; + tap(37).No=[1,3,6,7,8,9]; + tap(38).No=[1,2,3,6,8,9]; + tap(39).No=[2,3,4,5,6,9]; + tap(40).No=[3,4,5,6,7,9]; + tap(41).No=[2,4,6,7,8,9]; + tap(42).No=[1,2,3,5,7,9]; + tap(43).No=[2,3,4,5,7,9]; + tap(44).No=[2,4,5,6,7,9]; + tap(45).No=[1,2,4,5,7,9]; + tap(46).No=[2,4,5,6,7,9]; + tap(47).No=[1,3,4,5,6,7,8,9]; + tap(48).No=[1,2,3,4,5,6,8,9]; +case 10, tap(1).No=[3,10]; + tap(2).No=[7,10]; + tap(3).No=[2,3,8,10]; + tap(4).No=[2,7,8,10]; + tap(5).No=[1,3,4,10]; + tap(6).No=[6,7,9,10]; + tap(7).No=[1,5,8,10]; + tap(8).No=[2,5,9,10]; + tap(9).No=[4,5,8,10]; + tap(10).No=[2,5,6,10]; + tap(11).No=[1,4,9,10]; + tap(12).No=[1,6,9,10]; + tap(13).No=[3,4,8,10]; + tap(14).No=[2,6,7,10]; + tap(15).No=[2,3,5,10]; + tap(16).No=[5,7,8,10]; + tap(17).No=[1,2,5,10]; + tap(18).No=[5,8,9,10]; + tap(19).No=[2,4,9,10]; + tap(20).No=[1,6,8,10]; + tap(21).No=[3,7,9,10]; + tap(22).No=[1,3,7,10]; + tap(23).No=[1,2,3,5,6,10]; + tap(24).No=[4,5,7,8,9,10]; + tap(25).No=[2,3,6,8,9,10]; + tap(26).No=[1,2,4,7,8,10]; + tap(27).No=[1,5,6,8,9,10]; + tap(28).No=[1,2,4,5,9,10]; + tap(29).No=[2,5,6,7,8,10]; + tap(30).No=[2,3,4,5,8,10]; + tap(31).No=[2,4,6,8,9,10]; + tap(32).No=[1,2,4,6,8,10]; + tap(33).No=[1,2,3,7,8,10]; + tap(34).No=[2,3,7,8,9,10]; + tap(35).No=[3,4,5,8,9,10]; + tap(36).No=[1,2,5,6,7,10]; + tap(37).No=[1,4,6,7,9,10]; + tap(38).No=[1,3,4,6,9,10]; + tap(39).No=[1,2,6,8,9,10]; + tap(40).No=[1,2,4,8,9,10]; + tap(41).No=[1,4,7,8,9,10]; + tap(42).No=[1,2,3,6,9,10]; + tap(43).No=[1,2,6,7,8,10]; + tap(44).No=[2,3,4,8,9,10]; + tap(45).No=[1,2,4,6,7,10]; + tap(46).No=[3,4,6,8,9,10]; + tap(47).No=[2,4,5,7,9,10]; + tap(48).No=[1,3,5,6,8,10]; + tap(49).No=[3,4,5,6,9,10]; + tap(50).No=[1,4,5,6,7,10]; + tap(51).No=[1,3,4,5,6,7,8,10]; + tap(52).No=[2,3,4,5,6,7,9,10]; + tap(53).No=[3,4,5,6,7,8,9,10]; + tap(54).No=[1,2,3,4,5,6,7,10]; + tap(55).No=[1,2,3,4,5,6,9,10]; + tap(56).No=[1,4,5,6,7,8,9,10]; + tap(57).No=[2,3,4,5,6,8,9,10]; + tap(58).No=[1,2,4,5,6,7,8,10]; + tap(59).No=[1,2,3,4,6,7,9,10]; + tap(60).No=[1,3,4,6,7,8,9,10]; +case 11, tap(1).No=[9,11]; +case 12, tap(1).No=[6,8,11,12]; +case 13, tap(1).No=[9,10,12,13]; +case 14, tap(1).No=[4,8,13,14]; +case 15, tap(1).No=[14,15]; +case 16, tap(1).No=[4,13,15,16]; +case 17, tap(1).No=[14,17]; +case 18, tap(1).No=[11,18]; +case 19, tap(1).No=[14,17,18,19]; +case 20, tap(1).No=[17,20]; +case 21, tap(1).No=[19,21]; +case 22, tap(1).No=[21,22]; +case 23, tap(1).No=[18,23]; +case 24, tap(1).No=[17,22,23,24]; +case 25, tap(1).No=[22,25]; +case 26, tap(1).No=[20,24,25,26]; +case 27, tap(1).No=[22,25,26,27]; +case 28, tap(1).No=[25,28]; +case 29, tap(1).No=[27,29]; +case 30, tap(1).No=[7,28,29,30]; +otherwise error(sprintf('M-sequence %.0f^%.0f is not defined',baseVal,powerVal)) +end; +elseif baseVal==3, +switch powerVal, +case 2, tap(1).No=[2,1]; + tap(2).No=[1,1]; +case 3, tap(1).No=[0,1,2]; + tap(2).No=[1,0,2]; + tap(3).No=[1,2,2]; + tap(4).No=[2,1,2]; +case 4, tap(1).No=[0,0,2,1]; + tap(2).No=[0,0,1,1]; + tap(3).No=[2,0,0,1]; + tap(4).No=[2,2,1,1]; + tap(5).No=[2,1,1,1]; + tap(6).No=[1,0,0,1]; + tap(7).No=[1,2,2,1]; + tap(8).No=[1,1,2,1]; +case 5, tap(1).No=[0,0,0,1,2]; + tap(2).No=[0,0,0,1,2]; + tap(3).No=[0,0,1,2,2]; + tap(4).No=[0,2,1,0,2]; + tap(5).No=[0,2,1,1,2]; + tap(6).No=[0,1,2,0,2]; + tap(7).No=[0,1,1,2,2]; + tap(8).No=[2,0,0,1,2]; + tap(9).No=[2,0,2,0,2]; + tap(10).No=[2,0,2,2,2]; + tap(11).No=[2,2,0,2,2]; + tap(12).No=[2,2,2,1,2]; + tap(13).No=[2,2,1,2,2]; + tap(14).No=[2,1,2,2,2]; + tap(15).No=[2,1,1,0,2]; + tap(16).No=[1,0,0,0,2]; + tap(17).No=[1,0,0,2,2]; + tap(18).No=[1,0,1,1,2]; + tap(19).No=[1,2,2,2,2]; + tap(20).No=[1,1,0,1,2]; + tap(21).No=[1,1,2,0,2]; +case 6, tap(1).No=[0,0,0,0,2,1]; + tap(2).No=[0,0,0,0,1,1]; + tap(3).No=[0,0,2,0,2,1]; + tap(4).No=[0,0,1,0,1,1]; + tap(5).No=[0,2,0,1,2,1]; + tap(6).No=[0,2,0,1,1,1]; + tap(7).No=[0,2,2,0,1,1]; + tap(8).No=[0,2,2,2,1,1]; + tap(9).No=[2,1,1,1,0,1]; + tap(10).No=[1,0,0,0,0,1]; + tap(11).No=[1,0,2,1,0,1]; + tap(12).No=[1,0,1,0,0,1]; + tap(13).No=[1,0,1,2,1,1]; + tap(14).No=[1,0,1,1,1,1]; + tap(15).No=[1,2,0,2,2,1]; + tap(16).No=[1,2,0,1,0,1]; + tap(17).No=[1,2,2,1,2,1]; + tap(18).No=[1,2,1,0,1,1]; + tap(19).No=[1,2,1,2,1,1]; + tap(20).No=[1,2,1,1,2,1]; + tap(21).No=[1,1,2,1,0,1]; + tap(22).No=[1,1,1,0,1,1]; + tap(23).No=[1,1,1,2,0,1]; + tap(24).No=[1,1,1,1,1,1]; +case 7, tap(1).No=[0,0,0,0,2,1,2]; + tap(2).No=[0,0,0,0,1,0,2]; + tap(3).No=[0,0,0,2,0,2,2]; + tap(4).No=[0,0,0,2,2,2,2]; + tap(5).No=[0,0,0,2,1,0,2]; + tap(6).No=[0,0,0,1,1,2,2]; + tap(7).No=[0,0,0,1,1,1,2]; + tap(8).No=[0,0,2,2,2,0,2]; + tap(9).No=[0,0,2,2,1,2,2]; + tap(10).No=[0,0,2,1,0,0,2]; + tap(11).No=[0,0,2,1,2,2,2]; + tap(12).No=[0,0,1,0,2,1,2]; + tap(13).No=[0,0,1,0,1,1,2]; + tap(14).No=[0,0,1,1,0,1,2]; + tap(15).No=[0,0,1,1,2,0,2]; + tap(16).No=[0,2,0,0,0,2,2]; + tap(17).No=[0,2,0,0,1,0,2]; + tap(18).No=[0,2,0,0,1,1,2]; + tap(19).No=[0,2,0,2,2,0,2]; + tap(20).No=[0,2,0,2,1,2,2]; + tap(21).No=[0,2,0,1,1,0,2]; + tap(22).No=[0,2,2,0,2,0,2]; + tap(23).No=[0,2,2,0,1,2,2]; + tap(24).No=[0,2,2,2,2,1,2]; + tap(25).No=[0,2,2,2,1,0,2]; + tap(26).No=[0,2,2,1,0,1,2]; + tap(27).No=[0,2,2,1,2,2,2]; +otherwise error(sprintf('M-sequence %.0f^%.0f is not defined',baseVal,powerVal)) +end; +elseif baseVal==5, +switch powerVal, +case 2, tap(1).No=[4,3]; + tap(2).No=[3,2]; + tap(3).No=[2,2]; + tap(4).No=[1,3]; +case 3, tap(1).No=[0,2,3]; + tap(2).No=[4,1,2]; + tap(3).No=[3,0,2]; + tap(4).No=[3,4,2]; + tap(5).No=[3,3,3]; + tap(6).No=[3,3,2]; + tap(7).No=[3,1,3]; + tap(8).No=[2,0,3]; + tap(9).No=[2,4,3]; + tap(10).No=[2,3,3]; + tap(11).No=[2,3,2]; + tap(12).No=[2,1,2]; + tap(13).No=[1,0,2]; + tap(14).No=[1,4,3]; + tap(15).No=[1,1,3]; +case 4, tap(1).No=[0,4,3,3]; + tap(2).No=[0,4,3,2]; + tap(3).No=[0,4,2,3]; + tap(4).No=[0,4,2,2]; + tap(5).No=[0,1,4,3]; + tap(6).No=[0,1,4,2]; + tap(7).No=[0,1,1,3]; + tap(8).No=[0,1,1,2]; + tap(9).No=[4,0,4,2]; + tap(10).No=[4,0,3,2]; + tap(11).No=[4,0,2,3]; + tap(12).No=[4,0,1,3]; + tap(13).No=[4,4,4,2]; + tap(14).No=[4,3,0,3]; + tap(15).No=[4,3,4,3]; + tap(16).No=[4,2,0,2]; + tap(17).No=[4,2,1,3]; + tap(18).No=[4,1,1,2]; + tap(19).No=[3,0,4,2]; + tap(20).No=[3,0,3,3]; + tap(21).No=[3,0,2,2]; + tap(22).No=[3,0,1,3]; + tap(23).No=[3,4,3,2]; + tap(24).No=[3,3,0,2]; + tap(25).No=[3,3,3,3]; + tap(26).No=[3,2,0,3]; + tap(27).No=[3,2,2,3]; + tap(28).No=[3,1,2,2]; + tap(29).No=[2,0,4,3]; + tap(30).No=[2,0,3,2]; + tap(31).No=[2,0,2,3]; + tap(32).No=[2,0,1,2]; + tap(33).No=[2,4,2,2]; + tap(34).No=[2,3,0,2]; + tap(35).No=[2,3,2,3]; + tap(36).No=[2,2,0,3]; + tap(37).No=[2,2,3,3]; + tap(38).No=[2,1,3,2]; + tap(39).No=[1,0,4,3]; + tap(40).No=[1,0,3,3]; + tap(41).No=[1,0,2,2]; + tap(42).No=[1,0,1,2]; + tap(43).No=[1,4,1,2]; + tap(44).No=[1,3,0,3]; + tap(45).No=[1,3,1,3]; + tap(46).No=[1,2,0,2]; + tap(47).No=[1,2,4,3]; + tap(48).No=[1,1,4,2]; +otherwise error(sprintf('M-sequence %.0f^%.0f is not defined',baseVal,powerVal)) +end; +end; + +ms=zeros(bitNum,1); +if isempty(whichSeq), whichSeq=ceil(rand(1)*length(tap)); +else, + if whichSeq>length(tap) | whichSeq<1 + disp(sprintf(' wrapping arround!')); + whichSeq=rem(whichSeq,length(tap))+1; + end; +end; + +weights=zeros(1,powerVal); +if baseVal==2, + weights(tap(whichSeq).No)=1; +elseif baseVal>2, + weights=tap(whichSeq).No; +end; + +%weights + +for i=1:bitNum + % calculating next digit with modulo powerVal arithmetic + % register, (tap(1).No) + + %ms(i)=rem(sum(register(tap(whichSeq).No)),baseVal); + ms(i)=rem(weights*register+baseVal,baseVal); + % updating the register + + register=[ms(i);register(1:powerVal-1)]; +end + +ms=ms(:); +if ~isempty(shift), + shift=rem(shift, length(ms)); + ms=[ms(shift+1:end); ms(1:shift)]; +end; + +if baseVal==2, + ms=ms*2-1; +elseif baseVal==3, + ms(ms==2)=-1; +elseif baseVal==5, + ms(ms==4)=-1; + ms(ms==3)=-2; +else + error('wrong baseVal!'); +end; + + diff --git a/myColors.m b/myColors.m new file mode 100644 index 0000000..87918d6 --- /dev/null +++ b/myColors.m @@ -0,0 +1,4 @@ +function [Couleur] = myColors() +% myColors() renvoie un Cell permettant d'automatiser les couleurs des plots pour chaque condition +% e.g. couleur=myColors; plot(x,y, couleur{1}) pour la condition i (où i=1:5) +Couleur={ {'Color', [0 0 0]} , {'Color', [1 0 0]} , {'Color', [0 0 1]} , {'Color', [0 1 0]} , {'Color', [1 0 1]} } diff --git a/mycaxis.m b/mycaxis.m new file mode 100644 index 0000000..8cdfa78 --- /dev/null +++ b/mycaxis.m @@ -0,0 +1,26 @@ +function [] = mycaxis(lims) +%MYCAXIS - One line description goes here. +% [] = mycaxis(lims) +% Works ALSO when there are contours in the plot +% Example +% >> mycaxis +% +% See also: caxis + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2006 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2006-02-27 Creation +% +% ----------------------------- Script History --------------------------------- + +caxis(lims) +ch=get(gca, 'Children'); +if ~isempty(findobj(ch, 'type', 'hggroup')) + dsip('contours') +end \ No newline at end of file diff --git a/mycolormap.m b/mycolormap.m new file mode 100644 index 0000000..304ce7a --- /dev/null +++ b/mycolormap.m @@ -0,0 +1,21 @@ +function [m]=mycolormap(seuil,varargin) +% mycolormap - custom the jet colormap to display X% of the data +% [m]=mycolormap(X) +% [m]=mycolormap(X,CM) CM is a colormap + +if nargin==1 + + m=ones(100-seuil,3)*[.2 .7 1;0 0 0;0 0 0]; + m= [m ; ones(2*seuil,3)*.5]; + m= [m ; ones(100-seuil,3)*[1 .4 .2;0 0 0;0 0 0]]; + + l=.3; + m=hot(110); + m= [m(1:100,:)*(1-l)+ones(100,3)*l]; + m= [fliplr(flipud(m)) ; ones(2*seuil,3)*l ; m]; + +else + n=varargin{1}; + m=[ n(1:ceil(length(n)/2),:); ones(2*seuil/100*length(n),3)*.5 ; n(ceil(length(n)/2):end,:)]; + +end diff --git a/mymatlabpath.m b/mymatlabpath.m new file mode 100644 index 0000000..749509d --- /dev/null +++ b/mymatlabpath.m @@ -0,0 +1,3 @@ +function [MYMATLABDIR]=MYMATLABPATH() +% MYMATLABPATH - Retrieves the matlab working directory +MYMATLABDIR = fileparts(mfilename('fullpath')); diff --git a/mypath.m b/mypath.m new file mode 100644 index 0000000..80bd3c6 --- /dev/null +++ b/mypath.m @@ -0,0 +1,118 @@ +function [HOMEDIR]=mypath() +% MYPATH - Retrieves home directory on the current OS +HOMEDIR=''; +lcd=cd; +if ispc + [s,m]=system('echo %USERPROFILE%\My Documents & cd "%USERPROFILE%\My Documents"'); + if s + [s,m]=system('echo %USERPROFILE%\Mes Documents & cd "%USERPROFILE%\Mes Documents"'); + end + if s + [s,m]=system([ ... + 'echo "%TEMP%" & regedit /E "%TEMP%\homepath.txt" ' ... + '"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders\" & '... + 'type "%TEMP%\homepath.txt"|find "Personal"']); + if ~s + m=m(findstr(lower(deblank(m)), '"personal"="') + 12:end-2); + warning('MYPATH:HomepathFromRegistry','No "My Documents" in the user folder. Use Registry table value.'); + end + else + m=deblank(m); + end + if ~s + lcd=cd; + cd(m); + m=cd; + end +elseif isunix + % [s,m]=system('cd ~'); + % [p,n]=fileparts(cd); + % if isequal('matlab', lower(n)) + % cd(p); + % end + try + s=0; + cd('~'); + m=cd; + catch + s=1; + end +end +cd(lcd); +if s + error('Unable to find your home folder on this OS:\n%s',m); +else + HOMEDIR=m; +end + +return + + +% Set PATH variables: HOMEDIR & USBDIR +% [HOMEDIR,USBDIR]=mypath() + +HOMEDIR=''; +LACIE=''; +WENDY=''; +MAMMAMIA=''; +USBDIR=NaN; + +if nargin>0 + if isunix + testloc={'/mnt'} + elseif ispc + testloc=[ { 'F:' 'G:' 'H:'} cellstr([char(65:90)' repmat(':', 26,1)])' ]; + end + switch upper(NAME) + case 'WENDY' + testname='wendy.txt'; + case 'LACIE' + testname='lacie.txt'; + end + for i=1:length(testloc) + if exist(fullfile(testloc{i}, testname), 'file') + HOMEDIR=testloc{i}; + return + end + end + return +end + +if isunix + if exist('/home/ndiayek') + HOMEDIR='/home/ndiayek'; + elseif exist('/pclxserver2/home/ndiaye') + HOMEDIR='/pclxserver2/home/ndiaye'; + end + +elseif ispc + if exist('f:\matlab') && (exist('g:\lacie.txt') || exist('g:\wendy.txt')) + % USB key is F: + USBDIR='f:\'; + elseif exist('g:\matlab') && (exist('g:\lacie.txt') || exist('g:\wendy.txt') || exist('g:\mammamia.txt')) + % USB key is G: + USBDIR='g:\'; + elseif exist('h:\matlab') && (exist('h:\lacie.txt') || exist('h:\wendy.txt')) + % USB key is H: + USBDIR='h:\'; + % elseif exist('d:\matlab') && (exist('g:\lacie.txt') || exist('g:\wendy.txt')) + % % USB key is D: + % USBDIR='d:\'; + end + + % if D: is a CD drive, hangs the execution !!! + % if exist('d:\ndiaye\') + % HOMEDIR='d:\ndiaye\'; + % elseif exist('d:\ndiayek\') + % HOMEDIR='d:\ndiayek\'; + % elseif exist('e:\ndiaye\') + % HOMEDIR='e:\ndiaye\'; + % elseif exist('g:\ndiayek\') + % HOMEDIR='g:\ndiayek\'; + % end +end + +if nargout==0 + assignin('caller', 'HOMEDIR', HOMEDIR) + assignin('caller', 'USBDIR', USBDIR) +end diff --git a/mypsychtoolbox/ConfidenceDisc.m b/mypsychtoolbox/ConfidenceDisc.m new file mode 100644 index 0000000..64472f8 --- /dev/null +++ b/mypsychtoolbox/ConfidenceDisc.m @@ -0,0 +1,273 @@ +function [c,rt,events,onset_time]=ConfidenceDisc(frame,nlevels,timelimit,keylist,markers,FOV) +% ConfidenceScale - disc for confidence rating +% [c,rt]=ConfidenceDisc(frame,nlevels,timelimit) +% +% markers.onset +% markers.keys +c=NaN; +rt=NaN; +events=[]; +if nargin<1 + DEBUG=1; +else + DEBUG=0; +end +if ~exist('keylist','var') + keylist={}; +end +sendMarkers = 0; +if nargin>4 + sendMarkers = 1; +end + +%% Behaviour & visual aspect of the disc +% +% Shape parameters +% They are all relative top the field of view (fov) which is itself based +% on the frame/window size : +disc.diameter = .25; % height of the disc +disc.contour=1/60; % pen width (relatively to frame size) + +% NaN = starts at a random position; +disc.startlevel = NaN; + +% Number of consecutive key press to go to fast mode +nkey = 10; +repetitiontime = 0.05; % minimum time between consecutive keypresses +keytimebreak = 0.3; + +% To validate, SS press +% Esc = 27 or SPACE = 32 or RETURN = 13 +if isempty(keylist) + keylist = { + [ KbName('LeftArrow') KbName('LeftAlt') KbName('LeftControl') 74 257] + [ KbName('RightArrow') KbName('RightAlt') KbName('RightControl') 75 258] + [ KbName('Return') KbName('Space') 259 ] + [ KbName('Escape') ] }; +end + +listenParPort = 0; +if any([keylist{:}]>256) + if exist('ReadParPort')==2 + listenParPort = 1; + else + error('Listening to Parallel port requires readParPort() function'); + end +end + +if listenParPort || sendMarkers + OpenParPort(1); +end + +keys.less = keylist{1}; +keys.more = keylist{2}; +keys.ok = keylist{3}; +keys.quit = keylist{4}; +keys.resp = [ keys.less keys.more keys.ok keys.quit]; + +%% Processing of inputs +if nargin<1 + frame =[]; +end +WindowWasOpen = 0; +if isempty(frame) + clc + Screen('Preference', 'SkipSyncTests', 1); + frame = OpenDisplay([800,600], [140 140 140]); + WindowWasOpen = 1; +end +w = frame.ptr; + +black = BlackIndex(w); +white = WhiteIndex(w); +red = [white(1) black(1) black(1)]/3; +blue = [black(1) black(1) white(1)]./3; +grey = white/3; +if isfield(frame, 'color') + disc.bgcolor = frame.color; +else + disc.bgcolor = black; +end +disc.fgcolor = 2*grey; +disc.bordercolor = grey; +disc.textcolor = white*.75; + +% "Field of View" +if nargin<6 + fov = min(frame.size); +else + fov = FOV; +end + +if nargin<2 + nlevels=[]; % = 8 including zero! +end +if isempty(nlevels) + nlevels=7; % = 8 including zero! +end +if isnan(disc.startlevel) + c=1+floor(rand*(nlevels-1)); +elseif disc.startlevel<1 + c=round(disc.startlevel*nlevels); +else + c=disc.startlevel; +end +small_inc=1; +big_inc = max(small_inc,nlevels/25); + +if nargin<3 + timelimit = Inf; +end +if nargin<4 + labels={'Totalement incertain' 'Sûr et certain' }; +end + + +%% Drawing of the disc +rect = fov*disc.diameter*[1 1]; +pos = RectAlign(rect,frame.size, 'c'); +penwidth = fov*disc.contour*disc.diameter; +Screen('FrameArc', w, disc.bordercolor, pos, 0, 360, penwidth) + +% Remainder +pos2 = pos + penwidth*[1 1 -1 -1]/2; +Screen('FillArc', w, disc.bgcolor, pos2, 360*(1-(nlevels-c)/nlevels), 360); +% Lines +pos3 = [ (pos(3)-pos(1))/2 , (pos(3)-pos(1))/2 ]; +radius = pos3-penwidth/2; +pos3 = pos3 + pos(1:2); +angle = c/nlevels*2*pi; +Screen('DrawLine', w, disc.bordercolor, pos3(1),pos3(2),pos3(1),pos3(2)-radius(2), penwidth); +Screen('DrawLine', w, disc.bordercolor, pos3(1),pos3(2), ... + pos3(1)+radius(1)*(sin(c/nlevels*2*pi)), ... + pos3(2)-radius(2)*(cos(c/nlevels*2*pi)), ... + penwidth); + +% Filling +Screen('FillArc', w, disc.fgcolor, pos2, 0, 360*(c)/nlevels); +%% User interaction +try + ListenChar(2); +end; + +% Flip screen to show initial position +if exist('c:/documents and Settings/knierim/') + onset_time=Screen('Flip', w, 0); +else + onset_time=Screen('Flip', w, 0,1); +end +if sendMarkers + WriteParPort(markers.onset); +end + +% onset_time will be used to re-compute the response time according to the onset +% time of the display +secs(1:2) = onset_time; +rt=NaN; +btnPress = 0; + +% Log info regarding state of the confidence disc at onset +events(1).c=c; +events(1).dir=nan; +events(1).k=[]; +events(1).press_time=secs(1); +fprintf('c = %d ',c); + +while true + % Is response time over? + if (secs(2)-onset_time) > timelimit + fprintf(' >< time over.\n'); + c=NaN; + break + end + % Reads buttons from parallel port + if listenParPort + [btnPress] = ReadParPort(0); + end + [keyIsDown, secs(2), keyCode] = KbCheck; + keyCode=logical([keyCode bitget(btnPress,1:3)]); + if any(keyCode(keys.resp)); + inc=0; + dir=0; + % Subject pressed Escape (or the like) + if any(keyCode(keys.quit)) + rt=NaN; + c=NaN; + break + elseif any(keyCode(keys.ok)) + % Subject has validated his/her response + if sendMarkers + WriteParPort([c,markers.keys(keyCode),markers.onset]); + WaitSecs(0.0078); + end + % log events + events(end+1).c=c; + events(end).press_time=secs(2); + events(end).dir=nan; + events(end).k=find(keyCode); + fprintf(' = %d ; RT=%0.3fs\n',c,secs(2)-onset_time) + break + elseif any(keyCode(keys.more)) + dir=+1; + elseif any(keyCode(keys.less)) + dir=-1; + end + if (secs(2)-secs(1)) > repetitiontime + if sendMarkers + WriteParPort(markers.keys(keyCode)); + WaitSecs(0.0078); + WriteParPort(markers.onset); + end + % Separated clicks + inc=small_inc; + % Update confidence value + c=c+dir*inc; + c=max(c,0); + c=min(c,nlevels); + % log events + events(end+1).c=c; + events(end).press_time=secs(2); + events(end).dir=dir; + events(end).k=find(keyCode); + % fprintf('RT=%0.3fs [key=%s] %d -> c=%d\n',secs(2)-rt,... + % sprintf('%d ',events(end).k),events(end).dir,c) + if dir>0 + fprintf('%s', '+'); + elseif dir<0 + fprintf('%s', '-'); + end + %fprintf('%d', c); + %d'> c=%d\n',secs(2)-onset_time,... + % sprintf('%d ',events(end).k),events(end).dir,c) + end + secs(1)=secs(2); + + % Update the display + % secs(1:nkey-1) = secs(2:nkey); + Screen('FillArc', w, disc.bgcolor, pos2, 360*(1-(nlevels-c)/nlevels), 360); + Screen('DrawLine', w, disc.bordercolor, pos3(1),pos3(2),pos3(1),pos3(2)-radius(2), penwidth); + Screen('DrawLine', w, disc.bordercolor, pos3(1),pos3(2), ... + pos3(1)+radius(1)*(sin(c/nlevels*2*pi)), ... + pos3(2)-radius(2)*(cos(c/nlevels*2*pi)), ... + penwidth); + Screen('FillArc', w, disc.fgcolor, pos2, 0, 360*(c)/nlevels); + if exist('c:/documents and Settings/knierim/') + secs(2)=Screen('Flip', w, 0); + else + secs(2)=Screen('Flip', w, 0,1); + end + + end +end + +try + ListenChar(0); +end; +if WindowWasOpen + Screen('Close', w); + if doSendMarkers + WriteParPort(0); + end +end +rt=secs(end)-onset_time; + diff --git a/mypsychtoolbox/ConfidenceScale2_mod.m b/mypsychtoolbox/ConfidenceScale2_mod.m new file mode 100644 index 0000000..3e73de9 --- /dev/null +++ b/mypsychtoolbox/ConfidenceScale2_mod.m @@ -0,0 +1,181 @@ +function [c,rt,pos]=ConfidenceScale2_mod(frame,nlevels,labels) +% ConfidenceScale - Scale for confidence rating +if nargin<1 + DEBUG=1; +else + DEBUG=0; +end + +%% Behaviour & visual aspect of the scale +% +% Shape parameters +% They are all relative top the field of view (fov) which is itself based +% on the frame/window size : +scale.orientation = 90; % vertical = 0 ; horizontal = 90 +scale.excentricity = .5; % position of the scale on screen +scale.length = .3; % length of the scale +scale.shape = .25; % width-to-length ratio +scale.contour=1/60; % pen width (relatively to frame size) +scale.ticks = 1; % display ticks on the scale + +% NaN = starts at a random position; 0.5 = starts at the middle position; +scale.startlevel = 0.75; + +% Number of consecutive key press to go to fast mode +nkey = 10; +sluggish = 0.1; % minimum time between consecutive keypresses +keytimebreak = 0.3; +% To validate, SS press +% Esc = 27 or SPACE = 32 or RETURN = 13 +OkKey = [32 13]; +EscKey = 27; + +%% Processing of inputs +if nargin<1 + frame =[]; +end +WindowWasOpen = 0; +if isempty(frame) + clc + Screen('Preference', 'SkipSyncTests', 1); + frame = OpenDisplay([800,600], [0]); + frame.window = frame; + WindowWasOpen = 1; +end +if ~isfield(frame, 'rect') + frame.rect= Screen('Rect', frame.ptr); + frame.size = frame.rect(3:4); +end +w = frame.ptr; + +black = BlackIndex(w); +white = WhiteIndex(w); +red = [white(1) black(1) black(1)]/3; +blue = [black(1) black(1) white(1)]./[3 3 1]; +grey = white/3; + +scale.bgcolor = black; +scale.fgcolor = blue; +scale.bordercolor = grey; +scale.textcolor = white*.75; + +% "Field of View" +fov = min(frame.size); + +if nargin<2 + nlevels=20; +end +if isnan(scale.startlevel) + c=floor(rand*(nlevels+1)); +elseif scale.startlevel<1 + c=round(scale.startlevel*nlevels); +else + c=scale.startlevel; +end +small_inc=1; +big_inc = max(small_inc,nlevels/25); + +if nargin<3 + labels={' ' ' ' }; +% labels={'Totalement incertain','Sur de son choix'}; +end + +%% Drawing of the scale +scale.orientation + +rect = fov*scale.length*[scale.shape 1]; +pos = RectAlign(rect,frame.size, 'c'); +% Bottom label +DrawText(w,labels{1}, [mean(pos([1 3])) ... + frame.size(2)*(1-(1-scale.length)/4)], scale.textcolor); +DrawText(w,labels{2}, [mean(pos([1 3])) ... + frame.size(2)*((1-scale.length)/4)], scale.textcolor) + +penwidth = fov*scale.contour; +Screen('FrameRect',w,scale.bordercolor,pos, penwidth); + +% Upper Black rectangle +pos2 = pos + penwidth/2*[1 1 -1 -1]; +pos2 = pos2([1 2 3 2]) + [0 0 0 +1]*(nlevels-c)/(nlevels)*(pos2(4)-pos2(2)); +Screen('FillRect',w,scale.bgcolor,pos2); + +% Lower Filling Rectangle +pos2 = pos + penwidth/2*[1 1 -1 -1]; +pos2 = pos2([1 2 3 4]) + [0 +1 0 0]*(nlevels-c)/(nlevels)*(pos2(4)-pos2(2)); +Screen('FillRect',w,scale.fgcolor,pos2); + +Screen('Flip', w, 0, 0); + + + +%% User interaction + +ListenChar(2); +[keyIsDown,rt]=KbCheck; +secs(1:nkey) = rt; +prevc=c; +while true + [secs(nkey), keyCode] = KbWait; + if any(keyCode(EscKey)) + rt=NaN; + c=NaN; + break + end + if any(keyCode(OkKey)) + break + end + inc=0; + + if (secs(nkey)-secs(nkey-1)) > sluggish + % slow mode + secs(1:nkey)=secs(nkey); + inc=small_inc; + elseif all(diff(secs)>0) + if all(diff(secs) < sluggish) + % fast mode + inc=big_inc; + else + % wait that nkey have been sampled + inc=0; + end + end + % change cursor only once we are in slow or fast mode + if keyCode(38) + c=min(c+inc,nlevels); + elseif keyCode(40) + c=max(c-inc,0); + end + secs(1:nkey-1) = secs(2:nkey); + + %border + penwidth = fov*scale.contour; + Screen('FrameRect',w,scale.bordercolor,pos, penwidth); + + % Upper Black rectangle + pos2 = pos + penwidth/2*[1 1 -1 -1]; + pos2 = pos2([1 2 3 2]) + [0 0 0 +1]*(nlevels-c)/(nlevels)*(pos2(4)-pos2(2)); + Screen('FillRect',w,scale.bgcolor,pos2); + + % Lower Filling Rectangle + pos2 = pos + penwidth/2*[1 1 -1 -1]; + pos2 = pos2([1 2 3 4]) + [0 +1 0 0]*(nlevels-c)/(nlevels)*(pos2(4)-pos2(2)); + Screen('FillRect',w,scale.fgcolor,pos2); + + + DrawText(w,sprintf('%2.0f%', 100*prevc/nlevels), [mean(pos([1 3])) ... + frame.size(2)*((1.5-scale.length)/4)], scale.bgcolor); + DrawText(w,sprintf('%2.0f%', 100*c/nlevels), [mean(pos([1 3])) ... + frame.size(2)*((1.5-scale.length)/4)], 200); + prevc=c; + Screen('Flip', w, 0, 0); + + +end +try +ListenChar(0); +end; +if WindowWasOpen + Screen('Close', w); +end +rt=secs(nkey)-rt; + diff --git a/mypsychtoolbox/ConfidenceScale3.m b/mypsychtoolbox/ConfidenceScale3.m new file mode 100644 index 0000000..7e87264 --- /dev/null +++ b/mypsychtoolbox/ConfidenceScale3.m @@ -0,0 +1,237 @@ +function [c,rt,pos]=ConfidenceScale(frame,nlevels,labels) %#ok +% ConfidenceScale - Scale for confidence rating +% [] = ConfidenceScale(input) +% +% Example +% >> ConfidenceScale +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2009 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2009-03-?? Creation +% KND 2009-03-?? Now allows horiz orientation +% +% ----------------------------- Script History --------------------------------- + + +if nargin<1 + DEBUG=1; +else + DEBUG=0; +end + +%% Behaviour & visual aspect of the scale +% +% Shape parameters +% They are all relative top the field of view (fov) which is itself based +% on the frame/window size : +scale.orientation = 00; % vertical = 0 ; horizontal = 90 +scale.excentricity = .5; % position of the scale on screen +scale.length = .3; % length of the scale +scale.shape = .25; % width-to-length ratio +scale.contour=1/60; % pen width (relatively to frame size) +scale.ticks = 1; % display ticks on the scale + +% NaN = starts at a random position; 0.5 = starts at the middle position; +scale.startlevel = 0.75; + +% Number of consecutive key press to go to fast mode +nkey = 10; +sluggish = 0.1; % minimum time between consecutive keypresses +keytimebreak = 0.3; +% To validate, SS press +% Esc = 27 or SPACE = 32 or RETURN = 13 +OkKey = [32 13]; +EscKey = 27; + +%% Processing of inputs +if nargin<1 + frame =[]; +end +WindowWasOpen = 0; +if isempty(frame) + clc + Screen('Preference', 'SkipSyncTests', 1); + frame = OpenDisplay([800,600], [0]); + frame.window = frame; + WindowWasOpen = 1; +end +if ~isfield(frame, 'rect') + frame.rect= Screen('Rect', frame.ptr); + frame.size = frame.rect(3:4); +end +w = frame.ptr; + +black = BlackIndex(w); +white = WhiteIndex(w); +red = [white(1) black(1) black(1)]/3; +blue = [black(1) black(1) white(1)]./[3 3 1]; +grey = white/3; + +scale.bgcolor = black; +scale.fgcolor = blue; +scale.bordercolor = grey; +scale.textcolor = white*.75; + +% "Field of View" +fov = min(frame.size); + +if nargin<2 + nlevels=20; +end +if isnan(scale.startlevel) + c=floor(rand*(nlevels+1)); +elseif scale.startlevel<1 + c=round(scale.startlevel*nlevels); +else + c=scale.startlevel; +end +small_inc=1; +big_inc = max(small_inc,nlevels/25); + +if nargin<3 + labels={' ' ' ' }; +% labels={'Totalement incertain','Sur de son choix'}; +end + +%% Drawing of the scale +if scale.orientation ~= 90 && scale.orientation ~= 0 + error('Can''t deal with this orientation'); +end +if scale.orientation == 90 + rect = fov*scale.length*[1 scale.shape]; + pos = RectAlign(rect,frame.size, 'c'); + % Left label + DrawText(w,labels{1}, [... + mean(pos([1 3])) ... + frame.size(2)*(1-(1-scale.length)/4) ... + ], scale.textcolor); + DrawText(w,labels{2}, [mean(pos([1 3])) ... + frame.size(2)*((1-scale.length)/4)], scale.textcolor) +else + rect = fov*scale.length*[scale.shape 1]; + pos = RectAlign(rect,frame.size, 'c'); + % Bottom label + DrawText(w,labels{1}, [mean(pos([1 3])) ... + frame.size(2)*(1-(1-scale.length)/4)], scale.textcolor); + DrawText(w,labels{2}, [mean(pos([1 3])) ... + frame.size(2)*((1-scale.length)/4)], scale.textcolor) +end + +penwidth = fov*scale.contour; +Screen('FrameRect',w,scale.bordercolor,pos, penwidth); + +if scale.orientation == 90 + % Right Black rectangle + pos2 = pos + penwidth/2*[1 1 -1 -1]; + pos2 = pos2([1 2 3 4]) + [+1 0 0 0]*(c)/(nlevels)*(pos2(3)-pos2(1)); + Screen('FillRect',w,scale.bgcolor,pos2); + % Left Filling Rectangle + pos2 = pos + penwidth/2*[1 1 -1 -1]; + pos2 = pos2([1 2 1 4]) + [0 0 +1 0]*(c)/(nlevels)*(pos2(3)-pos2(1)); + Screen('FillRect',w,scale.fgcolor,pos2); +else + % Upper Black rectangle + pos2 = pos + penwidth/2*[1 1 -1 -1]; + pos2 = pos2([1 2 3 2]) + [0 0 0 +1]*(nlevels-c)/(nlevels)*(pos2(4)-pos2(2)); + Screen('FillRect',w,scale.bgcolor,pos2); + % Lower Filling Rectangle + pos2 = pos + penwidth/2*[1 1 -1 -1]; + pos2 = pos2([1 2 3 4]) + [0 +1 0 0]*(nlevels-c)/(nlevels)*(pos2(4)-pos2(2)); + Screen('FillRect',w,scale.fgcolor,pos2); +end + +Screen('Flip', w, 0, 0); + + + +%% User interaction + +ListenChar(2); +[keyIsDown,rt]=KbCheck; +secs(1:nkey) = rt; +prevc=c; +while true + [secs(nkey), keyCode] = KbWait; + if any(keyCode(EscKey)) + rt=NaN; + c=NaN; + break + end + if any(keyCode(OkKey)) + break + end + inc=0; + + if (secs(nkey)-secs(nkey-1)) > sluggish + % slow mode + secs(1:nkey)=secs(nkey); + inc=small_inc; + elseif all(diff(secs)>0) + if all(diff(secs) < sluggish) + % fast mode + inc=big_inc; + else + % wait that nkey have been sampled + inc=0; + end + end + % change cursor only once we are in slow or fast mode + if keyCode(38) || keyCode(39) + c=min(c+inc,nlevels); + elseif keyCode(40) || keyCode(37) + c=max(c-inc,0); + end + secs(1:nkey-1) = secs(2:nkey); + + %border + penwidth = fov*scale.contour; + Screen('FrameRect',w,scale.bordercolor,pos, penwidth); + + if scale.orientation == 90 + % Right Black rectangle + pos2 = pos + penwidth/2*[1 1 -1 -1]; + pos2 = pos2([1 2 3 4]) + [+1 0 0 0]*(c)/(nlevels)*(pos2(3)-pos2(1)); + Screen('FillRect',w,scale.bgcolor,pos2); + + % Left Filling Rectangle + pos2 = pos + penwidth/2*[1 1 -1 -1]; + pos2 = pos2([1 2 1 4]) + [0 0 +1 0]*(c)/(nlevels)*(pos2(3)-pos2(1)); + Screen('FillRect',w,scale.fgcolor,pos2); + + else + % Upper Black rectangle + pos2 = pos + penwidth/2*[1 1 -1 -1]; + pos2 = pos2([1 2 3 2]) + [0 0 0 +1]*(nlevels-c)/(nlevels)*(pos2(4)-pos2(2)); + Screen('FillRect',w,scale.bgcolor,pos2); + + % Lower Filling Rectangle + pos2 = pos + penwidth/2*[1 1 -1 -1]; + pos2 = pos2([1 2 3 4]) + [0 +1 0 0]*(nlevels-c)/(nlevels)*(pos2(4)-pos2(2)); + Screen('FillRect',w,scale.fgcolor,pos2); + end + + DrawText(w,sprintf('%2.0f%', 100*prevc/nlevels), [mean(pos([1 3])) ... + frame.size(2)*((1.5-scale.length)/4)], scale.bgcolor); + DrawText(w,sprintf('%2.0f%', 100*c/nlevels), [mean(pos([1 3])) ... + frame.size(2)*((1.5-scale.length)/4)], 200); + prevc=c; + Screen('Flip', w, 0, 0); + + +end +try + ListenChar(0); +end; +if WindowWasOpen + Screen('Close', w); +end +rt=secs(nkey)-rt; + diff --git a/mypsychtoolbox/DrawText.m b/mypsychtoolbox/DrawText.m new file mode 100644 index 0000000..3643a3c --- /dev/null +++ b/mypsychtoolbox/DrawText.m @@ -0,0 +1,189 @@ +function [varargout] = DrawText(wPtr,txt,alignment,fgcol,bgcol) +%DRAWTEXT - One line description goes here. +% [newXY] = DrawText(wPtr, text [,alignment ,fgColor ,bgColor]) +% Easy way to control position of the text on screen +% +% alignment may be any of: 'l' (left), 'c' (horizontally centered), +% 'r' (right), 't' (top), 'm' (vertically middle), 'b' (bottom) +% Any single letter can be used multiple times to: 'llc' position +% the text quite on the left (66% of the half-width), whereas 'rccc' +% puts it almost in the center (25% of the half-width off to the +% right). +% Alignment can also be specified as 2*1 or 4*1 numerical vector +% +% Example +% >> DrawText(w,'bla bla', 'ct') positions the text 'bla bla' at the +% (c)entered at the (t)op +% +% >> DrawText(w,'bla bla', 'ct') positions the text 'bla bla' at the +% (c)entered at the (t)op +% +% Based on: Screen('Drawtext?') +% See also: DrawFormattedtext + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2009 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2009-02-02 Creation +% +% ----------------------------- Script History --------------------------------- + +if nargin < 1 + error('DrawText: window handle missing!'); +end + +if nargin < 2 || isempty(txt) + % Empty text string -> Nothing to do. + return; +end +if isempty(wPtr) + clc + frame = OpenDisplay; + wPtr=frame.ptr; +end +if nargin<3 + alignment=''; +end +if nargin<4 + fgcol=[255]; +end +if nargin<5 + bgcol=[]; +end + +% [x,x,x]=intersect(alignment,'lcr'); +% if length(x)>1 +% error('Wrong horizontal alignment!'); +% end +% [y,y,y]=intersect(alignment,'tmb'); +% if length(y)>1 +% error('Wrong vertical alignment!'); +% end + +if ischar(txt) + txt={txt}; +end +wh =[]; +for i=1:numel(txt) + if isempty(txt{i}) + wh = [wh; Screen('TextBounds', wPtr, ' ')]; + wh(end,3)=0; + else + wh = [wh; Screen('TextBounds', wPtr, txt{i})]; + end +end +wh=wh(:,3:4)-wh(:,1:2); +%wh(:,1) = max(wh(:,1)); +%wh(:,2)= cumsum(wh(:,2)); + +xy=zeros(size(wh,1),2); +if isnumeric(alignment) + if numel(alignment) == 2 + x0=alignment(1); + y0=alignment(2); + x=0.*x0; + y=0.*y0; + x(x0==-1)=+1; + x(x0==+1)=-1; + y(y0==-1)=+1; + y(y0==+1)=-1; + + elseif numel(alignment) == 4 + x0=alignment(1); + y0=alignment(2); + x=alignment(3); + y=alignment(4); + else + error('Wrong numeric alignment') + end +else + x0=0; + y0=0; + alignment=lower(alignment); + if isequal(alignment, 'center') + alignment='c'; + end + if isequal(alignment, 'right') + alignment='r'; + x=-1; + end + if isequal(alignment, 'left') + alignment='l'; + x=+1; + end + if isequal(alignment, 'top') + alignment='t'; + y=+1; + end + if isequal(alignment, 'middle') + alignment='m'; + end + if isequal(alignment, 'bottom') + alignment='b'; + y=-1; + end + + [z,z]=ismember(alignment,'lcrtmb'); + if any(z==0) + error('Wrong alignment parameter!'); + end + if any(z<=3) + x0=mean(z(z<=3),2)-2; + else + x0=NaN; + end + x0(isnan(x0))=0; + if any(z>=4) + y0=mean(z(z>=4),2)-5; + else + y0=NaN; + end + y0(isnan(y0))=0; + x=0.*x0; + y=0.*y0; + x(x0==-1)=+1; + x(x0==+1)=-1; + y(y0==-1)=+1; + y(y0==+1)=-1; +end + +%scr = Screen('Resolution',wPtr); +r = [Screen('Rect', wPtr)]; +scr.width = r(3)-r(1); +scr.height = r(4)-r(2); + +if abs(x0)<=1 + x0=(1+x0)*scr.width/2; +end +if abs(y0)<=1 + y0=(1+y0)*scr.height/2; +end + +if x>1 + xy(:,1) = x0 + x; +else + xy(:,1) = x0 + (x-1).*wh(:,1)/2; +end +if y>1 + xy(:,2) = y0 + y; +else + xy(:,2) = y0 + (y-1).*sum(wh(:,2))/2+cumsum([0;wh(1:end-1,2)]); +end +for i=1:numel(txt) + if isempty(txt{i}) + xy2(i,1)=xy(i,1); + xy2(i,2)=xy(i,2); + else + [xy2(i,1) xy2(i,2)]=Screen('DrawText',wPtr,txt{i},xy(i,1),xy(i,2),fgcol,bgcol); + end +end +varargout={}; +if nargout>0 + varargout={xy2,xy,wh}; +end +return diff --git a/mypsychtoolbox/FlipWindow.m b/mypsychtoolbox/FlipWindow.m new file mode 100644 index 0000000..6125f94 --- /dev/null +++ b/mypsychtoolbox/FlipWindow.m @@ -0,0 +1,47 @@ +function [VBLTimestamp StimulusOnsetTime FlipTimestamp Missed Beampos] = FlipWindow(windowPtr,when,dontclear,dontsync,multiflip) +%FLIPWINDOW - Oneliner for Screen('Flip',...) +% [VBLT, OnsetTime, FlipT, Missed Beampos] = +% FlipWindow flips the last window +% FlipWindow(windowPtr) +% +% Example +% >> FlipWindow +% +% See also: Screen('Flip?') + +% Author: K. N'Diaye (kndiaye01gmail.com) +% Copyright (C) 2011 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2011-02-15 Creation +% +% ----------------------------- Script History --------------------------------- + +if nargin<1 + windowPtr=[]; +end +if isempty(windowPtr) + windowPtr = Screen('Windows'); + windowPtr = windowPtr(end); +end +if isempty(windowPtr) + warning('FlipWindow:NoWindow', 'No window found'); + return +end +% when,dontclear,dontsync,multiflip) +switch nargin + case {0 1} + [VBLTimestamp StimulusOnsetTime FlipTimestamp Missed Beampos] = Screen('Flip',windowPtr); + case 2 + [VBLTimestamp StimulusOnsetTime FlipTimestamp Missed Beampos] = Screen('Flip',windowPtr,when); + case 3 + [VBLTimestamp StimulusOnsetTime FlipTimestamp Missed Beampos] = Screen('Flip',windowPtr,when,dontclear); + case 4 + [VBLTimestamp StimulusOnsetTime FlipTimestamp Missed Beampos] = Screen('Flip',windowPtr,when,dontclear,dontsync); + case 5 + [VBLTimestamp StimulusOnsetTime FlipTimestamp Missed Beampos] = Screen('Flip',windowPtr,when,dontclear,dontsync,multiflip); +end \ No newline at end of file diff --git a/mypsychtoolbox/KbNameCell.m b/mypsychtoolbox/KbNameCell.m new file mode 100644 index 0000000..10381b7 --- /dev/null +++ b/mypsychtoolbox/KbNameCell.m @@ -0,0 +1,7 @@ +function [kbNameResult] = KbName(arg); +% KbNameCell - Forec KbName output into cell (for strings) +kbNameResult = KbName(arg); +if isnumeric(arg) && ~iscell(kbNameResult) + kbNameResult = {kbNameResult}; +end + diff --git a/mypsychtoolbox/OpenDisplay.m b/mypsychtoolbox/OpenDisplay.m new file mode 100644 index 0000000..5935196 --- /dev/null +++ b/mypsychtoolbox/OpenDisplay.m @@ -0,0 +1,266 @@ +function [window] = OpenDisplay(subscreen,color,screen_number,debug) +% OpenDisplay - Easy open of the display using Psychtoolbox's Screen +% [window] = OpenDisplay(subscreen,color) +%% INPUTS +if nargin<2 + color=[0 0 0]; +end +if nargin<1 + subscreen = [ 640 480 ]; +end +if nargin<4 + debug = ~isempty(subscreen); +end +%% Invoke PTB OpenGL functions +AssertOpenGL; +scr.Screens = Screen('Screens'); +scr.nScreens = numel(scr.Screens); +if nargin<3 + scr.screenNumber = max(scr.Screens); +else + scr.screenNumber = screen_number; +end +%scr.Resolution = Screen('Resolution',scr.screenNumber); +kPNFOW = []; + +if numel(subscreen)==2 + if isempty(scr.screenNumber) + scr.screenNumber = 0; + end + if scr.nScreens==1 + %do nothing + [wh]=Screen('Rect', scr.screenNumber); + subscreen = RectPosition(subscreen,wh,'ctmr'); + elseif all(Screen('Rect', 0)>=0) && all(Screen('Rect', 2)>=0) + if isempty(scr.screenNumber) + scr.screenNumber = 1; + end + [wh]=Screen('Rect', scr.screenNumber); + % if scr.screenNumber == 2 + % wh(:,1) = wh(:,1) - wh(:,3); + % wh(:,3) = 0; + % end + %subscreen = RectPosition(subscreen,wh,'rrrctttm'); + subscreen = RectPosition(subscreen,wh,'c'); + else + [wh]=Screen('Rect',2); + subscreen = 100+[ wh(1) 0 subscreen+[wh(1) 0] ]; + end +end + +if debug + Screen('Preference', 'SkipSyncTests', 1); +else + % kPsychNeedFastOffscreenWindows + kPNFOW = kPsychNeedFastOffscreenWindows; + Screen('Preference', 'SkipSyncTests', 0); +end +Screen('Preference', 'Verbosity', 1); +Screen('Preference', 'VBLTimeStampingMode', -1); + +window = struct; + +if numel(subscreen)==0 + % Open whole screen display + [window.ptr, window.rect] = Screen('OpenWindow', ... + scr.screenNumber, color, [], [], [], [], [], ... + kPNFOW); + [subscreen]=Screen('Rect', scr.screenNumber); + if scr.screenNumber == 2 + subscreen(:,1) = subscreen(:,1) - subscreen(:,3); + subscreen(:,3) = 0; + end + +else + %Open a smaller graphic window in the screen + [window.ptr, window.rect] = Screen('OpenWindow', ... + scr.screenNumber, color, subscreen, [], [], [], [], ... + kPNFOW); +end +window.screenNumber = scr.screenNumber; + + window.screen_rect = subscreen; +if scr.nScreens > 1 + all(Screen('Rect', 2)>=0) && all(Screen('Rect', 0)>=0) + window.screen_rect([1 3]) = window.screen_rect([1 3]) + Screen('Rect',2)*[-1;0;1;0]; +end + +window.color=color; +[window.size(1), window.size(2)] = Screen('WindowSize', window.ptr); +% Enable alpha blending with proper blend-function. We need it +% for drawing of smoothed points: +try + Screen('BlendFunction', window.ptr, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); +catch +end +Screen('Flip', window.ptr); +window.fps = Screen('FrameRate', window.ptr); +window.ifi = 1./window.fps; + +try + if ~EXPERIMENT.DEBUG + HideCursor; + Priority(MaxPriority(window.ptr)); + % Retrieves flip interval only for fullscreen mode + window.ifi = Screen('GetFlipInterval', window.ptr, 100, 50e-6, 10); + % Retrieves number of frames per second + window.fps = Screen('FrameRate', window.ptr); + if window.fps==0 + window.fps=1/window.ifi; + end; + + % Color calibration + % LoadIdentityClut(window.ptr); + % window.clut = CreateCalibratedClut(screen); + else + fprintf('Debug mode required\n'); + end +catch + fprintf('Debug mode\n'); +end + + + +return + +% % Activate compatibility mode: Try to behave like the old MacOS-9 Psychtoolbox: +% oldEnableFlag=Screen('Preference', 'EmulateOldPTB', [enableFlag]); +% +% % Open or close a window or texture: +% [windowPtr,rect]=Screen('OpenWindow',windowPtrOrScreenNumber [,color] [,rect] [,pixelSize] [,numberOfBuffers] [,stereomode] [,multisample][,imagingmode]); +% [windowPtr,rect]=Screen('OpenOffscreenWindow',windowPtrOrScreenNumber [,color] [,rect] [,pixelSize]); +% textureIndex=Screen('MakeTexture', WindowIndex, imageMatrix [, optimizeForDrawAngle=0] [, enforcepot=0] [, floatprecision=0] [, textureOrientation=0]); +% Screen('Close', windowOrTextureIndex); +% Screen('CloseAll'); +% +% % Draw lines and solids like QuickDraw and DirectX (OS 9 and Windows): +% Screen('SelectStereoDrawBuffer', windowPtr, bufferid); +% Screen('DrawLine', windowPtr [,color], fromH, fromV, toH, toV [,penWidth]); +% Screen('DrawArc',windowPtr,[color],[rect],startAngle,arcAngle) +% Screen('FrameArc',windowPtr,[color],[rect],startAngle,arcAngle[,penWidth] [,penHeight] [,penMode]) +% Screen('FillArc',windowPtr,[color],[rect],startAngle,arcAngle) +% Screen('FillRect', windowPtr [,color] [,rect] ); +% Screen('FrameRect', windowPtr [,color] [,rect] [,penWidth]); +% Screen('FillOval', windowPtr [,color] [,rect]); +% Screen('FrameOval', windowPtr [,color] [,rect] [,penWidth] [,penHeight] [,penMode]); +% Screen('FillPoly', windowPtr [,color], pointList); +% +% % New OpenGL functions for OS X: +% Screen('glPoint', windowPtr, color, x, y [,size]); +% Screen('gluDisk', windowPtr, color, x, y [,size]); +% Screen('DrawDots', windowPtr, xy [,size] [,color] [,center] [,dot_type]); +% Screen('DrawLines', windowPtr, xy [,width] [,colors] [,center] [,smooth]); +% [sourceFactorOld, destinationFactorOld]=('BlendFunction', windowIndex, [sourceFactorNew], [destinationFactorNew]); +% +% % Draw Text in windows +% textModes = Screen('TextModes'); +% oldCopyMode=Screen('TextMode', windowPtr [,textMode]); +% oldTextSize=Screen('TextSize', windowPtr [,textSize]); +% oldStyle=Screen('TextStyle', windowPtr [,style]); +% [oldFontName,oldFontNumber]=Screen(windowPtr,'TextFont' [,fontNameOrNumber]); +% [normBoundsRect, offsetBoundsRect]=Screen('TextBounds', windowPtr, text); +% [newX,newY]=Screen('DrawText', windowPtr, text [,x] [,y] [,color] [,backgroundColor] [,yPositionIsBaseline]); +% oldTextColor=Screen('TextColor', windowPtr [,colorVector]); +% oldTextBackgroundColor=Screen('TextBackgroundColor', windowPtr [,colorVector]); +% +% % Copy an image, very quickly, between textures, offscreen windows and onscreen windows. +% [resident [texidresident]] = Screen('PreloadTextures', windowPtr [, texids]); +% Screen('DrawTexture', windowPointer, texturePointer [,sourceRect] [,destinationRect] [,rotationAngle] [, filterMode] [, globalAlpha]); +% Screen('CopyWindow', srcWindowPtr, dstWindowPtr, [srcRect], [dstRect], [copyMode]) +% +% % Copy an image, slowly, between matrices and windows : +% imageArray=Screen('GetImage', windowPtr [,rect] [,bufferName]); +% Screen('PutImage', windowPtr, imageArray [,rect]); +% +% % Synchronize with the window's screen (on-screen only): +% [VBLTimestamp StimulusOnsetTime FlipTimestamp Missed Beampos] = Screen('Flip', windowPtr [, when] [, dontclear] [, dontsync] [, multiflip]); +% [telapsed] = Screen('DrawingFinished', windowPtr [, dontclear] [, sync]); +% framesSinceLastWait = Screen('WaitBlanking', windowPtr [, waitFrames]); +% +% % Load color lookup table of the window's screen (on-screen only): +% [gammatable, dacbits, reallutsize] = Screen('ReadNormalizedGammaTable', windowPtrOrScreenNumber); +% Screen('LoadNormalizedGammaTable', windowPtrOrScreenNumber, table [, loadOnNextFlip]); +% oldclut = Screen('LoadCLUT', windowPtrOrScreenNumber [, clut] [, startEntry=0] [, bits=8]); +% +% % Get (and set) information about a window or screen: +% screenNumbers=Screen('Screens); +% windowPtrs=Screen('Windows'); +% kind=Screen(windowPtr, 'WindowKind'); +% isOffscreen=Screen(windowPtr,'IsOffscreen'); +% hz=Screen('FrameRate', windowPtrOrScreenNumber [, mode] [, reqFrameRate]); +% hz=Screen('NominalFrameRate', windowPtrOrScreenNumber [, mode] [, reqFrameRate]); +% [ monitorFlipInterval nrValidSamples stddev ]=Screen('GetFlipInterval', windowPtr [, nrSamples] [, stddev] [, timeout]); +% screenNumber=Screen('WindowScreenNumber', windowPtr); +% rect=Screen('Rect', windowPtrOrScreenNumber); +% pixelSize=Screen('PixelSize', windowPtrOrScreenNumber); +% pixelSizes=Screen('PixelSizes', windowPtrOrScreenNumber); +% [width, height]=Screen('WindowSize', windowPointerOrScreenNumber); +% [width, height]=Screen('DisplaySize', ScreenNumber); +% [oldmaximumvalue oldclampcolors] = Screen('ColorRange', windowPtr [, maximumvalue][, clampcolors=1]); +% +% % Get/set details of environment, computer, and video card (i.e. screen): +% struct=Screen('Version'); +% comp=Screen('Computer'); +% oldBool=Screen('Preference', 'IgnoreCase' [,bool]); +% tick0Secs=Screen('Preference', 'Tick0Secs', tick0Secs); +% psychTableVersion=Screen('Preference', 'PsychTableVersion'); +% mexFunctionName=Screen('Preference', 'PsychTableCreator'); +% proc=Screen('Preference', 'Process'); +% oldBool=Screen('Preference','Backgrounding'); +% oldSecondsMultiplier=Screen('Preference', 'SecondsMultiplier'); +% Screen('Preference','SkipSyncTests', skipTest); +% Screen('Preference','VisualDebugLevel', level (valid values between 0 and 5)); +% Screen('Preference', 'ConserveVRAM', mode (valid values between 0 and 3)); +% Screen('Preference', 'Enable3DGraphics', [enableFlag]); +% +% % Helper functions. Don't call these directly, use eponymous wrappers: +% [x, y, buttonVector]= Screen('GetMouseHelper', numButtons); +% Screen('HideCursorHelper', windowPntr); +% Screen('ShowCursorHelper', windowPntr); +% Screen('SetMouseHelper', windowPntrOrScreenNumber, x, y); +% +% % Internal testing of Screen +% timeList= Screen('GetTimelist'); +% Screen('ClearTimelist'); +% Screen('Preference','DebugMakeTexture', enableDebugging); +% +% % Movie and multimedia playback functions: +% [ moviePtr [duration] [fps] [width] [height] [count]]=Screen('OpenMovie', windowPtr, moviefile [, async=0]); +% Screen('CloseMovie', moviePtr); +% [ texturePtr [timeindex]]=Screen('GetMovieImage', windowPtr, moviePtr, [waitForImage], [fortimeindex]); +% [droppedframes] = Screen('PlayMovie', moviePtr, rate, [loop], [soundvolume]); +% timeindex = Screen('GetMovieTimeIndex', moviePtr); +% [oldtimeindex] = Screen('SetMovieTimeIndex', moviePtr, timeindex); +% +% % Video capture functions: +% videoPtr =Screen('OpenVideoCapture', windowPtr [, deviceIndex] [,roirectangle] [, pixeldepth] [, numbuffers] [, allowfallback] [, targetmoviename] [, recordingflags]); +% Screen('CloseVideoCapture', capturePtr); +% [fps starttime] = Screen('StartVideoCapture', capturePtr [, captureRateFPS] [, dropframes=0] [, startAt]); +% droppedframes = Screen('StopVideoCapture', capturePtr); +% [texturePtr [capturetimestamp] [droppedcount] [summed_intensity]]=Screen('GetCapturedImage', windowPtr, capturePtr [, waitForImage=1] [,oldTexture] [,specialmode]); +% oldvalue = Screen('SetVideoCaptureParameter', capturePtr, 'parameterName' [, value]); +% +% % Low level direct access to OpenGL-API functions: +% % Online info for each function available by opening a terminal window +% % and typing 'man Functionname' + Enter. +% +% Screen('glPushMatrix', windowPtr); +% Screen('glPopMatrix', windowPtr); +% Screen('glLoadIdentity', windowPtr); +% Screen('glTranslate', windowPtr, tx, ty [, tz]); +% Screen('glScale', windowPtr, sx, sy [, sz]); +% Screen('glRotate', windowPtr, angle, [rx = 0], [ry = 0] ,[rz = 1]); +% +% % Support for 3D graphics rendering and for interfacing with external OpenGL code: +% Screen('Preference', 'Enable3DGraphics', [enableFlag]); % Enable 3D gfx support. +% Screen('BeginOpenGL', windowPtr [, sharecontext]); % Prepare window for external OpenGL drawing. +% Screen('EndOpenGL', windowPtr); % Finish external OpenGL drawing. +% [textureHandle rect] = Screen('SetOpenGLTextureFromMemPointer', windowPtr, textureHandle, imagePtr, width, height, depth [, upsidedown][, target][, glinternalformat][, gltype][, extdataformat]); +% [textureHandle rect] = Screen('SetOpenGLTexture', windowPtr, textureHandle, glTexid, target [, glWidth] [, glHeight] [, glDepth]); +% [ gltexid gltextarget texcoord_u texcoord_v ] =Screen('GetOpenGLTexture', windowPtr, textureHandle [, x][, y]); +% +% % Support for plugins and for builtin high performance image processing pipeline: +% [ret1, ret2, ...] = Screen('HookFunction', windowPtr, 'Subcommand', 'HookName', arg1, arg2, ...); +% proxyPtr = Screen('OpenProxy', windowPtr [, imagingmode]); +% transtexid = Screen('TransformTexture', sourceTexture, transformProxyPtr +% [, targetTexture]); \ No newline at end of file diff --git a/mypsychtoolbox/RectAlign.m b/mypsychtoolbox/RectAlign.m new file mode 100644 index 0000000..6a65a67 --- /dev/null +++ b/mypsychtoolbox/RectAlign.m @@ -0,0 +1,127 @@ +function [xywh,wh,scr] = RectAlign(wh,scr,varargin) +%RectAlign - Compute coordinates to align rectangular objects +% [xywh] = RectAlign(wh,r,alignment) +% wh = the N-by-2 array of widths and heights of the object to align +% r = the [width height] of the containing rectangle/window +% or a screen number/pointer, or a screen resolution structure, +% as output by Screen('Resolution') +% alignment is a string specifying the style of alignment to use : +% '[C]' for centered (default) | '[L]eft' | '[R]ight' | +% '[T]op' | '[M]iddle' | '[B]ottom' (or any combination of these +% letters, e.g., 'ctm' for horizontally centered but vertically +% at mid-way between the top and the middle of the screen. +% +%OUTPUTS: +% xywh = a N-by-4 array with: +% xywh(:,1) = the horizontal displacement(s) +% xywh(:,2) = the vertical displacement(s) +% xywh(:,3) = right border, i.e. xywh(:,3)-xywh(:,1) = wh(:,1) +% xywh(:,4) = bottom border, i.e. xywh(:,4)-xywh(:,2) = wh(:,2) +% +% Example +% RectAlign([300 200], [640 480]) +% RectAlign([300 NaN], [640 480]) % define a square window +% RectAlign([.3 200], [frame.ptr]) +% +% See also + +% Author: Karim NDiaye +% Created: Jan 2009 +% Copyright 2009 + +if nargin<2 + scr = 0; +end +if numel(scr)==1 + scr = Screen('Rect',scr); +end +if isstruct(scr) + scr = [scr.width scr.height]; +end +if size(scr,2) == 2 + scr = [0 0 scr(1) scr(2)]; +end +if size(wh,2)==4 + wh = [ wh(:,3)-wh(:,1) wh(:,4)-wh(:,2) ]; +end + +[i,j]=find(wh<=1); +k=find(wh<=1); +wh(k(j==1))=wh(k(j==1))*(scr(3)-scr(1)); +wh(k(j==2))=wh(k(j==2))*(scr(4)-scr(2)); + +if nargin<3 + alignment=''; +else + alignment=varargin{1}; +end +if isnumeric(alignment) + if size(alignment,2) == 2 + x=alignment(:,1); + y=alignment(:,2); + else + error('myspychoolbox:RectAlign','Wrong numeric alignment') + end +else + alignment=lower(alignment); + if isequal(alignment, 'center') + alignment='c'; + end + if isequal(alignment, 'right') + alignment='r'; + end + if isequal(alignment, 'left') + alignment='l'; + end + if isequal(alignment, 'top') + alignment='t'; + end + if isequal(alignment, 'middle') + alignment='m'; + end + if isequal(alignment, 'bottom') + alignment='b'; + end + + [z,z]=ismember(alignment,'lcrtmb'); + if any(z==0) + error('myspychoolbox:RectAlign','Wrong alignment parameter!'); + end + if any(z<=3) + x=mean(z(z<=3))-2; + else + x=NaN; + end + if isnan(x) + x=0; + end + if any(z>=4) + y=mean(z(z>=4))-5; + else + y=NaN; + end + if isnan(y) + y=0; + end +end +wnan = isnan(wh(:,1)); +hnan = isnan(wh(:,2)); +if numel(scr)==2 + scr=[0 0 scr ]; +end +wrel = abs(wh(:,1))<1; +hrel = abs(wh(:,2))<1; +wh(wrel,1)=(scr(:,3)-scr(:,1)).*wh(wrel,1); +wh(hrel,2)=(scr(:,4)-scr(:,2)).*wh(hrel,2); + +wh(wnan,1) = wh(wnan,2); +wh(hnan,2) = wh(hnan,1); + +% xywh(:,1) = (1+x) * (scr(1)-wh(:,1))/2; +% xywh(:,2) = (1+y) * (scr(2)-wh(:,2))/2; +xywh(:,1) = scr(:,1) + (1+x) * ((scr(:,3)-scr(:,1))-wh(:,1))/2; +xywh(:,2) = scr(:,2) + (1+y) * ((scr(:,4)-scr(:,2))-wh(:,2))/2; + + +xywh(:,3) = xywh(:,1) + wh(:,1); +xywh(:,4) = xywh(:,2) + wh(:,2); diff --git a/mypsychtoolbox/RectPosition.m b/mypsychtoolbox/RectPosition.m new file mode 100644 index 0000000..604ea6c --- /dev/null +++ b/mypsychtoolbox/RectPosition.m @@ -0,0 +1,4 @@ +function [xywh,wh,scr] = RectPosition(wh,scr,varargin) +% RectPosition - Deprecated see RectAlign +warning('MyPsychtoolbox:RectPositionDeprecated', 'RectPosition is deprecated; use RectAlign() instead'); +[xywh,wh,scr] = RectAlign(wh,scr,varargin{:}); diff --git a/mypsychtoolbox/WaitAnyPress.m b/mypsychtoolbox/WaitAnyPress.m new file mode 100644 index 0000000..3b63302 --- /dev/null +++ b/mypsychtoolbox/WaitAnyPress.m @@ -0,0 +1,102 @@ +function [rt, k, t0, t] = WaitAnyPress(keys, timeout, newOnly) +% WaitAnyPress - Wait for a button or a key press +% +% [rt, k, t0, t] = WaitAnyPress(keys, timeout, newOnly) +% +%INPUTS: +% keys: code of keys to listen to. Keyboard keys are mapped on 8:256. +% By default, mouse clicks (which are mapped on 1=Left, 2=Right, +% 3=??, 4=Middle, 5=Backward, 6=Forward) are not listened. Parallel +% port is read with ReadParPort(), if available. Bits are assigned +% to the keycodes [257:264] so that: +% 1 (bit0) = 257 (left LENA-button) +% 2 (bit1) = 258 (middle LENA-button) +% 4 (bit2) = 259 (right LENA-button) +% ... +% 128 (bit7) = 264 +% timeout: Duration of the timeout in seconds. +% newOnly: Only detect new keypress (not keys that were already pressed from +% the onset), default: true +%OUPUTS: +% rt: reaction time (NaN if no key was pressed before timeout) +% k : array with 1's for the button/key which have been pressed +% t0: start time of the wait +% t : real time of the press, so that rt = t - t0 +% +%See also: WaitAnyRelease, ReadParPort + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2009 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2009-03-13 Creation +% KND 2009-03-30 Test OpenParPort to set default keys +% KND 2010-11-18 Keys that are pressed on call may now be discarded +% ----------------------------- Script History --------------------------------- + +t0=GetSecs; +t=0; +rt=nan; +if nargin<3 + % wait for a new press (not one that was already down) + newOnly = 1; +end +if nargin<2 + timeout = Inf; +end +btnCode = 256+[1:8]; +if nargin<1 + keys=[]; +end +if isempty(keys) + % Not accepting mouse buttons (from 1 to 7) + keys = [ 8:256 ]; + global PAR_PORT + if ~isempty(PAR_PORT) + keys = [ keys btnCode ]; + end +end +if any(keys<8) + checkMouse = 1; +else + checkMouse = 0; +end +if any(keys>256) + checkParPort = 1; +else + checkParPort = 0; +end +if any(keys<1) + error('Can''t map keys below 1'); +end +k=zeros(1,max(keys)); +k_new = []; +k1=k; +while ~any(k(keys)) && ((t-t0) < timeout) + [keyIsDown,t,k] = KbCheck; + if checkParPort + [btnState] = ReadParPort(0); + k(btnCode) = 0; + k(btnCode(logical(bitget(btnState,1:8)))) = 1; + end + if checkMouse + [mouseState,mouseState,mouseState] = GetMouse; + k(mouseState) = 0; + k(1:3) = mouseState; + end + if newOnly + % Keys that were down from the start, will not trigger a response + if isempty(k_new);k_new=k;end + k1=k; + k(k==k_new)=0; + % Listen to them however if they have been released in the meantime + k_new(k1yahoo.fr) +% Copyright (C) 2009 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2009-03-23 Creation +% +% ----------------------------- Script History --------------------------------- +if nargin<2 + timeout = Inf; +end +btnCode = 256+[1:3]; +if nargin<1 + keys=[]; +end +if isempty(keys) + % By default doesn't listen to mouse buttons (from 1 to 7) + keys = [ 8:256 btnCode ]; +end +if any(keys>256) + checkButtons = 1; +else + checkButtons = 0; +end +% At onset, check state of buttons and keys +rt=nan; +t=0; +[keyIsDown,t0,k1] = KbCheck; +if checkButtons + btnPress=0; + [btnPress] = ReadParPort(0); + k1(btnCode) = 0; + k1(btnCode(logical(bitget(btnPress,1:3)))) = 1; +end +k=zeros(1,max(keys)); +while ~any(k(keys)) && ((t-t0) < timeout) + k0 = k1; + [keyIsDown,t,k1] = KbCheck; + if checkButtons + [btnState] = ReadParPort(0); + k1(btnCode) = 0; + k1(btnCode(logical(bitget(btnState,1:3)))) = 1; + end + k = k0 & ~logical(k1); +end +if any(k(keys)) + rt = t-t0; +end diff --git a/mypsychtoolbox/WaitKeyPress.m b/mypsychtoolbox/WaitKeyPress.m new file mode 100644 index 0000000..f75b343 --- /dev/null +++ b/mypsychtoolbox/WaitKeyPress.m @@ -0,0 +1,48 @@ +function [key, t, i] = WaitKeyPress(whichKeys, whichMarkers, timeout) +% WaitKeyPress - Wait for a key press from user and send marker(s) +% [key, t, i] = WaitKeyPress(whichKeys, whichMarkers, timeout) waits for a +% keypress from user and returns the key code and the time of it +% +%INPUTS: +% whichKeys: list of key codes/names. [] (default) listens to any key +% +if nargin < 3 + timeout = 10; +end +if nargin < 2 + whichMarkers = []; +end +if nargin < 1 + whichKeys = {}; +end + +if isempty(whichKeys) + whichKeyCodes = 1:256; +elseif iscell(whichKeys) + whichKeyCodes = KbName(whichKeys); +end +i = []; +t = []; +key = []; +t0 = GetSecs(); +while (GetSecs() - t0) <= timeout + [isKeyDown, keyTime, keyCode] = KbCheck(); + if any(keyCode(whichKeyCodes)) + t = keyTime; + i = find(keyCode(whichKeyCodes), 1); + key = KbName(whichKeyCodes(i)); + if ~isempty(whichMarkers) + WriteMarker(whichMarkers(i)); + end + FlushEvents('keyDown'); +% while true +% [isKeyDown, keyTime, keyCode] = KbCheck(); +% if ~any(keyCode(whichKeyCodes)) && GetSecs() - t > 10e-3 +% break +% end +% end + break + end +end + +end diff --git a/mypsychtoolbox/WaitMouseClick.m b/mypsychtoolbox/WaitMouseClick.m new file mode 100644 index 0000000..29445ca --- /dev/null +++ b/mypsychtoolbox/WaitMouseClick.m @@ -0,0 +1,47 @@ +function [button,t] = WaitMouseClick(timeout,whichButtons, whichMarkers) + +if nargin < 1 || isempty(timeout) + timeout = 10; +end +if nargin < 2 || isempty(whichButtons) + whichButtons = (1:3); +end +if nargin < 3 + whichMarkers = []; +end +if ~isempty(whichMarkers) + wm = whichMarkers; + try % Quick and dirty test to see if buttons & markers match + wm=whichButtons; + wm=whichMarkers; + catch + error('Markers and Buttons do not match'); + end +end +% whichButtons = swap(whichButtons, 2, 3); + +t0 = GetSecs(); +while true + if GetSecs() - t0 > timeout + t = []; + button = []; + break + end + [xMouse, yMouse, mouseButtons] = GetMouse(); + if any(mouseButtons(whichButtons)) + t = GetSecs(); + i = find(mouseButtons(whichButtons), 1); + button = whichButtons(i); + % button = swap(whichButtons(i), 2, 3); + if ~isempty(whichMarkers) + WriteMarker(whichMarkers(i)); + end + while true + [xMouse, yMouse, mouseButtons] = GetMouse(); + if ~any(mouseButtons(whichButtons)) && GetSecs() - t > 10e-3 + break + end + end + break + end +end diff --git a/mypsychtoolox.m b/mypsychtoolox.m new file mode 100644 index 0000000..0b0165b --- /dev/null +++ b/mypsychtoolox.m @@ -0,0 +1,42 @@ +function mypsychtoolox +%MYPSYCHTOOLOX - Add paths to run Psychtoolbox +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2010 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2010-01-17 Creation +% +% ----------------------------- Script History --------------------------------- +lcd=cd; +pwd=fullfile(fileparts(fileparts(mfilename('fullpath'))), 'mtoolbox', 'psychtoolbox'); +cd(pwd) +SetupPsychtoolbox +addpath(fullfile(mymatlabpath,'psychtoolbox')); +cd(lcd) + + +return + +addpath(pwd) +addpath(fullfile(pwd,'PsychBasic')) +addpath(fullfile(pwd,'PsychCal')) +addpath(fullfile(pwd,'PsychFiles')) +addpath(fullfile(pwd,'PsychGamma')) +addpath(fullfile(pwd,'PsychHardware')) +addpath(fullfile(pwd,'PsychInitialize')) +addpath(fullfile(pwd,'PsychJava')) +addpath(fullfile(pwd,'PsychOneliners')) +addpath(fullfile(pwd,'PsychOpenGL')) +addpath(fullfile(pwd,'PsychPriority')) +addpath(fullfile(pwd,'PsychProbability')) +addpath(fullfile(pwd,'PsychRects')) +addpath(fullfile(pwd,'PsychSignal')) +addpath(fullfile(pwd,'PsychOpenGL')) +addpath(fullfile(pwd,'PsychSound')) diff --git a/myspm.m b/myspm.m new file mode 100644 index 0000000..cba242c --- /dev/null +++ b/myspm.m @@ -0,0 +1,117 @@ +function [spmpath] = myspm(varargin) +%MYSPM - Sets SPM path according to the specified version +% [] = myspm('spm2') +% [] = myspm('2') +% Sets the path for SPM2 and runs it. +% +% [] = myspm('ver','spm2','do',action) +% Sets the path for SPM2 and performs the "action" ('run' [default] or 'nothing') +% +% Example +% >> myspm +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2008 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2008-10-31 Creation +% +% ----------------------------- Script History --------------------------------- +if nargin==1 + options=[{'ver'},varargin]; +else + options=varargin; +end +if nargin>0 + options=struct(options{:}); +else + options=[]; +end +defaults=struct('ver', 'spm5', 'do', 'run', 'mymatlabpath', fileparts(mfilename('fullpath')), 'rmpath', 1); +options = mergestructs(defaults,options); + +[hostname,hostname]=system('hostname'); + +if ~isempty(regexpi(hostname, 'chups.jussieu.fr')) + tbxpath='~/mtoolbox'; + myspmpath=fullfile(options.mymatlabpath,'spm'); +else + switch upper(deblank(hostname)) + case 'D5BDS81J' % LABNIC Office compute + switch options.ver + case {'spm2', '2'} + tbxpath='c:\spm\'; + myspmpath=fullfile(options.mymatabpath,'spm'); + otherwise + error('Unknown version: %s', options.ver); + end + case 'IMAGERIE2-PV' % steph's PC + case 'MONTBLANC' + case 'KARIMND' + options.mymatabpath = 'e:\ndiaye\home\matlab'; + tbxpath='e:\mtoolbox\'; + myspmpath=fullfile(options.mymatabpath,'spm'); + otherwise + tbxpath=fullfile(fileparts(options.mymatabpath),'mtoolbox'); + myspmpath=fullfile(options.mymatabpath,'spm'); + end +end +if isnumeric(options.ver) + options.ver=num2str(options.ver); +end +switch options.ver + case {'spm2', '2'} + options.ver = 'spm2'; + spmpath =fullfile(tbxpath,'spm2'); + myspmpath=fullfile(myspmpath,'spm2'); + case {'spm5', '5'} + options.ver = 'spm5'; + spmpath =fullfile(tbxpath,'spm5'); + myspmpath=fullfile(myspmpath,'spm5'); + case {'spm8', '8'} + options.ver='spm8'; + spmpath =fullfile(tbxpath,'spm8'); + myspmpath=fullfile(myspmpath,'spm8'); + otherwise + error('Unknown version: %s', options.ver); +end + +if options.rmpath + % Find current SPM-directories & subdirectories: + s=lower(fileparts(which('spm'))); + if ~isempty(s) && ~isequal(s,spmpath) + p=lower(path); + if numel(strread(p,'%s', 'delimiter', ';'))==1 + % In later matlab, they use ":" as a delimiter + p=strread(p,'%s', 'delimiter', ':'); + else + p=strread(p,'%s', 'delimiter', ':'); + end + p=p(:); + for d=p(strmatch(s,p))' + rmpath(d{1}); + end + end +end +clear global +clear functions +%clear classes +addpath(spmpath,'-begin') + +% Specific dll for R2007a and later +if ispc && (datenum(version('-date')) > datenum('2007-03-01')) +addpath(fullfile(spmpath,'..', [options.ver '_R2007a_winXP']),'-begin') +end +if exist(myspmpath) + addpath(myspmpath,'-begin') +end + +if isequal(options.do,'run') + spm fmri +end diff --git a/myspm_image.m b/myspm_image.m new file mode 100644 index 0000000..2019bfc --- /dev/null +++ b/myspm_image.m @@ -0,0 +1,466 @@ +function spm_image(op,varargin) +% image and header display +% FORMAT spm_image +%_______________________________________________________________________ +% +% spm_image is an interactive facility that allows orthogonal sections +% from an image volume to be displayed. Clicking the cursor on either +% of the three images moves the point around which the orthogonal +% sections are viewed. The co-ordinates of the cursor are shown both +% in voxel co-ordinates and millimeters within some fixed framework. +% The intensity at that point in the image (sampled using the current +% interpolation scheme) is also given. The position of the crosshairs +% can also be moved by specifying the co-ordinates in millimeters to +% which they should be moved. Clicking on the horizontal bar above +% these boxes will move the cursor back to the origin (analogous to +% setting the crosshair position (in mm) to [0 0 0]). +% +% The images can be re-oriented by entering appropriate translations, +% rotations and zooms into the panel on the left. The transformations +% can then be saved by hitting the ``Reorient images...'' button. The +% transformations that were applied to the image are saved to the +% ``.mat'' files of the selected images. The transformations are +% considered to be relative to any existing transformations that may be +% stored in the ``.mat'' files. Note that the order that the +% transformations are applied in is the same as in ``spm_matrix.m''. +% +% The ``Reset...'' button next to it is for setting the orientation of +% images back to transverse. It retains the current voxel sizes, +% but sets the origin of the images to be the centre of the volumes +% and all rotations back to zero. +% +% The right panel shows miscellaneous information about the image. +% This includes: +% Dimensions - the x, y and z dimensions of the image. +% Datatype - the computer representation of each voxel. +% Intensity - scalefactors and possibly a DC offset. +% Miscellaneous other information about the image. +% Vox size - the distance (in mm) between the centres of +% neighbouring voxels. +% Origin - the voxel at the origin of the co-ordinate system +% DIr Cos - Direction cosines. This is a widely used +% representation of the orientation of an image. +% +% There are also a few options for different resampling modes, zooms +% etc. You can also flip between voxel space (as would be displayed +% by Analyze) or world space (the orientation that SPM considers the +% image to be in). If you are re-orienting the images, make sure that +% world space is specified. Blobs (from activation studies) can be +% superimposed on the images and the intensity windowing can also be +% changed. +% +%_______________________________________________________________________ +% @(#)spm_image.m 2.20 John Ashburner 02/10/29 + +% global st + +% if nargin == 0, +% [Finter,Fgraph,CmdLine] = spm('FnUIsetup','Display',0); +% SPMid = spm('FnBanner',mfilename,'2.20'); +% spm_help('!ContextHelp',[mfilename,'.m']); +% +% % get the image's filename {P} +% %----------------------------------------------------------------------- +% P = spm_get(1,'IMAGE','please select image',[],0); +% myspm_image('init',P); +% return; +% end; + +try + if ~strcmp(op,'init') & ~strcmp(op,'reset') & isempty(st.vols{1}) + my_reset; warning('Lost all the image information'); + return; + end; +end + +if strcmp(op,'repos'), + % The widgets for translation rotation or zooms have been modified. + %----------------------------------------------------------------------- + fg = gcf % spm_figure('Findwin','Graphics'); + set(fg,'Pointer','watch'); + i = varargin{1}; + st.B(i) = eval(get(gco,'String'),num2str(st.B(i))); + set(gco,'String',st.B(i)); + st.vols{1}.premul = spm_matrix(st.B); + % spm_orthviews('MaxBB'); + myspm_image('zoom_in'); + myspm_image('update_info'); + set(fg,'Pointer','arrow'); + return; +end; + +if strcmp(op,'shopos'), + % The position of the crosshairs has been moved. + %----------------------------------------------------------------------- + if isfield(st,'mp'), + fg = gcf % spm_figure('Findwin','Graphics'); + if any(findobj(fg) == st.mp), + set(st.mp,'String',sprintf('%.1f %.1f %.1f',spm_orthviews('pos'))); + pos = spm_orthviews('pos',1); + set(st.vp,'String',sprintf('%.1f %.1f %.1f',pos)); + set(st.in,'String',sprintf('%g',spm_sample_vol(st.vols{1},pos(1),pos(2),pos(3),st.hld))); + else, + st.Callback = ';'; + rmfield(st,{'mp','vp','in'}); + end; + else, + st.Callback = ';'; + end; + return; +end; + +if strcmp(op,'setposmm'), + % Move the crosshairs to the specified position + %----------------------------------------------------------------------- + if isfield(st,'mp'), + fg = gcf % spm_figure('Findwin','Graphics'); + if any(findobj(fg) == st.mp), + pos = sscanf(get(st.mp,'String'), '%g %g %g'); + if length(pos)~=3, + pos = spm_orthviews('pos'); + end; + spm_orthviews('Reposition',pos); + end; + end; + return; +end; + +if strcmp(op,'setposvx'), + % Move the crosshairs to the specified position + %----------------------------------------------------------------------- + if isfield(st,'mp'), + fg = gcf % spm_figure('Findwin','Graphics'); + if any(findobj(fg) == st.vp), + pos = sscanf(get(st.vp,'String'), '%g %g %g'); + if length(pos)~=3, + pos = spm_orthviews('pos',1); + end; + tmp = st.vols{1}.premul*st.vols{1}.mat; + pos = tmp(1:3,:)*[pos ; 1]; + spm_orthviews('Reposition',pos); + end; + end; + return; +end; + + +if strcmp(op,'addblobs'), + % Add blobs to the image - in full colour + spm_figure('Clear','Interactive'); + nblobs = spm_input('Number of sets of blobs',1,'1|2|3|4|5|6',[1 2 3 4 5 6],1); + for i=1:nblobs, + [SPM,VOL] = spm_getSPM; + c = spm_input('Colour','+1','m','Red blobs|Yellow blobs|Green blobs|Cyan blobs|Blue blobs|Magenta blobs',[1 2 3 4 5 6],1); + colours = [1 0 0;1 1 0;0 1 0;0 1 1;0 0 1;1 0 1]; + spm_orthviews('addcolouredblobs',1,VOL.XYZ,VOL.Z,VOL.M,colours(c,:)); + set(st.blobber,'String','Remove Blobs','Callback','myspm_image(''rmblobs'');'); + end; + spm_orthviews('Redraw'); +end; + +if strcmp(op,'rmblobs'), + % Remove all blobs from the images + spm_orthviews('rmblobs',1); + set(st.blobber,'String','Add Blobs','Callback','myspm_image(''addblobs'');'); +end; + +if strcmp(op,'window'), + op = get(st.win,'Value'); + if op == 1, + spm_orthviews('window',1); + else, + spm_orthviews('window',1,spm_input('Range','+1','e','',2)); + end; +end; + + +if strcmp(op,'reorient'), + % Time to modify the ``.mat'' files for the images. + % I hope that giving people this facility is the right thing to do.... + %----------------------------------------------------------------------- + mat = spm_matrix(st.B); + if det(mat)<=0 + spm('alert!','This will flip the images',mfilename,0,1); + end; + P = spm_get(Inf, 'IMAGE','Images to reorient'); + Mats = zeros(4,4,size(P,1)); + spm_progress_bar('Init',size(P,1),'Reading current orientations',... + 'Images Complete'); + for i=1:size(P,1), + Mats(:,:,i) = spm_get_space(P(i,:)); + spm_progress_bar('Set',i); + end; + spm_progress_bar('Init',size(P,1),'Reorienting images',... + 'Images Complete'); + for i=1:size(P,1), + spm_get_space(P(i,:),mat*Mats(:,:,i)); + spm_progress_bar('Set',i); + end; + spm_progress_bar('Clear'); + tmp = spm_get_space([st.vols{1}.fname ',' num2str(st.vols{1}.n)]); + if sum((tmp(:)-st.vols{1}.mat(:)).^2) > 1e-8, + myspm_image('init',st.vols{1}.fname); + end; + return; +end; + +if strcmp(op,'resetorient'), + % Time to modify the ``.mat'' files for the images. + % I hope that giving people this facility is the right thing to do.... + %----------------------------------------------------------------------- + P = spm_get(Inf, 'IMAGE','Images to reset orientation of'); + V = spm_vol(P); + spm_progress_bar('Init',size(P,1),'Resetting orientations',... + 'Images Complete'); + for i=1:size(P,1), + V = spm_vol(deblank(P(i,:))); + M = V.mat; + vox = sqrt(sum(M(1:3,1:3).^2)); + if det(M(1:3,1:3))<0, vox(1) = -vox(1); end; + orig = (V.dim(1:3)+1)/2; + off = -vox.*orig; + M = [vox(1) 0 0 off(1) + 0 vox(2) 0 off(2) + 0 0 vox(3) off(3) + 0 0 0 1]; + spm_get_space(P(i,:),M); + spm_progress_bar('Set',i); + end; + spm_progress_bar('Clear'); + tmp = spm_get_space([st.vols{1}.fname ',' num2str(st.vols{1}.n)]); + if sum((tmp(:)-st.vols{1}.mat(:)).^2) > 1e-8, + myspm_image('init',st.vols{1}.fname); + end; + return; +end; + +if strcmp(op,'update_info'), + % Modify the positional information in the right hand panel. + %----------------------------------------------------------------------- + mat = st.vols{1}.premul*st.vols{1}.mat; + Z = spm_imatrix(mat); + Z = Z(7:9); + + set(st.posinf.z,'String', sprintf('%.3g x %.3g x %.3g', Z)); + + O = mat\[0 0 0 1]'; O=O(1:3)'; + set(st.posinf.o, 'String', sprintf('%.3g %.3g %.3g', O)); + + R = spm_imatrix(mat); + R = spm_matrix([0 0 0 R(4:6)]); + R = R(1:3,1:3); + + tmp2 = sprintf('%+5.3f %+5.3f %+5.3f', R(1,1:3)); tmp2(find(tmp2=='+')) = ' '; + set(st.posinf.m1, 'String', tmp2); + tmp2 = sprintf('%+5.3f %+5.3f %+5.3f', R(2,1:3)); tmp2(find(tmp2=='+')) = ' '; + set(st.posinf.m2, 'String', tmp2); + tmp2 = sprintf('%+5.3f %+5.3f %+5.3f', R(3,1:3)); tmp2(find(tmp2=='+')) = ' '; + set(st.posinf.m3, 'String', tmp2); + + tmp = [[R zeros(3,1)] ; 0 0 0 1]*diag([Z 1])*spm_matrix(-O) - mat; + + if sum(tmp(:).^2)>1e-8, + set(st.posinf.w, 'String', 'Warning: shears involved'); + else, + set(st.posinf.w, 'String', ''); + end; + + return; +end; + +if strcmp(op,'reset'), + my_reset; +end; + +if strcmp(op,'zoom_in'), + op = get(st.zoomer,'Value'); + if op==1, + spm_orthviews('resolution',1); + spm_orthviews('MaxBB'); + else, + vx = sqrt(sum(st.Space(1:3,1:3).^2)); + vx = vx.^(-1); + pos = spm_orthviews('pos'); + pos = st.Space\[pos ; 1]; + pos = pos(1:3)'; + if op == 2, st.bb = [pos-80*vx ; pos+80*vx] ; spm_orthviews('resolution',1); + elseif op == 3, st.bb = [pos-40*vx ; pos+40*vx] ; spm_orthviews('resolution',.5); + elseif op == 4, st.bb = [pos-20*vx ; pos+20*vx] ; spm_orthviews('resolution',.25); + elseif op == 5, st.bb = [pos-10*vx ; pos+10*vx] ; spm_orthviews('resolution',.125); + else , st.bb = [pos- 5*vx ; pos+ 5*vx] ; spm_orthviews('resolution',.125); + end; + end; + return; +end; + +if strcmp(op,'init'), +fg = spm_figure('GetWin','Graphics'); +if isempty(fg), error('Can''t create graphics window'); end +spm_figure('Clear','Graphics'); + +P = varargin{1}; +if ischar(P), P = spm_vol(P); end; +P = P(1); + +spm_orthviews('Reset'); +spm_orthviews('Image', P, [0.0 0.45 1 0.55]); +if isempty(st.vols{1}), return; end; + +spm_orthviews('MaxBB'); +st.callback = 'myspm_image(''shopos'');'; + +st.B = [0 0 0 0 0 0 1 1 1 0 0 0]; + +% locate Graphics window and clear it +%----------------------------------------------------------------------- +WS = spm('WinScale'); + +% Widgets for re-orienting images. +%----------------------------------------------------------------------- +uicontrol(fg,'Style','Frame','Position',[60 25 200 325].*WS,'DeleteFcn','myspm_image(''reset'');'); +uicontrol(fg,'Style','Text', 'Position',[75 220 100 016].*WS,'String','right {mm}'); +uicontrol(fg,'Style','Text', 'Position',[75 200 100 016].*WS,'String','foward {mm}'); +uicontrol(fg,'Style','Text', 'Position',[75 180 100 016].*WS,'String','up {mm}'); +uicontrol(fg,'Style','Text', 'Position',[75 160 100 016].*WS,'String','pitch {rad}'); +uicontrol(fg,'Style','Text', 'Position',[75 140 100 016].*WS,'String','roll {rad}'); +uicontrol(fg,'Style','Text', 'Position',[75 120 100 016].*WS,'String','yaw {rad}'); +uicontrol(fg,'Style','Text', 'Position',[75 100 100 016].*WS,'String','resize {x}'); +uicontrol(fg,'Style','Text', 'Position',[75 80 100 016].*WS,'String','resize {y}'); +uicontrol(fg,'Style','Text', 'Position',[75 60 100 016].*WS,'String','resize {z}'); + +uicontrol(fg,'Style','edit','Callback','myspm_image(''repos'',1)','Position',[175 220 065 020].*WS,'String','0','ToolTipString','translate'); +uicontrol(fg,'Style','edit','Callback','myspm_image(''repos'',2)','Position',[175 200 065 020].*WS,'String','0','ToolTipString','translate'); +uicontrol(fg,'Style','edit','Callback','myspm_image(''repos'',3)','Position',[175 180 065 020].*WS,'String','0','ToolTipString','translate'); +uicontrol(fg,'Style','edit','Callback','myspm_image(''repos'',4)','Position',[175 160 065 020].*WS,'String','0','ToolTipString','rotate'); +uicontrol(fg,'Style','edit','Callback','myspm_image(''repos'',5)','Position',[175 140 065 020].*WS,'String','0','ToolTipString','rotate'); +uicontrol(fg,'Style','edit','Callback','myspm_image(''repos'',6)','Position',[175 120 065 020].*WS,'String','0','ToolTipString','rotate'); +uicontrol(fg,'Style','edit','Callback','myspm_image(''repos'',7)','Position',[175 100 065 020].*WS,'String','1','ToolTipString','zoom'); +uicontrol(fg,'Style','edit','Callback','myspm_image(''repos'',8)','Position',[175 80 065 020].*WS,'String','1','ToolTipString','zoom'); +uicontrol(fg,'Style','edit','Callback','myspm_image(''repos'',9)','Position',[175 60 065 020].*WS,'String','1','ToolTipString','zoom'); + +uicontrol(fg,'Style','Pushbutton','String','Reorient images...','Callback','myspm_image(''reorient'')',... + 'Position',[70 35 125 020].*WS,'ToolTipString','modify position information of selected images'); + +uicontrol(fg,'Style','Pushbutton','String','Reset...','Callback','myspm_image(''resetorient'')',... + 'Position',[195 35 55 020].*WS,'ToolTipString','reset orientations of selected images'); + +% Crosshair position +%----------------------------------------------------------------------- +uicontrol(fg,'Style','Frame','Position',[70 250 180 90].*WS); +uicontrol(fg,'Style','Text', 'Position',[75 320 170 016].*WS,'String','Crosshair Position'); +uicontrol(fg,'Style','PushButton', 'Position',[75 316 170 006].*WS,... + 'Callback','spm_orthviews(''Reposition'',[0 0 0]);','ToolTipString','move crosshairs to origin'); +% uicontrol(fg,'Style','PushButton', 'Position',[75 315 170 020].*WS,'String','Crosshair Position',... +% 'Callback','spm_orthviews(''Reposition'',[0 0 0]);','ToolTipString','move crosshairs to origin'); +uicontrol(fg,'Style','Text', 'Position',[75 295 35 020].*WS,'String','mm:'); +uicontrol(fg,'Style','Text', 'Position',[75 275 35 020].*WS,'String','vx:'); +uicontrol(fg,'Style','Text', 'Position',[75 255 65 020].*WS,'String','Intensity:'); + +st.mp = uicontrol(fg,'Style','edit', 'Position',[110 295 135 020].*WS,'String','','Callback','myspm_image(''setposmm'')','ToolTipString','move crosshairs to mm coordinates'); +st.vp = uicontrol(fg,'Style','edit', 'Position',[110 275 135 020].*WS,'String','','Callback','myspm_image(''setposvx'')','ToolTipString','move crosshairs to voxel coordinates'); +st.in = uicontrol(fg,'Style','Text', 'Position',[140 255 85 020].*WS,'String',''); + +% General information +%----------------------------------------------------------------------- +uicontrol(fg,'Style','Frame','Position',[305 25 280 325].*WS); +uicontrol(fg,'Style','Text','Position' ,[310 330 50 016].*WS,... + 'HorizontalAlignment','right', 'String', 'File:'); +uicontrol(fg,'Style','Text','Position' ,[360 330 210 016].*WS,... + 'HorizontalAlignment','left', 'String', spm_str_manip(st.vols{1}.fname,'k25'),'FontWeight','bold'); +uicontrol(fg,'Style','Text','Position' ,[310 310 100 016].*WS,... + 'HorizontalAlignment','right', 'String', 'Dimensions:'); +uicontrol(fg,'Style','Text','Position' ,[410 310 160 016].*WS,... + 'HorizontalAlignment','left', 'String', sprintf('%d x %d x %d', st.vols{1}.dim(1:3)),'FontWeight','bold'); +uicontrol(fg,'Style','Text','Position' ,[310 290 100 016].*WS,... + 'HorizontalAlignment','right', 'String', 'Datatype:'); +uicontrol(fg,'Style','Text','Position' ,[410 290 160 016].*WS,... + 'HorizontalAlignment','left', 'String', spm_type(st.vols{1}.dim(4)),'FontWeight','bold'); +uicontrol(fg,'Style','Text','Position' ,[310 270 100 016].*WS,... + 'HorizontalAlignment','right', 'String', 'Intensity:'); +str = 'varied'; +if size(st.vols{1}.pinfo,2) == 1, + if st.vols{1}.pinfo(2), + str = sprintf('Y = %g X + %g', st.vols{1}.pinfo(1:2)'); + else, + str = sprintf('Y = %g X', st.vols{1}.pinfo(1)'); + end; +end; +uicontrol(fg,'Style','Text','Position' ,[410 270 160 016].*WS,... + 'HorizontalAlignment','left', 'String', str,'FontWeight','bold'); + +if isfield(st.vols{1}, 'descrip'), + uicontrol(fg,'Style','Text','Position' ,[310 250 260 016].*WS,... + 'HorizontalAlignment','center', 'String', st.vols{1}.descrip,'FontWeight','bold'); +end; + + +% Positional information +%----------------------------------------------------------------------- +mat = st.vols{1}.premul*st.vols{1}.mat; +Z = spm_imatrix(mat); +Z = Z(7:9); +uicontrol(fg,'Style','Text','Position' ,[310 210 100 016].*WS,... + 'HorizontalAlignment','right', 'String', 'Vox size:'); +st.posinf = struct('z',uicontrol(fg,'Style','Text','Position' ,[410 210 160 016].*WS,... + 'HorizontalAlignment','left', 'String', sprintf('%.3g x %.3g x %.3g', Z),'FontWeight','bold')); + +O = mat\[0 0 0 1]'; O=O(1:3)'; +uicontrol(fg,'Style','Text','Position' ,[310 190 100 016].*WS,... + 'HorizontalAlignment','right', 'String', 'Origin:'); +st.posinf.o = uicontrol(fg,'Style','Text','Position' ,[410 190 160 016].*WS,... + 'HorizontalAlignment','left', 'String', sprintf('%.3g %.3g %.3g', O),'FontWeight','bold'); + +R = spm_imatrix(mat); +R = spm_matrix([0 0 0 R(4:6)]); +R = R(1:3,1:3); + +uicontrol(fg,'Style','Text','Position' ,[310 170 100 016].*WS,... + 'HorizontalAlignment','right', 'String', 'Dir Cos:'); +tmp2 = sprintf('%+5.3f %+5.3f %+5.3f', R(1,1:3)); tmp2(find(tmp2=='+')) = ' '; +st.posinf.m1 = uicontrol(fg,'Style','Text','Position' ,[410 170 160 016].*WS,... + 'HorizontalAlignment','left', 'String', tmp2,'FontWeight','bold'); +tmp2 = sprintf('%+5.3f %+5.3f %+5.3f', R(2,1:3)); tmp2(find(tmp2=='+')) = ' '; +st.posinf.m2 = uicontrol(fg,'Style','Text','Position' ,[410 150 160 016].*WS,... + 'HorizontalAlignment','left', 'String', tmp2,'FontWeight','bold'); +tmp2 = sprintf('%+5.3f %+5.3f %+5.3f', R(3,1:3)); tmp2(find(tmp2=='+')) = ' '; +st.posinf.m3 = uicontrol(fg,'Style','Text','Position' ,[410 130 160 016].*WS,... + 'HorizontalAlignment','left', 'String', tmp2,'FontWeight','bold'); + +tmp = [[R zeros(3,1)] ; 0 0 0 1]*diag([Z 1])*spm_matrix(-O) - mat; +st.posinf.w = uicontrol(fg,'Style','Text','Position' ,[310 110 260 016].*WS,... + 'HorizontalAlignment','center', 'String', '','FontWeight','bold'); +if sum(tmp(:).^2)>1e-8, + set(st.posinf.w, 'String', 'Warning: shears involved'); +end; + +% Assorted other buttons. +%----------------------------------------------------------------------- +uicontrol(fg,'Style','Frame','Position',[310 30 270 70].*WS); +st.zoomer = uicontrol(fg,'Style','popupmenu' ,'Position',[315 75 125 20].*WS,... + 'String',str2mat('Full Volume','160x160x160mm','80x80x80mm','40x40x40mm','20x20x20mm','10x10x10mm'),... + 'Callback','myspm_image(''zoom_in'')','ToolTipString','zoom in by different amounts'); +c = 'if get(gco,''Value'')==1, spm_orthviews(''Space''), else, spm_orthviews(''Space'', 1);end;myspm_image(''zoom_in'')'; +uicontrol(fg,'Style','popupmenu' ,'Position',[315 55 125 20].*WS,... + 'String',str2mat('World Space','Voxel Space'),... + 'Callback',c,'ToolTipString','display in aquired/world orientation'); +c = 'if get(gco,''Value'')==1, spm_orthviews(''Xhairs'',''off''), else, spm_orthviews(''Xhairs'',''on''); end;'; +uicontrol(fg,'Style','togglebutton','Position',[450 75 125 20].*WS,... + 'String','Hide Crosshairs','Callback',c,'ToolTipString','show/hide crosshairs'); +uicontrol(fg,'Style','popupmenu' ,'Position',[450 55 125 20].*WS,... + 'String',str2mat('NN interp','bilin interp','sinc interp'),... + 'Callback','tmp_ = [0 1 -4];spm_orthviews(''Interp'',tmp_(get(gco,''Value'')))',... + 'Value',2,'ToolTipString','interpolation method for displaying images'); +st.win = uicontrol(fg,'Style','popupmenu','Position',[315 35 125 20].*WS,... + 'String',str2mat('Auto Window','Manual Window'),'Callback','myspm_image(''window'');','ToolTipString','range of voxel intensities displayed'); +% uicontrol(fg,'Style','pushbutton','Position',[315 35 125 20].*WS,... +% 'String','Window','Callback','myspm_image(''window'');','ToolTipString','range of voxel intensities % displayed'); +st.blobber = uicontrol(fg,'Style','pushbutton','Position',[450 35 125 20].*WS,... + 'String','Add Blobs','Callback','myspm_image(''addblobs'');','ToolTipString','superimpose activations'); +end; +return; + + +function my_reset +spm_orthviews('reset'); +spm_figure('Clear','Graphics'); +return; diff --git a/myspm_orthviews.m b/myspm_orthviews.m new file mode 100644 index 0000000..15af50e --- /dev/null +++ b/myspm_orthviews.m @@ -0,0 +1,1527 @@ +function varargout = spm_orthviews(action,varargin) +% Display Orthogonal Views of a Normalized Image +% FORMAT H = myspm_orthviews('Image',filename[,position]) +% filename - name of image to display +% area - position of image +% - area(1) - position x +% - area(2) - position y +% - area(3) - size x +% - area(4) - size y +% H - handle for ortho sections +% FORMAT myspm_orthviews('BB',bb) +% bb - bounding box +% [loX loY loZ +% hiX hiY hiZ] +% +% FORMAT myspm_orthviews('Redraw') +% Redraws the images +% +% FORMAT myspm_orthviews('Reposition',centre) +% centre - X, Y & Z coordinates of centre voxel +% +% FORMAT myspm_orthviews('Space'[,handle]) +% handle - the view to define the space by +% with no arguments - puts things into mm space +% +% FORMAT myspm_orthviews('MaxBB') +% sets the bounding box big enough display the whole of all images +% +% FORMAT myspm_orthviews('Resolution',res) +% res - resolution (mm) +% +% FORMAT myspm_orthviews('Delete', handle) +% handle - image number to delete +% +% FORMAT myspm_orthviews('Reset') +% clears the orthogonal views +% +% FORMAT myspm_orthviews('Pos') +% returns the co-ordinate of the crosshairs in millimetres in the +% standard space. +% +% FORMAT myspm_orthviews('Pos', i) +% returns the voxel co-ordinate of the crosshairs in the image in the +% ith orthogonal section. +% +% FORMAT myspm_orthviews('Xhairs','off') OR myspm_orthviews('Xhairs') +% disables the cross-hairs on the display. +% +% FORMAT myspm_orthviews('Xhairs','on') +% enables the cross-hairs. +% +% FORMAT myspm_orthviews('Interp',hld) +% sets the hold value to hld (see spm_slice_vol). +% +% FORMAT myspm_orthviews('AddBlobs',handle,XYZ,Z,mat) +% Adds blobs from a pointlist to the image specified by the handle(s). +% handle - image number to add blobs to +% XYZ - blob voxel locations (currently in millimeters) +% Z - blob voxel intensities +% mat - matrix from millimeters to voxels of blob. +% This method only adds one set of blobs, and displays them using a +% split colour table. +% +% myspm_orthviews('AddColouredBlobs',handle,XYZ,Z,mat,colour) +% Adds blobs from a pointlist to the image specified by the handle(s). +% handle - image number to add blobs to +% XYZ - blob voxel locations (currently in millimeters) +% Z - blob voxel intensities +% mat - matrix from millimeters to voxels of blob. +% colour - the 3 vector containing the colour that the blobs should be +% Several sets of blobs can be added in this way, and it uses full colour. +% Although it may not be particularly attractive on the screen, the colour +% blobs print well. +% +% myspm_orthviews('Register',hReg) +% See spm_XYZreg for more information. +% +% FORMAT myspm_orthviews('RemoveBlobs',handle) +% Removes all blobs from the image specified by the handle(s). +% +% myspm_orthviews('Register',hReg) +% hReg - Handle of HandleGraphics object to build registry in. +% See spm_XYZreg for more information. +% +% myspm_orthviews('AddContext',handle) +% handle - image number to add context menu to +% +% myspm_orthviews('RemoveContext',handle) +% handle - image number to remove context menu from +% +% CONTEXT MENU +% myspm_orthviews offers many of its features in a context menu, which is +% accessible via the right mouse button in each displayed image. +% +% PLUGINS +% The display capabilities of myspm_orthviews can be extended with +% plugins. These are located in the myspm_orthviews subdirectory of the SPM +% distribution. Currently there are 3 plugins available: +% quiver Add Quiver plots to a displayed image +% quiver3d Add 3D Quiver plots to a displayed image +% roi ROI creation and modification +% The functionality of plugins can be accessed via calls to +% myspm_orthviews('plugin_name', plugin_arguments). For detailed descriptions +% of each plugin see help myspm_orthviews/spm_ov_'plugin_name'. +% +%_______________________________________________________________________ +% @(#)myspm_orthviews.m 2.38 John Ashburner, Matthew Brett, Tom Nichols and Volkmar Glauche 03/04/17 + + +% The basic fields of st are: +% n - the number of images currently being displayed +% vols - a cell array containing the data on each of the +% displayed images. +% Space - a mapping between the displayed images and the +% mm space of each image. +% bb - the bounding box of the displayed images. +% centre - the current centre of the orthogonal views +% callback - a callback to be evaluated on a button-click. +% xhairs - crosshairs off/on +% hld - the interpolation method +% fig - the figure that everything is displayed in +% mode - the position/orientation of the sagittal view. +% - currently always 1 +% +% st.registry.hReg \_ See spm_XYZreg for documentation +% st.registry.hMe / +% +% For each of the displayed images, there is a non-empty entry in the +% vols cell array. Handles returned by "myspm_orthviews('Image',.....)" +% indicate the position in the cell array of the newly created ortho-view. +% Operations on each ortho-view require the handle to be passed. +% +% When a new image is displayed, the cell entry contains the information +% returned by spm_vol (type help spm_vol for more info). In addition, +% there are a few other fields, some of which I will document here: +% +% premul - a matrix to premultiply the .mat field by. Useful +% for re-orienting images. +% window - either 'auto' or an intensity range to display the +% image with. +% +% ax - a cell array containing an element for the three +% views. The fields of each element are handles for +% the axis, image and crosshairs. +% +% blobs - optional. Is there for using to superimpose blobs. +% vol - 3D array of image data +% mat - a mapping from vox-to-mm (see spm_vol, or +% help on image formats). +% max - maximum intensity for scaling to. If it +% does not exist, then images are auto-scaled. +% +% There are two colouring modes: full colour, and split +% colour. When using full colour, there should be a +% 'colour' field for each cell element. When using +% split colourscale, there is a handle for the colorbar +% axis. +% +% colour - if it exists it contains the +% red,green,blue that the blobs should be +% displayed in. +% cbar - handle for colorbar (for split colourscale). +% +% PLUGINS +% The plugin concept has been developed to extend the display capabilities +% of myspm_orthviews without the need to rewrite parts of it. Interaction +% between myspm_orthviews and plugins takes place +% a) at startup: The subfunction 'reset_st' looks for files with a name +% spm_ov_PLUGINNAME.m in the directory 'SWD/myspm_orthviews'. +% For each such file, PLUGINNAME will be added to the list +% st.plugins{:}. +% The subfunction 'add_context' calls each plugin with +% feval(['spm_ov_', st.plugins{k}], ... +% 'context_menu', i, parent_menu) +% Each plugin may add its own submenu to the context +% menu. +% b) at redraw: After images and blobs of st.vols{i} are drawn, the +% struct st.vols{i} is checked for field names that occur in +% the plugin list st.plugins{:}. For each matching entry, the +% corresponding plugin is called with the command 'redraw': +% feval(['spm_ov_', st.plugins{k}], ... +% 'redraw', i, TM0, TD, CM0, CD, SM0, SD); +% The values of TM0, TD, CM0, CD, SM0, SD are defined in the +% same way as in the redraw subfunction of myspm_orthviews. +% It is up to the plugin to do all necessary redraw +% operations for its display contents. Each displayed item +% must have set its property 'HitTest' to 'off' to let events +% go through to the underlying axis, which is responsible for +% callback handling. The order in which plugins are called is +% undefined. + +global st; + +if isempty(st), reset_st; end; + +spm('Pointer','watch'); + +if nargin == 0, action = ''; end; +action = lower(action); + +switch lower(action), +case 'image', + H = specify_image(varargin{1}); + if ~isempty(H) + st.vols{H}.area = [0 0 1 1]; + if length(varargin)>=2, st.vols{H}.area = varargin{2}; end; + if isempty(st.bb), st.bb = maxbb; end; + bbox; + redraw(H); + end; + varargout{1} = H; + +case 'bb', + if length(varargin)> 0 & all(size(varargin{1})==[2 3]), st.bb = varargin{1}; end; + bbox; + redraw_all; + +case 'redraw', + redraw_all; + eval(st.callback); + if isfield(st,'registry'), + spm_XYZreg('SetCoords',st.centre,st.registry.hReg,st.registry.hMe); + end; + +case 'reposition', + if length(varargin)<1, tmp = findcent; + else, tmp = varargin{1}; end; + if length(tmp)==3, st.centre = tmp(:); end; + redraw_all; + eval(st.callback); + if isfield(st,'registry'), + spm_XYZreg('SetCoords',st.centre,st.registry.hReg,st.registry.hMe); + end; + +case 'setcoords', + st.centre = varargin{1}; + st.centre = st.centre(:); + redraw_all; + eval(st.callback); + +case 'space', + if length(varargin)<1, + st.Space = eye(4); + st.bb = maxbb; + redraw_all; + else, + space(varargin{1}); + redraw_all; + end; + bbox; + +case 'maxbb', + st.bb = maxbb; + bbox; + redraw_all; + +case 'resolution', + resolution(varargin{1}); + bbox; + redraw_all; + +case 'window', + if length(varargin)<2, + win = 'auto'; + elseif length(varargin{2})==2, + win = varargin{2}; + end; + for i=valid_handles(varargin{1}), + st.vols{i}.window = win; + end; + redraw(varargin{1}); + +case 'delete', + my_delete(varargin{1}); + +case 'move', + move(varargin{1},varargin{2}); + % redraw_all; + +case 'reset', + my_reset; + +case 'pos', + if isempty(varargin), + H = st.centre(:); + else, + H = pos(varargin{1}); + end; + varargout{1} = H; + +case 'interp', + st.hld = varargin{1}; + redraw_all; + +case 'xhairs', + xhairs(varargin{1}); + +case 'register', + register(varargin{1}); + +case 'addblobs', + addblobs(varargin{1}, varargin{2},varargin{3},varargin{4}); + % redraw(varargin{1}); + +case 'addcolouredblobs', + addcolouredblobs(varargin{1}, varargin{2},varargin{3},varargin{4},varargin{5}); + % redraw(varargin{1}); + +case 'addimage', + addimage(varargin{1}, varargin{2}); + % redraw(varargin{1}); + +case 'addcolouredimage', + addcolouredimage(varargin{1}, varargin{2},varargin{3}); + % redraw(varargin{1}); + +case 'addtruecolourimage', + % myspm_orthviews('Addtruecolourimage',handle,filename,colourmap,prop,mx,mn) + % Adds blobs from an image in true colour + % handle - image number to add blobs to [default 1] + % filename of image containing blob data [default - request via GUI] + % colourmap - colormap to display blobs in [GUI input] + % prop - intensity proportion of activation cf grayscale [0.4] + % mx - maximum intensity to scale to [maximum value in activation image] + % mn - minimum intensity to scale to [minimum value in activation image] + % + if nargin < 2 + varargin(1) = {1}; + end + if nargin < 3 + varargin(2) = {spm_get(1, 'IMAGE', 'Image with activation signal')}; + end + if nargin < 4 + actc = []; + while isempty(actc) + actc = getcmap(spm_input('Colourmap for activation image', '+1','s')); + end + varargin(3) = {actc}; + end + if nargin < 5 + varargin(4) = {0.4}; + end + if nargin < 6 + actv = spm_vol(varargin{2}); + varargin(5) = {max([eps maxval(actv)])}; + end + if nargin < 7 + varargin(6) = {min([0 minval(actv)])}; + end + + addtruecolourimage(varargin{1}, varargin{2},varargin{3}, varargin{4}, ... + varargin{5}, varargin{6}); + % redraw(varargin{1}); + +case 'rmblobs', + rmblobs(varargin{1}); + % redraw(varargin{1}); + +case 'addcontext', + addcontexts(varargin{1}); + +case 'rmcontext', + rmcontexts(varargin{1}); + +case 'context_menu', + c_menu(varargin{:}); + +case 'set_pos2cm', + cm_pos(varargin{:}); + +otherwise, + addonaction = strcmp(st.plugins,action); + if any(addonaction) + feval(['spm_ov_' st.plugins{addonaction}],varargin{:}); + else + warning('Unknown action string') + end; +end; + +spm('Pointer'); +return; + + +%_______________________________________________________________________ +%_______________________________________________________________________ +function addblobs(handle, xyz, t, mat) +global st +for i=valid_handles(handle), + if ~isempty(xyz), + rcp = round(xyz); + dim = max(rcp,[],2)'; + off = rcp(1,:) + dim(1)*(rcp(2,:)-1 + dim(2)*(rcp(3,:)-1)); + vol = zeros(dim)+NaN; + vol(off) = t; + vol = reshape(vol,dim); + st.vols{i}.blobs=cell(1,1); + if st.mode == 0, + axpos = get(st.vols{i}.ax{2}.ax,'Position'); + else, + axpos = get(st.vols{i}.ax{1}.ax,'Position'); + end; + ax = axes('Parent',st.fig,'Position',[(axpos(1)+axpos(3)+0.1) (axpos(2)+0.005) 0.05 (axpos(4)-0.01)],... + 'Box','on'); + mx = max([eps max(t)]); + mn = min([0 min(t)]); + image([0 1],[mn mx],[1:64]' + 64,'Parent',ax); + set(ax,'YDir','normal','XTickLabel',[]); + st.vols{i}.blobs{1} = struct('vol',vol,'mat',mat,'cbar',ax,'max',mx, 'min',mn); + end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function addimage(handle, fname) +global st +for i=valid_handles(handle), + if isstruct(fname), + vol = fname(1); + else, + vol = spm_vol(fname); + end; + mat = vol.mat; + st.vols{i}.blobs=cell(1,1); + if st.mode == 0, + axpos = get(st.vols{i}.ax{2}.ax,'Position'); + else, + axpos = get(st.vols{i}.ax{1}.ax,'Position'); + end; + ax = axes('Parent',st.fig,'Position',[(axpos(1)+axpos(3)+0.05) (axpos(2)+0.005) 0.05 (axpos(4)-0.01)],... + 'Box','on'); + mx = max([eps maxval(vol)]); + mn = min([0 minval(vol)]); + image([0 1],[mn mx],[1:64]' + 64,'Parent',ax); + set(ax,'YDir','normal','XTickLabel',[]); + st.vols{i}.blobs{1} = struct('vol',vol,'mat',mat,'cbar',ax,'max',mx); +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function addcolouredblobs(handle, xyz, t, mat,colour) +global st +for i=valid_handles(handle), + if ~isempty(xyz), + rcp = round(xyz); + dim = max(rcp,[],2)'; + off = rcp(1,:) + dim(1)*(rcp(2,:)-1 + dim(2)*(rcp(3,:)-1)); + vol = zeros(dim)+NaN; + vol(off) = t; + vol = reshape(vol,dim); + if ~isfield(st.vols{i},'blobs'), + st.vols{i}.blobs=cell(1,1); + bset = 1; + else, + bset = length(st.vols{i}.blobs)+1; + end; + axpos = get(st.vols{i}.ax{2}.ax,'Position'); + mx = max([eps maxval(vol)]); + mn = min([0 minval(vol)]); + st.vols{i}.blobs{bset} = struct('vol',vol,'mat',mat,'max',mx,'min',mn,'colour',colour); + end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function addcolouredimage(handle, fname,colour) +global st +for i=valid_handles(handle), + if isstruct(fname), + vol = fname(1); + else, + vol = spm_vol(fname); + end; + mat = vol.mat; + if ~isfield(st.vols{i},'blobs'), + st.vols{i}.blobs=cell(1,1); + bset = 1; + else, + bset = length(st.vols{i}.blobs)+1; + end; + axpos = get(st.vols{i}.ax{2}.ax,'Position'); + mx = max([eps maxval(vol)]); + mn = min([0 minval(vol)]); + st.vols{i}.blobs{bset} = struct('vol',vol,'mat',mat,'max',mx,'min',mn,'colour',colour); +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function addtruecolourimage(handle,fname,colourmap,prop,mx,mn) +% adds true colour image to current displayed image +global st +for i=valid_handles(handle), + if isstruct(fname), + vol = fname(1); + else, + vol = spm_vol(fname); + end; + mat = vol.mat; + if ~isfield(st.vols{i},'blobs'), + st.vols{i}.blobs=cell(1,1); + bset = 1; + else, + bset = length(st.vols{i}.blobs)+1; + end; + % axpos = get(st.vols{i}.ax{2}.ax,'Position'); + c = struct('cmap', colourmap,'prop',prop); + st.vols{i}.blobs{bset} = struct('vol',vol,'mat',mat,'max',mx,'min',mn,'colour',c); +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function rmblobs(handle) +global st +for i=valid_handles(handle), + if isfield(st.vols{i},'blobs'), + for j=1:length(st.vols{i}.blobs), + if isfield(st.vols{i}.blobs{j},'cbar') & ishandle(st.vols{i}.blobs{j}.cbar), + delete(st.vols{i}.blobs{j}.cbar); + end; + end; + st.vols{i} = rmfield(st.vols{i},'blobs'); + end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function register(hreg) +global st +tmp = uicontrol('Position',[0 0 1 1],'Visible','off','Parent',st.fig); +h = valid_handles(1:24); +if ~isempty(h), + tmp = st.vols{h(1)}.ax{1}.ax; + st.registry = struct('hReg',hreg,'hMe', tmp); + spm_XYZreg('Add2Reg',st.registry.hReg,st.registry.hMe, 'myspm_orthviews'); +else, + warning('Nothing to register with'); +end; +st.centre = spm_XYZreg('GetCoords',st.registry.hReg); +st.centre = st.centre(:); +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function xhairs(arg1), +global st +st.xhairs = 0; +opt = 'on'; +if ~strcmp(arg1,'on'), + opt = 'off'; +else, + st.xhairs = 1; +end; +for i=valid_handles(1:24), + for j=1:3, + set(st.vols{i}.ax{j}.lx,'Visible',opt); + set(st.vols{i}.ax{j}.ly,'Visible',opt); + end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function H = pos(arg1) +global st +H = []; +for arg1=valid_handles(arg1), + is = inv(st.vols{arg1}.premul*st.vols{arg1}.mat); + H = is(1:3,1:3)*st.centre(:) + is(1:3,4); +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function my_reset +global st +if ~isempty(st) & isfield(st,'registry') & ishandle(st.registry.hMe), + delete(st.registry.hMe); st = rmfield(st,'registry'); +end; +my_delete(1:24); +reset_st; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function my_delete(arg1) +global st +for i=valid_handles(arg1), + kids = get(st.fig,'Children'); + for j=1:3, + if any(kids == st.vols{i}.ax{j}.ax), + set(get(st.vols{i}.ax{j}.ax,'Children'),'DeleteFcn',''); + delete(st.vols{i}.ax{j}.ax); + end; + end; + st.vols{i} = []; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function resolution(arg1) +global st +res = arg1/mean(svd(st.Space(1:3,1:3))); +Mat = diag([res res res 1]); +st.Space = st.Space*Mat; +st.bb = st.bb/res; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function move(handle,pos) +global st +for handle = valid_handles(handle), + st.vols{handle}.area = pos; +end; +bbox; +% redraw(valid_handles(handle)); +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function bb = maxbb +global st +mn = [Inf Inf Inf]; +mx = -mn; +for i=valid_handles(1:24), + bb = [[1 1 1];st.vols{i}.dim(1:3)]; + c = [ bb(1,1) bb(1,2) bb(1,3) 1 + bb(1,1) bb(1,2) bb(2,3) 1 + bb(1,1) bb(2,2) bb(1,3) 1 + bb(1,1) bb(2,2) bb(2,3) 1 + bb(2,1) bb(1,2) bb(1,3) 1 + bb(2,1) bb(1,2) bb(2,3) 1 + bb(2,1) bb(2,2) bb(1,3) 1 + bb(2,1) bb(2,2) bb(2,3) 1]'; + tc = st.Space\(st.vols{i}.premul*st.vols{i}.mat)*c; + tc = tc(1:3,:)'; + mx = max([tc ; mx]); + mn = min([tc ; mn]); +end; +bb = [mn ; mx]; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function space(arg1) +global st +if ~isempty(st.vols{arg1}) + num = arg1; + Mat = st.vols{num}.premul(1:3,1:3)*st.vols{num}.mat(1:3,1:3); + vox = sqrt(sum(Mat.^2)); + if det(Mat(1:3,1:3))<0, vox(1) = -vox(1); end; + Mat = diag([vox 1]); + Space = (st.vols{num}.mat)/Mat; + bb = [1 1 1;st.vols{num}.dim(1:3)]; + bb = [bb [1;1]]; + bb=bb*Mat'; + bb=bb(:,1:3); + bb=sort(bb); + st.Space = Space; + st.bb = bb; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function H = specify_image(arg1, arg2) +global st +H=[]; +ok = 1; +if isstruct(arg1), + V = arg1(1); +else, + try, + V = spm_vol(arg1); + catch, + fprintf('Can not use image "%s"\n', arg1); + return; + end; +end; + +ii = 1; +while ~isempty(st.vols{ii}), ii = ii + 1; end; + +DeleteFcn = ['myspm_orthviews(''Delete'',' num2str(ii) ');']; +V.ax = cell(3,1); +for i=1:3, + ax = axes('Visible','off','DrawMode','fast','Parent',st.fig,'DeleteFcn',DeleteFcn,... + 'YDir','normal','ButtonDownFcn',... + ['if strcmp(get(gcf,''SelectionType''),''normal''),myspm_orthviews(''Reposition'');',... + 'myspm_orthviews(''set_pos2cm'');',... + 'elseif strcmp(get(gcf,''SelectionType''),''extend''),myspm_orthviews(''Reposition'');',... + 'myspm_orthviews(''set_pos2cm'');',... + 'myspm_orthviews(''context_menu'',''ts'',1);end;']); + d = image(0,'Tag','Transverse','Parent',ax,... + 'DeleteFcn',DeleteFcn); + set(ax,'Ydir','normal','ButtonDownFcn',... + ['if strcmp(get(gcf,''SelectionType''),''normal''),myspm_orthviews(''Reposition'');',... + 'myspm_orthviews(''set_pos2cm'');',... + 'elseif strcmp(get(gcf,''SelectionType''),''extend''),myspm_orthviews(''reposition'');',... + 'myspm_orthviews(''set_pos2cm'');',... + 'myspm_orthviews(''context_menu'',''ts'',1);end;']); + + lx = line(0,0,'Parent',ax,'DeleteFcn',DeleteFcn); + ly = line(0,0,'Parent',ax,'DeleteFcn',DeleteFcn); + if ~st.xhairs, + set(lx,'Visible','off'); + set(ly,'Visible','off'); + end; + V.ax{i} = struct('ax',ax,'d',d,'lx',lx,'ly',ly); +end; +V.premul = eye(4); +V.window = 'auto'; +st.vols{ii} = V; + +H = ii; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function addcontexts(handles) +global st +for ii = valid_handles(handles), + cm_handle = addcontext(ii); + for i=1:3, + set(st.vols{ii}.ax{i}.ax,'UIcontextmenu',cm_handle); + st.vols{ii}.ax{i}.cm = cm_handle; + end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function rmcontexts(handles) +global st +for ii = valid_handles(handles), + for i=1:3, + set(st.vols{ii}.ax{i}.ax,'UIcontextmenu',[]); + st.vols{ii}.ax{i} = rmfield(st.vols{ii}.ax{i},'cm'); + end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function bbox +global st +Dims = diff(st.bb)'+1; + +TD = Dims([1 2])'; +CD = Dims([1 3])'; +if st.mode == 0, SD = Dims([3 2])'; else, SD = Dims([2 3])'; end; + +un = get(st.fig,'Units');set(st.fig,'Units','Pixels'); +sz = get(st.fig,'Position');set(st.fig,'Units',un); +sz = sz(3:4); +sz(2) = sz(2)-40; + +for i=valid_handles(1:24), + area = st.vols{i}.area(:); + area = [area(1)*sz(1) area(2)*sz(2) area(3)*sz(1) area(4)*sz(2)]; + if st.mode == 0, + sx = area(3)/(Dims(1)+Dims(3))/1.02; + else, + sx = area(3)/(Dims(1)+Dims(2))/1.02; + end; + sy = area(4)/(Dims(2)+Dims(3))/1.02; + s = min([sx sy]); + + offy = (area(4)-(Dims(2)+Dims(3))*1.02*s)/2 + area(2); + sky = s*(Dims(2)+Dims(3))*0.02; + if st.mode == 0, + offx = (area(3)-(Dims(1)+Dims(3))*1.02*s)/2 + area(1); + skx = s*(Dims(1)+Dims(3))*0.02; + else, + offx = (area(3)-(Dims(1)+Dims(2))*1.02*s)/2 + area(1); + skx = s*(Dims(1)+Dims(2))*0.02; + end; + + DeleteFcn = ['myspm_orthviews(''Delete'',' num2str(i) ');']; + + % Transverse + set(st.vols{i}.ax{1}.ax,'Units','pixels', ... + 'Position',[offx offy s*Dims(1) s*Dims(2)],... + 'Units','normalized','Xlim',[0 TD(1)]+0.5,'Ylim',[0 TD(2)]+0.5,... + 'Visible','on','XTick',[],'YTick',[]); + + % Coronal + set(st.vols{i}.ax{2}.ax,'Units','Pixels',... + 'Position',[offx offy+s*Dims(2)+sky s*Dims(1) s*Dims(3)],... + 'Units','normalized','Xlim',[0 CD(1)]+0.5,'Ylim',[0 CD(2)]+0.5,... + 'Visible','on','XTick',[],'YTick',[]); + + % Sagittal + if st.mode == 0, + set(st.vols{i}.ax{3}.ax,'Units','Pixels', 'Box','on',... + 'Position',[offx+s*Dims(1)+skx offy s*Dims(3) s*Dims(2)],... + 'Units','normalized','Xlim',[0 SD(1)]+0.5,'Ylim',[0 SD(2)]+0.5,... + 'Visible','on','XTick',[],'YTick',[]); + else, + set(st.vols{i}.ax{3}.ax,'Units','Pixels', 'Box','on',... + 'Position',[offx+s*Dims(1)+skx offy+s*Dims(2)+sky s*Dims(2) s*Dims(3)],... + 'Units','normalized','Xlim',[0 SD(1)]+0.5,'Ylim',[0 SD(2)]+0.5,... + 'Visible','on','XTick',[],'YTick',[]); + end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function redraw_all +global st +redraw(1:24); +return; +%_______________________________________________________________________ +function mx = maxval(vol) +if isstruct(vol), + mx = -Inf; + for i=1:vol.dim(3), + tmp = spm_slice_vol(vol,spm_matrix([0 0 i]),vol.dim(1:2),0); + imx = max(tmp(find(finite(tmp)))); + if ~isempty(imx),mx = max(mx,imx);end + end; +else, + mx = max(vol(find(finite(vol)))); +end; +%_______________________________________________________________________ +function mn = minval(vol) +if isstruct(vol), + mn = Inf; + for i=1:vol.dim(3), + tmp = spm_slice_vol(vol,spm_matrix([0 0 i]),vol.dim(1:2),0); + imn = min(tmp(find(finite(tmp)))); + if ~isempty(imn),mn = min(mn,imn);end + end; +else, + mn = min(vol(find(finite(vol)))); +end; + +%_______________________________________________________________________ +%_______________________________________________________________________ +function redraw(arg1) +global st +bb = st.bb; +Dims = round(diff(bb)'+1); +is = inv(st.Space); +cent = is(1:3,1:3)*st.centre(:) + is(1:3,4); + +for i = valid_handles(arg1), + M = st.vols{i}.premul*st.vols{i}.mat; + TM0 = [ 1 0 0 -bb(1,1)+1 + 0 1 0 -bb(1,2)+1 + 0 0 1 -cent(3) + 0 0 0 1]; + TM = inv(TM0*(st.Space\M)); + TD = Dims([1 2]); + + CM0 = [ 1 0 0 -bb(1,1)+1 + 0 0 1 -bb(1,3)+1 + 0 1 0 -cent(2) + 0 0 0 1]; + CM = inv(CM0*(st.Space\M)); + CD = Dims([1 3]); + + if st.mode ==0, + SM0 = [ 0 0 1 -bb(1,3)+1 + 0 1 0 -bb(1,2)+1 + 1 0 0 -cent(1) + 0 0 0 1]; + SM = inv(SM0*(st.Space\M)); SD = Dims([3 2]); + else, + SM0 = [ 0 1 0 -bb(1,2)+1 + 0 0 1 -bb(1,3)+1 + 1 0 0 -cent(1) + 0 0 0 1]; + SM0 = [ 0 -1 0 +bb(2,2)+1 + 0 0 1 -bb(1,3)+1 + 1 0 0 -cent(1) + 0 0 0 1]; + SM = inv(SM0*(st.Space\M)); + SD = Dims([2 3]); + end; + + ok=1; + eval('imgt = (myspm_slice_vol(st.vols{i},TM,TD,st.hld))'';','ok=0;'); + eval('imgc = (myspm_slice_vol(st.vols{i},CM,CD,st.hld))'';','ok=0;'); + eval('imgs = (myspm_slice_vol(st.vols{i},SM,SD,st.hld))'';','ok=0;'); + if (ok==0), fprintf('Image "%s" can not be resampled\n', st.vols{i}.fname); + else, + if strcmp(st.vols{i}.window,'auto'), + mx = -Inf; mn = Inf; + if ~isempty(imgt), + mx = max([mx max(max(imgt))]); + mn = min([mn min(min(imgt))]); + end; + if ~isempty(imgc), + mx = max([mx max(max(imgc))]); + mn = min([mn min(min(imgc))]); + end; + if ~isempty(imgs), + mx = max([mx max(max(imgs))]); + mn = min([mn min(min(imgs))]); + end; + if mx==mn, mx=mn+eps; end; + else, + mx = st.vols{i}.window(2); + mn = st.vols{i}.window(1); + r=min([mn mx]);imgt = max(imgt,r); r=max([mn mx]);imgt = min(imgt,r); + r=min([mn mx]);imgc = max(imgc,r); r=max([mn mx]);imgc = min(imgc,r); + r=min([mn mx]);imgs = max(imgs,r); r=max([mn mx]);imgs = min(imgs,r); + end; + + if isfield(st.vols{i},'blobs'), + if ~isfield(st.vols{i}.blobs{1},'colour'), + % Add blobs for display using the split colourmap + scal = 64/(mx-mn); + dcoff = -mn*scal; + imgt = imgt*scal+dcoff; + imgc = imgc*scal+dcoff; + imgs = imgs*scal+dcoff; + + if isfield(st.vols{i}.blobs{1},'max'), + mx = st.vols{i}.blobs{1}.max; + else, + mx = max([eps maxval(st.vols{i}.blobs{1}.vol)]); + st.vols{i}.blobs{1}.max = mx; + end; + if isfield(st.vols{i}.blobs{1},'min'), + mn = st.vols{i}.blobs{1}.min; + else, + mn = min([0 minval(st.vols{i}.blobs{1}.vol)]); + st.vols{i}.blobs{1}.min = mn; + end; + + vol = st.vols{i}.blobs{1}.vol; + M = st.vols{i}.premul*st.vols{i}.blobs{1}.mat; + tmpt = spm_slice_vol(vol,inv(TM0*(st.Space\M)),TD,[0 NaN])'; + tmpc = spm_slice_vol(vol,inv(CM0*(st.Space\M)),CD,[0 NaN])'; + tmps = spm_slice_vol(vol,inv(SM0*(st.Space\M)),SD,[0 NaN])'; + + sc = 64/(mx-mn); + off = 65.51-mn*sc; + msk = find(finite(tmpt)); imgt(msk) = off+tmpt(msk)*sc; + msk = find(finite(tmpc)); imgc(msk) = off+tmpc(msk)*sc; + msk = find(finite(tmps)); imgs(msk) = off+tmps(msk)*sc; + + cmap = get(st.fig,'Colormap'); + if size(cmap,1)~=128 + figure(st.fig) + spm_figure('Colormap','gray-hot') + end; + elseif isstruct(st.vols{i}.blobs{1}.colour), + % Add blobs for display using a defined + % colourmap + + % colourmaps + gryc = [0:63]'*ones(1,3)/63; + actc = ... + st.vols{1}.blobs{1}.colour.cmap; + actp = ... + st.vols{1}.blobs{1}.colour.prop; + + % scale grayscale image, not finite -> black + imgt = scaletocmap(imgt,mn,mx,gryc,65); + imgc = scaletocmap(imgc,mn,mx,gryc,65); + imgs = scaletocmap(imgs,mn,mx,gryc,65); + gryc = [gryc; 0 0 0]; + + % get max for blob image + vol = st.vols{i}.blobs{1}.vol; + mat = st.vols{i}.premul*st.vols{i}.blobs{1}.mat; + if isfield(st.vols{i}.blobs{1},'max'), + cmx = st.vols{i}.blobs{1}.max; + else, + cmx = max([eps maxval(st.vols{i}.blobs{1}.vol)]); + end; + cmx=10; + + % get blob data + vol = st.vols{i}.blobs{1}.vol; + M = st.vols{i}.premul*st.vols{i}.blobs{1}.mat; + tmpt = spm_slice_vol(vol,inv(TM0*(st.Space\M)),TD,[0 NaN])'; + tmpc = spm_slice_vol(vol,inv(CM0*(st.Space\M)),CD,[0 NaN])'; + tmps = spm_slice_vol(vol,inv(SM0*(st.Space\M)),SD,[0 NaN])'; + + % actimg scaled round 0, black NaNs + topc = size(actc,1)+1; + tmpt = scaletocmap(tmpt,-cmx,cmx,actc,topc); + tmpc = scaletocmap(tmpc,-cmx,cmx,actc,topc); + tmps = scaletocmap(tmps,-cmx,cmx,actc,topc); + actc = [actc; 0 0 0]; + + % combine gray and blob data to + % truecolour + imgt = reshape(actc(tmpt(:),:)*actp+ ... + gryc(imgt(:),:)*(1-actp), ... + [size(imgt) 3]); + imgc = reshape(actc(tmpc(:),:)*actp+ ... + gryc(imgc(:),:)*(1-actp), ... + [size(imgc) 3]); + imgs = reshape(actc(tmps(:),:)*actp+ ... + gryc(imgs(:),:)*(1-actp), ... + [size(imgs) 3]); + + + else, + % Add full colour blobs - several sets at once + scal = 1/(mx-mn); + dcoff = -mn*scal; + + wt = zeros(size(imgt)); + wc = zeros(size(imgc)); + ws = zeros(size(imgs)); + + imgt = repmat(imgt*scal+dcoff,[1,1,3]); + imgc = repmat(imgc*scal+dcoff,[1,1,3]); + imgs = repmat(imgs*scal+dcoff,[1,1,3]); + + cimgt = zeros(size(imgt)); + cimgc = zeros(size(imgc)); + cimgs = zeros(size(imgs)); + + for j=1:length(st.vols{i}.blobs), % get colours of all images first + if isfield(st.vols{i}.blobs{j},'colour'), + colour(j,:) = reshape(st.vols{i}.blobs{j}.colour, [1 3]); + else, + colour(j,:) = [1 0 0]; + end; + end; + %colour = colour/max(sum(colour)); + + for j=1:length(st.vols{i}.blobs), + if isfield(st.vols{i}.blobs{j},'max'), + mx = st.vols{i}.blobs{j}.max; + else, + mx = max([eps max(st.vols{i}.blobs{j}.vol(:))]); + st.vols{i}.blobs{j}.max = mx; + end; + if isfield(st.vols{i}.blobs{j},'min'), + mn = st.vols{i}.blobs{j}.min; + else, + mn = min([0 min(st.vols{i}.blobs{j}.vol(:))]); + st.vols{i}.blobs{j}.min = mn; + end; + + vol = st.vols{i}.blobs{j}.vol; + M = st.Space\st.vols{i}.premul*st.vols{i}.blobs{j}.mat; + tmpt = (spm_slice_vol(vol,inv(TM0*M),TD,[0 NaN])'+mn)/(mx-mn); + tmpc = (spm_slice_vol(vol,inv(CM0*M),CD,[0 NaN])'+mn)/(mx-mn); + tmps = (spm_slice_vol(vol,inv(SM0*M),SD,[0 NaN])'+mn)/(mx-mn); + tmpt(~finite(tmpt)) = 0; + tmpc(~finite(tmpc)) = 0; + tmps(~finite(tmps)) = 0; + + cimgt = cimgt + cat(3,tmpt*colour(j,1),tmpt*colour(j,2),tmpt*colour(j,3)); + cimgc = cimgc + cat(3,tmpc*colour(j,1),tmpc*colour(j,2),tmpc*colour(j,3)); + cimgs = cimgs + cat(3,tmps*colour(j,1),tmps*colour(j,2),tmps*colour(j,3)); + + wt = wt + tmpt; + wc = wc + tmpc; + ws = ws + tmps; + end; + + %cimgt(cimgt<0)=0; cimgt(cimgt>1)=1; + %cimgc(cimgc<0)=0; cimgc(cimgc>1)=1; + %cimgs(cimgs<0)=0; cimgs(cimgs>1)=1; + + imgt = repmat(1-wt,[1 1 3]).*imgt+cimgt; + imgc = repmat(1-wc,[1 1 3]).*imgc+cimgc; + imgs = repmat(1-ws,[1 1 3]).*imgs+cimgs; + + imgt(imgt<0)=0; imgt(imgt>1)=1; + imgc(imgc<0)=0; imgc(imgc>1)=1; + imgs(imgs<0)=0; imgs(imgs>1)=1; + end; + else, + scal = 64/(mx-mn); + dcoff = -mn*scal; + imgt = imgt*scal+dcoff; + imgc = imgc*scal+dcoff; + imgs = imgs*scal+dcoff; + end; + + set(st.vols{i}.ax{1}.d,'HitTest','off', 'Cdata',imgt); + set(st.vols{i}.ax{1}.lx,'HitTest','off',... + 'Xdata',[0 TD(1)]+0.5,'Ydata',[1 1]*(cent(2)-bb(1,2)+1)); + set(st.vols{i}.ax{1}.ly,'HitTest','off',... + 'Ydata',[0 TD(2)]+0.5,'Xdata',[1 1]*(cent(1)-bb(1,1)+1)); + + set(st.vols{i}.ax{2}.d,'HitTest','off', 'Cdata',imgc); + set(st.vols{i}.ax{2}.lx,'HitTest','off',... + 'Xdata',[0 CD(1)]+0.5,'Ydata',[1 1]*(cent(3)-bb(1,3)+1)); + set(st.vols{i}.ax{2}.ly,'HitTest','off',... + 'Ydata',[0 CD(2)]+0.5,'Xdata',[1 1]*(cent(1)-bb(1,1)+1)); + + set(st.vols{i}.ax{3}.d,'HitTest','off','Cdata',imgs); + if st.mode ==0, + set(st.vols{i}.ax{3}.lx,'HitTest','off',... + 'Xdata',[0 SD(1)]+0.5,'Ydata',[1 1]*(cent(2)-bb(1,2)+1)); + set(st.vols{i}.ax{3}.ly,'HitTest','off',... + 'Ydata',[0 SD(2)]+0.5,'Xdata',[1 1]*(cent(3)-bb(1,3)+1)); + else, + set(st.vols{i}.ax{3}.lx,'HitTest','off',... + 'Xdata',[0 SD(1)]+0.5,'Ydata',[1 1]*(cent(3)-bb(1,3)+1)); + set(st.vols{i}.ax{3}.ly,'HitTest','off',... + 'Ydata',[0 SD(2)]+0.5,'Xdata',[1 1]*(bb(2,2)+1-cent(2))); + end; + + if ~isempty(st.plugins) % process any addons + for k = 1:prod(size(st.plugins)) + if isfield(st.vols{i},st.plugins{k}) + feval(['spm_ov_', st.plugins{k}], ... + 'redraw', i, TM0, TD, CM0, CD, SM0, SD); + end; + end; + end; + end; +end; +drawnow; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function centre = findcent +global st +obj = get(st.fig,'CurrentObject'); +centre = []; +cent = []; +cp = []; +for i=valid_handles(1:24), + for j=1:3, + if ~isempty(obj), + if (st.vols{i}.ax{j}.ax == obj), + cp = get(obj,'CurrentPoint'); + end; + end; + if ~isempty(cp), + cp = cp(1,1:2); + is = inv(st.Space); + cent = is(1:3,1:3)*st.centre(:) + is(1:3,4); + switch j, + case 1, + cent([1 2])=[cp(1)+st.bb(1,1)-1 cp(2)+st.bb(1,2)-1]; + case 2, + cent([1 3])=[cp(1)+st.bb(1,1)-1 cp(2)+st.bb(1,3)-1]; + case 3, + if st.mode ==0, + cent([3 2])=[cp(1)+st.bb(1,3)-1 cp(2)+st.bb(1,2)-1]; + else, + cent([2 3])=[st.bb(2,2)+1-cp(1) cp(2)+st.bb(1,3)-1]; + end; + end; + break; + end; + end; + if ~isempty(cent), break; end; +end; +if ~isempty(cent), centre = st.Space(1:3,1:3)*cent(:) + st.Space(1:3,4); end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function handles = valid_handles(handles) +global st; +handles = handles(:)'; +handles = handles(find(handles<=24 & handles>=1 & ~rem(handles,1))); +for h=handles, + if isempty(st.vols{h}), handles(find(handles==h))=[]; end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function reset_st +global st +fig = spm_figure('FindWin','Graphics'); +bb = [ [-78 78]' [-112 76]' [-50 85]' ]; +st = struct('n', 0, 'vols',[], 'bb',bb,'Space',eye(4),'centre',[0 0 0],'callback',';','xhairs',1,'hld',1,'fig',fig,'mode',1,'plugins',[]); +st.vols = cell(24,1); + +pluginpath = fullfile(spm('Dir'),'myspm_orthviews'); +if isdir(pluginpath) + pluginfiles = dir(fullfile(pluginpath,'spm_ov_*.m')); + if ~isempty(pluginfiles) + addpath(pluginpath); + % fprintf('myspm_orthviews: Using Plugins in %s\n', pluginpath); + for k = 1:length(pluginfiles) + [p pluginname e v] = fileparts(pluginfiles(k).name); + st.plugins{k} = strrep(pluginname, 'spm_ov_',''); + % fprintf('%s\n',st.plugins{k}); + end; + end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function img = scaletocmap(inpimg,mn,mx,cmap,miscol) +if nargin < 5, miscol=1;end +cml = size(cmap,1); +scf = (cml-1)/(mx-mn); +img = round((inpimg-mn)*scf)+1; +img(find(img<1)) = 1; +img(find(img>cml)) = cml; +img(~finite(img)) = miscol; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function cmap = getcmap(acmapname) +% get colormap of name acmapname +if ~isempty(acmapname), + cmap = evalin('base',acmapname,'[]'); + if isempty(cmap), % not a matrix, is .mat file? + [p f e] = fileparts(acmapname); + acmat = fullfile(p, [f '.mat']); + if exist(acmat, 'file'), + s = struct2cell(load(acmat)); + cmap = s{1}; + end; + end; +end; +if size(cmap, 2)~=3, + warning('Colormap was not an N by 3 matrix') + cmap = []; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function item_parent = addcontext(volhandle) +global st; +%create context menu +fg = spm_figure('Findwin','Graphics');set(0,'CurrentFigure',fg); +%contextmenu +item_parent = uicontextmenu; + +%contextsubmenu 0 +item0a = uimenu(item_parent,'UserData','pos_mm'); +item0b = uimenu(item_parent,'UserData','pos_vx'); +item0c = uimenu(item_parent,'UserData','v_value'); + +%contextsubmenu 1 +item1 = uimenu(item_parent,'Label','Zoom'); +item1_1 = uimenu(item1, 'Label','Full Volume', 'Callback','myspm_orthviews(''context_menu'',''zoom'',6);', 'Checked','on'); +item1_2 = uimenu(item1, 'Label','160x160x160mm', 'Callback','myspm_orthviews(''context_menu'',''zoom'',5);'); +item1_3 = uimenu(item1, 'Label','80x80x80mm', 'Callback','myspm_orthviews(''context_menu'',''zoom'',4);'); +item1_4 = uimenu(item1, 'Label','40x40x40mm', 'Callback','myspm_orthviews(''context_menu'',''zoom'',3);'); +item1_5 = uimenu(item1, 'Label','20x20x20mm', 'Callback','myspm_orthviews(''context_menu'',''zoom'',2);'); +item1_6 = uimenu(item1, 'Label','10x10x10mm', 'Callback','myspm_orthviews(''context_menu'',''zoom'',1);'); + +%contextsubmenu 2 +item2 = uimenu(item_parent,'Label','Crosshairs'); +item2_1 = uimenu(item2, 'Label','on', 'Callback','myspm_orthviews(''context_menu'',''Xhair'',''on'');','Checked','on'); +item2_2 = uimenu(item2, 'Label','off', 'Callback','myspm_orthviews(''context_menu'',''Xhair'',''off'');'); + +%contextsubmenu 3 +item3 = uimenu(item_parent,'Label','Orientation'); +item3_1 = uimenu(item3, 'Label','World space', 'Callback','myspm_orthviews(''context_menu'',''orientation'',2);'); +item3_2 = uimenu(item3, 'Label','Voxel space', 'Callback','myspm_orthviews(''context_menu'',''orientation'',1);','Checked','on'); + +%contextsubmenu 4 +item4 = uimenu(item_parent,'Label','Interpolation'); +item4_1 = uimenu(item4, 'Label','NN', 'Callback','myspm_orthviews(''context_menu'',''interpolation'',3);'); +item4_2 = uimenu(item4, 'Label','Bilin', 'Callback','myspm_orthviews(''context_menu'',''interpolation'',2);','Checked','on'); +item4_3 = uimenu(item4, 'Label','Sinc', 'Callback','myspm_orthviews(''context_menu'',''interpolation'',1);'); + +%contextsubmenu 5 +% item5 = uimenu(item_parent,'Label','Position', 'Callback','myspm_orthviews(''context_menu'',''position'');'); + +%contextsubmenu 6 +item6 = uimenu(item_parent,'Label','Image','Separator','on'); +item6_1 = uimenu(item6, 'Label','Window'); +item6_1_1 = uimenu(item6_1, 'Label','local'); +item6_1_1_1 = uimenu(item6_1_1, 'Label','auto', 'Callback','myspm_orthviews(''context_menu'',''window'',2);'); +item6_1_1_2 = uimenu(item6_1_1, 'Label','manual', 'Callback','myspm_orthviews(''context_menu'',''window'',1);'); +item6_1_2 = uimenu(item6_1, 'Label','global'); +item6_1_2_1 = uimenu(item6_1_2, 'Label','auto', 'Callback','myspm_orthviews(''context_menu'',''window_gl'',2);'); +item6_1_2_2 = uimenu(item6_1_2, 'Label','manual', 'Callback','myspm_orthviews(''context_menu'',''window_gl'',1);'); +item6_2 = uimenu(item6, 'Label','Swap image', 'Callback','myspm_orthviews(''context_menu'',''swap_img'');'); + +%contextsubmenu 7 +item7 = uimenu(item_parent,'Label','Blobs'); +item7_1 = uimenu(item7, 'Label','Add blobs'); +item7_1_1 = uimenu(item7_1, 'Label','local', 'Callback','myspm_orthviews(''context_menu'',''add_blobs'',2);'); +item7_1_2 = uimenu(item7_1, 'Label','global', 'Callback','myspm_orthviews(''context_menu'',''add_blobs'',1);'); +item7_2 = uimenu(item7, 'Label','Add colored blobs'); +item7_2_1 = uimenu(item7_2, 'Label','local', 'Callback','myspm_orthviews(''context_menu'',''add_c_blobs'',2);'); +item7_2_2 = uimenu(item7_2, 'Label','global', 'Callback','myspm_orthviews(''context_menu'',''add_c_blobs'',1);'); +item7_3 = uimenu(item7, 'Label','Add colored image'); +item7_3_1 = uimenu(item7_3, 'Label','local', 'Callback','myspm_orthviews(''context_menu'',''add_c_image'',2);'); +item7_3_2 = uimenu(item7_3, 'Label','global', 'Callback','myspm_orthviews(''context_menu'',''add_c_image'',1);'); +item7_4 = uimenu(item7, 'Label','Remove blobs', 'Visible','off','Separator','on'); +item7_5 = uimenu(item7, 'Label','Remove colored blobs','Visible','off'); +item7_5_1 = uimenu(item7_5, 'Label','local', 'Visible','on'); +item7_5_2 = uimenu(item7_5, 'Label','global','Visible','on'); + +if ~isempty(st.plugins) % process any plugins + for k = 1:prod(size(st.plugins)), + feval(['spm_ov_', st.plugins{k}], ... + 'context_menu', volhandle, item_parent); + end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function c_menu(varargin) +global st + +switch lower(varargin{1}), +case 'zoom' + zoom_all(varargin{2}); + bbox; + redraw_all; + +case 'xhair', + myspm_orthviews('Xhairs',varargin{2}); + cm_handles = get_cm_handles; + for i = 1:length(cm_handles), + z_handle = get(findobj(cm_handles(i),'label','Crosshairs'),'Children'); + set(z_handle,'Checked','off'); %reset check + if strcmp(varargin{2},'off'), op = 1; else op = 2; end + set(z_handle(op),'Checked','on'); + end; + +case 'orientation', + if varargin{2} == 2, + myspm_orthviews('Space'); + else, + myspm_orthviews('Space',1); + end; + cm_handles = get_cm_handles; + for i = 1:length(cm_handles), + z_handle = get(findobj(cm_handles(i),'label','Orientation'),'Children'); + set(z_handle,'Checked','off'); + set(z_handle(varargin{2}),'Checked','on'); + end; + +case 'interpolation', + tmp = [-4 1 0]; + st.hld = tmp(varargin{2}); + cm_handles = get_cm_handles; + for i = 1:length(cm_handles), + z_handle = get(findobj(cm_handles(i),'label','Interpolation'),'Children'); + set(z_handle,'Checked','off'); + set(z_handle(varargin{2}),'Checked','on'); + end; + redraw_all; + +case 'window', + current_handle = get_current_handle; + if varargin{2} == 2, + myspm_orthviews('window',current_handle); + else + myspm_orthviews('window',current_handle,spm_input('Range','+1','e','',2)); + end; + +case 'window_gl', + if varargin{2} == 2, + for i = 1:length(get_cm_handles), + st.vols{i}.window = 'auto'; + end; + else, + data = spm_input('Range','+1','e','',2); + for i = 1:length(get_cm_handles), + st.vols{i}.window = data; + end; + end; + redraw_all; + +case 'swap_img', + current_handle = get_current_handle; + new_info = spm_vol(spm_get(1,'IMAGE','select new image')); + st.vols{current_handle}.fname = new_info.fname; + st.vols{current_handle}.dim = new_info.dim; + st.vols{current_handle}.mat = new_info.mat; + st.vols{current_handle}.pinfo = new_info.pinfo; + st.vols{current_handle}.descrip = new_info.descrip; + redraw_all; + +case 'add_blobs', + % Add blobs to the image - in split colortable + cm_handles = valid_handles(1:24); + if varargin{2} == 2, cm_handles = get_current_handle; end; + spm_figure('Clear','Interactive'); + [SPM,VOL] = spm_getSPM; + for i = 1:length(cm_handles), + addblobs(cm_handles(i),VOL.XYZ,VOL.Z,VOL.M); + c_handle = findobj(findobj(st.vols{cm_handles(i)}.ax{1}.cm,'label','Blobs'),'Label','Remove blobs'); + set(c_handle,'Visible','on'); + delete(get(c_handle,'Children')); + item7_3_1 = uimenu(c_handle,'Label','local','Callback','myspm_orthviews(''context_menu'',''remove_blobs'',2);'); + if varargin{2} == 1, + item7_3_2 = uimenu(c_handle,'Label','global','Callback','myspm_orthviews(''context_menu'',''remove_blobs'',1);'); + end; + end; + redraw_all; + +case 'remove_blobs', + cm_handles = valid_handles(1:24); + if varargin{2} == 2, cm_handles = get_current_handle; end; + for i = 1:length(cm_handles), + rmblobs(cm_handles(i)); + c_handle = findobj(findobj(st.vols{cm_handles(i)}.ax{1}.cm,'label','Blobs'),'Label','Remove blobs'); + delete(get(c_handle,'Children')); + set(c_handle,'Visible','off'); + end; + redraw_all; + +case 'add_c_blobs', + % Add blobs to the image - in full colour + cm_handles = valid_handles(1:24); + if varargin{2} == 2, cm_handles = get_current_handle; end; + spm_figure('Clear','Interactive'); + [SPM,VOL] = spm_getSPM; + c = spm_input('Colour','+1','m',... + 'Red blobs|Yellow blobs|Green blobs|Cyan blobs|Blue blobs|Magenta blobs',[1 2 3 4 5 6],1); + colours = [1 0 0;1 1 0;0 1 0;0 1 1;0 0 1;1 0 1]; + c_names = {'red';'yellow';'green';'cyan';'blue';'magenta'}; + for i = 1:length(cm_handles), + addcolouredblobs(cm_handles(i),VOL.XYZ,VOL.Z,VOL.M,colours(c,:)); + c_handle = findobj(findobj(st.vols{cm_handles(i)}.ax{1}.cm,'label','Blobs'),'Label','Remove colored blobs'); + ch_c_handle = get(c_handle,'Children'); + set(c_handle,'Visible','on'); + %set(ch_c_handle,'Visible',on'); + item7_4_1 = uimenu(ch_c_handle(2),'Label',c_names{c},'ForegroundColor',colours(c,:),... + 'Callback','c = get(gcbo,''UserData'');myspm_orthviews(''context_menu'',''remove_c_blobs'',2,c);',... + 'UserData',c); + if varargin{2} == 1, + item7_4_2 = uimenu(ch_c_handle(1),'Label',c_names{c},'ForegroundColor',colours(c,:),... + 'Callback','c = get(gcbo,''UserData'');myspm_orthviews(''context_menu'',''remove_c_blobs'',1,c);',... + 'UserData',c); + end; + end; + redraw_all; + +case 'remove_c_blobs', + cm_handles = valid_handles(1:24); + if varargin{2} == 2, cm_handles = get_current_handle; end; + colours = [1 0 0;1 1 0;0 1 0;0 1 1;0 0 1;1 0 1]; + c_names = {'red';'yellow';'green';'cyan';'blue';'magenta'}; + for i = 1:length(cm_handles), + if isfield(st.vols{cm_handles(i)},'blobs'), + for j = 1:length(st.vols{cm_handles(i)}.blobs), + if st.vols{cm_handles(i)}.blobs{j}.colour == colours(varargin{3},:); + st.vols{cm_handles(i)}.blobs(j) = []; + break; + end; + end; + rm_c_menu = findobj(st.vols{cm_handles(i)}.ax{1}.cm,'Label','Remove colored blobs'); + delete(findobj(rm_c_menu,'Label',c_names{varargin{3}})); + if isempty(st.vols{cm_handles(i)}.blobs), + st.vols{cm_handles(i)} = rmfield(st.vols{cm_handles(i)},'blobs'); + set(rm_c_menu, 'Visible', 'off'); + end; + end; + end; + redraw_all; + +case 'add_c_image', + % Add truecolored image + cm_handles = valid_handles(1:24); + if varargin{2} == 2, cm_handles = get_current_handle;end; + spm_figure('Clear','Interactive'); + fname = spm_get(1,'IMAGE','select image'); + c = spm_input('Colour','+1','m','Red blobs|Yellow blobs|Green blobs|Cyan blobs|Blue blobs|Magenta blobs',[1 2 3 4 5 6],1); + colours = [1 0 0;1 1 0;0 1 0;0 1 1;0 0 1;1 0 1]; + c_names = {'red';'yellow';'green';'cyan';'blue';'magenta'}; + for i = 1:length(cm_handles), + addcolouredimage(cm_handles(i),fname,colours(c,:)); + c_handle = findobj(findobj(st.vols{cm_handles(i)}.ax{1}.cm,'label','Blobs'),'Label','Remove colored blobs'); + ch_c_handle = get(c_handle,'Children'); + set(c_handle,'Visible','on'); + %set(ch_c_handle,'Visible',on'); + item7_4_1 = uimenu(ch_c_handle(2),'Label',c_names{c},'ForegroundColor',colours(c,:),... + 'Callback','c = get(gcbo,''UserData'');myspm_orthviews(''context_menu'',''remove_c_blobs'',2,c);','UserData',c); + if varargin{2} == 1 + item7_4_2 = uimenu(ch_c_handle(1),'Label',c_names{c},'ForegroundColor',colours(c,:),... + 'Callback','c = get(gcbo,''UserData'');myspm_orthviews(''context_menu'',''remove_c_blobs'',1,c);',... + 'UserData',c); + end + end + redraw_all; +end; +%_______________________________________________________________________ +%_______________________________________________________________________ +function current_handle = get_current_handle +global st +cm_handle = get(gca,'UIContextMenu'); +cm_handles = get_cm_handles; +current_handle = find(cm_handles==cm_handle); +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function cm_pos +global st +for i = 1:length(valid_handles(1:24)), + if isfield(st.vols{i}.ax{1},'cm') + set(findobj(st.vols{i}.ax{1}.cm,'UserData','pos_mm'),... + 'Label',sprintf('mm: %.1f %.1f %.1f',myspm_orthviews('pos'))); + pos = myspm_orthviews('pos',i); + set(findobj(st.vols{i}.ax{1}.cm,'UserData','pos_vx'),... + 'Label',sprintf('vx: %.1f %.1f %.1f',pos)); + set(findobj(st.vols{i}.ax{1}.cm,'UserData','v_value'),... + 'Label',sprintf('Y = %g',spm_sample_vol(st.vols{i},pos(1),pos(2),pos(3),st.hld))); + end +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function cm_handles = get_cm_handles +global st +cm_handles = []; +for i=valid_handles(1:24), + cm_handles = [cm_handles st.vols{i}.ax{1}.cm]; +end +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function zoom_all(op) +global st +cm_handles = get_cm_handles; +res = [.125 .125 .25 .5 1 1]; +if op==6, + st.bb = maxbb; +else, + vx = sqrt(sum(st.Space(1:3,1:3).^2)); + vx = vx.^(-1); + pos = myspm_orthviews('pos'); + pos = st.Space\[pos ; 1]; + pos = pos(1:3)'; + if op == 5, st.bb = [pos-80*vx ; pos+80*vx] ; + elseif op == 4, st.bb = [pos-40*vx ; pos+40*vx] ; + elseif op == 3, st.bb = [pos-20*vx ; pos+20*vx] ; + elseif op == 2, st.bb = [pos-10*vx ; pos+10*vx] ; + elseif op == 1; st.bb = [pos- 5*vx ; pos+ 5*vx] ; + else disp('no Zoom possible'); + end; +end +resolution(res(op)); +redraw_all; +for i = 1:length(cm_handles) + z_handle = get(findobj(cm_handles(i),'label','Zoom'),'Children'); + set(z_handle,'Checked','off'); + set(z_handle(op),'Checked','on'); +end +return; diff --git a/mystats/T_pair.m b/mystats/T_pair.m new file mode 100644 index 0000000..a3ebfeb --- /dev/null +++ b/mystats/T_pair.m @@ -0,0 +1,129 @@ +function [] = T_pair(dat,tail,B) +%T_paired(dat,tail,B) produce p-values per la verifica dellipotesi di uguaglianza in +%media di due campioni appaiati. +% +%Parametri in ingresso: +% dat= matrice di dati +% tail direzine delle alternative (vettore 1 X numero di varibili) +% tail = 1, alternativa: "media1 > 0" +% tail = -1, alternativa: "media1 < 0." +% B = Numero permutazioni +% +%ATTENZIONE!!! disporre i dati in modo 'X-Y'; ovvero inserire la matrice delle differenze delle misurazioni tra il +% il tempo 1 e il tempo 2. +% +%By DEFALUT: tail=-1, B=1000; +% +% +%References: Pesarin, F.(2001) Multivariate Permutation Test with Application in Biostatistcs. Wiley, New York. +% +%Livio Finos, Dipartimento di Sci. Statistiche - Università di Padova. +%versione 2.0, 16 Marzo 2003. + + + ora=cputime; +if nargin == 1 + tail=-ones(1,size(dat,2)); + B=1000; +end + +[n n_var]=size(dat); + +% calcolo stat test per dati osservati +Tperm((B+1),:)= mean(dat) ./ (std(dat) ./ sqrt(n)); + + +% calcolo stat test per B permutazioni +for i=1:B + perm=repmat(2.*binornd(1,.5,n,1)-1,1,n_var); + Tperm(i,:)= mean(perm.*dat) ./ (std(perm.*dat) ./ sqrt(n)) ; +end +%Tperm +%cambio segni delle statistiche a seconda della direzione della ipotesi alternativa in modo che a +%valori alti della stat test corrispondano p-value bassi (significativi). + +for i=1:n_var + if tail(i)==0 + Tperm(:,i)=abs(Tperm(:,i)); + else + Tperm(:,i)=Tperm(:,i).*tail(i); + end +end + + + +%[NULL Index]=sort(Tperm,1); +%for j=1:n_var +% rango(Index(:,j),j)=((B+1):-1:1)'; +%end +%clear NULL Index; +% +% +%decommentare le precedenti righe e commentare le successive 5 per ottenere un algoritmo più veloce (e corretto) +%ma meno buono nel caso di valori (nei dati di origine) ripetuti, sempre buono per dati continui +%(o con buona approssimazione al continuo). + +%calcolo del p-value per ogni valore della matrice di statistiche test Tperm (B+1)Xn_var. +for i=1:B+1 + for j=1:n_var + rango(i,j)=sum(Tperm(i,j)<=Tperm(:,j)); + end +end + + +rango=rango/(B+1); + + +%calcolo dei valori delle funzioni di combinazione. + + TpermTipp= max(1-rango,[],2); + TpermFish= -2*(sum(log(rango),2)); + Ninv=norminv(1-rango); + TpermLipt= sum(Ninv,2); + + +%calcolo del p-value dei test combinati +rangoFish=sum(TpermFish(B+1)<=TpermFish)/(B+1); +rangoTipp=sum(TpermTipp(B+1)<=TpermTipp)/(B+1); +rangoLipt=sum(TpermLipt(B+1)<=TpermLipt)/(B+1); + + +%output. + time=cputime-ora; + fprintf('\n Numerosità del campione: %3.0f ', n); + fprintf('\n \n'); + + fprintf(' Direzione delle ipotesi alternative \n \t'); + for var=1:n_var + fprintf('var %3.0f \t',var); + end + fprintf('\n \t'); + for var=1:n_var + fprintf('%3.0f \t \t',tail(var)); + end + fprintf('\n \n'); + + fprintf(' Numero Permutazioni= %3.0f. \t Tempo: %3.2f. \n \n',B,time); + + + fprintf(' p-values parziali \n'); + for var=1:n_var + fprintf('\t var %3.0f ',var); + end + fprintf('\n \t'); + + for var=1:n_var + fprintf(' %3.4f \t',rango(B+1,var)); + end + fprintf('\n'); + + fprintf('\n \n p-value Globale \n'); + + fprintf('\t FISHER \t \t %3.4f \n',rangoFish); + fprintf('\t TIPPETT \t \t %3.4f \n',rangoTipp); + fprintf('\t LIPTAK \t \t %3.4f \n ',rangoLipt); + fprintf('\n \n \n \n'); + + + +%pval=[rango(B+1,:) rangoFish rangoTipp rangoLipt]; \ No newline at end of file diff --git a/mystats/anova-a.m b/mystats/anova-a.m new file mode 100644 index 0000000..46c13de --- /dev/null +++ b/mystats/anova-a.m @@ -0,0 +1,102 @@ +function [pval, f, df_b, df_w] = anova (y, g) +% ANOVA One-way analysis of variance (ANOVA) +% Performs a one-way analysis of variance (ANOVA). The goal is to test +% whether the population means of data taken from k different groups +% are all equal. +% +% anova (y, g) provides all data in a single vector y; g is the vector +% of corresponding group labels (e.g., numbers from 1 to k). This is +% the general form which does not impose any restriction on the number +% of data in each group or the group labels (other than that they must +% be scalars). +% +% anova (y), where y is a matrix, treats each column as a group. This +% form is only appropriate for balanced ANOVA where the numbers of +% samples from each group are all equal. +% +% Under the null of constant means, the statistic f follows an F +% distribution with df_b and df_w degrees of freedom. pval is the +% p-value (1 minus the CDF of this distribution at f) of the test. +% +% If no output argument is given, the standard one-way ANOVA table is +% printed. + + +if ((nargin < 1) | (nargin > 2)) + error('USE anova (y [, g])'); + return +elseif (nargin == 1) + if ndims(y)==1 + error('anova: for anova (y), y must be a matrix'); + return + end + [group_count, k] = size(y); + n = group_count * k; + group_mean = mean(y); +else + if ~isvector(y) % ndims(y) ~= 1 + error('anova: for anova(y, g), y must be a vector'); + end + n = length(y); + if (~isvector(g) | (length(g) ~= n)) % (ndims(g) ~= 1 | (length(g) ~= n)) + error(['anova: for anova (y, g), g must be a vector of the same length y']); + end + group_label = sort(unique(g)); + + if (length(group_label) == 1) + error('anova: there should be at least 2 groups'); + return + end + for i = 1:length(group_label); + v = y(find(g == group_label(i))); + group_count(i) = length(v); + if i>1 + if group_count(i) ~= group_count(i-1) + error('anova: all groups should have the same number of observations'); + end + end + group_mean(i) = mean(v); + end +end +% moyenne de l'échantillon +total_mean = mean(group_mean); % OK car tous les grp ont meme nb d'obs +%total_mean = sum(group_count.*group_mean)/sum(group_count); + +%Variance Expliquée par la partition (inter-groupe) +SSB = sum(group_count .*(group_mean - total_mean) .^ 2); +%VARIANCE TOTALE: +SST = sum((reshape(y, n, 1) - total_mean).^2); +% Variance résiduelle (intra groupe) +SSW = SST - SSB; +%Deg de lib inter, intra +df_b = k - 1; +df_w = n - k; +%Var inter, intra +v_b = SSB / df_b; +v_w = SSW / df_w; + +f = v_b / v_w; + +pval = 1 - cdf('f', f, df_b, df_w); + +if (nargout == 0) + %% This eventually needs to be done more cleanly ... + disp(sprintf('\n')); + disp(sprintf('One-way ANOVA Table:\n')); + disp(sprintf('\n')); + disp(sprintf('Means:')); + disp(sprintf('Group %d: %3.4f (std:%3.4f)\n', [[1:k]; group_mean; std(y)])); + disp(sprintf('Difference of group %d with grandmean: %3.4f\n', [[1:k]; mean(group_mean)-group_mean])); + disp(sprintf('Source of Variation Sum of Squares df Empirical Var\n')); + disp(sprintf('*********************************************************\n')); + disp(sprintf('Between Groups %15.4f %4d %13.4f\n', SSB, df_b, v_b)); + disp(sprintf('Within Groups %15.4f %4d %13.4f\n', SSW, df_w, v_w)); + disp(sprintf('---------------------------------------------------------\n')); + disp(sprintf('Total %15.4f %4d\n', SST, n - 1)); + disp(sprintf('\n')); + disp(sprintf('Test Statistic F(%d,%d)=%15.4f\n', df_b,df_w,f)); + disp(sprintf('p-value p=%15.4f\n', pval)); + disp(sprintf('\n')); +end + + diff --git a/mystats/anova.m b/mystats/anova.m new file mode 100644 index 0000000..0a3249b --- /dev/null +++ b/mystats/anova.m @@ -0,0 +1,626 @@ +function [summary,para,resid] = anova(voie,Y,G,R,int,varargin) +%ANOVA one-way and two-way analysis of variance (ANOVA). +% [summary,para,resid] = anova(voie,Y,G,R,int) +% DO NOT USE FOR WITHIN SUBJECT EFFECTS!!! + +% +% If G isempty (or not specified) columns of Y make each group. No +% repetition is assumed. +% If G is given, Y should be a N*Kvector of length N*R and G a N-by-K matrix +% where the K columns specify the groups. +% R is the repetition factor. + + +% ANOVA calls ANOVA1 in case of one-way and +% ANOVA2 in case of two-way layout. +% +% ANOVA1 and ANOVA2 are from the matlab6 version, to have callbacks +if nargin<3 + G=[]; +end +if nargin<4 + R=1; +end +if nargin<5 + int=1; +end + +if (voie==1) + [p,summary,stats] = anova1(Y',G,'off'); + summary{2,1}='Rows'; + para.mu=mean(mean(Y')); + para.alpha=stats.means-para.mu; +% resid=Y-repmat(stats.means',1,R); +resid=NaN; +else + + if isvector(Y) + [sg,sidx]=sortrows(G); + ng1=length(unique(sg(:,1))); + ng2=length(unique(sg(:,2))); + R=size(G,1)/ng1/ng2; + Y=Y(sidx); + Y=reshape(Y, ng1, ng2*R)'; + n=ng1; + m=ng2*R; + else + if ndims(Y)<=3 + Y=Y(:,:); + else + error('Trop de dim!') + end + [n,m]=size(Y); + end + [p,summary,stats] = anova2(Y',R,'off'); + summary{2,1}='Rows'; + summary{3,1}='Columns'; + para.mu=mean(mean(Y')); + para.alpha=stats.colmeans-para.mu; + para.beta=stats.rowmeans-para.mu; + betainter=repmat(para.beta',1,R)'; + alphaplusbeta=repmat(para.alpha',1,m)+repmat(betainter(:)',n,1); + if ~(int) % no interaction + if (R>1) + % Summation of SC_ab+SC_e + summary{5,2}=summary{5,2}+summary{4,2}; + summary{5,3}=summary{5,3}+summary{4,3}; + summary{5,4}=summary{5,2}/summary{5,3}; + summary{2,5}=summary{2,4}/summary{5,4}; + summary{3,5}=summary{3,4}/summary{5,4}; + summary{2,6}=1-f_cdf(summary{2,5},summary{2,3},summary{5,3}); + summary{3,6}=1-f_cdf(summary{3,5},summary{3,3},summary{5,3}); + summary(4,:)=[]; + end; + resid=Y-alphaplusbeta-para.mu; + else + inter=mean(reshape(Y',R,m*n/R)); + para.alphabeta=reshape(inter,m/R,n)'-repmat(para.alpha',1,m/R) ... + -repmat(para.beta,n,1)-para.mu; + resid=Y-reshape(repmat(inter,R,1),m,n)'; + end; +end; + + +function [p,anovatab,stats] = anova1(x,group,displayopt,extra) +%ANOVA1 One-way analysis of variance (ANOVA). +% ANOVA1 performs a one-way ANOVA for comparing the means of two or more +% groups of data. It returns the p-value for the null hypothesis that the +% means of the groups are equal. +% +% P = ANOVA1(X,GROUP,DISPLAYOPT) +% If X is a matrix, ANOVA1 treats each column as a separate group, and +% determines whether the population means of the columns are equal. +% This form of ANOVA1 is appropriate when each group has the same +% number of elements (balanced ANOVA). GROUP can be a character +% array or a cell array of strings, with one row per column of +% X, containing the group names. Enter an empty array ([]) or +% omit this argument if you do not want to specify group names. +% If X is a vector, GROUP must be a vector of the same length, or a +% string array or cell array of strings with one row for each +% element of X. X values corresponding to the same value of +% GROUP are placed in the same group. +% +% DISPLAYOPT can be 'on' (the default) to display figures +% containing a standard one-way anova table and a boxplot, or +% 'off' to omit these displays. +% +% [P,ANOVATAB] = ANOVA1(...) returns the ANOVA table values as the +% cell array ANOVATAB. +% +% [P,ANOVATAB,STATS] = ANOVA1(...) returns an additional structure +% of statistics useful for performing a multiple comparison of means +% with the MULTCOMPARE function. +% +% See also ANOVA2, ANOVAN, BOXPLOT, MANOVA1, MULTCOMPARE. + +% Reference: Robert V. Hogg, and Johannes Ledolter, Engineering Statistics +% Macmillan 1987 pp. 205-206. +classical = 1; +nargs = nargin; +if (nargin>0 & strcmp(x,'kruskalwallis')) + % Called via kruskalwallis function, adjust inputs + classical = 0; + if (nargin >= 2), x = group; group = []; end + if (nargin >= 3), group = displayopt; displayopt = []; end + if (nargin >= 4), displayopt = extra; end + nargs = nargs-1; +end + +error(nargchk(1,3,nargs)); + +if (nargs < 2), group = []; end +if (nargs < 3), displayopt = 'on'; end +% Note: for backwards compatibility, accept 'nodisplay' for 'off' +willdisplay = ~(strcmp(displayopt,'nodisplay') | strcmp(displayopt,'n') ... + | strcmp(displayopt,'off')); + +% Convert group to cell array from character array, make it a column +if (ischar(group) & ~isempty(group)), group = cellstr(group); end +if (size(group, 1) == 1), group = group'; end + +% If X is a matrix with NaNs, convert to vector form. +if (length(x) < prod(size(x))) + if (any(isnan(x(:)))) + [n,m] = size(x); + x = x(:); + gi = reshape(repmat((1:m), n, 1), n*m, 1); + if (length(group) == 0) % no group names + group = gi; + elseif (size(group,1) == m) + group = group(gi,:); + else + error('X and GROUP must have the same length.'); + end + end +end + +% If X is a matrix and GROUP is strings, use GROUPs as names +if (iscell(group) & (length(x) < prod(size(x))) ... + & (size(x,2) == size(group,1))) + named = 1; + gnames = group; + grouped = 0; +else + named = 0; + gnames = []; + grouped = (length(group) > 0); +end + +if (grouped) + % Single data vector and a separate grouping variable + x = x(:); + lx = length(x); + if (lx ~= prod(size(x))) + error('First argument has to be a vector.') + end + nonan = ~isnan(x); + x = x(nonan); + + % Convert group to indices 1,...,g and separate names + group = group(nonan,:); + [groupnum, gnames] = grp2idx(group); + named = 1; + + % Remove NaN values + nonan = ~isnan(groupnum); + if (~all(nonan)) + groupnum = groupnum(nonan); + x = x(nonan); + end + + lx = length(x); + xorig = x; % use uncentered version to make M + groupnum = groupnum(:); + maxi = size(gnames, 1); + xm = zeros(1,maxi); + countx = xm; + if (willdisplay), M = []; end + if (classical) + mu = mean(x); + x = x - mu; % center to improve accuracy + xr = x; + else + [xr,tieadj] = tiedrank(x); + end + + for j = 1:maxi + % Get group sizes and means + k = find(groupnum == j); + lk = length(k); + countx(j) = lk; + xm(j) = mean(xr(k)); % column means + + if (willdisplay) % create matrix for boxplot + [r, c] = size(M); + if lk > r + tmp = NaN; + M(r+1:lk,:) = tmp(ones(lk - r,c)); + tmp = xorig(k); + M = [M tmp]; + else + tmp = xorig(k); + tmp1 = NaN; + tmp((lk + 1):r,1) = tmp1(ones(r - lk,1)); + M = [M tmp]; + end + end + + end + + gm = mean(xr); % grand mean + df1 = length(xm) - 1; % Column degrees of freedom + df2 = lx - df1 - 1; % Error degrees of freedom + RSS = countx .* (xm - gm)*(xm-gm)'; % Regression Sum of Squares +else + % Data in matrix form, no separate grouping variable + [r,c] = size(x); + lx = r * c; + if (classical) + xr = x; + mu = mean(xr(:)); + xr = xr - mu; % center to improve accuracy + else + [xr,tieadj] = tiedrank(x(:)); + xr = reshape(xr, size(x)); + end + countx = repmat(r, 1, c); + xorig = x; % save uncentered version for boxplot + xm = mean(xr); % column means + gm = mean(xm); % grand mean + df1 = c-1; % Column degrees of freedom + df2 = c*(r-1); % Error degrees of freedom + RSS = r*(xm - gm)*(xm-gm)'; % Regression Sum of Squares +end + +TSS = (xr(:) - gm)'*(xr(:) - gm); % Total Sum of Squares +SSE = TSS - RSS; % Error Sum of Squares + +if (df2 > 0) + mse = SSE/df2; +else + mse = NaN; +end + +if (classical) + if (SSE~=0) + F = (RSS/df1) / mse; + p = 1 - f_cdf(F,df1,df2); % Probability of F given equal means. + elseif (RSS==0) % Constant Matrix case. + F = 0; + p = 1; + else % Perfect fit case. + F = Inf; + p = 0; + end +else + F = (12 * RSS) / (lx * (lx+1)); + if (tieadj > 0) + F = F / (1 - 2 * tieadj/(lx^3-lx)); + end + p = 1 - chi2cdf(F, df1); +end + + +Table=zeros(3,5); %Formatting for ANOVA Table printout +Table(:,1)=[ RSS SSE TSS]'; +Table(:,2)=[df1 df2 df1+df2]'; +Table(:,3)=[ RSS/df1 mse Inf ]'; +Table(:,4)=[ F Inf Inf ]'; +Table(:,5)=[ p Inf Inf ]'; + +colheads = ['Source ';' SS ';' df ';... + ' MS ';' F ';' Prob>F ']; +if (~classical) + colheads(5,:) = ' Chi-sq '; + colheads(6,:) = ' Prob>Chi-sq'; +end +rowheads = ['Columns ';'Error ';'Total ']; +if (grouped) + rowheads(1,:) = 'Groups '; +end + +% Create cell array version of table +atab = num2cell(Table); +for i=1:size(atab,1) + for j=1:size(atab,2) + if (isinf(atab{i,j})) + atab{i,j} = []; + end + end +end +atab = [cellstr(strjust(rowheads, 'left')), atab]; +atab = [cellstr(strjust(colheads, 'left'))'; atab]; +if (nargout > 1) + anovatab = atab; +end + +% Create output stats structure if requested, used by MULTCOMPARE +if (nargout > 2) + if (length(gnames) > 0) + stats.gnames = gnames; + else + stats.gnames = strjust(num2str((1:length(xm))'),'left'); + end + stats.n = countx; + if (classical) + stats.source = 'anova1'; + stats.means = xm + mu; + stats.df = df2; + stats.s = sqrt(mse); + else + stats.source = 'kruskalwallis'; + stats.meanranks = xm; + stats.sumt = 2 * tieadj; + end +end + +if (~willdisplay), return; end + +digits = [-1 -1 0 -1 2 4]; +if (classical) + wtitle = 'One-way ANOVA'; + ttitle = 'ANOVA Table'; +else + wtitle = 'Kruskal-Wallis One-way ANOVA'; + ttitle = 'Kruskal-Wallis ANOVA Table'; +end +statdisptable(atab, wtitle, ttitle, '', digits); + +fig2 = figure('pos',get(gcf,'pos') + [0,-200,0,0]); + +if (~grouped) + boxplot(xorig,1); +else + boxplot(M,1); + h = get(gca,'XLabel'); + set(h,'String','Group Number'); +end + +% If there are group names, use them after removing blanks +if (length(gnames) > 0) + gnames = strrep(gnames, '|', '_'); + gstr = gnames{1}; + for j=2:size(gnames,1) + gstr = [gstr, '|', gnames{j}]; + end + h = get(gca,'XLabel'); + if (named) + set(h,'String',''); + end + set(gca, 'xtick', (1:df1+1), 'xticklabel', gstr); +end + + + +function [p,Table,stats] = anova2(X,reps,displayopt) +%ANOVA2 Two-way analysis of variance. +% ANOVA2(X,REPS,DISPLAYOPT) performs a balanced two-way ANOVA for +% comparing the means of two or more columns and two or more rows of the +% sample in X. The data in different columns represent changes in one +% factor. The data in different rows represent changes in the other +% factor. If there is more than one observation per row-column pair, then +% then the argument REPS indicates the number of observations per "cell". +% A cell contains REPS number of rows. DISPLAYOPT can be 'on' (the +% default) to display the table, or 'off' to skip the display. +% +% For example, if REPS = 3, then each cell contains 3 rows and the total +% number of rows must be a multiple of 3. If X has 12 rows, and REPS = 3, +% then the "row" factor has 4 levels (3*4 = 12). The second level of the +% row factor goes from rows 4 to 6. +% +% [P,TABLE] = ANOVA2(...) returns two items. P is a vector of p-values +% for testing row, column, and if possible interaction effects. TABLE +% is a cell array containing the contents of the anova table. +% +% To perform unbalanced two-way ANOVA, use ANOVAN. +% +% See also ANOVA1, ANOVAN. + +% Reference: Robert V. Hogg, and Johannes Ledolter, Engineering Statistics +% Macmillan 1987 pp. 227-231. + +if (nargin<3), displayopt = 'on'; end +if (nargin < 1), error('At least one input is required.'); end +if (any(isnan(X(:)))) + error('NaN values in input not allowed. Use anovan instead.'); +end +[r,c] = size(X); +if nargin == 1, + reps = 1; + m=r; + Y = X; +elseif reps == 1 + m=r; + Y = X; +else + m = r/reps; + if (floor(m) ~= r/reps), + error('The number of rows must be a multiple of reps.'); + end + Y = zeros(m,c); + for i=1:m, + j = (i-1)*reps; + Y(i,:) = mean(X(j+1:j+reps,:)); + end +end +colmean = mean(Y); % column means +rowmean = mean(Y'); % row means +gm = mean(colmean); % grand mean +df1 = c-1; % Column degrees of freedom +df2 = m-1; % Row degrees of freedom +if reps == 1, + edf = (c-1)*(r-1);% Error degrees of freedom. No replication. This assumes an additive model. +else + edf = (c*m*(reps-1)); % Error degrees of freedom with replicates + idf = (c-1)*(m-1); % Interaction degrees of freedom +end +CSS = m*reps*(colmean - gm)*(colmean-gm)'; % Column Sum of Squares +RSS = c*reps*(rowmean - gm)*(rowmean-gm)'; % Row Sum of Squares +correction = (c*m*reps)*gm^2; +TSS = sum(sum(X .* X)) - correction; % Total Sum of Squares +ISS = reps*sum(sum(Y .* Y)) - correction - CSS - RSS; % Interaction Sum of Squares +if reps == 1, + SSE = ISS; +else + SSE = TSS - CSS - RSS - ISS; % Error Sum of Squares +end + +ip = NaN; +if (SSE~=0) + MSE = SSE/edf; + colf = (CSS/df1) / MSE; + rowf = (RSS/df2) / MSE; + colp = 1 - f_cdf(colf,df1,edf); % Probability of F given equal column means. + rowp = 1 - f_cdf(rowf,df2,edf); % Probability of F given equal row means. + p = [colp rowp]; + + if (reps > 1), + intf = (ISS/idf)/MSE; + ip = 1 - f_cdf(intf,idf,edf); + p = [p ip]; + end + +else % Dealing with special cases around no error. + if (edf > 0) + MSE = 0; + else + MSE = NaN; + end + if CSS==0, % No between column variability. + colf = 0; + colp = 1; + else % Between column variability. + colf = Inf; + colp = 0; + end + + if RSS==0, % No between row variability. + rowf = 0; + rowp = 1; + else % Between row variability. + rowf = Inf; + rowp = 0; + end + + p = [colp rowp]; + + if (reps>1) & (ISS==0) % Replication but no interactions. + intf = 0; + p = [p 1]; + elseif (reps>1) % Replication with interactions. + intf = Inf; + p = [p 0]; + end +end + +if (reps > 1), + Table{6,6} = []; %Formatting for ANOVA Table printout with interactions. + Table(2:6,1)={'Columns';'Rows';'Interaction';'Error';'Total'}; + Table(2:6,2)={CSS; RSS; ISS; SSE; TSS}; + Table(2:6,3)={df1; df2; idf; edf; r*c-1}; + Table(2:5,4)={CSS/df1; RSS/df2; ISS/idf; SSE/edf;}; + Table(2:4,5)={colf; rowf; intf}; +else + Table{5,6} = []; %Formatting for ANOVA Table printout no interactions. + Table(2:5,1)={'Columns';'Rows';'Error';'Total'}; + Table(2:5,2)={CSS; RSS; SSE; TSS}; + Table(2:5,3)={df1; df2; edf; r*c-1}; + Table(2:4,4)={CSS/df1; RSS/df2; SSE/edf;}; + Table(2:3,5)={colf; rowf}; +end + +Table(1,1:6) = {'Source' 'SS' 'df' 'MS' 'F' 'Prob>F'}; +Table(2:(1+length(p)),6) = num2cell(p); + +if (isequal(displayopt, 'on')) + digits = [-1 -1 0 -1 2 4]; + statdisptable(Table, 'Two-way ANOVA', 'ANOVA Table', '', digits); +end + +if (nargout > 2) + stats.source = 'anova2'; + stats.sigmasq = MSE; + stats.colmeans = colmean; % mean of columns + stats.coln = m*reps; % n for estimating column means + stats.rowmeans = rowmean; + stats.rown = c*reps; + stats.inter = (reps>1); % was an interaction term included? + stats.pval = ip; % p-value for interactions + stats.df = edf; +end + + + +return + + +function [g,gn] = grp2idx(s) +% GRP2IDX Create index vector from a grouping variable. +% [G,GN]=GRP2IDX(S) creates an index vector G from the grouping +% variable S. S can be a numeric vector, a character matrix (each +% row representing a group name), or a cell array of strings stored +% as a column vector. The result G is a vector taking integer +% values from 1 up to the number of unique entries in S. GN is a +% cell array of names, so that GN(G) reproduces S (aside from any +% differences in type). + +% Copyright 1993-2002 The MathWorks, Inc. +% $Revision: 1.4 $ $Date: 2002/03/13 23:20:16 $ + +if (ischar(s)) + s = cellstr(s); +end +if (size(s, 1) == 1) + s = s'; +end + +[gn,i,g] = uniquep(s); % b=unique group names + +ii = find(strcmp(gn, '')); +if (length(ii) == 0) + ii = find(strcmp(gn, 'NaN')); +end + +if (length(ii) > 0) + nangrp = ii(1); % this group should really be NaN + gn(nangrp,:) = []; % remove it from the names array + g(g==nangrp) = NaN; % set NaN into the group number array + g = g - (g > nangrp); % re-number remaining groups +end + +% ----------------------------------------------------------- +function [b,i,j] = uniquep(s) +% Same as UNIQUE but orders result: +% if iscell(s), preserve original order +% otherwise use numeric order + +[b,i,j] = unique(s); % b=unique group names + +nb = size(b,1); +i = zeros(nb,1); +if (iscell(s)) + % Restore in original order + for k=1:size(b,1) + ii = find(strcmp(s, b(k))); + i(k) = ii(1); % find first instance of each element of b + end + isort = i; % sort based on this order +else + % If b is a vector, put in numeric order + for k=1:size(b,1) + ii = find(s == b(k)); + if (length(ii) > 0) + i(k) = ii(1); % make sure this is the first instance + end + end + + % Fix up bad treatment of NaN + if (any(isnan(b))) % remove multiple NaNs; put one at the end + nans = isnan(b); + b = [b(~nans); NaN]; + x = find(isnan(s)); + i = [i(~nans); x(1)]; + j(isnan(s)) = length(b); + end + + isort = b; % sort based on numeric values + isort(isnan(isort)) = max(isort) + 1; +end + +[is, f] = sort(isort); % sort according to the right criterion +b = b(f,:); + +[fs, ff] = sort(f); % rearrange j also +j = ff(j); + +if (~iscell(b)) % make sure b is a cell array of strings + b = cellstr(strjust(num2str(b), 'left')); +end +return + + +Y=[ 7.56 9.68 11.65; + 9.98 9.69 10.69; + 7.23 10.49 11.77; + 8.22 8.55 10.72; + 7.59 8.30 12.36]; diff --git a/mystats/kurtosis.m b/mystats/kurtosis.m new file mode 100644 index 0000000..134900b --- /dev/null +++ b/mystats/kurtosis.m @@ -0,0 +1,19 @@ +function [S]=kurtosis(X) +% kurtosis - computes sample kurtosis of a distribution +% [S]=kurtosis(X) computes row-wise kurtosis +% The unbiased Fisher's equation for finite samples, a.k.a, G2 is used. +% Note: +% Positive kurtosis reflects sharp distribution. +% Standard Error of kurtosis for normal distribution is ~ sqrt(24/n) + +if isvector(X) + X=X(:); +end +sz=[size(X) 1 ]; +n=sz(1); +if n<4 + error('Kurtosis needs sample of size > 3') +end +S=sum((X-repmat(mean(X),n,1)).^4); +S=S./(var(X).^2); +S=(n*(n+1)/(n-1)/(n-2)/(n-3)).*S - 3*(n-1).^2/(n-2)/(n-3); diff --git a/mystats/myanova.1.m b/mystats/myanova.1.m new file mode 100644 index 0000000..984c22d --- /dev/null +++ b/mystats/myanova.1.m @@ -0,0 +1,679 @@ +function [p,F,fx,varargout]=myanova(X,nf,rp,epsilon,SStype) +% myanova - N-way ANOVA +% [p,F,fx]=myanova(X,nf) +% [p,F,fx,epsilon,pomega2,df,dfe,SS,SSe,SSt,peta2]=... +% Performs a N-way beween subjects anova on X +% +%INPUTS: +% X = [ factor1 x factor2 x ... x factorN x replicates x ...] +% This function is vectorized, ie. anova is computed for each value in X +% after the 'replicates' (i.e. subject) dimension. +% nf: number of factors +% +%OUTPUTS: +% p: the Null hypothesis probability +% F: the F-values +% fx: a cell array listing the tested effects. +% epsilon: sphericity correction if applicable +% pomega2: partial omega squared (unbiased estimator of effect size) +% df: degrees of freedom for each effect +% dfe: degrees of freedom of error for each effect +% SS: sum of squares for each effect +% SSe: sum of squares of error for each effect +% SSt: Total sum of squares +% +%OPTIONAL INPUTS: +% [...]=myanova(X,nf,rp) +% To specifiy repeated/within-subject factors in a within-subject design +% rp: an array listing the dimensions of repeated factors +% +% [...]=myanova(X,nf,rp,epsilon) +% Correct for non-spherical data using epsilon (expanded to match the +% size of measures, if necessary). If epsilon=NaN, epsilon(s) will be +% computed using Greenhouse-Geisser correction or Huynh-Feldt (if eGG>.7) +% Default: epsilon=1 +% +% Examples: +% +% X(1:2,1:3,1:10) = data from 2 tasks by 3 condition for 10 subjects, +% both factors (dimensions 1 and 2) are within-subject factors: +% >> [p,F,fx]=myanova(X,2,1:2) +% p: 3x1 array +% F: 3x1 array +% fx: 3x1 cells: { 1 , 2 , [ 1 2 ]} +% +% X(1:3,1:4,1:10) = data from 3 groups of 10 subjects performing 4 tasks +% only the second factor (dimension 2 of X) is a within-subject variable: +% >> [p,F,fx]=myanova(X,2,[2]) +% p: 3x1 array +% F: 3x1 array +% fx: 3x1 cells: { 1 , 2 , [ 1 2 ]} +% +% See also: myanovaeffects, + +% +% peta2: (biased) percentage of explained variance by each factor +% omega2: (unbiased) percentage of explained variance by each factor + +% Requires: f_cdf() + +SS=[]; +SSb=[]; +sX=[size(X) 1]; +nX=ndims(X); +if nargin<2 + error('Number of factors (nf) is mandatory') +end +if nargin<3 + rp=[]; +end +if nargin<4 + epsilon=1; +end +if nargin<5 + SStype=[]; +end + +fx={}; +for i=1:nf + fx=[fx ; num2cell(nchoosek(1:nf,i),2)]; +end +nfx=length(fx); + +% Number of groups for each factor +ng=sX(1:nf); +% Number of groups +png=prod(ng); +% Number of replicates, ie. samples in each group +nr=sX(nf+1); +% Number of cells +pnc=prod(ng); +%Number of observations +pno=pnc*nr; +% Dimensions of (pseudo multivariate) dependent variables +svar=[sX(nf+2:end)]; + +% if ~isempty(rp) & ~all(ismember(1:nf,rp)) +% error('Mixed designs (split plot) ANOVA unavailable!') +% end +% if ~isempty(rp) & nf>2 +% error('Repeated measure ANOVA is only available for 2 factor designs!') +% end + +% Sphericity checking +if isnan(epsilon) % | ischar(epsilon) + % check for sphericity first + if all(ng<=2) + epsilon=1; + else + [eGG,eHF]=sphericity(X,nf); + epsilon=eGG; + epsilon(eGG>.7)=eHF(eGG>.7); + epsilon(ng==2)=1; + epsilon=min(epsilon,1); + end +end +if numel(epsilon)==1; + epsilon=epsilon*ones(nfx,prod(sX(nf+2:end))); +elseif length(epsilon(:))==nfx + epsilon=epsilon(:)*ones(1,prod(sX(nf+2:end))); +end + +% Number of groups in between subjects +pngb=1; +if ~isempty(rp) + tmp=zeros(1,nf); + tmp(rp)=1; + rp=logical(tmp); + pngb=prod(ng(~rp)); + png=png/pngb; + % Factors within first, between next + fxw = [find(rp) find(~rp)]; + % ngw = sX(pfxw); +end +if isempty(SStype) + if isempty(rp) + SStype=3; + error('I dunno which SS type to choose...'); + elseif all(rp) + SStype=2; + else + error('I dunno which SS type to choose...'); + end +end +% Within Group/Error stats +% i.e unmodeled error +mXe=mean(X,nf+1); +SSe=X-repmat(mXe,[ones(1,nf) nr 1]); +SSe=reshape(SSe, [pnc nr svar]).^2; +SSe=sum(sum(SSe)); +if (nr<=1) + error('myanova doesn''t work with 1 subject / group!') +end +dfe=png*(nr-1); +MSe=squeeze(SSe/dfe); +dfe=repmat(dfe, [nfx 1]); + +% Population stats +dft=(pno)-1; +mX=reshape(mXe,pnc,[svar]); +mX=mean(mX,1); + +if any(rp) + % Between-Subject Error (to account for repeated measurements) + mXr=permute(X, [nf+1 fxw nf+2:nX]); + mXr=reshape(mXr,nr,png,pngb,[]); + mXr=mean(mXr,2); + sz=sX; + sz(rp)=1; + mXr=reshape(mXr,sz); + SSr=sum(png*sum((reshape(mXr,[nr pngb svar]) - repmat(mX,[nr pngb 1])).^2)); + dfr=nr*pngb-1; + % + % mXb=permute(X, [nf+1 fxw nf+2:nX]); + % mXb=reshape(mXb,nr*png,pngb,[svar]); + % mXb=mean(mXb,2); + % SSb = sum(pngb*sum((reshape(mXb,[nr png svar]) - repmat(mX,[nr png 1])).^2)); + % + % mXb=permute(X, [nf+1 fxw nf+2:nX]); + % mXb=reshape(mXb,nr*png,pngb,[svar]); + % mXb=mean(mXb); + % SSb = sum(nr*png*sum((reshape(mXb,[pngb svar]) - repmat(mX,[pngb 1])).^2)); + % + % + % mXb=permute(X, [nf+1 fxw nf+2:nX]); + % mXb=reshape(mXb,nr,png,pngb,[]); + % mXr=mean(mXr,2); + % sz=sX; + % sz(rp)=1; + % mXr=reshape(mXr,sz); + + % SSe=SSe- + +end + +% Outputs are: +% [p,F,fx,1:epsilon,2:pomega2,3:df,4:dfe,5:SS,6:SSe,7:SSt,8:peta2]=... +if nargout >= 3+1 + varargout{1}=epsilon; +end +if nargout >= 3+2 + SSt=sum((reshape(X,pno, [])-repmat(mX,[pno 1])).^2); +end + +df=zeros(nfx,1); +SS=zeros([nfx svar]); +F=zeros([nfx svar]); +p=zeros([nfx svar]); +for i=1:length(fx) + j=fx{i}; + % Between groups stats for each factor + df(i)=prod(ng(j)-1); + mXb=permute(mXe, [j setdiff(1:nf,j) nf+1:nX]); + % Number of levels for this factor + nl=prod(ng(j)); + mXb=reshape(mXb,nl,pnc/nl,[svar]); + mXb=mean(mXb,2); + SS(i,:) = pno/nl *sum((reshape(mXb, [nl svar]) - repmat(mX,[nl 1])).^2); +% SSb=nr*png/prod(ng(j))*sum((reshape(mXb, prod(ng(j)), []) - repmat(mX,[prod(ng(j)) 1])).^2); + + + if length(j)>1 + % Interactions: + % we need to x remove single factor variance from the Sum of Squares + for k=1:i-1 + if all(ismember(fx{k}, j)) + SS(i,:)=SS(i,:)-SS(k,:); + end + end + end + MS=SS(i,:)./df(i); + + % Now corrects Error term to account for repeated measurements + if any(rp(j)) + if length(j)==1 + dfe(i)=(nr-1)*df(i); + mXer=permute(X, [nf+1 j setdiff(1:nf,j) nf+2:nX]); + mXer=reshape(mXer,nr*nl,png/nl,[svar]); + mXer=mean(mXer,2); + SSe(i,:)=png/nl*sum((reshape(mXer, nr*nl, []) - repmat(mX,[nr*nl pngb 1])).^2); + SSe(i,:)=SSe(i,:)-SSr-SS(i,:); + %SSbe(i,:)=SSe; + else + % Interaction effects residuals + % + % WARNING !!!! + % + % the following does NOT work for more-than-2-factors design! + % + dfe(i)=(png-sum(ng-1)-1)*(nr-1); + mXer=reshape(X,pno,[]); + SSe(i,:)=sum((mXer - repmat(mX,[nr*png 1])).^2); + SSe(i,:)=SSe(i,:)-SSr-sum(SSe(1:nf,:))-sum(SS(1:nfx,:)); + end + MSe=squeeze(SSe(i,:)./dfe(i)); + end + % Apply d.f. correction if needed + df(i)=df(i).*epsilon(i,:); + dfe(i)=dfe(i).*epsilon(i,:); + % Compute F-value and gets the probability + F(i,:)=squeeze(MS)./MSe; + p(i,:)=1-f_cdf(F(i,:),df(i),dfe(i)); + % Outputs (if requested) are: + % [p,F,fx,1:epsilon,2:pomega2,3:df,4:dfe,5:SS,6:SSe,7:SSt,8:peta2]=... + if nargout >= 3+2 %3+6 + % We also need SSe to compute partial omega2 at the end + varargout{6}(i,:)=SSe; + end + +end +% reshape data to match input +if prod(sX)>pno + p=reshape(p,[nfx,sX(nf+2:end)]); + F=reshape(F,[nfx,sX(nf+2:end)]); +end +% Process outputs +if nargout >= 3+2 + % partial omega squared + SSe=varargout{6}; + MSe=SSe./dfe; + varargout{2}=(SS-df.*MSe)./(SSt+(png-df).*MSe); +end +if nargout >= 3+3 + varargout{3}=df; +end +if nargout >= 3+4 + varargout{4}=dfe; +end +if nargout >= 3+5 + varargout{5}=SS; +end +% if nargout >= 3+6 : cf supra +if nargout >= 3+7 + % SSt = SS total + varargout{7}=SSt; +end +if nargout >= 3+8 + % partial eta squared + varargout{8}=SS./(SS+SSe); +end +if nargout >= 3+9 + % omega squared + varargout{9}=(SS-df.*MSe)./(SSt+MSe); +end + + + + +return + + +if nargout>3 + SS(nfx+1,:)=SSt; + SS(nfx+2,:)=SSw; + SS(nfx+3,:)=SSr; + SS(nfx+4,:)=SSe; +end +if nargout>4 + df(nfx+1,:)=dft; + df(nfx+2,:)=dfw; + df(nfx+3,:)=dfr; + df(nfx+4,:)=dfe; +end +if nargout>5 + MS(nfx+1,:)=MSt; + MS(nfx+2,:)=MSw; + % MS(nfx+3,:)= MSr=squeeze(SSr/dfr);; + MS(nfx+4,:)=MSe; +end + + +return + +%% RESSOURCES +% http://www.brown.edu/Research/LCE/Fall2004/Within%20subjs.pdf +% + +%% EXAMPLES +% +% from Zar, 1999, pp. 250 sqq. +% Ex. 12.5 repeated measure ANOVA +X= [ + 164 152 178 + 202 181 222 + 143 136 132 + 210 194 216 + 228 219 245 + 173 159 182 + 161 157 165 + ]'; +% F= 727/57.94 = 12.6 + +% http://www.richland.edu/james/lecture/m170/ch13-2wy.html +% SS = 512.8667 449.4667 143.1333 136.0000 1241.4667 +% F = 3.682 3.056 2.641 +X=[ + 106, 110 , 95, 100 , 94, 107 , 103, 104 , 100, 102; + 110, 112 , 98, 99 , 100, 101 , 108, 112 , 105, 107; + 94, 97, 86, 87 98, 99 99, 101 94, 98]; +X=cat(3,X(:,1:2:end),X(:,2:2:end)) + + + + +% % http://www.linguistics.ucla.edu/faciliti/facilities/statistics/fromoac.htm +X=[ 8 9 8 8 9 7 10 9 10; 9 10 9 10 9 13 8 9 9; 8 7 7 12 7 9 10 9 7; 6 8 9 8 10 10 12 9 10; 7 6 7 11 12 8 8 11 9]; +X=reshape(X,5,3,3); +% +% % COND TRIAL SUBJECT +X=permute(X,[3 2 1]); +% +[p,F]=myanova(X,2,1:2) +% l=factorlabels(X) +% rm_anova2(X(:), l(:,3),l(:,1),l(:,2),{'COND', 'TRIAL'}) +% % 'Source' 'SS' 'df' 'MS' 'F' 'p' +% % 'COND' [24.8444] [ 2] [12.4222] [4.0216] [0.0618] +% % 'TRIAL' [ 0.3111] [ 2] [ 0.1556] [0.0625] [0.9399] +% % 'COND x TRIAL' [ 1.6889] [ 4] [ 0.4222] [0.1907] [0.9397] +% % 'COND x Subj' [24.7111] [ 8] [ 3.0889] [] [] +% % 'COND x Subj' [19.9111] [ 8] [ 2.4889] [] [] +% % 'COND x TRIAL x Subj' [35.4222] [16] [ 2.2139] [] [] + + +%% DATA Eysenck 1974 +% http://web.uccs.edu/lbecker/Psy590/es.htm +% http://homepages.gold.ac.uk/aphome/GLM%20Simple%20Main%20Effects.html +% Counting Rhyming Adjective Imagery Intentional +X=[ + 9 7 11 12 10 + 8 9 13 11 19 + 6 6 8 16 14 + 8 6 6 11 5 + 10 6 14 9 10 + 4 11 11 23 11 + 6 6 13 12 14 + 5 3 13 10 15 + 7 8 10 19 11 + 7 7 11 11 11 + ]; + +% Eysenck 1974 OLD/YOUNG +% +X=urlread('http://forrest.psych.unc.edu/research/vista-frames/help/lecturenotes/lecture10/eysenck.xli'); +X='DATA "Eysenck" :TITLE "Eysenck" :ABOUT "Eysenck study of recall ability of subjects who were young (Y - 18-30 years old) or old (O - 55-65 years old) for recall conditions involving Counting, Rhyming, Adjectives, Imagery or Intentional. From Howell, David C., (Ed. 3) p. 325." :VARIABLES (QUOTE ("Recall" "Age" "Condition")) :TYPES (QUOTE ("Numeric" "Category" "Category")) :LABELS (QUOTE ("Obs0" "Obs1" "Obs2" "Obs3" "Obs4" "Obs5" "Obs6" "Obs7" "Obs8" "Obs9" "Obs10" "Obs11" "Obs12" "Obs13" "Obs14" "Obs15" "Obs16" "Obs17" "Obs18" "Obs19" "Obs20" "Obs21" "Obs22" "Obs23" "Obs24" "Obs25" "Obs26" "Obs27" "Obs28" "Obs29" "Obs30" "Obs31" "Obs32" "Obs33" "Obs34" "Obs35" "Obs36" "Obs37" "Obs38" "Obs39" "Obs40" "Obs41" "Obs42" "Obs43" "Obs44" "Obs45" "Obs46" "Obs47" "Obs48" "Obs49" "Obs50" "Obs51" "Obs52" "Obs53" "Obs54" "Obs55" "Obs56" "Obs57" "Obs58" "Obs59" "Obs60" "Obs61" "Obs62" "Obs63" "Obs64" "Obs65" "Obs66" "Obs67" "Obs68" "Obs69" "Obs70" "Obs71" "Obs72" "Obs73" "Obs74" "Obs75" "Obs76" "Obs77" "Obs78" "Obs79" "Obs80" "Obs81" "Obs82" "Obs83" "Obs84" "Obs85" "Obs86" "Obs87" "Obs88" "Obs89" "Obs90" "Obs91" "Obs92" "Obs93" "Obs94" "Obs95" "Obs96" "Obs97" "Obs98" "Obs99")) :DATA (QUOTE (9 "O" "C" 8 "O" "C" 6 "O" "C" 8 "O" "C" 10 "O" "C" 4 "O" "C" 6 "O" "C" 5 "O" "C" 7 "O" "C" 7 "O" "C" 7 "O" "R" 9 "O" "R" 6 "O" "R" 6 "O" "R" 6 "O" "R" 11 "O" "R" 6 "O" "R" 3 "O" "R" 8 "O" "R" 7 "O" "R" 11 "O" "A" 13 "O" "A" 8 "O" "A" 6 "O" "A" 14 "O" "A" 11 "O" "A" 13 "O" "A" 13 "O" "A" 10 "O" "A" 11 "O" "A" 12 "O" "I" 11 "O" "I" 16 "O" "I" 11 "O" "I" 9 "O" "I" 23 "O" "I" 12 "O" "I" 10 "O" "I" 19 "O" "I" 11 "O" "I" 10 "O" "IN" 19 "O" "IN" 14 "O" "IN" 5 "O" "IN" 10 "O" "IN" 11 "O" "IN" 14 "O" "IN" 15 "O" "IN" 11 "O" "IN" 11 "O" "IN" 8 "Y" "C" 6 "Y" "C" 4 "Y" "C" 6 "Y" "C" 7 "Y" "C" 6 "Y" "C" 5 "Y" "C" 7 "Y" "C" 9 "Y" "C" 7 "Y" "C" 10 "Y" "R" 7 "Y" "R" 8 "Y" "R" 10 "Y" "R" 4 "Y" "R" 7 "Y" "R" 10 "Y" "R" 6 "Y" "R" 7 "Y" "R" 7 "Y" "R" 14 "Y" "A" 11 "Y" "A" 18 "Y" "A" 14 "Y" "A" 13 "Y" "A" 22 "Y" "A" 17 "Y" "A" 16 "Y" "A" 12 "Y" "A" 11 "Y" "A" 20 "Y" "I" 16 "Y" "I" 16 "Y" "I" 15 "Y" "I" 18 "Y" "I" 16 "Y" "I" 20 "Y" "I" 22 "Y" "I" 14 "Y" "I" 19 "Y" "I" 21 "Y" "IN" 19 "Y" "IN" 17 "Y" "IN" 15 "Y" "IN" 22 "Y" "IN" 16 "Y" "IN" 22 "Y" "IN" 22 "Y" "IN" 18 "Y" "IN" 21 "Y" "IN")) :DATASHEET-ARGUMENTS (QUOTE ((420 475) (511 120) 2 8)))'; +X=strread(X, '%s', 'delimiter', ' '); +X=X(160:3:end-8) +X{1}(1)=[]; +X=str2num(strvcat(X)); +X=reshape(X,10,5,2); +X=permute(X,[3 2 1]); % AGE x COND x SUBJ + + +% Migraine Headache +% http://core.ecu.edu/psyc/wuenschk/SPSS/SPSS-Data.htm +X= [ + 21 22 8 6 6 + 20 19 10 4 9 + 7 5 5 4 5 + 25 30 13 12 4 + 30 33 10 8 6 + 19 27 8 7 4 + 26 16 5 2 5 + 13 4 8 1 5 + 26 24 14 8 17 + ]; +% Howell Table 14.3 +X= [ + 21 22 8 6 6 + 20 19 10 4 4 + 17 15 5 4 5 + 25 30 13 12 17 + 30 27 13 8 6 + 19 27 8 7 4 + 26 16 5 2 5 + 17 18 8 1 5 + 26 24 14 8 9 + ]; + + +% http://lib.stat.cmu.edu/DASL/Datafiles/airpullutionfiltersdat.html +% 1. NOISE = Noise level reading (decibels) +% 2. SIZE = Vehicle size: 1 small 2 medium 3 large +% 3. TYPE = 1 standard silencer 2 Octel filter +% 4. SIDE = 1 right side 2 left side of car +% and 3 replicates +x=[ + 810 1 1 1 + 820 1 1 1 + 820 1 1 1 + 840 2 1 1 + 840 2 1 1 + 845 2 1 1 + 785 3 1 1 + 790 3 1 1 + 785 3 1 1 + 835 1 1 2 + 835 1 1 2 + 835 1 1 2 + 845 2 1 2 + 855 2 1 2 + 850 2 1 2 + 760 3 1 2 + 760 3 1 2 + 770 3 1 2 + 820 1 2 1 + 820 1 2 1 + 820 1 2 1 + 820 2 2 1 + 820 2 2 1 + 825 2 2 1 + 775 3 2 1 + 775 3 2 1 + 775 3 2 1 + 825 1 2 2 + 825 1 2 2 + 825 1 2 2 + 815 2 2 2 + 825 2 2 2 + 825 2 2 2 + 770 3 2 2 + 760 3 2 2 + 765 3 2 2 + ]; +[i,nl]=dgrouping(x(:,2:end)); +X(i)=x(:,1); +X=reshape(X,nl); + + +% +X = [ + 92 + 90 + 92 + 91 + 90 + + 70 + 70 + 71 + 70 + 72 + + 90 + 90 + 92 + 94 + 91 + + 87 + 82 + 81 + 84 + 85 + + 66 + 65 + 67 + 69 + 71 + + 62 + 59 + 58 + 60 + 59 + + 88 + 88 + 83 + 82 + 88 + + 85 + 85 + 82 + 84 + 83 + ]; +X=permute(reshape(X,[5 2 2 2]), [4 3 2 1]); + + +% http://www.ou.edu/faculty/M/Jorge.L.Mendoza-1/psy5013/twoway-repeated.sas +% [Traning] [Males: Pre Post FU6 FU12] [Females: Pre Post FU6 FU12] +X = [ + 1 7 22 13 14 0 6 22 26 + 1 25 10 17 24 0 16 12 15 + 1 50 36 49 23 0 8 0 0 + 1 16 38 34 24 15 14 22 8 + 1 33 25 24 25 27 18 24 37 + 1 10 7 23 26 0 0 0 0 + 1 13 33 27 24 4 27 21 3 + 1 22 20 21 11 26 9 9 12 + 1 4 0 12 0 0 0 14 1 + 1 17 16 20 10 0 0 12 0 + 0 0 0 0 0 15 28 26 15 + 0 69 56 14 36 0 0 0 0 + 0 5 0 0 5 6 0 23 0 + 0 4 24 0 0 0 0 0 0 + 0 35 8 0 0 25 28 0 16 + 0 7 0 9 37 36 22 14 48 + 0 51 53 8 26 19 22 29 2 + 0 25 0 0 15 0 0 5 14 + 0 59 45 11 16 0 0 0 0 + 0 40 2 33 16 0 0 0 0 + ]; +X=reshape(X(:,2:end),[10,2,4 2]); +X=permute(X, [2 3 4 1]); +[p,F,fx,epsilon,pomega2,df,dfe,SS,SSe,SSt,peta2,omega2]=myanova(X,3,2) + + +% http://www.psych.northwestern.edu/help/systat/Syst.facmix.html +% subject Smoking High Stress Low Stress +% 1 light 8 1 +% 2 light 7 1 +% 3 heavy 6 8 +% 4 heavy 5 6 +% 5 light 8 3 +% 6 light 10 1 +% 7 heavy 6 7 +% 8 light 9 2 +% 9 heavy 8 10 +% 10 light 9 2 +x=[ + 1 8 1 + 1 7 1 + 2 6 8 + 2 5 6 + 1 8 3 + 1 10 1 + 2 6 7 + 1 9 2 + 2 8 10 + 1 9 2 + ]; +%X=reshape(X, +[i,nl]=dgrouping([ [x(:,1);x(:,1)],[repmat(1,10,1);repmat(2,10,1)]]); +[i,nl]=dgrouping([ [[1:10]';[1:10]'] [x(:,1);x(:,1)],[repmat(1,10,1);repmat(2,10,1)]]); +X=NaN*zeros(nl); + +%http://www.isogenic.info/html/example_2.html http://www.isogenic.info/html/example_2.html +% Strain EROD Treatm Block +x=[ + 1 18.7 1 1 + 2 17.9 1 1 + 3 19.2 1 1 + 4 26.3 1 1 + 1 7.7 2 1 + 2 8.4 2 1 + 3 9.8 2 1 + 4 9.7 2 1 + 1 16.7 1 2 + 2 14.4 1 2 + 3 12.0 1 2 + 4 19.8 1 2 + 1 6.4 2 2 + 2 6.7 2 2 + 3 8.1 2 2 + 4 6.0 2 2 + ] +[i,nl]=dgrouping(x(:,[1 3 4])) +X=NaN*zeros(nl); +X(i)=x(:,2); +%In this case a three-way ANOVA with strain and treatment being fixed +%effects (determined by the experimentalist) and the block being a random +%factor (i.e. not a specific treatment). +% +% Source DF SS MS F P +% Block 1 47.610 47.610 18.37 0.004 +% Strain 3 32.962 10.988 4.24 0.053 +% Trt 1 422.303 422.303 162.96 0.000 +% Strain*Trt 3 40.343 13.448 5.19 0.034 +% Error 7 18.140 2.591 +% Total 15 561.358 + + +% ABRDATA.TAB from OpenStat +% http://www.statpages.org/miller/openstat/Analyzing%20Data%20with%20Stats4U.pdf +% Row Col C1 C2 C3 C4 +X=[ + 1.00 1.00 18.00 14.00 12.00 6.00 + 1.00 1.00 19.00 12.00 8.00 4.00 + 1.00 1.00 14.00 10.00 6.00 2.00 + 1.00 2.00 16.00 12.00 10.00 4.00 + 1.00 2.00 12.00 8.00 6.00 2.00 + 1.00 2.00 18.00 10.00 5.00 1.00 + 2.00 1.00 16.00 10.00 8.00 4.00 + 2.00 1.00 18.00 8.00 4.00 1.00 + 2.00 1.00 16.00 12.00 6.00 2.00 + 2.00 2.00 19.00 16.00 10.00 8.00 + 2.00 2.00 16.00 14.00 10.00 9.00 + 2.00 2.00 16.00 12.00 8.00 8.00 + ]; +X=reshape(X(:,3:end), [3 2 2 4]); +X=permute(X,[2 3 4 1]); + +% SOURCE DF SS MS F PROB. +% Between Subjects 11 181.000 +% A Effects 1 10.083 10.083 0.978 0.352 +% B Effects 1 8.333 8.333 0.808 0.395 +% AB Effects 1 80.083 80.083 7.766 0.024 +% Error Between 8 82.500 10.312 +% Within Subjects 36 1077.000 +% C Replications 3 991.500 330.500 152.051 0.000 +% AC Effects 3 8.417 2.806 1.291 0.300 +% BC Effects 3 12.167 4.056 1.866 0.162 +% ABC Effects 3 12.750 4.250 1.955 0.148 +% Error Within 24 52.167 2.174 +% Total 47 1258.000 + +function [SS,df]=sumofsquares(X,f1,nf,mX) +sX=size(X); +f2=setdiff(1:nf+1,f1); +mXf = permute(X, [f1 f2 nf+2:ndims(X)]); +mXf = reshape(mXf,prod(sX(f1)),prod(sX(f2)),[]); +mXf = mean(mXf,1); +SS = mXf - repmat(mX,[1 prod(sX(f2)) 1]) +%SS = reshape(SS, prod(sX(f2)), []).^2 +SS = SS.^2 +SS = sum(sum(SS,2),1); +df = prod(sX(f1)) \ No newline at end of file diff --git a/mystats/myanova.2.m b/mystats/myanova.2.m new file mode 100644 index 0000000..b2d9ed2 --- /dev/null +++ b/mystats/myanova.2.m @@ -0,0 +1,516 @@ +function [p,F,fx,varargout]=myanova(X,nf,rp,epsilon,verbose) +% myanova - N-way ANOVA +% [p,F,fx]=myanova(X,nf) +% [p,F,fx,epsilon,df,dfe,SS,SSe,SSt]=... +% Performs a N-way beween subjects anova on X +% +%INPUTS: +% X = [ factor1 x factor2 x ... x factorN x replicates x ...] +% This function is vectorized, ie. anova is computed for each value in X +% after the 'replicates' (i.e. subject) dimension. +% nf: number of factors +% +%OUTPUTS: +% p: the Null hypothesis probability +% F: the F-values +% fx: a cell array listing the tested effects. +% eta: percentage of explained variance by each factor +% epsilon: sphericity correction if applicable +% +%OPTIONAL INPUTS: +% [...]=myanova(X,nf,rp) +% To specifiy repeated/within-subject factors in a within-subject design +% rp: an array listing the dimensions of repeated factors +% +% [...]=myanova(X,nf,rp,epsilon) +% Correct for non-spherical data using epsilon (expanded to match the +% size of measures, if necessary). If epsilon=NaN, epsilon(s) will be +% computed using Greenhouse-Geisser correction or Huynh-Feldt (if eGG>.7) +% +% Examples: +% +% X(1:2,1:3,1:10) = data from 2 tasks by 3 condition for 10 subjects, +% both factors (dimensions 1 and 2) are within-subject factors: +% >> [p,F,fx]=myanova(X,2,1:2) +% p: 3x1 array +% F: 3x1 array +% fx: 3x1 cells: { 1 , 2 , [ 1 2 ]} +% +% X(1:3,1:4,1:10) = data from 3 groups of 10 subjects performing 4 tasks +% only the second factor (dimension 2 of X) is a within-subject variable: +% >> [p,F,fx]=myanova(X,2,[2]) +% p: 3x1 array +% F: 3x1 array +% fx: 3x1 cells: { 1 , 2 , [ 1 2 ]} +% +% Requires: f_cdf() + +sX=[size(X) 1]; +nX=ndims(X); +if nargin<2 + error('Number of factors (nf) is mandatory') +end +% Number of groups for each factor +ng=sX(1:nf); +png=prod(ng); +% Number of replicates, ie. samples in each group +nr=sX(nf+1); +if nr<2 + error('You must have more than one observation per cell/one subject per condition') +end + +if nargin<3 + rp=[]; +end +% if ~isempty(rp) & ~all(ismember(1:nf,rp)) +% error('Mixed designs (split plot) ANOVA unavailable!') +% end +% if ~isempty(rp) & nf>2 +% error('Repeated measure ANOVA is only available for 2 factor designs!') +% end +if nargin<4 + epsilon=1; +end +if nargin<5 || isempty(verbose) + verbose=1; +end +if isempty(epsilon) + epsilon=NaN; +end + +% Within Group/Error stats +% Mean in each cell +mXw=mean(X,nf+1); +% Residual Error of the model +SSe=X-repmat(mXw,[ones(1,nf) nr 1]); +SSe=reshape(SSe, png*nr, []).^2; +SSe=sum(SSe); +dfe=png*(nr-1); +MSe=squeeze(SSe/dfe); +%keeps the full model unexplained variance for later +MSw=MSe; +dfw=dfe; + +% Population stats +dft=(png*nr)-1; +% Grand mean value +mX=reshape(mXw, png, []); +mX=mean(mX,1); +% Correction factor: +% cX=mX.^2./(png*nr); + +% These are not used afterwards +SSt=sum((reshape(X, png*nr, [])-repmat(mX,[png*nr 1])).^2); +% MSt=squeeze(SSt/dft); + +if ~isempty(rp) + % Correct Within Error term to account for repeated measurements + dfr=(nr-1); + mXr=permute(X, [nf+1 1:nf nf+2:nX]); + mXr=reshape(mXr,nr,png,[]); + mXr=mean(mXr,2); + SSr=png*sum((reshape(mXr, nr, []) - repmat(mX,[nr 1])).^2); +end + +% Sphericity checking +if isnan(epsilon) % | ischar(epsilon) + % check for sphericity first + if all(ng<=2) + epsilon=1; + else + [eGG,eHF]=sphericity(X,nf); + epsilon=eGG; + epsilon(eGG>.7)=eHF(eGG>.7); + epsilon(ng==2)=1; + epsilon=min(epsilon,1); + end +end +if numel(epsilon)==1; + epsilon=epsilon*ones(nfx,prod(sX(nf+2:end))); +elseif length(epsilon(:))==nfx + epsilon=epsilon(:)*ones(1,prod(sX(nf+2:end))); +end + +% Number of groups in between subjects +pngb=1; +if ~isempty(rp) + tmp=zeros(1,nf); + tmp(rp)=1; + rp=logical(tmp); + pngb=prod(ng(~rp)); + png=png/pngb; + % Factors within first, between next + fxw = [find(rp) find(~rp)]; + % ngw = sX(pfxw); +end + +% Within Group/Error stats +% i.e unmodeled error +mXe=mean(X,nf+1); +SSe=X-repmat(mXe,[ones(1,nf) nr 1]); +SSe=reshape(SSe, [pnc nr svar]).^2; +SSe=sum(sum(SSe)); +if (nr<=1) + error('myanova doesn''t work with 1 subject / group!') +end +dfe=png*(nr-1); +MSe=squeeze(SSe/dfe); +dfe=repmat(dfe, [nfx 1]); + +% Population stats +dft=(pno)-1; +mX=reshape(mXe,pnc,[svar]); + varargout{1}=epsilon; +end +if nargout > 8 || verbose==1 + varargout{6}=SSt; +end + +for i=1:length(fx) + j=fx{i}; + % Between groups stats for each factor + dfb=prod(ng(j)-1); + mXb=permute(mXw, [j setdiff(1:nf,j) nf+1:nX]); + mXb=reshape(mXb, prod(ng(j)),png/prod(ng(j)), []); + mXb=mean(mXb,2); + SSb=nr*png/prod(ng(j))*sum((reshape(mXb, prod(ng(j)), []) - repmat(mX,[prod(ng(j)) 1])).^2); + + if length(j)>1 + % Interaction: + % we need to remove the variance from emvbedded effects from the + % Sum of Squares of this effect + for k=1:i-1 + if all(ismember(fx{k}, j)) + SSb=SSb-SS(k,:); + end + end + end + SS(i,:)=SSb; + MSb=SSb./dfb; + + if any(ismember(j,rp)) + % Correct Error term to account for repeated measurements + dfe=(nr-1)*dfb; + mXe=permute(X, [nf+1 j setdiff(1:nf,j) nf+2:nX]); + mXe=reshape(mXe,nr*prod(ng(j)),png/prod(ng(j)),[]); + mXe=mean(mXe,2); + SSe=png/prod(ng(j))*sum((reshape(mXe, nr*prod(ng(j)), []) - repmat(mX,[nr*prod(ng(j)) 1])).^2); + SSe=SSe-SSr-SSb; + for k=1:i-1 + %remove embedded factors effects and error terms + if all(ismember(fx{k}, j)) + SSe=SSe-SS(k,:)-SSbe(k,:); + end + end + end + SSbe(i,:)=SSe; + MSe=squeeze(SSe/dfe); + % % MSr=squeeze(SSr/dfr); + % else + % dfw=png*(nr-1); + % MSw=squeeze(SSw/dfw); + % + % dfe=dfw; + % MSe=MSw; + % end + dfb=dfb.*epsilon(i,:); + dfe=dfe.*epsilon(i,:); + F(i,:)=squeeze(MSb)./MSe; + p(i,:)=1-f_cdf(F(i,:),dfb,dfe); + % dfb=dfb.*epsilon(i,:); + % dfw=dfw.*epsilon(i,:); + % F(i,:)=squeeze(MSb)./MSw; + % p(i,:)=1-f_cdf(F(i,:),dfb,dfw); + % end + % [p,F,fx,epsilon(1),df(2),dfe(3),SS(4),SSe(5),SSt(6)]=... + if nargout>4 || verbose==1 + varargout{2}(i,:)=dfb; + end + if nargout>5 || verbose==1 + %if not(isempty(rp)) + varargout{3}(i,:)=dfe; + %else + % varargout{3}(i,:)=dfw; + %end + end + if nargout>6 || verbose==1 + varargout{4}(i,:)=SSb; + end + if nargout>7 || verbose==1 + %if not(isempty(rp)) + varargout{5}(i,:)=SSe; + %else + % varargout{5}(i,:)=SSw; + %end + end +end + +if prod(sX)>png*nr + p=reshape(p,[nfx,sX(nf+2:end)]); + F=reshape(F,[nfx,sX(nf+2:end)]); +end + +if verbose + [ignore,df,dfe,SSb,SSe,SSt]=deal(varargout{:}); + MS=SS./df; + + %Display results + fprintf([repmat('-',1,80) '\n'] ); + fprintf(['%' num2str(max(cellfun('length', fx))*2+15) 's \t| df \t| SS \t| MS \t| F \t| p\n'], 'ANOVA results'); + fprintf([repmat('-',1,80) '\n'] ); + for j=1:min(size(p,2),3) + if size(p,2)>1 + fprintf('MULTIDIMENSIONAL DATA: [%d] ...\n',j) + end + + for i=1:length(fx) + if i<=nf + label=sprintf('Factor #%d',i); + else + label=['Interaction ' sprintf('%d*',fx{i})]; + label=label(1:end-1); + end + label=sprintf([ '%' num2str(max(cellfun('length', fx))*2+15) 's'],label); + fprintf('%s: \t % 3.3g \t% 8.4g\t% 8.4g\t% 8.4g\t %g\n',... + label,df(i),SS(i,j),MS(i,j),F(i,j),p(i,j)); + if ~isempty(rp) || i==nfx + label=sprintf([ '%' num2str(length(label)) 's'],'Error'); + fprintf('%s: \t % 3.3g \t% 8.4g\t% 8.4g\n',... + label,dfe(i),SSe(i,j),SSe(i,j)./dfe(i)); + end + end + + if ~isempty(rp) + MS_r=SSr./dfr; + F_r=squeeze(MS_r)./MSw; + p_r=1-f_cdf(F_r,dfr,dfw); + label=sprintf([ '%' num2str(length(label)) 's'],'Subjects'); + fprintf('%s: \t % 3.3g \t% 8.4g\t% 8.4g\t% 8.4g\t %g\n',... + label,dfr,SSr(j),MS_r(j),F_r(j),p_r(j)); + end + label=sprintf([ '%' num2str(max(cellfun('length', fx))*2+15) 's'],'Total'); + fprintf('%s: \t % 3.3g \t% 8.4g\n',... + label,dft,SSt(j)); + if size(p,2)>1 + fprintf([repmat('. ',1,40) '\n']) + end + + end + if j3 + SS(nfx+1,:)=SSt; + SS(nfx+2,:)=SSw; + SS(nfx+3,:)=SSr; + SS(nfx+4,:)=SSe; +end +if nargout>4 + df(nfx+1,:)=dft; + df(nfx+2,:)=dfw; + df(nfx+3,:)=dfr; + df(nfx+4,:)=dfe; +end +if nargout>5 + MS(nfx+1,:)=MSt; + MS(nfx+2,:)=MSw; + MS(nfx+3,:)=MSr; + MS(nfx+4,:)=MSe; +end + + +return + +%% RESSOURCES +% http://www.brown.edu/Research/LCE/Fall2004/Within%20subjs.pdf +% + +%% EXAMPLES +% +% from Zar, 1999, pp. 250 sqq. +% Ex. 12.5 (repeated measure one-way ANOVA) +X= [ + 164 152 178 + 202 181 222 + 143 136 132 + 210 194 216 + 228 219 245 + 173 159 182 + 161 157 165 + ]'; +% F = (1454/2)/(695.3/12) = 727/57.94 = 12.6 + + +% BETWEEN SUBJECT DESIGN WITH 2 REPLICATES +% ======================================== +% http://www.richland.edu/james/lecture/m170/ch13-2wy.html +% SS = 512.8667 449.4667 143.1333 136.0000 1241.4667 +% F observed = 28.283 12.393 1.973 +% F critique = 3.682 3.056 2.641 +X=[ + 106, 110 , 95, 100 , 94, 107 , 103, 104 , 100, 102; + 110, 112 , 98, 99 , 100, 101 , 108, 112 , 105, 107; + 94, 97, 86, 87 98, 99 99, 101 94, 98]; +X=cat(3,X(:,1:2:end),X(:,2:2:end)) +[p,F]=myanova(X,2,[]) + + + +% % http://www.linguistics.ucla.edu/faciliti/facilities/statistics/fromoac.htm +X=[ 8 9 8 8 9 7 10 9 10; 9 10 9 10 9 13 8 9 9; 8 7 7 12 7 9 10 9 7; 6 8 9 8 10 10 12 9 10; 7 6 7 11 12 8 8 11 9]; +X=reshape(X,5,3,3); +% +% % COND TRIAL SUBJECT +X=permute(X,[3 2 1]); +% +[p,F]=myanova(X,2,1:2) +% l=factorlabels(X) +% rm_anova2(X(:), l(:,3),l(:,1),l(:,2),{'COND', 'TRIAL'}) +% % 'Source' 'SS' 'df' 'MS' 'F' 'p' +% % 'COND' [24.8444] [ 2] [12.4222] [4.0216] [0.0618] +% % 'TRIAL' [ 0.3111] [ 2] [ 0.1556] [0.0625] [0.9399] +% % 'COND x TRIAL' [ 1.6889] [ 4] [ 0.4222] [0.1907] [0.9397] +% % 'COND x Subj' [24.7111] [ 8] [ 3.0889] [] [] +% % 'TRIAL x Subj' [19.9111] [ 8] [ 2.4889] [] [] +% % 'COND x TRIAL x Subj' [35.4222] [16] [ 2.2139] [] [] + + + +% http://lib.stat.cmu.edu/DASL/Datafiles/airpullutionfiltersdat.html +% 1. NOISE = Noise level reading (decibels) +% 2. SIZE = Vehicle size: 1 small 2 medium 3 large +% 3. TYPE = 1 standard silencer 2 Octel filter +% 4. SIDE = 1 right side 2 left side of car +% and 3 replicates + +x=[ + 810 1 1 1 + 820 1 1 1 + 820 1 1 1 + 840 2 1 1 + 840 2 1 1 + 845 2 1 1 + 785 3 1 1 + 790 3 1 1 + 785 3 1 1 + 835 1 1 2 + 835 1 1 2 + 835 1 1 2 + 845 2 1 2 + 855 2 1 2 + 850 2 1 2 + 760 3 1 2 + 760 3 1 2 + 770 3 1 2 + 820 1 2 1 + 820 1 2 1 + 820 1 2 1 + 820 2 2 1 + 820 2 2 1 + 825 2 2 1 + 775 3 2 1 + 775 3 2 1 + 775 3 2 1 + 825 1 2 2 + 825 1 2 2 + 825 1 2 2 + 815 2 2 2 + 825 2 2 2 + 825 2 2 2 + 770 3 2 2 + 760 3 2 2 + 765 3 2 2 + ]; +[i,nl]=dgrouping(x(:,2:end)); +X(i)=x(:,1); +X=reshape(X,nl); + +% One way ANOVA from Loftus & Masson, 1994 +% Exposure Duration Per Word (sec) +% 1 Sec 2 Sec 5 Sec +X= [ + 10 13 13 + 6 8 8 + 11 14 14 + 22 23 25 + 16 18 20 + 15 17 17 + 1 1 4 + 12 15 17 + 9 12 12 + 8 9 12 + ]'; +% M1 = 11.0 M2 = 13.0 M3 = 14.2 + +% Two-way ANOVA from Cousineau 2007 +X= [ + 550 580 610 + 605 635 655 + 660 690 710 + ]'; + + +% Cousineau ? +X= [ + 150. 44. 71. 59. 132. 74. 1. + 335. 270. 156. 160. 118. 230. 1. + 149. 52. 91. 115. 43. 154. 1. + 159. 31. 127. 212. 71. 224. 1. + 159. 0. 35. 75. 71. 34. 1. + 292. 125. 184. 246. 225. 170. 1. + 297. 187. 66. 96. 209. 74. 1. + 170. 37. 42. 66. 114. 81. 1. + 346. 175. 177. 192. 239. 140. 2. + 426. 329. 236. 76. 102. 232. 2. + 359. 238. 183. 123. 183. 30. 2. + 272. 60. 82. 85. 101. 98. 2. + 200. 271. 263. 216. 241. 227. 2. + 366. 291. 263. 144. 220. 180. 2. + 371. 364. 270. 308. 219. 267. 2. + 497. 402. 294. 216. 284. 255. 2. + 282. 186. 225. 134. 189. 169. 3. + 317. 31. 85. 120. 131. 205. 3. + 362. 104. 144. 114. 115. 127. 3. + 338. 132. 91. 77. 108. 169. 3. + 263. 94. 141. 142. 120. 195. 3. + 138. 38. 16. 95. 39. 55. 3. + 329. 62. 62. 6. 93. 67. 3. + 292. 139. 104. 184. 193. 122. 3. + ]; +X=reshape(X(:,1:6),[8,3,6]); +X=permute(X,[1 3 2]); + + +% Cousineau TQPM +X=str2num(urlread('http://www.tqmp.org/doc/vol1-1/p42.dat')); +X=reshape(X(:,end),[16,5,2]); +X=permute(X,[3 2 1]); +myanova(X,2,1:2) + +% Loftus & Masson 1994 +X= [ + 1 450 462 12 460 482 22 460 497 37 480 507 27 + 2 510 492 -18 515 530 15 520 534 14 504 550 46 + 3 492 508 16 512 522 10 503 553 50 520 539 19 + 4 524 532 8 530 543 13 517 546 29 503 553 50 + 5 420 409 -11 424 452 28 431 468 37 446 472 26 + 6 540 550 10 538 528 -10 552 575 23 562 598 36 + ]; +X=reshape(X(:,2:end),6,3,4); +X=permute(X(:,1:2,:),[2 3 1]); +% X : R vs U | SOA 50/100/200/400 | SUBJ(6) diff --git a/mystats/myanova.6.m b/mystats/myanova.6.m new file mode 100644 index 0000000..8b99077 --- /dev/null +++ b/mystats/myanova.6.m @@ -0,0 +1,737 @@ +function [p,F,fx,varargout]=myanova(X,nf,rp,epsilon,SStype) +% myanova - N-way ANOVA +% [p,F,fx]=myanova(X,nf) +% [p,F,fx,epsilon,pomega2,df,dfe,SS,SSe,SSt]=... +% myanova(X,nf,rp,epsilon,SStype) +% Performs a N-way beween subjects anova on X +% +%INPUTS: +% X = [ factor1 x factor2 x ... x factorN x replicates x ...] +% This function is vectorized, ie. anova is computed for each value in X +% after the 'replicates' (i.e. subject) dimension. +% nf: number of factors +% +%OUTPUTS: +% p: the Null hypothesis probability +% F: the F-values +% fx: a cell array listing the tested effects. +% epsilon: sphericity correction if applicable +% pomega2: partial omega squared (unbiased estimator of effect size) +% df: degrees of freedom for each effect +% dfe: degrees of freedom of error for each effect +% SS: sum of squares for each effect +% SSe: sum of squares of error for each effect +% SSt: Total sum of squares +% peta2: partial eta-squared, (biased) percentage of explained variance by each factor +% omega2: (unbiased) percentage of explained variance by each factor +% eta2: cf infra +% +%OPTIONAL INPUTS: +% [...]=myanova(X,nf,rp) +% To specifiy repeated/within-subject factors in a within-subject design +% rp: an array listing the dimensions of repeated factors +% +% [...]=myanova(X,nf,rp,epsilon) +% Correct for non-spherical data using epsilon (expanded to match the +% size of measures, if necessary). If epsilon=NaN, epsilon(s) will be +% computed using Greenhouse-Geisser correction or Huynh-Feldt (if eGG>.7) +% +% Examples: +% +% X(1:2,1:3,1:10) = data from 2 tasks by 3 condition for 10 subjects, +% both factors (dimensions 1 and 2) are within-subject factors: +% >> [p,F,fx]=myanova(X,2,1:2) +% p: 3x1 array +% F: 3x1 array +% fx: 3x1 cells: { 1 , 2 , [ 1 2 ]} +% +% X(1:3,1:4,1:10) = data from 3 groups of 10 subjects performing 4 tasks +% only the second factor (dimension 2 of X) is a within-subject variable: +% >> [p,F,fx]=myanova(X,2,[2]) +% p: 3x1 array +% F: 3x1 array +% fx: 3x1 cells: { 1 , 2 , [ 1 2 ]} +% +% Requires: f_cdf() +% +% See also: myanovaeffects, factorlabels, dgrouping + + +% References: +% On effect sizes: http://epm.sagepub.com/cgi/content/abstract/64/6/916 +% http://psyphz.psych.wisc.edu/~shackman/olejnik_PsychMeth2003.pdf +% http://www.psy.jhu.edu/~ashelton/courses/stats315/week5.pdf +% + +% peta2: (biased) percentage of explained variance by each factor +% omega2: (unbiased) percentage of explained variance by each factor + +SS=[]; +SSb=[]; +sX=[size(X) 1]; +nX=ndims(X); +if nargin<2 + error('Number of factors (nf) is mandatory') +end +% Number of groups for each factor +ng=sX(1:nf); +png=prod(ng); +% Number of replicates, ie. samples in each group +nr=sX(nf+1); +% Number of cells +pnc=prod(ng); +%Number of observations +pno=pnc*nr; +% Dimensions of (pseudo multivariate) dependent variables +svar=[sX(nf+2:end)]; +if nr<2 + error('MYANOVA:NoReplicates''You must have more than one observation per cell/one subject per condition') +end +if nargin<3 + rp=[]; +end +if ~isempty(rp) & ~all(ismember(1:nf,rp)) + error('MYANOVA:MixedDesign','Mixed designs (split plot) ANOVA unavailable!\nAll factors must be either within or between.') +end +if nargin<4 + epsilon=1; +end +if nargin<5 + SStype=[]; +end +% Number of groups in between subjects +pngb=1; +if ~isempty(rp) + tmp=zeros(1,nf); + tmp(rp)=1; + rp=logical(tmp); + pngb=prod(ng(~rp)); + png=png/pngb; + % Factors within first, between next + fxw = [find(rp) find(~rp)]; + % ngw = sX(pfxw); +end +if isempty(SStype) + SSStype=3; +% if isempty(rp) +% SStype=3; +% % error('I dunno which SS type to choose...'); +% elseif all(rp) +% SStype=2; +% else +% error('I dunno which SS type to choose...'); +% end +end + +% Within Group/Error stats +% Mean in each cell +mXw=mean(X,nf+1); +% Residual Error of the model +SSe=X-repmat(mXw,[ones(1,nf) nr 1]); +SSe=reshape(SSe, png*nr, []).^2; +SSe=sum(SSe); +dfe=png*(nr-1); +dfe=repmat(dfe, [nfx 1]); +MSe=squeeze(SSe/dfe); +%Keeps the full model unexplained variance for later +MSw=MSe; +dfw=dfe; + +% Population stats +dft=(png*nr)-1; +% Grand mean value +mX=reshape(mXw, png, []); +mX=mean(mX,1); + + +if ~isempty(rp) + % Correct Within Error term to account for repeated measurements + dfr=(nr-1); + mXr=permute(X, [nf+1 1:nf nf+2:nX]); + mXr=reshape(mXr,nr,png,[]); + mXr=mean(mXr,2); + SSr=png*sum((reshape(mXr, nr, []) - repmat(mX,[nr 1])).^2); +end + +fx={}; +for i=1:nf + fx=[fx ; num2cell(nchoosek(1:nf,i),2)]; +end +nfx=length(fx); + +% Sphericity checking +if isnan(epsilon) + % check for sphericity first + if all(ng<=2) + epsilon=1; + else + [eGG,eHF]=sphericity(X,nf); + epsilon=eGG; + epsilon(eGG>.7)=eHF(eGG>.7); + epsilon(ng==2)=1; + epsilon=min(epsilon,1); + end +end +if numel(epsilon)==1; + epsilon=epsilon*ones(nfx,prod(sX(nf+2:end))); +elseif length(epsilon(:))==nfx + epsilon=epsilon(:)*ones(1,prod(sX(nf+2:end))); +end + +% [p,F,fx,epsilon(1),df(2),dfe(3),SS(4),SSe(5),SSt(6)]=... +if nargout > 3 || nargout==0 + varargout{1}=epsilon; +end + +df=zeros(nfx,1); +SS=zeros([nfx svar]); +F=zeros([nfx svar]); +p=zeros([nfx svar]); + +for i=1:length(fx) + j=fx{i}; + % Between groups stats for each factor + df=prod(ng(j)-1); + mXb=permute(mXw, [j setdiff(1:nf,j) nf+1:nX]); + mXb=reshape(mXb, prod(ng(j)),png/prod(ng(j)), []); + mXb=mean(mXb,2); + SSb=nr*png/prod(ng(j))*sum((reshape(mXb, prod(ng(j)), []) - repmat(mX,[prod(ng(j)) 1])).^2); + if length(j)>1 + % Interaction: + % we need to remove the variance from emvbedded effects from the + % Sum of Squares of this effect + for k=1:i-1 + if all(ismember(fx{k}, j)) + SSb=SSb-SS(k,:); + end + end + end + SS(i,:)=SSb; + MSb=SSb./df; + + if any(ismember(j,rp)) + % Correct Error term to account for repeated measurements + dfe=(nr-1)*df; + mXe=permute(X, [nf+1 j setdiff(1:nf,j) nf+2:nX]); + mXe=reshape(mXe,nr*prod(ng(j)),png/prod(ng(j)),[]); + mXe=mean(mXe,2); + SSe=png/prod(ng(j))*sum((reshape(mXe, nr*prod(ng(j)), []) - repmat(mX,[nr*prod(ng(j)) 1])).^2); + SSe=SSe-SSr-SSb; + for k=1:i-1 + %remove embedded factors effects and error terms + if all(ismember(fx{k}, j)) + SSe=SSe-SS(k,:)-SSbe(k,:); + end + end + end + SSbe(i,:)=SSe; + MSe=squeeze(SSe/dfe); + % Non-sphericity correction of degrees of liberty + df=df.*epsilon(i,:); + dfe=dfe.*epsilon(i,:); + % F-ratio & p-value + F(i,:)=squeeze(MSb)./MSe; + p(i,:)=1-f_cdf(F(i,:),df,dfe); + % [p,F,fx,epsilon(1),df(2),dfe(3),SS(4),SSe(5),SSt(6)]=... + if nargout>=6 || nargout==0 + varargout{3}(i,:)=df; + end + if nargout>=7 || nargout==0 + varargout{4}(i,:)=dfe; + end +end + +SSt=sum((reshape(X, png*nr, [])-repmat(mX,[png*nr 1])).^2); +SSe=SSbe; +% Process outputs +if nargout >= 5 || nargout==0 + % partial omega squared + MSe=SSe./dfe; + varargout{2}=(SS-df.*MSe)./(SSt+(png-df).*MSe); +end + +if nargout>=8 || nargout==0 + varargout{5}=SSbe; +end +if nargout>=9 || nargout==0 + varargout{6}=SSbe; +end +if nargout >=10 || nargout == 0 + varargout{7}=SSt; +end +if nargout >= 11 + % partial eta squared + varargout{8}=SS./(SS+SSe); +end +if nargout >= 12 + % omega squared + varargout{9}=(SS-df.*MSe)./(SSt+MSe); +end + +if prod(sX)>png*nr + p=reshape(p,[nfx,sX(nf+2:end)]); + F=reshape(F,[nfx,sX(nf+2:end)]); +end + +%% Process output for display +if nargout==0 + %Display results + %Retrieves numerical results + [pomega2,df,dfe,SS,SSe,SSt,peta2]=deal(varargout{:}); + MS=SS./df; + + fprintf([repmat('-',1,80) '\n'] ); + fprintf(['%' num2str(max(cellfun('length', fx))*2+15) 's \t| df \t| SS \t| MS \t| F \t| p\n'], 'ANOVA results'); + fprintf([repmat('-',1,80) '\n'] ); + for j=1:min(size(p,2),3) + if size(p,2)>1 + fprintf('MULTIDIMENSIONAL DATA: [%d] ...\n',j) + end + + for i=1:length(fx) + if i<=nf + label=sprintf('Factor #%d',i); + else + label=['Interaction ' sprintf('%d*',fx{i})]; + label=label(1:end-1); + end + label=sprintf([ '%' num2str(max(cellfun('length', fx))*2+15) 's'],label); + fprintf('%s: \t % 3.3g \t% 8.4g\t% 8.4g\t% 8.4g\t %g\n',... + label,df(i),SS(i,j),MS(i,j),F(i,j),p(i,j)); + if ~isempty(rp) || i==nfx + label=sprintf([ '%' num2str(length(label)) 's'],'Error'); + fprintf('%s: \t % 3.3g \t% 8.4g\t% 8.4g\n',... + label,dfe(i),SSe(i,j),SSe(i,j)./dfe(i)); + end + end + + if ~isempty(rp) + MS_r=SSr./dfr; + F_r=squeeze(MS_r)./MSw; + p_r=1-f_cdf(F_r,dfr,dfw); + label=sprintf([ '%' num2str(length(label)) 's'],'Subjects'); + fprintf('%s: \t % 3.3g \t% 8.4g\t% 8.4g\t% 8.4g\t %g\n',... + label,dfr,SSr(j),MS_r(j),F_r(j),p_r(j)); + end + label=sprintf([ '%' num2str(max(cellfun('length', fx))*2+15) 's'],'Total'); + fprintf('%s: \t % 3.3g \t% 8.4g\n',... + label,dft,SSt(j)); + if size(p,2)>1 + fprintf([repmat('. ',1,40) '\n']) + end + + end + if j3 + SS(nfx+1,:)=SSt; + SS(nfx+2,:)=SSw; + SS(nfx+3,:)=SSr; + SS(nfx+4,:)=SSe; +end +if nargout>4 + df(nfx+1,:)=dft; + df(nfx+2,:)=dfw; + df(nfx+3,:)=dfr; + df(nfx+4,:)=dfe; +end +if nargout>5 + MS(nfx+1,:)=MSt; + MS(nfx+2,:)=MSw; + MS(nfx+3,:)=MSr; + MS(nfx+4,:)=MSe; +end + + +return + +%% RESSOURCES +% http://www.brown.edu/Research/LCE/Fall2004/Within%20subjs.pdf +% + +%% EXAMPLES +% +% from Zar, 1999, pp. 250 sqq. +% Ex. 12.5 (repeated measure one-way ANOVA) +X= [ + 164 152 178 + 202 181 222 + 143 136 132 + 210 194 216 + 228 219 245 + 173 159 182 + 161 157 165 + ]'; +% F = (1454/2)/(695.3/12) = 727/57.94 = 12.6 + + +% BETWEEN SUBJECT DESIGN WITH 2 REPLICATES +% ======================================== +% http://www.richland.edu/james/lecture/m170/ch13-2wy.html +% SS = 512.8667 449.4667 143.1333 136.0000 1241.4667 +% F observed = 28.283 12.393 1.973 +% F critique = 3.682 3.056 2.641 +X=[ + 106, 110 , 95, 100 , 94, 107 , 103, 104 , 100, 102; + 110, 112 , 98, 99 , 100, 101 , 108, 112 , 105, 107; + 94, 97, 86, 87 98, 99 99, 101 94, 98]; +X=cat(3,X(:,1:2:end),X(:,2:2:end)) +[p,F]=myanova(X,2,[]) + + + +% % http://www.linguistics.ucla.edu/faciliti/facilities/statistics/fromoac.htm +X=[ 8 9 8 8 9 7 10 9 10; 9 10 9 10 9 13 8 9 9; 8 7 7 12 7 9 10 9 7; 6 8 9 8 10 10 12 9 10; 7 6 7 11 12 8 8 11 9]; +X=reshape(X,5,3,3); +% +% % COND TRIAL SUBJECT +X=permute(X,[3 2 1]); +% +[p,F]=myanova(X,2,1:2) +% l=factorlabels(X) +% rm_anova2(X(:), l(:,3),l(:,1),l(:,2),{'COND', 'TRIAL'}) +% % 'Source' 'SS' 'df' 'MS' 'F' 'p' +% % 'COND' [24.8444] [ 2] [12.4222] [4.0216] [0.0618] +% % 'TRIAL' [ 0.3111] [ 2] [ 0.1556] [0.0625] [0.9399] +% % 'COND x TRIAL' [ 1.6889] [ 4] [ 0.4222] [0.1907] [0.9397] +% % 'COND x Subj' [24.7111] [ 8] [ 3.0889] [] [] +% % 'TRIAL x Subj' [19.9111] [ 8] [ 2.4889] [] [] +% % 'COND x TRIAL x Subj' [35.4222] [16] [ 2.2139] [] [] + + + +% http://lib.stat.cmu.edu/DASL/Datafiles/airpullutionfiltersdat.html +% 1. NOISE = Noise level reading (decibels) +% 2. SIZE = Vehicle size: 1 small 2 medium 3 large +% 3. TYPE = 1 standard silencer 2 Octel filter +% 4. SIDE = 1 right side 2 left side of car +% and 3 replicates +x=[ + 810 1 1 1 + 820 1 1 1 + 820 1 1 1 + 840 2 1 1 + 840 2 1 1 + 845 2 1 1 + 785 3 1 1 + 790 3 1 1 + 785 3 1 1 + 835 1 1 2 + 835 1 1 2 + 835 1 1 2 + 845 2 1 2 + 855 2 1 2 + 850 2 1 2 + 760 3 1 2 + 760 3 1 2 + 770 3 1 2 + 820 1 2 1 + 820 1 2 1 + 820 1 2 1 + 820 2 2 1 + 820 2 2 1 + 825 2 2 1 + 775 3 2 1 + 775 3 2 1 + 775 3 2 1 + 825 1 2 2 + 825 1 2 2 + 825 1 2 2 + 815 2 2 2 + 825 2 2 2 + 825 2 2 2 + 770 3 2 2 + 760 3 2 2 + 765 3 2 2 + ]; +[i,nl]=dgrouping(x(:,2:end)); +clear X +X(i)=x(:,1); +X=reshape(X,nl); + + + +% Two-way ANOVA from Cousineau 2007 +X= [ + 550 580 610 + 605 635 655 + 660 690 710 + ]'; + + +% Cousineau ? +X= [ + 150. 44. 71. 59. 132. 74. 1. + 335. 270. 156. 160. 118. 230. 1. + 149. 52. 91. 115. 43. 154. 1. + 159. 31. 127. 212. 71. 224. 1. + 159. 0. 35. 75. 71. 34. 1. + 292. 125. 184. 246. 225. 170. 1. + 297. 187. 66. 96. 209. 74. 1. + 170. 37. 42. 66. 114. 81. 1. + 346. 175. 177. 192. 239. 140. 2. + 426. 329. 236. 76. 102. 232. 2. + 359. 238. 183. 123. 183. 30. 2. + 272. 60. 82. 85. 101. 98. 2. + 200. 271. 263. 216. 241. 227. 2. + 366. 291. 263. 144. 220. 180. 2. + 371. 364. 270. 308. 219. 267. 2. + 497. 402. 294. 216. 284. 255. 2. + 282. 186. 225. 134. 189. 169. 3. + 317. 31. 85. 120. 131. 205. 3. + 362. 104. 144. 114. 115. 127. 3. + 338. 132. 91. 77. 108. 169. 3. + 263. 94. 141. 142. 120. 195. 3. + 138. 38. 16. 95. 39. 55. 3. + 329. 62. 62. 6. 93. 67. 3. + 292. 139. 104. 184. 193. 122. 3. + ]; +X=reshape(X(:,1:6),[8,3,6]); +X=permute(X,[1 3 2]); + +% Cousineau TQPM +X=str2num(urlread('http://www.tqmp.org/doc/vol1-1/p42.dat')); +X=reshape(X(:,end),[16,5,2]); +X=permute(X,[3 2 1]); +myanova(X,2,1:2) + +% One way ANOVA from Loftus & Masson, 1994 +% Exposure Duration Per Word (sec) +% 1 Sec 2 Sec 5 Sec +X= [ + 10 13 13 + 6 8 8 + 11 14 14 + 22 23 25 + 16 18 20 + 15 17 17 + 1 1 4 + 12 15 17 + 9 12 12 + 8 9 12 + ]'; +% M1 = 11.0 M2 = 13.0 M3 = 14.2 + + +% Loftus & Masson 1994 +X= [ + 1 450 462 12 460 482 22 460 497 37 480 507 27 + 2 510 492 -18 515 530 15 520 534 14 504 550 46 + 3 492 508 16 512 522 10 503 553 50 520 539 19 + 4 524 532 8 530 543 13 517 546 29 503 553 50 + 5 420 409 -11 424 452 28 431 468 37 446 472 26 + 6 540 550 10 538 528 -10 552 575 23 562 598 36 + ]; +X=reshape(X(:,2:end),6,3,4); +X=permute(X(:,1:2,:),[2 3 1]); +% X : R vs U | SOA 50/100/200/400 | SUBJ(6) + + +%% DATA Eysenck 1974 +% http://web.uccs.edu/lbecker/Psy590/es.htm +% http://homepages.gold.ac.uk/aphome/GLM%20Simple%20Main%20Effects.html +% Counting Rhyming Adjective Imagery Intentional +X=[ + 9 7 11 12 10 + 8 9 13 11 19 + 6 6 8 16 14 + 8 6 6 11 5 + 10 6 14 9 10 + 4 11 11 23 11 + 6 6 13 12 14 + 5 3 13 10 15 + 7 8 10 19 11 + 7 7 11 11 11 + ]; + +% Eysenck 1974 OLD/YOUNG +% +X=urlread('http://forrest.psych.unc.edu/research/vista-frames/help/lecturenotes/lecture10/eysenck.xli'); +X='DATA "Eysenck" :TITLE "Eysenck" :ABOUT "Eysenck study of recall ability of subjects who were young (Y - 18-30 years old) or old (O - 55-65 years old) for recall conditions involving Counting, Rhyming, Adjectives, Imagery or Intentional. From Howell, David C., (Ed. 3) p. 325." :VARIABLES (QUOTE ("Recall" "Age" "Condition")) :TYPES (QUOTE ("Numeric" "Category" "Category")) :LABELS (QUOTE ("Obs0" "Obs1" "Obs2" "Obs3" "Obs4" "Obs5" "Obs6" "Obs7" "Obs8" "Obs9" "Obs10" "Obs11" "Obs12" "Obs13" "Obs14" "Obs15" "Obs16" "Obs17" "Obs18" "Obs19" "Obs20" "Obs21" "Obs22" "Obs23" "Obs24" "Obs25" "Obs26" "Obs27" "Obs28" "Obs29" "Obs30" "Obs31" "Obs32" "Obs33" "Obs34" "Obs35" "Obs36" "Obs37" "Obs38" "Obs39" "Obs40" "Obs41" "Obs42" "Obs43" "Obs44" "Obs45" "Obs46" "Obs47" "Obs48" "Obs49" "Obs50" "Obs51" "Obs52" "Obs53" "Obs54" "Obs55" "Obs56" "Obs57" "Obs58" "Obs59" "Obs60" "Obs61" "Obs62" "Obs63" "Obs64" "Obs65" "Obs66" "Obs67" "Obs68" "Obs69" "Obs70" "Obs71" "Obs72" "Obs73" "Obs74" "Obs75" "Obs76" "Obs77" "Obs78" "Obs79" "Obs80" "Obs81" "Obs82" "Obs83" "Obs84" "Obs85" "Obs86" "Obs87" "Obs88" "Obs89" "Obs90" "Obs91" "Obs92" "Obs93" "Obs94" "Obs95" "Obs96" "Obs97" "Obs98" "Obs99")) :DATA (QUOTE (9 "O" "C" 8 "O" "C" 6 "O" "C" 8 "O" "C" 10 "O" "C" 4 "O" "C" 6 "O" "C" 5 "O" "C" 7 "O" "C" 7 "O" "C" 7 "O" "R" 9 "O" "R" 6 "O" "R" 6 "O" "R" 6 "O" "R" 11 "O" "R" 6 "O" "R" 3 "O" "R" 8 "O" "R" 7 "O" "R" 11 "O" "A" 13 "O" "A" 8 "O" "A" 6 "O" "A" 14 "O" "A" 11 "O" "A" 13 "O" "A" 13 "O" "A" 10 "O" "A" 11 "O" "A" 12 "O" "I" 11 "O" "I" 16 "O" "I" 11 "O" "I" 9 "O" "I" 23 "O" "I" 12 "O" "I" 10 "O" "I" 19 "O" "I" 11 "O" "I" 10 "O" "IN" 19 "O" "IN" 14 "O" "IN" 5 "O" "IN" 10 "O" "IN" 11 "O" "IN" 14 "O" "IN" 15 "O" "IN" 11 "O" "IN" 11 "O" "IN" 8 "Y" "C" 6 "Y" "C" 4 "Y" "C" 6 "Y" "C" 7 "Y" "C" 6 "Y" "C" 5 "Y" "C" 7 "Y" "C" 9 "Y" "C" 7 "Y" "C" 10 "Y" "R" 7 "Y" "R" 8 "Y" "R" 10 "Y" "R" 4 "Y" "R" 7 "Y" "R" 10 "Y" "R" 6 "Y" "R" 7 "Y" "R" 7 "Y" "R" 14 "Y" "A" 11 "Y" "A" 18 "Y" "A" 14 "Y" "A" 13 "Y" "A" 22 "Y" "A" 17 "Y" "A" 16 "Y" "A" 12 "Y" "A" 11 "Y" "A" 20 "Y" "I" 16 "Y" "I" 16 "Y" "I" 15 "Y" "I" 18 "Y" "I" 16 "Y" "I" 20 "Y" "I" 22 "Y" "I" 14 "Y" "I" 19 "Y" "I" 21 "Y" "IN" 19 "Y" "IN" 17 "Y" "IN" 15 "Y" "IN" 22 "Y" "IN" 16 "Y" "IN" 22 "Y" "IN" 22 "Y" "IN" 18 "Y" "IN" 21 "Y" "IN")) :DATASHEET-ARGUMENTS (QUOTE ((420 475) (511 120) 2 8)))'; +X=strread(X, '%s', 'delimiter', ' '); +X=X(160:3:end-8) +X{1}(1)=[]; +X=str2num(strvcat(X)); +X=reshape(X,10,5,2); +X=permute(X,[3 2 1]); % AGE x COND x SUBJ + + +% Migraine Headache +% http://core.ecu.edu/psyc/wuenschk/SPSS/SPSS-Data.htm +X= [ + 21 22 8 6 6 + 20 19 10 4 9 + 7 5 5 4 5 + 25 30 13 12 4 + 30 33 10 8 6 + 19 27 8 7 4 + 26 16 5 2 5 + 13 4 8 1 5 + 26 24 14 8 17 + ]; +% Howell Table 14.3 +X= [ + 21 22 8 6 6 + 20 19 10 4 4 + 17 15 5 4 5 + 25 30 13 12 17 + 30 27 13 8 6 + 19 27 8 7 4 + 26 16 5 2 5 + 17 18 8 1 5 + 26 24 14 8 9 + ]; + + +% http://www.ou.edu/faculty/M/Jorge.L.Mendoza-1/psy5013/twoway-repeated.sas +% [Traning] [Males: Pre Post FU6 FU12] [Females: Pre Post FU6 FU12] +X = [ + 1 7 22 13 14 0 6 22 26 + 1 25 10 17 24 0 16 12 15 + 1 50 36 49 23 0 8 0 0 + 1 16 38 34 24 15 14 22 8 + 1 33 25 24 25 27 18 24 37 + 1 10 7 23 26 0 0 0 0 + 1 13 33 27 24 4 27 21 3 + 1 22 20 21 11 26 9 9 12 + 1 4 0 12 0 0 0 14 1 + 1 17 16 20 10 0 0 12 0 + 0 0 0 0 0 15 28 26 15 + 0 69 56 14 36 0 0 0 0 + 0 5 0 0 5 6 0 23 0 + 0 4 24 0 0 0 0 0 0 + 0 35 8 0 0 25 28 0 16 + 0 7 0 9 37 36 22 14 48 + 0 51 53 8 26 19 22 29 2 + 0 25 0 0 15 0 0 5 14 + 0 59 45 11 16 0 0 0 0 + 0 40 2 33 16 0 0 0 0 + ]; +X=reshape(X(:,2:end),[10,2,4 2]); +X=permute(X, [2 3 4 1]); +[p,F,fx,epsilon,pomega2,df,dfe,SS,SSe,SSt,peta2,omega2]=myanova(X,3,2) + + +% http://www.psych.northwestern.edu/help/systat/Syst.facmix.html +% subject Smoking High Stress Low Stress +% 1 light 8 1 +% 2 light 7 1 +% 3 heavy 6 8 +% 4 heavy 5 6 +% 5 light 8 3 +% 6 light 10 1 +% 7 heavy 6 7 +% 8 light 9 2 +% 9 heavy 8 10 +% 10 light 9 2 +x=[ + 1 8 1 + 1 7 1 + 2 6 8 + 2 5 6 + 1 8 3 + 1 10 1 + 2 6 7 + 1 9 2 + 2 8 10 + 1 9 2 + ]; +%X=reshape(X, +[i,nl]=dgrouping([ [x(:,1);x(:,1)],[repmat(1,10,1);repmat(2,10,1)]]); +[i,nl]=dgrouping([ [[1:10]';[1:10]'] [x(:,1);x(:,1)],[repmat(1,10,1);repmat(2,10,1)]]); +X=NaN*zeros(nl); + +%http://www.isogenic.info/html/example_2.html http://www.isogenic.info/html/example_2.html +% Strain EROD Treatm Block +x=[ + 1 18.7 1 1 + 2 17.9 1 1 + 3 19.2 1 1 + 4 26.3 1 1 + 1 7.7 2 1 + 2 8.4 2 1 + 3 9.8 2 1 + 4 9.7 2 1 + 1 16.7 1 2 + 2 14.4 1 2 + 3 12.0 1 2 + 4 19.8 1 2 + 1 6.4 2 2 + 2 6.7 2 2 + 3 8.1 2 2 + 4 6.0 2 2 + ] +[i,nl]=dgrouping(x(:,[1 3 4])) +X=NaN*zeros(nl); +X(i)=x(:,2); +%In this case a three-way ANOVA with strain and treatment being fixed +%effects (determined by the experimentalist) and the block being a random +%factor (i.e. not a specific treatment). +% +% Source DF SS MS F P +% Block 1 47.610 47.610 18.37 0.004 +% Strain 3 32.962 10.988 4.24 0.053 +% Trt 1 422.303 422.303 162.96 0.000 +% Strain*Trt 3 40.343 13.448 5.19 0.034 +% Error 7 18.140 2.591 +% Total 15 561.358 + + +% ABRDATA.TAB from OpenStat +% http://www.statpages.org/miller/openstat/Analyzing%20Data%20with%20Stats4U.pdf +% Row Col C1 C2 C3 C4 +X=[ + 1.00 1.00 18.00 14.00 12.00 6.00 + 1.00 1.00 19.00 12.00 8.00 4.00 + 1.00 1.00 14.00 10.00 6.00 2.00 + 1.00 2.00 16.00 12.00 10.00 4.00 + 1.00 2.00 12.00 8.00 6.00 2.00 + 1.00 2.00 18.00 10.00 5.00 1.00 + 2.00 1.00 16.00 10.00 8.00 4.00 + 2.00 1.00 18.00 8.00 4.00 1.00 + 2.00 1.00 16.00 12.00 6.00 2.00 + 2.00 2.00 19.00 16.00 10.00 8.00 + 2.00 2.00 16.00 14.00 10.00 9.00 + 2.00 2.00 16.00 12.00 8.00 8.00 + ]; +X=reshape(X(:,3:end), [3 2 2 4]); +X=permute(X,[2 3 4 1]); + +% SOURCE DF SS MS F PROB. +% Between Subjects 11 181.000 +% A Effects 1 10.083 10.083 0.978 0.352 +% B Effects 1 8.333 8.333 0.808 0.395 +% AB Effects 1 80.083 80.083 7.766 0.024 +% Error Between 8 82.500 10.312 +% Within Subjects 36 1077.000 +% C Replications 3 991.500 330.500 152.051 0.000 +% AC Effects 3 8.417 2.806 1.291 0.300 +% BC Effects 3 12.167 4.056 1.866 0.162 +% ABC Effects 3 12.750 4.250 1.955 0.148 +% Error Within 24 52.167 2.174 +% Total 47 1258.000 + +function [SS,df]=sumofsquares(X,f1,nf,mX) +sX=size(X); +f2=setdiff(1:nf+1,f1); +mXf = permute(X, [f1 f2 nf+2:ndims(X)]); +mXf = reshape(mXf,prod(sX(f1)),prod(sX(f2)),[]); +mXf = mean(mXf,1); +SS = mXf - repmat(mX,[1 prod(sX(f2)) 1]) +%SS = reshape(SS, prod(sX(f2)), []).^2 +SS = SS.^2 +SS = sum(sum(SS,2),1); +df = prod(sX(f1)) \ No newline at end of file diff --git a/mystats/myanova.m b/mystats/myanova.m new file mode 100644 index 0000000..b9ecad8 --- /dev/null +++ b/mystats/myanova.m @@ -0,0 +1,737 @@ +function [p,F,fx,varargout]=myanova(X,nf,rp,epsilon,SStype) +% myanova - N-way ANOVA +% [p,F,fx]=myanova(X,nf) +% [p,F,fx,epsilon,pomega2,df,dfe,SS,SSe,SSt]=... +% myanova(X,nf,rp,epsilon,SStype) +% Performs a N-way beween subjects anova on X +% +%INPUTS: +% X = [ factor1 x factor2 x ... x factorN x replicates x ...] +% This function is vectorized, ie. anova is computed for each value in X +% after the 'replicates' (i.e. subject) dimension. +% nf: number of factors +% +%OUTPUTS: +% p: the Null hypothesis probability +% F: the F-values +% fx: a cell array listing the tested effects. +% epsilon: sphericity correction if applicable +% pomega2: partial omega squared (unbiased estimator of effect size) +% df: degrees of freedom for each effect +% dfe: degrees of freedom of error for each effect +% SS: sum of squares for each effect +% SSe: sum of squares of error for each effect +% SSt: Total sum of squares +% peta2: partial eta-squared, (biased) percentage of explained variance by each factor +% omega2: (unbiased) percentage of explained variance by each factor +% eta2: cf infra +% +%OPTIONAL INPUTS: +% [...]=myanova(X,nf,rp) +% To specifiy repeated/within-subject factors in a within-subject design +% rp: an array listing the dimensions of repeated factors +% +% [...]=myanova(X,nf,rp,epsilon) +% Correct for non-spherical data using epsilon (expanded to match the +% size of measures, if necessary). If epsilon=NaN, epsilon(s) will be +% computed using Greenhouse-Geisser correction or Huynh-Feldt (if eGG>.7) +% +% Examples: +% +% X(1:2,1:3,1:10) = data from 2 tasks by 3 condition for 10 subjects, +% both factors (dimensions 1 and 2) are within-subject factors: +% >> [p,F,fx]=myanova(X,2,1:2) +% p: 3x1 array +% F: 3x1 array +% fx: 3x1 cells: { 1 , 2 , [ 1 2 ]} +% +% X(1:3,1:4,1:10) = data from 3 groups of 10 subjects performing 4 tasks +% only the second factor (dimension 2 of X) is a within-subject variable: +% >> [p,F,fx]=myanova(X,2,[2]) +% p: 3x1 array +% F: 3x1 array +% fx: 3x1 cells: { 1 , 2 , [ 1 2 ]} +% +% Requires: f_cdf() +% +% See also: myanovaeffects, factorlabels, dgrouping + + +% References: +% On effect sizes: http://epm.sagepub.com/cgi/content/abstract/64/6/916 +% http://psyphz.psych.wisc.edu/~shackman/olejnik_PsychMeth2003.pdf +% http://www.psy.jhu.edu/~ashelton/courses/stats315/week5.pdf +% + +% peta2: (biased) percentage of explained variance by each factor +% omega2: (unbiased) percentage of explained variance by each factor + +SS=[]; +SSb=[]; +sX=[size(X) 1]; +nX=ndims(X); +if nargin<2 + error('Number of factors (nf) is mandatory') +end +% Number of groups for each factor +ng=sX(1:nf); +png=prod(ng); +% Number of replicates, ie. samples in each group +nr=sX(nf+1); +% Number of cells +pnc=prod(ng); +%Number of observations +pno=pnc*nr; +% Dimensions of (pseudo multivariate) dependent variables +svar=[sX(nf+2:end)]; +if nr<2 + error('MYANOVA:NoReplicates''You must have more than one observation per cell/one subject per condition') +end +if nargin<3 + rp=[]; +end +if ~isempty(rp) & ~all(ismember(1:nf,rp)) + error('MYANOVA:MixedDesign','Mixed designs (split plot) ANOVA unavailable!\nAll factors must be either within or between.') +end +if nargin<4 + epsilon=1; +end +if nargin<5 + SStype=[]; +end +% Number of groups in between subjects +pngb=1; +if ~isempty(rp) + tmp=zeros(1,nf); + tmp(rp)=1; + rp=logical(tmp); + pngb=prod(ng(~rp)); + png=png/pngb; + % Factors within first, between next + fxw = [find(rp) find(~rp)]; + % ngw = sX(pfxw); +end +if isempty(SStype) + SSStype=3; +% if isempty(rp) +% SStype=3; +% % error('I dunno which SS type to choose...'); +% elseif all(rp) +% SStype=2; +% else +% error('I dunno which SS type to choose...'); +% end +end + +fx={}; +for i=1:nf + fx=[fx ; num2cell(nchoosek(1:nf,i),2)]; +end +nfx=length(fx); + +% Within Group/Error stats +% Mean in each cell +mXw=mean(X,nf+1); +% Residual Error of the model +SSe=X-repmat(mXw,[ones(1,nf) nr 1]); +SSe=reshape(SSe, png*nr, []).^2; +SSe=sum(SSe); +dfe=png*(nr-1); +dfe=repmat(dfe, [nfx 1]); +MSe=squeeze(SSe/dfe); +%Keeps the full model unexplained variance for later +MSw=MSe; +dfw=dfe; + +% Population stats +dft=(png*nr)-1; +% Grand mean value +mX=reshape(mXw, png, []); +mX=mean(mX,1); + + +if ~isempty(rp) + % Correct Within Error term to account for repeated measurements + dfr=(nr-1); + mXr=permute(X, [nf+1 1:nf nf+2:nX]); + mXr=reshape(mXr,nr,png,[]); + mXr=mean(mXr,2); + SSr=png*sum((reshape(mXr, nr, []) - repmat(mX,[nr 1])).^2); +end + +% Sphericity checking +if isnan(epsilon) + % check for sphericity first + if all(ng<=2) + epsilon=1; + else + [eGG,eHF]=sphericity(X,nf); + epsilon=eGG; + epsilon(eGG>.7)=eHF(eGG>.7); + epsilon(ng==2)=1; + epsilon=min(epsilon,1); + end +end +if numel(epsilon)==1; + epsilon=epsilon*ones(nfx,prod(sX(nf+2:end))); +elseif length(epsilon(:))==nfx + epsilon=epsilon(:)*ones(1,prod(sX(nf+2:end))); +end + +% [p,F,fx,epsilon(1),df(2),dfe(3),SS(4),SSe(5),SSt(6)]=... +if nargout > 3 || nargout==0 + varargout{1}=epsilon; +end + +df=zeros(nfx,1); +SS=zeros([nfx svar]); +F=zeros([nfx svar]); +p=zeros([nfx svar]); + +for i=1:length(fx) + j=fx{i}; + % Between groups stats for each factor + df=prod(ng(j)-1); + mXb=permute(mXw, [j setdiff(1:nf,j) nf+1:nX]); + mXb=reshape(mXb, prod(ng(j)),png/prod(ng(j)), []); + mXb=mean(mXb,2); + SSb=nr*png/prod(ng(j))*sum((reshape(mXb, prod(ng(j)), []) - repmat(mX,[prod(ng(j)) 1])).^2); + if length(j)>1 + % Interaction: + % we need to remove the variance from emvbedded effects from the + % Sum of Squares of this effect + for k=1:i-1 + if all(ismember(fx{k}, j)) + SSb=SSb-SS(k,:); + end + end + end + SS(i,:)=SSb; + MSb=SSb./df; + + if any(ismember(j,rp)) + % Correct Error term to account for repeated measurements + dfe=(nr-1)*df; + mXe=permute(X, [nf+1 j setdiff(1:nf,j) nf+2:nX]); + mXe=reshape(mXe,nr*prod(ng(j)),png/prod(ng(j)),[]); + mXe=mean(mXe,2); + SSe=png/prod(ng(j))*sum((reshape(mXe, nr*prod(ng(j)), []) - repmat(mX,[nr*prod(ng(j)) 1])).^2); + SSe=SSe-SSr-SSb; + for k=1:i-1 + %remove embedded factors effects and error terms + if all(ismember(fx{k}, j)) + SSe=SSe-SS(k,:)-SSbe(k,:); + end + end + end + SSbe(i,:)=SSe; + MSe=squeeze(SSe/dfe); + % Non-sphericity correction of degrees of liberty + df=df.*epsilon(i,:); + dfe=dfe.*epsilon(i,:); + % F-ratio & p-value + F(i,:)=squeeze(MSb)./MSe; + p(i,:)=1-f_cdf(F(i,:),df,dfe); + % [p,F,fx,epsilon(1),df(2),dfe(3),SS(4),SSe(5),SSt(6)]=... + if nargout>=6 || nargout==0 + varargout{3}(i,:)=df; + end + if nargout>=7 || nargout==0 + varargout{4}(i,:)=dfe; + end +end + +SSt=sum((reshape(X, png*nr, [])-repmat(mX,[png*nr 1])).^2); +SSe=SSbe; +% Process outputs +if nargout >= 5 || nargout==0 + % partial omega squared + MSe=SSe./dfe; + varargout{2}=(SS-df.*MSe)./(SSt+(png-df).*MSe); +end + +if nargout>=8 || nargout==0 + varargout{5}=SSbe; +end +if nargout>=9 || nargout==0 + varargout{6}=SSbe; +end +if nargout >=10 || nargout == 0 + varargout{7}=SSt; +end +if nargout >= 11 + % partial eta squared + varargout{8}=SS./(SS+SSe); +end +if nargout >= 12 + % omega squared + varargout{9}=(SS-df.*MSe)./(SSt+MSe); +end + +if prod(sX)>png*nr + p=reshape(p,[nfx,sX(nf+2:end)]); + F=reshape(F,[nfx,sX(nf+2:end)]); +end + +%% Process output for display +if nargout==0 + %Display results + %Retrieves numerical results + [pomega2,df,dfe,SS,SSe,SSt,peta2]=deal(varargout{:}); + MS=SS./df; + + fprintf([repmat('-',1,80) '\n'] ); + fprintf(['%' num2str(max(cellfun('length', fx))*2+15) 's \t| df \t| SS \t| MS \t| F \t| p\n'], 'ANOVA results'); + fprintf([repmat('-',1,80) '\n'] ); + for j=1:min(size(p,2),3) + if size(p,2)>1 + fprintf('MULTIDIMENSIONAL DATA: [%d] ...\n',j) + end + + for i=1:length(fx) + if i<=nf + label=sprintf('Factor #%d',i); + else + label=['Interaction ' sprintf('%d*',fx{i})]; + label=label(1:end-1); + end + label=sprintf([ '%' num2str(max(cellfun('length', fx))*2+15) 's'],label); + fprintf('%s: \t % 3.3g \t% 8.4g\t% 8.4g\t% 8.4g\t %g\n',... + label,df(i),SS(i,j),MS(i,j),F(i,j),p(i,j)); + if ~isempty(rp) || i==nfx + label=sprintf([ '%' num2str(length(label)) 's'],'Error'); + fprintf('%s: \t % 3.3g \t% 8.4g\t% 8.4g\n',... + label,dfe(i),SSe(i,j),SSe(i,j)./dfe(i)); + end + end + + if ~isempty(rp) + MS_r=SSr./dfr; + F_r=squeeze(MS_r)./MSw; + p_r=1-f_cdf(F_r,dfr,dfw); + label=sprintf([ '%' num2str(length(label)) 's'],'Subjects'); + fprintf('%s: \t % 3.3g \t% 8.4g\t% 8.4g\t% 8.4g\t %g\n',... + label,dfr,SSr(j),MS_r(j),F_r(j),p_r(j)); + end + label=sprintf([ '%' num2str(max(cellfun('length', fx))*2+15) 's'],'Total'); + fprintf('%s: \t % 3.3g \t% 8.4g\n',... + label,dft,SSt(j)); + if size(p,2)>1 + fprintf([repmat('. ',1,40) '\n']) + end + + end + if j3 + SS(nfx+1,:)=SSt; + SS(nfx+2,:)=SSw; + SS(nfx+3,:)=SSr; + SS(nfx+4,:)=SSe; +end +if nargout>4 + df(nfx+1,:)=dft; + df(nfx+2,:)=dfw; + df(nfx+3,:)=dfr; + df(nfx+4,:)=dfe; +end +if nargout>5 + MS(nfx+1,:)=MSt; + MS(nfx+2,:)=MSw; + MS(nfx+3,:)=MSr; + MS(nfx+4,:)=MSe; +end + + +return + +%% RESSOURCES +% http://www.brown.edu/Research/LCE/Fall2004/Within%20subjs.pdf +% + +%% EXAMPLES +% +% from Zar, 1999, pp. 250 sqq. +% Ex. 12.5 (repeated measure one-way ANOVA) +X= [ + 164 152 178 + 202 181 222 + 143 136 132 + 210 194 216 + 228 219 245 + 173 159 182 + 161 157 165 + ]'; +% F = (1454/2)/(695.3/12) = 727/57.94 = 12.6 + + +% BETWEEN SUBJECT DESIGN WITH 2 REPLICATES +% ======================================== +% http://www.richland.edu/james/lecture/m170/ch13-2wy.html +% SS = 512.8667 449.4667 143.1333 136.0000 1241.4667 +% F observed = 28.283 12.393 1.973 +% F critique = 3.682 3.056 2.641 +X=[ + 106, 110 , 95, 100 , 94, 107 , 103, 104 , 100, 102; + 110, 112 , 98, 99 , 100, 101 , 108, 112 , 105, 107; + 94, 97, 86, 87 98, 99 99, 101 94, 98]; +X=cat(3,X(:,1:2:end),X(:,2:2:end)) +[p,F]=myanova(X,2,[]) + + + +% % http://www.linguistics.ucla.edu/faciliti/facilities/statistics/fromoac.htm +X=[ 8 9 8 8 9 7 10 9 10; 9 10 9 10 9 13 8 9 9; 8 7 7 12 7 9 10 9 7; 6 8 9 8 10 10 12 9 10; 7 6 7 11 12 8 8 11 9]; +X=reshape(X,5,3,3); +% +% % COND TRIAL SUBJECT +X=permute(X,[3 2 1]); +% +[p,F]=myanova(X,2,1:2) +% l=factorlabels(X) +% rm_anova2(X(:), l(:,3),l(:,1),l(:,2),{'COND', 'TRIAL'}) +% % 'Source' 'SS' 'df' 'MS' 'F' 'p' +% % 'COND' [24.8444] [ 2] [12.4222] [4.0216] [0.0618] +% % 'TRIAL' [ 0.3111] [ 2] [ 0.1556] [0.0625] [0.9399] +% % 'COND x TRIAL' [ 1.6889] [ 4] [ 0.4222] [0.1907] [0.9397] +% % 'COND x Subj' [24.7111] [ 8] [ 3.0889] [] [] +% % 'TRIAL x Subj' [19.9111] [ 8] [ 2.4889] [] [] +% % 'COND x TRIAL x Subj' [35.4222] [16] [ 2.2139] [] [] + + + +% http://lib.stat.cmu.edu/DASL/Datafiles/airpullutionfiltersdat.html +% 1. NOISE = Noise level reading (decibels) +% 2. SIZE = Vehicle size: 1 small 2 medium 3 large +% 3. TYPE = 1 standard silencer 2 Octel filter +% 4. SIDE = 1 right side 2 left side of car +% and 3 replicates +x=[ + 810 1 1 1 + 820 1 1 1 + 820 1 1 1 + 840 2 1 1 + 840 2 1 1 + 845 2 1 1 + 785 3 1 1 + 790 3 1 1 + 785 3 1 1 + 835 1 1 2 + 835 1 1 2 + 835 1 1 2 + 845 2 1 2 + 855 2 1 2 + 850 2 1 2 + 760 3 1 2 + 760 3 1 2 + 770 3 1 2 + 820 1 2 1 + 820 1 2 1 + 820 1 2 1 + 820 2 2 1 + 820 2 2 1 + 825 2 2 1 + 775 3 2 1 + 775 3 2 1 + 775 3 2 1 + 825 1 2 2 + 825 1 2 2 + 825 1 2 2 + 815 2 2 2 + 825 2 2 2 + 825 2 2 2 + 770 3 2 2 + 760 3 2 2 + 765 3 2 2 + ]; +[i,nl]=dgrouping(x(:,2:end)); +clear X +X(i)=x(:,1); +X=reshape(X,nl); + + + +% Two-way ANOVA from Cousineau 2007 +X= [ + 550 580 610 + 605 635 655 + 660 690 710 + ]'; + + +% Cousineau ? +X= [ + 150. 44. 71. 59. 132. 74. 1. + 335. 270. 156. 160. 118. 230. 1. + 149. 52. 91. 115. 43. 154. 1. + 159. 31. 127. 212. 71. 224. 1. + 159. 0. 35. 75. 71. 34. 1. + 292. 125. 184. 246. 225. 170. 1. + 297. 187. 66. 96. 209. 74. 1. + 170. 37. 42. 66. 114. 81. 1. + 346. 175. 177. 192. 239. 140. 2. + 426. 329. 236. 76. 102. 232. 2. + 359. 238. 183. 123. 183. 30. 2. + 272. 60. 82. 85. 101. 98. 2. + 200. 271. 263. 216. 241. 227. 2. + 366. 291. 263. 144. 220. 180. 2. + 371. 364. 270. 308. 219. 267. 2. + 497. 402. 294. 216. 284. 255. 2. + 282. 186. 225. 134. 189. 169. 3. + 317. 31. 85. 120. 131. 205. 3. + 362. 104. 144. 114. 115. 127. 3. + 338. 132. 91. 77. 108. 169. 3. + 263. 94. 141. 142. 120. 195. 3. + 138. 38. 16. 95. 39. 55. 3. + 329. 62. 62. 6. 93. 67. 3. + 292. 139. 104. 184. 193. 122. 3. + ]; +X=reshape(X(:,1:6),[8,3,6]); +X=permute(X,[1 3 2]); + +% Cousineau TQPM +X=str2num(urlread('http://www.tqmp.org/doc/vol1-1/p42.dat')); +X=reshape(X(:,end),[16,5,2]); +X=permute(X,[3 2 1]); +myanova(X,2,1:2) + +% One way ANOVA from Loftus & Masson, 1994 +% Exposure Duration Per Word (sec) +% 1 Sec 2 Sec 5 Sec +X= [ + 10 13 13 + 6 8 8 + 11 14 14 + 22 23 25 + 16 18 20 + 15 17 17 + 1 1 4 + 12 15 17 + 9 12 12 + 8 9 12 + ]'; +% M1 = 11.0 M2 = 13.0 M3 = 14.2 + + +% Loftus & Masson 1994 +X= [ + 1 450 462 12 460 482 22 460 497 37 480 507 27 + 2 510 492 -18 515 530 15 520 534 14 504 550 46 + 3 492 508 16 512 522 10 503 553 50 520 539 19 + 4 524 532 8 530 543 13 517 546 29 503 553 50 + 5 420 409 -11 424 452 28 431 468 37 446 472 26 + 6 540 550 10 538 528 -10 552 575 23 562 598 36 + ]; +X=reshape(X(:,2:end),6,3,4); +X=permute(X(:,1:2,:),[2 3 1]); +% X : R vs U | SOA 50/100/200/400 | SUBJ(6) + + +%% DATA Eysenck 1974 +% http://web.uccs.edu/lbecker/Psy590/es.htm +% http://homepages.gold.ac.uk/aphome/GLM%20Simple%20Main%20Effects.html +% Counting Rhyming Adjective Imagery Intentional +X=[ + 9 7 11 12 10 + 8 9 13 11 19 + 6 6 8 16 14 + 8 6 6 11 5 + 10 6 14 9 10 + 4 11 11 23 11 + 6 6 13 12 14 + 5 3 13 10 15 + 7 8 10 19 11 + 7 7 11 11 11 + ]; + +% Eysenck 1974 OLD/YOUNG +% +X=urlread('http://forrest.psych.unc.edu/research/vista-frames/help/lecturenotes/lecture10/eysenck.xli'); +X='DATA "Eysenck" :TITLE "Eysenck" :ABOUT "Eysenck study of recall ability of subjects who were young (Y - 18-30 years old) or old (O - 55-65 years old) for recall conditions involving Counting, Rhyming, Adjectives, Imagery or Intentional. From Howell, David C., (Ed. 3) p. 325." :VARIABLES (QUOTE ("Recall" "Age" "Condition")) :TYPES (QUOTE ("Numeric" "Category" "Category")) :LABELS (QUOTE ("Obs0" "Obs1" "Obs2" "Obs3" "Obs4" "Obs5" "Obs6" "Obs7" "Obs8" "Obs9" "Obs10" "Obs11" "Obs12" "Obs13" "Obs14" "Obs15" "Obs16" "Obs17" "Obs18" "Obs19" "Obs20" "Obs21" "Obs22" "Obs23" "Obs24" "Obs25" "Obs26" "Obs27" "Obs28" "Obs29" "Obs30" "Obs31" "Obs32" "Obs33" "Obs34" "Obs35" "Obs36" "Obs37" "Obs38" "Obs39" "Obs40" "Obs41" "Obs42" "Obs43" "Obs44" "Obs45" "Obs46" "Obs47" "Obs48" "Obs49" "Obs50" "Obs51" "Obs52" "Obs53" "Obs54" "Obs55" "Obs56" "Obs57" "Obs58" "Obs59" "Obs60" "Obs61" "Obs62" "Obs63" "Obs64" "Obs65" "Obs66" "Obs67" "Obs68" "Obs69" "Obs70" "Obs71" "Obs72" "Obs73" "Obs74" "Obs75" "Obs76" "Obs77" "Obs78" "Obs79" "Obs80" "Obs81" "Obs82" "Obs83" "Obs84" "Obs85" "Obs86" "Obs87" "Obs88" "Obs89" "Obs90" "Obs91" "Obs92" "Obs93" "Obs94" "Obs95" "Obs96" "Obs97" "Obs98" "Obs99")) :DATA (QUOTE (9 "O" "C" 8 "O" "C" 6 "O" "C" 8 "O" "C" 10 "O" "C" 4 "O" "C" 6 "O" "C" 5 "O" "C" 7 "O" "C" 7 "O" "C" 7 "O" "R" 9 "O" "R" 6 "O" "R" 6 "O" "R" 6 "O" "R" 11 "O" "R" 6 "O" "R" 3 "O" "R" 8 "O" "R" 7 "O" "R" 11 "O" "A" 13 "O" "A" 8 "O" "A" 6 "O" "A" 14 "O" "A" 11 "O" "A" 13 "O" "A" 13 "O" "A" 10 "O" "A" 11 "O" "A" 12 "O" "I" 11 "O" "I" 16 "O" "I" 11 "O" "I" 9 "O" "I" 23 "O" "I" 12 "O" "I" 10 "O" "I" 19 "O" "I" 11 "O" "I" 10 "O" "IN" 19 "O" "IN" 14 "O" "IN" 5 "O" "IN" 10 "O" "IN" 11 "O" "IN" 14 "O" "IN" 15 "O" "IN" 11 "O" "IN" 11 "O" "IN" 8 "Y" "C" 6 "Y" "C" 4 "Y" "C" 6 "Y" "C" 7 "Y" "C" 6 "Y" "C" 5 "Y" "C" 7 "Y" "C" 9 "Y" "C" 7 "Y" "C" 10 "Y" "R" 7 "Y" "R" 8 "Y" "R" 10 "Y" "R" 4 "Y" "R" 7 "Y" "R" 10 "Y" "R" 6 "Y" "R" 7 "Y" "R" 7 "Y" "R" 14 "Y" "A" 11 "Y" "A" 18 "Y" "A" 14 "Y" "A" 13 "Y" "A" 22 "Y" "A" 17 "Y" "A" 16 "Y" "A" 12 "Y" "A" 11 "Y" "A" 20 "Y" "I" 16 "Y" "I" 16 "Y" "I" 15 "Y" "I" 18 "Y" "I" 16 "Y" "I" 20 "Y" "I" 22 "Y" "I" 14 "Y" "I" 19 "Y" "I" 21 "Y" "IN" 19 "Y" "IN" 17 "Y" "IN" 15 "Y" "IN" 22 "Y" "IN" 16 "Y" "IN" 22 "Y" "IN" 22 "Y" "IN" 18 "Y" "IN" 21 "Y" "IN")) :DATASHEET-ARGUMENTS (QUOTE ((420 475) (511 120) 2 8)))'; +X=strread(X, '%s', 'delimiter', ' '); +X=X(160:3:end-8) +X{1}(1)=[]; +X=str2num(strvcat(X)); +X=reshape(X,10,5,2); +X=permute(X,[3 2 1]); % AGE x COND x SUBJ + + +% Migraine Headache +% http://core.ecu.edu/psyc/wuenschk/SPSS/SPSS-Data.htm +X= [ + 21 22 8 6 6 + 20 19 10 4 9 + 7 5 5 4 5 + 25 30 13 12 4 + 30 33 10 8 6 + 19 27 8 7 4 + 26 16 5 2 5 + 13 4 8 1 5 + 26 24 14 8 17 + ]; +% Howell Table 14.3 +X= [ + 21 22 8 6 6 + 20 19 10 4 4 + 17 15 5 4 5 + 25 30 13 12 17 + 30 27 13 8 6 + 19 27 8 7 4 + 26 16 5 2 5 + 17 18 8 1 5 + 26 24 14 8 9 + ]; + + +% http://www.ou.edu/faculty/M/Jorge.L.Mendoza-1/psy5013/twoway-repeated.sas +% [Traning] [Males: Pre Post FU6 FU12] [Females: Pre Post FU6 FU12] +X = [ + 1 7 22 13 14 0 6 22 26 + 1 25 10 17 24 0 16 12 15 + 1 50 36 49 23 0 8 0 0 + 1 16 38 34 24 15 14 22 8 + 1 33 25 24 25 27 18 24 37 + 1 10 7 23 26 0 0 0 0 + 1 13 33 27 24 4 27 21 3 + 1 22 20 21 11 26 9 9 12 + 1 4 0 12 0 0 0 14 1 + 1 17 16 20 10 0 0 12 0 + 0 0 0 0 0 15 28 26 15 + 0 69 56 14 36 0 0 0 0 + 0 5 0 0 5 6 0 23 0 + 0 4 24 0 0 0 0 0 0 + 0 35 8 0 0 25 28 0 16 + 0 7 0 9 37 36 22 14 48 + 0 51 53 8 26 19 22 29 2 + 0 25 0 0 15 0 0 5 14 + 0 59 45 11 16 0 0 0 0 + 0 40 2 33 16 0 0 0 0 + ]; +X=reshape(X(:,2:end),[10,2,4 2]); +X=permute(X, [2 3 4 1]); +[p,F,fx,epsilon,pomega2,df,dfe,SS,SSe,SSt,peta2,omega2]=myanova(X,3,2) + + +% http://www.psych.northwestern.edu/help/systat/Syst.facmix.html +% subject Smoking High Stress Low Stress +% 1 light 8 1 +% 2 light 7 1 +% 3 heavy 6 8 +% 4 heavy 5 6 +% 5 light 8 3 +% 6 light 10 1 +% 7 heavy 6 7 +% 8 light 9 2 +% 9 heavy 8 10 +% 10 light 9 2 +x=[ + 1 8 1 + 1 7 1 + 2 6 8 + 2 5 6 + 1 8 3 + 1 10 1 + 2 6 7 + 1 9 2 + 2 8 10 + 1 9 2 + ]; +%X=reshape(X, +[i,nl]=dgrouping([ [x(:,1);x(:,1)],[repmat(1,10,1);repmat(2,10,1)]]); +[i,nl]=dgrouping([ [[1:10]';[1:10]'] [x(:,1);x(:,1)],[repmat(1,10,1);repmat(2,10,1)]]); +X=NaN*zeros(nl); + +%http://www.isogenic.info/html/example_2.html http://www.isogenic.info/html/example_2.html +% Strain EROD Treatm Block +x=[ + 1 18.7 1 1 + 2 17.9 1 1 + 3 19.2 1 1 + 4 26.3 1 1 + 1 7.7 2 1 + 2 8.4 2 1 + 3 9.8 2 1 + 4 9.7 2 1 + 1 16.7 1 2 + 2 14.4 1 2 + 3 12.0 1 2 + 4 19.8 1 2 + 1 6.4 2 2 + 2 6.7 2 2 + 3 8.1 2 2 + 4 6.0 2 2 + ] +[i,nl]=dgrouping(x(:,[1 3 4])) +X=NaN*zeros(nl); +X(i)=x(:,2); +%In this case a three-way ANOVA with strain and treatment being fixed +%effects (determined by the experimentalist) and the block being a random +%factor (i.e. not a specific treatment). +% +% Source DF SS MS F P +% Block 1 47.610 47.610 18.37 0.004 +% Strain 3 32.962 10.988 4.24 0.053 +% Trt 1 422.303 422.303 162.96 0.000 +% Strain*Trt 3 40.343 13.448 5.19 0.034 +% Error 7 18.140 2.591 +% Total 15 561.358 + + +% ABRDATA.TAB from OpenStat +% http://www.statpages.org/miller/openstat/Analyzing%20Data%20with%20Stats4U.pdf +% Row Col C1 C2 C3 C4 +X=[ + 1.00 1.00 18.00 14.00 12.00 6.00 + 1.00 1.00 19.00 12.00 8.00 4.00 + 1.00 1.00 14.00 10.00 6.00 2.00 + 1.00 2.00 16.00 12.00 10.00 4.00 + 1.00 2.00 12.00 8.00 6.00 2.00 + 1.00 2.00 18.00 10.00 5.00 1.00 + 2.00 1.00 16.00 10.00 8.00 4.00 + 2.00 1.00 18.00 8.00 4.00 1.00 + 2.00 1.00 16.00 12.00 6.00 2.00 + 2.00 2.00 19.00 16.00 10.00 8.00 + 2.00 2.00 16.00 14.00 10.00 9.00 + 2.00 2.00 16.00 12.00 8.00 8.00 + ]; +X=reshape(X(:,3:end), [3 2 2 4]); +X=permute(X,[2 3 4 1]); + +% SOURCE DF SS MS F PROB. +% Between Subjects 11 181.000 +% A Effects 1 10.083 10.083 0.978 0.352 +% B Effects 1 8.333 8.333 0.808 0.395 +% AB Effects 1 80.083 80.083 7.766 0.024 +% Error Between 8 82.500 10.312 +% Within Subjects 36 1077.000 +% C Replications 3 991.500 330.500 152.051 0.000 +% AC Effects 3 8.417 2.806 1.291 0.300 +% BC Effects 3 12.167 4.056 1.866 0.162 +% ABC Effects 3 12.750 4.250 1.955 0.148 +% Error Within 24 52.167 2.174 +% Total 47 1258.000 + +function [SS,df]=sumofsquares(X,f1,nf,mX) +sX=size(X); +f2=setdiff(1:nf+1,f1); +mXf = permute(X, [f1 f2 nf+2:ndims(X)]); +mXf = reshape(mXf,prod(sX(f1)),prod(sX(f2)),[]); +mXf = mean(mXf,1); +SS = mXf - repmat(mX,[1 prod(sX(f2)) 1]) +%SS = reshape(SS, prod(sX(f2)), []).^2 +SS = SS.^2 +SS = sum(sum(SS,2),1); +df = prod(sX(f1)) \ No newline at end of file diff --git a/mystats/myanova.old.2.m b/mystats/myanova.old.2.m new file mode 100644 index 0000000..f2d17b0 --- /dev/null +++ b/mystats/myanova.old.2.m @@ -0,0 +1,186 @@ +function [p,F,fx,SS,df,MS]=myanova(X,nf,rp) +% myanova - N-way ANOVA +% [p,F,fx]=myanova(X,nf,rp) +% Performs a N-way anova on X: +% nf: number of factors +% rp: an array listing the repeated factors +% OUTPUTS: +% p: the Null hypothesis probability +% F: the F-values +% fx: a cell array listing the tested effects. +% Input data should be as follows: +% X = [ factor1 x factor2 x ... x factorN x replicates x ...] +% This function is vectorized, ie. anova is computed for each value in X +% after the 'replicates' (i.e. subject) dimension. +% +% Ex: +% 2 tasks by 3 condition for 10 subjects, within subject design: +% >> [p,F,fx]=myanova(X,2,1:2) +% p: 3x1 array +% F: 3x1 array +% fx: 3x1 cells: { 1 , 2 , [ 1 2 ]} +% +% Requires: f_cdf() function + +sX=size(X); +nX=ndims(X); + +% Number of groups for each factor +ng=sX(1:nf); +png=prod(ng); +% Number of replicates, ie. samples in each group +nr=sX(nf+1); + +if nargin<3 + rp=[]; +end +if ~isempty(rp) & ~all(ismember(1:nf,rp)) + error('Mixed designs (split plot) ANOVA unavailable!') +end +if ~isempty(rp) & nf>2 + error('Repeated measure ANOVA is only available for 2 factor designs!') +end + +% Within group stats +dfw=png*(nr-1); +mXw=mean(X,nf+1); +SSw=X-repmat(mXw,[ones(1,nf) nr 1]); +SSw=reshape(SSw, png*nr, []).^2; +SSw=sum(SSw); +MSw=squeeze(SSw/dfw); + +% Population stats +dft=(png*nr)-1; +mX=reshape(mXw, png, []); +mX=mean(mX,1); +% These are not used afterwards +SSt=sum((reshape(X, png*nr, [])-repmat(mX,[png*nr 1])).^2); +MSt=squeeze(SSt/dft); + +if ~isempty(rp) + % Correct Within Error term to account for repeated measurements + dfr=(nr-1); + mXr=permute(X, [nf+1 1:nf nf+2:nX]); + mXr=reshape(mXr,nr,png,[]); + mXr=mean(mXr,2); + SSr=png*sum((reshape(mXr, nr, []) - repmat(mX,[nr 1])).^2); + SSw=SSt-SSr; +end + +fx={}; +for i=1:nf + fx=[fx ; num2cell(nchoosek(1:nf,i),2)]; +end +nfx=length(fx); + +for i=1:length(fx) + j=fx{i}; + % Between groups stats for each factor + dfb=prod(ng(j)-1); + mXb=permute(mXw, [j setdiff(1:nf,j) nf+1:nX]); + mXb=reshape(mXb, prod(ng(j)),png/prod(ng(j)), []); + mXb=mean(mXb,2); + SSb=nr*png/prod(ng(j))*sum((reshape(mXb, prod(ng(j)), []) - repmat(mX,[prod(ng(j)) 1])).^2); + if length(j)>1 + % Interaction: + % we need to remove single factor variance from the Sum of Squares + for k=1:i-1 + if all(ismember(fx{k}, j)) + SSb=SSb-SS(k,:); + end + end + end + SS(i,:)=SSb; + MSb=SSb./dfb; + + if not(isempty(rp)) + % Correct Error term to account for repeated measurements + if length(j)==1 + dfe=(nr-1)*dfb; + mXe=permute(X, [nf+1 j setdiff(1:nf,j) nf+2:nX]); + mXe=reshape(mXe,nr*prod(ng(j)),png/prod(ng(j)),[]); + mXe=mean(mXe,2); + SSe=png/prod(ng(j))*sum((reshape(mXe, nr*prod(ng(j)), []) - repmat(mX,[nr*prod(ng(j)) 1])).^2); + SSe=SSe-SSr-SSb; + SSbe(i,:)=SSe; + else + % Interaction effects residuals + % + % WARNING !!!! + % + % the following does NOT work for higher than 2 factor design! + % + dfe=(png-sum(ng-1)-1)*(nr-1) + mXe=reshape(X,png*nr,[]); + SSe=sum((mXe - repmat(mX,[nr*png 1])).^2) + SSe=SSe-SSr-sum(SSbe(1:nf,:))-sum(SS(1:nf,:)) + end + MSe=squeeze(SSe/dfe); + MSr=squeeze(SSr/dfr); + F(i,:)=squeeze(MSb)./MSe; + p(i,:)=1-f_cdf(F(i,:),dfb,dfe); + else + F(i,:)=squeeze(MSb)./MSw; + p(i,:)=1-f_cdf(F(i,:),dfb,dfw); + end + if nargout>4 + df(i,:)=dfb; + end + if nargout>5 + MS(i,:)=MSb; + end +end + + +if prod(sX)>png*nr + p=reshape(p,[nfx,sX(nf+2:end)]); + F=reshape(F,[nfx,sX(nf+2:end)]); +end + +return + + +if nargout>3 + SS(nfx+1,:)=SSt; + SS(nfx+2,:)=SSw; + SS(nfx+3,:)=SSr; + SS(nfx+4,:)=SSe; +end +if nargout>4 + df(nfx+1,:)=dft; + df(nfx+2,:)=dfw; + df(nfx+3,:)=dfr; + df(nfx+4,:)=dfe; +end +if nargout>5 + MS(nfx+1,:)=MSt; + MS(nfx+2,:)=MSw; + MS(nfx+3,:)=MSr; + MS(nfx+4,:)=MSe; +end + +return + + +% Examples from ZAR, pp. 250 sqq. +% +% Ex. 12.5 repeated measure ANOVA +X= [ + 164 152 178 + 202 181 222 + 143 136 132 + 210 194 216 + 228 219 245 + 173 159 182 + 161 157 165 +]'; + + +% http://www.richland.edu/james/lecture/m170/ch13-2wy.html +% SS = 512.8667 449.4667 143.1333 136.0000 1241.4667 +% F = 3.682 3.056 2.641 +X=[ + 106, 110 , 95, 100 , 94, 107 , 103, 104 , 100, 102; + 110, 112 , 98, 99 , 100, 101 , 108, 112 , 105, 107; + 94, 97, 86, 87 98, 99 99, 101 94, 98]; +X=cat(3,X(:,1:2:end),X(:,2:2:end)) diff --git a/mystats/myanova.old.m b/mystats/myanova.old.m new file mode 100644 index 0000000..6f6dfb9 --- /dev/null +++ b/mystats/myanova.old.m @@ -0,0 +1,186 @@ +function [p,fx,F]=myanova(X,factors,repls) +% myanva - computes N-way ANOVA +% [p,fx,F]=myanova(X,factors,repls) +% factors : dimensions of factors. default: [1 2] +% repls : replications. Default [3] +% repts : repeated measures. Default [] +% +% +% If X is typically a N1 x N2 x NS matrix of a given measure for +% 2 factors across NS subjects (or replications) +% >> myanova(X) +% will say if any of the 2 factor or their interaction has a significnt +% effect on X +% +% For p factors: +% >> myanova(X,[1 2 ... p]) +% (The replications is supposed to be the p+1 dimension) +% +% The function is vectorized so that for X a N1 x N2 x NS x M measures +% >> myanova(X) +% will return a [3 by M] matrix of p values +% Idem for p factors, and X a N1 x N2 x ... Np x NS x NM matrix + +sX=size(X); + +if nargin<2 + if ndims(X)==2 + factors=1; + elseif ndims(X)==3 + factors=[1 2]; + end +end + +if nargin<3 + repls=max(factors)+1; +end + +% Number of modalities for each factor +% i.e. degrees of freedom +df=sX(factors); +% Number of replications for each factor +Nrepls=sX(repls); + +% +% % Number of observations +% N=prod([df, Nrepls]) +% dftot=N-1; + +% List of effects/interactions +nf=length(factors); +fx={}; +for i=1:nf + fx=[fx ;num2cell(nchoosek(1:nf,i),2)]; +end +nfx=length(fx); + +% % Reshape X for latter easier use +% if prod(sX)>prod([df Nrepls]) +% X= +% end + +N=prod([df Nrepls]); +SSw=N-prod(df(1:nf)); + +for i=1:nfx + j=fx{i}; + + if length(j)==1 + x=reshape(permute(X, [j 1:j-1 j+1:nf nf+1 nf+2]), df(j),prod(df)*Nrepls/df(j),[]); + [p(i,:),F(i,:)]=anova1(x); + else + end +end + +% +% for i=1:length(factors) +% +% mY{i}=mean(reshape(permute(X, [factors(i) 1:factors(i)-1 factors(i)+1:length(sX)]), df(i), N/df(i), []) ,2); +% sY=[ df Nrepls ]; +% sY(factors(i))=1; +% nY=prod(sY); +% +% sY=size(X); +% sY(factors(i))=[]; +% sY=[1 sY]; +% ssY=repmat(mY{i},sY) +% ssY=permute(ssY, [2:factors(i) 1 factor(i)+1:ndims(ssY)]); +% ssY=(X-ssY).^2 +% SSwithin= SSwithin + sum(reshape(ssY,N,[]),1) +% +% ssY=(squeeze(mY{i})-repmat(mX, df(i) ,1 )).^2 * nY +% SSbetween= SSbetween + sum(ssY) +% +% dfb=prod(df-1) +% Vbetween=SSbetween/dfb +% dfw=N-prod(df) +% Vwithin=SSwithin/dfw +% +% % Test whether F [ dfbetween , dfwithin ] < x +% F=Vbetween./Vwithin +% p=1-f_cdf(F,dfb,dfw) +% +% end +% +% nfx=2; + + +% Reshape output to match input format +if length(sX)>repls+1 + p=reshape(p, [ nfx sX(repls+1:end) ]); + F=reshape(F, [ nfx sX(repls+1:end) ]); +end + + + +function [p,F]=anova1(X,) +% Row-by-row One-way ANOVA +% X = [groups x replicates x ...] + +% Number of groups +ng=size(X,1); +% Number of replicates, ie. samples in each group +nr=size(X,2); + +% Between group stats +dfb=ng-1; +mXb=mean(X,2); +SSb=sum(sum((X-repmat(mXb,[1 nr 1])).^2,2),1); +MSb=SSb/dfb; + +% Population stats +dft=ng*nr-1; +mX=mean(mXb,2); +SSt=sum(sum((X-repmat(mX,[ng nr 1])).^2)); +MSt=SSt/dft; + +% Within group stats +dfw=ng*(nr-1); +mXw=mean(X,2); +SSw=sum(sum((X-repmat(mXw,[1 nr 1])).^2,2),1); +MSw=SSw/dfw; + +% SSb=SSt-SSw; +% MSb=SSb/dfb; + +% Test whether F [ dfbetween , dfwithin ] < x +F=MSb./MSw; +p=1-f_cdf(F,dfb,dfw); + +return + +function [p,F]=anova1_ok(X) +% Row-by-row One-way ANOVA +% X = [groups x replicates x ...] + +% Number of groups +ng=size(X,1); +% Number of replicates, ie. samples in each group +nr=size(X,2); + +% Between group stats +dfb=ng-1; +mXb=mean(X,2); +SSb=sum(sum((X-repmat(mXb,[1 nr 1])).^2,2),1); +MSb=SSb/dfb; + +% Population stats +dft=ng*nr-1; +mX=mean(mXb,2); +SSt=sum(sum((X-repmat(mX,[ng nr 1])).^2)); +MSt=SSt/dft; + +% Within group stats +dfw=ng*(nr-1); +mXw=mean(X,2); +SSw=sum(sum((X-repmat(mXw,[1 nr 1])).^2,2),1); +MSw=SSw/dfw; + +% SSb=SSt-SSw; +% MSb=SSb/dfb; + +% Test whether F [ dfbetween , dfwithin ] < x +F=MSb./MSw; +p=1-f_cdf(F,dfb,dfw); + +return \ No newline at end of file diff --git a/mystats/myanova1.m b/mystats/myanova1.m new file mode 100644 index 0000000..836e33f --- /dev/null +++ b/mystats/myanova1.m @@ -0,0 +1,36 @@ +function [p,F]=anova1(X) +% Row-by-row One-way ANOVA +% X = [groups x replicates x ...] + +sX=size(X); +% Number of groups +ng=sX(1); +% Number of replicates, ie. samples in each group +nr=sX(2); + +% Within group stats +dfw=ng*(nr-1); +mXw=mean(X,2); +SSw=sum(sum((X-repmat(mXw,[1 nr])).^2,2),1); +MSw=SSw/dfw; + +% Population stats +dft=ng*nr-1; +mX=mean(mXw,1); +SSt=sum(sum((X-repmat(mX,[ng nr])).^2)); +MSt=SSt/dft; + +% Between group stats +dfb=ng-1; +mXb=mean(X,1); +SSb=nr*sum(sum((mXw-repmat(mX,[ng 1])).^2,2),1); +MSb=SSb/dfb; + +% SSb=SSt-SSw; +% MSb=SSb/dfb; + +% Test whether F [ dfbetween , dfwithin ] < x +F=MSb./MSw; +p=1-f_cdf(F,dfb,dfw); + +return \ No newline at end of file diff --git a/mystats/myanova2.m b/mystats/myanova2.m new file mode 100644 index 0000000..80b93b7 --- /dev/null +++ b/mystats/myanova2.m @@ -0,0 +1,89 @@ +function [p,F,fx,SS,df,MS]=myanova(X,nf) +% myanova - N-way ANOVA +% [p,F,fx]=myanova(X,nf) +% Performs a N-way anova (nf is the number of factors) on X +% Ouputs p is the Null hypothesis probability, F is the F-value +% where: +% X = [group1 x group2 x ... x groupN x replicates x ...] +% This function is vectorized, ie. anova is computed for each value in X +% after the 'replicates' dimension. + +sX=size(X); +nX=ndims(X); + +% Number of groups for each factor +ng=sX(1:nf); +png=prod(ng); +% Number of replicates, ie. samples in each group +nr=sX(nf+1); + +% Within group stats +dfw=prod(ng)*(nr-1); +mXw=mean(X,nf+1); +SSw=X-repmat(mXw,[ones(1,nf) nr 1]); +SSw=reshape(SSw, png*nr, []).^2; +SSw=sum(SSw); +MSw=squeeze(SSw/dfw); + +% Population stats +dft=prod(ng)*nr-1; +mX=reshape(mXw, png, []); +mX=mean(mX,1); +SSt=sum((reshape(X, png*nr, [])-repmat(mX,[png*nr 1])).^2); +MSt=squeeze(SSt/dft); + +fx={}; +for i=1:nf + fx=[fx ;num2cell(nchoosek(1:nf,i),2)]; +end +nfx=length(fx); + +for i=1:length(fx) + j=fx{i}; + % Between groups stats for each factor + dfb=prod(ng(j)-1); + mXb=permute(mXw, [j setdiff(1:nf,j) nf+1:nX]); + mXb=reshape(mXb, prod(ng(j)),png/prod(ng(j)), []); + mXb=mean(mXb,2); + SSb=nr*png/prod(ng(j))*sum((reshape(mXb, prod(ng(j)), []) - repmat(mX,[prod(ng(j)) 1])).^2); + if length(j)>1 + % Multiple factors + % Subtract crossed terms from the Sum of squares + for k=1:i-1 + if all(ismember(fx{k}, j)) + SSb=SSb-SS(k,:); + end + end + end + SS(i,:)=SSb; + MSb=SSb./dfb; + if nargout>4 + df(i,:)=dfb; + end + if nargout>5 + MS(i,:)=MSb; + end + + F(i,:)=squeeze(MSb)./MSw; + p(i,:)=1-f_cdf(F(i,:),dfb,dfw); +end + + +if prod(sX)>png*nr + p=reshape(p,[nfx,sX(nf+2:end)]); + F=reshape(F,[nfx,sX(nf+2:end)]); +end +if nargout>3 + SS(nfx+1,:)=SSw; + SS(nfx+2,:)=SSt; +end +if nargout>4 + df(nfx+1,:)=dfw; + df(nfx+2,:)=dft; +end +if nargout>5 + MS(nfx+1,:)=MSw; + MS(nfx+2,:)=MSt; +end + +return diff --git a/mystats/myanova3.m b/mystats/myanova3.m new file mode 100644 index 0000000..36ed703 --- /dev/null +++ b/mystats/myanova3.m @@ -0,0 +1,301 @@ +function [p,F,fx,varargout]=myanova(X,nf,rp,epsilon) +% myanova - N-way ANOVA +% [p,F,fx]=myanova(X,nf) +% [p,F,fx,epsilon,df,dfe,SS,SSe,SSt]=... +% Performs a N-way beween subjects anova on X +% +%INPUTS: +% X = [ factor1 x factor2 x ... x factorN x replicates x ...] +% This function is vectorized, ie. anova is computed for each value in X +% after the 'replicates' (i.e. subject) dimension. +% nf: number of factors +% +%OUTPUTS: +% p: the Null hypothesis probability +% F: the F-values +% fx: a cell array listing the tested effects. +% eta: percentage of explained variance by each factor +% epsilon: sphericity correction if applicable +% +%OPTIONAL INPUTS: +% [...]=myanova(X,nf,rp) +% To specifiy repeated factors in a within-subject design +% rp: an array listing the repeated factors +% +% [...]=myanova(X,nf,rp,epsilon) +% Correct for non-spherical data using epsilon (expanded to match the +% size of measures, if necessary). If epsilon=NaN, epsilon(s) will be +% computed using Greenhouse-Geisser or Huynh-Feldt (if eGG>.7) +% +% Ex: +% 2 tasks by 3 condition for 10 subjects, within subject design: +% >> [p,F,fx]=myanova(X(1:2,1:3,1:10),2,1:2) +% p: 3x1 array +% F: 3x1 array +% fx: 3x1 cells: { 1 , 2 , [ 1 2 ]} +% +% Requires: f_cdf() + +sX=[size(X) 1]; +nX=ndims(X); +if nargin<2 + error('Number of factors (nf) is mandatory') +end +% Number of groups for each factor +ng=sX(1:nf); +% Nb of cells +png=prod(ng); +% Number of replicates, ie. samples in each group +nr=sX(nf+1); +%Size of dependent variables (DV) +szd=[size(X) ones(1,nf)]; +szd=szd(nf+2:end); +% Number of DV +pszd=prod(szd); + +if nargin<3 + rp=[]; +end +if ~isempty(rp) & ~all(ismember(1:nf,rp)) + error('Mixed designs (split plot) ANOVA unavailable!') +end +if ~isempty(rp) & nf>3 + error('Repeated measure ANOVA is only available for 2 factor designs!') +end +if nargin<4 + epsilon=1; +end + +% Within Group/Error stats +mXw=sum(X,nf+1)./sX(nf+1); +SSw=X-repmat(mXw,[ones(1,nf) nr 1]); +SSw=reshape(SSw, [png*nr, szd]); +SSw=sum(SSw.^2); +if (nr>1) + dfw=png*(nr-1); + MSw=squeeze(SSw/dfw); +else + error('(my)anova doesn''t work with 1 subject / group!') +end + +% Population stats +%Total number of freedom +dft=(png*nr)-1; +mX=reshape(mXw, [png, szd]); +% Mean value of DV per subject +mX=sum(mX,1)./png; + +% Useful value: +sX2=sum(reshape(X, [png*nr, szd]),1).^2./(png*nr); + +% These are not used afterwards +SSt=sum(reshape(X, [png*nr, szd]).^2,1); +SSt=SSt-sX2; +MSt=squeeze(SSt/dft); + +error +unterminated program!!! + +if ~isempty(rp) + % Correct Within Error term to account for repeated measurements + dfr=(nr-1); + mXr=permute(X, [nf+1 1:nf nf+2:nX]); + mXr=reshape(mXr,nr,png,pszd); + mXr=mean(mXr,2); + % SSr=png*sum((reshape(mXr, nr, szd) - repmat(mX,[nr 1])).^2); + SSr=png*(sum(reshape(mXr, nr, szd).^2,1) - mX2); + SSw=SSt-SSr; +end + +fx={}; +for i=1:nf + fx=[fx ; num2cell(nchoosek(1:nf,i),2)]; +end +nfx=length(fx); + +% Sphericity checking +if isnan(epsilon) + % check for sphericity first + if all(ng<=2) + epsilon=1; + else + [eGG,eHF]=sphericity(X,nf); + epsilon=eGG; + epsilon(eGG>.7)=eHF(eGG>.7); + epsilon(ng==2)=1; + epsilon=min(epsilon,1); + end +end +if numel(epsilon)==1; + epsilon=epsilon*ones(nfx,prod(sX(nf+2:end))); +elseif length(epsilon(:))==nfx + epsilon=epsilon(:)*ones(1,prod(sX(nf+2:end))); +end + +% [p,F,fx,epsilon(1),df(2),dfe(3),SS(4),SSe(5),SSt(6)]=... +if nargout > 3 + varargout{1}=epsilon; +end +if nargout > 8 + varargout{6}=SSt; +end + +for i=1:length(fx) + j=fx{i}; + % Between groups stats for each factor + dfb=prod(ng(j)-1); + mXb=permute(mXw, [j setdiff(1:nf,j) nf+1:nX]); + mXb=reshape(mXb, prod(ng(j)),png/prod(ng(j)), []); + mXb=mean(mXb,2); + SSb=nr*png/prod(ng(j))*sum((reshape(mXb, prod(ng(j)), []) - repmat(mX,[prod(ng(j)) 1])).^2); + if length(j)>1 + % Interaction: + % we need to remove single factor variance from the Sum of Squares + for k=1:i-1 + if all(ismember(fx{k}, j)) + SSb=SSb-SS(k,:); + end + end + end + SS(i,:)=SSb; + MSb=SSb./dfb; + + if not(isempty(rp)) + % Correct Error term to account for repeated measurements + if length(j)==1 + dfe=(nr-1)*dfb; + mXe=permute(X, [nf+1 j setdiff(1:nf,j) nf+2:nX]); + mXe=reshape(mXe,nr*prod(ng(j)),png/prod(ng(j)),[]); + mXe=mean(mXe,2); + SSe=png/prod(ng(j))*sum((reshape(mXe, nr*prod(ng(j)), []) - repmat(mX,[nr*prod(ng(j)) 1])).^2); + SSe=SSe-SSr-SSb; + SSbe(i,:)=SSe; + else + % Interaction effects residuals + % + % WARNING !!!! + % + % the following does NOT work for more-than-2-factors design! + % + dfe=(png-sum(ng-1)-1)*(nr-1); + mXe=reshape(X,png*nr,[]); + SSe=sum((mXe - repmat(mX,[nr*png 1])).^2); + SSe=SSe-SSr-sum(SSbe(1:nf,:))-sum(SS(1:nfx,:)); + end + MSe=squeeze(SSe/dfe); + MSr=squeeze(SSr/dfr); + dfb=dfb.*epsilon(i,:); + dfe=dfe.*epsilon(i,:); + F(i,:)=squeeze(MSb)./MSe; + p(i,:)=1-f_cdf(F(i,:),dfb,dfe); + else + dfb=dfb.*epsilon(i,:); + dfw=dfw.*epsilon(i,:); + F(i,:)=squeeze(MSb)./MSw; + p(i,:)=1-f_cdf(F(i,:),dfb,dfw); + end + % [p,F,fx,epsilon(1),df(2),dfe(3),SS(4),SSe(5),SSt(6)]=... + if nargout>4 + varargout{2}(i,:)=dfb; + end + if nargout>5 + if not(isempty(rp)) + varargout{3}(i,:)=dfe; + else + varargout{3}(i,:)=dfw; + end + end + if nargout>6 + varargout{4}(i,:)=SSb; + end + if nargout>7 + if not(isempty(rp)) + varargout{5}(i,:)=SSe; + else + varargout{5}(i,:)=SSw; + end + end +end + +if prod(sX)>png*nr + p=reshape(p,[nfx,sX(nf+2:end)]); + F=reshape(F,[nfx,sX(nf+2:end)]); +end + + + +return + + +if nargout>3 + SS(nfx+1,:)=SSt; + SS(nfx+2,:)=SSw; + SS(nfx+3,:)=SSr; + SS(nfx+4,:)=SSe; +end +if nargout>4 + df(nfx+1,:)=dft; + df(nfx+2,:)=dfw; + df(nfx+3,:)=dfr; + df(nfx+4,:)=dfe; +end +if nargout>5 + MS(nfx+1,:)=MSt; + MS(nfx+2,:)=MSw; + MS(nfx+3,:)=MSr; + MS(nfx+4,:)=MSe; +end + + +return + +%% RESSOURCES +% http://www.brown.edu/Research/LCE/Fall2004/Within%20subjs.pdf +% + +%% EXAMPLES +% +% from Zar, 1999, pp. 250 sqq. +% Ex. 12.5 repeated measure ANOVA +X= [ + 164 152 178 + 202 181 222 + 143 136 132 + 210 194 216 + 228 219 245 + 173 159 182 + 161 157 165 + ]'; +% F= 727/57.94 = 12.6 + +% http://www.richland.edu/james/lecture/m170/ch13-2wy.html +% SS = 512.8667 449.4667 143.1333 136.0000 1241.4667 +% F = 3.682 3.056 2.641 +X=[ + 106, 110 , 95, 100 , 94, 107 , 103, 104 , 100, 102; + 110, 112 , 98, 99 , 100, 101 , 108, 112 , 105, 107; + 94, 97, 86, 87 98, 99 99, 101 94, 98]; +X=cat(3,X(:,1:2:end),X(:,2:2:end)) + + + + +% % http://www.linguistics.ucla.edu/faciliti/facilities/statistics/fromoac.htm +% X=[ 8 9 8 8 9 7 10 9 10; 9 10 9 10 9 13 8 9 9; 8 7 7 12 7 9 10 9 7; 6 8 9 8 10 10 12 9 10; 7 6 7 11 12 8 8 11 9]; +% X=reshape(X,5,3,3); +% +% % COND TRIAL SUBJECT +% X=permute(X,[3 2 1]); +% +% [p,F]=myanova(X,2,1:2) +% l=factorlabels(X) +% rm_anova2(X(:), l(:,3),l(:,1),l(:,2),{'COND', 'TRIAL'}) +% % 'Source' 'SS' 'df' 'MS' 'F' 'p' +% % 'COND' [24.8444] [ 2] [12.4222] [4.0216] [0.0618] +% % 'TRIAL' [ 0.3111] [ 2] [ 0.1556] [0.0625] [0.9399] +% % 'COND x TRIAL' [ 1.6889] [ 4] [ 0.4222] [0.1907] [0.9397] +% % 'COND x Subj' [24.7111] [ 8] [ 3.0889] [] [] +% % 'COND x Subj' [19.9111] [ 8] [ 2.4889] [] [] +% % 'COND x TRIAL x Subj' [35.4222] [16] [ 2.2139] [] [] + + \ No newline at end of file diff --git a/mystats/myanova4.m b/mystats/myanova4.m new file mode 100644 index 0000000..3f393aa --- /dev/null +++ b/mystats/myanova4.m @@ -0,0 +1,57 @@ +function [p,F,fx,pomega2,peta2]=myanova4(X,varargin) +% Effect sizes for anova +%cf. http://psyphz.psych.wisc.edu/~shackman/olejnik_PsychMeth2003.pdf +% http://www.psy.ohio-state.edu/visionlab/lore/827/lab5_07.pdf + + +[p,F,fx,epsilon,df,dfe,SS,SSe,SSt]=myanova(X,varargin{:}); + +eta2=SS./SSt +peta2=SS./(SSe+SS) +MSe=(SSe./dfe); +N=prod(size(X))./(df+1); +%omega2= +pomega2=(SS-df.*MSe)./(SSe+(N-dfe).*MSe) + +% MS=SS./df +% theta2=dfe*(MS-MSe)./prod(size(X)) +% theta2/(theta2+MSe) + +return + +% http://www.ats.ucla.edu/stat/stata/faq/crf24 +% http://www.ats.ucla.edu/stat/stata/faq/omega2.htm +y=[ + 3 1 1 +4 1 2 +7 1 3 +7 1 4 +1 2 1 +2 2 2 +5 2 3 +10 2 4 +6 1 1 +5 1 2 +8 1 3 +8 1 4 +2 2 1 +3 2 2 +6 2 3 +10 2 4 +3 1 1 +4 1 2 +7 1 3 +9 1 4 +2 2 1 +4 2 2 +5 2 3 +9 2 4 +3 1 1 +3 1 2 +6 1 3 +8 1 4 +2 2 1 +3 2 2 +6 2 3 +11 2 4]; +y=reshape(y(:,1),4,2,4) \ No newline at end of file diff --git a/mystats/myanova5.m b/mystats/myanova5.m new file mode 100644 index 0000000..b2d9ed2 --- /dev/null +++ b/mystats/myanova5.m @@ -0,0 +1,516 @@ +function [p,F,fx,varargout]=myanova(X,nf,rp,epsilon,verbose) +% myanova - N-way ANOVA +% [p,F,fx]=myanova(X,nf) +% [p,F,fx,epsilon,df,dfe,SS,SSe,SSt]=... +% Performs a N-way beween subjects anova on X +% +%INPUTS: +% X = [ factor1 x factor2 x ... x factorN x replicates x ...] +% This function is vectorized, ie. anova is computed for each value in X +% after the 'replicates' (i.e. subject) dimension. +% nf: number of factors +% +%OUTPUTS: +% p: the Null hypothesis probability +% F: the F-values +% fx: a cell array listing the tested effects. +% eta: percentage of explained variance by each factor +% epsilon: sphericity correction if applicable +% +%OPTIONAL INPUTS: +% [...]=myanova(X,nf,rp) +% To specifiy repeated/within-subject factors in a within-subject design +% rp: an array listing the dimensions of repeated factors +% +% [...]=myanova(X,nf,rp,epsilon) +% Correct for non-spherical data using epsilon (expanded to match the +% size of measures, if necessary). If epsilon=NaN, epsilon(s) will be +% computed using Greenhouse-Geisser correction or Huynh-Feldt (if eGG>.7) +% +% Examples: +% +% X(1:2,1:3,1:10) = data from 2 tasks by 3 condition for 10 subjects, +% both factors (dimensions 1 and 2) are within-subject factors: +% >> [p,F,fx]=myanova(X,2,1:2) +% p: 3x1 array +% F: 3x1 array +% fx: 3x1 cells: { 1 , 2 , [ 1 2 ]} +% +% X(1:3,1:4,1:10) = data from 3 groups of 10 subjects performing 4 tasks +% only the second factor (dimension 2 of X) is a within-subject variable: +% >> [p,F,fx]=myanova(X,2,[2]) +% p: 3x1 array +% F: 3x1 array +% fx: 3x1 cells: { 1 , 2 , [ 1 2 ]} +% +% Requires: f_cdf() + +sX=[size(X) 1]; +nX=ndims(X); +if nargin<2 + error('Number of factors (nf) is mandatory') +end +% Number of groups for each factor +ng=sX(1:nf); +png=prod(ng); +% Number of replicates, ie. samples in each group +nr=sX(nf+1); +if nr<2 + error('You must have more than one observation per cell/one subject per condition') +end + +if nargin<3 + rp=[]; +end +% if ~isempty(rp) & ~all(ismember(1:nf,rp)) +% error('Mixed designs (split plot) ANOVA unavailable!') +% end +% if ~isempty(rp) & nf>2 +% error('Repeated measure ANOVA is only available for 2 factor designs!') +% end +if nargin<4 + epsilon=1; +end +if nargin<5 || isempty(verbose) + verbose=1; +end +if isempty(epsilon) + epsilon=NaN; +end + +% Within Group/Error stats +% Mean in each cell +mXw=mean(X,nf+1); +% Residual Error of the model +SSe=X-repmat(mXw,[ones(1,nf) nr 1]); +SSe=reshape(SSe, png*nr, []).^2; +SSe=sum(SSe); +dfe=png*(nr-1); +MSe=squeeze(SSe/dfe); +%keeps the full model unexplained variance for later +MSw=MSe; +dfw=dfe; + +% Population stats +dft=(png*nr)-1; +% Grand mean value +mX=reshape(mXw, png, []); +mX=mean(mX,1); +% Correction factor: +% cX=mX.^2./(png*nr); + +% These are not used afterwards +SSt=sum((reshape(X, png*nr, [])-repmat(mX,[png*nr 1])).^2); +% MSt=squeeze(SSt/dft); + +if ~isempty(rp) + % Correct Within Error term to account for repeated measurements + dfr=(nr-1); + mXr=permute(X, [nf+1 1:nf nf+2:nX]); + mXr=reshape(mXr,nr,png,[]); + mXr=mean(mXr,2); + SSr=png*sum((reshape(mXr, nr, []) - repmat(mX,[nr 1])).^2); +end + +% Sphericity checking +if isnan(epsilon) % | ischar(epsilon) + % check for sphericity first + if all(ng<=2) + epsilon=1; + else + [eGG,eHF]=sphericity(X,nf); + epsilon=eGG; + epsilon(eGG>.7)=eHF(eGG>.7); + epsilon(ng==2)=1; + epsilon=min(epsilon,1); + end +end +if numel(epsilon)==1; + epsilon=epsilon*ones(nfx,prod(sX(nf+2:end))); +elseif length(epsilon(:))==nfx + epsilon=epsilon(:)*ones(1,prod(sX(nf+2:end))); +end + +% Number of groups in between subjects +pngb=1; +if ~isempty(rp) + tmp=zeros(1,nf); + tmp(rp)=1; + rp=logical(tmp); + pngb=prod(ng(~rp)); + png=png/pngb; + % Factors within first, between next + fxw = [find(rp) find(~rp)]; + % ngw = sX(pfxw); +end + +% Within Group/Error stats +% i.e unmodeled error +mXe=mean(X,nf+1); +SSe=X-repmat(mXe,[ones(1,nf) nr 1]); +SSe=reshape(SSe, [pnc nr svar]).^2; +SSe=sum(sum(SSe)); +if (nr<=1) + error('myanova doesn''t work with 1 subject / group!') +end +dfe=png*(nr-1); +MSe=squeeze(SSe/dfe); +dfe=repmat(dfe, [nfx 1]); + +% Population stats +dft=(pno)-1; +mX=reshape(mXe,pnc,[svar]); + varargout{1}=epsilon; +end +if nargout > 8 || verbose==1 + varargout{6}=SSt; +end + +for i=1:length(fx) + j=fx{i}; + % Between groups stats for each factor + dfb=prod(ng(j)-1); + mXb=permute(mXw, [j setdiff(1:nf,j) nf+1:nX]); + mXb=reshape(mXb, prod(ng(j)),png/prod(ng(j)), []); + mXb=mean(mXb,2); + SSb=nr*png/prod(ng(j))*sum((reshape(mXb, prod(ng(j)), []) - repmat(mX,[prod(ng(j)) 1])).^2); + + if length(j)>1 + % Interaction: + % we need to remove the variance from emvbedded effects from the + % Sum of Squares of this effect + for k=1:i-1 + if all(ismember(fx{k}, j)) + SSb=SSb-SS(k,:); + end + end + end + SS(i,:)=SSb; + MSb=SSb./dfb; + + if any(ismember(j,rp)) + % Correct Error term to account for repeated measurements + dfe=(nr-1)*dfb; + mXe=permute(X, [nf+1 j setdiff(1:nf,j) nf+2:nX]); + mXe=reshape(mXe,nr*prod(ng(j)),png/prod(ng(j)),[]); + mXe=mean(mXe,2); + SSe=png/prod(ng(j))*sum((reshape(mXe, nr*prod(ng(j)), []) - repmat(mX,[nr*prod(ng(j)) 1])).^2); + SSe=SSe-SSr-SSb; + for k=1:i-1 + %remove embedded factors effects and error terms + if all(ismember(fx{k}, j)) + SSe=SSe-SS(k,:)-SSbe(k,:); + end + end + end + SSbe(i,:)=SSe; + MSe=squeeze(SSe/dfe); + % % MSr=squeeze(SSr/dfr); + % else + % dfw=png*(nr-1); + % MSw=squeeze(SSw/dfw); + % + % dfe=dfw; + % MSe=MSw; + % end + dfb=dfb.*epsilon(i,:); + dfe=dfe.*epsilon(i,:); + F(i,:)=squeeze(MSb)./MSe; + p(i,:)=1-f_cdf(F(i,:),dfb,dfe); + % dfb=dfb.*epsilon(i,:); + % dfw=dfw.*epsilon(i,:); + % F(i,:)=squeeze(MSb)./MSw; + % p(i,:)=1-f_cdf(F(i,:),dfb,dfw); + % end + % [p,F,fx,epsilon(1),df(2),dfe(3),SS(4),SSe(5),SSt(6)]=... + if nargout>4 || verbose==1 + varargout{2}(i,:)=dfb; + end + if nargout>5 || verbose==1 + %if not(isempty(rp)) + varargout{3}(i,:)=dfe; + %else + % varargout{3}(i,:)=dfw; + %end + end + if nargout>6 || verbose==1 + varargout{4}(i,:)=SSb; + end + if nargout>7 || verbose==1 + %if not(isempty(rp)) + varargout{5}(i,:)=SSe; + %else + % varargout{5}(i,:)=SSw; + %end + end +end + +if prod(sX)>png*nr + p=reshape(p,[nfx,sX(nf+2:end)]); + F=reshape(F,[nfx,sX(nf+2:end)]); +end + +if verbose + [ignore,df,dfe,SSb,SSe,SSt]=deal(varargout{:}); + MS=SS./df; + + %Display results + fprintf([repmat('-',1,80) '\n'] ); + fprintf(['%' num2str(max(cellfun('length', fx))*2+15) 's \t| df \t| SS \t| MS \t| F \t| p\n'], 'ANOVA results'); + fprintf([repmat('-',1,80) '\n'] ); + for j=1:min(size(p,2),3) + if size(p,2)>1 + fprintf('MULTIDIMENSIONAL DATA: [%d] ...\n',j) + end + + for i=1:length(fx) + if i<=nf + label=sprintf('Factor #%d',i); + else + label=['Interaction ' sprintf('%d*',fx{i})]; + label=label(1:end-1); + end + label=sprintf([ '%' num2str(max(cellfun('length', fx))*2+15) 's'],label); + fprintf('%s: \t % 3.3g \t% 8.4g\t% 8.4g\t% 8.4g\t %g\n',... + label,df(i),SS(i,j),MS(i,j),F(i,j),p(i,j)); + if ~isempty(rp) || i==nfx + label=sprintf([ '%' num2str(length(label)) 's'],'Error'); + fprintf('%s: \t % 3.3g \t% 8.4g\t% 8.4g\n',... + label,dfe(i),SSe(i,j),SSe(i,j)./dfe(i)); + end + end + + if ~isempty(rp) + MS_r=SSr./dfr; + F_r=squeeze(MS_r)./MSw; + p_r=1-f_cdf(F_r,dfr,dfw); + label=sprintf([ '%' num2str(length(label)) 's'],'Subjects'); + fprintf('%s: \t % 3.3g \t% 8.4g\t% 8.4g\t% 8.4g\t %g\n',... + label,dfr,SSr(j),MS_r(j),F_r(j),p_r(j)); + end + label=sprintf([ '%' num2str(max(cellfun('length', fx))*2+15) 's'],'Total'); + fprintf('%s: \t % 3.3g \t% 8.4g\n',... + label,dft,SSt(j)); + if size(p,2)>1 + fprintf([repmat('. ',1,40) '\n']) + end + + end + if j3 + SS(nfx+1,:)=SSt; + SS(nfx+2,:)=SSw; + SS(nfx+3,:)=SSr; + SS(nfx+4,:)=SSe; +end +if nargout>4 + df(nfx+1,:)=dft; + df(nfx+2,:)=dfw; + df(nfx+3,:)=dfr; + df(nfx+4,:)=dfe; +end +if nargout>5 + MS(nfx+1,:)=MSt; + MS(nfx+2,:)=MSw; + MS(nfx+3,:)=MSr; + MS(nfx+4,:)=MSe; +end + + +return + +%% RESSOURCES +% http://www.brown.edu/Research/LCE/Fall2004/Within%20subjs.pdf +% + +%% EXAMPLES +% +% from Zar, 1999, pp. 250 sqq. +% Ex. 12.5 (repeated measure one-way ANOVA) +X= [ + 164 152 178 + 202 181 222 + 143 136 132 + 210 194 216 + 228 219 245 + 173 159 182 + 161 157 165 + ]'; +% F = (1454/2)/(695.3/12) = 727/57.94 = 12.6 + + +% BETWEEN SUBJECT DESIGN WITH 2 REPLICATES +% ======================================== +% http://www.richland.edu/james/lecture/m170/ch13-2wy.html +% SS = 512.8667 449.4667 143.1333 136.0000 1241.4667 +% F observed = 28.283 12.393 1.973 +% F critique = 3.682 3.056 2.641 +X=[ + 106, 110 , 95, 100 , 94, 107 , 103, 104 , 100, 102; + 110, 112 , 98, 99 , 100, 101 , 108, 112 , 105, 107; + 94, 97, 86, 87 98, 99 99, 101 94, 98]; +X=cat(3,X(:,1:2:end),X(:,2:2:end)) +[p,F]=myanova(X,2,[]) + + + +% % http://www.linguistics.ucla.edu/faciliti/facilities/statistics/fromoac.htm +X=[ 8 9 8 8 9 7 10 9 10; 9 10 9 10 9 13 8 9 9; 8 7 7 12 7 9 10 9 7; 6 8 9 8 10 10 12 9 10; 7 6 7 11 12 8 8 11 9]; +X=reshape(X,5,3,3); +% +% % COND TRIAL SUBJECT +X=permute(X,[3 2 1]); +% +[p,F]=myanova(X,2,1:2) +% l=factorlabels(X) +% rm_anova2(X(:), l(:,3),l(:,1),l(:,2),{'COND', 'TRIAL'}) +% % 'Source' 'SS' 'df' 'MS' 'F' 'p' +% % 'COND' [24.8444] [ 2] [12.4222] [4.0216] [0.0618] +% % 'TRIAL' [ 0.3111] [ 2] [ 0.1556] [0.0625] [0.9399] +% % 'COND x TRIAL' [ 1.6889] [ 4] [ 0.4222] [0.1907] [0.9397] +% % 'COND x Subj' [24.7111] [ 8] [ 3.0889] [] [] +% % 'TRIAL x Subj' [19.9111] [ 8] [ 2.4889] [] [] +% % 'COND x TRIAL x Subj' [35.4222] [16] [ 2.2139] [] [] + + + +% http://lib.stat.cmu.edu/DASL/Datafiles/airpullutionfiltersdat.html +% 1. NOISE = Noise level reading (decibels) +% 2. SIZE = Vehicle size: 1 small 2 medium 3 large +% 3. TYPE = 1 standard silencer 2 Octel filter +% 4. SIDE = 1 right side 2 left side of car +% and 3 replicates + +x=[ + 810 1 1 1 + 820 1 1 1 + 820 1 1 1 + 840 2 1 1 + 840 2 1 1 + 845 2 1 1 + 785 3 1 1 + 790 3 1 1 + 785 3 1 1 + 835 1 1 2 + 835 1 1 2 + 835 1 1 2 + 845 2 1 2 + 855 2 1 2 + 850 2 1 2 + 760 3 1 2 + 760 3 1 2 + 770 3 1 2 + 820 1 2 1 + 820 1 2 1 + 820 1 2 1 + 820 2 2 1 + 820 2 2 1 + 825 2 2 1 + 775 3 2 1 + 775 3 2 1 + 775 3 2 1 + 825 1 2 2 + 825 1 2 2 + 825 1 2 2 + 815 2 2 2 + 825 2 2 2 + 825 2 2 2 + 770 3 2 2 + 760 3 2 2 + 765 3 2 2 + ]; +[i,nl]=dgrouping(x(:,2:end)); +X(i)=x(:,1); +X=reshape(X,nl); + +% One way ANOVA from Loftus & Masson, 1994 +% Exposure Duration Per Word (sec) +% 1 Sec 2 Sec 5 Sec +X= [ + 10 13 13 + 6 8 8 + 11 14 14 + 22 23 25 + 16 18 20 + 15 17 17 + 1 1 4 + 12 15 17 + 9 12 12 + 8 9 12 + ]'; +% M1 = 11.0 M2 = 13.0 M3 = 14.2 + +% Two-way ANOVA from Cousineau 2007 +X= [ + 550 580 610 + 605 635 655 + 660 690 710 + ]'; + + +% Cousineau ? +X= [ + 150. 44. 71. 59. 132. 74. 1. + 335. 270. 156. 160. 118. 230. 1. + 149. 52. 91. 115. 43. 154. 1. + 159. 31. 127. 212. 71. 224. 1. + 159. 0. 35. 75. 71. 34. 1. + 292. 125. 184. 246. 225. 170. 1. + 297. 187. 66. 96. 209. 74. 1. + 170. 37. 42. 66. 114. 81. 1. + 346. 175. 177. 192. 239. 140. 2. + 426. 329. 236. 76. 102. 232. 2. + 359. 238. 183. 123. 183. 30. 2. + 272. 60. 82. 85. 101. 98. 2. + 200. 271. 263. 216. 241. 227. 2. + 366. 291. 263. 144. 220. 180. 2. + 371. 364. 270. 308. 219. 267. 2. + 497. 402. 294. 216. 284. 255. 2. + 282. 186. 225. 134. 189. 169. 3. + 317. 31. 85. 120. 131. 205. 3. + 362. 104. 144. 114. 115. 127. 3. + 338. 132. 91. 77. 108. 169. 3. + 263. 94. 141. 142. 120. 195. 3. + 138. 38. 16. 95. 39. 55. 3. + 329. 62. 62. 6. 93. 67. 3. + 292. 139. 104. 184. 193. 122. 3. + ]; +X=reshape(X(:,1:6),[8,3,6]); +X=permute(X,[1 3 2]); + + +% Cousineau TQPM +X=str2num(urlread('http://www.tqmp.org/doc/vol1-1/p42.dat')); +X=reshape(X(:,end),[16,5,2]); +X=permute(X,[3 2 1]); +myanova(X,2,1:2) + +% Loftus & Masson 1994 +X= [ + 1 450 462 12 460 482 22 460 497 37 480 507 27 + 2 510 492 -18 515 530 15 520 534 14 504 550 46 + 3 492 508 16 512 522 10 503 553 50 520 539 19 + 4 524 532 8 530 543 13 517 546 29 503 553 50 + 5 420 409 -11 424 452 28 431 468 37 446 472 26 + 6 540 550 10 538 528 -10 552 575 23 562 598 36 + ]; +X=reshape(X(:,2:end),6,3,4); +X=permute(X(:,1:2,:),[2 3 1]); +% X : R vs U | SOA 50/100/200/400 | SUBJ(6) diff --git a/mystats/myanovaeffects.m b/mystats/myanovaeffects.m new file mode 100644 index 0000000..fd76895 --- /dev/null +++ b/mystats/myanovaeffects.m @@ -0,0 +1,27 @@ +function [fx]=myanovaeffects(nf,factornames) +% myanovaeffects - List effects and their interaction in a factorial design +% [fx]=myanovaeffects(nf) +% List main effects and interaction(s) in factorial design with [nf] factors +% [fx] is a N-by-1 cell array +% +% [fx]=myanovaeffects(nf,factornames) +% Makes up a cell list of char listing instead of the factor index, +% their names according to factornames. +% Example: +% >> myanovaeffects(2, {'FACTOR1','FACTOR2'}) +% outputs: { 'FACTOR1' 'FACTOR2' 'FACTOR1*FACTOR2' } +% +% See also: myanova +fx={}; +for i=1:nf + fx=[fx ; num2cell(nchoosek(1:nf,i),2)]; +end +if nargin>1 + for i=1:length(fx) + fxn{i,1}=factornames{fx{i}(1)}; + for j=2:length(fx{i}) + fxn{i,1}=[ fxn{i} '*' factornames{fx{i}(j)}]; + end + end + fx=fxn; +end \ No newline at end of file diff --git a/mystats/myanovan.m b/mystats/myanovan.m new file mode 100644 index 0000000..964b59c --- /dev/null +++ b/mystats/myanovan.m @@ -0,0 +1,372 @@ +% Perform a multi-way analysis of variance (ANOVA). The goal is to test +% whether the population means of data taken from {k} different +% groups are all equal. +% +% Data is a single vector {data} with groups specified by +% a corresponding matrix of group labels {grps}, where {grps} +% has the same number of rows as {data}. For example, if +% {data} = [1.1;1.2]; {grps}= [1,2,1; 1,5,2]; +% then data point 1.1 was measured under conditions 1,2,1 and +% data point 1.2 was measured under conditions 1,5,2. +% Note that groups do not need to be sequentially numbered. +% +% By default, a 'linear' model is used, computing the N main effects +% with no interactions. this may be modified by param 'model' +% +% p= anovan(data,groups, 'model', modeltype) +% - modeltype = 'linear': compute N main effects +% - modeltype = 'interaction': compute N effects and +% N*(N-1) two-factor interactions +% - modeltype = 'full': compute interactions at all levels +% +% Under the null of constant means, the statistic {f} follows an F +% distribution with {df_b} and {df_e} degrees of freedom. +% +% The p-value (1 minus the CDF of this distribution at {f}) is +% returned in {pval}. +% +% If no output argument is given, the standard one-way ANOVA table is +% printed. +% +% BUG: DFE is incorrect for modeltypes != full +% @end deftypefn + +% Copyright (C) 2003-2005 Andy Adler +% +% Octave is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by +% the Free Software Foundation; either version 2, or (at your option) +% any later version. +% +% Octave is distributed in the hope that it will be useful, but +% WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +% General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with Octave; see the file COPYING. If not, write to the Free +% Software Foundation, 59 Temple Place - Suite 330, Boston, MA +% 02111-1307, USA. + +% -*- texinfo -*- +% @deftypefn {Function File} {[{pval}, {f}, {df_b}, {df_e}] =} anovan ({data}, {grps}) +% @deftypefnx {Function File} {[{pval}, {f}, {df_b}, {df_e}] =} anovan ({data}, {grps}, 'param1', {value1}) +% + +% Author: Andy Adler +% Based on code by: KH +% $Id: anovan.m,v 1.10 2005/05/30 13:04:41 adb014 Exp $ +% +% TESTING RESULTS: +% 1. ANOVA ACCURACY: www.itl.nist.gov/div898/strd/anova/anova.html +% Passes 'easy' test. Comes close on 'Average'. Fails 'Higher'. +% This could be fixed with higher precision arithmetic +% 2. Matlab anova2 test +% www.mathworks.com/access/helpdesk/help/toolbox/stats/anova2.html +% % From web site: +% popcorn= [ 5.5 4.5 3.5; 5.5 4.5 4.0; 6.0 4.0 3.0; +% 6.5 5.0 4.0; 7.0 5.5 5.0; 7.0 5.0 4.5]; +% % Define groups so reps = 3 +% groups = [ 1 1;1 2;1 3;1 1;1 2;1 3;1 1;1 2;1 3; +% 2 1;2 2;2 3;2 1;2 2;2 3;2 1;2 2;2 3 ]; +% anovan( vec(popcorn'), groups, 'model', 'full') +% % Results same as Matlab output +% 3. Matlab anovan test +% www.mathworks.com/access/helpdesk/help/toolbox/stats/anovan.html +% % From web site +% y = [52.7 57.5 45.9 44.5 53.0 57.0 45.9 44.0]'; +% g1 = [1 2 1 2 1 2 1 2]; +% g2 = {'hi';'hi';'lo';'lo';'hi';'hi';'lo';'lo'}; +% g3 = {'may'; 'may'; 'may'; 'may'; 'june'; 'june'; 'june'; 'june'}; +% anovan( y', [g1',g2',g3']) +% % Fails because we always do interactions + + +function [PVAL, FSTAT, DF_B, DFE] = anovan (data, grps, varargin) + +% if nargin <= 1 +% usage ('anovan (data, grps)'); +% end + +% test supplied parameters +modeltype= 'linear'; +for idx= 3:2:nargin + param= varargin{idx-2}; + value= varargin{idx-1}; + if strcmp(param, 'model') + modeltype= value; + % elseif strcmp(param % add other parameters here + else + error(sprintf('parameter %s is not supported', param)); + end +end + +if ~isvector (data) + error ('anova: for `anova (data, grps) data must be a vector'); +end + +nd = size (grps,1); % number of data points +nw = size (grps,2); % number of anova 'ways' +if (~ isvector (data) || (length(data) ~= nd)) + % error ('anova: grps must be a matrix of the same number of rows as data'); +end + +[g,grp_map] = relabel_groups (grps); +if strcmp(modeltype, 'linear') + max_interact = 1; +elseif strcmp(modeltype,'interaction') + max_interact = 2; +elseif strcmp(modeltype,'full') + max_interact = size(grps,1); +else + error(sprintf('modeltype %s is not supported', modeltype)); +end +ng = length(grp_map); +int_tbl = interact_tbl (nw, ng, max_interact ); +[gn, gs, gss] = raw_sums(data, g, ng, int_tbl); + +stats_tbl = int_tbl(2:size(int_tbl,1),:)>0; +nstats= size(stats_tbl,1); +stats= zeros( nstats+1, 5); % SS, DF, MS, F, p +for i= 1:nstats + [SS, DF, MS]= factor_sums( gn, gs, gss, stats_tbl(i,:), ng, nw); + stats(i,1:3)= [SS, DF, MS]; +end + +% The Mean squared error is the data - avg for each possible measurement +% This calculation doesn't work unless there is replication for all grps +% SSE= sum( gss(sel) ) - sum( gs(sel).^2 ./ gn(sel) ); +SST= gss(1) - gs(1)^2/gn(1); +SSE= SST - sum(stats(:,1)); + +% KND: Error with the following! +sel = select_pat( ones(1,nw), ng, nw); %incorrect for modeltypes != full +DFE= sum( (gn(sel)-1).*(gn(sel)>0) ); +MSE= SSE/DFE; +stats(nstats+1,1:3)= [SSE, DFE, MSE]; + +for i= 1:nstats + MS= stats(i,3); + DF= stats(i,2); + F= MS/MSE; + pval = 1 - f_cdf (F, DF, DFE); + stats(i,4:5)= [F, pval]; +end + +if nargout==0; + printout( stats, stats_tbl ); +else + PVAL= stats(1:nstats,5); + FSTAT=stats(1:nstats,4); + DF_B= stats(1:nstats,2); + DF_E= DFE; +end +return + + +% relabel groups to a mapping from 1 to ng +% Input +% grps input grouping +% Output +% g relabelled grouping +% grp_map map from output to input grouping +function [g,grp_map] = relabel_groups(grps) +grp_vec= vec(grps); +s= sort (grp_vec); +uniq = 1+[0;find(diff(s))]; +% mapping from new grps to old groups +grp_map = s(uniq); +% create new group g +ngroups= length(uniq); +g= zeros(size(grp_vec)); +for i = 1:ngroups + g( find( grp_vec== grp_map(i) ) ) = i; +end +g= reshape(g, size(grps)); +return + +% Create interaction table +% +% Input: +% nw number of 'ways' +% ng number of ANOVA groups +% max_interact maximum number of interactions to consider +% default is nw +function int_tbl =interact_tbl(nw, ng, max_interact) +combin= 2^nw; +inter_tbl= zeros( combin, nw); +idx= (0:combin-1)'; +for i=1:nw; + inter_tbl(:,i) = ( rem(idx,2^i) >= 2^(i-1) ); +end + +% find elements with more than max_interact 1's +idx = ( sum(inter_tbl',1) > max_interact ); +inter_tbl(idx,:) =[]; +combin= size(inter_tbl,1); % update value + +%scale inter_tbl +% use ng+1 to map combinations of groups to integers +% this would be lots easier with a hash data structure +int_tbl = inter_tbl .* (ones(combin,1) * (ng+1).^(0:nw-1) ); +return + +% Calculate sums for each combination +% +% Input: +% g relabelled grouping matrix +% ng number of ANOVA groups +% max_interact +% +% Output (virtual (ng+1)x(nw) matrices): +% gn number of data sums in each group +% gs sum of data in each group +% gss sumsqr of data in each group +function [gn, gs, gss] = raw_sums(data, g, ng, int_tbl); +nw= size(g,2); +ndata= size(g,1); +gn=zeros((ng+1)^nw, 1); +gs=zeros((ng+1)^nw, 1); +gss=zeros((ng+1)^nw, 1); +for i=1:ndata + % need offset by one for indexing + datapt= data(i); + idx = 1+ int_tbl*g(i,:)'; + gn(idx)=gn(idx)+1; + gs(idx)=gs(idx)+datapt; + gss(idx)=gss(idx)+datapt^2; +end +return + +% Calcualte the various factor sums +% Input: +% gn number of data sums in each group +% gs sum of data in each group +% gss sumsqr of data in each group +% select binary vector of factor for this 'way'? +% ng number of ANOVA groups +% nw number of ways + +function [SS,DF]= raw_factor_sums( gn, gs, gss, select, ng, nw); +sel= select_pat( select, ng, nw); +ss_raw= gs(sel).^2 ./ gn(sel); +SS= sum( ss_raw( ~isnan(ss_raw) )); +if length(find(select>0))==1 + DF= sum(gn(sel)>0)-1; +else + DF= 1; %this isn't the real DF, but needed to multiply +end +return + +function [SS, DF, MS]= factor_sums( gn, gs, gss, select, ng, nw); +SS=0; +DF=1; + +ff = find(select); +lff= length(ff); +% zero terms added, one term subtracted, two added, etc +for i= 0:2^lff-1 + remove= find( rem( floor( i * 2.^(-lff+1:0) ), 2) ); + sel1= select; + if ~isempty(remove) + sel1( ff( remove ) )=0; + end + [raw_sum,raw_df]= raw_factor_sums(gn,gs,gss,sel1,ng,nw); + + add_sub= (-1)^length(remove); + SS = SS + add_sub*raw_sum; + DF = DF*raw_df; +end + +MS= SS/DF; +return + +% Calcualte the various factor sums +% Input: +% select binary vector of factor for this 'way'? +% ng number of ANOVA groups +% nw number of ways +function sel= select_pat( select, ng, nw); +% if select(i) is zero, remove nonzeros +% if select(i) is zero, remove zero terms for i + +persistent field +%field=[]; + +if length(select) ~= nw; + error('length of select must be = nw'); +end +ng1= ng+1; + +if isempty(field) + % expand 0:(ng+1)^nw in base ng+1 + field= (0:(ng1)^nw-1)'* ng1.^(-nw+1:0); + field= rem( floor( field), ng1); + % select zero or non-zero elements + + %knd + field= (field>0); +end +%disp('selection pattern') %debug +sel= find( all( field == ones(ng1^nw,1)*select(:)', 2) ); + +return + + +function printout( stats, stats_tbl ); +nw= size( stats_tbl,2); +[jnk,order]= sort( sum(stats_tbl,2) ); + +fprintf('\n%d-way ANOVA Table (Factors A%s):\n\n', nw, ... + sprintf(',%c',('A')+(1:nw-1)) ); +fprintf('Source of Variation Sum Sqr df MeanSS Fval p-value\n'); +fprintf('*********************************************************************\n'); +fprintf('Error %10.2f %4d %10.2f\n', stats( size(stats,1),1:3)); + +for i= order(:)' + str= sprintf(' %c x',('A')+find(stats_tbl(i,:)>0)-1 ); + str= str(1:length(str)-2); % remove x + fprintf('Factor %15s %10.2f %4d %10.2f %7.3f %7.6f\n', ... + str, stats(i,:) ); +end +fprintf('\n'); +return + +% Test Data from http://maths.sci.shu.ac.uk/distance/stats/14.shtml +data=[7 9 9 8 12 10 ... + 9 8 10 11 13 13 ... + 9 10 10 12 10 12]'; +grp = [1,1; 1,1; 1,2; 1,2; 1,3; 1,3; + 2,1; 2,1; 2,2; 2,2; 2,3; 2,3; + 3,1; 3,1; 3,2; 3,2; 3,3; 3,3]; +data=[7 9 9 8 12 10 9 8 ... + 9 8 10 11 13 13 10 11 ... + 9 10 10 12 10 12 10 12]'; +grp = [1,4; 1,4; 1,5; 1,5; 1,6; 1,6; 1,7; 1,7; + 2,4; 2,4; 2,5; 2,5; 2,6; 2,6; 2,7; 2,7; + 3,4; 3,4; 3,5; 3,5; 3,6; 3,6; 3,7; 3,7]; +% Test Data from http://maths.sci.shu.ac.uk/distance/stats/9.shtml +data=[9.5 11.1 11.9 12.8 ... + 10.9 10.0 11.0 11.9 ... + 11.2 10.4 10.8 13.4]'; +grp= [1:4,1:4,1:4]'; +% Test Data from http://maths.sci.shu.ac.uk/distance/stats/13.shtml +data=[7.56 9.68 11.65 ... + 9.98 9.69 10.69 ... + 7.23 10.49 11.77 ... + 8.22 8.55 10.72 ... + 7.59 8.30 12.36]'; +grp = [1,1;1,2;1,3; + 2,1;2,2;2,3; + 3,1;3,2;3,3; + 4,1;4,2;4,3; + 5,1;5,2;5,3]; +% Test Data from www.mathworks.com/ +% access/helpdesk/help/toolbox/stats/linear10.shtml +data=[23 27 43 41 15 17 3 9 20 63 55 90]; +grp= [ 1 1 1 1 2 2 2 2 3 3 3 3; + 1 1 2 2 1 1 2 2 1 1 2 2]'; + + + + diff --git a/mystats/permfriedman.m b/mystats/permfriedman.m new file mode 100644 index 0000000..0d6290c --- /dev/null +++ b/mystats/permfriedman.m @@ -0,0 +1,148 @@ +function [p,S0,fx,P] = permfriedman(X,nf,dim_m,NP,rp) +%PERMFRIEDMAN - Permutation test based on Friedman's ANOVA +% [pv,F,fx,P] = permfriedman(X,nf,dimM,NP,rp) +% +% Performs permutation test based on Friedman's non-parametric N-wy ANOVA +% +%INPUTS: +% X: Data matrix should be as follows: +% X = [ factor1 x factor2 x ... x factorN x Subjects x ...] +% nf: number of factors. +% dimM: Dimensions of data over which the maximum T-value is to be found. +% Default: all dimensions +% NP: Number of permutations (may lead to exhaustive permutation, +% for example if NP <= 2^N-1 using paired data) +% rp: an array listing the repeated factors +% Default: full within subject design, ie rp=1:nf +% +%OUPUTS: +% pv: p-values of the observed data, computed from the permutations +% F: the F-values of the observed data +% fx: a cell array listing the tested effects +% P: List of permutations used +% +% Example +% permfriedmann(X,2) +% +% See also; permtest2 + +% Author: Karim N'Diaye +% Created: Sep 2005 +% Copyright 2005 + + +NPERMS=1000; %Max. number of permutations + +%initialize parameters +if nf>ndims(X)-1 + error('Error Number of factors and dimensions of X don''t match!') +end +if nargin<3 + dim_m=[]; +end +if nargin<4 + NP=[]; +end +if nargin<5 + rp=[1:nf]; +end +rp=rp(:)'; +if not(isempty(rp)) & ~all(ismember(1:nf, rp)) + error('Cannot deal with spli-plot (ie. mixed) designs') +end + +sX=size(X); +ng=sX(1:nf); % nb of groups in each factor +png=prod(ng); % nb of cells +nr=sX(nf+1); % nb of subjects/repeated measures +nsX=[sX(nf+2:end) 1 1]; % shape of the data +ndX=ndims(X); + +% Default is to compute the maximum F value across all dimensions +if isempty(dim_m) + dim_m=1:ndX-nf-1; +end + +within=0; +% Now compute the number of exhaustive permutations +Nexh=inf; % I don't want to think on how to compute the perms... + +if isequal(rp,[1:nf]) + within=1; +end +exhaustive=0; +if isempty(NP) + NP=NPERMS; +end + +if NP>Nexh + warning(sprintf('Cannot do %d permutations. For this dataset, the maximum is: %d.\nThis latter value will be used.',NP, Nexh)); + NP=Nexh; + exhaustive=1; +end + +if NP>NPERMS + warning(sprintf('Too many permutations! Only %d will be performed.',NPERMS)); + NP = NPERMS; + exhaustive=0; +end + +% DEBUG: +% NP = min(500,NP); + + +% PERMUTATION LOOP +for i=0:NP + if i==0 + % At the 0-th permutation, evaluate original data + Y=X; + else + Y=reshape(X,[png*nr,nsX]); + if within + [ignore,P] = sort(rand(png,nr)); + P=P+repmat([0:png:png*(nr-1)],png,1); + else + P=randperm(png*nr); + end + Y=Y(P(:),:); + Y=reshape(X,[ng,nr,nsX]); + end + + [p,Z,fx]=friedmantest(Y,nf,rp); + + + if i==0 + nfx=length(fx); + Z=reshape(Z,[nfx nsX]); + S0=Z; + sz=size(S0); + % preallocate memory + S=zeros(sz); + else + Z=reshape(Z,[nfx nsX]); + if dim_m==0 + % keep Z as it is + elseif ~isequal(dim_m,1:(ndX-nf-1)) + Z=submax(Z,dim_m+1); + else + Z=max(Z(:,:),[],2); + Z=repmat(Z,[1 sz(2:end) 1]); + end + S=S+(Z>=S0); + end +end +% Compute p-values +pv=(S+1)/(NP+1); + +return + +function [Z]=submax(Z,dims) +sZ=size(Z); +odims=setdiff(1:length(sZ),dims); +pdims=prod(sZ(dims)); +Z=permute(Z, [dims odims]); +Z=reshape(Z,[pdims, sZ(odims) 1]); +Z=max(Z,[],1); +Z=repmat(Z,[pdims 1]); +Z=reshape(Z,[sZ(dims) sZ(odims) 1]); +Z=ipermute(Z, [dims odims]); diff --git a/mystats/permtest.m b/mystats/permtest.m new file mode 100644 index 0000000..8de9204 --- /dev/null +++ b/mystats/permtest.m @@ -0,0 +1,604 @@ +function [pv,S0,NP,PS,P]=permtest(X,X2,testoptions,dim_p,dim_m,NP,TimeBar,tails,verbose,varargin) +% permtest - Generic permutation/randomization test +% +% [pv,S0,NP,PS,P] = permtest(X1,X2,{testname testparams},dim_p,dim_m,NP,TimeBar,tails) +% +% Performs a permutation test with the data +% +%MANDATORY INPUTS: +% X1,X2: [N1 x Ma x Mb...] and [ N2 x Ma x Mb x...] Multidimensional matrices +% of Ni samples/subjects for each group, eg. [ Subjects x Channel x Time ] +% Alternative input formats: +% [pv ... ] = permtest(X,D,{testname},...) +% X is a single data matrix. The two conditions are specified by +% dimension D in the matrix. +% size(X,D)=2 & X1=X(...,1,...) & X2=X(...,2,...) +% [pv ... ] = permtest(X,-N1,{...},...) +% X can be a single data matrix. But the number of samples (ie subjects) +% in the first group should be set using 'N1' (which is input as a +% negative value) +% I.e. X = [ X1 ; X2 ] & N1=size(X1,1)=nb of subjects in group 1 +% +% { testname testparams } : Name of the test statistic to use (and its +% parameters). Must be a cell or a char: +% 'ttest': Student's t-value +% 'difftest': difference of the means +% 'pseudottest': t-value with smoothed variance +% 'signtest': sign of the differences +% 'wilcoxon': signed ranks (used for paired data) +% 'edist': euclidian distance, along given dimension(s) +% Test parameters (if needed) should be given in the right order +% following testname: +% 'ttest': No parameters needed +% 'mann-whitney': No parameters needed +% 'wilcoxon': No parameters needed +% 'signtest': No parameters needed +% 'pseudottest': Needs the smooting kernel +% 'edist': Specify the dimension(s) on which distance is computed + +% You can add post-processing on a 2nd line of the cell (if none: enter []) +% 'vertconn_min': Uses the minimal statistic value of the +% neighourhoud. Vertices must be the first dimension +% of dependent variables. +% Vertex connectivity must be provided. +% +% And on a 3rd line, the post-hoc correction for multiple comparison +% across dimension(s) specified in dim_m (skipped if dim_m==0) +% 'max': (default) uses the maximal statistic value +% 'vertconn_max',vc: uses the maximum statistic across neighbours +% only (not the whole brain). It needs the vertex +% connectivity: vc +% 'quantile',p: uses the p-th quantile of data +% 'wyrm': Westfall-Young Randomization Method for multiple +% comparisons +% +% Example: +% >> permtest(x1,x2,{'ttest' [];'vertconn_min', vertconn}) +% Note that the [] is needed by matlab but is not used. +% +%OPTIONAL INPUTS (use [] for default values): +% dimP: Permuted dimensions (ie. subjects). Default: first non singleton +% If dim_p > 0 : paired permutations (default) +% Use negative dim_p for unpaired samples/subjects +% dimM: Dimensions of data across which the maximum (or whatever post +% processing ) of T-value is computed. +% For multidimensional data, eg. [ Subject x Channel x Time ], +% control of Family-wise Error Rate requires to compute T-value +% across all dimensions (starting after the permuted one. +% Default is: all dimensions besides the "permuted" dimensions +% (typically: [2 3 ... ndims(X)]) +% NP: Number of permutations (may be reduced if bigger than the number of permutations, +% for example if NP > 2^N-1 using paired data) +% If not specify, permttest uses 10000 (at most). +% Set NP=inf so to enforce exhaustive permutations, ie. all possible permutations +% (this may lead to HUGE numbers of permutations, avoid with unpaired tests) +% TimeBar: 1 (default) or 0. To display a progress bar. +% tails: 1- (one: X1>X2) or 2- (two: X1X2) tailed test (default: 2) +% +%OUTPUTS: +% pv: p-values of the observed data, computed from the permutations +% (dimensions subjects and dim_m are squeezed). This is a fairly good +% approximation of the parametric test when data are normally +% distributed. But it is still valid when they are not! +% S0: [Ma x Mb...] Observed values of the statistic +% NP: Number of permutations actually performed +% PS: [P x Ma x Mb... ] Matrix of permutation statistics (may be quite big!) +% P: [NP x (N1+N2)] List of permutations used +% +% References: +% On pseudo T-value (smoothed variance): Nichols, NeuroImage, 2003[?] +% On euclidian distance: Greenblatt, Brain Topography, 2004 +% +% Authors: +% K² Team, aka. K. N'Diaye (kndiaye01yahoo.fr> & K. Jerbi (jerbichups.jussieu.fr) +% CNRS UPR640, Lab. d'imagerie cérébrale et neurosciences cognitives +% Hopital de la Salpetriere, Paris, France +% Copyright (C) 2006 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% K² 2006-02-02 Creation +% KND 2006-02-14 Added euclidian distance test ('edist') +% KND 2006-10-12 Corrected bug due to missing reshape following global stat +% ----------------------------- Script History --------------------------------- + +NMAX_UNPAIRED=15; % maximum number of observations for exhaustive search for unpaired samples +NMAX_PAIRED=30; % maximum number of observations for exhaustive search in paired samples +NMAX_SUBJECTS=1000; % Max number of subjects +NPERMS=10000; % Default number of permutations +TESTS={'ttest','difftest','pseudottest','signtest','wilcoxon','edist','cluttest', 'pairedttest'}; +PAIREDTESTS={'wilcoxon', 'mann-whitney', 'signtest', 'plusminus', 'pairedttest'}; +%Note for authors: +% 'mann-whitney' : check code below before making it available to user +% 'plusminus' : should be ok +% 'plusminus' (sum of X1>X2 along dim_m) + +%initialize parameters +if nargin<3 + error('Test Statistic need to be specified!') +end +if ischar(testoptions) + testoptions={testoptions}; +end +if ~iscell(testoptions) + error('The test to be used should be specified as char or cell!') +end +if not(ismember(testoptions{1}, TESTS)) + error('Wrong type of test! Possible test statistics are:\n%s\n', sprintf('%s ', TESTS{:})) +end +if nargin<4 + dim_p=[]; +end +if nargin<5 + dim_m=[]; +end +if nargin<6 + NP=[]; +end +if nargin<7 + TimeBar=1; % KJ, default is off now... +end +if nargin<8 + tails=2; +end +if nargin<9 + verbose=1; +end +if ~isequal(tails,1) && ~isequal(tails,2) + error('Tails can be 1 or 2') +end + +%__________________________________________________________________________ +% +%% Checking dimensions +% +if isempty(dim_p) + dim=find(size(X)>1); + if isempty(dim) + error('Input X has only singleton or null dimensions!') + end + dim=dim(1); + dim_p=dim; +end +dim=abs(dim_p); +if dim_p<0 && ~isempty(strmatch(testoptions{1}, PAIREDTESTS)) + error('Unpaired data not supported for %s statistics, are you still a Student? LOL',testoptions{1}) +end + + +%__________________________________________________________________________ +% +%% RESHAPING ORIGINAL DATA +% +% Data in X (and possibly in X2) will be concatenated (if needed) and +% reshaped so that following computations are made easier. +% Resulting size of X will be: [ (N1+N2) "dim_m"(1) "dim_m"(2) (...) ] +% i.e - subjects are put in the first dimension, group 1 above group 2 +% - data from the dimension(s) dim_m are put as the second, third... dimension of X +% - additional dimensions (tested separately, ie univariately) follow +% +ndX=ndims(X); +sX1=[size(X) 1 1]; +if numel(X2)==1 && X2>0 % X2 is the dimension of the "group factor" + X=permute(X, [ setdiff(1:dim-1,X2) dim X2 setdiff(dim+1:ndX,X2) ]); + X=reshape(X, [ sX1(setdiff(1:dim-1,X2)) sX1(dim)*2 sX1(setdiff(dim+1:ndX,X2)) 1 ]); + sX1(X2)=[]; + dim=dim-(X21 % X2 is a matrix + X2=permute(X2, [dim dX1]); + sX2=[size(X2) ones(1,ndX)]; + if ~isequal(sX(2:end), sX2(2:end)) + error('X1 and X2 should be of the same size in the non-permuted dimensions!') + end + X=cat(1,X,X2); + N=[sX(1) sX2(1)]; + clear sX2; +elseif numel(X2)==1 && X2<0 % X2 is the number of samples in the first group + X2=-X2; + N=[X2 sX(1)-X2]; +elseif numel(X2)==1 && X2>0 +else + error('Check inputs!') +end +clear X2; +X=double(X); +NS=sum(N); +if NS>NMAX_SUBJECTS + error('Too many subjects!') +end +if dim_p>0 && N(1) ~= N(2) + error('Paired data should have the same number of samples!') +end +nsX=[size(X) 1 1]; +Options.Test.InputSize=nsX; +Options.Test.OuputSize=Options.Test.InputSize; +nsX(1)=[]; +Options.Test.OuputSize(1)=[]; + + +%__________________________________________________________________________ +% +%% PREPARE DATA FOR SPECIFIC TESTS +% +Options.Test.Name=testoptions{1}; +switch(Options.Test.Name) + case {'ttest' 'pairedttest' 'difftest' 'pseudottest' 'signtest' 'wilcoxon'} + % Nothing to do + if verbose, fprintf('The %s statistic will be used\n', Options.Test.Name); end + case 'edist' + d={}; + if size(testoptions,2)>1 + d=testoptions{1,2}; + end + if isempty(d) + error('You must specify one or many dimension(s) to compute Euclidan Distance'); + elseif ismember(d, dim_m) + error('Euclidan Distance cannot use the same dimension as dim_m'); + end + d=d-(dim1 + Options.Local.Function=testoptions{2,1}; + Options.Local.Apply=1; +end +if isempty(Options.Local.Function) + Options.Local.Apply=0; +end +if Options.Local.Apply + switch Options.Local.Function + % This apply a correction factor on the statistisc by replacing + % each value by the minimum found over a neigbourhood of connected + % nodes on a mesh + case 'vertconn_min' + Options.Local.Dims=1; + Options.Local.Permute=[1:max(ndX-1,2)]; + Options.Local.Repmat=[1]; + if verbose, fprintf('Local correction using minimal statistic\n'); end + + otherwise + error('Unknown processing options: %s', Options.Local.Function) + end +end +%__________________________________________________________________________ +% +%% PROCESSING OF THE GLOBAL STATISTICS +% +if isequal(dim_m,0) + Options.Global.Apply=0; +else + %SETTING dim_m VARIABLE & SHAPE OF X ALONG IT + % Default is to compute the maximum T value across all dimensions of the + % measurement data (dim_m). + dim_m0=dim_m; + if isempty(dim_m) + dim_m=setdiff(1:ndX,dim); + end + dim_m=unique(dim_m(:))'; + if max(dim_m)>ndX + error('Wrong dim_m dimensions!') + end + if ismember(dim_p,dim_m) + error(sprintf('Wrong dim_m dimensions: [%d] is already set as the permuted dimension!\n' , dim)) + end + dim_m1=dim_m; + dim_m=dim_m-(dim_m>dim); + + Options.Global.Apply=1; + % Default: uses the global maximum + Options.Global.Function='max'; + if size(testoptions, 1)>=3 + Options.Global.Function=testoptions{3,1}; + end + if isempty(Options.Global.Function) + Options.Global.Apply=0; + end + +end +if Options.Global.Apply + switch Options.Global.Function + case {'max', 'quantile'} + switch Options.Global.Function + case 'max' + Options.Global.FunctionName='Maximum'; + case 'quantile' + Options.Global.FunctionName='Quantile'; + end + if verbose, fprintf('The %s statistic will be computed over dimensions: %s\n', Options.Global.FunctionName, sprintf('%d ', dim_m)); end + Options.Global.Dims=dim_m; + Options.Global.Permute=[dim_m setdiff([1:max(ndX-1,2)],dim_m)]; + Options.Global.Reshape=[prod(sX1(dim_m1)) nsX(setdiff(1:ndX-1, dim_m)) 1]; + Options.Global.Repmat=[ prod(sX1(dim_m1)) 1]; + case 'vertconn_max' + if verbose, fprintf('Maximum Statistic will be computed over a neighbouring vertex in dimension: %d\n', dim_m(1)); end + Options.Global.Dims=dim_m(1); + Options.Global.Permute=[dim_m setdiff([1:max(ndX-1,2)],dim_m)]; + Options.Global.Reshape=[prod(sX1(dim_m1)) nsX(setdiff(1:ndX-1, dim_m)) 1]; + Options.Global.Repmat=[ 1 1]; + case 'wyrm' + if verbose, fprintf('Westfall-Young Randomization Method for multiple comparisons will be used in dimension: %d\n', dim_m(1)); end + Options.Global.Dims=dim_m(1); + Options.Global.Permute=[dim_m setdiff([1:max(ndX-1,2)],dim_m)]; + Options.Global.Reshape=[prod(sX1(dim_m1)) nsX(setdiff(1:ndX-1, dim_m)) 1]; + Options.Global.Repmat=[ 1 1]; + otherwise + error('Unknown processing options: %s', Options.Global.Function) + end +else + if verbose, fprintf('No correction for multiple comparisons will be applied.\n'); end +end + + + + +%__________________________________________________________________________ +% +%% NUMBER OF PERMUTATIONS +% +% Now compute the number of exhaustive permutations +if dim_p>0 + if tails==2 + % We compute only one half of the permutations for paired samples + Nexh=2^(N(1)-1)-1; + else + Nexh=2^(N(1))-1; + end +else + Nexh=factorial(N(1)+N(2))/(factorial(N(1))*factorial(N(2)))-1; +end + +exhaustive=0; +if isempty(NP) + if (dim_p>0 && (N(1)+N(2))0 + msg=[ msg ' paired']; + end + msg=sprintf([ 'You are under-sampling (with %d permutations)!\nFor this dataset (with %s subjects),'... + 'the possible number of permutations is: %d.'],NP,msg,Nexh); + + warning(msg); + clear msg + end + elseif isequal(NP, inf) + if TimeBar + if verbose, disp(sprintf('You have requested exhaustive permutations: %d.',Nexh)); end + end + NP=Nexh; + exhaustive=1; + elseif NP>Nexh + if TimeBar + msg=sprintf('%d', N(1)); + if dim_p>0 + msg=[ msg ' paired']; + end + warning(sprintf([ 'You are over-sampling (with %d permutations)!\nFor this dataset (with %s subjects),'... + 'the maximum number of permutations is: %d.'],NP,msg,Nexh)); + end + elseif NP==Nexh; + exhaustive=1; + else + if NP>NPERMS + if TimeBar + warning(sprintf('So many permutations to be done (%d) may take a long time!',NP)); + end + end + end +end + + +%__________________________________________________________________________ +% +%% LIST OF PERMUTATIONS +if dim_p>0 + % paired permutations + if exhaustive + % compute exhaustive list of perms + P=49==(dec2bin(1:NP,N(1))); + else + % non exhaustive + P=round(rand(NP,N(1))); + end + P=[P 1-P].*N(1)+repmat(1:N(1), [NP,2]); +else + % non-paired perms + if exhaustive % do all possible perms + p=nchoosek(1:(N(1)+N(2)),N(1)); + p(1,:)=[]; % the 1st is not a permutation + for i=1:NP + P(i,:)=[p(i,:) setdiff(1:(N(1)+N(2)),p(i,:))]; + end + clear p + else % non exhaustive + [ignore,P]=sort(rand(NP,N(1)+N(2)),2); + end +end + +%__________________________________________________________________________ +% +%% DISPLAY TIMEBAR +if TimeBar + try + htimer = timebar('Permutation Statistics','Progress...'); + catch + warning('Function timebar.m missing. waitbar.m is used instead') + htimer = waitbar(0,'Permutation Statistics'); + end +else + htimer = NaN; +end + +%__________________________________________________________________________ +% +%% PERMUTATION LOOP +for i=0:NP + if i==0 + % At the 0-th permutation, evaluate original data + Y=X; + else + Y=X(P(i,:),:); + end + Y=reshape(Y,[Options.Test.InputSize]); + switch(testoptions{1}) + case 'ttest' + Z=tvalue(Y(1:N(1),:),Y(N(1)+(1:N(2)),:)); + case 'pairedttest' + Z=tvalue_paired(Y(1:N(1),:),Y(N(1)+(1:N(2)),:)); + case 'difftest' + Z=sum(Y(1:N(1),:),1)./N(1)-sum(Y(N(1)+(1:N(2)),:),1)./N(2); %this is to use difference of mean instead of T-statistic (KJ) + case 'pseudottest' + Z=pseudotvalue(Y(1:N(1),:),Y(N(1)+(1:N(2)),:),testoptions{1,2}); + case 'signtest' + Z=sign(Y(1:N(1),:)-Y(N(1)+(1:N(2)),:)); + Z=sum(Z,1).^2./sum(abs(Z),1); + case 'wilcoxon' + %This is the signed ranks (not sum of ranks!) + %Or almost: we don't correct by the denominator because it is + %the same for all permutations. But it should be when there is + %no ties: + % Z=sum(Z(:,:),1)./sqrt(n*(n+1)*(2*n+1)/6) + %otherwise: + % Z=sum(Z(:,:),1)./sum(abs(Z).^2,1) + Z=Y(1:N(1),:)-Y(N(1)+(1:N(2)),:); + Z=sign(Z).*tiedrank(abs(Z),1); + Z=sum(Z(:,:),1); + case 'edist' + Y=reshape(Y,Options.Test.InputSize); + Y=permute(Y,Options.Test.Permute); + Z=sqrt(sum((sum(Y(1:N(1),:,:),1)./N(1)-sum(Y(N(1)+(1:N(2)),:,:),1)./N(2)).^2,2)); + otherwise + error('No statistic comput''d!!! Check da f*** m code') + % case 'mann-whitney' + % Z=tiedrank(Y(:,:),1); + % Z=sum(Z(1:N(1),:),1); + % case 'plusminus' + % Z=sum(Y(1:N(1),:)>Y(N(1)+(1:N(2)),:),1); + end + Z=reshape(Z,[Options.Test.OuputSize]); + if i==0 + S0=Z; + end + if tails==2 + Z=abs(Z); + end + if Options.Local.Apply + switch Options.Local.Function + case 'vertconn_min' + Z=permute(Z,Options.Local.Permute); + Z=vertconn_min(Z(:,:),testoptions{2,2}); + Z=ipermute(Z,Options.Local.Permute); + Z=repmat(Z,Options.Local.Repmat); + end + Z=reshape(Z,[Options.Test.OuputSize]); %Z=reshape(Z,nsX); + end + if i==0 + if tails==2 + S0=abs(Z).*sign(S0); + else + S0=Z;%abs(Z).*sign(S0); + end + sz=size(S0); + S=zeros(sz); + if nargout>3 + try; + PS=zeros([NP sz 1],'single'); + catch + pack; + PS=zeros([NP sz 1],'single'); + end + end + else + if Options.Global.Apply + Z=permute(Z,Options.Global.Permute); + Z=reshape(Z,Options.Global.Reshape); + switch Options.Global.Function + case 'max' + [Z]=max(Z,[],1); + case 'vertconn_max' + Z=vertconn_max(Z(:,:),testoptions{3,2}); + case 'quantile' + Z=quantile(Z(:,:),testoptions{3,2}); + end + Z=repmat(Z,Options.Global.Repmat); + Z=ipermute(Z,Options.Global.Permute); + Z=reshape(Z,[Options.Test.OuputSize]); + end + + if tails==2 + S=S+(Z>=abs(S0)); + %S=S+(repmat(Z,[nsX(1) 1])>=abs(S0)); + else + S=S+(Z>=S0); + end + % If (and only if) requested, PS is output + if nargout>3 + PS(i,:)=Z(:)'; + end + if ishandle(htimer),try,if isequal(get(htimer, 'tag'), 'timebar'), timebar(htimer,i/NP),else,waitbar(i/NP,htimer);end;catch,end,end + end +end + +%% COMPUTE RESULTING P-VALUES +pv=(S+1)./(NP+1); + +%% PROCESSING OUTPUTS + +% Put back the dim_m dimension at their original place +% sX1(dim)=[]; +% pv=reshape(pv, Options.Test.OutputSize); +% S0=reshape(S0, Options.Test.OutputSize); + +if nargout>3 + PS=reshape(PS, [NP nsX]); +end +if ishandle(htimer),try,close(htimer),end,drawnow;end +return + + +%% Validation: +X1=randn(100,1); +X=X1+0.2+randn(size(X1)); +% Paired case: +[p,K,P,PS]=permttest(X1,X,1,[],999,0);p +myttest(X1,X,1,'pttest') +% Unpaired (unequal variance) +[p,K,P,PS]=permttest(X1,X,-1,[],999,0);p +myttest(X1,X,1,'uttest') \ No newline at end of file diff --git a/mystats/permtest2.m b/mystats/permtest2.m new file mode 100644 index 0000000..1d42b08 --- /dev/null +++ b/mystats/permtest2.m @@ -0,0 +1,129 @@ +function [Thd,S0,S]=mypermtest(X,X2,dimS,dimT,pS,pT,NP) +% permtest2 - BrainStorm data permutation test +% [thd,obs]=mypermtest(F1,F2,dimS,dimT,pS,pT) +% +%Inputs: +% F1,F2: data matrices. +% dimS: subject dimension (negative: unpaired test) +% dimT: Time dimension +% pS: threshold for subject-wise pemutation on temporal extent [0.05] +% pT: threshold for sample-by-sample significance [0.05] +% +%Outputs: +% thd: thresholds at the given p-values, computed from the permutations +% obs: observed value in the original data +% stt: statistics +%initialize parameters + +if nargin<5 + p1=.05; +end +if nargin<6 + pT=.05; +end +if nargin<7 + NP = 500; % max number of permutations +end +Nmax=12; % maximum number of observations for Fisher statistic + +if dimS<0 + error('Unpaired testing not implemented yet!') +end + +% ORIGINAL OBSERVATIONS +sX=size(X); +nX=length(sX); +X=permute(X, [dimS dimT setdiff(1:nX,[dimS dimT])]); +X2=permute(X2, [dimS dimT setdiff(1:nX,[dimS dimT])]); +X=cat(1,X,X2); +N= [sX(dimS) ; size(X2,dimS) ]; +clear X2; +% the shape of the measurement data +nsX=[size(X) 1]; +nsX(1)=[]; +np=size(X,1); +nt=sX(dimT); + +if max(N(:,1))Thd; + +if 0 % look in previous permutations +V=S1>repmat(Thd, [NP,1]); +for i=1:NP + p=max(piecemeal(V(i,:))); + if isempty(p),p=0;end + S2(i)=p; +end +end + +if 1 +for i=1:NP + p=max(piecemeal(V0(randperm(nt)))); + if isempty(p),p=0;end + S2(i)=p; +end +end + + + + + + return + + +function [Z]=tmax(Y1,Y2) +% default function: max of pseudo-t +% sqrt((std_PA_orig.*std_PA_orig/JA)+(std_PB_orig.*std_PB_orig/JB)); +Z=(mean(Y1)-mean(Y2)) ./ sqrt( std(Y1).*std(Y1)/size(Y1,1) + std(Y2).*std(Y2)/size(Y2,1) ); + +function [Z]=abstmax(Y1,Y2,dims) +[Z]=tmax(Y1,Y2); +Z=shiftdim(Z,2); +Z=abs(Z); +sZ=size(Z); +Z=reshape(Z,[prod(sZ(dims)), sZ(setdiff(1:length(sZ),dims))]); +Z=max(Z,[],1); + + + +% EX: +% X=rand(3,5)+cumsum(ones(3,5)) +% [th,o,ss]=mypermtest(X,1,2,[.05],500,'myanova', {1}) \ No newline at end of file diff --git a/mystats/permtest2b.m b/mystats/permtest2b.m new file mode 100644 index 0000000..1d42b08 --- /dev/null +++ b/mystats/permtest2b.m @@ -0,0 +1,129 @@ +function [Thd,S0,S]=mypermtest(X,X2,dimS,dimT,pS,pT,NP) +% permtest2 - BrainStorm data permutation test +% [thd,obs]=mypermtest(F1,F2,dimS,dimT,pS,pT) +% +%Inputs: +% F1,F2: data matrices. +% dimS: subject dimension (negative: unpaired test) +% dimT: Time dimension +% pS: threshold for subject-wise pemutation on temporal extent [0.05] +% pT: threshold for sample-by-sample significance [0.05] +% +%Outputs: +% thd: thresholds at the given p-values, computed from the permutations +% obs: observed value in the original data +% stt: statistics +%initialize parameters + +if nargin<5 + p1=.05; +end +if nargin<6 + pT=.05; +end +if nargin<7 + NP = 500; % max number of permutations +end +Nmax=12; % maximum number of observations for Fisher statistic + +if dimS<0 + error('Unpaired testing not implemented yet!') +end + +% ORIGINAL OBSERVATIONS +sX=size(X); +nX=length(sX); +X=permute(X, [dimS dimT setdiff(1:nX,[dimS dimT])]); +X2=permute(X2, [dimS dimT setdiff(1:nX,[dimS dimT])]); +X=cat(1,X,X2); +N= [sX(dimS) ; size(X2,dimS) ]; +clear X2; +% the shape of the measurement data +nsX=[size(X) 1]; +nsX(1)=[]; +np=size(X,1); +nt=sX(dimT); + +if max(N(:,1))Thd; + +if 0 % look in previous permutations +V=S1>repmat(Thd, [NP,1]); +for i=1:NP + p=max(piecemeal(V(i,:))); + if isempty(p),p=0;end + S2(i)=p; +end +end + +if 1 +for i=1:NP + p=max(piecemeal(V0(randperm(nt)))); + if isempty(p),p=0;end + S2(i)=p; +end +end + + + + + + return + + +function [Z]=tmax(Y1,Y2) +% default function: max of pseudo-t +% sqrt((std_PA_orig.*std_PA_orig/JA)+(std_PB_orig.*std_PB_orig/JB)); +Z=(mean(Y1)-mean(Y2)) ./ sqrt( std(Y1).*std(Y1)/size(Y1,1) + std(Y2).*std(Y2)/size(Y2,1) ); + +function [Z]=abstmax(Y1,Y2,dims) +[Z]=tmax(Y1,Y2); +Z=shiftdim(Z,2); +Z=abs(Z); +sZ=size(Z); +Z=reshape(Z,[prod(sZ(dims)), sZ(setdiff(1:length(sZ),dims))]); +Z=max(Z,[],1); + + + +% EX: +% X=rand(3,5)+cumsum(ones(3,5)) +% [th,o,ss]=mypermtest(X,1,2,[.05],500,'myanova', {1}) \ No newline at end of file diff --git a/mystats/skewness.m b/mystats/skewness.m new file mode 100644 index 0000000..43e2ec3 --- /dev/null +++ b/mystats/skewness.m @@ -0,0 +1,23 @@ +function [S]=skewness(X) +% skewness - computes skewness of a distribution +% [S]=skewness(X) computes row-wise skewness +% The unbiased Fisher's skewness for finite samples, a.k.a. G1, is used. +% skewness = n/(n-1)/(n-2) * sum( ((X-mean)/std)^3 ) +% Note: +% Positive skewness reflects an asymmetrical distribution with tail in positive values. +% Standard Error of skewness for normal distribution is: SES ~ sqrt(6/n) +% i.e. if abs(skew) > 2*SES, your data are significantly skewed (2 is the +% T value à.05) +% +if isvector(X) + X=X(:); +end +sz=[size(X) 1 ]; +n=sz(1); +if n<3 + error('Skewness needs sample of size > 2') +end +S=sum((X-repmat(mean(X),n,1)).^3); +S=S./(var(X).^1.5); +S=S.*(n/(n-1)/(n-2)); + \ No newline at end of file diff --git a/mystats/trimmean.m b/mystats/trimmean.m new file mode 100644 index 0000000..e77c9a0 --- /dev/null +++ b/mystats/trimmean.m @@ -0,0 +1,3 @@ +function [ output_args ] = Untitled1( input_args ) +%UNTITLED1 Summary of this function goes here +% Detailed explanation goes here diff --git a/mystats/wyrm.m b/mystats/wyrm.m new file mode 100644 index 0000000..5385c99 --- /dev/null +++ b/mystats/wyrm.m @@ -0,0 +1,50 @@ +function r = wyrm(X,Y,alpha,NP) +%WYRM - Westfall-Young Randomization Method for multiple comparisons tests +% [] = wyrm(X,alpha,N) +% +% Example +% >> wyrm +% +% See also: + +% Ref: +% Pointwise Testing with Functional Data Using the Westfall-Young Randomization Method +% http://cohesion.rice.edu/engineering/Statistics/emplibrary/lee_cox_070119.pdf + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2007 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2007-11-05 Creation +% +% ----------------------------- Script History --------------------------------- + +X=X(:,:); +Y=Y(:,:); + +alpha=0.05; +NP=100; + +t=tvalue(X,Y); +[to,it]=sort(-(t)); +sX=size(X); +N(1)=size(X,1); +N(2)=size(Y,1); +X=cat(1,X,Y); +[ignore,P]=sort(rand(NP,N(1)+N(2)),2); +pp=zeros([NP sX(2:end),1]); +n=prod(sX(2:end)); +for i=1:NP + t(i,:)=tvalue(X(P(i,1:N(1)),:),X(P(i,N(1)+1:end),:)); + for j=1:n + q(i,j)=max(t(i,it(j:end))); + end +end +r=sum(q<=repmat(to, [NP,1]))/NP; +j=find(r>alpha); +if ~isempty(j);j=j(1); + diff --git a/mytriplot.m b/mytriplot.m new file mode 100644 index 0000000..6e63970 --- /dev/null +++ b/mytriplot.m @@ -0,0 +1,324 @@ +function [hs, hc, contour] = triplot(pnt, dhk, val, mode, levels) + +% TRIPLOT make 2D or 3D plot of a triangulated surface and interpolated values +% the surface can be displayed with linear interpolated values or with +% linear interpolated contours of a potential distribution +% +% Use as +% triplot(pnt, dhk, value, mode, levels) +% This will make a plot of value on the surface described by triangles +% dhk with vertices pnt. The matrix dhk can be [], in which case a +% it will be computed using a delaunay triangulation. +% +% The visualization mode can be +% 'faces' plot white triangles only (value can be []) +% 'faces_skin' plot skin-colored triangles only (value can be []) +% 'face_index' plot index of each triangle (value can be []) +% 'nodes' plot black vertices only (value can be []) +% 'node_index' plot index of each vertex (value can be []) +% 'node_label' plot label of each vertex (value should be cell array) +% 'edges' plot black edges only (value can be []) +% 'surface' make interpolated plot of value on surface +% 'contour' make interpolated contour plot of value on surface +% 'contour_bw' make interpolated black-white contour plot +% 'contour_rb' make interpolated contour plot with red-blue +% +% With the optional levels, you can specify the levels at which contours will +% be plotted +% +% See also PATCH, COLORMAP, VIEW (general Matlab commands) + +% updated on Mon Jul 23 12:41:44 MET DST 2001 +% updated on Fri Jan 31 11:47:28 CET 2003 + +% Copyright (C) 2001, Robert Oostenveld +% +% $Log: triplot.m,v $ +% Revision 1.3 2004/06/28 07:51:39 roberto +% improved documentation, added faces_skin +% +% Revision 1.2 2003/03/17 10:37:29 roberto +% improved general help comments and added copyrights +% + +% start with empty return values +hs = []; +hc = []; +contour = []; + +% everything is added to the current figure +holdflag = ishold; +hold on + +% check the input variables +if ~isempty(val) + val = val(:); +end + +if nargin<4 + mode = 'surface'; +end + +if isempty(dhk) & ~strcmp(mode, 'nodes') + % no triangulation was specified but a triangulation is needed + mpnt=mean(pnt); + pnt=pnt-repmat(mpnt,size(pnt,1),1) + + if size(pnt,2)==2 + % make a 2d triangulation of the points using delaunay + dhk = delaunay(pnt(:,1), pnt(:,2)); + else + % make a 2d triangulation of the projected points using delaunay + prj = elproj(pnt); + dhk = delaunay(prj(:,1), prj(:,2)); + end + pnt=pnt+repmat(mpnt,size(pnt,1),1) + +end + +if nargin<3 + warning('only displaying triangle edges') + mode='edges'; + val = []; +elseif nargin<4 + % warning('default displaying surface') + mode='surface'; +elseif nargin<5 + % determine contour levels + if ~isempty(val) & ~iscell(val) + absmax = max(abs([min(val) max(val)])); + levels = linspace(-absmax,absmax,21); + else + levels = []; + end +end + +if isequal(mode, 'contour') | isequal(mode, 'contour_bw') | isequal(mode, 'contour_rb') + % compute contours for 2D or 3D triangulation + triangle_val = val(dhk); + triangle_min = min(triangle_val, [], 2); + triangle_max = max(triangle_val, [], 2); + + for cnt_indx=1:length(levels) + cnt = levels(cnt_indx); + use = cnt>=triangle_min & cnt<=triangle_max; + counter = 0; + intersect1 = []; + intersect2 = []; + + for tri_indx=find(use)' + pos = pnt(dhk(tri_indx,:), :); + v(1) = triangle_val(tri_indx,1); + v(2) = triangle_val(tri_indx,2); + v(3) = triangle_val(tri_indx,3); + la(1) = (cnt-v(1)) / (v(2)-v(1)); % abcissa between vertex 1 and 2 + la(2) = (cnt-v(2)) / (v(3)-v(2)); % abcissa between vertex 2 and 3 + la(3) = (cnt-v(3)) / (v(1)-v(3)); % abcissa between vertex 1 and 2 + abc(1,:) = pos(1,:) + la(1) * (pos(2,:) - pos(1,:)); + abc(2,:) = pos(2,:) + la(2) * (pos(3,:) - pos(2,:)); + abc(3,:) = pos(3,:) + la(3) * (pos(1,:) - pos(3,:)); + counter = counter + 1; + sel = find(la>=0 & la<=1); + intersect1(counter, :) = abc(sel(1),:); + intersect2(counter, :) = abc(sel(2),:); + end + + % remember the details for external reference + contour(cnt_indx).level = cnt; + contour(cnt_indx).n = counter; + contour(cnt_indx).intersect1 = intersect1; + contour(cnt_indx).intersect2 = intersect2; + end + + % collect all different contourlevels for plotting + intersect1 = []; + intersect2 = []; + cntlevel = []; + for cnt_indx=1:length(levels) + intersect1 = [intersect1; contour(cnt_indx).intersect1]; + intersect2 = [intersect2; contour(cnt_indx).intersect2]; + cntlevel = [cntlevel; ones(contour(cnt_indx).n,1) * levels(cnt_indx)]; + end + + X = [intersect1(:,1) intersect2(:,1)]'; + Y = [intersect1(:,2) intersect2(:,2)]'; + C = [cntlevel(:) cntlevel(:)]'; + + if size(pnt,2)>2 + Z = [intersect1(:,3) intersect2(:,3)]'; + else + Z = zeros(2, length(cntlevel)); + end +end + + +switch lower(mode) + +case 'faces' +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% plot the faces of the 2D or 3D triangulation +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + hs = patch('Vertices', pnt, 'Faces', dhk); + set(hs, 'FaceColor', 'white'); + set(hs, 'EdgeColor', 'none'); + +case 'faces_skin' +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% plot the faces of the 2D or 3D triangulation +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + skin = [255 213 119]/255; + brain = [202 100 100]/255; + cortex = [255 213 119]/255; + hs = patch('Vertices', pnt, 'Faces', dhk); + set(hs, 'FaceColor', skin); + set(hs, 'EdgeColor', 'none'); + lighting gouraud + material shiny + camlight + +case 'face_index' +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% plot the triangle indices (numbers) at each face +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + for face_indx=1:size(dhk,1) + str = sprintf('%d', face_indx); + tri_x = (pnt(dhk(face_indx,1), 1) + pnt(dhk(face_indx,2), 1) + pnt(dhk(face_indx,3), 1))/3; + tri_y = (pnt(dhk(face_indx,1), 2) + pnt(dhk(face_indx,2), 2) + pnt(dhk(face_indx,3), 2))/3; + tri_z = (pnt(dhk(face_indx,1), 3) + pnt(dhk(face_indx,2), 3) + pnt(dhk(face_indx,3), 3))/3; + h = text(tri_x, tri_y, tri_z, str, 'HorizontalAlignment', 'center', 'VerticalAlignment', 'middle'); + hs = [hs; h]; + end + +case 'edges' +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% plot the edges of the 2D or 3D triangulation +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + hs = patch('Vertices', pnt, 'Faces', dhk); + set(hs, 'FaceColor', 'none'); + set(hs, 'EdgeColor', 'black'); + +case 'nodes' +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% plot the nodes (vertices) only as points +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + if size(pnt, 2)==2 + plot(pnt(:,1), pnt(:,2), 'k.'); + else + plot3(pnt(:,1), pnt(:,2), pnt(:,3), 'k.'); + end + +case 'node_index' +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% plot the vertex indices (numbers) at each node +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + for node_indx=1:size(pnt,1) + str = sprintf('%d', node_indx); + if size(pnt, 2)==2 + h = text(pnt(node_indx, 1), pnt(node_indx, 2), str, 'HorizontalAlignment', 'center', 'VerticalAlignment', 'middle'); + else + h = text(pnt(node_indx, 1), pnt(node_indx, 2), pnt(node_indx, 3), str, 'HorizontalAlignment', 'center', 'VerticalAlignment', 'middle'); + end + hs = [hs; h]; + end + +case 'node_label' +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% plot the vertex indices (numbers) at each node +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + for node_indx=1:size(pnt,1) + str = val{node_indx}; + if ~isempty(str) + if size(pnt, 2)==2 + h = text(pnt(node_indx, 1), pnt(node_indx, 2), str, 'HorizontalAlignment', 'center', 'VerticalAlignment', 'middle'); + else + h = text(pnt(node_indx, 1), pnt(node_indx, 2), pnt(node_indx, 3), str, 'HorizontalAlignment', 'center', 'VerticalAlignment', 'middle'); + end + else + h = -1; + end + hs = [hs; h]; + end + +case 'surface' +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% plot a 2D or 3D triangulated surface with linear interpolation +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +if length(val)==size(pnt,1) + hs = patch('Vertices', pnt, 'Faces', dhk, 'FaceVertexCData', val, 'FaceColor', 'interp'); +else + hs = patch('Vertices', pnt, 'Faces', dhk, 'CData', val, 'FaceColor', 'flat'); +end +set(hs, 'EdgeColor', 'none'); + +case 'contour_bw' +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% make black-white contours +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + hc = []; + for i=1:length(cntlevel) + if cntlevel(i)>0 + linestyle = '-'; + linewidth = 1; + elseif cntlevel(i)<0 + linestyle = '--'; + linewidth = 1; + else + linestyle = '-'; + linewidth = 2; + end + h1 = patch('XData', X(:,i), 'Ydata', Y(:,i), ... + 'ZData', Z(:,i), 'CData', C(:,i), ... + 'facecolor','none','edgecolor','black', ... + 'linestyle', linestyle, 'linewidth', linewidth, ... + 'userdata',cntlevel(i)); + hc = [hc; h1]; + end + +case 'contour_rb' +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% make red-blue contours +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + hc = []; + for i=1:length(cntlevel) + if cntlevel(i)>0 + edgecolor = 'red'; + elseif cntlevel(i)<0 + edgecolor = 'blue'; + else + edgecolor = 'black'; + end + h1 = patch('XData', X(:,i), 'Ydata', Y(:,i), ... + 'ZData', Z(:,i), 'CData', C(:,i), ... + 'facecolor','none','edgecolor',edgecolor, ... + 'linestyle', '-', 'linewidth', 1, ... + 'userdata',cntlevel(i)); + hc = [hc; h1]; + end + +case 'contour' +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% make full-color contours +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + hc = []; + for i=1:length(cntlevel) + h1 = patch('XData', X(:,i), 'Ydata', Y(:,i), ... + 'ZData', Z(:,i), 'CData', C(:,i), ... + 'facecolor','none','edgecolor','flat',... + 'userdata',cntlevel(i)); + hc = [hc; h1]; + end + +end % switch + +axis off +axis vis3d +axis equal + +if nargout==0 + clear contour hc hs +end + +if ~holdflag + hold off +end + diff --git a/myview_surface.m b/myview_surface.m new file mode 100644 index 0000000..54e70a8 --- /dev/null +++ b/myview_surface.m @@ -0,0 +1,47 @@ +function [hf,hs,hl] = view_surface(figname,faces,verts,cdata); +% VIEW_SURFACE Convenient function to consistently plot surfaces +% function [hf,hs,hl] = view_surface(figname,faces,verts,cdata); +% figname is the name of the figure window +% faces is the triangle listing +% verts are the corresponding vertices +% cdata is the colordata to use. If not given, uses norms of verts + +% John C. Mosher, Ph.D. See Copyright.m for information +% $Revision: 1 $ $Date: 1/24/01 5:50p $ +% ---> Karim NDIAYEs version <---- + +if(size(verts,2) > 3), % assume transposed + verts = verts'; % if the assumption is wrong, will crash below anyway +end + +if(~exist('cdata','var')), + cdata = rownorm(verts); +end + +cla +hs = patch('faces',faces,'vertices',verts,... + 'facevertexcdata',cdata(:),'facecolor','interp','edgecolor','none'); +view(2) +axis image +axis off +lighting phong + + + +material dull; +lighting phong +set(gcf, 'Color', [1 1 1 ]) +rotate3d on + +hl(1) = camlight(-20,30); +hl(2) = camlight(20,30); +hl(3) = camlight(-20,-30); +%for i = 1:length(hl), +% set(hl(i),'color',[.8 1 1]/length(hl)/1.2); % mute the intensity of the lights +%end + +if(nargout>0), + hf = h; % only if the user has output argument +end + +return diff --git a/nappe.m b/nappe.m new file mode 100644 index 0000000..8fc944d --- /dev/null +++ b/nappe.m @@ -0,0 +1,28 @@ +function h = nappe(data) +%NAPPE - One line description goes here. +% [] = nappe(input) +% +% Example +% >> nappe +% +% See also: + +% Author: K. N'Diaye (kndiaye01gmail.com) +% Copyright (C) 2011 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2011-05-23 Creation +% +% ----------------------------- Script History --------------------------------- +s=size(data); +if sum(s>1)==2 + data=squeeze(data); +end + h = pcolor(data); +set(h, 'EdgeColor', 'none'); + +% time,1:12,nd2array(normalize(mean(data(ok,:,:,:)),'baseline',4,128:256),4)') diff --git a/nd2array.m b/nd2array.m new file mode 100644 index 0000000..93c0de5 --- /dev/null +++ b/nd2array.m @@ -0,0 +1,71 @@ +function [y]=nd2array(x,dim,sx) +% nd2array - Reshape & permute N-dimensional data to (2 dimensional) array +% +% [y]=nd2array(x,dim [,sx]) +% Permutes dimensions so that dim will be the first dimension (i.e. rows) +% of the to-be-created 2-d array. +%INPUTS: +% X: a N-dimensional matrix +% dim: dimension(s) to be put as rows. Default is to use the first non +% singleton dimension. dim can be a vector [dim1 ... dimN], in that +% case, all these dimensions will be append to form the 1st +% dimension of the output Y. +% sx: if set to [], the shape of remaining dimensions in the output will +% be preserved (i.e., equivalent to a permute) +% +% To return back to original shape, use negative dim: +% nd2array(y,-dim,size(x)); +% If dim is a scalar or if size(y,1)==1, size(x,dim) will be ignored, +% otherwise you must have: prod(size(x,dim)) == size(y,1) + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2009 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2009-04-16 Updated help +% +% ----------------------------- Script History --------------------------------- + + +if nargin<2 + dim = min(find(size(x)>1)); + if isempty(dim), dim = 1; end +end +if dim==0 + error('Dimension can''t be 0') +end +if all(dim>0) + reshape2d=1; + if nargin>2 && isempty(sx) + reshape2d=0; + end + odim=setdiff(1:max(ndims(x),dim),dim); + pd=[dim, odim]; + sx=[size(x) pd.*0+1]; + x=permute(x,pd); + if reshape2d + s2=[prod(sx(dim)) prod(sx(odim))]; + else + s2=[prod(sx(dim)) sx(odim)]; + end + y=reshape(x,s2); +elseif all(dim<0) + if nargin<3 + error('Size of the original array is needed!') + end + dim=-dim; + ndim=length(sx); + if length(dim)==1 || size(x,1)==1 + sx(dim)=size(x,1); + end + odim=setdiff(1:ndim,dim); + y=reshape(x,[sx(dim) sx(odim)]); + y=ipermute(y,[dim odim]); +else + error('You can''t mix positive and negative dim!') +end + diff --git a/ndcat.m b/ndcat.m new file mode 100644 index 0000000..95e0e85 --- /dev/null +++ b/ndcat.m @@ -0,0 +1,140 @@ +function M = ndcat(d,varargin) +%NDCAT - Assemble matrices as blocks +% [M] = ndcat(d,A,B,C,...) constructs the matrix M which is the +% concatenation of matrices A, B, C,... along dimension(s) given in d. +% NDCAT fills with zeros. +% A,B,C,... are matrices to be assembled +% d: Specify along which dimensions the concatenation should proceed +% Examples: +% d=[1 1] would place matrices in the diagonal, ie growing +% both dimension 1 (rows) and dimension 2 (columns). +% d=[0 1] would place matrices horizontally, that is without +% increasing the 1st dimension (rows) +% If d=[], default is to concatenate along the diagonal. +% If d has multiple rows, each row corresponds to the +% concatenations with the corresponding matrix in the list: +% d(1,:) specifies how B should be concatenated with A; +% d(2,:) specifies how C should then be concatenated with the +% outcome of the previous concatenation (of A and B), etc. +% d can also be provided as a cell array: d={[dB], [dC], etc} +% +% Presently, NDCAT works only with 2-D matrices +% +% Example +% >> ndcat([1 1], ones(2,3), 2*eye +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2007 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2007-10-30 Creation +% +% ----------------------------- Script History --------------------------------- + +if nargin==2 + M=varargin{1}; + return +end + + +%% Check input d +nm=nargin-1; +nd=0; +% sz=[]; +for i=1:nm + nd(i)=ndims(varargin{i}); + % sz(i,1:nd(i))=size(varargin{i}); +end +if iscell(d) + dd=[]; + for i=1:length(d) + dd(i,1:length(d{i}))=d{i}; + end + d=dd; +end +if isempty(d) + % where to put matrices relatively to the others + d=ones(1,max(nd)); +end +if size(d,1)==1 + d=repmat(d, [nm-1,1]); +end +if size(d,1)~=(nm-1) + error('Number of matrices and specifier ''d'' don''t match') +end +if size(d,2)==1 + d=[d ones(nm-1,max(nd)-1)]; +end + +%% recursive call +N=ndcat(d(1:end-1,:), varargin{1:end-1}); +d=d(end,:); +nn=numel(N); +sn=size(N); +dn=ndims(N); +Z=varargin{end}; +sz=size(Z); +dz=ndims(Z); +if isequal(d, [1 1]) + M=[N zeros(sn(1),sz(2)) ; zeros(sz(1),sn(2)) Z]; +elseif isequal(d, [0 1]) + M=[ [N;zeros(max(0,sz(1)-sn(1)),sn(2))] [Z;zeros(max(0,sn(1)-sz(1)),sz(2))] ] ; +elseif isequal(d, [1 0]) + M=[ [N zeros(sn(1),max(0,sz(2)-sn(2)))];[Z zeros(sz(1),max(0,sn(2)-sz(2)))] ] ; +else + error('Can''t process multidim arrays yet') + + +end + +return +if nz1 + r=r+[0:sz(j)-1]'*ones(1,sz/(sz(j)-1)); + end + idx=idx+r(:); +end +M(idx)=varargin{end}; + +return + +% nd(2)=ndims(varargin{end}); +% sz(2,1:nd(2))=size(varargin{end}); +% ne=numel(N); +%% ndx = ndx + (v-1)*k(i); +% +% for i=1:nm +% idx=[]; +% for j=1:nd(i)-1 +% z=idx +% if i==1 && j==1 +% z=1; +% end +% ne=prod(msz(1:j)) +% for k=1:sz(i,j) +% idx=[idx, z+ne*(k-1)] +% end +% end +% +% +% M(idx)=varargin{i} +% end +% +% idx \ No newline at end of file diff --git a/ndiags.m b/ndiags.m new file mode 100644 index 0000000..c9093cb --- /dev/null +++ b/ndiags.m @@ -0,0 +1,29 @@ +function [X]=ndiags(A,d) +s=[size(A)]; +if any(s~=s(1)) + error('Non square matrix') +end +n=1+prod(s(1:end-1))+sum(s(1:end-2)) +X=A(1:n:end); +return + +error('NDIAGS: Not implemented yet') +if length(d)~=2 + error('d has to be a 2 value vector') +end +if not(size(A,d(1))==size(A,d(2))) + error('Diagonalized dimensions of A have to form a square, same ') +end; + +if ndims(A)==4 + A=permute(A,[2 3 1 4]); + for i=1:size(A,d(1)) + i + a=squeeze(A(i, i, :,:)); + d(i,:,:)=a; + end + d=permute([2 1 3]); +end + + + \ No newline at end of file diff --git a/ndmultiply.m b/ndmultiply.m new file mode 100644 index 0000000..31e0c56 --- /dev/null +++ b/ndmultiply.m @@ -0,0 +1,50 @@ +function Z = ndmultiply(X,Y,dim) +%NDMULTIPLY Multiply two (matching) dimensions of N-dimension matrices +% Z = ndmultiply(X,Y,dimY) +% Computes Z = X*W(:,:) where W is a permutation of Y placing dimY in +% first position so that it match X second dimension +% i.e. size(X,2) == size(Y,dim) +% The result Z is then permuted to match Y dimensions -- except dimY, +% the one which has been multiplied and now equals to size(X,1) +% +% If Y is an array and dim > 2 and X is a N-d array, N>=2, then the +% multiplication is done "on the right side" of dimension dim of X +% i.e. size(X,dim) == size(Y,1) +% +% Example: +% X=rand(3,4);Y=rand(2,4,7); +% Z=ndmultiply(X,Y,2) +% Z will be of size 2-by-3-by-7 +% Conversely, with: +% X=rand(2,5); Y=rand(2,4,7); +% ndmultiply(Y,X,1) is of size [5,4,7] +% +% See also: nd2array + +% Author: KND +% Created: Sep 2005 +% Copyright 2005 + +if nargin<3 + dim=1; +end +if ndims(X)>=dim && ndims(Y)==2 + Z=X; + X=Y'; + Y=Z; +end +if ndims(X) == 2 + if dim > ndims(Y) + error('Cannot perform multiplication on dimension dim=%d of Y',dim) + end +else + error('Wrong inputs, check dimensions!') +end +sY=size(Y); +try + Z=X*nd2array(Y,dim); +catch + warning('Data put in double format') + Z=double(X)*double(nd2array(Y,dim)); +end +Z=nd2array(Z, -dim, sY); diff --git a/nearest.c b/nearest.c new file mode 100644 index 0000000..6beaeed --- /dev/null +++ b/nearest.c @@ -0,0 +1,84 @@ +//------------------------------------------------------------------------ +// file: nearest.c - Find nearest points according to euclidian distance +// [I,D,DD] = nearest(A,B) +// INPUTS: +// A - mxd matrix (m=number of points, d=number of dimensions) +// B - kxd matrix (k=number of points, d=number of dimensions) +// OUTPUTS: +// I - mx1 vector of matching points in B +// D - mx1 vector of distances +// D - mxk matrix of all-to-all euclidian distances +//------------------------------------------------------------------------ +#include +#include "mex.h" + +//-------------------------------------------------------------- +// function: mexFunction - Entry point from Matlab environment +// INPUTS: +// nlhs - number of left hand side arguments (outputs) +// plhs[] - pointer to table where created matrix pointers are +// to be placed +// nrhs - number of right hand side arguments (inputs) +// prhs[] - pointer to table of input matrices +//-------------------------------------------------------------- +void mexFunction( int nlhs, mxArray *plhs[], int nrhs, const mxArray +*prhs[] ) +{ + int M,N,K; // Sizes + int m,n,k; // Loop variables + double *pA, *pB, *pI, *pD, *pDD; + + + if (nrhs < 2) + mexErrMsgTxt("Not enough input arguments."); + if (nrhs > 2) + mexErrMsgTxt("Too many input arguments."); + if (!mxIsDouble(prhs[0]) || !mxIsDouble(prhs[1])) + mexErrMsgTxt("Arguments must be type double."); + if (mxGetN(prhs[0]) != mxGetN(prhs[1])) + mexErrMsgTxt("matrix dimensions (number of columns) must agree."); + if (mxIsComplex(prhs[0]) || mxIsComplex(prhs[1])) + mexWarnMsgTxt("Complex parts ignored."); + + // edist(MxK,NxK) => MxN + M = mxGetM(prhs[0]); // Number of rows in input A + N = mxGetM(prhs[1]); // Number of rows in input B + K = mxGetN(prhs[0]); // Number of dimensions (columns of A) + + + plhs[0] = mxCreateDoubleMatrix(M,1,mxREAL); + pI = mxGetPr(plhs[0]); + plhs[1] = mxCreateDoubleMatrix(M,1,mxREAL); + pD = mxGetPr(plhs[1]); + + if (nlhs >2) + { + plhs[2] = mxCreateDoubleMatrix(M,N,mxREAL); + pDD = mxGetPr(plhs[2]); + } + + // Now the distance computation + pA = mxGetPr(prhs[0]); + pB = mxGetPr(prhs[1]); + for (m=0; m 2) + pDD[m+M*n] = sum ; + if (n==0 || (sum < pD[m])) + { + pD[m]=sum; + pI[m]=n+1; // Matab indexing + } + } + } + +} // end mexFunction() diff --git a/nearest.dll b/nearest.dll new file mode 100644 index 0000000000000000000000000000000000000000..272607acc0184c1b7b9ed9b952e3db45c7f4b2fa GIT binary patch literal 12288 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 literal 0 HcmV?d00001 diff --git a/nestingmatrix.m b/nestingmatrix.m new file mode 100644 index 0000000..f11611a --- /dev/null +++ b/nestingmatrix.m @@ -0,0 +1,27 @@ +function M = nestingmatrix(nf,rp) +%NESTINGMATRIX - Compute nesting matrix for ANOVA +% [M] = nestingmatrix(nf,rp) +% A matrix M of 0's and 1's specifying the nesting +% relationships among the grouping variables. M(i,j) +% is 1 if variable i is nested in variable j. +% +% Example +% >> nestingmatrix +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2007 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2007-02-08 Creation +% +% ----------------------------- Script History --------------------------------- +M=zeros(nf+1); +M(rp,nf+1)=1; + + diff --git a/newdeal.m b/newdeal.m new file mode 100644 index 0000000..a544df5 --- /dev/null +++ b/newdeal.m @@ -0,0 +1,28 @@ +function varargout = newdeal(func,X,dim,varargin) +%NEWDEAL - Deal a given dimension of X as arguments to a function +% varargout = newdeal(func,X,dim,params) +% For 2-D matrix, default is to deal columns (ie. dim = 2) +% Otherwise (vectors, and N-D array, N>2) dim = first non singleton +% +% Example of use: +% PLOT3 wants coordinates as 3 separate arrays: XYZ(:,1) XYZ(:,2) XYZ(:,3) +% newdeal(@plot3,XYZ)) does it in one go + +if nargin<3 + dim = []; +end +if isempty(dim) + if ndims(X) == 2 && size(X,2)>1 + dim = 2; + else + [dim] = max([1 find(size(X)>1,1)]); + end +end +X = num2cell(X,setdiff(1:ndims(X),dim)); + +if nargout == 0 + feval(func,X{:},varargin{:}); +else + [varargout{1:nargout}] = feval(func,X{:},varargin{:}); +end +return diff --git a/noaccents.m b/noaccents.m new file mode 100644 index 0000000..19d01dd --- /dev/null +++ b/noaccents.m @@ -0,0 +1,63 @@ +function s = noaccents(s) +%NOACCENTS - Convert LATIN-1 string to ASCII (remove accents) +% [S] = noaccents(str) +% +% Example +% >> noaccents +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2008 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2008-10-08 Creation +% +% ----------------------------- Script History --------------------------------- +if iscell(s) + for i=1:numel(s) + s{i}=noaccents(s{i}); + end + return +end +if ~ischar(s) + return +end + +j=find(double(s)>127); + +LATIN1=[ + 'ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏ'... + 'ÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞß'... + 'àáâãäåæçèéêëìíîï'... + 'ðñòóôõö÷øùúûüýþÿ'... + 131 ... + 135 ... + 141 ... + 142 143 144 145 ... + 148 149 ... %î ï + 150 ... %ñ + 151 153 ... %?o ô + ]; + +ASCII=[ + 'AAAAAAACEEEEIIII'... + 'DNOOOOO*OUUUUY_S'... + 'aaaaaaaceeeeiiii'... + 'dnooooo:ouuuuy_y'... + 'E' ... + 'a' ... + 'c' ... + 'eeee' ... + 'ii' ... + 'n' ... + 'oo' ... + ]; + +[i,i]=ismember(s(j),LATIN1); +s(j(i==0))='_'; +s(j(i>0))=ASCII(i(i>0)); \ No newline at end of file diff --git a/nonempty.m b/nonempty.m new file mode 100644 index 0000000..8025412 --- /dev/null +++ b/nonempty.m @@ -0,0 +1,25 @@ +function [S,loc] = nonempty(S); +%nonempty - Non empty cells from a cell array +% nonempty(S) is a full column vector of the non-empty cells of S. +% This gives the s, but not the i and j, from [i,j,s] = find(isempty(S)). +% Warning! Even if S is a multidimensional cell array, nonempty() outputs +% a single column vector of cells. +% Example +% >> nonempty({ [] , 'a' , 3 , '' , {} , 'last' }) +% +% See also: NONZEROS + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2007 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% -------------------------- Script History ------------------------------- +% KND 2007-10-04 Creation +% +% -------------------------- Script History ------------------------------- +loc = find(~cellfun('isempty',S)); +S=S(loc); + diff --git a/nonnans.m b/nonnans.m new file mode 100644 index 0000000..9e0fd4c --- /dev/null +++ b/nonnans.m @@ -0,0 +1,45 @@ +function [S,loc,i] = nonnans(S,dim) +%nonnans - Non-NaN matrix elements. +% +% V=nonnans(S) is a full column vector of the non-nans elements of S. +% This gives the s, but not the i and j, from [i,j,s] = find(isnan(S)). +% Be aware that if S is a matrix, it outputs a single column vector. +% [V,L]=nonanas(S) also outputs indices of non-NaN values +% +% B = nonnans(A,dim) will return a matrix without the NaN's removing the +% whole rows/columns in each dimension specified in the vector [dim] +% +% Example +% >> histk +% +% See also: NONZEROS, NUMREP + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2006 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2006-02-07 Creation +% +% ----------------------------- Script History --------------------------------- +loc=find(~isnan(S)); +if nargin<2 + S=S(loc); +else + s=size(S); + i=[]; + i=find(isnan(S)); + e= ['[' sprintf('i(:,%d),',1:ndims(S)) ']=ind2sub(size(S),i);']; + eval(e) + i(:,size(i,2)+1:max(dim))=1; + for d=dim(:)' + e = ['S(' ... + repmat(':,',1,d-1) sprintf('i(:,%d)',d) repmat(',:',1,ndims(S)-d) ... + ')=[];']; + eval(e) + + end +end diff --git a/nonunique.m b/nonunique.m new file mode 100644 index 0000000..d9cb5dd --- /dev/null +++ b/nonunique.m @@ -0,0 +1,47 @@ +function [B,I] = nonunique(A,varargin) +%NONUNIQUE - Retrieves non-unique elements +% [B,I] = nonunique(A) +% [B,I] = nonunique(A, 'rows') +% +% Example +% >> nonunique +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2008 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2008-09-03 Creation +% +% ----------------------------- Script History --------------------------------- + +% Example data: +% A=flipud([0 0 1:3 3 3 4:10 13]') +% size(A), +% [b,i]=nonunique(A) +% i{:},A(cat(1,i{:})) + +[C,J,K] = unique(A,varargin{:}); +% Now find indices of nonunique elements +% First sort the indices listed in K +[k,i]=sort(K); +% 1) those where two successive location in K point to the same value of C +j=[(diff(k)==0)]; +% 2) you want only one item of each duplicate, keep the first of each +% series of repeated values +j = j & [1;[diff(diff(k))~=0]]; +% Retrieves the values they correspond to in A +B = C(K(i(j)),:); +if nargout<2 + return +end +% if requested by the user, output the indices of these duplicates +k=i(j); +for i=1:length(k) + I{i,1} = find(K==K(k(i))); +end diff --git a/normalize.m b/normalize.m new file mode 100644 index 0000000..82fb1c4 --- /dev/null +++ b/normalize.m @@ -0,0 +1,198 @@ +function [X,nX,cX] = normalize(X,P,D,varargin) +% normalize() - normalize a vector or a matrix +%[Y,nX,cX]=NORMALIZE(X,P,D) +% Vector-byvector normalization of data X +% X: vector or 2D matrix of data +% P: norm to be used, is a number [default: 2] of -inf or +inf or: +% - 'center'/'centered' set the mean to 0 +% - 'unity' range of data is linearly mapped to [0:1] +% (the signed maximum is set to 1; the signed minimum is set to 0) +% - 'rms' normalize by the rootmeansquare +% - 'z' z-transform +% - 'baseline' e.g. NORMALIZE(X,'baseline',1,[1:50]) to compute the +% baseline on the first 50 samples +% - 'baseline', dim, [samples], function, options... +% to compute baseline using a specific norm. E.g. +% normalize(X,'baseline',2,'median',[1:100]) normalize according to +% median of the 100 first samples +% +% See: norm() +% D: dimension(s) along which to normalize (default: first non singleton) +% Y: normalized data +% nX: value(s) of the norm(s) +% cX: centering value + +% if ndims(X)>2 +% error('Normalize apply only to vector or 2D-matrices') +% end +% +sX=[size(X)]; + +if nargin<3 + if max(sX)==prod(sX) + % it is a vector + [ignore, D]=max(size(X)); + else + % it is a matrix + D=1; + end +end +sX=[sX ones(1,1+length(D))]; +if nargin<2 + P=[]; +end +if isempty(P) + P=2; +end + +pX=[]; +if length(D)>1 + pX=[D setdiff(1:length(sX),D)]; + X=permute(X,pX); + X=reshape(X, prod(sX(D)), []); + D=1; +end + +[cX,nX] = normalize2(X,P,D,varargin{:}); + +nsX=size(X); +nsX(~ismember(1:length(sX),D))=1; +if ~isequal(cX,0) + if numel(cX)>1 + cX=repmat(cX,nsX); + end + X=X-cX; +end +if ~isequal(nX,1) + if numel(nX)>1 + nX=repmat(nX,nsX); + end + X=X./nX; +end +if ~isempty(pX) + X=reshape(X,sX(pX)); + X=ipermute(X,pX); +end + +return + +function [cX,nX] = normalize2(X,P,D,varargin) +sX=[size(X)]; +cX=0; +if isnumeric(P) + switch P + case inf + nX=max(abs(X),[],D); + case -inf + nX=min(abs(X),[],D); + otherwise + nX=sum(abs(X).^P,D).^(1/P); + end +else + if ischar(P) + P=lower(P); + switch P + case {'center', 'centered'} + cX=mean(X,D); + nX=.0*cX+1; + if nargin>3 + error('There should be no extra argument for norm ''%s''',P); + end + case 'rms' + nX=sqrt(mean(abs(X).^2,D)); + if nargin>3 + error('There should be no extra argument for norm ''%s''',P); + end + case 'unity' % rescale between 0 and 1 + cX=min(X,[],D); + nX=max(X,[],D)-cX; + if nargin>3 + error('There should be no extra argument for norm ''%s''',P); + end + case {'z', 'zscore', 'z-score'} + cX=mean(X,D); + nX=std(X,[],D); + if nargin>3 + error('There should be no extra argument for norm ''%s''',P); + end + case 'baseline' + if nargin<4 + error('# of samples for baseline not specified!'); + end + b = varargin{1}; + if isempty(b) + b=1:sX(D); + elseif numel(b)==1 && sX(D)>100 + warning('Baseline on one sample ??? If it''s what you want...'); + end + if nargin<5 % default baseline correction is centering on mean + [cX,nX] = normalize2(subarray(X,b,D),'center',D); + else + [cX,nX] = normalize2(subarray(X,b,D),varargin{2},D,varargin{3:end}); + end + case {'(x-a)/b'} + if nargin<5 + error('a and b factors required!'); + end + cX = varargin{1}; + nX = varargin{2}; + case {'ax+b'} + if nargin<5 + error('a and b factors required!'); + end + a = varargin{1}; + b = varargin{2}; + nX=1./a; + cX=-b./a; + case 'max' % rescale between 0 and 1 + cX=max(X,[],D); + nX=0.*cX+1; + if nargin>3 + error('There should be no extra argument for norm ''%s''',P); + end + case 'min' % rescale between 0 and 1 + cX=min(X,[],D); + nX=0.*cX+1; + if nargin>3 + error('There should be no extra argument for norm ''%s''',P); + end + + otherwise + if strcmp(P,'mean') + error('Didn''t you mean ''center'' instead of ''mean''?'); + else + error('Unknown norm: ''%s''', P); + end + end + else + % {'translation' 'scaling'} format + % e.f. ['mean' 'std'} for z-score + if length(P)<2 + error('Missing info in norm: {''translation'' ''scaling''}'); + end + if isnumeric(P{1}) + cX=X(1,:).*0+P{1}; + else + if nargin>3 + cX=feval(P{1},X,varargin{1}{1}{1:end}); + else + cX=feval(P{1},X); + end + end + if isnumeric(P{2}) + if numel(P{2})==1 + nX=cX.*0+P{2}; + else + nX=cX; + nX(:)=P{2}(:); + end + else + if nargin>3 && numel(varargin{1})>1 + nX=feval(P{2},X,varargin{1}{2}{1:end}); + else + nX=feval(P{2},X); + end + end + end +end +return \ No newline at end of file diff --git a/normalize.m~ b/normalize.m~ new file mode 100644 index 0000000..9894ea5 --- /dev/null +++ b/normalize.m~ @@ -0,0 +1,198 @@ +function [X,nX,cX] = normalize(X,P,D,varargin) +% normalize() - normalize a vector or a matrix +%[Y,nX,cX]=NORMALIZE(X,P,D) +% Vector-byvector normalization of data X +% X: vector or 2D matrix of data +% P: norm to be used, is a number [default: 2] of -inf or +inf or: +% - 'center'/'centered' set the mean to 0 +% - 'unity' range of data is linearly mapped to [0:1] +% (the signed maximum is set to 1; the signed minimum is set to 0) +% - 'rms' normalize by the rootmeansquare +% - 'z' z-transform +% - 'baseline' e.g. NORMALIZE(X,'baseline',1,[1:50]) to compute the +% baseline on the first 50 samples +% - 'baseline', dim, [samples], function, options... +% to compute baseline using a specific norm. E.g. +% normalize(X,'baseline',2,'median',[1:100]) normalize according to +% median of the 100 first samples +% +% See: norm() +% D: dimension(s) along which to normalize (default: first non singleton) +% Y: normalized data +% nX: value(s) of the norm(s) +% cX: centering value + +% if ndims(X)>2 +% error('Normalize apply only to vector or 2D-matrices') +% end +% +sX=[size(X)]; + +if nargin<3 + if max(sX)==prod(sX) + % it is a vector + [ignore, D]=max(size(X)); + else + % it is a matrix + D=1; + end +end +sX=[sX ones(1,1+length(D))]; +if nargin<2 + P=[]; +end +if isempty(P) + P=2; +end + +pX=[]; +if length(D)>1 + pX=[D setdiff(1:length(sX),D)]; + X=permute(X,pX); + X=reshape(X, prod(sX(D)), []); + D=1; +end + +[cX,nX] = normalize2(X,P,D,varargin{:}); + +nsX=size(X); +nsX(~ismember(1:length(sX),D))=1; +if ~isequal(cX,0) + if numel(cX)>1 + cX=repmat(cX,nsX); + end + X=X-cX; +end +if ~isequal(nX,1) + if numel(nX)>1 + nX=repmat(nX,nsX); + end + X=X./nX; +end +if ~isempty(pX) + X=reshape(X,sX(pX)); + X=ipermute(X,pX); +end + +return + +function [cX,nX] = normalize2(X,P,D,varargin) +sX=[size(X)]; +cX=0; +if isnumeric(P) + switch P + case inf + nX=max(abs(X),[],D); + case -inf + nX=min(abs(X),[],D); + otherwise + nX=sum(abs(X).^P,D).^(1/P); + end +else + if ischar(P) + P=lower(P); + switch P + case {'center', 'centered', 'mean'} + cX=mean(X,D); + nX=.0*cX+1; + if nargin>3 + error('There should be no extra argument for norm ''%s''',P); + end + case 'rms' + nX=sqrt(mean(abs(X).^2,D)); + if nargin>3 + error('There should be no extra argument for norm ''%s''',P); + end + case 'unity' % rescale between 0 and 1 + cX=min(X,[],D); + nX=max(X,[],D)-cX; + if nargin>3 + error('There should be no extra argument for norm ''%s''',P); + end + case {'z', 'zscore', 'z-score'} + cX=mean(X,D); + nX=std(X,[],D); + if nargin>3 + error('There should be no extra argument for norm ''%s''',P); + end + case 'baseline' + if nargin<4 + error('# of samples for baseline not specified!'); + end + b = varargin{1}; + if isempty(b) + b=1:sX(D); + elseif numel(b)==1 && sX(D)>100 + warning('Baseline on one sample ??? If it''s what you want...'); + end + if nargin<5 % default baseline correction is centering on mean + [cX,nX] = normalize2(subarray(X,b,D),'center',D); + else + [cX,nX] = normalize2(subarray(X,b,D),varargin{2},D,varargin{3:end}); + end + case {'(x-a)/b'} + if nargin<5 + error('a and b factors required!'); + end + cX = varargin{1}; + nX = varargin{2}; + case {'ax+b'} + if nargin<5 + error('a and b factors required!'); + end + a = varargin{1}; + b = varargin{2}; + nX=1./a; + cX=-b./a; + case 'max' % rescale between 0 and 1 + cX=max(X,[],D); + nX=0.*cX+1; + if nargin>3 + error('There should be no extra argument for norm ''%s''',P); + end + case 'min' % rescale between 0 and 1 + cX=min(X,[],D); + nX=0.*cX+1; + if nargin>3 + error('There should be no extra argument for norm ''%s''',P); + end + + otherwise + if strcmp(P,'mean') + error('Didn''t you mean ''center'' instead of ''mean''?'); + else + error('Unknown norm: ''%s''', P); + end + end + else + % {'translation' 'scaling'} format + % e.f. ['mean' 'std'} for z-score + if length(P)<2 + error('Missing info in norm: {''translation'' ''scaling''}'); + end + if isnumeric(P{1}) + cX=X(1,:).*0+P{1}; + else + if nargin>3 + cX=feval(P{1},X,varargin{1}{1}{1:end}); + else + cX=feval(P{1},X); + end + end + if isnumeric(P{2}) + if numel(P{2})==1 + nX=cX.*0+P{2}; + else + nX=cX; + nX(:)=P{2}(:); + end + else + if nargin>3 && numel(varargin{1})>1 + nX=feval(P{2},X,varargin{1}{2}{1:end}); + else + nX=feval(P{2},X); + end + end + end +end +return \ No newline at end of file diff --git a/normalsOutFaces.m b/normalsOutFaces.m new file mode 100644 index 0000000..916b16e --- /dev/null +++ b/normalsOutFaces.m @@ -0,0 +1,35 @@ +function [normals, centroid]=normalsOutFaces(vertices, faces) +% [normals, centroid]=normalsfaces(vertices, faces) +% Normals of the (triangular) faces defined by vertices and faces % + +v=vertices(faces,:); +v=reshape(v, [size(faces, 1) 3 3]); +v1=squeeze(v(:,1,:)); +v2=squeeze(v(:,2,:)); +v3=squeeze(v(:,3,:)); + +% Position of the barycentre of each triangle +centroid = squeeze(mean(v,2)); + +% The normals to the surface of the triangle +normals=cross(v2-v1, v3-v1,2); +%surfaces = rownorm(normals)/2; % area of each triangle + +% is the orientation of the triangle in or out? +inout = sign(sum(centroid .* normals ,2)); +% Make the normals out going +normals=normals.*repmat(inout,1,3); +return + +if 0 +figure(1) +clf +plot3(vertices(faces(1,[1 2 3 1]),1),vertices(faces(1,[1 2 3 1]),2), vertices(faces(1,[1 2 3 1]),3)) +hold on +v=[centroid(1,:) ; centroid(1,:)]; +plot3(v(:,1), v(:,2), v(:,3), 'rx') +v=[centroid(1,:) ; centroid(1,:) + normals(1,:)]; +plot3(v(:,1), v(:,2), v(:,3), 'g') +grid on +axis equal +end \ No newline at end of file diff --git a/normalsOutVertices.m b/normalsOutVertices.m new file mode 100644 index 0000000..83c5d02 --- /dev/null +++ b/normalsOutVertices.m @@ -0,0 +1,40 @@ +function [normals, centroid]=normalsOutVertices(vertices, faces) +% [normals, centroid]=normalsOutVertices(vertices, faces) +% Normals going out of the patch, at each vertex +% + + normals=normalsvertices(vertices, faces); + + +% is the orientation of the triangle in or out? +inout = sign(sum(vertices .* normals ,2)); +% Make the normals out going +normals=normals.*repmat(inout,1,3); +return + +%v=vertices(faces,:); +%v=reshape(v, [size(faces, 1) 3 3]); +%v1=squeeze(v(:,1,:)); +%v2=squeeze(v(:,2,:)); +%v3=squeeze(v(:,3,:)); + +% Position of the barycentre of each triangle +% centroid = squeeze(mean(v,2)); + +% The normals to the surface of the triangle +% normals=cross(v2-v1, v3-v1,2); +%surfaces = rownorm(normals)/2; % area of each triangle + + +if 0 +figure(1) +clf +plot3(vertices(faces(1,[1 2 3 1]),1),vertices(faces(1,[1 2 3 1]),2), vertices(faces(1,[1 2 3 1]),3)) +hold on +v=[centroid(1,:) ; centroid(1,:)]; +plot3(v(:,1), v(:,2), v(:,3), 'rx') +v=[centroid(1,:) ; centroid(1,:) + normals(1,:)]; +plot3(v(:,1), v(:,2), v(:,3), 'g') +grid on +axis equal +end \ No newline at end of file diff --git a/normalsfaces.m b/normalsfaces.m new file mode 100644 index 0000000..4f8494c --- /dev/null +++ b/normalsfaces.m @@ -0,0 +1,35 @@ +function [normals, centroid]=normalsfaces(vertices, faces) +% [normals, centroid]=normalsfaces(vertices, faces) +% Normals of the (triangular) faces defined by vertices and faces % + +v=vertices(faces,:); +v=reshape(v, [size(faces, 1) 3 3]); +v1=squeeze(v(:,1,:)); +v2=squeeze(v(:,2,:)); +v3=squeeze(v(:,3,:)); + +% Position of the barycentre of each triangle +centroid = squeeze(mean(v,2)); + +% The normals to the surface of the triangle +normals=cross(v2-v1, v3-v1,2); +%surfaces = rownorm(normals)/2; % area of each triangle + +% is the orientation of the triangle in or out? +inout = sign(sum(centroid .* normals ,2)); +% Make the normals out going +% normals=normals.*repmat(inout,1,3); +return + +if 0 +figure(1) +clf +plot3(vertices(faces(1,[1 2 3 1]),1),vertices(faces(1,[1 2 3 1]),2), vertices(faces(1,[1 2 3 1]),3)) +hold on +v=[centroid(1,:) ; centroid(1,:)]; +plot3(v(:,1), v(:,2), v(:,3), 'rx') +v=[centroid(1,:) ; centroid(1,:) + normals(1,:)]; +plot3(v(:,1), v(:,2), v(:,3), 'g') +grid on +axis equal +end \ No newline at end of file diff --git a/normalspatch.m b/normalspatch.m new file mode 100644 index 0000000..2c0665d --- /dev/null +++ b/normalspatch.m @@ -0,0 +1,11 @@ +function [normals]=normalspatch(vertices, faces) +% Normals at the vertices od the (triangular) faces defined by vertices and faces + +h=patch('vertices',vertices, 'faces', faces); +normals=get(h, 'VertexNormals'); +delete(h); +%normals=normals./repmat(rownorm(normals),1,3); +%inout=-sign(sum(vertices .* normals ,2)); +%normals=repmat(inout, 1,3).*normals; +return + diff --git a/normalsvertices.m b/normalsvertices.m new file mode 100644 index 0000000..28e69e7 --- /dev/null +++ b/normalsvertices.m @@ -0,0 +1,11 @@ +function [normals]=normalsvertices(vertices, faces) +% Normals at the vertices od the (triangular) faces defined by vertices and faces + +h=patch('vertices',vertices, 'faces', faces); +normals=get(h, 'VertexNormals'); +delete(h); +%normals=normals./repmat(rownorm(normals),1,3); +%inout=-sign(sum(vertices .* normals ,2)); +%normals=repmat(inout, 1,3).*normals; +return + diff --git a/normalz.m b/normalz.m new file mode 100644 index 0000000..379f852 --- /dev/null +++ b/normalz.m @@ -0,0 +1,7 @@ +function z = normalz(p, mu, sigma) +% NORMALZ Normal z-score +% >> z = normalz(p, [mu], [sigma]) +if nargin < 3, sigma = 1; end +if nargin < 2, mu = 0; end +if nargin < 1, error('Not enough input parameters.'); end +z = mu+sqrt(2)*sigma*erfinv(2*p-1); diff --git a/norminv.m b/norminv.m new file mode 100644 index 0000000..48309f3 --- /dev/null +++ b/norminv.m @@ -0,0 +1,28 @@ +function z = norminv(p,mu,sigma) +%NORMINV - Inverse Normal Law +% [z] = norminv(p,mu,sigma) +% +% Example +% >> norminv() +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2006 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2006-06-01 Creation +% +% ----------------------------- Script History --------------------------------- +if nargin < 3 + sigma = 1; + if nargin < 2 + mu = 0; + end +end +z=-sqrt(2)*sigma.*erfcinv(2*p) + mu; +%z=-sqrt(2)*erfcinv(2*p); diff --git a/normminmax.m b/normminmax.m new file mode 100644 index 0000000..8a11d49 --- /dev/null +++ b/normminmax.m @@ -0,0 +1,20 @@ +function [X,mn,mx]=normminmax(X,dims) +% normminmax() - normalize a vector or a matrix to the [0 1] range +%[Y,nY]=NORMMINMAX(X) does a column-by-column normalization of data X +% Y: normalized data +if nargin<2 | isempty(dims) + dims=1; +end +if ~isequal(dims,1) + sX=[ size(X) 1 ]; + npX = setdiff(1:ndims(X), dims); + pX=[dims npX]; + X=permute(X, pX); + X=reshape(X, [ prod(sX(dims)) sX(npX) 1] ); +end +X=X-repmat(min(X), [size(X,1) 1]); +X=X./repmat(max(X), [size(X,1) 1]); +if ~isequal(dims,1) + X=reshape(X, [ sX(dims) sX(npX) 1] ); + X=ipermute(X, pX); +end \ No newline at end of file diff --git a/nth.m b/nth.m new file mode 100644 index 0000000..a16ae42 --- /dev/null +++ b/nth.m @@ -0,0 +1,16 @@ +function [x]=nth(a,varargin) +%Nth : N-th element in an array +% +% [x]=nth(a,varargin) +% +% Useful for 'pipeing' i.e. input-output redirection. +% e.g. fun1() returns an array, but you want fun2() to use the k-th value +% returned by fun1() : disp(fun2(nth(fun1(x), k))) +if nargin<2 + error(('no')) +end +if all(cellfun('isreal',varargin)) + x=a(varargin{:}); +else + x=eval(['a(' varargin{:} ')']); +end \ No newline at end of file diff --git a/numrep.m b/numrep.m new file mode 100644 index 0000000..3c8d6bb --- /dev/null +++ b/numrep.m @@ -0,0 +1,45 @@ +function Y = numrep(X,varargin) +%numrep() - Replace numerical value with another +% [Y] = numrep(X,p,q) replaces p's in X with q's +% Works with p=NaN (as well as p=+Inf, and p=-Inf) +% p may be an array so that all of these values will be replaced by q +% [Y] = numrep(X,[p1 p2],q1 , p3,q3) works also +% +% See also: strrep + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2005 +% This program is free software; you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation; either version 2 of the License, or +% (at your option) any later version. +% +% ----------------------------- Script History --------------------------------- +% KND 2005-12-12 Creation +% KND 2006-10-23 Added possibility to replace multiple p values at once +% KND 2009-10-22 Logical array are converted to double if needed (+warning) +% ----------------------------- Script History --------------------------------- + +Y=X; +p=varargin(1:2:end); +q=varargin(2:2:end); +for i=1:length(p) + while ~isempty(p{i}) + if isnan(p{i}(1)) + Y(isnan(X))=q{i}; + else + try + Y(X==p{i}(1))=q{i}; + catch ME + if isequal(ME.identifier, 'MATLAB:nologicalnan') + warning('numrep:NAnintoLogical','numrep:Logical array is converted to double to incoporate NaN''s.'); + Y=double(Y); + Y(X==p{i}(1))=q{i}; + else + rethrow(ME) + end + end + end + p{i}(1)=[]; + end +end \ No newline at end of file diff --git a/octave/fminunc.m b/octave/fminunc.m new file mode 100644 index 0000000..79d09ba --- /dev/null +++ b/octave/fminunc.m @@ -0,0 +1,426 @@ +% [x,v,flag,out,df,d2f] = fminunc (f,x,opt,...) - M*tlab-like optimization +% +% Imitation of m*tlab's fminunc(). The optional 'opt' argument is a struct, +% e.g. produced by 'optimset()'. +% +% Supported options +% ----------------- +% Diagnostics, [off|on] : Be verbose +% Display , [off|iter|notify|final] +% : Be verbose unless value is "off" +% GradObj , [off|on] : Function's 2nd return value is derivatives +% Hessian , [off|on] : Function's 2nd and 3rd return value are +% derivatives and Hessian. +% TolFun , scalar : Termination criterion (see 'ftol' in minimize()) +% TolX , scalar : Termination criterion (see 'utol' in minimize()) +% MaxFunEvals, int : Max. number of function evaluations +% MaxIter , int : Max. number of algorithm iterations +% +% These non-m*tlab are provided to facilitate porting code to octave: +% ----------------------- +% "MinEquiv" , [off|on] : Don't minimize 'fun', but instead return the +% option passed to minimize(). +% +% "Backend" , [off|on] : Don't minimize 'fun', but instead return +% [backend, opt], the name of the backend +% optimization function that is used and the +% optional arguments that will be passed to it. See +% the 'backend' option of minimize(). +% +% This function is a front-end to minimize(). + + +% Copyright (C) 2008, 2009 VZLU Prague, a.s. +% +% This file is part of Octave. +% +% Octave is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by +% the Free Software Foundation; either version 3 of the License, or (at +% your option) any later version. +% +% Octave is distributed in the hope that it will be useful, but +% WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +% General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with Octave; see the file COPYING. If not, see +% . +% +% Author: Jaroslav Hajek + +% -*- texinfo -*- +% @deftypefn{Function File} {} fminunc (@var{fcn}, @var{x0}, @var{options}) +% @deftypefnx{Function File} {[@var{x}, @var{fvec}, @var{info}, @var{output}, @var{grad}, @var{hess}]} = fminunc (@var{fcn}, @dots{}) +% Solve a unconstrained optimization problem defined by the function @var{fcn}. +% @var{fcn} should accepts a vector (array) defining the unknown variables, +% and return the objective function value, optionally with gradient. +% In other words, this function attempts to determine a vector @var{x} such +% +% that @code{@var{fcn} (@var{x})} is a local minimum. +% @var{x0} determines a starting guess. The shape of @var{x0} is preserved +% in all calls to @var{fcn}, but otherwise it is treated as a column vector. +% @var{options} is a structure specifying additional options. +% Currently, @code{fminunc} recognizes these options: +% @code{'FunValCheck'}, @code{'OutputFcn'}, @code{'TolX'}, +% @code{'TolFun'}, @code{'MaxIter'}, @code{'MaxFunEvals'}, +% @code{'GradObj'}, @code{'FinDiffType'}. +% +% If @code{'GradObj'} is @code{'on'}, it specifies that @var{fcn}, +% called with 2 output arguments, also returns the Jacobian matrix +% of right-hand sides at the requested point. @code{'TolX'} specifies +% the termination tolerance in the unknown variables, while +% @code{'TolFun'} is a tolerance for equations. Default is @code{1e-7} +% for both @code{'TolX'} and @code{'TolFun'}. +% +% For description of the other options, see @code{optimset}. +% +% On return, @var{fval} contains the value of the function @var{fcn} +% evaluated at @var{x}, and @var{info} may be one of the following values: +% +% @table @asis +% @item 1 +% Converged to a solution point. Relative gradient error is less than specified +% by TolFun. +% @item 2 +% Last relative step size was less that TolX. +% @item 3 +% Last relative decrease in func value was less than TolF. +% @item 0 +% Iteration limit exceeded. +% @item -3 +% The trust region radius became excessively small. +% @end table +% +% Optionally, fminunc can also yield a structure with convergence statistics +% (@var{output}), the output gradient (@var{grad}) and approximate hessian +% (@var{hess}). +% +% Note: If you only have a single nonlinear equation of one variable, using +% @code{fminbnd} is usually a much better idea. +% @seealso{fminbnd, optimset} +% @end deftypefn + +% PKG_ADD: all_opts ('fminunc'); + +function [x, fval, info, output, grad, hess] = fminunc (fcn, x0, options) +if nargin<3 + options = struct (); +end +% Get default options if requested. +if (nargin == 1 && ischar (fcn) && strcmp (fcn, 'defaults')) + x = optimset ('MaxIter', 400, 'MaxFunEvals', Inf, ... + 'GradObj', 'off', 'TolX', 1.5e-8, 'TolFun', 1.5e-8, ... + 'OutputFcn', [], 'FunValCheck', 'off', ... + 'FinDiffType', 'central'); + return; +end + +if (nargin < 2 || nargin > 3 || ~ ismatrix (x0)) + print_usage (); +end + +if (ischar (fcn)) + fcn = str2func (fcn, 'global'); +end + +xsiz = size (x0); +n = numel (x0); + +has_grad = strcmpi (optimget (options, 'GradObj', 'off'), 'on'); +cdif = strcmpi (optimget (options, 'FinDiffType', 'central'), 'central'); +maxiter = optimget (options, 'MaxIter', 400); +maxfev = optimget (options, 'MaxFunEvals', Inf); +outfcn = optimget (options, 'OutputFcn'); + +funvalchk = strcmpi (optimget (options, 'FunValCheck', 'off'), 'on'); + +if (funvalchk) + % Replace fcn with a guarded version. + fcn = @(x) guarded_eval (fcn, x); +end + +% These defaults are rather stringent. I think that normally, user +% prefers accuracy to performance. + +macheps = eps (class (x0)); + +tolx = optimget (options, 'TolX', sqrt (macheps)); +tolf = optimget (options, 'TolFun', sqrt (macheps)); + +factor = 0.1; +% FIXME: TypicalX corresponds to user scaling (???) +autodg = true; + +niter = 1; +nfev = 0; + +x = x0(:); +info = 0; + +% Initial evaluation. +fval = fcn (reshape (x, xsiz)); +n = length (x); + +if (~isempty (outfcn)) + optimvalues.iter = niter; + optimvalues.funccount = nfev; + optimvalues.fval = fval; + optimvalues.searchdirection = zeros (n, 1); + state = 'init'; + stop = outfcn (x, optimvalues, state); + if (stop) + info = -1; + break; + end +end + +nsuciter = 0; +lastratio = 0; + +grad = []; + +% Outer loop. +while (niter < maxiter && nfev < maxfev && ~ info) + + grad0 = grad; + + % Calculate function value and gradient (possibly via FD). + if (has_grad) + [fval, grad] = fcn (reshape (x, xsiz)); + grad = grad(:); + nfev =nfev + 1; + else + grad = fdjac (fcn, reshape (x, xsiz), fval, cdif)(:); + nfev = nfev + (1 + cdif) * length (x); + end + + if (niter == 1) + % Initialize by identity matrix. + hesr = eye (n); + else + % Use the damped BFGS formula. + y = grad - grad0; + sBs = sumsq (w); + Bs = hesr'*w; + sy = y'*s; + theta = 0.8 / max (1 - sy / sBs, 0.8); + r = theta * y + (1-theta) * Bs; + hesr = cholupdate (hesr, r / sqrt (s'*r), '+'); + [hesr, info] = cholupdate (hesr, Bs / sqrt (sBs), '-'); + if (info) + hesr = eye (n); + end + end + + % Second derivatives approximate the hessian. + d2f = norm (hesr, 'columns').'; + if (niter == 1) + dg = d2f; + xn = norm (dg .* x); + % FIXME: something better? + delta = factor * max (xn, 1); + end + + % FIXME: maybe fixed lower and upper bounds? + dg = max (0.1*dg, d2f); + + % FIXME -- why tolf*n*xn? If abs (e) ~ abs(x) * eps is a vector + % of perturbations of x, then norm (hesr*e) <= eps*xn, i.e. by + % tolf ~ eps we demand as much accuracy as we can expect. + if (norm (grad) <= tolf*n*xn) + info = 1; + break; + end + + suc = false; + decfac = 0.5; + + % Inner loop. + while (~ suc && niter <= maxiter && nfev < maxfev && ~ info) + + s = - doglegm (hesr, grad, dg, delta); + + sn = norm (dg .* s); + if (niter == 1) + delta = min (delta, sn); + end + + fval1 = fcn (reshape (x + s, xsiz)) (:); + nfev =nfev+1; + + if (fval1 < fval) + % Scaled actual reduction. + actred = (fval - fval1) / (abs (fval1) + abs (fval)); + else + actred = -1; + end + + w = hesr*s; + % Scaled predicted reduction, and ratio. + t = 1/2 * sumsq (w) + grad'*s; + if (t < 0) + prered = -t/(abs (fval) + abs (fval + t)); + ratio = actred / prered; + else + prered = 0; + ratio = 0; + end + + % Update delta. + if (ratio < min(max(0.1, 0.8*lastratio), 0.9)) + delta *= decfac; + decfac ^= 1.4142; + if (delta <= 1e1*macheps*xn) + % Trust region became uselessly small. + info = -3; + break; + end + else + lastratio = ratio; + decfac = 0.5; + if (abs (1-ratio) <= 0.1) + delta = 1.4142*sn; + elseif (ratio >= 0.5) + delta = max (delta, 1.4142*sn); + end + end + + if (ratio >= 1e-4) + % Successful iteration. + x = x+s; + xn = norm (dg .* x); + fval = fval1; + nsuciter =nsuciter +1; + suc = true; + end + + niter =niter +1; + + % FIXME: should outputfcn be only called after a successful iteration? + if (~ isempty (outfcn)) + optimvalues.iter = niter; + optimvalues.funccount = nfev; + optimvalues.fval = fval; + optimvalues.searchdirection = s; + state = 'iter'; + stop = outfcn (x, optimvalues, state); + if (stop) + info = -1; + break; + end + end + + % Tests for termination conditions. A mysterious place, anything + % can happen if you change something here... + + % The rule of thumb (which I'm not sure M*b is quite following) + % is that for a tolerance that depends on scaling, only 0 makes + % sense as a default value. But 0 usually means uselessly long + % iterations, so we need scaling-independent tolerances wherever + % possible. + + % The following tests done only after successful step. + if (ratio >= 1e-4) + % This one is classic. Note that we use scaled variables again, + % but compare to scaled step, so nothing bad. + if (sn <= tolx*xn) + info = 2; + % Again a classic one. + elseif (actred < tolf) + info = 3; + end + end + + end +end + +% Restore original shapes. +x = reshape (x, xsiz); + +output.iterations = niter; +output.successful = nsuciter; +output.funcCount = nfev; + +if (nargout > 5) + hess = hesr'*hesr; +end + +return + +% An assistant function that evaluates a function handle and checks for +% bad results. +function [fx, gx] = guarded_eval (fun, x) +if (nargout > 1) + [fx, gx] = fun (x); +else + fx = fun (x); + gx = []; +end + +if (~ (isreal (fx) && isreal (jx))) + error ('fminunc:notreal', 'fminunc: non-real value encountered'); +elseif (complexeqn && ~ (isnumeric (fx) && isnumeric(jx))) + error ('fminunc:notnum', 'fminunc: non-numeric value encountered'); +elseif (any (isnan (fx(:)))) + error ('fminunc:isnan', 'fminunc: NaN value encountered'); +end +return + +%~function f = rosenb (x) +%~ n = length (x); +%~ f = sumsq (1 - x(1:n-1)) + 100 * sumsq (x(2:n) - x(1:n-1).^2); +%~test +%~ [x, fval, info, out] = fminunc (@rosenb, [5, -5]); +%~ tol = 2e-5; +%~ assert (info > 0); +%~ assert (x, ones (1, 2), tol); +%~ assert (fval, 0, tol); +%~test +%~ [x, fval, info, out] = fminunc (@rosenb, zeros (1, 4)); +%~ tol = 2e-5; +%~ assert (info > 0); +%~ assert (x, ones (1, 4), tol); +%~ assert (fval, 0, tol); + +% Solve the double dogleg trust-region minimization problem: +% Minimize 1/2*norm(r*x)^2 subject to the constraint norm(d.*x) <= delta, +% x being a convex combination of the gauss-newton and scaled gradient. + +% TODO: error checks +% TODO: handle singularity, or leave it up to mldivide? + +function x = doglegm (r, g, d, delta) +% Get Gauss-Newton direction. +b = r' \ g; +x = r \ b; +xn = norm (d .* x); +if (xn > delta) + % GN is too big, get scaled gradient. + s = g ./ d; + sn = norm (s); + if (sn > 0) + % Normalize and rescale. + s = (s / sn) ./ d; + % Get the line minimizer in s direction. + tn = norm (r*s); + snm = (sn / tn) / tn; + if (snm < delta) + % Get the dogleg path minimizer. + bn = norm (b); + dxn = delta/xn; snmd = snm/delta; + t = (bn/sn) * (bn/xn) * snmd; + t -= dxn * snmd^2 - sqrt ((t-dxn)^2 + (1-dxn^2)*(1-snmd^2)); + alpha = dxn*(1-snmd^2) / t; + else + alpha = 0; + end + else + alpha = delta / xn; + snm = 0; + end + % Form the appropriate convex combination. + x = alpha * x + ((1-alpha) * min (snm, delta)) * s; +end + diff --git a/oddsratio.m b/oddsratio.m new file mode 100644 index 0000000..ef94540 --- /dev/null +++ b/oddsratio.m @@ -0,0 +1,22 @@ +function r = oddsratio(p,q) +%ODDSRATIO - Computes the odds ratio +% [r] = oddsratio(p,q) +% +% Example +% >> oddsratio( 1/23 , 5/100 ) +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2008 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2008-04-30 Creation +% +% ----------------------------- Script History --------------------------------- + +r=p.*(1-q)./q./(1-p); diff --git a/openpdf.m b/openpdf.m new file mode 100644 index 0000000..f241773 --- /dev/null +++ b/openpdf.m @@ -0,0 +1,2 @@ +function [] = openpdf(f) +dir('c:\program Files\Adobe\Acrobat *') \ No newline at end of file diff --git a/overlap.m b/overlap.m new file mode 100644 index 0000000..e1ebee4 --- /dev/null +++ b/overlap.m @@ -0,0 +1,44 @@ +function [x]=overlap(S,s2) +% overlap - return the overlap between two segments +% +% [x]=overlap(s1,s2) +% Computes the overlap x=[a b] between the two (closed) segments. +% s1 and s2 MUST be real numbers. +% +% [x]=overlap(S) +% If S is a N-by-2 matrix, computes the global overlap of these N +% segments. +% +% x may be empty if s1 and s2 have no overlap, equals to [a a] if their +% overlap is a single point, or equals to the smaller of s1 and s2 if one +% includes the other. +% +%INPUTS: +% s1=[a1 b1], s2=[a2 b2] are two numerical segment (of real numbers) +% +if nargin<2 + s2=[]; +end +if any(~isreal([S(:) ; s2(:)])) + error('s1 and s2 must be real numbers') +end +if nargin>1 & isempty(s2) + x=[]; + return +end +if isempty(S) + x=[]; + return +end +S=[S;s2]; + +if size(S,1)>2 + x=overlap(S(1,:), overlap(S(2:end,:))); +else + if S(1)>S(4) | S(3)2 % VARARGIN can be used to pass standard arguments to DEPFUN + % Concatentate the VARARGIN into a string that can be used with DEPFUN + input=[]; + for n=1:length(varargin) + input=[input,'''',varargin{n},'''']; + if n~=length(varargin), input=[input,',']; end + end +else % Default input argument to DEPFUN +% input=['''','-quiet','''',',','''','-toponly','''']; % This restricts tracing to first level + input=['''','-quiet','''']; +end + +try +T=eval(['depfun(fname,',input,');']); +catch + T=depfun(fname); +end + +G=[]; m=1; +for k=1:length(T) + f=strfind(T{k},keyword); + if ~isempty(f), + G{m}=T{k}; + m=m+1; + end +end + +G=G'; + +return + + +% % DEPFUN2 finds the M-file dependencies of an m-file whos +% % path also contains a keyword. It relies upon the use of the +% % DEPFUN function. DEPFUN2 accepts a keyword to 'grep' for allowing +% % the return of m-files that are only found on a specific path (a users +% % path for example). The keyword (or part of a word) is used to find +% % matches in the full pathname of all files that the m-file depends on. +% % +% % SYNTAX: G=depfun2(fname,keyword,varargin); +% % +% % fname - The name of the m-file for which to determine dependencies +% % keyword - The keyword used to retain m-files whose pathname contains 'keyword' +% % DEFAULT keyword is unix('whoami') +% % varargin - Any of the various arguments that DEPFUN usually accepts. +% % +% % Example: G=depfun2('bench','graphics','-quiet','-toponly'); % Returns FEWER results than... +% % G=depfun2('bench','graphics','-quiet'); % Because this includes indirectly called m-files +% % +% % DBE 02/07/03 +% % DBE 04/10/26 Modified to support full range of DEPFUN inputs. +% % +% % To Do: Figure out a good way to include functions that sub-functions +% % depend upon etc. +% +% function G=depfun2(fname,keyword,varargin); +% if nargin <2 & isunix +% [status,keyword]=unix('whoami'); +% keyword=deblank(keyword); +% if status +% error('UNIX WHOAMI call failed.'); +% end +% elseif nargin<2 & ~isunix +% error('PACKAGE.M requires two user inputs on the PC platform'); +% elseif nargin==2 +% else +% error('depfun2.M requires two user inputs'); +% end +% +% if nargin>2 % Concatentate the VARARGIN into a string that can be used with DEPFUN +% input=[]; +% for n=1:length(varargin) +% input=[input,'''',varargin{n},'''']; +% if n~=length(varargin), input=[input,',']; end +% end +% else % Default input string to DEPFUN +% input=['''','-quiet','''',',','''','-toponly','''']; +% % input=['''','-quiet',''''] +% end +% +% T=eval(['depfun(fname,',input,');']); +% +% G=[]; m=1; +% for k=1:length(T) +% f=strfind(T{k},keyword); +% if ~isempty(f), +% G{m}=T{k}; +% m=m+1; +% end +% end +% +% G=G'; +% +% return + diff --git a/paren_parse.m b/paren_parse.m new file mode 100644 index 0000000..dd9edc2 --- /dev/null +++ b/paren_parse.m @@ -0,0 +1,2 @@ +function [tree]=paren_parse(txt) +error('paren_parse parse no more! See paren_parser') \ No newline at end of file diff --git a/paren_parser.m b/paren_parser.m new file mode 100644 index 0000000..1b5b686 --- /dev/null +++ b/paren_parser.m @@ -0,0 +1,28 @@ +function [paren_tree]=paren_parser(txt) +% Parsing an expression with parentheses +% [paren_tree]=paren_parser(txt) + +paren_op={'(' ')'; '[' ']'}; +opens=findstr(paren_op{1,1}, txt); %opening paren +closes=findstr(paren_op{1,2}, txt); %closing paren + +error('not working yet!') + +if not(isempty(opens)) + pairs=[]; + open = 1; + while open opens(nbopen) + nbopen=nbopen+1; + end + open=open+1; + end + pairs=[pairs idx_op{i,1}] + nbpar=nbpar+1; +end +return +end +paren_tree=struct('op', '', 'e', {txt}); diff --git a/pascualmarqui.m b/pascualmarqui.m new file mode 100644 index 0000000..17eb547 --- /dev/null +++ b/pascualmarqui.m @@ -0,0 +1,41 @@ +function L = loreta_laplacian(varargin) +%loreta_laplacian - Laplacian operator for a mesh according to Pascual-Marqui +% [L] = loreta_laplacian(D) +% Computes LORETA's laplacian L for a distance matrix (D) of a mesh +% +% [L] = loreta_laplacian(vertices,faces) +% Computes laplacian L for a mesh defined by its vertices and faces +% +% [L] = loreta_laplacian(vertices,vertconn) +% Computes laplacian L for a mesh where vertex connectivity cell list has +% already been computed using: vertices_connectivity +% +% See also: bst_laplacian, mesh_laplacian + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2006 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2006-02-03 Creation (thanks to Jean Daunizeau scripts) +% +% ----------------------------- Script History --------------------------------- +if nargin>1 + if ~iscell(varargin{2}) + vertices_distances + else + D=vertices_distances(vararagin{1},vararagin{2},1); + end +else + D=varargin{1}; +end +L=-bst_smooth_fun([],D,1,0,'1./r'); +nv=size(L,1); +L(1:(nv+1):end)=0; +L(1:(nv+1):end)=-mean(sum(L,2)); + +return + diff --git a/patch_swell.m b/patch_swell.m new file mode 100644 index 0000000..e6702e4 --- /dev/null +++ b/patch_swell.m @@ -0,0 +1,55 @@ +function newverts = patch_swell(iverts, vconn) +% PATCH_SWELL: Enlarge a patch by appending the next set of adjacent vertices. +% +% USAGE: newverts = patch_swell(iverts, vconn); +% +% INPUT: +% - iverts : index list of vertex numbers +% - vconn : sparse matrix of vertex connectivity +% OUTPUT: +% - newverts: row vec list of NEW vertex numbers adjacent the patch + +% @============================================================================= +% This software is part of The Brainstorm Toolbox +% http://neuroimage.usc.edu/brainstorm +% +% Copyright (c)2008 Brainstorm by the University of Southern California +% This software is distributed under the terms of the GNU General Public License +% as published by the Free Software Foundation. Further details on the GPL +% license can be found at http://www.gnu.org/copyleft/gpl.html. +% +% FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE +% UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY +% WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF +% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY +% LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE. +% +% For more information type "brainstorm licence" at command prompt. +% =============================================================================@ +% +% Authors: ? +% ----------------------------- Script History --------------------------------- +% +% ------------------------------------------------------------------------------ + +if (size(iverts,1) ~= 1) + iverts = iverts(:)'; % ensure row vector +end + +%%%%%%% BUG WORKAROUND FOR MATLAB R2009a: 7.8.0 %%%%%% +% Get version name +vername = version; +% If bugged version: need to convert matrice to a FULL one +if strcmpi(vername(1:5), '7.8.0') + % Concatenate all vertex connections for all verts + newverts = find(max(full(vconn(iverts, :)), [], 1)); +else + % Concatenate all vertex connections for all verts + newverts = find(max(vconn(iverts, :), [], 1)); +end + +% Extract unique set of indices, remove existing vertices +newverts = setdiff(newverts, iverts); + + + diff --git a/patchify.m b/patchify.m new file mode 100644 index 0000000..e995c9c --- /dev/null +++ b/patchify.m @@ -0,0 +1,34 @@ +function FV = patchify(S,varargin) +%PATCHIFY - Remove subfields from a struct to make it a FV patch +% FV = patchify(S) remove fields from S other than .vertices and .faces +% If needed, those fields will be lower-cased. +% See also: patch + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2005 +% This program is free software; you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation; either version 2 of the License, or +% (at your option) any later version. +% +% ----------------------------- Script History --------------------------------- +% KND 2005-12-08 Creation +% +% ----------------------------- Script History --------------------------------- +FV=struct('vertices', [], 'faces', []); +if isfield(S,'vertices') + FV=setfield(FV,'vertices',getfield(S, 'vertices')); +elseif isfield(S,'Vertices') + FV=setfield(FV,'vertices',getfield(S, 'Vertices')); +end +if isfield(S,'faces') + FV=setfield(FV,'faces',getfield(S, 'faces')); +elseif isfield(S,'Faces') + FV=setfield(FV,'faces',getfield(S, 'Faces')); +end +if isfield(S,'facevertexcdata') + FV=setfield(FV,'facevertexcdata',getfield(S, 'facevertexcdata')); +elseif isfield(S,'FaceVertexCData') + FV=setfield(FV,'facevertexcdata',getfield(S, 'FaceVertexCData')); +end + \ No newline at end of file diff --git a/patchplot.m b/patchplot.m new file mode 100644 index 0000000..39e74ec --- /dev/null +++ b/patchplot.m @@ -0,0 +1,66 @@ +function h = patchplot(X,Y,varargin) +%PATCHPLOT - One line description goes here. +% [h] = patchplot(Y) +% [h] = patchplot(X,Y) +% [h] = patchplot(X,Y,varargin) +% +% Example +% >> patchplot +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2009 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2009-04-06 Creation +% +% ----------------------------- Script History --------------------------------- + +if nargin==1 + Y=X; + X=[]; +end +if numel(Y)==max(size(Y)) + Y=Y(:); +end +Y=Y(:,:); +if isempty(X) + X=1:size(Y,1); +end +bl=0; +if nargin>1 + try + i = strmatch('baseline', lower(varargin(1:2:end)), 'exact'); + if ~isempty(i) + bl=varargin{2*i}; + varargin((2*i-1):(2*i))=[]; + else + bl=0; + end + end +end + +minY = zeros(1,size(Y,2))+bl; +% minY(all(Y>=0,1),:)=min(Y,[],1); +% minY(all(Y<=0,1),:)=min(Y,[],1); +X=X([1 1:end end]); +Y = [minY;Y;minY]; + +%Y(end+1,:) = Y(1,:); + +for i=1:size(Y,2) + if size(Y,2)>1 + C=rand(1,3); + else + C=[.87 .87 .87]; + end + h(i)=patch(X,Y,C,varargin{:}); +end +% Move it to the back layer +hh = get(gca, 'children'); +set(gca, 'children',[ hh(hh~=h);h]); \ No newline at end of file diff --git a/pca.m b/pca.m new file mode 100644 index 0000000..bca4269 --- /dev/null +++ b/pca.m @@ -0,0 +1,118 @@ +function [Y, lbd, cos2, CC] = pca(X, P, labels) +%PCA Principal Components Analysis +% +% Y = PCA(X, P) returns the first P principal components of X. +% X must be a NxL matrix of N vectors in L dimensions. +% +% Y = PCA(X, P, LABELS) displays several plots related to the PCA: +% . projection of data on the first two principal axes, +% . correlation circle, +% . % inertia of the principal components, +% . angles of the data w.r.t. the PC subspace. +% If LABELS is a matrix containing N rows of text, each row is used +% to label the corresponding data point, otherwise data go unlabelled. +% +% Additional outputs: [Y, LBD, C2, CC] = PCA(X, P [, LABELS]), where +% Y is the projection of X on the first P principal components, +% LBD contains the % inertia of the required P components, +% C2 is the squared cosine between projected data and original data, +% CC is the correlation between the input features and the first P P.C. +% +% See also: . +if (nargin < 2) or (nargin > 4) + error('PCANA: wrong number of arguments.') +end + +[N, L] = size(X) ; +if (P < 1) + error('PCANA: must return at least 1 principal component.') +end +if (P > L) + error('PCANA: can''t get more principal components than dimensions.') +end + +if (nargin == 2) + verbose = 0 ; +end + +%%%%%%%%%% + +R = corrcoef(X) ; +% XM= ones(N,1) * mean(X') ; +% Xc = X' - XM ; +% [U, S, V] = svd(Xc, 0) ; +[V, S] = eig(R) ; +[l, idx] = sort(diag(S)') ; +l = fliplr(l) ; +idx = fliplr(idx) ; + +% Coordonees centrees reduites. +XCR = (X - ones(N,1)*mean(X)) / diag(std(X)) ; +%PC = (Xc * V(:,idx)' + XM)' ; +PC = (XCR * V(:,idx) ) ; + +% P first principal components: +Y = PC(:, 1:P) ; + +% P first portions of inertia: +lbd = l(1:P) / sum(l) ; + +% Squared cosines of projection vs. original data: +cos2 = sum((Y.^2)') ./ sum((XCR.^2)') ; + +% Correlations of features with PCs: +CC = V(:,idx) * diag(sqrt(l)) ; + +if (nargin == 3) + figure + subplot(2,2,1) ; + plot(PC(:,1), PC(:,2), '+') ; + if (size(labels, 1) == N) + for i = 1:N + text(PC(i,1), PC(i,2), [' ', labels(i,:)]) ; + end + end + xlabel('First principal axis') ; + ylabel('Second principal axis') ; + title('Data projection on the first two principal axes') + subplot(2,2,2) ; + plot(cos(2*pi*(0:100)/100), sin(2*pi*(0:100)/100)) ; + hold on, plot([-1, 1], [0, 0]), plot([0, 0], [-1, 1]) ; + plot(CC(:,1), CC(:,2), 'x') ; + for k = 1:L + text(CC(k,1)+.03, CC(k,2)+.03, num2str(k)) ; + end + xlabel('First PC') ; + ylabel('Second PC') ; + title('Correlation of features with first 2 PCs') + subplot(2,2,3) ; + hh = plot(l/sum(l), '--') ; + set(hh, 'LineWidth', 3) ; + hold on, hh = plot(cumsum(l)/sum(l)) ; + set(hh, 'LineWidth', 3) ; + axis([1 L 0 1]) ; + plot([2 2], [0 1]) ; + xlabel('PC no.') ; + ylabel('Inertia') ; + legend('PC inertia', 'Cumulated inertia') ; + title('Repartition of inertia on the PCs') ; + subplot(2,2,4) ; + hold on + hh = plot(acos(sqrt(cos2))/pi*180, 1:N, '+') ; + set(hh, 'LineWidth', 3) ; + hold on + for i = 1:N + hh = plot([0, acos(sqrt(cos2(i)))/pi*180], [i, i], '-') ; + set(hh, 'LineWidth', 3) ; + end + set(gca, 'Box', 'on') ; + set(gca, 'YLim', [1 N]) ; + if (size(labels, 1) == N) + set(gca, 'YTick', 1:N) ; + set(gca, 'YTickLabels', labels) ; + end + xlabel('Angle (deg)') + title('Angle of data with the PC plan') +end + +CC = CC(:,1:P) ; diff --git a/permarray.m b/permarray.m new file mode 100644 index 0000000..e05b420 --- /dev/null +++ b/permarray.m @@ -0,0 +1,4 @@ +function [y]=permarray(x,pv,dim) +% not aailable !!! (Can't even remember what it is suppose to do) +error('bla bla') +return \ No newline at end of file diff --git a/permprep.m b/permprep.m new file mode 100644 index 0000000..a375028 --- /dev/null +++ b/permprep.m @@ -0,0 +1,31 @@ +% function prep=permprep(X,Y,paired,tails) +for i=1:10000 + n=subarray(randperm(30),1:15); + y(i)=mean(z{1}(n)); + n=subarray(randperm(30),1:15); + y(i)=y(i)-mean(z{2}(n)); +end + +% doesn't work + + +% % +% % Z=sum(Y(1:ceil(N(1)/2),:),1)./ceil(N(1)/2)-sum(Y(N(1)+(1:ceil(N(2)/2)),:),1)./ceil(N(2)/2); +% % if i==0 +% +% Randomization methods avoid assumptions of normality, are useful for +% small-n experiments, and are robust against heteroscedasticity. To employ +% them: +% +% * Bootstrap populations for the experimental and control samples +% independently, generating subsamples of half the size of the original +% samples, using software such as Resampling Stats©(Bruce, 2003). This +% half-sizing provides the sqrt(2) increase in the standard +% deviation intrinsic to calculation of prep. +% * Generate an empirical sampling distribution of the difference of +% the means of the subsamples, or of the mean of the differences for a +% matched-sample design. +% * The proportion of the means that are positive gives prep. +% +% This robust approach does not take into account ??2, and so is accurate +% only for exact replications. \ No newline at end of file diff --git a/permute2.m b/permute2.m new file mode 100644 index 0000000..a87dcf3 --- /dev/null +++ b/permute2.m @@ -0,0 +1,31 @@ +function Y = permute2(X,p) +%PERMUTE2 - Easi(er) to use than permute +% [Y] = permute2(X,p) +% +% Example +% >> permute2(rand(3,2,4), 3) will put 3rd dimension first followed +% by the other ones +% +% See also: permute, nd2array + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2006 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2006-03-13 Creation +% +% ----------------------------- Script History --------------------------------- + +ndx=ndims(X); +if all(p(:)>0) +Y=permute(X,[p(:)' setdiff(1:ndx,p(:))]); +elseif all(p(:)<0) +Y=ipermute(X,[p(:)' setdiff(1:ndx,p(:))]); +else + error('Can''t handle negative and positive dimensions!'); +end + diff --git a/perso/predictors.m b/perso/predictors.m new file mode 100644 index 0000000..a0f084b --- /dev/null +++ b/perso/predictors.m @@ -0,0 +1,18 @@ +function []=p() +g(1,1:2)=0; +k=2; +for i=2:1000 + p1=ceil(rand*6); + sum(k-3.5)/i + [p2]=round(3.5-mean(k-3.5)) + k(i)=ceil(rand*6); + k + p2 + + g(i,1)=g(i-1,1)+(p1(i)==k(i)); + g(i,2)=g(i-1,2)+(p2(i)==k(i)); + +end + + plot(cumsum(g)) + drawnow diff --git a/perso/prerdictors.m b/perso/prerdictors.m new file mode 100644 index 0000000..276701b --- /dev/null +++ b/perso/prerdictors.m @@ -0,0 +1,12 @@ +function []=p() +g(1:2,1)=0; +for i=2:1000 + p1=ceil(rand*6); + [v,p2]=min(sum(k-3.5)); + k + p2 + k(i)=ceil(rand*6); + g(i,1)=g(i-1,1)+(p1(i)==k(i)); + g(i,2)=g(i-1,2)+(p2(i)==k(i)); + + plot(cumsum([g1;g2])) \ No newline at end of file diff --git a/piecemeal.m b/piecemeal.m new file mode 100644 index 0000000..3732297 --- /dev/null +++ b/piecemeal.m @@ -0,0 +1,42 @@ +function [sz,idx]=piecemeal(p) % ,dim,minsize) +% piecemeal - find continuous segments of true in a logical array +% +% [sz,idx]=piecemeal(p) +%INPUT: +% p: logical vector +%OUPUTS: +% sz: size of the chunks/clusters/segment... +% idx: position of the first element of each chunk + +dim=1; +p=p(:); +p=logical(p); +np=size(p,dim); +dip=diff(cumsum(p),2); +dp1=find(dip==1)+2; +dp2=find(dip==-1)+2; +if isequal(p(1),[1]) + dp1=[1; dp1]; + if isequal(p(2),[0]) + dp2=[2; dp2]; + end +elseif isequal(p(2),[1]) + dp1=[2; dp1]; +end +if isequal(p(end),[1]) + dp2=[dp2; np+1]; +end +sz=dp2-dp1; +idx=dp1; + +return + +% one patch per col +dp=diff(p); +fdp=find(dp) +pfdp=p(fdp) + +fx=[fdp; fdp]; +fx=[1 fx(:)' length(p)] +fy=[[p(fdp) p(end)]; [p(fdp) p(end)]]; +fy=[fy(:)']; diff --git a/plot2d.m b/plot2d.m new file mode 100644 index 0000000..25b86c3 --- /dev/null +++ b/plot2d.m @@ -0,0 +1,21 @@ +function h = plot2d(XY, varargin) +%PLOT2D - Plot 2 data X,Y +% [h] = plot2d(XY) +% +% Example +% >> plot2d(randn(1000,2),'.') +% +% See also: plot, plot3d, plotnd() + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2008 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2008-09-03 Creation +% +% ----------------------------- Script History --------------------------------- +h=plot(XY(:,1), XY(:,2), varargin{:}); diff --git a/plot3d.m b/plot3d.m new file mode 100644 index 0000000..4a620f1 --- /dev/null +++ b/plot3d.m @@ -0,0 +1,18 @@ +function [p]=plot3d(xyz, varargin); +% plot in 3 dimension +% [p]=plot3d(xyz, varargin); +if size(xyz, 2) ~= 3 && size(xyz, 1) == 3 + xyz=xyz'; +end +if nargin<2 + varargin={'.'}; +end +if size(xyz,2)==2 + xyz(:,3)=0.*xyz(:,1); +end +% varargin{1}=['.' varargin{1}]; +% , 'LineStyle', 'None', 'Marker', '.' +p=plot3(xyz(:,1),xyz(:,2),xyz(:,3),varargin{:} ); +if all(xyz(:,3)==0) + view(2) +end \ No newline at end of file diff --git a/plot4.m b/plot4.m new file mode 100644 index 0000000..72b7ee5 --- /dev/null +++ b/plot4.m @@ -0,0 +1,51 @@ +function varargout=plot4(x,y,z,c,varargin); + +% PLOT4 Plot colored lines and points in 3-D space +% +% PLOT3(x,y,z,c), where x, y, z and c are four vectors of the same length N, +% plots a line in 3-space through the points whose coordinates are the +% elements of x, y and z, colored occording to the values in c. The line +% consists of N-1 line segments. +% +% Various line types, plot symbols and colors may be obtained with +% PLOT3(X,Y,Z,s) where s is a 1, 2 or 3 character string made from +% the characters listed under the PLOT command. +% +% Example: A helix: +% +% t = 0:pi/50:4*pi; +% plot4(sin(t),cos(t),t.^2,t,'.-'); +% +% PLOT4 returns a column vector of handles to lineseries objects, one +% handle per line segment. +% +% See also plot, plot3, line, axis, view, mesh, surf. + +% author: Christophe Lauwerys +% contact: christophe.lauwerys@gmail.com +% date: 22/03/2006 + +error(nargchk(4,inf,nargin)) +error(nargoutchk(0,1,nargout)) + +x=x(:); +y=y(:); +z=z(:); +c=c(:); + +% For all c that are NaN, set x to NaN to overcome plotting +idx = find(isnan(c)); +x(idx) = NaN; +c(idx) = min(c); + +% Interpolate colormap +cmap=colormap; % get current colormap +yy=linspace(min(c),max(c),size(cmap,1)); % Generate range of color indices that map to cmap +cm = interp1(yy,cmap,c,'linear')'; + +% Plot data +storeNextPlot = get(gca,'NextPlot'); +set(gca,'ColorOrder',cm','NextPlot','ReplaceChildren') +h=plot3([x(1:end-1) x(2:end)]',[y(1:end-1) y(2:end)]',[z(1:end-1) z(2:end)]',varargin{:}); +set(gca,'NextPlot',storeNextPlot); +if nargout==1, varargout{1}=h; end \ No newline at end of file diff --git a/plotSDV.m b/plotSDV.m new file mode 100644 index 0000000..0edc43e --- /dev/null +++ b/plotSDV.m @@ -0,0 +1,10 @@ +function [p] = plotSDV(x, y, sdv, varargin) +%PLOTSDV : [plots()] = plotSDV(x,y, sdv, varargin) +% Plot standard deviation around a value 'moustache' like +for k=1:length(x) + z=y(k) + [-sdv(k) sdv(k)]; + p(k)=plot([x(k) x(k)], z, varargin{:}) + set(p(k), 'Marker', '+') +end + + diff --git a/plotdata.m b/plotdata.m new file mode 100644 index 0000000..8dceb6c --- /dev/null +++ b/plotdata.m @@ -0,0 +1,308 @@ +function [varargout]=plotdata(action,varargin); +% plotdata - plots multiple data typically [ Channels x Time x Trials ] +% +% Example +% >> plotdata(F) +% >> plotdata('plot',F,Options) +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2004 +% This program is free software; you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation; either version 2 of the License, or +% (at your option) any later version. +% +% ----------------------------- Script History --------------------------------- +% KND 2004-12-15 Creation +% KND 2005-12-15 Now links between many plotdata axes +% ----------------------------- Script History --------------------------------- +if nargin<1 + error('No data!') +elseif nargin==1 || isnumeric(action) + varargin=[{action} varargin ]; + action='plot'; +end + +try + varargout={eval(sprintf('action_%s(varargin{:});',action))}; +catch + eval(sprintf('action_%s(varargin{:});',action)); +end + + +function [ha]=action_plot(data, varargin) +% ------------------------------------------------------------------------- +% Check OPTIONS and default values when optional arguments are not +% specified +Def_OPTIONS = struct(... + 'TimeDim',imax(size(data)),... + 'Fields', {{'Channel', 'Trial' , 'Condition', 'Field4', 'Field5'}} ,... + 'Time', [] ,... + 'TimeWindow', [], ... + 'Values', [], ... + 'Interactive', 1, ... + 'LinkedAxes', [] ... % Link to other plots + ); +if nargin < 2 + OPTIONS = Def_OPTIONS; +else + if length(varargin)>1 + OPTIONS = cell2struct(varargin(2:2:end),varargin(1:2:end),2);% struct(varargin{:}); + else + OPTIONS = varargin{1}; + end + % Check field names of passed OPTIONS and fill missing ones with default values + DefFieldNames = fieldnames(Def_OPTIONS); + for k = 1:length(DefFieldNames) + if ~isfield(OPTIONS,DefFieldNames{k}) + OPTIONS = setfield(OPTIONS,DefFieldNames{k},getfield(Def_OPTIONS,DefFieldNames{k})); + end + end + clear DefFieldNames +end +clear Def_OPTIONS +% ------------------------------------------------------------------------- + +z=permute(data, [OPTIONS.TimeDim 1:(OPTIONS.TimeDim-1) (OPTIONS.TimeDim+1):ndims(data)]); +if isempty(OPTIONS.Time) + Time=1:size(z,1); +else + Time=OPTIONS.Time; +end +nf=ndims(data)-1; +if length(OPTIONS.Fields) < nf + OPTIONS.Fields(end+1:nf)={[]}; +end +for i=1:nf + if isempty( OPTIONS.Fields{i} ) + OPTIONS.Fields{i}=sprintf('Field %d',i); + end +end +cla +if isempty(OPTIONS.TimeWindow) + hp=plot(Time,z(:,:)); +else + hold on + for i=1:size(z(:,:),2); + hp(i)=plot(Time(1:OPTIONS.TimeWindow(i)), z(1:OPTIONS.TimeWindow(i),i), 'Color',subarray(get(gca, 'ColorOrder'), mod(i-1,size(get(gca, 'ColorOrder'),1))+1)); + end + hold off +end +ha=get(hp(1), 'Parent'); +sz=[size(z)]; +x=OPTIONS; +x.Axes=ha; +x.DataSize=sz; +% x.Data=data; +x.Time=OPTIONS.Time; +sz(1)=[]; +sz=[sz 1]; +hp=reshape(hp,sz); +x.Traces=hp; +x.Selected=[]; +setappdata(ha,mfilename,x) +for i=1:length(x.LinkedAxes) + y=getappdata(x.LinkedAxes(i),mfilename); + y.LinkedAxes=[y.LinkedAxes x.Axes]; + setappdata(y.Axes, mfilename, y); +end +if OPTIONS.Interactive + set(hp, 'ButtonDownFcn', sprintf('%s(''click_trace'', gcbo)', mfilename)); + title('Click on a trace') + h=uicontextmenu; + + uimenu('Label', 'Unselect all' , 'Parent', h, 'Callback', sprintf('%s(''select_rmv'',overobj(''axes''))', mfilename)); + uimenu('Label', 'Unselect visible ones' , 'Parent', h, 'Callback', sprintf('%s(''select_rmv'',overobj(''axes''), ''Visible'', ''on'')', mfilename)); + uimenu('Label', 'Hide selected traces' , 'Parent', h, 'Callback', sprintf('%s(''hide_add'',overobj(''axes''))', mfilename)); + % 'set(getappdata(get(gcbo, ''UserData''), ''SelectedHandle''), ''Visible'', ''off'')'); + uimenu('Label', 'Un-hide last selected trace', 'Parent', h, 'Callback', sprintf('%s(''hide_rmv'',overobj(''axes''),1)', mfilename)); + % 'set(getappdata(get(gcbo, ''UserData''), ''SelectedHandle''), ''Visible'', ''on'')'); + uimenu('Label', 'Un-hide all traces', 'Parent', h, 'Callback', sprintf('%s(''hide_rmv'',overobj(''axes''))', mfilename)); + %'set(getappdata(get(gcbo, ''UserData''), ''Traces''), ''Visible'', ''on'')'); + uimenu('Label', 'List hidden traces', 'parent', h, 'Callback', sprintf('%s(''hide_list'',gca)', mfilename)); + + % + % uimenu('Label', 'Display single channel traces', 'Parent', h, 'Separator', 'on', 'Callback', [ ... + % 'figure(''Name'', sprintf(''Channel #%d'', subarray(getappdata(get(gcbo, ''UserData''), ''Selected''), 1)));'... + % 'plotdata(permute(cell2mat(get(subarray(getappdata(get(gcbo, ''UserData''), ''Traces''),'... + % 'subarray(getappdata(get(gcbo, ''UserData''), ''Selected''), 1), 1),''YData'')),[3 2 1]));'... + % ]); + if prod(sz)>1 + for i=1:length(sz) + h2=uimenu('Label', sprintf('Display single (%s) traces', OPTIONS.Fields{i}), 'Parent', h, 'Callback', [ ... + 'figure(''Name'', [ get(gcbf, ''Name'') '', '' sprintf(''' sprintf('%s', OPTIONS.Fields{i}) ' #%d'', subarray(getappdata(get(gcbo, ''UserData''), ''Selected''), ' sprintf('%d',i) '))]);' ... + 'plotdata(reshape(shiftdim(cell2mat(x2cell(get(subarray(getappdata(get(gcbo, ''UserData''), ''Traces''),'... + 'subarray(getappdata(get(gcbo, ''UserData''), ''Selected''), ' sprintf('%d',i) '), ' sprintf('%d',i) '),''YData''))),1),'... + '[subarray(getappdata(get(gcbo, ''UserData''),''DataSize''),' sprintf('%d',i+1) ', -2) 1])' ... + ', ''TimeDim'',1, ''Time'', getappdata(get(gcbo, ''UserData''),''Time''), ' ... + ' ''Fields'', {subarray(getappdata(get(gcbo, ''UserData''),''Fields''),' sprintf('%d',i) ', -2)} );'... + ]); + if i==1 + set(h2, 'separator', 'on') + end + + end + + uimenu('separator', 'on', 'Label', 'Select one trace', 'Parent', h, 'Callback', 'set(getappdata(get(gcbo, ''UserData''), ''Traces''), ''Visible'', ''on'')'); + + end + set(get(h, 'Children'),'UserData', ha); + set(hp, 'uicontextmenu', h); + set(ha, 'uicontextmenu', h); + +end + +function sel=handles2ind(x,ho) +if ~isstruct(x) + x=getappdata(x, mfilename); +end +sel=ind2sub2(size(x.Traces), idxmember(ho,x.Traces)); + +function b=ind2logical(traces,sel) +b=logical(zeros(size(traces))); +sel=num2cell(sel); +for i=1:size(sel,1) + b(sel{i,:})=1; +end +return + +function []=action_click_trace(ho) +ha=get(ho(1), 'Parent'); +hf=get(ha, 'Parent'); +if ismember(get(hf, 'SelectionType'), {'normal', 'extend'}) + action_select_toggle(ha,handles2ind(ha,ho)); +end + +function action_select_toggle(x, sel) +% Toggle selected/not-selected style +if ~isstruct(x) + x=getappdata(x, mfilename); +end +for i=1:size(sel,1) + if ismember(sel,x.Selected, 'rows') + action_select_rmv(x, sel(i,:)) + else + action_select_add(x, sel(i,:)) + end +end + + +function action_select_add(x, sel,link) +% Put back non-selected style +if nargin<2 + error('No selection') +end +if ~isstruct(x) + x=getappdata(x, mfilename); +end +x.Selected=[x.Selected;sel]; +set(x.Traces(ind2logical(x.Traces,sel)), 'LineWidth', 2); +setappdata(x.Axes, mfilename, x); +set(get(x.Axes,'Title'), 'String', [ 'Selection: [ ' sprintf('%d ', sel) ']']); +if nargin<3 + link=[]; +end +if all(link)>0 + link=[link x.Axes]; + ToBeLinked=setdiff(x.LinkedAxes,link); + for i=1:length(ToBeLinked) + action_select_add(ToBeLinked(i),sel,link) + end +end +return + +function action_select_rmv(x, sel,link) +% Put back to non-selected style +if ~isstruct(x) + x=getappdata(x, mfilename); +end +if nargin<2 + % If unspecified act on the previously selected traces + sel=x.Selected; +end +if nargin>1 + if ischar(sel) + sel=findobj(x.Traces(ind2logical(x.Traces,x.Selected)), sel, link); + sel=handles2ind(x, sel); + link=[]; + end +end +x.Selected=x.Selected(~ismember(x.Selected, sel, 'rows'),:); +set(x.Traces(ind2logical(x.Traces,sel)), 'LineWidth', .5); +setappdata(x.Axes, mfilename, x); +if nargin<3 + link=[]; +end +if all(link)>0 + link=[link x.Axes]; + ToBeLinked=setdiff(x.LinkedAxes,link); + for i=1:length(ToBeLinked) + action_select_rmv(ToBeLinked(i), sel,link) + end +end +return + +function action_hide_toggle(x, sel) +% Toggle selected/not-selected style +if ~isstruct(x) + x=getappdata(x, mfilename); +end +for i=1:size(sel,1) + if ismember(sel,x.Selected, 'rows') + action_hide_rmv(x, sel(i,:)) + else + action_hide_add(x, sel(i,:)) + end +end + + +function action_hide_add(x, sel) +% Hide selection +if ~isstruct(x) + x=getappdata(x, mfilename); +end +if nargin<2 + sel=x.Selected; +elseif isscalar(sel) + if sel<=0 + error('wrong input') + end + sel=x.Selected(end:-1:(end-abs(sel)+1)); +end +set(x.Traces(ind2logical(x.Traces,sel)), 'Visible', 'off'); +return + +function action_hide_rmv(x, sel) +% Put back non-selected style +if ~isstruct(x) + x=getappdata(x, mfilename); +end +if nargin<2 + % If unspecified act on the previously selected traces + sel=x.Selected; +elseif isscalar(sel) + if sel<=0 + error('wrong input') + end + if isequal(get(x.Traces(ind2logical(x.Traces,x.Selected(end,:))), 'Visible'), 'on') + % If we are being selecting a trace, we unselect it and go one step + % further in the selected list. + action_select_rmv(x,x.Selected(end,:)) + x=getappdata(x.Axes, mfilename); + end + sel=x.Selected(end:-1:(end-abs(sel)+1),:); +end +set(x.Traces(ind2logical(x.Traces,sel)), 'Visible', 'on'); +return + +function action_hide_list(x) +% List (in command window) the hidden traces +if ~isstruct(x) + x=getappdata(x, mfilename); +end +disp(handles2ind(x ,findobj(x.Traces, 'Visible','off'))) + +% [ '[ cell2mat(x2cell(argouts('... +% ' ''ind2sub( size(getappdata(gca, ''''Traces'''')),nonzeros(idxmember(findobj(gca, ''''Visible'''', ''''off'''') , getappdata(gca,''''Traces''''))))'' ,'... +% ' ndims(getappdata(gca,''Traces''))))) ]' ]); diff --git a/ploterr.m b/ploterr.m new file mode 100644 index 0000000..c5de3ae --- /dev/null +++ b/ploterr.m @@ -0,0 +1,82 @@ +function [h,Options]=ploterr(x,y,s,varargin) +% ploterr - plot with a patch representing dispersion/error around the value +% [h]=ploterr(x,y,s) +% [h]=ploterr(x,y,s, 'Color', [r g b], 'PatchColor', [r g b]) +% [h]=ploterr(y,s) +% [h]=ploterr(y) +% +% Plots data y with their dispersion s, given abscissa x. +%INPUTS: +% x: T-long vector, abscissa for data point (if not given: x=[1:length(y)]) +% y: T-by-N matrix of mean data points (N may be 1) +% s: T-by-N matrix of dispersion around mean for each data point +%OUTPUT: +% h is N-by-2 array of handles: h(:,1) are the lines, h(:,2) the patches +% +% If no s is provided. Default is to plot the STANDARD ERROR around the +% mean: +% s=std(y)/sqrt(n); +% y=mean(y); +% ie. y is assumed to be a [ K x T x N ] matrix (of K observations/subjects) + +if nargin<2 + y=x; + x=[]; +end +if nargin<3 + s=[]; +end +if ~isempty(s) && ischar(s) + varargin=[{s} varargin]; + s=[]; +end +Options.Color=get(gca, 'ColorOrder'); +Options.Color([1 2 3],:)=Options.Color([1 3 2],:); +Options.PatchColor=brighten(Options.Color,.97); +if nargin>3 + Options=mergestructs(Options,struct(varargin{:})); +end +if isempty(s) + s=shiftdim(std(y),1)./sqrt(size(y,1)); + y=shiftdim(mean(y),1); +end +if isempty(x) + x=[1:size(y,1)]'; +end + +if prod(size(y))~=max(size(y)) + Ys=y; + holdstate=ishold; + y=y(:,:); + s=s(:,:); + for i=1:size(y,2) + o=Options; + o.Color=circshift(Options.Color,1-i); + o.PatchColor=circshift(Options.PatchColor,1-i); + [h(i,:)]=ploterr(x,y(:,i),s(:,i),o); + hold on + end + if ~holdstate + hold off + end + return +end + +px=[x(:); flipud(x(:))]; % use px=[x1;x2(end:-1:1)]; if row vecs +py=[ y(:)-s(:) ; flipud(y(:)+s(:)) ]; % use ... + +holdstate=ishold; +if ~holdstate + delete(get(gca, 'Children')) +end +h=patch(px, py, Options.PatchColor(1,:)); +% set(h, 'FaceAlpha', .5) +hold on +h=[plot(x,y, 'LineWidth', 2,'Color', Options.Color(1,:)) h]; +set([h(:,2)], 'FaceAlpha', .35); +if ~holdstate + hold off; +end + +return + diff --git a/ploterrw.m b/ploterrw.m new file mode 100644 index 0000000..2b0a6b7 --- /dev/null +++ b/ploterrw.m @@ -0,0 +1,103 @@ +function [h,Options]=ploterrw(x,y,s,varargin) +% ploterr - plot with a patch representing dispersion/error around the value +% [h]=ploterrw(x,y,s) +% [h]=ploterrw(x,y,s, 'Color', [r g b], 'PatchColor', [r g b]) +% [h]=ploterrw(y,s) +% [h]=ploterrw(y) +% [h]=ploterrw(y,dim) +% +% +% Plots data y with their dispersion s, given abscissa x. +% +%INPUTS: +% x: T-long vector, abscissa for data point (if not given: x=[1:length(y)]) +% y: T-by-N matrix of mean data points (N may be 1) +% s: T-by-N matrix of dispersion around mean for each data point +%OUTPUT: +% h is N-by-2 array of handles: h(:,1) are the lines, h(:,2) the patches +% +% If no s is provided. Default is to plot the STANDARD ERROR around the +% mean OF THE DIFFERENCE: (cf. ploterr()) +% s=std(y)/sqrt(n); +% y=mean(y); +% ie. y is assumed to be a [ K x T x N ] matrix (of K observations/subjects) +% +% See also: ploterr +if nargin<2 + y=x; + x=[]; +end +if nargin<3 + s=[]; + if isscalar(y) + y=permute(x,[y setdiff(1:ndims(x),y)]); + x=[]; + end +end + +if ~isempty(s) + if ischar(s) + varargin=[{s} varargin]; + s=[]; + elseif isscalar(s) + y=permute(y,[s setdiff(1:ndims(y),s)]); + s=[]; + end +end +Options.Color=get(gca, 'ColorOrder'); +if Options.Color(2)~=1 + Options.Color([1 2 3],:)=Options.Color([1 3 2],:); + set(gca, 'ColorOrder',Options.Color); +end + +Options.PatchColor=brighten(Options.Color,.97); +if nargin>3 + Options=mergestructs(Options,struct(varargin{:})); +end + +if isempty(s) + y=squeeze(y); + s=shiftdim(std(diff(y,[],3)),1)./sqrt(size(y,1)); % New... + s=repmat(s,1,size(y,3)); + y=shiftdim(mean(y),1); +end +if isempty(x) + x=[1:size(y,1)]'; +end + +if prod(size(y))~=max(size(y)) + Ys=y; + holdstate=ishold; + y=y(:,:); + s=s(:,:); + for i=1:size(y,2) + o=Options; + o.Color=circshift(Options.Color,1-i); + o.PatchColor=circshift(Options.PatchColor,1-i); + [h(i,:)]=ploterr(x,y(:,i),s(:,i),o); + hold on + end + if ~holdstate + hold off + end + return +end + +px=[x(:); flipud(x(:))]; % use px=[x1;x2(end:-1:1)]; if row vecs +py=[ y(:)-s(:) ; flipud(y(:)+s(:)) ]; % use ... + +holdstate=ishold; +if ~holdstate + delete(get(gca, 'Children')) +end +h=patch(px, py, Options.PatchColor(1,:)); +% set(h, 'FaceAlpha', .5) +hold on +h=[plot(x,y, 'LineWidth', 2,'Color', Options.Color(1,:)) h]; +set([h(:,2)], 'FaceAlpha', .35); +if ~holdstate + hold off; +end + +return + diff --git a/ploterrw1.m b/ploterrw1.m new file mode 100644 index 0000000..0601697 --- /dev/null +++ b/ploterrw1.m @@ -0,0 +1,86 @@ +function [h,Options]=ploterrw1(x,y,s,varargin) +% ploterr - plot with a patch representing dispersion/error around the value +% [h]=ploterrw(x,y,s) +% [h]=ploterrw(x,y,s, 'Color', [r g b], 'PatchColor', [r g b]) +% [h]=ploterrw(y,s) +% [h]=ploterrw(y) +% +% +% Plots data y with their dispersion s, given abscissa x. +% +%INPUTS: +% x: T-long vector, abscissa for data point (if not given: x=[1:length(y)]) +% y: T-by-N matrix of mean data points (N may be 1) +% s: T-by-N matrix of dispersion around mean for each data point +%OUTPUT: +% h is N-by-2 array of handles: h(:,1) are the lines, h(:,2) the patches +% +% If no s is provided. Default is to plot the STANDARD ERROR around the +% mean OF THE DIFFERENCE: (cf. ploterr()) +% s=std(y)/sqrt(n); +% y=mean(y); +% ie. y is assumed to be a [ K x T x N ] matrix (of K observations/subjects) +% +% See also: ploterr +if nargin<2 + y=x; + x=[]; +end +if nargin<3 + s=[]; +end +if ~isempty(s) && ischar(s) + varargin=[{s} varargin]; + s=[]; +end +Options.Color=get(gca, 'ColorOrder'); +Options.PatchColor=brighten(Options.Color,.97); +if nargin>3 + Options=mergestructs(Options,struct(varargin{:})); +end +if isempty(s) + s=shiftdim(std(diff(y,[],3)),1)./sqrt(size(y,1)); % New... + s=repmat(s,1,size(y,3)); + s=s/2; % divide Standard errot by 2 to represent 1-tail type statistical inference (well u know...) + y=shiftdim(mean(y),1); +end +if isempty(x) + x=[1:size(y,1)]'; +end + +if prod(size(y))~=max(size(y)) + Ys=y; + holdstate=ishold; + y=y(:,:); + s=s(:,:); + for i=1:size(y,2) + o=Options; + o.Color=circshift(Options.Color,1-i); + o.PatchColor=circshift(Options.PatchColor,1-i); + [h(i,:)]=ploterr(x,y(:,i),s(:,i),o); + hold on + end + if ~holdstate + hold off + end + return +end + +px=[x(:); flipud(x(:))]; % use px=[x1;x2(end:-1:1)]; if row vecs +py=[ y(:)-s(:) ; flipud(y(:)+s(:)) ]; % use ... + +holdstate=ishold; +if ~holdstate + delete(get(gca, 'Children')) +end +h=patch(px, py, Options.PatchColor(1,:)); +% set(h, 'FaceAlpha', .5) +hold on +h=[plot(x,y, 'LineWidth', 2,'Color', Options.Color(1,:)) h]; +set([h(:,2)], 'FaceAlpha', .35); +if ~holdstate + hold off; +end + +return + diff --git a/plotlabel.m b/plotlabel.m new file mode 100644 index 0000000..e5fb1a3 --- /dev/null +++ b/plotlabel.m @@ -0,0 +1,60 @@ +function [] = plotlabel(h,t) +%PLOTLABEL - Add text label near each plot +% [] = plotlabel(h) add a label near each point of the plot (default: +% current plot) +% [] = plotlabel(ax) add a label near each child of the axes + +% Example +% >> plotlabel +% +% See also: text + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2009 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2009-01-28 Creation +% +% ----------------------------- Script History --------------------------------- + +if nargin<1 + h=gco; + t=[]; +end +if ~ishandle(h) & iscell(h) + if nargin==2 + o=t; + else + o=gco; + end + t=h; + h=o; +end + +x=get(h,'XData'); +y=get(h,'YData'); +z=get(h,'ZData'); +n=length(x); +if n>100 + error(); +end +for i=1:n + if isempty(z) + if ~isempty(t) + ht(i)=text(x(i),y(i),t{i}); + else + ht(i)=text(x(i),y(i),sprintf('%d [ %.2g ; %.2g ]', i, x(i),y(i))); + end + else + if ~isempty(t) + ht(i)=text(x(i),y(i),z(i),t{i}); + else + ht(i)=text(x(i),y(i),z(i),sprintf('%d [ %.2g ; %.2g ; %.2g ]', i, x(i),y(i),z(i))); + end + end +end +set(ht, 'ButtonDownFcn', 'set(gcbo, ''Visible'', ''off'')'); \ No newline at end of file diff --git a/plotlinearfit.m b/plotlinearfit.m new file mode 100644 index 0000000..df549cf --- /dev/null +++ b/plotlinearfit.m @@ -0,0 +1,67 @@ +function [h,slope,offset,R2,SCE,P,T] = plotlinearfit(Y,X,varargin) +%PLOTLINEARFIT - One line description goes here. +% [h,slope,offset,R2,SCE,P,T] = plotlinearfit(Y,X) +% +% Example +% >> plotlinearfit(Y,X) +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2008 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2008-07-01 Creation +% +% ----------------------------- Script History --------------------------------- + +if nargin<1 + error('No data!') +end +switch nargin + case 0 + error('No input!') + case 1 + [slope,offset,R2,SCE,P,T] = linearfit(Y); + X=1:size(Y,1); + otherwise + [slope,offset,R2,SCE,P,T] = linearfit(Y,X); +end +% [s,o,r2,sce,p]=linearfit(roi.allcons,roi.allregressors); +% text(median(roi.allregressors), quantile(roi.allcons,.90), {'Correlation: ' sprintf('r2 = %g\np = %g', r2,p)}) +% xlabel(xSPM.SPM.xX.name(find(xSPM.SPM.xCon(Icc).c))) +% ylabel(xSPM.SPM.xY.VY(1).descrip) +if size(X,2) == 1 + h=plot(X, Y,'.', varargin{:}); +elseif size(X,2) == 2 + for i=1:size(Y,2) + h(i)=stem(X(:,1),X(:,2),Y(:,i),'.', varargin{:}); + end +else +end +hold on; +h=h(:); +set(h, 'linestyle', 'none') +[xx]=axis; +for i=1:size(Y,2) + h(i,2)=plot(xx(1:2)', xx(1:2)'*slope(i)+(xx(1:2)'.*0+1)*offset(i), ... + '--', 'Color', get(h(i), 'Color'), 'Marker', 'none'); + hold on +end +set(h(P<.05,2),'LineStyle', '-'); +hold off +if nargout==0 + set(h(P>0.05,2),'LineStyle',':'); + fprintf('Slope....: %s\n',sprintf('% 5.2g\t',slope)); + fprintf('Offset...: %s\n',sprintf('% 5.2g\t',offset)); + fprintf('r2.......: %s ; r = %s\n',sprintf('% 5.2g\t',R2),sprintf('% 5.2g\t',sqrt(R2))); + fprintf('p-value..: %s\n',sprintf('% 5.2g\t',P)); + fprintf('SCE......: %s\n',sprintf('% 5.2g\t',SCE)); + fprintf('T........: %s\n',sprintf('% 5.2g\t',T)); +end + + diff --git a/plotnd.m b/plotnd.m new file mode 100644 index 0000000..7bcbf85 --- /dev/null +++ b/plotnd.m @@ -0,0 +1,472 @@ +function [varargout]=plotnd(action,varargin) +% plotnd - plots multidimensional data typically [ Channels x Time x Trials ] +% +% +%OPTIONS: +% 'XDim': abscissa dimension. Default: imax(size(data)),... +% 'DimensionNames', {{'Channel', 'Trial' , 'Condition', 'Field4', 'Field5'}} +% 'X': +% 'XLim' +% 'Values' +% 'Interactive', 1 +% 'LinkedAxes': Plotting axes that should be linked +% +% Example +% >> plotnd(F) +% >> plotnd('plot',F,Options) +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2004 +% This program is free software; you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation; either version 2 of the License, or +% (at your option) any later version. +% +% ----------------------------- Script History --------------------------------- +% KND 2004-12-15 Creation +% KND 2005-12-15 Now links between many plotdata axes +% KND 2007-04-03 Renamed plotnd (because 'plotdata' is an eeglab function) +% KND 2009-09-09 Added zoom ability +% ----------------------------- Script History --------------------------------- +if nargin<1 + error('No data!') + +elseif all(ishandle(action(:))) && (nargin==2) + if isstruct(varargin{1}) && isfield(varargin{1},'VerticalScrollAmount') + hf=action; + action = 'wheelzoom'; + varargin=[ {hf} varargin(:) ]; + end +elseif nargin==1 || isnumeric(action) + varargin=[{action} varargin ]; + action='plot'; +end + +try + varargout={eval(sprintf('action_%s(varargin{:});',action))}; +catch + eval(sprintf('action_%s(varargin{:});',action)); +end + + +function [ha]=action_plot(data, varargin) +% ------------------------------------------------------------------------- +% Check OPTIONS and default values when optional arguments are not +% specified +Def_OPTIONS = struct(... + 'DimensionNames', {{'Channel','Time','Trial','Condition', 'Field4', 'Field5'}} ,... + 'X', [] ,... + 'XDim', [] ,... + 'XLim', [], ... + 'Values', [], ... + 'Interactive', 1, ... + 'LinkedAxes', [], ... % Link to other plots + 'ColorDim', NaN , ... + 'LineStyleDim', NaN ... + ); +if nargin < 2 + OPTIONS = Def_OPTIONS; +else + if length(varargin)>1 + OPTIONS = cell2struct(varargin(2:2:end),varargin(1:2:end),2);% struct(varargin{:}); + elseif isnumeric(varargin{1}) + OPTIONS.X=varargin{1}; + else + OPTIONS = varargin{1}; + end + % Check field names of passed OPTIONS and fill missing ones with default values + DefFieldNames = fieldnames(Def_OPTIONS); + for k = 1:length(DefFieldNames) + if ~isfield(OPTIONS,DefFieldNames{k}) + OPTIONS = setfield(OPTIONS,DefFieldNames{k},getfield(Def_OPTIONS,DefFieldNames{k})); + end + end + clear DefFieldNames +end +clear Def_OPTIONS +% ------------------------------------------------------------------------- + +if ~isfield(OPTIONS, 'XDim') || isempty(OPTIONS.XDim) + if isfield(OPTIONS, 'X') && ~isempty(OPTIONS.X) + OPTIONS.XDim=find(size(data) == numel(OPTIONS.X)); + if numel(OPTIONS.XDim)>1 + warning('Multiple dimensions match the size of X... I take the 1st one'); + end + else + OPTIONS.XDim=imax(size(data)); + end +end + +%% Reshape data & update dimension names +nf=ndims(data); +sz0 = size(data); +permdim = [OPTIONS.XDim setdiff(1:nf,OPTIONS.XDim)]; +ipermdim(permdim) = 1:numel(permdim); +z = permute(data,permdim); +sz=[size(z) 1]; +if isempty(OPTIONS.X) + X=1:size(z,1); +else + X=OPTIONS.X; +end +if length(OPTIONS.DimensionNames) < nf + OPTIONS.DimensionNames(end+1:nf)=regexprep(cellstr(num2str([length(OPTIONS.DimensionNames)+1:nf]')), '(.*)', 'dim_$1'); +end +for i=1:nf + if isempty( OPTIONS.DimensionNames{i} ) + OPTIONS.DimensionNames{i}=sprintf('Dim #%d',i); + if i==OPTIONS.XDim + OPTIONS.DimensionNames{i}=[ OPTIONS.DimensionNames{i} ' (X-axis)']; + end + end +end +%% Plot options +cla +sz=[size(z)]; +if numel(sz)<3 +sz=[sz 1]; +end +ncurves = prod(sz(2:end)); +if isnan(OPTIONS.ColorDim) + try + OPTIONS.ColorDim = subarray(setdiff(find(sz0>1),OPTIONS.XDim),1); + catch + OPTIONS.ColorDim = []; + end +end +if isnan(OPTIONS.LineStyleDim) + try + OPTIONS.LineStyleDim = subarray(setdiff(find(sz0>1),OPTIONS.XDim),2); + catch + OPTIONS.LineStyleDim = []; + end +end +if ~isfield(OPTIONS, 'ColorOrder') + co = get(gca, 'ColorOrder'); + d=ipermdim(OPTIONS.ColorDim); + if size(co,1)>=sz(d) + % Enough colors in the ColorOrder + OPTIONS.ColorOrder = co(1:sz(d),:); + else + OPTIONS.ColorOrder=co; + OPTIONS.ColorOrder(end+[1:ncurves-length(co)],:) = rand(ncurves-length(co),3); + end +end +if ~isfield(OPTIONS, 'LineStyleOrder') + lso = get(gca, 'LineStyleOrder'); + if ischar(lso) + lso=strread(lso, '%s', 'delimiter', '|'); + end + OPTIONS.LineStyleOrder = lso; + d=ipermdim(OPTIONS.LineStyleDim); + if length(lso)

    Journal

    1992

    1993

    1994

    1995

    1996

    1997

    1998

    1999

    2000

    2001

    2002

    NATURE22,13922,32625,46627,07428,41727,36828,83329,49125,81427,955
    SCIENCE20,96721,07422,06721,91123,60524,67624,38624,59523,87223,329
    P NATL ACAD SCI USA10,4810,32510,66710,5210,2449,049,82110,2610,7891,896
    P ROY SOC LOND B BIO3,0393,1372,792,5892,8672,8733,0332,7553,0373,192
    PHILOS T ROY SOC B2,1151,7682,192,2812,8322,5122,8862,653,5163,66
    ACTA NEUROBIOL EXP0,4130,5050,5510,480,560,7660,50,6310,767
    ACTA NEUROCHIR0,8220,7280,7480,6560,4980,6230,7481,040,8170,957
    ACTA NEUROL BELG0,3880,1630,2290,3330,3290,2530,2260,2750,521
    ACTA NEUROL SCAND1,1220,9771,1331,1421,0680,9021,1081,3251,3041,64
    ACTA NEUROPATHOL2,942,3952,1682,5462,9462,2992,3362,4022,4462,165
    ACTA NEUROPSYCHIATR0,0890,2980,0360,338
    ADV NEUROL0,9681,142
    AKTUEL NEUROL0,2930,1960,3230,2770,2350,240,2750,3140,2960,356
    AM J ELECTRONEUROD T0,0770,0940,0650,53
    AM J NEURORADIOL2,0591,8451,7331,5561,751,7071,8692,3582,1262,24
    ANN NEUROL6,327,0357,6246,6998,7159,5139,4558,3218,488,481
    ANNU REV NEUROSCI20,42227,62917,95329,08333,62521,95223,02322,6526,67627,152
    ARCH CLIN NEUROPSYCH0,750,8421,3950,988
    ARCH NEUROL-CHICAGO3,5173,1643,8514,263,7783,7793,3753,8634,3934,53
    ARQ NEURO-PSIQUIAT0,1410,1730,1970,228
    AUDIOL NEURO-OTOL2,0182,392,532
    AUDIT NEUROSCI2,461,929
    AUTON NEUROSCI-BASIC00,930
    BEHAV NEUROL0,250,2630,2560,333
    BEHAV NEUROSCI2,7292,993,2632,9692,5282,6653,0262,7192,7512,86
    BRIT J NEUROSURG0,9380,5340,5330,5460,5020,5380,5740,5390,563
    CAN J NEUROL SCI1,1811,531,7121,5441,0331,171,0741,2921,5041,317
    CELL MOL NEUROBIOL2,4122,441,6431,3491,6861,9681,961,7522,0932,12
    CESK SLOV NEUROL N0,0290,0650,0590,41
    CHILD NEUROPSYCHOL0,3140,9410,60,565
    CLIN NEUROL NEUROSUR0,3530,3260,4890,5940,6190,6130,4060,5640,6190,595
    CLIN NEUROPATHOL1,0491,3180,840,9350,7470,6360,9060,9681,0120,553
    CLIN NEUROPHARMACOL1,5682,1872,0711,5321,2511,6041,6362,0161,9431,533
    CLIN NEUROPHYSIOL1,6721,922
    CLIN NEUROPSYCHOL0,6740,5610,9480,551
    CLIN NEUROSCI0,6561,3331,3531,751,0141,781
    COGN NEUROPSYCHOL3,397
    COGNITIVE NEUROPSYCH2,92,4922,18
    CRIT REV NEUROBIOL7,7785,750
    CRIT REV NEUROSURG0,0940,2750,2330,227
    CURR OPIN NEUROBIOL5,3457,0797,3568,8549,2879,2779,995
    CURR OPIN NEUROL0,4730,9411,3951,8691,8552,4972,9933,1764,35
    DEV MED CHILD NEUROL1,1021,2081,171,1771,7781,5631,3161,4711,781,988
    DEV NEUROPSYCHOL1,361,0830,8710,914
    DEV NEUROSCI-BASEL0,7271,0380,6130,7561,2211,5961,672,1532,5231,93
    ELECTROEN CLIN NEURO1,8721,6991,6171,8942,0272,42,452,8613,327
    EUR J NEUROL0,6410,6080,670,9521,358
    EUR J NEUROSCI4,824,564,9514,9214,3033,9473,823,8993,8623,919
    EUR NEUROL1,3511,5630,8990,9151,2571,221,051,37911,179
    EUR NEUROPSYCHOPHARM1,9332,3281,6241,8521,7782,3872,0452,437
    EXP NEUROL3,1442,9582,8033,4513,6043,452,8633,263,8583,53
    FOLIA NEUROPATHOL0,2110,3380,2580,471
    FORTSCHR NEUROL PSYC0,430,5240,6170,670,5870,5170,4410,6020,6360,593
    FRONT NEUROENDOCRIN6,2616,0458,2387,875,7787,1117,257,8268,37515
    FUNCT NEUROL0,4120,5310,710,463
    GIORN NEUROPSI EVOL0,0310
    GIORN NEUROPSICOFARM000
    INT J DEV NEUROSCI1,7481,3391,5571,6481,3521,4061,4971,4251,5832,156
    INT J NEUROPSYCHOPH1,8331,3232,779
    INT J NEURORADIOL0,4150,3590,1390,238
    INT J NEUROSCI0,5270,622
    INT REV NEUROBIOL4,86,5881,8282,43,52,9441,78
    INTERV NEURORADIOL0,5850,441
    ITAL J NEUROL SCI0,1880,1980,3390,2160,2610,4180,4210,3490,1620,635
    J CHEM NEUROANAT3,3541,6171,9432,0921,5691,6672,7112,5522,1411,649
    J CHILD NEUROL1,3471,1320,920,9240,8970,9180,9291,0951,1341,393
    J CLIN EXP NEUROPSYC1,8142,8212,1561,5851,2011,3171,3671,2651,0671,14
    J CLIN NEUROPHYSIOL1,5831,5891,4251,1552,1641,4591,5841,9152,1732,538
    J CLIN NEUROSCI0,3850,320,2620,2920,1440,1780,392
    J COGNITIVE NEUROSCI3,5714,0634,3833,6794,8445,225,7935,1156,736
    J COMP NEUROL3,5553,6573,4683,5843,7483,7583,4763,7643,7723,515
    J COMPUT NEUROSCI2,6112,6222,2862,3462,596
    J INT NEUROPSYCH SOC2,3762,340
    J MOL NEUROSCI2,3512,4671,8751,4581,5222,0771,9121,6091,7651,688
    J NEUROBIOL2,8132,4094,1184,5684,8062,8142,8713,333,4653,34
    J NEUROCHEM4,2154,2234,5254,8524,2194,2344,6514,9064,94,834
    J NEUROCYTOL2,4912,3842,562,5362,6791,9732,1541,861,2311,776
    J NEUROENDOCRINOL2,3912,6162,5392,9892,8562,5632,8322,3772,5982,58
    J NEUROGENET1,9092,3642,51,6671,2350,9571,3571,6921,9381,125
    J NEUROIMAGING0,8350,6521,0440,9740,9420,926
    J NEUROIMMUNOL2,9642,9553,1883,6393,0832,8453,2853,2333,3553,342
    J NEUROL1,7361,791,8491,5811,9441,9762,32,8462,0612,653
    J NEUROL NEUROSUR PS2,6962,2612,5342,5042,933,0412,9382,7352,8463,24
    J NEUROL ORTH MED S0,0160,4
    J NEUROL REHABIL0,220,2980,385
    J NEUROL SCI2,3852,1651,7761,8671,7351,4531,8381,6851,6781,986
    J NEUROLINGUIST0,3780,9090,5140,722
    J NEURO-ONCOL1,3411,2771,0960,7771,1111,2321,6411,6551,5811,435
    J NEURO-OPHTHALMOL0,0240,6240,3890,2730,3740,2520,778
    J NEUROPATH EXP NEUR4,9044,2735,3675,8314,7844,2535,3665,595,5655,533
    J NEUROPHYSIOL3,8744,1344,0013,5783,8343,2633,4113,9353,8553,517
    J NEUROPSYCH CLIN N1,7372,0371,5731,7951,6973,2092,141,632
    J NEURORADIOLOGY0,3270,3330,4260,3040,3770,5880,3680,5220,62
    J NEUROSCI7,168,0358,6578,2057,9557,9158,4038,9558,5028,178
    J NEUROSCI METH1,5671,5231,4421,8151,4121,3351,431,3621,4771,659
    J NEUROSCI RES3,1973,0053,6373,3772,8432,4422,8743,1263,2073,378
    J NEUROSURG2,5152,6883,2963,0122,7692,9993,3723,3462,9182,748
    J NEUROSURG ANESTH0,6380,8311,4331,6521,2371,3331,1440,9340,9370,739
    J NEUROTRAUM2,1442,8023,0153,0863,0193,4042,8773,952
    J NEUROVIROL1,8332,4571,7292,0983,3972,71
    J PSYCHIATR NEUROSCI0,9661,6962,0392,516
    JNMS-J NEUROMUSC SYS0,1110,1250,1030,111
    KLIN NEUROPHYSIOL0,3210,2030,156
    MINIM INVAS NEUROSUR0,0770,5250,6490,6920,7440,667
    MOL CELL NEUROSCI3,3072,5293,2596,086,0895,6545,7465,446
    MOL CHEM NEUROPATHOL0,6361,0381,2451,1511,0640,780,881,0941,163
    MOL NEUROBIOL0,8112,2863,2551,62,3853,4834,3885,6234,3822,4
    NAT NEUROSCI8,86312,63615,668
    NAT REV NEUROSCI014,353
    NEUROBIOL AGING3,5273,4343,1012,112,1212,8723,5173,3994,1594,49
    NEUROBIOL DIS2,6114,2254,8695,0235,3334,487
    NEUROBIOL LEARN MEM1,5742,1221,8412,8053,041,83
    NEUROCASE1,0121,2681,8711,312
    NEUROCHEM INT1,8551,5181,4342,2361,9881,7051,7812,1752,6623,4
    NEUROCHEM RES1,4321,591,5761,5771,2921,311,6771,7551,8581,638
    NEUROCHIRURGIE0,4040,4290,5050,2810,2830,3880,3090,3360,390,346
    NEUROCIRUGIA0,0570,1850,1540,13
    NEUROCOMPUTING0,2430,6090,5770,4220,4530,360,6360,534
    NEUROENDOCRINOLOGY2,8412,7022,4222,6842,4452,44133,2142,7442,144
    NEUROEPIDEMIOLOGY0,9490,78611,0471,6541,3971,3531,8031,6541,39
    NEUROGASTROENT MOTIL1,3551,51,7211,6921,9362,0812,5
    NEUROGENETICS1,872,0942,5963,69
    NEUROIMAG CLIN N AM0,6770,831,0950,957
    NEUROIMAGE2,7860,7781,6174,3115,6118,936,8577,879
    NEUROIMMUNOMODULAT0,741,6092,7011,989
    NEUROL CLIN1,1551,0421,7610,8950,9441,331,3751,5660,8961,283
    NEUROL CROATICA0,09400,032
    NEUROL INDIA0,10,0570,0920,173
    NEUROL MED-CHIR0,386
    NEUROL PSYCHIAT BR0,2160,3330,278
    NEUROL RES0,6970,8521,1421,0271,1281,10,8661,176
    NEUROL SCI00,380
    NEUROL SURG TOKYO0,0920,1580,1560,12
    NEUROLOGIST0,3120,3970,2720,22
    NEUROLOGY4,3553,994,3474,6334,6124,5264,9725,2324,7815,212
    NEUROMODULATION2,0951,2161,96
    NEUROMUSCULAR DISORD1,4771,8182,7182,3512,5822,7492,7182,547
    NEURON15,58917,25618,34816,61916,95315,82116,50516,78215,08114,153
    NEURO-OPHTHALMOLOGY0,4570,2980,3720,2890,3740,3680,3610,450,3760,176
    NEURO-ORTHOPEDICS0,2730,222
    NEUROPATH APPL NEURO2,3262,3761,9831,8892,2042,1211,5912,5752,5232,963
    NEUROPATHOLOGY0,6910,4130,4870,575
    NEUROPEDIATRICS0,9261,20,7291,0081,3891,9851,5451,6691,5971,476
    NEUROPEPTIDES1,7972,1642,0261,6010,9640,9051,241,0751,4131,959
    NEUROPHARMACOLOGY2,2461,9972,4243,6344,4583,9093,244,1764,1253,854
    NEUROPHYSIOL CLIN0,4690,350,4330,4220,3690,5320,5161,33
    NEUROPHYSIOLOGY0,1140,1770
    NEUROPHYSIOLOGY+0,0270,870
    NEUROPSY NEUROPSY BE0,9740,6880,7930,6950,50,7580,8961,347
    NEUROPSYCHIATRIE0,1250,2350,3870,135
    NEUROPSYCHOBIOLOGY0,3680,3940,9861,050,8550,8210,8460,9531,561,64
    NEUROPSYCHOL REHABIL0,9470,6090,6670,759
    NEUROPSYCHOL REV0,7271,1821,250,92
    NEUROPSYCHOLOGIA1,8852,0531,9661,8282,0592,2672,9392,6872,7783,32
    NEUROPSYCHOLOGY2,6133,1132,7022,486
    NEUROPSYCHOPHARMACOL3,6612,5212,783,5673,9364,1054,3184,8584,5794,715
    NEURORADIOLOGY1,0510,9350,90,8580,9970,7541,0231,2870,9971,97
    NEUROREHAB NEURAL RE0,190,610
    NEUROREPORT2,2773,0792,572,4872,2622,5912,6822,6962,374
    NEUROSCI BIOBEHAV R2,4962,8573,7932,992,7863,3163,5953,3825,212
    NEUROSCI LETT2,4192,6452,7032,3182,091,7681,9342,0852,0912,21
    NEUROSCI RES1,9782,0592,1412,1652,1021,5231,7751,7261,8071,77
    NEUROSCI RES COMMUN0,9691,1910,9170,871,0740,8740,7420,9890,5380,67
    NEUROSCIENCE4,3244,5824,6264,2883,9083,5943,5913,9243,5633,219
    NEUROSCIENTIST2,1742,2331,9181,42
    NEUROSURG CLIN N AM0,8710,9220,9331,2650,783
    NEUROSURG QUART0,50,7670,50,217
    NEUROSURG REV0,3720,290,2560,1560,20,1610,3270,2860,3580,478
    NEUROSURGERY1,5541,4361,671,0060,7311,1132,42,8212,8992,783
    NEUROTOXICOL TERATOL1,5711,7031,8961,5451,9791,1871,321,8222,288
    NEUROTOXICOLOGY0,8951,3841,511,3631,2841,7281,2051,2821,741,576
    NEUROUROL URODYNAM0,7271,1920,7161,3432,1222,082,4182,4381,9682,266
    PEDIATR NEUROL0,9171,2561,0341,0780,8250,9180,9211,1191,0071,11
    PEDIATR NEUROSURG0,4490,3290,5320,7040,770,6560,7960,7350,8110,787
    PERSPECT DEV NEUROBI2,7231,2622,3171,581
    PROG NEUROBIOL7,8097,48186,1845,88855,6887,099,9339,377
    PROG NEURO-PSYCHOPH0,9460,8811,1410,9140,8870,8191,1141,3891,0781,58
    PSYCHIAT CLIN NEUROS0,1380,1910,3820,3480,4520,548
    PSYCHIAT RES-NEUROIM1,2861,9771,6671,7071,9322,1761,3292,191,9192,354
    PSYCHONEUROENDOCRINO1,5711,4861,4831,6992,2541,9921,8692,0453,0083,369
    RESTOR NEUROL NEUROS2,6091,4350,9150,561,1171,1960,50,9110,678
    REV ECUAT NEUROL0,17200,1060,4
    REV NEUROL0,8130,8820,6550,5140,5351,111,451,0130
    REV NEUROL-FRANCE0,6920,598
    REV NEUROLOGIA0,2650,2560,26
    REV NEUROPSYCHOL0,2110,1750,2860,262
    REV NEUROSCI1,7593,3873,43,794
    RIV NEURORADIOL0,0630,1060,0510,128
    SEMIN NEUROL0,6630,5650,4240,5640,6950,5110,671,3661,6351,49
    SEMIN NEUROSCI1,9892,1722,0833,3253,5962,71,667
    STEREOT FUNCT NEUROS0,2840,4460,6570,672
    SURG NEUROL0,7140,6850,6250,5290,3420,3280,7241,041,0180,936
    TECH NEUROSURG0,2150,290
    TRENDS NEUROSCI15,42617,29920,19419,97217,75517,08418,46319,92517,41716,475
    VISUAL NEUROSCI2,162,2122,2722,1372,491,8872,1862,2042,1492,351
    ZBL NEUROCHIR0,2220,40,9390,688
    ACTA NEUROPSYCHIATR0,0890,2980,0360,338
    ACTA PSYCHIAT SCAND1,31,1421,4951,4181,5951,5881,5541,6191,7742,17
    AM J GERIAT PSYCHIAT2,436
    AM J ORTHOPSYCHIAT0,921,0620,8591,0161,0541,7182,2671,4441,9391,55
    AM J PSYCHIAT4,8244,9544,575,126,0696,5015,9396,3416,5776,913
    ANN MED-PSYCHOL0,1050,0550,1120,1440,1920,210,2080,2380,236
    ANNU REV PSYCHOL5,7255,556,8816,8215,4394,8416,3957,5455,8515,979
    ARCH CLIN NEUROPSYCH0,750,8421,3950,988
    ARCH GEN PSYCHIAT8,2279,50511,41611,15511,50910,7519,39810,95211,77811,981
    AUST NZ J PSYCHIAT0,7840,6240,6350,6450,8570,5740,8471,1971,2650,858
    BIOL PSYCHIAT2,572,6012,1482,3522,4842,2542,4053,3194,2695,55
    BIOL PSYCHOL1,0261,0561,81,4741,51,778
    BRIT J MED PSYCHOL0,6710,4790,4590,5920,9390,6670,7020,8810,5620,674
    BRIT J PSYCHIAT2,3092,3192,862,9513,3933,2653,5034,0934,8274,143
    CAN J PSYCHIAT0,70,5430,5470,7180,8321,1511,0581,4621,6231,624
    CHILD NEUROPSYCHOL0,3140,9410,60,565
    CHILD PSYCHIAT HUM D0,2130,2340,3540,5320,3410,4880,4520,3780,4220,625
    CLIN NEUROPSYCHOL0,6740,5610,9480,551
    COGN NEUROPSYCHOL3,397
    COGNITIVE NEUROPSYCH2,92,4922,18
    COGNITIVE PSYCHOL3,7374,1673,4633,711
    COMPR PSYCHIAT1,4451,0711,2731,6221,521,2461,2341,6881,41,28
    DEV NEUROPSYCHOL1,361,0830,8710,914
    DEV PSYCHOBIOL1,1441,1881,4211,0411,2031,4351,5961,3121,3221,286
    EDUC PSYCHOL MEAS0,3240,2280,3680,3170,3160,4440,6180,6230,6080,789
    EUR NEUROPSYCHOPHARM1,9332,3281,6241,8521,7782,3872,0452,437
    EUR PSYCHIAT0,5440,4970,3940,5910,7481,72
    EXP CLIN PSYCHOPHARM1,511,251,7471,726
    GEN HOSP PSYCHIAT0,7390,921,1161,0281,3371,2981,1441,41,5122,65
    HUM PSYCHOPHARM CLIN0,8180,8540,8690,850,9410,5840,6091,1030,968
    INT CLIN PSYCHOPHARM0,750,8161,3642,2271,6381,9261,5571,0962,0762,295
    INT J GERIATR PSYCH1,5631,4951,778
    INT J NEUROPSYCHOPH1,8331,3232,779
    INT J PSYCHIAT MED0,7970,5520,7410,9181,0820,9440,631,2071,0330,714
    INT J PSYCHOPHYSIOL0,870,6840,8750,5851,0890,7031,1481,8291,4891,747
    INT J SPORT PSYCHOL0,2760,4790,8040,386
    J APPL SPORT PSYCHOL0,970
    J CHILD ADOL PSYCHOP1,3881,9331,6092,11,9821,754
    J CLIN PSYCHIAT2,5542,7693,133,3374,2934,0034,0734,1724,4544,735
    J CLIN PSYCHOPHARM3,6793,3663,4964,9635,35,0945,3385,7385,0524,13
    J COMP PSYCHOL1,2021,4041,5171,4091,6021,5631,7441,571,4771,663
    J EXP PSYCHOL ANIM B1,7752,562,5161,1591,9831,3971,2671,441,3651,95
    J EXP PSYCHOL HUMAN2,4062,3322,2472,498
    J EXP PSYCHOL LEARN2,8383,2052,7532,21
    J GERIATR PSYCH NEUR0,8021,2721,2220,8810,9091,263
    J GERONTOL B-PSYCHOL1,5542,3311,2241,5431,1021,594
    J INT NEUROPSYCH SOC2,3762,340
    J MATH PSYCHOL1,4210,8640,80,7660,780,7920,660,6921,0561,195
    J NEUROPSYCH CLIN N1,7372,0371,5731,7951,6973,2092,141,632
    J PSYCHIAT RES1,4031,0181,0851,5312,1591,6051,3622,1522,332,776
    J PSYCHIATR NEUROSCI0,9661,6962,0392,516
    J PSYCHOPHARMACOL1,3081,8881,4261,3811,4662,8272,3282,642
    J PSYCHOPHYSIOL0,5930,5370,6850,587
    J PSYCHOSOM OBST GYN0,4240,6480,3020,6270,5560,60,6060,7170,5290,67
    J PSYCHOSOM RES1,2220,9741,1021,1611,5841,0061,1291,5731,4461,717
    MOL PSYCHIATR2,1624,7567,9428,9276,25
    NEUROL PSYCHIAT BR0,2160,3330,278
    NEUROPSYCHIATRIE0,1250,2350,3870,135
    NEUROPSYCHOBIOLOGY0,3680,3940,9861,050,8550,8210,8460,9531,561,64
    NEUROPSYCHOL REHABIL0,9470,6090,6670,759
    NEUROPSYCHOL REV0,7271,1821,250,92
    NEUROPSYCHOLOGIA1,8852,0531,9661,8282,0592,2672,9392,6872,7783,32
    NEUROPSYCHOLOGY2,6133,1132,7022,486
    NEUROPSYCHOPHARMACOL3,6612,5212,783,5673,9364,1054,3184,8584,5794,715
    PERCEPT PSYCHOPHYS1,2521,492
    PHARMACOPSYCHIATRY1,1331,41,61,1171,2182,0532,3042,8032,39
    PROG NEURO-PSYCHOPH0,9460,8811,1410,9140,8870,8191,1141,3891,0781,58
    PSYCHIAT CLIN NEUROS0,1380,1910,3820,3480,4520,548
    PSYCHIAT RES1,8751,4741,6921,4731,4891,3271,4241,5511,5571,775
    PSYCHIAT RES-NEUROIM1,2861,9771,6671,7071,9322,1761,3292,191,9192,354
    PSYCHIATR GENET1,862,0162,6091,257
    PSYCHIATR SERV0,9351,4661,571,7471,7951,589
    PSYCHIATRY0,6230,6670,9031,260,7310,7681,2550,9440,8271,2
    PSYCHOBIOLOGY2,0711,7911,5380,9521,2321,0510,8151,2821,1941,652
    PSYCHOL BULL4,9585,1976,6976,9666,5916,0386,3467,796,9136,87
    PSYCHOL MED2,1772,6632,4332,7152,8153,0173,1243,3893,4123,119
    PSYCHOL REV6,4856,57,1875,0585,217,068,2396,8036,0695,756
    PSYCHOMETRIKA0,7870,6360,6230,7680,8330,6560,9330,8240,8980,85
    PSYCHONEUROENDOCRINO1,5711,4861,4831,6992,2541,9921,8692,0453,0083,369
    PSYCHO-ONCOL1,388
    PSYCHOPATHOLOGY0,4330,3270,4460,4510,7070,5750,4740,4250,5470,469
    PSYCHOPHARMACOL BULL1,6711,7291,4391,4311,5711,8162,592,2452,809
    PSYCHOPHARMACOLOGY2,7692,52,5492,8822,5992,9333,0322,9182,8043,145
    PSYCHOPHARMAKOTHERAP0,1970,3010,171
    PSYCHOPHYSIOLOGY2,6382,3492,6592,9482,8272,7742,4323,0063,1063,35
    PSYCHOSOM MED2,6932,3112,8082,9123,0313,0893,0462,6243,2462,815
    PSYCHOSOMATICS1,41,0711,1691,3212,1321,7071,5411,1741,5551,926
    PSYCHOTHER PSYCHOSOM0,3760,7521,0591,0471,5781,8052,1032,2592,3723,429
    Q J EXP PSYCHOL-A1,9481,5611,4521,28
    Q J EXP PSYCHOL-B1,31,1321,2751,1281,3491,4191,221,33311,4
    REV NEUROPSYCHOL0,2110,1750,2860,262
    SPORT PSYCHOL0,8730,9670,6490,76
    Z PSYCHOSOM MED PSYC0,3590,5760,460,4910,440,371,2090,4440,633
    BEHAV BRAIN RES1,8541,4441,5631,8621,6132,342,7962,3182,2632,473
    BEHAV BRAIN SCI0,2970,4260,63915,62598,1188,811,27614,2517,312
    BRAIN3,5893,8935,0354,8675,7395,3815,9527,3747,3037,47
    BRAIN BEHAV EVOLUT1,5491,9591,4171,5771,491,7861,5591,651,3811,635
    BRAIN BEHAV IMMUN2,7592,5242,3191,4841,951,752,1111,6882,1842,23
    BRAIN COGNITION1,51,231,3731,111,0730,5660,5080,7360,630,791
    BRAIN DEV-JPN0,7870,5760,6880,7090,5390,570,7611,3031,1551,49
    BRAIN INJURY0,880,8431,2561,0851,0170,9140,924
    BRAIN LANG1,1970,7130,9761,6581,41,5792,1161,9291,4731,38
    BRAIN PATHOL4,258,5676,4555,6634,8974,1546,4358,654
    BRAIN RES2,8652,8542,8692,6872,5262,1192,152,3022,5262,489
    BRAIN RES BULL1,6921,5991,8111,7941,6411,7081,651,9771,7581,783
    BRAIN RES PROTOC0,2820,7811,0671,284
    BRAIN RES REV9,7429,09410,20713,9627,61911,5536,256,6519,2127,72
    BRAIN TOPOGR0,5771,9061,5961,745
    COGNITIVE BRAIN RES0,882,2221,731,5762,7552,3332,7332,884
    DEV BRAIN DYSFUNCT0,1480,213
    DEV BRAIN RES2,3222,1772,2072,2461,9111,8231,6951,5351,8271,662
    EXP BRAIN RES2,3442,4092,2271,9672,0491,8982,0182,2462,1372,36
    HUM BRAIN MAPP10,0494,7385,285,1635,688
    J BRAIN RES0,3970,6710,2790,182
    METAB BRAIN DIS0,7321,1221,0290,631,751,311,53711,4111,91
    MOL BRAIN RES4,5223,8014,1343,483,4772,7422,4752,5392,6222,538
    PROG BRAIN RES1,8881,2611,4121,6821,4880,7721,951,4252,521,49
    + + + + + + + + + + + + + + + + + + + +
    + Products & Services + + Solutions + + Academia + + Support + + User Community + + Company +
    spacerspacerspacerspacerspacerspacer
    + + + + + + +
    + + + + +
    +
    +

    Page Not Found

    +

    We apologize for the inconvenience. The link you followed is no longer valid. The MathWorks Web site is continually being updated, and we have either removed or relocated the page that you requested.

    + To find the information you need: +
      +
    • Visit our home page
    • +
    • Reference our site map
    • +
    • Select the global navigation links at the top of the page
    • +
    • Use the advanced site search below
    • +
    +

    If you were sent to this page from a link on www.mathworks.com, please inform the webmaster.

    + +

    Advanced Search

    + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + +
    Find results
    +  Results Per Page +
    search tips
    + +
    + + To narrow your search, select one or more categories.
    + By default, the entire Web site is searched. + +
    + + + + + + + + + + + + + + + + +
    +
    + +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    + + + +
    +
    + +
    +
    +
    +
    + +
    +
    +
    + + + +
    +
    +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    + +
    +
    +
    + + + + +
    + +
    +
    + +
    + + +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scalpface.m b/scalpface.m new file mode 100644 index 0000000..a0582e2 --- /dev/null +++ b/scalpface.m @@ -0,0 +1,63 @@ +function [face_t]=scalpface(scalp) +% Convert scalp (a patch structure) to head.mat to be used in EEGLAB +% cf. headplot() + +if 0 %s4head + %Vertices in the face + face_v=find(scalp.vertices(:,3)<-.01 & scalp.vertices(:,1)>.025 ); + % remove also the basal part + x1=-0.0960; + y1=0.0533; + x2=0.0264; + y2=-0.0308; + face_v=[face_v ; find(scalp.vertices(:,3) <= (y2-y1)/(x2-x1)*(scalp.vertices(:,1)-x1)+y1)]; + % Which triangles encompass these vertices + face_t=[]; + for v=face_v' + face_t=[face_t; find(scalp.faces(:,1)==v | scalp.faces(:,2)==v |scalp.faces(:,3)==v )]; + end + face_t=unique(scalp.faces(face_t,:), 'rows'); + %[ign,face_t]=intersect(scalp.faces(:),face_v); + %[face_t, ign]=ind2sub([length(scalp.faces), 3] , face_t); + %face_t=scalp.faces(face_t,:); + scalp_t=setdiff(scalp.faces, face_t, 'rows'); +end + +% ------------------------- +% Head1 or nicehead +% ------------------------- + nv=size(scalp.vertices,1); +% Distance au centre 1 +d1=sqrt(sum((scalp.vertices(:,[1 3])-repmat([70 230],nv,1)).^2, 2)); +% Distance au centre 2 +d2=sqrt(sum((scalp.vertices(:,[1 3])-repmat([105 -75],nv,1)).^2, 2)); +scalp_v=find((d1 < 265 & d2 > 95 & scalp.vertices(:,[3])> -30)); + +% nicehead: +% scalp_v=find((d1 < 255 & d2 > 95 & scalp.vertices(:,[3])> -30) | (scalp.vertices(:,[3]) > -30 & scalp.vertices(:,[2]) < -69)); + +scalp_t=find(ismember(scalp.faces(:,1), scalp_v) | ismember(scalp.faces(:,2), scalp_v) | ismember(scalp.faces(:,3), scalp_v)); +scalp_t=scalp.faces(scalp_t,:); + +% For NiceHead, add the following line: +% scalp_t=scalp_t([1:629 632:end],:); + +face_t=setdiff(scalp.faces, scalp_t, 'rows'); + + +POS=scalp.vertices; +TRI1=scalp_t; +index1=unique(scalp_t(:)); +TRI2=face_t; +HeadCenter=[0 0 0]; +save head1.mat POS TRI1 TRI2 HeadCenter index1 + +return +% in mhead.mat +% POS 2678x3 64272 double array : vertices +% TRI1 2697x3 64728 double array : scalp +% TRI2 2305x3 55320 double array : face +% center 1x3 24 double array : center shouldn't it be HeadCenter ? +% index1 1612x1 12896 double array : vertices of the scalp + + diff --git a/scrollaxes.m b/scrollaxes.m new file mode 100644 index 0000000..5928d38 --- /dev/null +++ b/scrollaxes.m @@ -0,0 +1,421 @@ +function scrollaxes(arg,arg2) +%scrollaxes Interactively scrollte the view of a 3-D plot. +% scrollaxes ON turns on mouse-based scroll +% scrollaxes OFF turns if off. +% scrollaxes by itself toggles the state. +% +% scrollaxes(FIG,...) works on the figure FIG. +% scrollaxes(AXIS,...) works on the axis AXIS. +% In this latter case, ONLY this axis will be scrollted. +% +% See also ZOOM. + +% scrollaxes on enables text feedback +% scrollaxes ON disables text feedback. + +% Revised by Rick Paxson 10-25-96 +% Clay M. Thompson 5-3-94 +% Copyright 1984-2002 The MathWorks, Inc. +% $Revision: 1.46 $ $Date: 2002/06/14 18:45:51 $ +% KND : 2005-07-06 : Rotate ONLY pre-specified axis, if any. + +if(nargin == 0) + setState(gcf,'toggle'); +elseif nargin==1 + if ishandle(arg) + setState(arg,'toggle') + else + switch(lower(arg)) % how much performance hit here + case 'motion' + scrollMotionFcn + case 'down' + scrollButtonDownFcn + case 'up' + scrollButtonUpFcn + case 'on' + setState(gcf,arg); + case 'off' + setState(gcf,arg); + otherwise + error('Unknown action string.'); + end + end +elseif nargin==2 + if ~ishandle(arg), error('Unknown figure.'); end + switch(lower(arg2)) % how much performance hit here + case 'on' + setState(arg,arg2) + case 'off' + setState(arg,arg2); + otherwise + error('Unknown action string.'); + end +end + +%-------------------------------- +% Set activation state. Options on, ON, off +function setState(target,state) + +% if the target is an axis, restrict to that +if strcmp(get(target,'Type'),'axes') + axis = target; + fig = get(axis,'Parent'); +else % otherwise, allow any axis in this figure + axis = []; + fig = target; +end +scrollObj = findobj(allchild(fig),'Tag','scrollObj'); + +% %KND : Add Button Rotate3DAxes in figure's toolbar +% h=findall(fig,'Tag','figToolRotate3DAxes'); +% if isempty(h) +% %disp('Creating Rotate3DAxes button'); +% h=findall(fig,'Tag','figToolRotate3D'); +% h=copyobj(h, get(h, 'Parent')); +% set(h, 'Tag','figToolRotate3DAxes'); +% set(h, 'ClickedCallback', 'scrollaxes(gca), putdowntext(''scrollaxes'',gcbo)'); +% c=get(h, 'CData'); +% c(6:12,6:12,:)=1; +% c(11,6:12,:)=0; +% c(6:12,7,:)=0; +% set(h, 'CData', c); +% end + +if(strcmp(state,'toggle')) + if(~isempty(scrollObj)) + setState(target,'off'); + else + setState(target,'on'); + end + return; +elseif(strcmp(lower(state),'on')) + if(isempty(scrollObj)) + plotedit(fig,'locktoolbarvisibility'); + scrollObj = makeRotaObj(fig); + if isempty(axis) +% set(findall(fig,'Tag','figToolRotate3D'),'State','on'); + set(findall(fig,'Tag','figToolScrollAxes'),'State','off'); + else +% set(findall(fig,'Tag','figToolRotate3D'),'State','off'); + set(findall(fig,'Tag','figToolScrollAxes'),'State','on'); + end + end + + rdata = getappdata(scrollObj,'ScrollData'); + rdata.destAxis = axis; + + + % Handle toggle of text feedback. ON means no feedback on means feedback. + if(strcmp(state,'on')) + rdata.textState = 1; + else + rdata.textState = 0; + end + setappdata(scrollObj,'ScrollData',rdata); + % set this so we can know if Rotate3d is on + % for now there is only one on state + % and this app data will not exist if it is off + setappdata(fig,'ScrollAxesOnState','on'); + scribefiglisten(fig,'on'); +elseif(strcmp(lower(state),'off')) + scribefiglisten(fig,'off'); +% set(findall(fig,'Tag','figToolScroll'),'State','off'); +% set(findall(fig,'Tag','figToolRotate3DAxes'),'State','off'); + if(~isempty(scrollObj)) + destroyRotaObj(scrollObj); + end + % get rid of on state appdata + % if it exists. + % if isappdata(fig,'Rotate3dOnState') + % rmappdata(fig,'Rotate3dOnState'); + % end + + scribefiglisten(fig,'off'); + state = getappdata(fig,'ScrollAxesFigureState'); + if ~isempty(state) + % since we didn't set the pointer, + % make sure it does not get reset + ptr = get(fig,'pointer'); + % restore figure and non-uicontrol children + % don't restore uicontrols because they were restored + % already when scroll was turned on + uirestore(state,'nouicontrols'); + set(fig,'pointer',ptr) + if isappdata(fig,'ScrollAxesFigureState') + rmappdata(fig,'ScrollAxesFigureState'); + end + end + if isappdata(fig,'ScrollAxesOnState') + rmappdata(fig,'ScrollAxesOnState'); + end +end + +%--------------------------- +% Button down callback +function scrollButtonDownFcn +scrollObj = findobj(allchild(gcbf),'Tag','scrollObj'); +if(isempty(scrollObj)) + return; +else + rdata = getappdata(scrollObj,'ScrollData'); + + %KND: Rotate ONLY the axes which were + if ~isempty(rdata.destAxis) & ~isequal(gca, rdata.destAxis) + return + end + + % Activate axis that is clicked in + allAxes = findobj(datachildren(gcbf),'flat','type','axes'); + axes_found = 0; + funits = get(gcbf,'units'); + set(gcbf,'units','pixels'); + for i=1:length(allAxes), + ax=allAxes(i); + cp = get(gcbf,'CurrentPoint'); + aunits = get(ax,'units'); + set(ax,'units','pixels') + pos = get(ax,'position'); + set(ax,'units',aunits) + if cp(1) >= pos(1) & cp(1) <= pos(1)+pos(3) & ... + cp(2) >= pos(2) & cp(2) <= pos(2)+pos(4) + axes_found = 1; + set(gcbf,'currentaxes',ax); + break + end % if + end % for + set(gcbf,'units',funits) + if axes_found==0, return, end + + if (not(isempty(rdata.destAxis)) & rdata.destAxis ~= ax) + return + end + + rdata.targetAxis = ax; + rdata.XLim = get(ax,'XLim'); + + % store the state on the zlabel: that way if the user + % plots over this axis, this state will be cleared and + % we get to start over. +% viewData = getappdata(get(ax,'ZLabel'),'ROTATEAxesView'); +% if isempty(viewData) +% setappdata(get(ax,'ZLabel'),'ROTATEAxesView', get(ax, 'View')); +% end + + selection_type = get(gcbf,'SelectionType'); + if strcmp(selection_type,'open') + % this assumes that we will be getting a button up + % callback after the open button down + lims = getappdata(get(ax,'ZLabel'),'ROTATEAxesView'); + if(rdata.textState) + set(rdata.textBoxText,'String',... + sprintf('X: %4.0f %4.0f',lims)); + end + set(scrollObj, 'XLim', lims); + return + end + + rdata.oldFigureUnits = get(gcbf,'Units'); + set(gcbf,'Units','pixels'); + rdata.oldPt = get(gcbf,'CurrentPoint'); + rdata.oldLims = [ get(rdata.targetAxis,'XLim') ; get(rdata.targetAxis,'YLim')]; + + setappdata(scrollObj,'ScrollData',rdata); + setOutlineObjToFitAxes(scrollObj); + copyAxisProps(rdata.targetAxis, scrollObj); + + rdata = getappdata(scrollObj,'ScrollData'); + setappdata(scrollObj,'ScrollData',rdata); + + if(rdata.textState) + fig_color = get(gcbf,'Color'); + % if the figure color is 'none', setting the uicontrol + % backgroundcolor to white and the foreground accordingly. + if strcmp(fig_color, 'none') + fig_color = [1 1 1]; + end + c = sum([.3 .6 .1].*fig_color); + set(rdata.textBoxText,'BackgroundColor',fig_color); + if(c > .5) + set(rdata.textBoxText,'ForegroundColor',[0 0 0]); + else + set(rdata.textBoxText,'ForegroundColor',[1 1 1]); + end + set(rdata.textBoxText,'Visible','on'); + end + set(rdata.outlineObj,'Visible','on'); + set(gcbf,'WindowButtonMotionFcn','scrollaxes(''motion'')'); +end + +%------------------------------- +% Button up callback +function scrollButtonUpFcn +scrollObj = findobj(allchild(gcbf),'Tag','scrollObj'); +if isempty(scrollObj) | ... + ~strcmp(get(gcbf,'WindowButtonMotionFcn'),'scrollaxes(''motion'')') + return; +else + set(gcbf,'WindowButtonMotionFcn',''); + rdata = getappdata(scrollObj,'ScrollData'); + set([rdata.outlineObj rdata.textBoxText],'Visible','off'); + rdata.XLim = get(scrollObj,'XLim'); + set(rdata.targetAxis,'XLim',rdata.XLim); +% set(gcbf,'Units',rdata.oldFigureUnits); + setappdata(scrollObj,'ScrollData',rdata) +end + +%----------------------------- +% Mouse motion callback +function scrollMotionFcn +scrollObj = findobj(allchild(gcbf),'Tag','scrollObj'); +rdata = getappdata(scrollObj,'ScrollData'); +switch get(get(rdata.targetAxis, 'Parent'), 'selectiontype') + case {'extend'} + new_pt = get(gcbf,'CurrentPoint'); + old_pt = rdata.oldPt; + dx = new_pt(1) - old_pt(1); + dy = new_pt(2) - old_pt(2); + lims = mappingFunction(rdata, dx, dy); + set(scrollObj,'XLim',lims); + + otherwise + return + plims=get(rdata.targetAxis,'XLim'); + axis(rdata.targetAxis, 'normal'); + axis(rdata.targetAxis, 'tight'); + lims=get(rdata.targetAxis,'XLim'); + set(rdata.targetAxis,'XLim',plims); + set(scrollObj,'XLim',lims); + +end + +% if(new_azel(2) < 0 & rdata.crossPos == 0) +% set(rdata.outlineObj,'ZData',rdata.scaledData(4,:)); +% rdata.crossPos = 1; +% setappdata(scrollObj,'ScrollData',rdata); +% end +% if(new_azel(2) > 0 & rdata.crossPos == 1) +% set(rdata.outlineObj,'ZData',rdata.scaledData(3,:)); +% rdata.crossPos = 0; +% setappdata(scrollObj,'ScrollData',rdata); +% end +setappdata(scrollObj,'ScrollData',rdata); +if(rdata.textState) + set(rdata.textBoxText,'String',sprintf('X: %4.0f %4.0f',lims)); +end + +%---------------------------- +% Map a dx dy to a zoomed window on data +function lims = mappingFunction(rdata, dx, dy) +lims = rdata.XLim; +ctr = (lims(1)+lims(2))/2; +width = (lims(2)-lims(1))/2; +lims = ctr+[-width width]*exp(dy/rdata.GAIN(2)); +lims= lims + width*rdata.GAIN(1)*(-dx); + +%----------------------------- +% Scale data to fit target axes limits +function setOutlineObjToFitAxes(scrollObj) +rdata = getappdata(scrollObj,'ScrollData'); +ax = rdata.targetAxis; +x_extent = get(ax,'XLim'); +y_extent = get(ax,'YLim'); +z_extent = get(ax,'ZLim'); +X = rdata.outlineData; +X(1,:) = X(1,:)*diff(x_extent) + x_extent(1); +X(2,:) = X(2,:)*diff(y_extent) + y_extent(1); +X(3,:) = X(3,:)*diff(z_extent) + z_extent(1); +X(4,:) = X(4,:)*diff(z_extent) + z_extent(1); +set(rdata.outlineObj,'XData',X(1,:),'YData',X(2,:),'ZData',X(3,:)); +rdata.scaledData = X; +setappdata(scrollObj,'ScrollData',rdata); + +%------------------------------- +% Copy properties from one axes to another. +function copyAxisProps(original, dest) +props = { + 'DataAspectRatio' + 'DataAspectRatioMode' + 'CameraViewAngle' + 'CameraViewAngleMode' + 'XLim' + 'YLim' + 'ZLim' + 'PlotBoxAspectRatio' + 'PlotBoxAspectRatioMode' + 'Units' + 'Position' + 'View' + 'Projection' +}; +values = get(original,props); +set(dest,props,values); + +%------------------------------------------- +% Constructor for the Rotate object. +function scrollObj = makeRotaObj(fig) + +% save the previous state of the figure window +% rdata.uistate = uiclearmode(fig,'scroll',fig,'off'); + +rdata.targetAxis = []; % Axis that is being scrollted (target axis) +rdata.destAxis = []; % the axis the caller specified (may be []) +rdata.GAIN = [ 1e-2 40 ] ; % Motion gain +rdata.oldPt = []; % Point where the button down happened +rdata.oldLims = []; +curax = get(fig,'currentaxes'); +scrollObj = axes('Parent',fig,'Visible','off','HandleVisibility','off','Drawmode','fast'); +nondataobj = []; +setappdata(scrollObj,'NonDataObject',nondataobj); +% Data points for the outline box. +rdata.outlineData = [0 0 1 0;0 1 1 0;1 1 1 0;1 1 0 1;0 0 0 1;0 0 1 0; ... + 1 0 1 0;1 0 0 1;0 0 0 1;0 1 0 1;1 1 0 1;1 0 0 1;0 1 0 1;0 1 1 0; ... + NaN NaN NaN NaN;1 1 1 0;1 0 1 0]'; +rdata.outlineObj = line(rdata.outlineData(1,:),rdata.outlineData(2,:),rdata.outlineData(3,:), ... + 'Parent',scrollObj,'Erasemode','xor','Visible','off','HandleVisibility','off', ... + 'Clipping','off'); + +% Make text box. +fig_color = get(fig, 'Color'); + +% if the figure color is 'none', setting the uicontrol +% backgroundcolor to white and the foreground accordingly. +if strcmp(fig_color, 'none') + fig_color = [1 1 1]; +end +rdata.textBoxText = uicontrol('parent',fig,'Units','Pixels','Position',[2 2 130 20],'Visible','off', ... + 'Style','text','BackgroundColor', fig_color,'HandleVisibility','off'); + +rdata.textState = []; +rdata.oldFigureUnits = ''; +rdata.crossPos = 0; % where do we put the X at zmin or zmax? 0 means zmin 1 means zmax +rdata.scaledData = rdata.outlineData; + + +state = getappdata(fig,'Rotate3dFigureState'); +if isempty(state) + % turn off all other interactive modes + state = uiclearmode(fig,'docontext','scrollaxes',fig,'off'); + % restore button down functions for uicontrol children of the figure + uirestore(state,'uicontrols'); + setappdata(fig,'Rotate3dFigureState',state); +end + + +set(fig,'WindowButtonDownFcn','scrollaxes(''down'')'); +set(fig,'WindowButtonUpFcn' ,'scrollaxes(''up'')'); +set(fig,'WindowButtonMotionFcn',''); +set(fig,'ButtonDownFcn',''); + +set(scrollObj,'Tag','scrollObj'); +setappdata(scrollObj,'ScrollData',rdata); +set(fig,'currentaxes',curax) + +%---------------------------------- +% Deactivate scrollte object +function destroyRotaObj(scrollObj) +rdata = getappdata(scrollObj,'ScrollData'); + +% uirestore(rdata.uistate); + +delete(rdata.textBoxText); +delete(scrollObj); diff --git a/sdt.m b/sdt.m new file mode 100644 index 0000000..b6060e6 --- /dev/null +++ b/sdt.m @@ -0,0 +1,57 @@ +function [dprime,logbeta,c] = sdt(varargin) +%SDT - Signal detection théory computations (d-prime, beta, criterion) +% [dprime,logb,c] = sdt(s,r) where s and r are two vectors of 0's and 1's +% +% Example +% >> +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2009 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2009-03-24 Creation +% +% ----------------------------- Script History --------------------------------- + +s=logical(varargin{1}(:)); +r=logical(varargin{2}(:)); +%Hit rate: +hr = sum(r(s))/sum(s); +fa = sum(r(~s))/sum(~s); +if hr==1 + hr = 1-1/(2*sum(s)); + warning(sprintf('Hit rate is approximated to: %g%%', 100*hr)); +elseif hr==0 + hr = 1/(2*sum(s)); + warning(sprintf('Hit rate is approximated to: %g%%', 100*hr)); +end +if fa==0 + fa = 1/(2*sum(~s)); + warning(sprintf('False alarm rate is approximated to: %g%%', 100*fa)); +elseif fa==1 + fa = 1-1/(2*sum(~s)); + warning(sprintf('False alarm rate is approximated to: %g%%', 100*fa)); +end + + +% Discrimination index (d') +dprime = normalz(hr)-normalz(fa); +% Response bias (or criterion) +c = -(normalz(hr)+normalz(fa))/2; +% Log-likelihood ratio +logbeta = dprime * c; +return + +function z = normalz(p, mu, sigma) +% NORMALZ Normal z-score +% >> z = normalz(p, [mu], [sigma]) +if nargin < 3, sigma = 1; end +if nargin < 2, mu = 0; end +if nargin < 1, error('Not enough input parameters.'); end +z = mu+sqrt(2)*sigma*erfinv(2*p-1); diff --git a/select3d.m b/select3d.m new file mode 100644 index 0000000..0b87699 --- /dev/null +++ b/select3d.m @@ -0,0 +1,381 @@ +function [pout, vout, viout, facevout, faceiout] = select3d(obj) +%SELECT3D(H) Determines the selected point in 3-D data space. +% P = SELECT3D determines the point, P, in data space corresponding +% to the current selection position. P is a point on the first +% patch or surface face intersected along the selection ray. If no +% face is encountered along the selection ray, P returns empty. +% +% P = SELECT3D(H) constrains selection to graphics handle H and, +% if applicable, any of its children. H can be a figure, axes, +% patch, or surface object. +% +% [P V] = SELECT3D(...), V is the closest face or line vertex +% selected based on the figure's current object. +% +% [P V VI] = SELECT3D(...), VI is the index into the object's +% x,y,zdata properties corresponding to V, the closest face vertex +% selected. +% +% [P V VI FACEV] = SELECT3D(...), FACE is an array of vertices +% corresponding to the face polygon containing P and V. +% +% [P V VI FACEV FACEI] = SELECT3D(...), FACEI is the row index into +% the object's face array corresponding to FACE. For patch +% objects, the face array can be obtained by doing +% get(mypatch,'faces'). For surface objects, the face array +% can be obtained from the output of SURF2PATCH (see +% SURF2PATCH for more information). +% +% RESTRICTIONS: +% SELECT3D supports surface, patch, or line object primitives. For surface +% and patches, the algorithm assumes non-self-intersecting planar faces. +% For line objects, the algorithm always returns P as empty, and V will +% be the closest vertex relative to the selection point. +% +% Example: +% +% h = surf(peaks); +% zoom(10); +% disp('Click anywhere on the surface, then hit return') +% pause +% [p v vi face facei] = select3d; +% marker1 = line('xdata',p(1),'ydata',p(2),'zdata',p(3),'marker','o',... +% 'erasemode','xor','markerfacecolor','k'); +% marker2 = line('xdata',v(1),'ydata',v(2),'zdata',v(3),'marker','o',... +% 'erasemode','xor','markerfacecolor','k'); +% marker2 = line('erasemode','xor','xdata',face(1,:),'ydata',face(2,:),... +% 'zdata',face(3,:),'linewidth',10); +% disp(sprintf('\nYou clicked at\nX: %.2f\nY: %.2f\nZ: %.2f',p(1),p(2),p(3)')) +% disp(sprintf('\nThe nearest vertex is\nX: %.2f\nY: %.2f\nZ: %.2f',v(1),v(2),v(3)')) +% +% Version 1.2 2-15-02 +% Copyright Joe Conti 2002 +% Send comments to jconti@mathworks.com +% +% See also GINPUT, GCO. + +% Output variables +pout = []; +vout = []; +viout = []; +facevout = []; +faceiout = []; + +% other variables +ERRMSG = 'Input argument must be a valid graphics handle'; +isline = logical(0); +isperspective = logical(0); + +% Parse input arguments +if nargin<1 + obj = gco; +end + +if isempty(obj) | ~ishandle(obj) | length(obj)~=1 + error(ERRMSG); +end + +% if obj is a figure +if strcmp(get(obj,'type'),'figure') + fig = obj; + ax = get(fig,'currentobject'); + currobj = get(fig,'currentobject'); + + % bail out if not a child of the axes + if ~strcmp(get(get(currobj,'parent'),'type'),'axes') + return; + end + +% if obj is an axes +elseif strcmp(get(obj,'type'),'axes') + ax = obj; + fig = get(ax,'parent'); + currobj = get(fig,'currentobject'); + currax = get(currobj,'parent'); + + % Bail out if current object is under an unspecified axes + if ~isequal(ax,currax) + return; + end + +% if obj is child of axes +elseif strcmp(get(get(obj,'parent'),'type'),'axes') + currobj = obj; + ax = get(obj,'parent'); + fig = get(ax,'parent'); + +% Bail out +else + return +end + +axchild = currobj; +obj_type = get(axchild,'type'); +is_perspective = strcmp(get(ax,'projection'),'perspective'); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Get projection transformation %% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% syntax not supported in old versions of MATLAB +[a b] = view(ax); +xform = viewmtx(a,b); +if is_perspective + warning(sprintf('%s does not support perspective axes projection.',mfilename)); + d = norm(camtarget(ax)-campos(ax)) + P = [1 0 0 0; + 0 1 0 0; + 0 0 1 0; + 0 0 -1/d 1]; + xform = P*xform; +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Get vertex, face, and current point data %% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +cp = get(ax,'currentpoint')'; + +% If surface object +if strcmp(obj_type,'surface') + % Get surface face and vertices + fv = surf2patch(axchild); + vert = fv.vertices; + faces = fv.faces; + +% If patch object +elseif strcmp(obj_type,'patch') + vert = get(axchild,'vertices'); + faces = get(axchild,'faces'); + vzb = get(axchild,'FaceVertexAlphaData'); + vert(vzb==0)=NaN; + if isequal('interp', get(axchild,'FaceVertexAlphaData')) + [ignore,tmp]=ismember(faces(:),vzb); + faces(tmp)=NaN; + clear tmp; + end +% If line object +elseif strcmp(obj_type,'line') + xdata = get(axchild,'xdata'); + ydata = get(axchild,'ydata'); + zdata = get(axchild,'zdata'); + vert = [xdata', ydata',zdata']; + faces = []; + isline = logical(1); + +% Ignore all other objects +else + return; +end + +% Add z if empty +if size(vert,2)==2 + vert(:,3) = zeros(size(vert(:,2))); + if isline + zdata = vert(:,3); + end +end + +% NaN and Inf check +nan_inf_test1 = isnan(faces) | isinf(faces); +nan_inf_test2 = isnan(vert) | isinf(vert); +if any(nan_inf_test1(:)) | any(nan_inf_test2(:)) + warning(sprintf('%s does not support NaNs or Infs in face/vertex data.',mfilename)); +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Normalize for data aspect ratio %% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +dar = get(ax,'DataAspectRatio'); + +ncp(1,:) = cp(1,:)./dar(1); +ncp(2,:) = cp(2,:)./dar(2); +ncp(3,:) = cp(3,:)./dar(3); +ncp(4,:) = ones(size(ncp(3,:))); + +nvert(:,1) = vert(:,1)./dar(1); +nvert(:,2) = vert(:,2)./dar(2); +nvert(:,3) = vert(:,3)./dar(3); +nvert(:,4) = ones(size(nvert(:,3))); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Transform data to view space %% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +xvert = xform*nvert'; +xcp = xform*ncp; + +if is_perspective % normalize 4th dimension + xcp(1,:) = xcp(1,:)./xcp(4,:); + xcp(2,:) = xcp(2,:)./xcp(4,:); + xcp(3,:) = xcp(3,:)./xcp(4,:); + xcp(4,:) = xcp(4,:)./xcp(4,:); + + xvert(1,:) = xvert(1,:)./xvert(4,:); + xvert(2,:) = xvert(2,:)./xvert(4,:); + xvert(3,:) = xvert(3,:)./xvert(4,:); + xvert(4,:) = xvert(4,:)./xvert(4,:); +end + +% Ignore 3rd & 4th dimensions for crossing test +xvert(4,:) = []; +xvert(3,:) = []; +xcp(4,:) = []; +xcp(3,:) = []; + +% For debugging +% if 0 +% ax1 = getappdata(ax,'testselect3d'); +% if isempty(ax1) | ~ishandle(ax1) +% fig = figure; +% ax1 = axes; +% axis(ax1,'equal'); +% setappdata(ax,'testselect3d',ax1); +% end +% cla(ax1); +% patch('parent',ax1,'faces',faces,'vertices',xvert','facecolor','none','edgecolor','k'); +% line('parent',ax1,'xdata',xcp(1,2),'ydata',xcp(2,2),'zdata',0,'marker','o','markerfacecolor','r','erasemode','xor'); +% end + +% Translate vertices so that the selection point is at the origin. +xvert(1,:) = xvert(1,:) - xcp(1,2); +xvert(2,:) = xvert(2,:) - xcp(2,2); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% simple algorithm (almost naive algorithm!) for line objects %% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +if isline + + % Ignoring line width and marker attributes, find closest + % vertex in 2-D view space. + d = xvert(1,:).*xvert(1,:) + xvert(2,:).*xvert(2,:); + [val i] = min(d); + i = i(1); % enforce only one output + + % Assign output + vout = [ xdata(i) ydata(i) zdata(i)]; + viout = i; + + return % Bail out early +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Perform 2-D crossing test (Jordan Curve Theorem) %% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% Find all vertices that have y components less than zero +vert_with_negative_y = zeros(size(faces)); +face_y_vert = xvert(2,faces); +ind_vert_with_negative_y = find(face_y_vert<0); +vert_with_negative_y(ind_vert_with_negative_y) = logical(1); + +% Find all the line segments that span the x axis +is_line_segment_spanning_x = abs(diff([vert_with_negative_y, vert_with_negative_y(:,1)],1,2)); + +% Find all the faces that have line segments that span the x axis +ind_is_face_spanning_x = find(any(is_line_segment_spanning_x,2)); + +% Ignore data that doesn't span the x axis +candidate_faces = faces(ind_is_face_spanning_x,:); +vert_with_negative_y = vert_with_negative_y(ind_is_face_spanning_x,:); +is_line_segment_spanning_x = is_line_segment_spanning_x(ind_is_face_spanning_x,:); + +% Create line segment arrays +pt1 = candidate_faces; +pt2 = [candidate_faces(:,2:end), candidate_faces(:,1)]; + +% Point 1 +x1 = reshape(xvert(1,pt1),size(pt1)); +y1 = reshape(xvert(2,pt1),size(pt1)); + +% Point 2 +x2 = reshape(xvert(1,pt2),size(pt2)); +y2 = reshape(xvert(2,pt2),size(pt2)); + +% Cross product of vector to origin with line segment +cross_product_test = -x1.*(y2-y1) > -y1.*(x2-x1); + +% Find all line segments that cross the positive x axis +crossing_test = (cross_product_test==vert_with_negative_y) & is_line_segment_spanning_x; + +% If the number of line segments is odd, then we intersected the polygon +s = sum(crossing_test,2); +s = mod(s,2); +ind_intersection_test = find(s~=0); + +% Bail out early if no faces were hit +if isempty(ind_intersection_test) + return; +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Plane/ray intersection test %% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Perform plane/ray intersection with the faces that passed +% the polygon intersection tests. Grab the only the first +% three vertices since that is all we need to define a plane). +% assuming planar polygons. +candidate_faces = candidate_faces(ind_intersection_test,1:3); +candidate_faces = reshape(candidate_faces',1,prod(size(candidate_faces))); +vert = vert'; +candidate_facev = vert(:,candidate_faces); +candidate_facev = reshape(candidate_facev,3,3,length(ind_intersection_test)); + +% Get three contiguous vertices along polygon +v1 = squeeze(candidate_facev(:,1,:)); +v2 = squeeze(candidate_facev(:,2,:)); +v3 = squeeze(candidate_facev(:,3,:)); + +% Get normal to face plane +vec1 = [v2-v1]; +vec2 = [v3-v2]; +crs = cross(vec1,vec2); +mag = sqrt(sum(crs.*crs)); +nplane(1,:) = crs(1,:)./mag; +nplane(2,:) = crs(2,:)./mag; +nplane(3,:) = crs(3,:)./mag; + +% Compute intersection between plane and ray +cp1 = cp(:,1); +cp2 = cp(:,2); +d = cp2-cp1; +dp = dot(-nplane,v1); + +%A = dot(nplane,d); +A(1,:) = nplane(1,:).*d(1); +A(2,:) = nplane(2,:).*d(2); +A(3,:) = nplane(3,:).*d(3); +A = sum(A,1); + +%B = dot(nplane,pt1) +B(1,:) = nplane(1,:).*cp1(1); +B(2,:) = nplane(2,:).*cp1(2); +B(3,:) = nplane(3,:).*cp1(3); +B = sum(B,1); + +% Distance to intersection point +t = (-dp-B)./A; + +% Find "best" distance (smallest) +[tbest ind_best] = min(t); + +% Determine intersection point +pout = cp1 + tbest .* d; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Assign additional output variables %% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +if nargout>1 + + % Get face index and vertices + faceiout = ind_is_face_spanning_x(ind_intersection_test(ind_best)); + facevout = vert(:,faces(faceiout,:)); + + % Determine index of closest face vertex intersected + facexv = xvert(:,faces(faceiout,:)); + dist = sqrt(facexv(1,:).*facexv(1,:) + facexv(2,:).*facexv(2,:)); + min_dist = min(dist); + min_index = find(dist==min_dist); + + % Get closest vertex index and vertex + viout = faces(faceiout,min_index); + vout = vert(:,viout); +end diff --git a/selectscout.m b/selectscout.m new file mode 100644 index 0000000..579cfb7 --- /dev/null +++ b/selectscout.m @@ -0,0 +1,17 @@ +function [vi,p,fi,v,f]=selectscout(ctx) +% selectscout - Select scout on the cortical surface +% [vi,p,fi,v,f]=selectscout(ctx) selects a vertex on the patch surface ctx +% If ctx not given, find the tessellation. +% If ctx=[] the surface used is the one one which the user click. +if nargin<1 + ctx=findTessellationHandles; +end +if ~isempty(ctx) + axes(get(ctx, 'Parent')); +end + +ginput(1); +if isempty(ctx) + ctx=gco; +end +[p v vi f fi]=select3d(ctx); diff --git a/setAllText.m b/setAllText.m new file mode 100644 index 0000000..999d4bf --- /dev/null +++ b/setAllText.m @@ -0,0 +1,29 @@ +function ht = setAll(p,v,h) +%SETALLTEXT - Set properties of all text objects (in a given figure/plot/...) +% ht = setAllText(Property,Value,[Handles]) +% ht are the handles to text object found + +% Author: KND +% Created: Sep 2005 +if nargin<3 + h=gca; +end + + +ht=findobj(h, 'type', 'text'); +set(ht, p, v) +ha=findobj(h,'type', 'axes') +if ~isempty(ha) + for i=1:length(ha) + if isfield(get(ha(i)), p) + set(ha(i),p,v) + else + warning(sprintf('No property %s for object Axes', p)) + end + for hah=get(ha(i),'Children')' + if isfield(get(hah), p) + %set(hah + end + end + end +end diff --git a/setall.m b/setall.m new file mode 100644 index 0000000..f80e83e --- /dev/null +++ b/setall.m @@ -0,0 +1,26 @@ +function varargout = setAll(p,v,h) +%SETALL - Set a given property for all objects that have it +% ht = setall(Property,Value,[ParentHandles]) +% ht are the modified handles + +% Author: KND +% Created: Sep 2005 +if nargin<3 + h=gca; +end +if ~ishandle(h) + return +end +h=findall(h); +ht=[]; +for i=1:length(h) + try%if isfield(get(h(i)), p) + set(h(i),p,v); + ht=[ht h(i)]; + end +end +if nargout + varargout={ht}; +end +return + diff --git a/setdiff.m b/setdiff.m new file mode 100644 index 0000000..1ac5ec8 --- /dev/null +++ b/setdiff.m @@ -0,0 +1,199 @@ +function [c,ia] = setdiff(a,b,flag) +%SETDIFF Set difference. +% SETDIFF(A,B) when A and B are vectors returns the values +% in A that are not in B. The result will be sorted. A and B +% can be cell arrays of strings. +% +% SETDIFF(A,B,'rows') when A are B are matrices with the same +% number of columns returns the rows from A that are not in B. +% +% [C,I] = SETDIFF(...) also returns an index vector I such that +% C = A(I) (or C = A(I,:)). +% +% See also UNIQUE, UNION, INTERSECT, SETXOR, ISMEMBER. + +% Copyright 1984-2003 The MathWorks, Inc. +% $Revision: 1.22.4.3 $ $Date: 2004/12/06 16:35:41 $ + +% Cell array implementation in @cell/setdiff.m + +nIn = nargin; + +if nIn < 2 + error('MATLAB:SETDIFF:NotEnoughInputs', 'Not enough input arguments.'); +elseif nIn > 3 + error('MATLAB:SETDIFF:TooManyInputs', 'Too many input arguments.'); +end + +if nIn == 2 + flag = []; +end + +isrows = strcmpi(flag,'rows'); + +rowsA = size(a,1); +colsA = size(a,2); +rowsB = size(b,1); +colsB = size(b,2); + +rowvec = ~((rowsA > 1 && colsB <= 1) || (rowsB > 1 && colsA <= 1) || isrows); + +nOut = nargout; + +if isempty(flag) + + numelA = length(a); + numelB = length(b); + + if numel(a)~=numelA || numel(b)~=numelB + error('MATLAB:SETDIFF:AandBvectorsOrRowsFlag', ... + 'A and B must be vectors or ''rows'' must be specified.'); + end + + % Handle empty arrays. + + if (numelA == 0) + % Predefine outputs to be of the correct type. + c = a([]); + ia = []; + % Ambiguous if no way to determine whether to return a row or column. + ambiguous = (rowsA==0 && colsA==0) && ... + ((rowsB==0 && colsB==0) || numelB == 1); + if ~ambiguous + c = reshape(c,0,1); + ia = reshape(ia,0,1); + end + elseif (numelB == 0) + % If B is empty, invoke UNIQUE to remove duplicates from A. + if nOut <= 1 + c = unique(a); + else + [c,ia] = unique(a); + end + return + + % Handle scalar: one element. Scalar A done only. + % Scalar B handled within ISMEMBER and general implementation. + + elseif (numelA == 1) + if ~ismember(a,b,flag) + c = a; + ia = 1; + else + c = []; + ia = []; + end + return + + % General handling. + + else + + % Convert to columns. + a = a(:); + b = b(:); + + % Convert to double arrays, which sort faster than other types. + + whichclass = class(a); + isdouble = strcmp(whichclass,'double'); + + if ~isdouble + a = double(a); + end + + if ~strcmp(class(b),'double') + b = double(b); + end + + % Call ISMEMBER to determine list of non-matching elements of A. + tf = ~(ismember(a,b)); + c = a(tf); + + % Call UNIQUE to remove duplicates from list of non-matches. + if nargout <= 1 + c = unique(c); + else + [c,ndx] = unique(c); + + % Find indices by using TF and NDX. + where = find(tf); + ia = where(ndx); + end + + % Re-convert to correct output data type using FEVAL. + if ~isdouble + c = feval(whichclass,c); + end + end + + % If row vector, return as row vector. + if rowvec + c = c.'; + if nOut > 1 + ia = ia.'; + end + end + +else % 'rows' case + if ~isrows + error('MATLAB:SETDIFF:UnknownFlag', 'Unknown flag.'); + end + + % Automatically pad strings with spaces + if ischar(a) && ischar(b) + if colsA > colsB + b = [b repmat(' ',rowsB,colsA-colsB)]; + elseif colsA < colsB + a = [a repmat(' ',rowsA,colsB-colsA)]; + colsA = colsB; + end + elseif colsA ~= colsB + error('MATLAB:SETDIFF:AandBColnumAgree',... + 'A and B must have the same number of columns.'); + end + + % Handle empty arrays + if rowsA == 0 + c = zeros(rowsA,colsA); + ia = []; + elseif colsA == 0 && rowsA > 0 + c = zeros(1,0); + ia = rowsA; + % General handling + else + % Remove duplicates from A; get indices only if needed + if nOut > 1 + [a,ia] = unique(a,flag); + else + a = unique(a,flag); + end + + % Create sorted list of unique A and B; want non-matching entries + [c,ndx] = sortrows([a;b]); + [rowsC,colsC] = size(c); + if rowsC > 1 && colsC ~= 0 + % d indicates the location of non-matching entries + d = c(1:rowsC-1,:) ~= c(2:rowsC,:); + else + d = zeros(rowsC-1,0); + end + d = any(d,2); + d(rowsC,1) = 1; % Final entry always included. + + % d = 1 now for any unmatched entry of A or of B. + n = size(a,1); + d = d & ndx <= n; % Now find only the ones in A. + + c = c(d,:); + + if nOut > 1 + ia = ia(ndx(d)); + end + end +end + +% Automatically deblank strings +if ischar(a) + c = deblank(c); +end diff --git a/setdiff2.m b/setdiff2.m new file mode 100644 index 0000000..1ac5ec8 --- /dev/null +++ b/setdiff2.m @@ -0,0 +1,199 @@ +function [c,ia] = setdiff(a,b,flag) +%SETDIFF Set difference. +% SETDIFF(A,B) when A and B are vectors returns the values +% in A that are not in B. The result will be sorted. A and B +% can be cell arrays of strings. +% +% SETDIFF(A,B,'rows') when A are B are matrices with the same +% number of columns returns the rows from A that are not in B. +% +% [C,I] = SETDIFF(...) also returns an index vector I such that +% C = A(I) (or C = A(I,:)). +% +% See also UNIQUE, UNION, INTERSECT, SETXOR, ISMEMBER. + +% Copyright 1984-2003 The MathWorks, Inc. +% $Revision: 1.22.4.3 $ $Date: 2004/12/06 16:35:41 $ + +% Cell array implementation in @cell/setdiff.m + +nIn = nargin; + +if nIn < 2 + error('MATLAB:SETDIFF:NotEnoughInputs', 'Not enough input arguments.'); +elseif nIn > 3 + error('MATLAB:SETDIFF:TooManyInputs', 'Too many input arguments.'); +end + +if nIn == 2 + flag = []; +end + +isrows = strcmpi(flag,'rows'); + +rowsA = size(a,1); +colsA = size(a,2); +rowsB = size(b,1); +colsB = size(b,2); + +rowvec = ~((rowsA > 1 && colsB <= 1) || (rowsB > 1 && colsA <= 1) || isrows); + +nOut = nargout; + +if isempty(flag) + + numelA = length(a); + numelB = length(b); + + if numel(a)~=numelA || numel(b)~=numelB + error('MATLAB:SETDIFF:AandBvectorsOrRowsFlag', ... + 'A and B must be vectors or ''rows'' must be specified.'); + end + + % Handle empty arrays. + + if (numelA == 0) + % Predefine outputs to be of the correct type. + c = a([]); + ia = []; + % Ambiguous if no way to determine whether to return a row or column. + ambiguous = (rowsA==0 && colsA==0) && ... + ((rowsB==0 && colsB==0) || numelB == 1); + if ~ambiguous + c = reshape(c,0,1); + ia = reshape(ia,0,1); + end + elseif (numelB == 0) + % If B is empty, invoke UNIQUE to remove duplicates from A. + if nOut <= 1 + c = unique(a); + else + [c,ia] = unique(a); + end + return + + % Handle scalar: one element. Scalar A done only. + % Scalar B handled within ISMEMBER and general implementation. + + elseif (numelA == 1) + if ~ismember(a,b,flag) + c = a; + ia = 1; + else + c = []; + ia = []; + end + return + + % General handling. + + else + + % Convert to columns. + a = a(:); + b = b(:); + + % Convert to double arrays, which sort faster than other types. + + whichclass = class(a); + isdouble = strcmp(whichclass,'double'); + + if ~isdouble + a = double(a); + end + + if ~strcmp(class(b),'double') + b = double(b); + end + + % Call ISMEMBER to determine list of non-matching elements of A. + tf = ~(ismember(a,b)); + c = a(tf); + + % Call UNIQUE to remove duplicates from list of non-matches. + if nargout <= 1 + c = unique(c); + else + [c,ndx] = unique(c); + + % Find indices by using TF and NDX. + where = find(tf); + ia = where(ndx); + end + + % Re-convert to correct output data type using FEVAL. + if ~isdouble + c = feval(whichclass,c); + end + end + + % If row vector, return as row vector. + if rowvec + c = c.'; + if nOut > 1 + ia = ia.'; + end + end + +else % 'rows' case + if ~isrows + error('MATLAB:SETDIFF:UnknownFlag', 'Unknown flag.'); + end + + % Automatically pad strings with spaces + if ischar(a) && ischar(b) + if colsA > colsB + b = [b repmat(' ',rowsB,colsA-colsB)]; + elseif colsA < colsB + a = [a repmat(' ',rowsA,colsB-colsA)]; + colsA = colsB; + end + elseif colsA ~= colsB + error('MATLAB:SETDIFF:AandBColnumAgree',... + 'A and B must have the same number of columns.'); + end + + % Handle empty arrays + if rowsA == 0 + c = zeros(rowsA,colsA); + ia = []; + elseif colsA == 0 && rowsA > 0 + c = zeros(1,0); + ia = rowsA; + % General handling + else + % Remove duplicates from A; get indices only if needed + if nOut > 1 + [a,ia] = unique(a,flag); + else + a = unique(a,flag); + end + + % Create sorted list of unique A and B; want non-matching entries + [c,ndx] = sortrows([a;b]); + [rowsC,colsC] = size(c); + if rowsC > 1 && colsC ~= 0 + % d indicates the location of non-matching entries + d = c(1:rowsC-1,:) ~= c(2:rowsC,:); + else + d = zeros(rowsC-1,0); + end + d = any(d,2); + d(rowsC,1) = 1; % Final entry always included. + + % d = 1 now for any unmatched entry of A or of B. + n = size(a,1); + d = d & ndx <= n; % Now find only the ones in A. + + c = c(d,:); + + if nOut > 1 + ia = ia(ndx(d)); + end + end +end + +% Automatically deblank strings +if ischar(a) + c = deblank(c); +end diff --git a/setfield2.m b/setfield2.m new file mode 100644 index 0000000..0c62d64 --- /dev/null +++ b/setfield2.m @@ -0,0 +1,14 @@ +function B = setfield2(A,F,V) +%SETFIELD2 - Set structure field contents (even for arrays) +% S = SETFIELD(S,F,V) sets the contents of the specified +% field(s) F to the value(s) V. Structure S, fields F and values V may +% be array of the same size or expandable scalar. +% +% S = SETFIELD(S,{i,j},'field',{k},V) is equivalent to the syntax +% S(i,j).field(k) = V; +% +% +% ----------------------------- Script History --------------------------------- +% KND 2011-06-08 Creation +% +% ----------------------------- Script History --------------------------------- diff --git a/setfocus.m b/setfocus.m new file mode 100644 index 0000000..daafa2c --- /dev/null +++ b/setfocus.m @@ -0,0 +1,75 @@ +function setfocus(h); +% +% +% sample of a simple apporch to the set a focus to a uicontrol: +% setfocus forces a mouseclick in the upper left corner of the +% object with the handle h. +% + +% $Revision: 1.3 $ +% $Date: 2002/06/27 12:41:01 $ +% $Author: weber $ + +if nargin<1 + error('No input handle defined!') +end + +if ~ishandle(h) + error('Input must be a handle!'); +end + + +callback=''; + +% When the object is a 'uicontrol' the function of the object would be +% executed. Therefore I have to set it to '', and set it back after the mouseclick + +if strcmp(get(h, 'Type'), 'uicontrol') + callback=get(h, 'Callback'); + set(h, 'Callback', ''); + drawnow; % Update the figure data +end + + +figh=get(h, 'Parent'); +unit_root=get(0, 'Unit'); +unit_fig=get(figh, 'Unit'); +unit_obj=get(h, 'Unit'); +set(0, 'Units', 'pixels'); +set(figh, 'Units', 'pixels'); +set(h, 'Units', 'pixels'); +drawnow; + +% get the current mouse pointer coordinates +mouse_coord=get(0, 'PointerLocation'); +fig_pos=get(figh, 'Position'); +obj_pos=get(h, 'Position'); + +act_pos=fig_pos+obj_pos; +act_pos=act_pos(1:2); +act_pos(1)=act_pos(1)+3; +act_pos(2)=act_pos(2)+obj_pos(4)-7; + +% set the mouse pointer to the upper left corner of the object +% (I did this for listboxes, to highlight the first entry) + +set(0, 'PointerLocation', act_pos); + +mouseclick; %simulate the mouseclick + + +%----------------------------------------------------------------------------- +% set all parameters that were changed back to the starting values + +set(0, 'PointerLocation', mouse_coord); + +if strcmp(get(h, 'Type'), 'uicontrol') + drawnow; % When there is no 'drawnow', the original Callback- String + % will be executed, even the Callback String is set to ''; + set(h, 'Callback', callback); +end + +set(0, 'Unit', unit_root); +set(figh, 'Unit', unit_fig); +set(h, 'Unit', unit_obj); +drawnow; diff --git a/setmycolormap.m b/setmycolormap.m new file mode 100644 index 0000000..5a15423 --- /dev/null +++ b/setmycolormap.m @@ -0,0 +1,19 @@ +function [varargout]=setmycolormap(seuil,varargin) + +m=mycolormap(seuil, varargin{:}); +colormap(m); +material dull; +%h=light; +%set(h, 'position', [0 -1 0]) +%set(h, 'color', [0.5 .5 0.5]) +%h=light; +%set(h, 'position', [0 1 0]) +%set(h, 'color', [0.5 .5 0.5]) +% caxis([-mean(abs(caxis)) mean(abs(caxis))]) + +colorbar +set(gcf, 'Color', [1 1 1 ]) +rotate3d on +if nargout > 0 +varargout=m; +end diff --git a/sf_estrrep.m b/sf_estrrep.m new file mode 100644 index 0000000..77d332e --- /dev/null +++ b/sf_estrrep.m @@ -0,0 +1,9 @@ +function str = sf_estrrep(str,srstr) +%sf_estrrep - Replaces multiples strings +% str = sf_estrrep(str,srstr) replaces srstr multiple times in str. +% srstr is a n-by-2 cell array of strings +% +% (from SPM2 toolbox) +for i = 1:size(srstr,1) + str = strrep(str,srstr{i,1},srstr{i,2}); +end \ No newline at end of file diff --git a/sfieldnames.m b/sfieldnames.m new file mode 100644 index 0000000..5fbeb04 --- /dev/null +++ b/sfieldnames.m @@ -0,0 +1,22 @@ +function [f]=sfieldnames(s) +%SFIELDNAMES Get structure field names. +% NAMES = SFIELDNAMES(S) returns a cell array of strings containing +% the structure field and subfields names (under the form: +% 'field.subfield') associated with the structure s +% +% See also SISFIELD, SSETFIELD + +f=fieldnames(s); +f=f(:); +i=0; +while iyahoo.fr) +% Copyright (C) 2009 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2009-03-12 Also process struct arrays +% +% ----------------------------- Script History --------------------------------- + +% Check for sufficient inputs +if (isempty(varargin)) + error('MATLAB:sgetfield:InsufficientInputs',... + 'Not enough input arguments.') +end + +sz = size(s) + +% The most common case +strField = varargin{1}; +if (length(varargin)==1 && ischar(strField)) + [strField, subField]=strtok(strField, '.'); + f = [s.(deblank(strField))]; % deblank field name + if numel(f)==numel(s) + f=reshape(f,size(s)); + end + if isempty(subField) + return + end + % recursive call to process subfields + f = sgetfield(f,subField); + if numel(f)==numel(s) + f=reshape(f,size(s)); + else + warning('MATLAB:sgetfield:EmptySubields','Incomplete array of (sub)fields, some are empty') + end +end + +return + + +f = s; +for i = 1:length(varargin) + index = varargin{i}; + if (isa(index, 'cell')) + f = f(index{:}); + elseif ischar(index) + + % Return the first element, if a comma separated list is generated + try + f = f.(deblank(index)); % deblank field name + catch + tmp = cell(1,length(f)); + [tmp{:}] = deal(f.(deblank(index))); + f = tmp{1}; + end + else + error('MATLAB:getfield:InvalidType', 'Inputs must be either cell arrays or strings.'); + end +end + diff --git a/showpercent.m b/showpercent.m new file mode 100644 index 0000000..06ebb7c --- /dev/null +++ b/showpercent.m @@ -0,0 +1,138 @@ +function [keep,c,clim]=showpercent(p,hpatch,basecolor,cmap,asym) +%showpercent - Set the colormap to display some percentage of active sources +% +%[keep,c,clim]=showpercent(p,hpatch,basecolor,cmap,asym) +% Set the colormap to display some percentage of the most active sources +% of the cortical surface (according to FaceVertexCData). +%INPUTS: +% p: the percentage to keep (if p<0, the actual number, i.e. -p==keep) +%OPTIONAL INPUTS: +% hpatch: handle to the patch surface. If missing, find it... If array, +% If this is not a handle but an array of numbers, showpercent +% compute the colormap that would display p% of those values. +% basecolor: color to be used for unactive sources [.6 .6 .6] +% cmap: colormap to use, if missing use the colormap of the figure +% asym: 0:symmetical colormap (to display +/- values), +% 1: positive; -1:negative values only +%OUTPUT: +% keep: number of sources; +% c: the new colormap +% clim: the new limits for the colormap +% +% If FaceVertexCData is >=0 the CLim properties are changed to display only +% the p% most active sources and the lowest color is set to the basecolor. +% Otherwise (some FVCData<0), the ColorMap is symmetrized, with basecolor +% inserted in the middle to "mute" low-amplitude sources. + +if p==0 + return +end +if nargin<2 + hpatch=findTessellationHandles; +end +if nargin<3 || isempty(basecolor) + basecolor=[.6 .6 .6]; +end +if nargin<4 || isempty(cmap) + cmap=get(get(get(hpatch, 'Parent'), 'Parent'), 'Colormap'); + cmap=cmap(~all((cmap==repmat(basecolor, size(cmap,1), 1))'),:); +end +if nargin<5 + asym=NaN; +end +if ishandle(hpatch) +cdata=get(hpatch, 'FaceVertexCData'); +else + cdata=hpatch; +end +cdata=cdata(:); +if p>0 + % Number of vertices to keep so that it make only X% + keep=round(p/100*size(cdata,1)); +else + keep=-p; +end +if p==100 | keep == length(cdata) + top=max(abs(cdata)); + bot=-top; + c=cmap; +elseif (isnan(asym) && any(cdata>0) & any(cdata<0)) || ~asym + f=-sort(-abs(cdata)); + bot=-f(1); + top=f(1); + noff=size(cmap,1)/2*f(keep)/(f(1) - f(keep)); + c=[cmap(1:end/2,:) ; repmat(basecolor, ceil(2*noff), 1) ; cmap(end/2+1:end,:)]; + %bot=cdata(f(keep)) + %neg=find(cdata<0); + %pos=find(cdata>0); + %nmap=length(colormap); + %c=[fliplr(flipud(cmap)); repmat(basecolor,nmap*100/p,1) ; cmap]; + % top=-bot; + % if top < 0 + % top=-top;bot=-bot; + % end +elseif (isnan(asym) & all(cdata>=0)) || asym>0 + f=-sort(-(cdata)); + top=f(1); + % Alt. version: Colormap show also lower bound + % bot=0; + % noff=size(cmap,1)*f(keep)/(f(1) - f(keep)); + % c=[repmat(basecolor, ceil(2*noff), 1) ; cmap(1:end,:)]; + bot = f(keep+1); + c=([basecolor; cmap]); +elseif (isnan(asym) & all(cdata<=0)) || asym<0 + f=-sort(-(cdata)); + top=-f(keep+1); + % Alt. version: Colormap show also lower bound + % bot=0; + % noff=size(cmap,1)*f(keep)/(f(1) - f(keep)); + % c=[repmat(basecolor, ceil(2*noff), 1) ; cmap(1:end,:)]; + bot = -f(1); + c=([cmap;basecolor]); + +end +if ishandle(hpatch) + set(get(hpatch, 'Parent'), 'CLim', [bot top]); + colormap(get(hpatch, 'Parent'),c); +end +return + + +% ==================================================== + + +% Previous version +cmap=hot(100).*(1-flipud(cumsum(ones(100,3))/100)*.6)+ ... + (flipud(cumsum(ones(100,3))/100)*.6) + + + +if any(cdata>0) & any(cdata<0) + bot=cdata(f(keep)) +else + bot=cdata(f(keep)); + top=cdata(f(1)); + if all(cdata<0) + % All negative sign + bot=-bot; + top=-top; + end + set(gca, 'CLim', [bot top]); + if nargin < 4 + [ign, ci]=unique(colormap, 'rows'); + c=colormap; + c=c(ci,:); + if size(c,1) < size(colormap,1) + disp('Changing previous ColorMap') + c=([basecolor; c(2:end,:)]); + else + disp('Adding gray to previous ColorMap') + c=([basecolor; c]); + end + else + c=([basecolor; cmap]); + end + +end +colormap(c); +colorbar diff --git a/sigproc/__zp2ssg2__.m b/sigproc/__zp2ssg2__.m new file mode 100644 index 0000000..39132eb --- /dev/null +++ b/sigproc/__zp2ssg2__.m @@ -0,0 +1,68 @@ +## Copyright (C) 1996, 1998 Auburn University. All rights reserved. +## +## This file is part of Octave. +## +## Octave is free software; you can redistribute it and/or modify it +## under the terms of the GNU General Public License as published by the +## Free Software Foundation; either version 2, or (at your option) any +## later version. +## +## Octave is distributed in the hope that it will be useful, but WITHOUT +## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +## for more details. +## +## You should have received a copy of the GNU General Public License +## along with Octave; see the file COPYING. If not, write to the Free +## Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. + +## -*- texinfo -*- +## @deftypefn {Function File} {[@var{poly}, @var{rvals}] =} __zp2ssg2__ (@var{rvals}) +## Used internally in @code{zp2ss} +## Extract 2 values from @var{rvals} (if possible) and construct +## a polynomial with those roots. +## @end deftypefn + +## Author: A. S. Hodel +## Created: August 1996 + +function [poly, rvals] = __zp2ssg2__ (rvals) + + ## locate imaginary roots (if any) + cidx = find(imag(rvals)); + + if(!isempty(cidx)) + ## select first complex root, omit from cidx + r1i = cidx(1); r1 = rvals(r1i); cidx = complement(r1i,cidx); + + ## locate conjugate root (must be in cidx list, just in case there's + ## roundoff) + err = abs(rvals(cidx) - r1'); + minerr = min(err); + c2i = min(find(err == minerr)); + r2i = cidx(c2i); + r2 = rvals(r2i); + cidx = complement(r2i,cidx); + + ## don't check for divide by zero, since 0 is not complex. + if(abs(r2 - r1')/abs(r1) > 1e-12) + error(sprintf("r1=(%f,%f); r2=(%f,%f), not conjugates.", ... + real(r1),imag(r1),real(r2),imag(r2))); + endif + + ## complex conjugate pair + poly = [1, -2*real(r1), real(r1)^2+imag(r1)^2]; + else + ## select two roots (they're all real) + r1 = rvals(1); + r2 = rvals(2); + poly = [1, -(r1+r2), (r1*r2)]; + r1i = 1; r2i = 2; + endif + + ## remove roots used + idx = complement([r1i, r2i],1:length(rvals)); + rvals = rvals(idx); + +endfunction + diff --git a/sigproc/bilinear.m b/sigproc/bilinear.m new file mode 100644 index 0000000..adf2cc0 --- /dev/null +++ b/sigproc/bilinear.m @@ -0,0 +1,103 @@ +% Copyright (C) 1999 Paul Kienzle +% +% This program is free software; you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation; either version 2 of the License, or +% (at your option) any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with this program; if not, write to the Free Software +% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +% usage: [Zz, Zp, Zg] = bilinear(Sz, Sp, Sg, T) +% [Zb, Za] = bilinear(Sb, Sa, T) +% +% Transform a s-plane filter specification into a z-plane +% specification. Filters can be specified in either zero-pole-gain or +% transfer function form. The input form does not have to match the +% output form. T is the sampling frequency represented in the z plane. +% +% Theory: Given a piecewise flat filter design, you can transform it +% from the s-plane to the z-plane while maintaining the band edges by +% means of the bilinear transform. This maps the left hand side of the +% s-plane into the interior of the unit circle. The mapping is highly +% non-linear, so you must design your filter with band edges in the +% s-plane positioned at 2/T tan(w*T/2) so that they will be positioned +% at w after the bilinear transform is complete. +% +% The following table summarizes the transformation: +% +% Transform Zero at x Pole at x +% ---------------- ------------------------- ------------------------ +% Bilinear zero: (2+xT)/(2-xT) pole: (2+xT)/(2-xT) +% 2 z-1 pole: -1 zero: -1 +% S -> - --- gain: (2-xT)/T gain: (2-xT)/T +% T z+1 +% ---------------- ------------------------- ------------------------ +% +% With tedious algebra, you can derive the above formulae yourself by +% substituting the transform for S into H(S)=S-x for a zero at x or +% H(S)=1/(S-x) for a pole at x, and converting the result into the +% form: +% +% H(Z)=g prod(Z-Xi)/prod(Z-Xj) +% +% Please note that a pole and a zero at the same place exactly cancel. +% This is significant since the bilinear transform creates numerous +% extra poles and zeros, most of which cancel. Those which do not +% cancel have a 'fill-in' effect, extending the shorter of the sets to +% have the same number of as the longer of the sets of poles and zeros +% (or at least split the difference in the case of the band pass +% filter). There may be other opportunistic cancellations but I will +% not check for them. +% +% Also note that any pole on the unit circle or beyond will result in +% an unstable filter. Because of cancellation, this will only happen +% if the number of poles is smaller than the number of zeros. The +% analytic design methods all yield more poles than zeros, so this will +% not be a problem. +% +% References: +% +% Proakis & Manolakis (1992). Digital Signal Processing. New York: +% Macmillan Publishing Company. + +% Author: pkienzle@cs.indiana.edu + +function [Zz, Zp, Zg] = bilinear(Sz, Sp, Sg, T) + +if nargin==3 + T = Sg; + [Sz, Sp, Sg] = tf2zp(Sz, Sp); +elseif nargin ~= 4 + usage('[Zz, Zp, Zg]=bilinear(Sz,Sp,Sg,T) or [Zb, Za]=blinear(Sb,Sa,T)'); +end; + +p = length(Sp); +z = length(Sz); +if z > p || p==0 + error('bilinear: must have at least as many poles as zeros in s-plane'); +end + +% ---------------- ------------------------- ------------------------ +% Bilinear zero: (2+xT)/(2-xT) pole: (2+xT)/(2-xT) +% 2 z-1 pole: -1 zero: -1 +% S -> - --- gain: (2-xT)/T gain: (2-xT)/T +% T z+1 +% ---------------- ------------------------- ------------------------ +Zg = real(Sg * prod((2-Sz*T)/T) / prod((2-Sp*T)/T)); +Zp = (2+Sp*T)./(2-Sp*T); +if isempty(Sz) + Zz = -ones(size(Zp)); +else + Zz = [(2+Sz*T)./(2-Sz*T)]; + Zz = postpad(Zz, p, -1); +end + +if nargout==2, [Zz, Zp] = zp2tf(Zz, Zp, Zg); end +end diff --git a/sigproc/cheby1.m b/sigproc/cheby1.m new file mode 100644 index 0000000..3c7a4dc --- /dev/null +++ b/sigproc/cheby1.m @@ -0,0 +1,99 @@ +function [Zz, Zp, Zg] = cheby1(n, Rp, W, stype) +% Generate an Chebyshev type I filter with Rp dB of pass band ripple. +% +% [b, a] = cheby1(n, Rp, Wc) +% low pass filter with cutoff pi*Wc radians +% +% [b, a] = cheby1(n, Rp, Wc, 'high') +% high pass filter with cutoff pi*Wc radians +% +% [b, a] = cheby1(n, Rp, [Wl, Wh]) +% band pass filter with edges pi*Wl and pi*Wh radians +% +% [b, a] = cheby1(n, Rp, [Wl, Wh], 'stop') +% band reject filter with edges pi*Wl and pi*Wh radians +% +% [z, p, g] = cheby1(...) +% return filter as zero-pole-gain rather than coefficients of the +% numerator and denominator polynomials. +% +% References: +% +% Parks & Burrus (1987). Digital Filter Design. New York: +% John Wiley & Sons, Inc. + +% Author: pkienzle@cs.indiana.edu + +% Copyright (C) 1999 Paul Kienzle +% +% This program is free software; you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation; either version 2 of the License, or +% (at your option) any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with this program; if not, write to the Free Software +% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + + +if (nargin>4 || nargin<3) || (nargout>3 || nargout<2) + disp('[b, a] or [z, p, g] = cheby1 (n, Rp, W, [, ''ftype''])'); +end + +stop = nargin==4; +if stop && ~(strcmp(stype, 'high') || strcmp(stype, 'stop')) + error ('cheby1: ftype must be ''high'' or ''stop'''); +end + +[r, c]=size(W); +if (~(length(W)<=2 && (r==1 || c==1))) + error('cheby1: frequency must be given as w0 or [w0, w1]'); +elseif (~all(W >= 0 & W <= 1)) + error('cheby1: critical frequencies must be in (0, 1)'); +elseif (~(length(W)==1 || length(W) == 2)) + error('cheby1: only one filter band allowed'); +elseif (length(W)==2 && ~(W(1) < W(2))) + error('cheby1: first band edge must be smaller than second'); +end + +if (Rp < 0) + error('cheby1: passband ripple must be positive decibels'); +end + +% Prewarp to the band edges to s plane +T = 2; % sampling frequency of 2 Hz +Ws = 2/T*tan(pi*W/T); + +% Generate splane poles and zeros for the chebyshev type 1 filter +C = 1; % default cutoff frequency +epsilon = sqrt(10^(Rp/10) - 1); +beta = ((sqrt(1+epsilon^2)+1)/epsilon)^(1/n); +r = C*(beta^2-1)/(2*beta); +R = C*(beta^2+1)/(2*beta); +Sz = []; +Sp = exp(1i*pi*(2*[1:n] + n - 1)/(2*n)); +Sp = r*real(Sp) + 1i*R*imag(Sp); + +% compensate for amplitude at s=0 +Sg = prod(-Sp); +% if n is even, the ripple starts low, but if n is odd the ripple +% starts high. We must adjust the s=0 amplitude to compensate. +if (rem(n,2)==0) + Sg = Sg/10^(Rp/20); +end + +% splane frequency transform +[Sz, Sp, Sg] = sftrans(Sz, Sp, Sg, Ws, stop); + +% Use bilinear transform to convert poles to the z plane +[Zz, Zp, Zg] = bilinear(Sz, Sp, Sg, T); + +if nargout==2, [Zz, Zp] = zp2tf(Zz, Zp, Zg); end + +end \ No newline at end of file diff --git a/sigproc/complement.m b/sigproc/complement.m new file mode 100644 index 0000000..00945d5 --- /dev/null +++ b/sigproc/complement.m @@ -0,0 +1,61 @@ +% Copyright (C) 1996, 1997 John W. Eaton +% +% This file is part of Octave. +% +% Octave is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by +% the Free Software Foundation; either version 2, or (at your option) +% any later version. +% +% Octave is distributed in the hope that it will be useful, but +% WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +% General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with Octave; see the file COPYING. If not, write to the Free +% Software Foundation, 59 Temple Place - Suite 330, Boston, MA +% 02111-1307, USA. + +% -*- texinfo -*- +% @deftypefn {Function File} {} complement (@var{x}, @var{y}) +% Return the elements of set @var{y} that are not in set @var{x}. For +% example, +% +% @example +% @group +% complement ([ 1, 2, 3 ], [ 2, 3, 5 ]) +% @result{} 5 +% @end group +% @end example +% @end deftypefn +% +% @seealso{create_set, union, and intersection} + +% Author: jwe + +function y = complement (a, b) + +if (nargin ~= 2) + usage ('complement(a,b)'); +end + +if (isempty (a)) + y = create_set(b); +elseif (isempty (b)) + y = []; +else + a = create_set (a); + b = create_set (b); + yindex = 1; + y = zeros (1, length (b)); + for index = 1:length (b) + if (all (a ~= b (index))) + y(yindex) = b(index); + yindex=yindex+1; + end + end + y = y(1:(yindex-1)); +end + +end diff --git a/sigproc/create_set.m b/sigproc/create_set.m new file mode 100644 index 0000000..0fbea09 --- /dev/null +++ b/sigproc/create_set.m @@ -0,0 +1,56 @@ +% Copyright (C) 1996, 1997 John W. Eaton +% +% This file is part of Octave. +% +% Octave is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by +% the Free Software Foundation; either version 2, or (at your option) +% any later version. +% +% Octave is distributed in the hope that it will be useful, but +% WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +% General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with Octave; see the file COPYING. If not, write to the Free +% Software Foundation, 59 Temple Place - Suite 330, Boston, MA +% 02111-1307, USA. + +% -*- texinfo -*- +% @deftypefn {Function File} {} create_set (@var{x}) +% Return a row vector containing the unique values in @var{x}, sorted in +% ascending order. For example, +% +% @example +% @group +% create_set ([ 1, 2; 3, 4; 4, 2 ]) +% @result{} [ 1, 2, 3, 4 ] +% @end group +% @end example +% @end deftypefn +% +% @seealso{union, intersection, and complement} + +% Author: jwe + +function y = create_set(x) + +if (nargin ~= 1) + usage ('create_set(x)'); +end + +if (isempty(x)) + y = []; +else + nel = numel (x); + y = sort (reshape (x, 1, nel)); + els = find (y(1:nel-1) ~= y(2:nel)); + if (isempty (els)); + y = y(1); + else + y = y([1, els+1]); + end +end + +end diff --git a/sigproc/decimate.m b/sigproc/decimate.m new file mode 100644 index 0000000..b474753 --- /dev/null +++ b/sigproc/decimate.m @@ -0,0 +1,82 @@ +function y = decimate(x, q, n, ftype) +% usage: y = decimate(x, q [, n] [, ftype]) +% +% Downsample the signal x by a factor of q, using an order n filter +% of ftype 'fir' or 'iir'. By default, an order 8 Chebyshev type I +% filter is used or a 30 point FIR filter if ftype is 'fir'. Note +% that q must be an integer for this rate change method. +% +% Example +% % Generate a signal that starts away from zero, is slowly varying +% % at the start and quickly varying at the end, decimate and plot. +% % Since it starts away from zero, you will see the boundary +% % effects of the antialiasing filter clearly. Next you will see +% % how it follows the curve nicely in the slowly varying early +% % part of the signal, but averages the curve in the quickly +% % varying late part of the signal. +% t=0:0.01:2; x=chirp(t,2,.5,10,'quadratic')+sin(2*pi*t*0.4); +% y = decimate(x,4); # factor of 4 decimation +% stem(t(1:121)*1000,x(1:121),"-g;Original;"); hold on; # plot original +% stem(t(1:4:121)*1000,y(1:31),"-r;Decimated;"); hold off; # decimated + +% Copyright (C) 2000 Paul Kienzle +% +% This program is free software; you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation; either version 2 of the License, or +% (at your option) any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with this program; if not, write to the Free Software +% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +if nargin < 1 || nargin > 4, + disp('y=decimate(x, q [, n] [, ftype])'); +end +if q ~= fix(q), error('decimate only works with integer q.'); end + +if nargin<3 + ftype='iir'; + n=[]; +elseif nargin==3 + if isstr(n) + ftype=n; + n=[]; + else + ftype='iir'; + end +end + +fir = strcmp(ftype, 'fir'); +if isempty(n) + if fir, n=30; else n=8; end +end + +if fir + b = fir1(n, 1/q); + y=fftfilt(b, x); +else + [b, a] = cheby1(n, 0.05, 1/q); + y=filtfilt(b,a,x); +end +y = y(1:q:length(x)); +end + +%!demo +%! t=0:0.01:2; x=chirp(t,2,.5,10,'quadratic')+sin(2*pi*t*0.4); +%! y = decimate(x,4); # factor of 4 decimation +%! stem(t(1:121)*1000,x(1:121),"-g;Original;"); hold on; # plot original +%! stem(t(1:4:121)*1000,y(1:31),"-r;Decimated;"); hold off; # decimated +%! %------------------------------------------------------------------ +%! % The signal to decimate starts away from zero, is slowly varying +%! % at the start and quickly varying at the end, decimate and plot. +%! % Since it starts away from zero, you will see the boundary +%! % effects of the antialiasing filter clearly. You will also see +%! % how it follows the curve nicely in the slowly varying early +%! % part of the signal, but averages the curve in the quickly +%! % varying late part of the signal. diff --git a/sigproc/filtfilt.m b/sigproc/filtfilt.m new file mode 100644 index 0000000..806403e --- /dev/null +++ b/sigproc/filtfilt.m @@ -0,0 +1,72 @@ +% Copyright (C) 1999 Paul Kienzle +% +% This program is free software; you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation; either version 2 of the License, or +% (at your option) any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with this program; if not, write to the Free Software +% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +% usage: y = filtfilt(b, a, x) +% +% Forward and reverse filter the signal. This corrects for phase +% distortion introduced by a one-pass filter, though it does square the +% magnitude response in the process. That's the theory at least. In +% practice the phase correction is not perfect, and magnitude response +% is distorted, particularly in the stop band. +% +% In this version, I zero-pad the end of the signal to give the reverse +% filter time to ramp up to the level at the end of the signal. +% Unfortunately, the degree of padding required is dependent on the +% nature of the filter and not just its order, so this function needs +% some work yet. +% +% Example +% [b, a]=butter(3, 0.1); % 10 Hz low-pass filter +% t = 0:0.01:1.0; % 1 second sample +% x=sin(2*pi*t*2.3)+0.25*randn(size(t)); % 2.3 Hz sinusoid+noise +% y = filtfilt(b,a,x); z = filter(b,a,x); % apply filter +% plot(t,x,';data;',t,y,';filtfilt;',t,z,';filter;') + +% Changelog: +% 2000 02 pkienzle@kienzle.powernet.co.uk +% - pad with zeros to load up the state vector on filter reverse. +% - add example + +% TODO: In Matlab filtfilt `reduces filter startup transients by carefully +% TODO: choosing initial conditions, and by prepending onto the input +% TODO: sequence a short, reflected piece of the input sequence'. +% TODO: Once filtic is written, use that here. +% TODO: My version seems to have similar quality to matlab, but both are +% TODO: pretty bad. They do remove gross lag errors, though. +% TODO: Note that if x is really long, it might be worth doing +% TODO: the zero padding as a separate call to filter so that the +% TODO: vector never has to be copied. E.g., +% TODO: [y, state] = filter(b,a,x); +% TODO: tail = filter(b,a,zeros(1,max(length(b),length(a))),state); +% TODO: [tail, state] = filter(b,a,flipXX(tail)); +% TODO: y = flipXX(filter(b,a,flipXX(y), state)); +% TODO: Don't know for what n this would be faster, if any, but the +% TODO: memory saving might be nice. + +function y = filtfilt(b, a, x) +if (nargin ~= 3) + usage('y=filtfilt(b,a,x)'); +end + +if (size(x,1) == 1) + y = filter(b,a,[x, zeros(1,2*max(length(a),length(b)))]); + y = fliplr(filter(b,a,fliplr(y))); +else + y = filter(b,a,[x ; zeros(2*max(length(a),length(b)), 1)]); + y = flipud(filter(b,a,flipud(y))); +end +y = y(1:length(x)); +end diff --git a/sigproc/firls.m b/sigproc/firls.m new file mode 100644 index 0000000..31a75bb --- /dev/null +++ b/sigproc/firls.m @@ -0,0 +1,116 @@ +% Copyright (C) 2006 Quentin Spencer +% +% This program is free software; you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation; either version 2 of the License, or +% (at your option) any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with this program; If not, see . + +% b = firls(N, F, A); +% b = firls(N, F, A, W); +% +% FIR filter design using least squares method. Returns a length N+1 +% linear phase filter such that the integral of the weighted mean +% squared error in the specified bands is minimized. +% +% F specifies the frequencies of the band edges, normalized so that +% half the sample frequency is equal to 1. Each band is specified by +% two frequencies, to the vector must have an even length. +% +% A specifies the amplitude of the desired response at each band edge. +% +% W is an optional weighting function that contains one value for each +% band that weights the mean squared error in that band. A must be the +% same length as F, and W must be half the length of F. + +% The least squares optimization algorithm for computing FIR filter +% coefficients is derived in detail in: +% +% I. Selesnick, 'Linear-Phase FIR Filter Design by Least Squares,' +% http://cnx.org/content/m10577 + +function coef = firls(N, frequencies, pass, weight, str); + + +if nargin<3 | nargin>6 + usage(''); +end +if nargin==3 + weight = ones(1, length(pass)/2); + str = []; +end +if nargin==4 + if ischar(weight) + str = weight; + weight = ones (size (pass)); + else + str = []; + end +end +if length (frequencies) ~= length (pass) + error('F and A must have equal lengths.'); +end +if 2 * length (weight) ~= length (pass) + error('W must contain one weight per band.'); +end + +if ischar(str) + error('This feature is implemented yet'); +else + + M = N/2; + w = kron(weight(:), [-1; 1]); + omega = frequencies * pi; + i1 = 1:2:length(omega); + i2 = 2:2:length(omega); + + % Generate the matrix Q + % As illustrated in the above-cited reference, the matrix can be + % expressed as the sum of a Hankel and Toeplitz matrix. A factor of + % 1/2 has been dropped and the final filter coefficients multiplied + % by 2 to compensate. + cos_ints = [omega; sin((1:N)' * omega)]; + q = [1, 1./(1:N)]' .* (cos_ints * w); + Q = toeplitz (q(1:M+1)) + hankel (q(1:M+1), q(M+1:end)); + + % The vector b is derived from solving the integral: + % + % _ w + % / 2 + % b = / W(w) D(w) cos(kw) dw + % k / w + % - 1 + % + % Since we assume that W(w) is constant over each band (if not, the + % computation of Q above would be considerably more complex), but + % D(w) is allowed to be a linear function, in general the function + % W(w) D(w) is linear. The computations below are derived from the + % fact that: + % _ + % / a ax + b + % / (ax + b) cos(nx) dx = --- cos (nx) + ------ sin(nx) + % / 2 n + % - n + % + cos_ints2 = [omega(i1).^2 - omega(i2).^2; ... + cos((1:M)' * omega(i2)) - cos((1:M)' * omega(i1))] ./ ... + ([2, 1:M]' * (omega(i2) - omega(i1))); + d = [-weight .* pass(i1); weight .* pass(i2)] ; + d = d(:); + b = [1, 1./(1:M)]' .* ((kron (cos_ints2, [1, 1]) + cos_ints(1:M+1,:)) * d); + + % Having computed the components Q and b of the matrix equation, + % solve for the filter coefficients. + a = Q \ b; + coef = [ a(end:-1:2); 2 * a(1); a(2:end) ]; + +end + +end diff --git a/sigproc/hann.m b/sigproc/hann.m new file mode 100644 index 0000000..c7145e0 --- /dev/null +++ b/sigproc/hann.m @@ -0,0 +1,48 @@ +% Copyright (C) 1995, 1996, 1997 Andreas Weingessel +% +% This file is part of Octave. +% +% Octave is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by +% the Free Software Foundation; either version 2, or (at your option) +% any later version. +% +% Octave is distributed in the hope that it will be useful, but +% WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +% General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with Octave; see the file COPYING. If not, write to the Free +% Software Foundation, 59 Temple Place - Suite 330, Boston, MA +% 02111-1307, USA. + +% -*- texinfo -*- +% @deftypefn {Function File} {} hanning (@var{m}) +% Return the filter coefficients of a Hanning window of length @var{m}. +% +% For a definition of this window type, see e.g. A. V. Oppenheim & +% R. W. Schafer, 'Discrete-Time Signal Processing'. +% @end deftypefn + +% Author: AW +% Description: Coefficients of the Hanning window + +function c = hann(m) + + if (nargin ~= 1) + usage ('hanning (m)'); + end + + if (~ (isscalar (m) && (m == round (m)) && (m > 0))) + error ('hanning: m has to be an integer > 0'); + end + + if (m == 1) + c = 1; + else + m = m - 1; + c = 0.5 - 0.5 * cos (2 * pi * (0 : m)' / m); + end + +end diff --git a/sigproc/hanning.m b/sigproc/hanning.m new file mode 100644 index 0000000..a1eff29 --- /dev/null +++ b/sigproc/hanning.m @@ -0,0 +1,48 @@ +## Copyright (C) 1995, 1996, 1997 Andreas Weingessel +## +## This file is part of Octave. +## +## Octave is free software; you can redistribute it and/or modify it +## under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2, or (at your option) +## any later version. +## +## Octave is distributed in the hope that it will be useful, but +## WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +## General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with Octave; see the file COPYING. If not, write to the Free +## Software Foundation, 59 Temple Place - Suite 330, Boston, MA +## 02111-1307, USA. + +## -*- texinfo -*- +## @deftypefn {Function File} {} hanning (@var{m}) +## Return the filter coefficients of a Hanning window of length @var{m}. +## +## For a definition of this window type, see e.g. A. V. Oppenheim & +## R. W. Schafer, "Discrete-Time Signal Processing". +## @end deftypefn + +## Author: AW +## Description: Coefficients of the Hanning window + +function c = hanning (m) + + if (nargin != 1) + usage ("hanning (m)"); + endif + + if (! (isscalar (m) && (m == round (m)) && (m > 0))) + error ("hanning: m has to be an integer > 0"); + endif + + if (m == 1) + c = 1; + else + m = m - 1; + c = 0.5 - 0.5 * cos (2 * pi * (0 : m)' / m); + endif + +endfunction diff --git a/sigproc/sftrans.m b/sigproc/sftrans.m new file mode 100644 index 0000000..ef5b3a9 --- /dev/null +++ b/sigproc/sftrans.m @@ -0,0 +1,185 @@ +% usage: [Sz, Sp, Sg] = sftrans(Sz, Sp, Sg, W, stop) +% +% Transform band edges of a generic lowpass filter (cutoff at W=1) +% represented in splane zero-pole-gain form. W is the edge of the +% target filter (or edges if band pass or band stop). Stop is true for +% high pass and band stop filters or false for low pass and band pass +% filters. Filter edges are specified in radians, from 0 to pi (the +% nyquist frequency). Warning: gain Sg is returned as a complex value +% which may have some residual imaginary component. This matters for +% poles at extreme values in the s-plane, such as those created by odd +% order chebyshev type II low pass filters. +% +% Theory: Given a low pass filter represented by poles and zeros in the +% splane, you can convert it to a low pass, high pass, band pass or +% band stop by transforming each of the poles and zeros individually. +% The following table summarizes the transformation: +% +% Transform Zero at x Pole at x +% ---------------- ------------------------- ------------------------ +% Low Pass zero: Fc x/C pole: Fc x/C +% S -> C S/Fc gain: C/Fc gain: Fc/C +% ---------------- ------------------------- ------------------------ +% High Pass zero: Fc C/x pole: Fc C/x +% S -> C Fc/S pole: 0 zero: 0 +% gain: -x gain: -1/x +% ---------------- ------------------------- ------------------------ +% Band Pass zero: b ± sqrt(b^2-FhFl) pole: b ± sqrt(b^2-FhFl) +% S^2+FhFl pole: 0 zero: 0 +% S -> C -------- gain: C/(Fh-Fl) gain: (Fh-Fl)/C +% S(Fh-Fl) b=x/C (Fh-Fl)/2 b=x/C (Fh-Fl)/2 +% ---------------- ------------------------- ------------------------ +% Band Stop zero: b ± sqrt(b^2-FhFl) pole: b ± sqrt(b^2-FhFl) +% S(Fh-Fl) pole: ±sqrt(-FhFl) zero: ±sqrt(-FhFl) +% S -> C -------- gain: -x gain: -1/x +% S^2+FhFl b=C/x (Fh-Fl)/2 b=C/x (Fh-Fl)/2 +% ---------------- ------------------------- ------------------------ +% Bilinear zero: (2+xT)/(2-xT) pole: (2+xT)/(2-xT) +% 2 z-1 pole: -1 zero: -1 +% S -> - --- gain: (2-xT)/T gain: (2-xT)/T +% T z+1 +% ---------------- ------------------------- ------------------------ +% +% where C is the cutoff frequency of the initial lowpass filter, Fc is +% the edge of the target low/high pass filter and [Fl,Fh] are the edges +% of the target band pass/stop filter. With abundant tedious algebra, +% you can derive the above formulae yourself by substituting the +% transform for S into H(S)=S-x for a zero at x or H(S)=1/(S-x) for a +% pole at x, and converting the result into the form: +% +% H(S)=g prod(S-Xi)/prod(S-Xj) +% +% The transforms are from the references. The actual pole-zero-gain +% changes I derived myself. +% +% Please note that a pole and a zero at the same place exactly cancel. +% This is significant for High Pass, Band Pass and Band Stop filters +% which create numerous extra poles and zeros, most of which cancel. +% Those which do not cancel have a "fill-in" effect, extending the +% shorter of the sets to have the same number of as the longer of the +% sets of poles and zeros (or at least split the difference in the case +% of the band pass filter). There may be other opportunistic +% cancellations but I will not check for them. +% +% Also note that any pole on the unit circle or beyond will result in +% an unstable filter. Because of cancellation, this will only happen +% if the number of poles is smaller than the number of zeros and the +% filter is high pass or band pass. The analytic design methods all +% yield more poles than zeros, so this will not be a problem. +% +% References: +% +% Proakis & Manolakis (1992). Digital Signal Processing. New York: +% Macmillan Publishing Company. + +% Author: pkienzle@cs.indiana.edu + +% 2000-03 pkienzle@kienzle.powernet.co.uk +% leave transformed Sg as a complex value since cheby2 blows up +% otherwise (but only for odd-order low-pass filters). bilinear +% will return Zg as real, so there is no visible change to the +% user of the IIR filter design functions. + +% TODO: Shoul return real(Sg) rather than complex. Must fix cheby2 to be +% TODO: more stable first + +% Copyright (C) 1999 Paul Kienzle +% +% This program is free software; you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation; either version 2 of the License, or +% (at your option) any later version. +% +% This program is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with this program; if not, write to the Free Software +% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +function [Sz, Sp, Sg] = sftrans(Sz, Sp, Sg, W, stop) +if (nargin ~= 5) + disp('[Sz, Sp, Sg] = sftrans(Sz, Sp, Sg, W, stop)'); +end; + +C = 1; +p = length(Sp); +z = length(Sz); +if z > p || p == 0 + error('sftrans: must have at least as many poles as zeros in s-plane'); +end + +if length(W)==2 + Fl = W(1); + Fh = W(2); + if stop + % ---------------- ------------------------- ------------------------ + % Band Stop zero: b ± sqrt(b^2-FhFl) pole: b ± sqrt(b^2-FhFl) + % S(Fh-Fl) pole: ±sqrt(-FhFl) zero: ±sqrt(-FhFl) + % S -> C -------- gain: -x gain: -1/x + % S^2+FhFl b=C/x (Fh-Fl)/2 b=C/x (Fh-Fl)/2 + % ---------------- ------------------------- ------------------------ + Sg = Sg * prod(-Sz)/prod(-Sp); + b = (C*(Fh-Fl)/2)./Sp; + Sp = [b+sqrt(b.^2-Fh*Fl), b-sqrt(b.^2-Fh*Fl)]; + extend = [sqrt(-Fh*Fl), -sqrt(-Fh*Fl)]; + if isempty(Sz) + Sz = [extend(1+rem([1:2*p],2))]; + else + b = (C*(Fh-Fl)/2)./Sz; + Sz = [b+sqrt(b.^2-Fh*Fl), b-sqrt(b.^2-Fh*Fl)]; + if (p > z) + Sz = [Sz, extend(1+rem([1:2*(p-z)],2))]; + end + end + else + % ---------------- ------------------------- ------------------------ + % Band Pass zero: b ± sqrt(b^2-FhFl) pole: b ± sqrt(b^2-FhFl) + % S^2+FhFl pole: 0 zero: 0 + % S -> C -------- gain: C/(Fh-Fl) gain: (Fh-Fl)/C + % S(Fh-Fl) b=x/C (Fh-Fl)/2 b=x/C (Fh-Fl)/2 + % ---------------- ------------------------- ------------------------ + Sg = Sg * (C/(Fh-Fl))^(z-p); + b = Sp*((Fh-Fl)/(2*C)); + Sp = [b+sqrt(b.^2-Fh*Fl), b-sqrt(b.^2-Fh*Fl)]; + if isempty(Sz) + Sz = zeros(1,p); + else + b = Sz*((Fh-Fl)/(2*C)); + Sz = [b+sqrt(b.^2-Fh*Fl), b-sqrt(b.^2-Fh*Fl)]; + if (p>z) + Sz = [Sz, zeros(1, (p-z))]; + end + end + end +else + Fc = W; + if stop + % ---------------- ------------------------- ------------------------ + % High Pass zero: Fc C/x pole: Fc C/x + % S -> C Fc/S pole: 0 zero: 0 + % gain: -x gain: -1/x + % ---------------- ------------------------- ------------------------ + Sg = Sg * prod(-Sz)/prod(-Sp); + Sp = C * Fc ./ Sp; + if isempty(Sz) + Sz = zeros(1,p); + else + Sz = [C * Fc ./ Sz]; + if (p > z) + Sz = [Sz, zeros(1,p-z)]; + end + end + else + % ---------------- ------------------------- ------------------------ + % Low Pass zero: Fc x/C pole: Fc x/C + % S -> C S/Fc gain: C/Fc gain: Fc/C + % ---------------- ------------------------- ------------------------ + Sg = Sg * (C/Fc)^(z-p); + Sp = Fc * Sp / C; + Sz = Fc * Sz / C; + end +end +end diff --git a/sigproc/zp2ssg2.m b/sigproc/zp2ssg2.m new file mode 100644 index 0000000..7cd4afe --- /dev/null +++ b/sigproc/zp2ssg2.m @@ -0,0 +1,68 @@ +% Copyright (C) 1996, 1998 Auburn University. All rights reserved. +% +% This file is part of Octave. +% +% Octave is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2, or (at your option) any +% later version. +% +% Octave is distributed in the hope that it will be useful, but WITHOUT +% ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +% FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +% for more details. +% +% You should have received a copy of the GNU General Public License +% along with Octave; see the file COPYING. If not, write to the Free +% Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. + +% -*- texinfo -*- +% @deftypefn {Function File} {[@var{poly}, @var{rvals}] =} __zp2ssg2__ (@var{rvals}) +% Used internally in @code{zp2ss} +% Extract 2 values from @var{rvals} (if possible) and construct +% a polynomial with those roots. +% @end deftypefn + +% Author: A. S. Hodel +% Created: August 1996 + +function [poly, rvals] = zp2ssg2(rvals) + +% locate imaginary roots (if any) +cidx = find(imag(rvals)); + +if(~isempty(cidx)) + % select first complex root, omit from cidx + r1i = cidx(1); r1 = rvals(r1i); cidx = complement(r1i,cidx); + + % locate conjugate root (must be in cidx list, just in case there's + % roundoff) + err = abs(rvals(cidx) - r1'); + minerr = min(err); + c2i = min(find(err == minerr)); + r2i = cidx(c2i); + r2 = rvals(r2i); + cidx = complement(r2i,cidx); + + % don't check for divide by zero, since 0 is not complex. + if(abs(r2 - r1')/abs(r1) > 1e-12) + error(sprintf('r1=(%f,%f); r2=(%f,%f), not conjugates.', ... + real(r1),imag(r1),real(r2),imag(r2))); + end + + % complex conjugate pair + poly = [1, -2*real(r1), real(r1)^2+imag(r1)^2]; +else + % select two roots (they're all real) + r1 = rvals(1); + r2 = rvals(2); + poly = [1, -(r1+r2), (r1*r2)]; + r1i = 1; r2i = 2; +end + +% remove roots used +idx = complement([r1i, r2i],1:length(rvals)); +rvals = rvals(idx); + +end + diff --git a/sigproc/zp2tf.m b/sigproc/zp2tf.m new file mode 100644 index 0000000..7848ac4 --- /dev/null +++ b/sigproc/zp2tf.m @@ -0,0 +1,74 @@ +% Copyright (C) 1996, 1998 Auburn University. All rights reserved. +% +% This file is part of Octave. +% +% Octave is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2, or (at your option) any +% later version. +% +% Octave is distributed in the hope that it will be useful, but WITHOUT +% ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +% FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +% for more details. +% +% You should have received a copy of the GNU General Public License +% along with Octave; see the file COPYING. If not, write to the Free +% Software Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. + +% -*- texinfo -*- +% @deftypefn {Function File} {[@var{num}, @var{den}] =} zp2tf (@var{zer}, @var{pol}, @var{k}) +% Converts zeros / poles to a transfer function. +% +% @strong{Inputs} +% @table @var +% @item zer +% @itemx pol +% Vectors of (possibly complex) poles and zeros of a transfer +% function. Complex values must appear in conjugate pairs. +% @item k +% Real scalar (leading coefficient). +% @end table +% @end deftypefn + +% Author: A. S. Hodel +% (With help from students Ingram, McGowan.) + +function [num, den] = zp2tf (zer, pol, k) + +% Find out whether data was entered as a row or a column vector and +% convert to a column vector if necessary. + +[rp,cp] = size(pol); +[rz,cz] = size(zer); + +if(~(isvector(zer) | isempty(zer)) ) + error(sprintf('zer(%dx%d) must be a vector',rz,cz)); +elseif(~(isvector(pol) | isempty(pol)) ) + error(sprintf('pol(%dx%d) must be a vector',rp,cp)); +elseif(length(zer) > length(pol)) + error(sprintf('zer(%dx%d) longer than pol(%dx%d)',rz,cz,rp,cp)); +end + +% initialize converted polynomials + +num = k; den = 1; + +% call __zp2ssg2__ if there are complex conjugate pairs left, otherwise +% construct real zeros one by one. Repeat for poles. + +while(~isempty(zer)) + if( max(abs(imag(zer))) ) [poly, zer] = zp2ssg2(zer); + else poly = [1, -zer(1)]; + zer = zer(2:length(zer)); end + num = conv(num,poly); +end + +while(~isempty(pol)) + if( max(abs(imag(pol))) ) [poly, pol] = zp2ssg2(pol); + else poly = [1, -pol(1)]; + pol = pol(2:length(pol)); end + den = conv(den,poly); +end + +end diff --git a/simsort.m b/simsort.m new file mode 100644 index 0000000..d4a227f --- /dev/null +++ b/simsort.m @@ -0,0 +1,49 @@ +function [r,cc,M,YY] = simsort(X,simfun,M) +% simsort() - Sort data based on their similarity with a model + +% try +% D=evalin('base', 'D'); +% catch +% evalin('base', 'load mri'); +% end +% j=[1 20 30 31 60 61 62 63 64 65 66 70 75 80 85 110 120] +% X=double(D(j,40:80,1,13)); +% dbstop in simsort + +if nargin<3 +M=meannan(X); +end +if nargin<2 +simfun = 'corrcoef2'; +end +Y=M; +if nargout>4 + YY=repmat(M,size(X,1)-1,1); +end +ii=1:size(X,1); +r=NaN.*ii; +for i=1:size(X,1); + if nargout>4 + YY(i,:)=Y; + end + Z=X(ii,:); + [cc(i) tmp]=max(abs(feval(simfun,Y,Z'))); + r(i) = ii(tmp) + ii(tmp)=[]; + se(i)=0; + Y=meannan(X(r(1:i),:),1); + subplot(2,1,1); cla + hold on; + plot(X(r(1:i),:)') + plot(Y(:),'linewidth',3); + subplot(2,1,2) + plot(Z(:,:)'); + pause(.1) +end +%r(end)=ii; +size(Z) +abs(feval(simfun,Y,Z')) +cc(end+1)=abs(feval(simfun,Y,Z')); + + +%if exist('j','var');disp(j(r));end diff --git a/slidingwindow.m b/slidingwindow.m new file mode 100644 index 0000000..0bf4a84 --- /dev/null +++ b/slidingwindow.m @@ -0,0 +1,209 @@ +function [Y,W] = slidingwindow(X,N,dim,W,Wnorm,padval,fun,direction,varargin) +% SLIDINGWINDOW - Sliding window filter +% Y = slidingwindow(X,N) +% Y = slidingwindow(X,N,dim) +% Computes a sliding window filtering of width N along a given dimension +% of X (default: use the first non-singleton dimension) +% Note: The floor(N/2) first and last samples are computed with NaN padding +% Contrary to BUFFER, SLIDINGWINDOW works on matrices +% See also: BUFFER +% +% [Y,w] = slidingwindow(X,N,dim,W,Wnorm) +% Computes with a given windowing. +% W can be: +% - A vector of values (e.g. [0:10 9:-1:0] for a triangular windowing) +% - A function name (e.g. 'hanning') +% - A cell { funname arguments }, e.g. {'gausswin',5} (see: GAUSSWIN) +% N is not used (i.e. N is forced to the length of the windowing vector.) +% The W vector can be normalized so that norm(W,Wnorm)=1 (See NORM) +% Use Wnorm=[] to prevent normalizing W (default). +% +% [Y,w] = slidingwindow(X,N,dim,W,Wnorm,padval) +% padval: padding value on both sides +% +% Y = slidingwindow(X,N,dim,window,wnorm,fun,direction,funoptions) +% Instead of computing the sum of windowed values, it will uses +% 'fun' (a function handle/name) using additional options, if any. +% E.g. +% >> slidingwindow(X,10,1,[],[],0,'var') will compute local variance + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2005 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2005-12-15 Creation +% 2007-04-03 Updated, now allows specific padding +% KND 2009-04-15 Updated: remove borders +% +% ----------------------------- Script History --------------------------------- + +% Author: KND +% Created: Dec 2005 +% Copyright 2005 +if nargin<3 || isempty(dim) + dim = min(find(size(X)>1)); + if isempty(dim), dim = 1; end +end +if dim>1 + ndX=ndims(X); + X=permute(X,[dim 1:dim-1 dim+1:ndX]); +end +sX=[size(X) 1]; +X=X(:,:); +Y=zeros(size(X)); +if numel(N)>1 || numel(dim) > 1 + error('N and dim should be a single value'); +end +if nargin<4 + W=[]; +elseif ~isempty(W) + N=length(W); +elseif isempty(N) + error('N and W cannot be both []!') +end +if nargin<5 + Wnorm=[]; +end +if nargin<6 || isempty(padval) + padval=NaN; +end +if nargin<7 || isempty(fun) + fun=''; +end +if nargin<8 || isempty(direction) + direction = 0; +end + +if isempty(W) + if ~isempty(fun) + W=ones(N,1); + else + W=ones(N,1)/N; + end +elseif iscell(W) + W=feval(W{1},N,W{2:end}); +elseif ischar(W) + W=feval(W,N); +elseif isnumeric(W) + W=W; +end +W=W(:); +if ~isempty(Wnorm) + W=W./norm(W,Wnorm); +end + +padding = repmat(padval,floor(N/2),size(X,2)); +%padval=2*padval; +switch fun + case {'', 'sum'} + %for i=1:size(X,2) + %ya= filter(flipud(W),1,[padval+zeros(floor(N/2),1); X(:,i) ;padval+zeros(floor(N/2),1)]); + %yb=flipud(filter(W ,1,[padval+zeros(floor(N/2),1);flipud(X(:,i));padval+zeros(floor(N/2),1)])); + %ya=ya(N:end-1+mod(N,2)); + %yb=yb(2-mod(N,2):end-N+1); + %Y(:,i)=(ya+yb)/2; + %Y(:,i)=filter(W,1, + %end + + Y=filter(W,1,[ padding; X ;padding]); + Y(1:(N-mod(N,2)),:)=[]; + + case 'std' + warning('Uncjhecked result!!!') + W=diag(W); + for i=1:size(X,2) + ya=W*buffer([X(:,i);padval+zeros(floor(N/2),1)],N,N-1); + yb=fliplr(W*buffer([flipud(X(:,i));padval+zeros(floor(N/2),1)],N,N-1)); + ya=std(ya(:,floor(N/2)+1:end)); + yb=std(yb(:,1:end-floor(N/2))); + + ya=W*buffer([padval+zeros(floor(N/2),1);X(:,i);padval+zeros(floor(N/2),1)],N,N-1); + yb=fliplr(flipud(fliplr(W))*buffer([padval+zeros(floor(N/2),1);flipud(X(:,i));padval+zeros(floor(N/2),1)],N,N-1)); + ya=std(ya(:,N:end-1+mod(N,2))); + yb=std(yb(:,2-mod(N,2):end-N+1)); + + Y(:,i)=(ya+yb)'/2; +% Y(:,i)=std(W*buffer(X(:,i),N,N-1),[],1)+fliplr(std(W*buffer(flipud(X(:,i)),N,N-1),[],1))'; +% Y(:,i)=Y(:,i)/2; + end + W=diag(W); + otherwise + W=diag(W); + if exist('buffer', 'builtin') + error('unverified') + if direction>=0 + for i=1:size(X,2) + Y(:,i)=Y(:,i)+feval(fun,W*buffer(X(:,i),N,N-1),varargin{:}) + end + elseif direction <= 0 + for i=1:size(X,2) + Y(:,i)=Y(:,i)+fliplr(feval(fun,W*buffer(flipud(X(:,i)),N,N-1),varargin{:})); + end + else + error('Wrong direction'); + end + else % No buffer function + N2 = floor(N/2); + L = size(X,1); + if direction>=0 + for i=1:size(X,2) + for j=1:L % ceil(N/2):size(X,1)-floor(N/2) + Y(j,i)=feval(fun,W*[... + padding(end+((N2-(N-j)+1):0),i); ... + X(max(1,j-N2):min(L,j+N2),i); ... + padding(1:(N2-(L-j)),i)]); + end + end + elseif direction <= 0 + for i=1:size(X,2) + for j=1:L % ceil(N/2):size(X,1)-floor(N/2) + Y(j,i)=feval(fun,W*[... + padding(end+((N2-(N-j)+1):0),i); ... + X(max(1,j-N2):min(L,j+N2),i); ... + padding(1:(N2-(L-j)),i)]); + end + end + else + error('Wrong direction'); + end + end + if direction==0 + Y=Y./2; + end + W=diag(W); +end +Y=reshape(Y, [sX(1), sX(2:end)]); +if dim>1 + Y=ipermute(Y,[dim 1:dim-1 dim+1:ndX]); + %Y=permute(Y, [2:dim 1 dim+1:ndX]); +end + +return +% use FILTER instead of BUFFER (which requires the signal processing toolbox) +% Y(:,i)=[mean(w'*buffer(X(:,i),N,N-1),1)+fliplr(mean(w'*buffer(flipud(X(:,i)),N,N-1),1))]'; +% Y(:,i)=filter(w,1,X(:,i),[],1);%+flipud(filter(w,1,flipud(X(:,i)),[],1)); +% Y(:,i)=Y(:,i)/2; + +% Y(:,i)=(W'*buffer(X(:,i),N,N-1)+fliplr(W'*buffer(flipud(X(:,i)),N,N-1)))'/2; + +% FILTER is a causal filter!! +% ya=[filter(W,1,X(:,i),[],1);zeros(floor(N/2),1)]; +% ya=ya(floor(N/2)+1:end); +% yb=flipud([ filter(W,1,flipud(X(:,i)),[],1) ; zeros(floor(N/2),1)]); +% yb=yb(1:end-floor(N/2)); +% Y(:,i)=(ya+yb)/2; + + +% OUT OF MEMORY error with buffer +% for i=1:size(X,2) +% ya=W'*buffer([X(:,i);padval+zeros(floor(N/2),1)],N,N-1); +% yb=fliplr(W'*buffer([flipud(X(:,i));padval+zeros(floor(N/2),1)],N,N-1)); +% ya=ya(floor(N/2)+1:end); +% yb=yb(1:end-floor(N/2)); +% Y(:,i)=(ya+yb)'/2; +% end + diff --git a/sources_menu.m b/sources_menu.m new file mode 100644 index 0000000..674839a --- /dev/null +++ b/sources_menu.m @@ -0,0 +1,60 @@ +function [] = meeg_menu( varargin ) +%SOURCES_MENU - Add a MEEG Source related menue +return + +[hp,ha,hf]=findTessellationHandles(varargin{:}); +uhp=get(hp, 'UserData'); + +% Remove previous instances +t0=findobj(hf, 'Type', 'uimenu', 'Label', 'MEEG'); +delete(t0); + +t0=uimenu('Label', 'MEEG', 'Parent', hf) +t1 = uimenu('Parent',t0,... + 'Label','Surface processing'); + +cback='uhp=get(findTessellationHandles, ''UserData'');'; +t11 = uimenu('Parent',t1,... + 'Label','Smooth surface ',... + 'CallBack',[ ... + 'uhp=get(findTessellationHandles, ''UserData'');'... + 'p=handle2struct(uhp.h);p=lowerfields(p.properties);'... + '[fv]=smooth_cortex(p,uhp.vertconn,.3,10);' ... + 'set(uhp.h, ''Vertices'', fv.vertices)']); +t12 = uimenu('Parent',t1,... + 'Label','Adv. Smooth surface',... + 'CallBack',[ ... + 'uhp=get(findTessellationHandles, ''UserData'');'... + 'p=handle2struct(uhp.h);p=lowerfields(p.properties);'... + 'vals=inputdlg({''Smoothing factor'',''Iterations''}, ''Smooting parameters'', 1 , {''.3'', ''10''});'... + '[fv]=smooth_cortex(p,uhp.vertconn,str2num(vals{1}),str2num(vals{2}));' ... + 'set(uhp.h, ''Vertices'', fv.vertices)']); +if ~isfield(uhp, 'vertconn') + set([t11 t12], 'Enable', 'off') +end + + +t2 = uimenu('Parent',t1,... + 'Label','Back to original surface',... + 'CallBack',[ ... + 'uhp=get(findTessellationHandles, ''UserData'');'... + 'set(uhp.h, ''Vertices'', uhp.vertices)']); + +t2 = uimenu('Parent',t1,... + 'Label','Vertex Connectivity',... + 'CallBack','disp(''smoothing'');'); + +return +% Context menu +t0 = uicontextmenu('Visible', 'on'); +cback= ['u=get(gcbf, ''UserData'');' ... + '[p v vi f fi]=select3d(gcbo);'... + 'figure(''Name'', sprintf(''Scout: Vert#%d [%0.2g %0.2g %0.2g]'', vi, v)); plot(u.time, u.cdata(vi,:));'... + 'hold on; stem(get(u.htime, ''Value''), u.cdata(vi,get(u.htime, ''Value'')), ''.r'')']; + +t1 = uimenu('Parent',t0,'Position',1,... + 'Label', 'Scout', 'CallBack', cback); + + + +[cortex_smoothed]=smooth_cortex(cortex,cortex.vertconn,.3,10); \ No newline at end of file diff --git a/spatfreq/exp8full.jpg b/spatfreq/exp8full.jpg new file mode 100644 index 0000000000000000000000000000000000000000..170846b10b02b8299f74a4f46bf99fab9af20fdc GIT binary patch literal 29102 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 literal 0 HcmV?d00001 diff --git a/spatfreq/exp8hf.jpg b/spatfreq/exp8hf.jpg new file mode 100644 index 0000000000000000000000000000000000000000..fb1bbac1d8bca70dd9c99c4dc96d4a0677862fb2 GIT binary patch literal 28813 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 Gz5fHW#@=uM literal 0 HcmV?d00001 diff --git a/spatfreq/exp8lf.jpg b/spatfreq/exp8lf.jpg new file mode 100644 index 0000000000000000000000000000000000000000..78da2d04a1aa3b89f93c2a54c76f4bc7ed6f705a GIT binary patch literal 17276 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
  • Q z({>FK=SSgX_<9T-f{iZFO?k~w`Jvj{t5j6kp{6E)Kfz8h#1y5bAw~w42G6rzMSHAu z#qD`KKZu+CT0m{HYli311p%4qzVzOvY2_GgiRbB-d)__TEZ^KQr~?_5do-p!s?;#> zWryST5c+yZ1zkhfxGflN1AB<|a?`F#k2{7?q(PVQ4j#CklLMRmAK```^m8Y7831(L zj62!36DYistV@Px#vWl|?o)50tD&cnEIthjEz)izHbl&??O{ZyUe(yc+u z{trD4ePn2cwOzOYXq&OMG=vq~ISieuhFy{SJmFMM)f;pTUoDoY}( z#Q;bR13D}a87g6jV@$QF0{1R}QtwPwxf!N_OLgmIef z-E>jP^#PQR61d!k8}8hrpWA0AoF25aN5utGN66^Rg2Y8aoH4XV^d;Xfju@K2>c|4UqM>X4@Q z8JOs%0A3CQa1I_*5VRWbqx_V1GkHNRuA;@n%v}(U|M~Xv_zPKR#Rh&ALe6{GaL6lM$EWV`t6Y^L@U#V?7l1TM=8aN|eeU`(TyU!D}TbtxO) z>lIg7no5JOQCr1ocShu~_#>2K+wivd3*ka{Za$N{%2XOskW$WpNux*6!leSaGF&X3 zq_r;z)GqDM-Qi3$1~q$}mV8I!I^RH3iPc^jLXWhrGJnqHzIm51Tr%ZYn#}1n8Bq5G z(4C6ceJXILJzv8US98Vf=3ckqEK02yH2v*|>v6`BPG=&~Pm4AkAqG$YM;8>A#u+Y6 za;ERQnAMRBgOM+=EQUAlqg0mYfQ*W&qI8(A)iAQ{09XOeQr=%b%Z6}5jn1-!8fs-! zbE(YFNX(dHAF+XU3YY( ziJak7?pd}x$+)82UYG~9`x|!MnP&Ag>~uzpJ{x{CS8=HbQt?4edJ1);De4GX zQ?oqIc-L7{^IOIk8|U&-(_Zk_zqj_DAqwV4-U^+S?p*3+^n>ou4mj;UeDyc4zAZTp zv4-1T)ea}mk#mTpiRo9P#0y5}AdM?q<~c zi^OXChfUGf@VH*9@Pc3(u=U>E%pGNIA?2oT9NCSTrNQql8;uq|cc@?KTrv+lm>X&{ z%20>$o$yi;f+CrPsY}~qi*3f*yAi;(CH*u7&kS9k>bqP#`*TNi{ML>oW%G*LGqq{i z)`o>FIsG1}^S-77kqb+MwcNY>^|&yZtH-Kcy6ehCpEl{-_re?2`L1NtG|=sXX64+2 zadjV0a#W}GHXWFJ(vzX(sz_LdzeqHrLOvy+BPE2pINga3&MTO{98euw;GMRB@{a5A zcTI!Z#$#f#O+y5WozCg&*reb%v9KQl&<=>0kc9&&9WOgwJzoq3-yE>j6qP`s)C5^4WurZS{zl>y9)OnQI2|+{O&>GFJU+^vmDYu8 zKWhVb>5{nq4enY>lCs9DDL#~e7Q!P3I`CwbxGyn+AL+$>fgC!5Bb;a>ejYX|F9#Br zfgldN-fi$$HdRAEb&S%ek4+s`b$`!z`aCWD@K}t64fS}Qwup)8y^XmwFro)xZQ-K0 zs#;^?n~%HkLm@i(g{Qr3^3x$#sqW<_%I?^Yk?x^^X^&Eoo4FcuQ{qZ7{gPmQ!Q1d0|0`#Sf|U%t+s(b147G@rdtib&Z)ZUM z89`CCQtYS=E%i12kmIOnA literal 0 HcmV?d00001 diff --git a/spatfreq/exp8mf.jpg b/spatfreq/exp8mf.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2010cd4e757dbb2028c14785ffc9db21abfbc576 GIT binary patch literal 21077 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 literal 0 HcmV?d00001 diff --git a/spatfreq/fft.jpg b/spatfreq/fft.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6edf70c3a70ae26e8059ed62cf8cb3dfede3a5ce GIT binary patch literal 93017 zcmbrlc{E#J_&%D}&?>4ET8gTvEumFIYf4*1)mU0nj713|RrB0x%~MfPd{8qr6&cK_ zxvD5JCIm6n5E3O)lDPTa-(A1E*8S_Qdrwxf)=5tGInO@t+53H;=h?^8#|waSPj&Tm z0VhrX04G>4z%d@6&D#6_xBX8e|NmNG{rmVk;QVPo^~rZ^C$0ldo<71ew9)wCb$=;}Su ze`@~xg@vV+wT-ij>nk^Rh=;GAe?VYRaLD@)(J`@c@d@b}pEBWDpR;p{ic3n%$}1|r zHZ&rTD0EYEOILSKZ(sk9fuCd8@rlW)=^5N2acOyFb?wjk24(l}-ahq!_V4h2aGe0K z{qHXOU%>t!xX!b1on&WcV?XsjxK5l5WWCwWvvY{w;k@wh*(oQVi`Nz3oxb!at+2k6 zOX98>ng8XR(K7;)N{dpI|AF>@BKv;_Eb9Lgvi}Y2|Hg#_@UWd=-8{DQ01$wYatw$n zi|l-e@iLj|>L>CX12`jqdJF%Q6a;DIhGX=RlVNwJB1X!mgQBQEyLf`CI&*$Eox|x| zviBl*?KjwXkuU&^xVNC)asQw7hR zOziJccw2wT?Iq_ZU)OZr_%cuWqyM~}T9x-WvgrJ=R7M(q32nC_tuwbN(Z#9|?%4(m4ijykKju#N6rBS+2JvW}<&3!PGOHyb z$|W0h`G&^%aqf@DfX){n?_+>?S=Q3mbKEBj$TbnXa#dL_wqIx=ia#HCIfk{N0N0oh zDMk%E^(!sbDllBa7?nPJZ?Of1YHpoUsD@#_)mRS0lpsVZet2RH9i@k?kDd@DUBpY~ z$)_z!{A;s|{)C9MnLvH+;;dWjX47s5ucaTErf=_BUHWL~v3gVR^%;q4Z~4aN zRGDXPvMybgay0-}NH?2sN!e=X_tUx%CXzB&u;$9=6HKN6#}zAXZ9M&?{;!jN&oYV$ zjO=u8^Di^1a6bl|->Z7gsUQu6brzi1!#98UqNwpqwHtdz`x;0NZPs(=KR5Q&4`Cvd zQQn-?GRuehe)D@l2o`$FWAVa3-py;!_M?R=wP&x@&a9L}IM zS2i9xD)962_N)B0Ax;o5N5wToOwN&0?orXVprm|vz(0fD0tA{apuSk>yd*nv#UlU$ zXHJm=yZGJ{?v5onsS0}TaD{1&e@OskqLpIqh&~Bmchd3~BfoIKcX(C}KBM$l+u9&1 zm60ReV()*?+6-QFD$JKnjqP0W2g$`912kOnzQ4qL#jxYJT}>JupwYzWfA3Q|d@t=PyyCbU~4q)jd6&orZ@IMi=niWJBH+#LLw)5HJYOOqy;^x!A;~|w}ANlAt8mY*c;sn ze>BV`gU0Sx_~FTv5x$wg$6XlBHhrQF+zjgd`BHPtqc4bTPOG1sX14=R{C5r@(dbmc zcKDVi%nBX}Mzw~?Vms&=C9v0lB%4_J2#bEFC1ibaSdnu`wG;I^d3xl`sb7F$-f)$9 z)DG#SbBq;Jyih;j8;G0`bahGM^R9+c(7u<8Gax^fazynX_4ZeYRx{c%R)jjk6<64z zb(~wz40mf}Uj6Si=P8lL7@;uDVp^#NTg?WS+gB6un9D8!DLPrOf$MJ7PE1Gb3kD){ zPp*koSk9wONL;Qb(jDTTaa{@@Cl|cU7L#EUZc_xmzR%8<^5HBZOoY*fafe-`LutEpDjrXvOmrt!668qE=-TsSeahSC{(H~24 zi5YyF9+aUwN<_4yCjBs5Kg> z$EL{q_LpvMnez8c))X^IlbQgIN`RapLwkQc1>lE1)tg zJ65_Nzzcb~xS{!W{7Q}te4o{?olq9)3Kin^_XunNE~c4xsHCzCPZy?fg!@9hsR4(j zbNQvf@-B7Zd%{05zo6S}LW!& zH-297tjgvYbgOWH_|Cdh%5oa}!y&vY)LfNR-e_jDYdy5^NrN8jJQmS|LSrTnqe|ze zXRRG(nm+!!pre(&YHK&`F(S2Jx~waF+Gzs8Fb30*chufS#If+&iqUSa{DR-MWsU{Z z1x(%@te6&y0VjQ8j+=21{IQZ-sq}|FeL*YMmBq*ti6rLIqk|hYhWf4 zjhA1kfB!e5M^J@0%y-d4(T4VFIB@q>(`29xE$~4Db7VZa=Wykx9b{c0c6%}XVK8uI zI3v`@hhr`iIhb8X)xm!PKV^BnZw zu5T(Ma9%ckH=$t?P2=cRKtzTNU0{lN<~3BQqL@+UXk=oUAF6(HUo1+jaT%UID4M4^ z^hM3TF3L;3wsuwZn@NRU$%ZQN@}9K}0-+W@js~_bVrW1<`lZRgwh7&4j4~kMP6h2H zP#4Gdjx_{H35*hK=OJq7X{2Xrw9O@xSKcJ(_8OuIyE3jMGG!!RSt+%hR?he9bTt$a z(C&ua)}K(2cWZY=-dM&%HkcVh{np(78*(}LVPyq|icNENO*E8Bh{)XKpi$z~k~`>-w1g#Ci? z9P+?gU`7i83=4`NU7&z@MrV;mSTbdl`iaOIG>$5TCX&F?|g?J_{?zO0p%DS`aNA^yOP)W9~xT)`8sJ z4IkTihfXC9rtrE!g@~mAB0+XA6lyOVl)igZ#gR*<8_E9e={7Wb z2_I)EWQl1enL)_XWM->>C*pQBb=`8XFQR~F4>xc`Zg7U2nm!UkL_*4Qb~hg<%nwnX zTj0E;hwY3fpxb-l24)jd@6Fm#XmbsfEb@9w{^mR_SC0*1lUEw$sb6Zl?tS7=R)#Lf znzcfG&K77yBEso+lvHm9GP21qYS2Xakx@*+b%)a=5|d{`)uy(PcWaU)omgVc3M48n zIbm+z4nl1aiz_N%alI1Cs_kqW4C5)y-K#HCxk4#px^P|x(e3L5A%1VIa}%pVh1;FY>nDKKdqV-Cq5q)8KSDi8cTKV$HjVok+|it(LX&Hj zL;Rirkx@pk{t6q27;T$mSD2K}d@gPpg}cUYLpcjVtK6v#fQT%24A~Y#ySbCoumM8M z9>BBIVh~VhAq{v08VG5L#Faq>^t~MxYGb{>X=YCIG~l}ZT9DBx@mE=&9gRzx=9gI` zoIhrvd{jF`kMvBbWejX9Rhtn=mR(@CIp2c%ytLqrkkr@i{>zL%?8})5n(HOWn4h>(Q+<&3K+fe>32t7 zsl%n19_xO8>O|{%=6MEapdQfxVCXckij1YmPD_OyAPGw?)R)OQoqunugC}C>_4-u6 z;lswZw(;r`4DFeG5oI%=rRGy$?TbBzv~1q3%e5r=)zBgB^@8aG=9xOyZI@*fb|}|< z--#1LBp)P%-CwGUnnyY#a!z&ce9+>c2o%%?xlzq&gRGi_ZNtVbjwu|b^}Fra;+(@% z3gO)s&m7(2(#;lS3jF7dlHImOSQQiC5hxV>J!h4Qe@)Y98j}()wunt`3u>O31;uS#V5*|Q)atY z^**^;rfHO0WsA&cCfcw9XN2xJ39MBbd{R^Mp3@+7NDBv4WI~9^<9h7zP@blOm5evT zW4a4buV19D_IJ<854D}I7~n4n6tLJrpYQcfY8BbG-YLUP5()e&qu{6K?Qj3M;>;Nd z)Zt5=M#!o9%y0O7#$c$?tsjM zf|gR4MWeL~gxG+MxJK(3R##GJcTyRw(BZNgdoIXz|6|cx{TCM88?2nI(>6kuE{FVv z&gvydQ)(MSeT1fQD0C(5>&7loT40AgBn)eBaxbrBp65)po6Gn3gu4ZC8s_CecNh9& zJ49&ji6$RNZT`f%`1~7KWz4A{#bbb#&tf3W@>Pd2c`Z>nQefR)a01xos*vCh5}FB$ znD5L{vb3>lYZ1sIioDs?U(9`s9zV1*cs{P7N73#UyMQ1mbYghD5MUtKWE7!rGALxQ z`!>W9TLN)%(@YY`NvR30$b~${ZiIXw`SWlEcWm6V2`&a4qKSHR{`br?cPL!7exPU& z8$DDM3Hs=#ZeS!m)#>YbenYa8J;xS@9L@+1`StCJ1osuIY;SL^haBIvV4*|>+PylP z-%6k>kPy8Oj#wnOZ%~-B&ygL?q$@)Dyzu9xltEVJ_93@7L@@Tt_^)B0RzIZ1_DSOp zryR4Jv~wZ7Iw4ahVdhTY7}U;UQx)$~9YXxP>`7^JSTx z;*}zU*rC}$o+h)+d7=GQ89TAw4#kNDgWRJ;7P;Y$!tJi(`~)nrP-KmqnnA4s`LbWD z1j5q6%{4j8>$2tP!h_e&=Jp>0$S*Lo8&lsy?aV+p{DLJ3Pu4T}z4Zk#86hz{6K0>a zD3bGIlWilJ;@mZLKw!Jg1?`qE7K?kKUm$nn&91kZ`YsY#mkRPwi1DFieFvdHeJ`gT zf0kE@OvRkD*sxkoc6)_>PI+lNpmE|^I{PiD_uuyGVrOC7eOaa18rfbrq4_Yp$heiY`z#V*z4 z^hk1v@1|q>pXEAg=LXR~;Y3`5K40$BvY?oB?ch5$dkJTdiOe%F*4TY<6oXoBZk+I| zGQoqXsZm_DMYoIMBtaJz4Ir0361kt0DqQWIDex0lpJ#PmYTr(*m@tu%#7gOl#5mfF zj+7!s(`gp{Jvgx#x@%-vJvD5k=w2!wb6WhTYa)3snvQ#6 z5Po)VhGolI$M!kU`y;ruZhJLyU}A&-Evqvpk3@J^G2bsFl{27W*x~e*y=^FB~OJZ9C?KCQ*>ipRq2-$sOwWN-Jz1VX<5`(#uwyt?GkVWfz_*~ zKWIrY$qrO&`5`8LRe&J7X;8-kSu+|Y-yzaW!&5Abo(ii66;IYhNCXYQr0|lC1f$B@ zI%;|PA?4^9(&TR zn)cPC*1VU0ovjAZHv*MlG;NGWYK*rQ#!7P=}hc_5-|;@NP5Ki4_aDnl!G|s z)Jya$G$dJXL9AyaUZAD_{l2Eji#8KJ2s&SwJlR~qkJb7)*>Q7}u3Yhm;??q(`Iu4A z<_ueyAt^=1sZ_LlZ0;DXy(V6mZv_9*sUV@KR3EpD{@3$SSK13Y@)K;f*3IOf(9)!R zJrt`i5d<~FlGd*T1li+F5aY)HCqCR$!v1PQQ?O(DeTk;e2(JtCgZht`lMBCt)cqpy zo6Iv@j_YO9%vlEnq3Xh5@yusrD(>QvaUIb>Rkt<#slkDXP(SQ?(0d)`udMFFgvNeI zh4I+@dcKz>Wy;+1C@^AP<5F!XgH& zUde53ZwC?lTa(=WrKLU3sB6SEunLlcLM20#QT3v^!POg*9xaK}GXs@2y|18CFQRyF z-5(1oj_S6`;eAC|(w?Z@H?OekubTg3836ZLKb*`64mz<Cr)nkkssL{~ai3s55IEQ|y9S?QFf2i_!|R9jct+Lj}1_j1SJujG+ZQI~p+ zgKgu4oWi`vlyR=HTieYtE)7jLHy=+|(Lr!xBG;-)NPZ$4uDwlg@`6ce@0HeYsp#%G z2qBZQRnc$s>egnONSo__bWBj0xDT$Yhz~_s?o3L z-oBpz>Y{D?c2Y}Z^kR(q50boc>*hEzu}g(KI~eR67+h{l4Vf#AsdMEB)=&m+qbmma zvIO!(>zw5d{eDXf`Ewv^KT#l-CnsNgx=mrH;!1XWB_}gj5%pZ{304Mv{luOkbIt*M zT{$MRlsSz-{M-}_K<9j|t*885lA;SkeLWrFJZ~NnIflhm{I`q zYXsvxQ1(|6UJmhQKyqR#p;a5P%blK_8O4Ln{P04FS?k#i2S}nx^8~UInRw)I*3h9x z#o1__-SQ3ut)gNh4%OP2tCq~~HW*9E=C2y4T%hfX=sjysC+-q{i>UUs*r$0>sq-$A zb)|22CZyZLZ|#`9D;^hMLgbus^&1;ws5?(u$u)S$VFdDl*mGJVmCR`HOE!5SD25Uv zaTU&>npN_O48o6|gqb1W8^qNOya^ilL|)@ny>LuBGOe1lt(2?IU6ricuaE4wJvUUL zz*WP<7ocU=eOs6_4oEkgw?Ht-ZxDtPUgF5yx_LGm;#p^HW7wfdW{`gQ*v>KPOvYF9 zS3|jv?t9m4>E>O-decXGI)vfSS8h!irr1^H{G_~Bp3Tou(#-N0Z0`@WCuxAU{g;aL z$a5@dU!5ChWahb<%ck;G5*q~Z)}aX`fVwmz(xHzu^$c5^JVT+n_#OdA(Ir(9{s^|A7Tr1%F)fDd0y|jWnvu^t z(hqxy`IZLr7g50t>x7zIu(c;lBjDyGgd5sr;AsepMoN-KbWH4<@UGZK%wtd!E6MqV zfvhKe>UM9SuYbi5w3Hr1TPm~1|A7a}mg9^nAb)8%-lx-Z+M$}Wq*6U#3HoOFCcG+q z(KBptQEf7^}a4tR+_%cX? z0d+9RdEzJ=>RGy(_UOb8&XV-+$WJ;6h=k$qQol8gmyUYN^T06(P~T0_LCdBDcel{i zC87wAowit($Yd^mZEk5i$$OvIZV$vWbSgGX{j3s(Y+8Vi5SIO0&cx3ngC}0WgQed0 zh>0=p_7?p~OAZ*GJQ|op>|C&$nXk>{-S4)kw)EtzQ|IPGxWnOxPS_5g_A!$89u1ji zyrQ5Cl`);zm(}<)71%PI3~aAP3GV?#{~@zU{$NeE7j}xeDZILxPlt(~?2UNhQMjy8 zqFKW3=!cMqR33D?q`)^%%5aW%j&#BggiT7hE9C2(t-bB6XMjZ<8Juk0m$xz?HEXUP zD)(LNsZ9hS61_h`t@05h6&~5(?37f6GFM6p5U`&0YNgN8YPIlhGggG4E=WW#XF11e zG0b1H^i$SUfTc}9fyDY@cx_raekc@${>g-M}B}PpogpWVS#GX;gJPl#q z#O|Bmgo$k}3UNx|dc=_rjvZt|^A{YgXtVopE~eaZq|7to>V!S?uG!djont=hvRiaKjuZ~NS-#N{U7Z!8 zRaz;9#_ep^N+Gk%C5*5;JN}nzy-Fr|>EFcyB4)+VdL-xK0bl3>E2%D~CXx#|y3jFN zs&s?jcCVjsfS*!y3LWfEA5N|Dy*&m%#qc|(&5&AFnyY|UBPuS(tuZRK?mc}Q!k*D@ z_$8F1wwOv(HT5uW1}5(ehY!&2DN_Qw^UM|sO^x5^$?TI701PE8$Wsg-`t0Tv1{&iz zAp^){_2S+4`S*WNDn^#vp4s9r$CQ-;r9?h^!AH7zkO-2Df8;IL-o57xQQ@fFeUQ@E)s`gc#FQA)rKeU~m2`sgMt8kmruUa| z_+fq<#r_3(suw;v70?k}557O$m3HMA}W90dNXPCwQpr{TWibc)_JfL?uDMjMZoA3GEhyGL| z=FTwyqA+bk*yYBP6inDjX;UuRv(!^pKy@1@{`o>!g$qC9>q3K*DhWuTth^9Y!d#9J zZ*`keAYS)n27gs!G0}rmRIF`+Dx)ak9-(vf?aO9b-BN4ZXP4IlD(%>U;+$ERfQ;i=JE=U?<#_fpn^f1?-5dSv8yb;7^(1Ar_;^0YSTXQV=#gQrjBOz9jVI`fmt^9;OZ^WHJwKL1|qmbS>OgBX)d zvUWBMLbLv;zZ}`+U+Cg^6rt7b&UU0$lyHNM_LW5wZB@YgiDmpOt?L7}51%=x=AGK3 zmJkEb?^aPg_W;*@-#iMGs}NIp=8uiv$FR+1y*ThWZz)cpy8|ZzM802qmy!0~VXU7n z=xzsg7vnEUnSKiHBi!4aYqG-z6+Cx+xuF

    GfsM=k3`CV!r~rDiv0LZvfMp!a@=<85t(m6%?vgxUDd#=yn?OZp7hOS8~-|Ccj)#G?JR{m7gb;juCzE(6; zBHroIBDAP^Tqg6~olYm{dHBYzX+l`5`%BBcZ6;vHQtf& zNaDrZ>f`?Qum5|6@_CLD<6Xma-QkJ^a@RZdf(|*J^N0?o=a#;AAB)<0UATQ@`8})t zPeWge>!-j2IBLkpHvl75`k(ybtge9Kfx>0oY4~Vsw!~%bs0Fq^sob`1s z#}l0E3M*IimF9WmGX90Y%XY6^Y88&VzALJDaN+)2=0E1o0-g@hFfMXWi$XVuSGaZ3 z1v$|cNc5%FME3Zivn0!(Wu8NQMjgbh;!PUSVs6QW8C&0?;g3!n97KgFEU7oi?a*BV z1!JGHH)&lRTZ>vUpB)hQ40i)ZTX0wIDVJ!vD|YO1~QBn>DT-QO~_nn#{mIu&d51FLLJlF12b(3ZDA(vu)UwQMU_l#ZER(DQ(B^m&Ko@>+%`iO8@@fk_`EP$t^~? zXh)N!xGjpMwkcQItskhl#lgc}Nq9d?n>V^yFwg5Z9qyOBxF)?NE_FFA9pn#sr4U_k zo_Usb%OP4!03nMx9rlpSyV4&U_OQerJajeiE`$Ir^2qUPEaGMen^|#eXIJ!e=#cVy z1kW|fo&yPB7OEl}`}>Xoc)_LA?#P*A0M(V|OoSymTtL>Cad|6Yc&!ZlT^El|Jx68P zQFN<2{YK8ZtL#3B*|ePSmLpx`>0Om9`pEaYV8(!&&MM|)$OXC-G4$x@D}-eBRD z;9pfjJ26(>Pn`2lo$g_44rY7j5Y8Oj{eBD(?clVZwd;p?3ij1SzmAv6lt=-$V#IoBFH6;>MUq?p zhwFbsH>Js^(-l*nvUcj2ojGnPbmhX?*<_i1A;Cn9y9rk}Z{}=Zpx_0tbVf~QxgE|L zLV~@bGQ)hqEjpsV`IcVdL==zA(jOz<8%~!I04M+~76zx!Gw(NA=vFgB;hnq4p9mqDIH-Y_G8t-JG|;>!pvAwew7k<1SGqd` z-oX6H$*fI|g!!`L!fp@n&SlqQQq;47d+R$`<72?%(WO!Ew^v-1`D0-9!6DGo zadtBs`?;XkHGR2}N_V(Q^E*k-Eg9M=r(MRZdl3gVVJ*)EZ${S2-G$^y7Zi3AUu18$ zfqY7Hg&AtcfG71if={;m6N91U6GTMwGpYY>ik3pS4XLW8>a$=%@5K=@Y9_OBbOsV@ zF$&xxX&WJ!W35rEM*PmLt<7U3vuTI2M5UZhC?d1l)J_E=hm7A>e^LXSvf#Stu%0&e zB;>SGyRb?6m2I7Nc&R!L{AbA&yLG)a+`u?x)21asHZDolH?rLQqGv;kd)AxSdIaT+ ze#7>*{%1A`eKM)!wsamxN2y9Odx$@NBLJmlk;fjAT%!8ui4#^FY-a%?dEW5b8;1f( z)jGp@74DNc;rs8Uey4D&*T}qR6;dhp2jS$0GghZPTfC?iV|H_PcKj%;vZl$_n@#jo z1`7EV(y!lk3`lU+xijcr4l%dIYcNvE7l({jjB4YFR`PpCY2`X4QbU=9DkuN1>-B^= zn~fpPO#^UiXhrc@ozfBGn(?>Ip^|y(Ez&>b8jrHF z{5I7}YwOOUk#OG3{^@PHUb)BE`r6|RQ#81an-*$;K_-CxDYoovKVD(?bM55C_+}oe z#8%j?$7rfh8B&vrqsob!K>gy39?y~ul@>yQ+--s$5dVfBZOluf_tXa-^t_E)x$mQ>;hh&{4gK-9&O-K1$UamP*qU_OPrH*L% zM(Sd>a&i{6(H6hKTh7QKKyUT+0%g+eNS-RWJg@EzXNsewCHy1%vCKcCn{YTD)XBZ<>P2d|3yrGV8umLxh*wb;>483sUk)A9c9SB5o zWu@m}tmell;RT-#x@c`Qno;ri0W?V*0ow-v$@WlQ^eLvGN!8@jaGe zcuQ3uyLkB$KQbvLYa=8-Io!2EKe!}LGW`X)o6*96lH?^44O2<)yK~w)BB?|F-R=zz z)NGv|;4LeV%hij)KaBZnFmd^MG#IbKk3Fi4$(A zmgvyWap1NY3h{b?0ULn~?ifzGAEJu+_uo#C z7LDEzpN}n#`%Smy8hmPvy5T^*+zjY*htT~VXTz67J6TyF3S-WZomq z<%NV~82zX3*a1cMmeLLVk`-~G470hi(MTk;+HoBCssXShGlpr{S6?SBPt^kVxq({T z2F+$bdEv(m@U2pWsa^%xo-p+0Kp?QC9od_QXj#eo;%WEhaBeU8mX@|@D`L{-aNcZO z>BuF{fjir~Y)lU7w~IDd6EVKxFa0jLy|-%}os;m;9}2DPmM{1^___`%DY{-M)uvVm zeX8%$-WsK7C?FdtYoZQGlSIQOFA%y~!p!@5-!+4Si!#g`J%I*RzSOCv)e?$UZUD6J zQ{%>o2kuInys+*`p}a+v$t6-wc*hqx;LaivHMuv44O5P6@DLP&ziqV$#Wy(uc7>A_ zo{zuE2P(?u{;PHlH7HBRE!KJ~Y+aD@%vP9mK_v{VO?Zo$HZZ@Bi{z`%czGd(2dfhO z8PfkVHwKxl?l8}29;pUBA`*J{+6{~uU;J{{liP3scui-jJ8ixkGIblW&d6^GI}q3m z!l9zzSiOt(k0#Ubc=ySc+D7t=1gpW&ir!L!eLs}Hw6ymx-a}2LPD!C6$a}pwd7m5H z?zmy1l^W<=foIjPnz`+b?jf0D4u1qE=Vw4=7_u(VV{D0erts+2gSv2ktv?=vwy_a@>VA%1A|esQ$D&=%(Y5bs36NLIOK4`+ z%76ovme?euGTcbEyV94(z#CVHM0QMgS6_#CJC0S$*FAH2&Q6~@g3%$9T0HdAl(WbZ z93)>%Kvb~3ML$!dW3*B#bsON~245fu{^BT3KOs5jtx@r_Gz3P!l-Q{fS)ba8pxo%=$?@xbl4(abiT-hf910RBhPzbs#TcOHogUhVz_{8 zsMsk;i)$uhwW_YW$|LKGXlDs6f>*(2TuYKw&PY(sVtgUc;Ib#bo3T)@rv|W6o1oww ze$v)nG&< zZ!*JTp-1C`h3-S6yYQC@L~E(09KJ8ey67yMwJ1N{FuTuTptFe{XdI!=EA}x&{tKiu zzuUAOITs=)Y7sSQB&l55`=>eKia}?9Yq#9T$*z>gbr2LwJG=N&gP*{}du47k?YxD8( zbyA3}_I4g4IekkQC)nexpGr(PwMDsp;)HA~Kwr7&SfIhrPdF;h(j2~na5+o8Lab?D z>vF`@l zG4=fH9Jk3Cchz*g*JHa8yo`j1>k+sDUImOhAMJ)rkAh&t53%~;0=H6ox)>wBjok^0 zfuyF`aAsEG?LCBc;;Q%c<-y;z*d|CEec7TVIsRqkp6r4)dCLSbYt-zKTgoxOT_2Ge z8^J;2I}&3KGx=$}h zlO<9_5)$|{0E+s3`pSQD96l(7@%g%kg%X?TFP4l#R(DLX$`&8&?KiyPuYp5lnI(|l z+Ej}MEk9M$^%lENDjyN(tXtnS4ovn0QWvZ>UX*YwA|7^u$~#kG}V7?fw_uouqs&S~!cDn`QhLCPRiKm!4SvWcl*Og)ElkC6U0M%ROsZ!ozifqfm9 z8O2QHFdjy6+g+B4f0{Lz#|4RNupK?2YqT|_KBAN*2m4gh@6cuzYF@}+>;~~i@Y52_ z8op&XkW&*J5!p3ktAr8osB)D^XqT4sC>^u}Izub&ZV^lk)Ba6&I8vg&#A>)LK%auu z+ix-PW_*klr6({8HDcdnx*YJbRDZHd zz7*^0goy8l!^umRl}vabX7luiv_Z0wf@2e9C(5r?M1|OIM)59R%zLT{B1Xi2`HAcJ zifU2lF*!@)(`}>3^vdGF-%o2@3sax;X35qgv7#4#+szwIRYPm6i}J-iB`tksGvpe? z;iolz3<~D!m$>SivP;O?pCF4Z$`z1J6lg$`9h5qPnsFhRRs=UAZ)w)qvB$(k@QhlcNOq3Z!)LtZ~yjRra3KY3U=R~aR~b0o-@%1 zu1nOqHjncZGQvbLuhK%O&`A4St9pt+5tEPRtqhAJhyspS7J<@2=12h_Q})j>fS<`1 zRMm~PzerhO#FR?fgfrp|?4B}AuPmkC)CWV2RS?OaZZ;^@jO7e-tizf2F>j8`JY>0b{^W>7wW(Lz+IvEk z0z9kc{c z(ATgFqY<2uozTdat#vUreU;^``RWw?0?XvZ?@z`r`sK{678Zr6PPC~X@kHQIgd5G| z+Su2U83x}d0m-0u%G^hr_&l)flrls8Ie5s-=&s@Ky`dXyyQ+rBG1zw9TFMW1w0Upo zz}G*rmEc}rX}96p^X!G+rHH0)+bpvS~qW;6XeIME27Ojzxs}& zX0kdBG8SI*vR=({hTa;G1n&Cr(?WSv&TYyWTT=t8-pgHwA2QXsH1u;>D<7;J)(=D_ zN3&kkk9!w4jkP_5LHr|CK2iQqr#P`T;41Yd#I&IRE+#{HeCiHl5`DS`qGWlvi1i~h z@LHNTBpBq~8VEBg@jy)qJr4{N>ZpK548p>Fi#LgtunP4)y<29yk>-h&lLVi!%;;crhJOVF>$eM%Q z5@zSZJGT2Khr5p+t(mRYwB2zcaw}t+zQMoaY(4Z#hWyJ1UVF9O4|WTMHjB3)gLb^W z#2nH)%O8m1sFR+qP1p-_Ec2f6CaA%|qM;#*rJ5!Qx$q-9#pAoA5~&zK&YbizSJQX8Jn2OfIus2TDHl^pO+@DG_MVCfNPSiUDd~ zYiefcIKx=nl<@_RL6$%{;O`^ns{@l0nk=ZC9}%d038(t5+!@{9gql3n8;8{aGfX{s zC7xmDpHSFFpoMY+kfKQUxpiDjaby{;EX}i$mIKs1Fv5(x#_A_NOC4x!c>i0hJBPGd zg3SO=e)RO;E8d)?1k+el;MC z93CQDK3EMcLVmOTLJEr*KV-Z+Eo5$^{6$svQcG?Ap;2^Ssx+nqyb)rznij|sWjkGc zO4c{OZkp?Qdug-|N!Vh6I|xeSqO5G9!C)lJ)K9+w=3~T;QGaMteixZUOWiJ=6$g z-upK#ekXeflR!tj%dKv#RLi_+!Vh6!;9fLYLg7d$Nej;~iz4hU88(gHnEtb>e;Q5z z=eF8u4da-Ed*YbZ7`$csf7~z>%f5_5N8yd#NeW9H?adtt+o4y8P_4;3{I&m`AO^PB z4X&$<1%>K2jgNH&LAMcq5SvC?U~NgY7e#o@vPR*)fn&hMvifml_rZQQXN7n(_pRQf zL%$r=kV}CoaA|OFGj=&oez+Kz(Occm6Q$R}9BN>#B{mRF)?LPTkQN3@@V4#5e}d|@ zt?SWWR6eouHn7-oN(Uv~v6{ZvH_$r}m()xi$)Q)b)R8c#;iwkVn#4G$$Dc+yW6DF4qIj|J; zG0|K>HG83QYzCal02Yu(To-re_|yaFSk_BV7c_%{*>f@ zwpOgECc#3wC)D3A z)!9bo=V_{i8WA;9@+EKS{p2(LEVlSttQ`A`$+Hf^+l-%2Yn8vJaj^4`;fo#XD5fD7 zKkte0$@M;BuHEmuo4JN<0x=>!u&oJ$EBCoEKrV^b3oZwn>zjSy;Z=VM*xmDOO41KH z*9z)RP5le3%cgThatxOZeteR_R_;u5raxRO4fib6mv&0I_5fP^ZZp;J7*Npj`&1=F zUv2I?3Lih)B#X~i6Ai~0`Z|1#Y!uC_d5TIh{1TSu`(tUZH_V*uyGWVxCb{O{{@!rm z$%xOC#QILkvgKBJXTh2E4uN{s0*Udj;u<&BQ~&m(FP&fs%F*-Y_2~D(Z%)>REWB1@ zgfc-7H*fcs(V|AHcZkV@h1%`U_b>NLVBe=On(OW~QYJ(NCcaWdHN_A#AFnOHE_~dyt*IHw)>*fwF}A(8Bk(6{@-IeJLkO5dB0zm=R2#!oHPK7 zGCyvxrZIX%h-cg>6WzQk8@zVr?g*?X#p3G)EkRAyV#d5Q3mh<8mGlaR+15PCxA^qL zFDbZTHJFwAJN1{UJzFadKXjOZ3YqkN*mw35?!$b2`@uV{> zf?KgO{*gEAJo54#9sHL?ee_^x(y+*~@hX2l@vl%EL!1RtiLtzmH`jbjWbaQ87K953 zTjHx(^E^C>HqP~DZR=#=eg zxwru#wBM$UsSsM8!FHj`KIfr1M=T1y*P)2r>HqR$p{!}kgs|QtJ$Ig%y}D*0RHe`_ z(g|~s`)5dMHEs-kNx7xtjGOy(`acKK;!oev**YJkV_aUv(|Y`mRy0Xs?TO7tR6+OA z1e8)M4YNy7SXE=Dc5_6N``@1P6UQ%S-Ba@K;whWyhg zee)c6ckW!w2af(D%E39fZk#~Td8JKrGytBf;qWs@gmV&BzdG0jp>>>iAgs3A0M87v z4KXYF@uP|E^kVtQg5Rv#BwShrL8H ze8a=qU9mgn3`uH>=s)+GEF>&nlFb|(&n2ohdF@nOuaG}K?ut=6ETOQPD|%{zmgLUd zSkv0v?RxS-h%f*CL!_{MarPCQB9K{OGrEvS*rygLRv}Ni^B1N`DyzV;9}eoy#cHhH z&o1h~kdmm&J$ft9PqQMK(i=cwd{c!FHo8_be;S%?oepO-o=t98(|ePWVTb zQYHreJ1hLo0yt~qXJKvn#ffg|8kF(;Wh(6#ZjNF{*^<2x#}F;rqBL4(9K53 zP(d13rt;q5^6XkEh|Qu8^UX09m57MJIbNzX-~{`V+@Wtn0&>c5=ng16a0KGm3G>X` zD}NNVcSeSGX6@(#`R8IZ4TkUuI8BhW)L6uRyY%yFsR&otjR`qj6x$`ZKy9J7>K%-f z{&3+~qF=LrlmFEkJUFy8Hq)@nb_KQH;JOoD?WY+LDvPIw_GhWo|2Ly{3xj=rXuK(V z**Z|ApByU+f-KG6w9w|+-1;Q0#LMR2K$&op#thCd zk@619JI4w>(S`swqmWQ|eML`uFjIrem|$yq*BxrUo&<(OOuwd=Hg zLpow4KIvi)+-AnHyQ)&feVRC(cb>!tV$g$hV>9$M2+lzcYKqfgs@1y^rbvvbjmQvTbL1 zmw(@jcF*jzn^7efal(2mzE_*T=48Pic1`>=zRiEN$1|%MAuk^_y*AA12C6f?ky+-c zT{20wS~x~=cFA2cM@x(E>9r{r2=&%E%sFoNkrRzEj};cj1){NhYZUqp@2?P`qe1qt zn}*LAkH*`yft%$4AkZa9*UR|VIF+%bJJCsPVzp6DST7kf@R?mpX10;O3UlTUPV0j~nl z^%ro-d*L>=)*FE#@1On$94Fi1!oVewq+L_LN)$%%$VI)tJkL`_0}apiWtAv*Bg*|AJtGw7<*acG*zD|_HLo*S;ydc1Db2bLvp~W=S}JTE|f~G z`jgKQKd`kEw_V-xU}LoHi3-^#lwl`E-?4YFvx%t8E9TlnW#nSpxQk^`Xrh(NnDZlh zYu$S}3tpPJ_jB}}P41H#+L{~2j*joNZ61Ulssy2!sSfpZ5NEx13b?f)22dGh23MTH zUjf-#G!^*Kk~(}q$9=I@yJ)0N6@$z9=d6hu=??8 zZ@s``Erbu~Ed7<{9PHcgQ}^Dpr8=g=TAs4y;6_Yo_b@Uv$Q=(;b(3M9V;EJ?6JV z>okh>m@*z;hc(n298d@3ivw5M^&zH9f|FD&L^`~TmGVJ(DA%`aNpG4ZDB8;kR;F3O z)TL?EcPYjGC_>_I8SOfpx*MZ_im02N*^=;02%Ueif)w!nl7sn6H zJxSl8KacnGL&Cy?T#+v$1;T5Bv#2fd`#F&Ukl83tonucF)&9?V5`>12n(PB>dj)wqVd_%W;jQZv-D7mEF2n45U)mGO)QO3fXS=ZWe0iumL7iR`;88kl{F{nD1)i?V_ zQyX07&oWw4uX3*^tIE7}RL=e(ty7_$c8)R>_mHdcO6=c}EqjcXa#2I=7qTK|ZGn8I zMgP+;?!#pncVg_iR^=upeZf#qXh|a_{#10{3HrqH*B$4N7~QM9PzwsPHYQk}Z(Y63 zW#hM-u3}KO-!{#WXD#8yy$6d5{y(=?Pg$L-`Oh0uUP_;_ey(wVn>jWXh^gPE;XY7l5~b%J}A z4L+1lCtZZwZmfKgzs|hz>2-feQAym|0&%3}h9&~Hxh~<-?S)mB)+Iu{H40bkX!SM6 z(van!rx}y&zJLK@B~PBY8=9roHIaDl7Hw~9_mM`0?n#J!Vbf;NGa6&n+=fy)ZcPqQ z(fLo*V*Ea(qN|!g>)?n6RHlm1GPz85Upk(|@h&kh)1+mm7iJO!k zZUMtSFPD3@oolN9P_-5O-4XdAwET|Gy2gAxc``5{T-F+Uy$E*P!OEu!{@J?tU5vi7 z?G|!NBjOSKdGXD;(S@cOYJfc`@d>$V9mH2@OhDW@q_LJSw4p|qOc=%kJ?Frkw+Q!8 z`TQ3zD%^%2{<3#GO;fv(b*}+HF}M@oMh?6{f>0OQG>U-4*r3CoG)L%EH_jovUbGx8 z$rhwmn0%guz=4X`cA$>p1BeF}_8c;OZDUNW0PZ!m!_8*O@^y)T>VdS)8MlU;kx6yk zt)Fjw_1_BpT0kJwIbTxN3Hr~{U?;hCv~zXmZCWP`35=HF<(Qe%1UPJsw-?x#Gp^*Sk9T3-x$;GriD;*k0FgTtnUlTa&RLI&)@nfw~Ea?14PfGgRns)(qwF4#c;ck~IP5?^Uk-M)qDm<*_CIHH0^;;t_+muxXRh2x80jzRgsk65LU7c$jN>4S8e z8u(UAi@kwq>5$Za#badM=5(6jodaaCo-CleQ&cf-oUgz(<)fv#0kiGiUe-^JHa6pf zdc@URs#0Ss`Cw(h%}He214FzzeeYS;qZqmSe7k@)!NdlB{fJ7zu}tKR;TN|qq1#7y zx`2$Ru=w6v`~+fVah2$Jq~9y^Dt!Po9yDHcp=VW&42ry)l$|AS?)p70I--LkJVQ|E zWE^oc_kL0N6kk_MOgB+VJ1yw*)sBSae#?R3-u4`{g$a{C7Mx4Br_WjQ^w2ga(rhub z=D-7b@2lZum(va=z3Lmn4cH!?1bx&O&&QF$nvM_X4^5=sk6dWZB?%N|8#Z%ezis>* ze=wss_`hkb41-tTg1%P=EM^a0y?XuITwv}s_0Qjej*Iu8=>=b|hCS!C^A8EW$tzQ} z{E_=3D7p0_t1Py7_oJolhyCRco<38n+i-tp6Se!pb(3?MtC{21O^-M$`AEA>A99a1 z21?NFfo-R~(UR!d8q$$AC7MgB#4qC`0$HyRvCd@)Copm@)LI;}LK`ffYdu1{DBSRl zFzxqbRP(@!R`zFajT4aGE2awH0A9m>02_2!eD_4WiGFP_S&J9DiKSM+SPBLIAJ=BWHJszPpy&^Bl3+Ie8ZBu8P zp%V6%!DzYOxGf;-6Usru5kFP3-9W`Phq4|8HYETGuy2461Okr}lH;T@Nzv9{N(o@3 z1jAdwy}p*bP?tVpV1h(2WwX60_lNPa5b&uCn=D5l=o3&qyz0*nz%E=|Xnuf+bKB60 zWE^P`V=vxr}U9N>DnCdo4sZeW~MDug?pR@rlXM*e|Z^-npRHMrtcEn*Wg`+RnihBz7Huump5+ zMm`0tc!%;sPxu`v8lP2)REo8u=3FF%dothQeJ3YjUhK&!P({R(ze3+0cq7<}UD(z! zoV!A|c#7jJwNk_XZHq2|)iBT`lSNYRw8H$BB7B#9y~oM|zfql5bhP*L8)&U@Gal`N)FQLUKj{gL9P zCNRqbcqSl^xjwF03AeYD>U1>qbVhmDJpg()Q(g6NqdPgJ03;2 zbav`}giK42Ezr8i;OsX`fH(i1>jOzPum#2WbfB9sf*h+=kiq4z;O@}DsdkM< zp?nv?I9l4+{gB4)nPC1km`;uqr?Q|3C1TF0>RgvZ<^BsG_E>)1>2zM8RH6&VI`YM? zICK4o32xf*)r>}64SSWC)*kFz-~xf!nEKz+L&munj~E^2dGvSHhfs5f^M8dTVD(tL zU8Sb2o9K^WA@Y|kH`+}zN{Vp@IsP3F>q_^W* zTP6pvimM_?b-z^kPitn;E;K`Ox5=F;}PHM(<=!&Y2g8R6Enbvd&w1B z&W}0-=j-uf=ZLN3FmM&Htm$YwMb(C9#&BtuG-xP&aT;A9dIDJMcV8u^^!@$Tu^`hc^+P4xFpa9OJIkgoohrDOk`|6Y==1<~>c?96~+JD*Urm zuu*pD(t}PvggYR1xPDsV5_R|A8c|n-A*A2#VU6J|H*N%{INp^Vdz%{Oz~pB?d{pLT zBxQO9Wh7_(*kbF3fgf&qs`nGhRup{r04}tG0WlAuo4)?=!$JXU$rI&Rb5kh-`P~3s zTJf$85ZGSt&XHylyGjLeLrgLAyW<9up=Eb6xAl-c(D*v@yk*-qMY2|2GugaT>z^+I zz4XpO^eO1B0kNg7E>VrC(4`=Zxs>Y z8UXf1`kaeCkl`|QBSBW*i+ZB?T}SWHTi4xfP&=7D45IYz3Ao^JB3r?6JYa|hon2?C za%P4wF2!PNLxRJ$1hGvKN&Jb(9us^sI(Azr*Ed%J}0+m9oD=hZ%`s7aRv? z77VqQ^jtY|S{D926Z07d>!~55g5&J(VitXqFZ*NB)WfmPrV!s0XjP>XUiAJjC3$#0f zr(DGfVk3KYOsC&w>VZ;|wyJol3>JlxkMb9_Uu+Q2>NDqhyP>gCdLR4FN}q)5g$tt8 zHFy5)SBCNw-F8jbTJaG;+@{?f?K2eJCb##&*o_pkW1eL2g#9wicDM_Q7h#!lvU=L& zMgzpIKj7JT&O?BIx+H&*MBPmO_xF8XIL@n0fl}G>h?)GTGgqul5Tq^*M|2Po8@HVEY^lc6$kqGOZsXR6D9^<&Gg8m?>w7i+He}OX;CYZ?4Ldcp` zV_(q>WL(F56v-7XYnIx4l=S^b7Ouke<-T7tr{EKWY{=v4nuLXKVlJ|W{6pb?-fP5U z3n%H7wfsiR6GzsNlK5%2`^3>A>bcR)><4hipty1KF^vm^CI4_o+Fv0Xcn4 zgx|0k+R$AwZaV;)NqYf;8`c{+P}W}%AP1|dKDPRoGVYD4U~Z0?#*fhd$i&8~=Z`Da zL<%>BS`?>K%wu3UJU!012A_3_JhN-*cBI=6=Be>R@Fb~jD7<8#$f=8#r450wE{9&M zI{CP{sU>{_o~*I~zgRT7*leZ1vI}y2f)8>OOA4}fGr%&%jBTiSjag5qo4uHCYuxR} zLO=jC-8DyW*q~MaK^qmZrC|iO&jA()kB~)!C66&o`l`;(lb=N!CU8%9sMwVBe?#y< zG2FmUhP7u2WQ`q)3a)T(Y@n<;Kh5wc@zabx8pxi(t1LdLd4Jlym2%cfxNG6Yni2%G zQMIPUY6%68Rd!dVArGbog#Lt4tbK@Ay`}yNy@(lH8yB2-+d!hsjv}9q26)75J#T6w z$cM97)QL#WDpPA^MvTN)ml_+tdZt1(^i*Rtbxlh%02~@0HpD`J)(OJ3<34MeuA`eC z)$smbYd8tmv-H#8qKenD|G`F_ ztGh+lu6{g_*d9SXXJxi5h0&0oTAhGc8l}IGw)#-D*{?(d?9|dz%_+NIcanC(dvc{R z_da-R({dW0(e4)kq+hWqa)17S@<2D!<)TQ&3fAD|*P+nBo#>(#TK4qVhZB)E2sbtT z&8qz_UBpI}XfTR$APetv|~ zZMWsmH&cG#Q8Hkdc3%+{q0$6Iuq?35ZE!Po{vJoA!bgS-0Yfy*7Jumv9m?ttwbabf zX~1sWKx(qi0oMZJPZ@xzU52*1$^tpUk0C!y~M zN4O$XYN-hPo)5&?*m|_Z(9bBXWNB<@mJWB;y$>XMu#RH(4kkf?UJwPl#RK@;#TFoR>p z$Km7SI=&9xCoEr4{9nPtp;T`1=^t8dyOu9AgP%46a&d%h?!Ps}y0KaI7OQRG(c=aw zuvfSGPpO`Xcz^!nQ#@(3GClE6pKkb>FlIDERx|}|H=ejyR?~nsVZG`g{A5^%JOe^AiOz{ z87Kp4s}p;B&7p?^ z>)tetV*14XXN}fw3cDrCKfPv;tEl=6>$tH{gY98NbCeX=Nrv?B)W;v3l)qBeJYyfJ z{OE-vf8MJfdng*}P^p2qfPt%7&VP71`f#AqhX9Il68bHg=;kiG^}YNo(3Vhdi^wh< z{B3pYv)yA|J8~O#Z08G{=Y#lhW!Y;aJmLF+`fBRdVfFQ!+3-8$DjbFf3kev(`Ztd{ zqL|~X7u1XE4zC11_$q^)?8z(E!I~w(p(V?@VkNM& zp-u5s6XEPCRzF_>;z(P$4&+liAMPH-$Luh7?5&D%C1G~#u{O2YA57y~FCwQimt>7HO?nzKJzA5)XC|wL-aolZqZEsz*m+3WyC*iZ5Y#Z3x6zxitWJ}USNlSiTgf@+}Qs) zYUq!kxJ`6K`9%NA&Ln-mU)Q%*dP(!p0umnaiP9qc0`gO31idLfM5_8L1o>@!TTE~I zit4)2)t;L-XVIxOtXm3Ug1#ueT{ous;`E3A_9e|P?9a6xZ;f~pK{eXfMr7+vo9=&& zFmZppq2o!}{VSw3lsnl*2UJCk;FXX(u`q~hnx-Eo05Sc zKm>a4h5wM``VKlt8(Dj3p*0ru;E-nH?5mqbC31eyL60ha1k6!0<$08i$+^_X+9Y-> z>c!SyA=RSbOn|G49FpMmc8%EEPdizj&G;;q^y4@XFXQk0719$DOhsdPjwkJ1Muz6~}9?`z`xLIcKsbeJJqVzEo!kWx`uIrhxMVgfV0 zd&_UoZ+thLWM*)NyH|^xJqoHpH{!^ASCOT@)BJ&-+NA>NI_Cy#5128 z@2~<}*I~wlQ?1{fx@6ij@m|O`1&tA@srr%8PLf%TI1fx*t8kY~x!}qS?)YAP5ye3KGtkq4zjH%eo3&1zRU98TzQ>s%Ndo1X_fRRHHXzsC zZe{$uIi>*)uug)$I2_ir7c$a7*~AatH#6SZLjg9LE%svmYvXirp4KV#+(Qv+;0u_; zOz5e>sQo^3m5kHzPj8+613uY&Mz--FDGeGEWFa_PeL3jbt%_g&UOe)xlCF^bCvdu< z?)q`F`yFup5n!^*n#A?CGZNy}W8o_A3XHH!r;7qT~g9Nw?qmYCN=ABzRA0wbJX%_#dn9mvhy!> zk3OG&d3qK|h5**-Y%&zz8)al9=xk!Fo}3R|Xyx^?V>^tlk8D~Wo@yD(jL_nl?L@lp zkdG-acfhR|RD-{-jj8v4&_L;ade_rv_MyckuG^1p{cJU1x4Wn<^@gog1%hd}8&ZZ}vgU)ZkUx}Q7lp5iMJs@y`@Ao4 zTczh7)D9bfO2D8dZ<&Q+Ox(|Pz-IlQoXq@J@;vdy+ngD}i@tq#ttC?(Mt`*FIwr4* z1;{Kw;_eI~^SeqXt0NBtCt4p>yQKWy67B|iJDjFs*1yo+JS@S zhh&#`D%*(1TXR1AfZk7Y@Ctc7eF|%p^1jA_?$Ys#L%Tb3=iA~d!KDatF3}liuxPw6 zkevBEAsvis6+uXM; z(gUSiYWI$X8PCd!>DniD*hwshpp)DiV#1BaZurxio$ovQS=P0-gtnUXgqCTkuX$cQ z9H0h9ktZ;wRXsxqe}$wYl*63J+F%yACy2tc>lWOg`cDRkIsb&b$L~p2_=H(8Tg~>L zi@HO`iR}i4SY@{Ye$T6{Vl13g8?VI4(mKT}xeY|?1^E1>7CWPIuN|e@GQ8k>qRqyq zs}_}G#F)c~;nsFLLZjEZLEmZVkZs11+jHwuVfq~Cn^oorXaLHTjs~$pzX<+m5scue z6>W-PdgJaKS+6GHnJiDG)Up-%lNq)npN7AGnsNr`DPcA~jNKL58j4krv2#pkn2x;I zVutDg*-+{B~K=Y;+wj^Byz~=n8Awj^}8OUTAH~ z=zO^>VJ@GR?`VqjRfy95AY7~booJWB=8boyUC}w|ATEvywDgpwWs|m8>{sIZ!$kM8 z)hqBU)lPZw^qviVdy^|=wfkf3P~Nf(Atf(ZxZtuj>1oO-3KMIFoVokJ=(MoVPE?1{ zS+;I__C*x(i|A3AfGE)3)^K(u2z>%xLpMSAeunFc5~`3ooo98}W;L`)T7?d#o;z0E zE5scVD#1sSm1`_&#%Pq*4XBntO!jpru?xY{;@AQUmgev;XCJ(@dxFw!Q6m z|W{aVQ#te9gyG~Ra3qF80A-{yfvn{@-1(CCyf28LSB`dX>rt@q!?{oBX3FX*(h*sv}5> zD+7&>xPrnCN5SS5;sTp)>(mG8g(5RL+a&Sn%<=mUMvSt)fX{zCdYMR`@^lMuKHDFX zW^>eBC0g`6S?&Cb-El-EZ#UlWS(|Ig%Wa;M^Vh)JU;*pH$ML>;y=T0j&dlFN<7tRO zu#*3gd%x3fIbAWk6qEMI@2boDXIFPP0C;yNVsIOJ(s;JOSjYffc8iRH&N8|g(MDQr z&Azo#oOivc+E?eAFTNH#n_;479)2O#Ja+Zh&%;l>_OIxxLH6{(OCUMdAKHDm>-}%V zL#!Kbhj~dS)D4u_*OwvHr75=&eumksy>XhysM6>hA$@Hu9P}C!dL8BH>=TN+7+zo< z9H4fIP#)Z3j3{+k33-xITRbEVxPE&%Lc&ufkm4daD?#rd7pE;fHE1vP4 zvs<=9&QS_#}KjQeL`gcLOSML z6B(#OR;~&s69^-@!!3&3tBcU(ohT>Et1<1K4P<5ZuF=2C#BvFG$6H6EY_&pUfI4$9 zFh=bFNA};QV9W~*)lTU^mgihnf2+%47X~A9-8)2(0f+g5whDFsGjSCKf2OZIn>83_ zOuCqI;pKk-Fez(bO0Q`2L`rV^w#T}I>BeX~;y}hS_PbiJA6NA2&tZeBV$|uukC-O^ z%x1Udx})1CS9Blr8sheHqCHC-er4X0(~Y(Rx3m*EVjpwehzY{=HDIy(^n2KCcRW}M z7vu?-9FM6~A~ws*y~D%3)}NARTZwg62g%bzhksJK@R0kNin>|_FR(k?6w1smuz71r_`om~dwjot*6wELDQ|Bs@C;A)sz;Hti6 zc6owFL6=TGu5A;$IIe-j!!*UxTCJ?+;c#$#;=`_5w8qe9CSEeh@`RpJQ&rW|oP zAxnFt5+vQik?i@%tv1o#8R*H&*U2-E)z&Z@q<~!u;bZlftKH>RpPl>AW%WhEZFTLt zk2u8k^|v}w#2Fpln^}doSdS0rIg6DbgY=HteK%E8p z8UT;ac~vha^{C%6hUR2D78tbdH;?S0i%)9J=60b|&RZr>Zy8woVmw4;wN;|sL6)fv)hnO`M z%rJh@x|6w?;lZB9+j4wS!7$gDsJl8@66O*veYc6z{T)ku9ihWB8sd96>vjmPvju5^ z8YQ(dg;GI|0AB=pj2PWw=Pou=QZ?zt-5Dr(Tf{M{V_F~eCI+38>;J_nc9M;A1?wlD zHjD%ZxIQ25b`KC^B`KOxwf`V~@^93UA8rWxeTq&|AdbIYc}w+n06C6VGMHq~lCDhY z6JA{Gz;~eYSYlNHGo~H!OebsT1=PO*d97*p@IgtGEcn}4tA^14Sz)pf*&m@e1JY2u z7k=3}MEn@f_O}@skJ@sU+8AVt-5I)Xxv3U!vUCl`F>4C{=y+;lpu}0TG|k#G7*wI< zewuNIf{P|vJ|GyxBL0ZjlC-c z@0C>bl}?(xXiAs@mwHQ$OC7_;Q(WMQqLGXZ=J+*rj#6nZT7^QPZ9UD{N?&e?inHK~31vFbiK`Bd$bzb;M z+Z#`FXxxR3HTt8NOL>+P9&3#6#7Ks1g6DB3?-7^t1WKiAeG@qSuTX&k+w21YSW*dJ z1oqaW7D1~`aA7_K3TEg*%94+!@tj4NJLKli79APGZZ1|^9gjZ zGPcvPEOx40c`ezsWUm=mRTVY1vH{#IJJyANALo8Bbq<{@xR@e-9Hu4t^CyeAO6BC* zH)BOcH8*+r4S08gV5Ekkp~XV1KeD;Dv=b0h;Hrt;H;8WU#RRqi>9EN6g0H&76K5R7 zO;7z^elQS&DI+A}H-zcH)@qiq*z+3ut_S_Lr8bJ))N zrZ0g^qL{C8dSN8wS>o$&ZV6B09FIqz{QjKAIE!w7u35xk0C-GQ9>B~9O?E*h>;z1I zB$Ab+h(!5^XVMPG_WuLOA%8Ngeir=9xqAL02`reBv05O_u8W3a4~;PmFYD;kOC=k4 zyTzPQPCvtV9xCCy0FL`#$CD?DnfeJrVZp$~xXcr04kbftSh+onx5=e21<5v1?-^NM z+N_-0Ef1?8v$0)~a0CwG{A-+Tb+lfl5CK!i1}qbrX*1?x^dEU*J9V6X>%A->n+lyk za^ZV*5t4Ab9jzp^XX@D{`D?sYZOF=y_;kb|S~@}@>_QDMb$;luizBbolz@~&*)pEw zcFL%`oF7JPi6S0#66<%kiKv~N>{1%cNqn_V^(~;DJs*m1LZ$>{=^NhGc(JQh_)FSL zlWr?BU0g}S6rev_21&N1!Aq30@mG3Jg~W2ByEf?89Sh9X|BL{ae1uoT5#j3ZC6Z!=O$h1s;Jp6dR$vmrwG@J#AH05{oM&jO}U#u(z2pSp}%PHJn>46Z*zLo2Hu3 zc9Q4a?z0sh^sv6AG2Z9oy(Uv3ma!|6mT1R|VwW(bQwytytpS%$!ugIU#F%AeY%`4Z ztP+CJq6a=#7Qs$oL4@ufj?S2jQtxr!KF@=VOgPT!e5mN5g}T&M-eJCh3ursVNGlA+ zQRBhqBI0TRL4yOllHs4*=fPinl=PfuN`iFq3Sc8B&qrf#+1|^vg9mFC0SI`0B*PP$yCD!vw_z%LY_nO+&XFBHq1{H6AzBONVrA?b1+di$| z)g(96iKgv;Jhmk+!b_a~9CWAgF<|2%Hn(oBRdG%=IYJ#}rlzHUD(R{7%A8T7`tK>ss)xW) zS)DWcD7yj0@H+}}+HQ7eaTDMd#0NA+fkbk}wD=q2XN^W`5hH?wylmhe-tTP8Onn0+ zxkztF;sWi-FpbgmMYwpJg4AyC+`#@G#$mKaJqMYH3|Jw_PEWw}FlkI^A}zTmuL3r# z#)~o?!67#LFFLP;9p{<*ve)O3-RRc}Cw8^|3Wc$V0c z3pnDsz4WT6%m`h(mUO<}1`f@2*;P>{-M zbsVywAR}a#1qwu}0hlA>tZyf@wN8V~tM343GZ{R)9w)tH&Z^^^k-D@Fna|0++V0>g z)aABA0fS4l?EsJAz+U`f{RVvu(xoG#lqpb;?fNSu79oqWv*&pC7V?_sMY=+*+`6{m zvq(`mR;rnfZodZXNMJoF!gb|2%%##hxrxoAF9hn#(7Mb{s0b>9snvx;Xs^fXI%1;P zBr~>n7ehSayFdb@~L8KwY<-$cg>M5=2!L8ZE9KPL(ELy;_tQe zi_W{%;xG0-lV0S;8=v(|=$z9i&d5Vabits<+7wW-9VcYP_w%&Agjf+L@rH``$cSV@ zN@6}8#_s5udzSR|d(9UiwS^G#-IJc&5b-h;iK!abCW|7@Z>P2#p4E~Lv$BqA6VI@! z8{2hbipg3wj`;(RDws@ff+l|G7=2m((rA7xn7pB0R*Nl~)r9lNckb z#`IkJxxwJ6l6EB(p(Dn#MKW5En=C1Z-J4as1O+kgG#dSY!zR7#e%86v!xzjiK<+W6 zkMvwCozy?|`k2TUM!m@HDNliDb?$M2)CvIBHg>C;q;$w%ry5AZ`6nWVR?1P@s$D}-3Ni+srQU(Y#|ezMG@d+MUluCbDIU2iH{Uf9=83anH-kqv5!Vi*?}R<# z1MkxIW(FI1FuphpzL#%sQ=~_n=Dk6&7;lpWCbtoJMPa^sd7d@nATe78cpLzpdUomc zM9o$m`c?0FntQCI;j`(2m9cM%Q}v#UF(V5 zkvbss?mJ_Ge!heFAu%lqdrtgsLR(8Rcm1_q>STu&EmI)c%lCx=v~VJ;HP3n$I$1Jj zTHNf**Nhm-U*p^GJlTHj;(mx=ja4n>XndO*FPf=XxD-;7+NQL;BcE9oc8j{%d?3-1 z&FEYg-PPp<&l)mlvfToCV3)pDAX`Z4EIm&oP|)swg?JYQq%tSEeOrNBA&jfQzF$SF zGaK0wWvN_3D1$}!teuI+fc9`FIv63GbP{6eE2}`$2<;IZ+RHR{H9m&&?66tV(HkQa zw=@mrN#d_qxqn)s!Oc`rYw`zefO#C_4mn*X0Y#7342FqQqf*$dM493 zyL*Aef5H>z(s+h^9mAczfHGL_KSswR`k)HyEmrf1at7-;YSyQCwY{bz>V?rIRu5+; z-aF5up8s$ELY#)?3&a)}%yRUV>!zsDl4Uk5@%$0(YiV3(DVnGy%bZ&T`~cxVh(9fy zG6eGr+X>@p6d@eFxQ?yK1GV-lHJ*M-+cMNbTjD@5lm?wto+&AChW)qXn6%)a4V$ke zj--#R2}E1(AEIsSsQ~3S1<`CL$CgND&k(pd9O^LqSEw3?Tvr0N5n2~(*_NrU54?T! zk%^z*Uan#Go}dIKl^2S3d(l*%S++JcIprdntbgyiIg)9q?q79e`3{*WI3d?vMbikF z*4Y?kEN^%Fx>yk&VKeo{8+Z^5DA=8seF zawM~^jDnKy@ATTVP>~uSv19uX1@_ZRA!u53(L!SRx3O(N&kS<>2Uu4S{g&+lYiGOz z1#W%Ji{qVA55ZC~#oE-q`nHi(f4ps4^r%kVlFg9D7OOHtrvRmK&_K^1qc0qRP^m+f zd6Bhg)U{lAaVM}R2Ds4qyGk=QT-VVbpWZb&W2yN;vI#bxHXuou{;so)b2fiV0K;VM zs6YUa1}Z9RLs5rQu_|j7y>! zPA|ze|0akyRv`2X=xhu9QVajw|9PniH|26Yer+q%`Sz{}Q*6I2<(a)(!i=1;b8`#X zU%wUlkeW|f*Veb!UU9bTM0<`Xj(BXSW|%Mgi@$Zz<>5-U=2j zAK*zlbGe$b1~ODx!mlJhM2gX7rf3e{fi=?(zjodYbPXFcfducab&RaYgh3qy-Hd-g zxhW(W^%jvp##45ETCqNFW(gFo|76U6NQ4)7L+KdTu8G=J%Zqa2NHsJaAN#fpfYXB5 zsDzzfNbLx2W^pH(9TXL&#pZX)=tdbx=iBrAIs^)JQhH{oC1!5*gxK%FMS-3$)uxt~ zCQRtG`GXb=6X0TuYWfi5?kp_qEHjz6#F^H>TYMMB{wwsjm27l=*N77vja*xmfqXT1 zGMf!;m-)#eaI4B`xhK4;%AmO29goIx^YFb~|D!`lIlNTnBaKxTN48$c8 z<(+5N0I+3yI2^<5;D!TfTTQE8u634MxgG&m*9rUF4tIl#)0G+;APTF^*}D8U0PQ^r za4gQYh?MUda0I!andGwQ9AKR|zpA$$8xW`XPTjzCuGPlv-R1!N{_mpW-`#S^8|2Ud zMZ4Njydn4zMGKvtOH-WHW*lq@=78tc6A*Z7NqdyObMq*u73m}sKzI|a&^dFNOlbAI zho@@cvoA1Q*ljo%)8RaoAleGHNVRR`yX+DFgS-y2(#-ain40cB;Oo4699-zqTQ*_B z{qWP(y%dBGiFu!_QdjV2CxPZDO?KB63lC;m_z)}!^s%u+2vU4+NQcjQiJqW2*hbXnV-n3Fc^Bowrwtn zmZqg-A7GU#1^(^}*_P4h-kZT`(>!wJ1_;x|dA>nKrwC-9k{q(HT-T0KRRqZJj-k)2 zP}s^VyB^D6fL|0?QwJs!^quc@7I{mlV|x-`YWJ7JN7-$_(`atb1?EAm()`p~KdKu3 za5C(FBZPy5(J=nwWP(=pq$zjrGWn(wk*Wg09XzC=wOO2SkUd2Vk*ZRc+Ic&!=Ux)K zJ~pTpuD;~A5@HoSP&Ab09=uLsC+WOry#N_rK65^@-KU~zMzquepLL&n9%H3Rle_bK zH5NSjthJ3an0r>2sB62Er@5V}%q#WpAvT7>)|EypH5R3bkLWHgo#DPge)&G5YY)}3 zArETPFR6~^T8`w6Z;2V3xVneY0+M9xVwGx}gf;^3)1}uOt);ThW|(PivhFPZnKaMT z-v7YCc7gmjpqcD=<7nVEx8HBkdrU7i@ssX(abbSFCqlC0 zJme^`sJzM`$gae=7kh)(!FhtmB2hdSt#F*1(w{8?mB3No_Xfmp9oo11l$+=AIe>Qf zCsf9OW`XxJ9yBBbXCPI{a{xbK?6YSa>Wt;@}rX>aXQ?E z7$7Kr_&6!mjAuspOUDa3z)U;-rH^cb$8~hkv-xM!ZeW(iB9|OhVw48+o{lrNC`m># z%n|EU>yeVW6z7LWpUGPszV_^UYR6M!@A<*lAn{n~%rK12k#v>h^Fm+m?%Ehp{3rA} z2rCzP&Ygp7M>VhyuAC;NMQK@HS1Se5S)5*QWSq%B@ZS6LUp!=TBUc~YZ~PUNitVgk z-4^M_K9}<-U`7fcC8+}jezA+nSAJV71V+94CzSCCkWN|J?B%iER4rmo>owrM67cw;8(k+hj5v5T=;HQ|~g)s;LI9u1@Yab%GmWV^W_+YOW9-Tz1HQ z!TCOuu4kacAxrI)1g%p>NnJg|xXTzP2M;ojtuZ###<#TG(F5cUYaoPfDGzH{$2 zw-J?oUi$VEYvmIX;GL~4OWqO5j_l$(Zjhi~@pMy)1q4eon|sX)x&7ZsPR{(>TKlU) zZoj_&fyReXQ_BEn4!USb=%K9pN4oNAJxi8139G*Z`}LU@jYSu*YkY#d1KyhzJ|Db< z)jkf#%J~F(t6*zhS{gZs>|7r*KemfkiET%nN`XGdNxBr>OE-pgrI%tJJYRLfp7o4Y zI%}AC&`FfKK$zzoA{R8xls!UEcqs{QYIW>c~BepViZm4j1^@Kv{$$Cc+|j zbJ%mdUtF`UWEIFgvkDKl%xf~~?s0$LJnvekQ(gkawBm^&QiP->F1)g|TX^o}% z8)E+7yw{uS3BPZi6kil72;6(F@#A~(P{7f0@4{d*?x}nr= z)m61Pr|ct=@N;bQ*Vx+Lr=&c1Fh`>oENP;QZND0Qw~jz4;alf|?H;<$>`|jT4}=~1yh|t0Qi}BU#)PQFNZT27zbVVEY&R#xUL0eV;}Fl7-ozgM z)FzjBH@!z&%y@P}GkvAkI=!1`T;xH*^ycdK(`g`r-)3P;%X9YmI^BiNy)Bud3e%rNK9hOd z+y9}F3sR%msR>5vc$lP*$JwS@_*q^1T`N@TxSZRJlHPe!h5C1yY=G^A!S2|=J-tdh z!`ReXQ=N-8;+u8AbN~O$%Om&5?;g7K{6ZsC;mi{5u;y0IBIjPwG_0Hes%l0F%jBut z0S#ll(NSIX(&KWD$$|cL^@;O*6YP=72K@|lIO@+|?oq+#HW0t^|E`|~7|W&-1qbxWg2W4Gn9EDML893mS$nPiX^{I}_%f z^Pn?7EW^uO0bE2isSjS{t6%*zOolPbMMw5gm{##T{Zh`%s%;x9jALp9W-iQ?MCV=B zwLcP3ldXZrx=S;sZRVjOJ}=FsUl;0RZUdg>U3bplgg2z7nAr2PM<{dk9v3)_O3ZD} z5OdpwvtBPaXsZN_5b>4gL0O4yq8Ov$tq8|;pcs%B)5Yt$%h)`l$@&|amaoX{Wa3To zFI~Dm1Zv#brvF+q6K8A_tXTy5^v~_2Hf`U?lOrw8c^4AaFB2-)*QyYL{zA(VJEfa~ zj%TN$>`BAQF@|W(-R+`h^cyCy)oN>`s>ohf?OtP1dyW!wCtN&b2S7<*w4GWFo1Px) zDN=h;IHD5El{EkMBXrZo=L|XPYqLH%cvY!pVqYT~@ge5C816e*aL|R0Oz@CnZV`Zh zjk%0~aIcb;Hc8fYC;#CBSFPI-b!dP3>UBy(C^7s~uB;cy_S#Z!;9{qKnx?tNwiCOY z-y)C_bYkmfa#`F3Y}_V7$!t`o-v)fx$2A47kb(z_mLv8){)kPy^pqJYBI1c>S4NJ4 z2?Nh~z3B7lp!{EDplJve2YgnK`vHZiXs2$;i=Yiz8cZ(YRRa)b%heO@zn(VH74l8W zGOa-S*im!Cc(u+;HDIC=(Z?8{V4X&?CD91b* z0|b?JML3rG_2;r)Y^KIw#dU0f*VUnQw8{v~UTn<-$b+55(h(0| z*VkiPW1|g1Dn~@)-_FOM?t)DLpL02AK;MNQhehHJU0h4;jNHYMVL9$d`}}CShz_UA zB;Yq(em>cAEoeH7t$tkPQSnd1kj(euz?MD>DcWj#Cij#aG_H@Te5zsCuM$@SK%y6G zEDnX=GHOtZ*$Ysuz^pK`VeOCFbE5dfNLQ9)DiX!oLr1=7(}L??bk%KH^=msj!oa9M zcye6*aNh~1a|a9qUdZn!EcDOYM|p96vNk&MgiQji7ybz;Ez#~KKS~%eD{=G4>0d2; zY<=>GD~uJI9PK5)tvxjjT^8dW;@H?TP4b!7`t6C*A&Co=SUJUv-Bn|W*M@)?9T+)` z@a5_sS;fZ(-+TS(IUuC;bvNefM5!uJPz9d_THlJaF$~a^&lxy(c43r()L!%V(@6|T zkl#;CzG;=t3897GLX;JHoW`x$msk$_%;+_?5Zh8+>S~#vVM}8Cfq|^(GZD8C#8gIe z#hF^}iF&!@7yosg;`#x?77NJacrm=$h&Y+q7CNRot>=F3H`_+zdDabooxV*``HIAx zjkQ(CBo_|_4z%wVXJ%GznWQPjZuk|Acuvq|NutUyAXVm)v&$weB)4dyKeTvRvZ~07 zU!m|L+nn+S_XrM;&^lXKTmQ7q6{2GEU$xuRq^U|+V|`7-xDFS7b|GocQWNHuiex(y zrIlJZVfqU{rBCxndO4Zsehz}SMY*T0X*m58GK;c0j}CJ;B_M;{?PwjV_6E9}b?j`% zrV^Kl-r`B$oiLbVBu2*3QCd2m_-T`)YuIlnoO-wIj`xGoNf$S)cdou&!q39$7@E5o z_Ipw6FP+hKEx@|*B&oSArf0*tjV!HSQ;4d^d5)-HMZbTaLO0mv4KFUXgOuUU{s4KJ z(RbSSKmcMNe*Qbx{nk1O9xgpp_1gApbh(Xm)z0IJ2)N|b@}0@0uvcLr@xtffdT=-@ z0vS>G_Hbz?Tgy-S^A>%K_+GqJhY{EwIH*`uHt-swr-UI6oWmqOr;L&e!S& z;@d3+6RtyJ<6Z8V+!A2UNJl19@58LML2znZPl2DZfEv|Y$1*7h(9E)_m|1C;(0DZ@ zZL^>DwL`KJI<-QJAPq$4HKEX2h1JC0r}fxnrOu{B*U1X~Q*IMN0sr5E_H!D5LE)zq zAMFxssX%gWRJAFh7k~|gZYMGoKMIz`4H%R0S5 zfTL-PV~PEcpGrI)88YRzRILNxX7w!SfNaBPJ};MFOPR}<-38nouu8L-n@ni@I@lYC z^g9hqG`6YqdBOA186ORPgm0+>l%g#7`Ihz(G~`YlE9Skn4#TOt4LI3g(rFkS$n*{D ze&0MTmIt2jQv8Prg$U#vct`P&ObA@N1KdKQrvVb0BGYoA7XY&M{<; z&=qqQW)o(QNiKZN0Sew@FuCNcs9y(vaua6ASYx&X9?5j^lk zjvb(zXB&68xIxe{BfVjYHQ?xVYy{5+Z(Hg%Yf^VmR^B>dx8bV7<3-tSc8#R(Z&%*~ zoxl99w=^q(jbseSy)>>Tq*F+qFa+LJqSewi?tSJ+vSJy z^LmuJshX~~Aa8UbO9aFbaL0ZZhuwyc6seb>8=f&Ins z?C#p-FpZV?kZ%*qtR(~U7c$bz2)zRRcr6{kc|vES3Xp@LGSjIv8|#{y#-VvV>|QLa zqMxBPMC{J~dKv45O&SNrG~ZMYGk&JbxZCb6w+#-|O@a&8yZT~3Ox5wkLg=)fVUV8G z$cJH&|9nb#pLXx}JeqvC3+N$5&M~#T;=3=KO1r@+a7blb=tYEWcN3~KyBoe!m^VR+ zqJ&AckJ*qZpSo!@t{pfnNk2y>jdRD=@!o;lb3Ev`Y|}8ZUM6iQ;WJpQ^GuG;cHJ_y zdP0Z%`e)Sgz;TgFz@-Vr6ePo01hzH{#9s-0!n;;YY>q-^1uxdRwpNt4ybO)8dDieq zTLmwa?RcHGGFyNp_7LWfhcLp-8B%EJu{r>tewtt5c~v--a4<-Fk^%fn^OaXr_xGpo|prp#K3 z&*#6=h4WC%yaKMTPoyj#7YP(%%wHYiaUMF%?bwBHZJHSKZ~i=U=7uQ*DuWIeNF;+H z!$gq0Q)9-xv6lTaDzP1|e$|r`zIo@}Ma^4Ab#lTN$JRcAFH^kTHQ1H>Y9d$`Jw8W? zdH)UU1>MJU9g38|Sa7xot2>Y17Qj4CjRk`YL^s6I8lB@#Y+6Yd9(yy^wVFJpvms__ zoS7^(okrEt`M!}ax?RE;13YW>*G%b3-(~2FX(FROg6?JBhV9s-%0cpBe2S5vfCSav` zo1*dtJnG^;@*vaD{N0}wMAl?;mX0R(y zi=#h>+k^G<3CSMps=yv~bK_lzu&@<{43T>4T4LMtj>onybZ5R-vcn)pB25IdI7tvs z?4Qv7ws@xUypiB^Gsm5)AsA3pZm!4Ck)8b;vgl(R!!F=B=>hK{)^c)gG;-wgp>XT> z9Lw$yn%-|FF>?q(HV28jMrQpY=?UxMuc;Zdqwa`N$48{ z4ewMu#X^0onUAn>;p~Z6FvRaT#SuZ4ZlgR|Cc&1I>PuR2y@SSyY(FfC&x_AeD|4^h&<&h&}9z0 zVvK5^Z+DrT<3JbGGXa*W7k4-ad#oS$wKyE})k>tg;Pd%Pa%_(h^CllWKe(n&*Pb+- zt2Ah37ct8RJ;nuMX8K*W1~$fby7Nvj)sl*uhFWYZ_~}-g;kLfN?lHUQqp%pR-7rGC zxVDa}dq9$VQz$|e2#fyq|F7=()g{WY77(kT-9wkt9KkA|?I9D^`T&z+0BfZnSbv}_ zz)j~gpj8_T#=CepYcaMGH{u#RG@22ju4CFQpej&7eIh+vqgC(cl?{%{EbnMTcb*|9 zXN~}*1y_ts-sWtvtUNGQRE$QY&k=W+K$bMrqVF}I3fv_gt-$Db$1A*}%qt5S9neFY zr{G#?bZlItR4r?oF;g**iv}O?-@?U!9}s}))^04*#h!C^y@X}iLz_+7L{3=FYj^#h zJ)j-Vs#=wEXszSu36w@!0$EtAQNL`682C>0s6X%09C*Jk?Z4;r)gHkGTjVuLOYFQ% zDzQKH8ArGRnT!x-r(N4xO9dR7B zL7oJPZpqN*o_DsDVaRgLFpoL;biO^{uZ&U1M`P*G_QCJ`{E^vCvUo?UEXLF~8`YDV zq$oCG)5j9GAZ6GCi5m;tTxdt$dDbzx*%v@-|KI>i=>;%zl%J=|&(@iJI!lZNtiR0I z?^w`iuy1PIKcS;{(Okj734nGC;R5NQC3KKR&rW9Id%&@RUm7X1G9!rft43Y(uIi2aYWsa30P*@G!5P+ zMy1sJ8Wo&|1z01CU~;dJp@ndR@mQT)!|Ou?P!PjtPX!ChuwF>zoQ>^HeE~j1!cELt z^7GmZeLc=La>7~4?Ron-QOE_{z8Rwa6U%*+{1p}l(lL%G@vU*~4mK_tdzROi3sPLY zb41}bb~k5h!G6M1u>Ta-0)2S=7Dv56FpaC<=psnK$R$s~-r7REt1Zl5@fbB2J>oV9 z+)=lhu{0|tP@f^h8JU%PxT-xfQc#QiG1lU4vRrvG`?FP&02mPy_GXP@MZj|erMlD( zLY$+dJFuj>nW)5~Zy9csoI5yk);(a)E@~P^%(sp{%BHV65Ml3g*Ol~b!EfBUPh(mK zyOpA6#jua*K>5429ZTYO@+SKlj|-}MBu zceq97a7IOYV7M@?e9)w!m_t@b9@7j`Ptw0*qdobM zN$CLj(ncrybZS~hCyzAu3_T5o);Dyyo6#HEZACujiNm|c+qB^x!oa0N!DZQTpv>Ee z6o#o)3))hy&!^!FNP~gaN?R_Y9nQ@>GOw`&0 zS4J>OsRMV0BkRmzMJritd-?HgR{%WFGVvkcMA+0|J_Z=8>E_bEWJ{UEVH0(?0Ha+$mw1xufLf{fM|M^#WHs{z51xMWVb%IAIOt%%hC3KOK9NalY z{$F$Eo40cu@_L=>#~2`nW^zwz$;$Y}rR=WAXJA_K`1@QHV65r!w_*x8$9`;`*Qpd2 zR51`T1%a{5id$UUavpfA<~8}P2PLj9Y3a(x5u4%Dh7h40Zwso?iQkj6z9?xNP5U;sSWKPc;A-SUXor6G(l{W@uwSm*5p zLja{`OEWk^?Pk4|R>}w6oHvn(p%PL} zih}~~b9!QiC|8{gGIc@tb#HDvj>}Z9kaJ_I>Zxx>5c%nd+p8U{-v4xu7Wx{m z=g<{yz3b;w%AmDh;&lgzv=do*(kV6(%621FL0m|peLXN5GKeir%=NWqoFO)~P{MNt z^oD|3ukT@Z3Jw&oqS|*vdbtYw_&~7s1iGP}*6?ie^iE_{Aw&%8(#*srV6J$>%RoJ8 zaOs>-nWH_8>{YAorv?@fu0p^ zJy}@+ZhsBb-*pDS^JiTxrM_4x=k@g4CxqiKJU-gP>PLp|OaPu?2l)%YhKjXkV!Kjz z`v9VSPkhNT*_M*1)poKZx&IM-sweasivSt-C0)j-4%eiHNV!-i8#u^9Or{gy9x>wYeN3cAb=}pv zL<1`)XE<;OuaM))UywsWz5na&U7+cz+z8n23~#$mnzp$(`4hcAc7ptkg>jWZc7se_4_1D5G0S zhvBY0xv;+r^oV-|i^iP<9hvQ6$RuQTX=jRhYpNmoaXd-=pHN7eDySR87O5~w zbxK4QE$`&cSCd9J`9^z^S>AD4!D8xPvjTPW$FrkcT>DUk2XvZ#RvSduEpLyLtx{lK z#8K++*}JxO>m3^waxt~Y*KbuvivQSf2}cY!&gd52V^9Yr7exM=of+6kSgvZ&tAp5}v-CUY z3pUP(^{NfLu9%|6b-i1?y8KEI9EoO2PIIavltjDrX**^0QC)IRCi{e)v>$+yXhq8- zYgOxKnp=lA2hPpG>so1@PlE9v74oQV|JH~fQSaNs=c~@I_PkTpnQnc~vv?ZE^-k3Q67?;C|APbZ zh7$T;t+lLyR`Rf$wazER#+FQVjk|6^mNgw@ip>20fp90eHO13?WfLtj(wXC^owF?? zzka)4v(@<2Hl(=*@+A43%G~G(F?;~wLXXe5Sr@{$FDy%^p`)zjNS~+7VE^Tv$lS1^ zo@{IMSyX3d3N7I=UX}KbX-KwH%qDlWB7DG(b0(*tugLls%5ffFg?Kyy{Qul(-_PL# zS62#bz6Ie$8AkwKPo{yrXzaO!n~>?=Kyf_=q#7 zXO7jA8f~+^awd-xC$xgK>#e-0KS*b`R1)o8--Ifx>K85OXQGZc(;`xipB~+?Sw;rJ z15g@}n{^Y;%hj+8Gv94X-3Jr~8e?6|y#1a=j}|s-&p6>NESVy1N4cp8-`W-&RQA_3cHKOTvOc-B z(yNr}-?Bv*1V=0GOE^ECZgD;dlJ@S2eThG1;i{4cPk|EvFKbs|tY}@U@cKB%w7fMA zz&Lpt+6%EMl>$XzHV^h++G`63si(^?FDn>C27kqN%qO;|87Ey7_PW+6lJ}-piI4nB zF$VkK5>SFOqt-CDp5G^aqx*Pm1v>#(WiZ|g#%4YAxx(}tg>6=F6I#pRAqsQVXsjRa~6V8zzebxPEZR7%nOa@FG?m{F$xe|Fb?wH*$Rfd5HlzZ(gL z6OR8A@>-5pw`}_TG3ml_&8y43e|vRynaz7gk-+hJmul*&6RbaEuIv7ldUKzgGNE8B z&PiQc5FGf)RbCS8WBTLhh{z5VAH>6VZx@7^&&buQX;g|NW$rDd&h3JNc30j>lEabC zE1?;Uoll%Ywl=p3%gSz?ao~cvpQ9}}ijZF1zbK9K7L_c$a+I+a_UpvMr$+tkVt#87 zE(!*YH8NBQu-;8PSS=+i^;bmn>k}{`5`2;PPiQi=M{s%kHr*z%4yiK7Up~kHt=O3~ z4&qUfC;6E?Q?4m`jCA1oABdaV-*@X?w1K?VD_;!~b_}>|p^-LOj)&o2#?YA*sh4$U z#a=cyCvEYuSH-_6eeY#Q-GO@tN1TPFd3rWAi3t74SUGF}Snlkov73MmIkDs!Z%^qm zYkKXL_qqGl&$|2!k4`cH7HSXM?^|?z9X}1Bx4ZA>UdM%~B=O(sl!k^iK7OTbFQx6p zmwS~zFv(LC&?$Y1$v~|X&v}$5&r)g+ zr#2h~48=GRXAOl;65@$5BJY75rz9uV23MMQKBEgOg{UfO(8AI7PpGFwoi)P!8AL)i zY}isvJ|!l2BTBkVu4e!c9b@}aV%556*92rbmpFfZ;j|F<5Nl)5O-cB_XT=A8$miTEPU90v{Is5y;Q<&Wspq3CFm zg5JpO^py^k=FHEZ<15xHeu)ss7CnB%UpGA{Yo zU|^t(1YI+}VpQx^&*z;V)O~5Cv(DKiVW z+Hi%JOq6R`c}ai*`E1&W~%Y(;4rF&5N0tQssnWKM(-Eiv=xRjFc5K9fcLU7##KJu)0}tZeDNWY4%w~^ylD}cR zN;tbWI$wS-)F)gV*xMUTf=ut$mDKpcmJYl=sN(x{4^xSa9E&^&6X>!`GMKZ_(H2$J z`Y3EjEzM&Jk{d!lJ_=(md+*O+SUNO<9zVM-5;0P#@5s=yUrJ>t%n$D2*t3r_fzZ_? zti*kmI719ofia~L>xVY9FitEbbI#lM?lb@wc){7Bt}^spzve6qD0jVtQRVorr)IzJ ztjA^MSDnfWN4NP=__;>5a|-bJ-WR7vFp$#7Z+hgOW(3Qr-9+}Hm>MuQCgT6IF3POA zIep^cXpjhNUzv1`bU~@_?_Mvh)KbXo$uvx<8o!O%%aep&GGU!Od;zod;NhyJ;0OrI8=H$;J7)JWXo(%8_sFK7ulq%Wu zyht;){mka<^I7H%@$Zs!ZQe0?cEO0>I?r@I_hOH|Fh_2OI-?}XGvnvCz>00uqKu9f zoBvl9B>s$NjPin&d4x)s&NmsE(+4c>U395YdWEekVJNV$>?(din~DJF?hvJ$Lg|!v zCAkbcR-mu`w!UQh`=vW>vsD@MfSwSjf}4mu&SVs-7cG&!8dK!MosVe!b#;*kwv#ym zlCrmNIE@n&E?uFku2Un_WTi)bOtDN?qYrP}lMXbRBmm`w~iaPoB_OC(8i zm>w<)Nz6A%%}+LH_kuaHZBp&{i9r#k!3Qvk)JiS7gs+ll<(dalmp7rUgFT9VP7_Su zd!_I)9{);jpJ)YER&y8sil7fG@rb!p2PH+PjH)2SI(ju9i=4CaJCyuX`u5)?XI@t| zUyqg51JdGlT$+U~%IgchG+Mizw0yYYYWD7g#21J1I?bpDZYgjkYL4BwV@#o`99$NC zcfYUIrMjx?NO^lwlt}rHBbF-s5#XFTzT`R>`>3{g?BqWo8)A^xOnXDE$-M@~!H-HW zk3^jdc=tmnGLmtRpGgb(H|wuEjM7T?vugKw8C9HgynF)i z-URykJl8xmemnpnR<;E8hR=Tw5`EKuu`(-Q`G7>v%mYQ3dhsQRdr=qu+`4*PDK-*l zXg)`A2<${oR4GgmAa<)bHrI(mJa_v3?%d6K+P%>_3i}nC7V}y@$M!KDHiTHq+qrU6 z%uvWm({kyb5F>P69s5{&qH}KWBP2CWj*=9_-*a^oV-GKf(?5Ux>F?cedQlyO$A)BP z`vuw}{nBMs8xkzFIzs{Q65xv4-HkjSeBfN{ZD&7Gaq`e+C7d&Ba*0mKA=5)XDL=88 zvA%NfDMO6-Qrr+Y58@h9yG+3RYJo7+mYvFR!PeY%u{mb@Q#3JAN#gUkfAyND(YHz8 zht5vIm5H)nFT(%KvKu&+IARtNXTu}}Hefm)5Wo`5*hP+vbxTvaz3xY+45>c44toc83OB-o2yj+e3RV^B0`&Bq-siFj`#Y>-@84;U(VatChz9<+nV2HRNOSC`5w z^^&yl4hWQq{SgCGuG5!c|8aBfd!Wz$Po%(yeTLdCH*4EC%y64ADL7O2!zfF<)E;nb zmQS46(0u0xcYU!f15&3linHld{rEXJSlr28tV>#AvI=+RS*Zzy7%Ctzu_KL;W@?I}Bo~Ri`7feE97L^y7`Mn`I``MMlxZV|#PVj@7;S zIj*zSQY)uo@SfB-nVW$RwieU9}K9Rb<7L)DNSh zbt5!;cYo2;0SqqkRwf+PGY^~|8xDL7+^(bSQ}(Bi-)yXRUQ>R)VR>{VI}$ggS5kV@ z?@+9LW5Va5+deAJ_#nnLx(0U_yv#DN%vI&3`&Iv+fVlMMh)wv|7(QQ)60>gPLhqC0IuKEibZA35|O5H!l{R#~p-H6|5NbCCgo<>vf+#k}@hoy`{JVoe|-QU{KZ z`&IreI1~iZDonjaq3$Jusx%#Gb|uAdX$u?p72>4TWo;f0Y zHzK_7J8ddnQww7l@~1bHE)M~PtsqSH2Wt=xL%x1gTo#WbFi4kk%xkh2fA81%>rawj z9oKtujjexl(#)jvgELYhz!MQ6oevKv(F}Q+5#U{`AY;}t@|N)^k4dzcm%*t2RY6iq!xyHm74I=Q9Bu3gWNu&P< zc|?s$$RaZZ6{<}MUl-}3msGGw^02Da3HyEd?40Il-^HUkSr=V>Z=2)$0s~P#}a8ylu3#?|SJQSoEUd{8DxmF0=%kDW@{J1#uZQE&Kndulo{8 zPL+3jcnB2z^BX*9#EaaPzO*Nfr8$19n+}bK=O=w-K%~hw7s9{ND{ z$HkS4OXJ%zDE$p#hkmX62d9Q5Ay{#D_dbMpUUo+O0=lT>zPKX1Z*%@MG}#qK{HxP? z%`|{5ig?0ap^3f$>Ea`P#q?jV5qt}W#H%zKJbtTM#QQzr@qJ>M3N&*gp92T3b}R7tofC90g8E)i^>Iuf^+U#pVY0P2=FiX-vo)c9A-`~-tz2-m2`qf0 z#Q@rrAnfH9OD~ghYVG$4XLw#@{)1yX;gx9-^>j#1bhh!e8Cflit|t<|wC!=Gb~Jc@ zP(_pE-6;E@rfmA4)m`qS;6Q#Uj0)r~bk6U~qxqLb%nj1~Y@}wg!Xrw1&qQ-od5DSp>jsD z#WNnR5E~QID@u+}?A zIKngFm-D=}a4cmY-q4;I)!ufPpUbmHFbz80M@&xk;a$---ISw|U|)eE%XaoD(Pa}4 zwqc>VjP*N}#CV<#a1FspVHq-&{LXi$iJh&Rb-t-CX97vZmPSQw% zZK6^aF9R7Fy7!CHIJ6~QmmYC(t|vgc=;3`zg6ck5egBu-L53GonI4-gkOICa%`JIt z@m`h;eYTrIo7U}z#(G>HRf!0@6U?NqcIPQ_o)M;2(p{&Q2Q~Zi-8`JpRX|&GQ(UT- zE?V{=em?Jr_;V8@-hm+=h?Ug^Ec}iQ8D~JG{=W>(vFyr%l42S9bfn};p6oYWnBY9; zE)5O-m?z3|irbo%TNYg6B+``>T$f-A8xqiDh}|%frwV_+j9&mBAE4v)*MR(9XwT|2 zlKq087AZc3@dTXA05c-OJU+E2H3lk*IaM&doG0~q*@O{v|oN@b+(JC-up|f7(Wc;T zwzd(cw7W$XP&mc-(Um&j3CS*j5};+{xbm1&{Fg?zn01|&h%U-8iZ|{Mx}w_^7ll#9 z7TZgW$KQ|boDt(ZV-0?k&`MmKSdnSiIhMtnuDsj%vG;2sJ^(lL5Z)1f zGI(x!HXd4!?HN4A3nKB0Fs97@Im;Medc&Qyo;)8{>H3G=#KOjVvXC#Zb2x16jx#X6 zG2W$=n0Ezw7-pi)Asx#ODMB#0mcZzBlqnx-+cVc40*?PmwfYWcePw-4%w5 z$5>aOuddKCV*~XWV3reoyGu#j*I)`P*5Wi@W&{=-05o6WO=c?aw7L7yS#-7TRI%^N zSfW&H3eU&rPPuF*!VNA5jUy0OH7pv*7Ej1tRXK;eznrGIP_6wz>A>hX_+rZ?X79o{ zV~lIgfd~$Eaw9m4E8MOFfM1a^v4u%I3fKE5bXaf*1G1M}mizV0L=I#0jSw`Ur6VT# zkxAoZiav68asi0yylpp@!eZ+}E2`V%63|qRqyXSvqQ2rjmOkSsGp8dgx#&#;XPhPP zFt#aXu;Tp}V@p^uQLbxhw4%TMkQt#2se9LTV6XqlSL#3C?Ey?>z{J=tgbmlsvY91D z#m^ybgfiBH`WSmhzcnLj9ud>Uwe-a$TShEJc5LEGPTHK^uN%1wtz^dRNu>`;tz_qA z6~YW+3bQwN5S$qK(=)ffn2!t|7-+2w4Gh=utM2`|FJk3xz|8Sj8^uGzi!4ylg0lOWe0gT3{%MvY#-uyP+@g&N3et5U$tDeSI5I z5=!%xRlH2nu;~wnJ&?JGXiWA>*Ly8nCHW=3g%hWGY)@CD3E-hiISjNl3Mkc<4ABQg zFxqv;wkw>73K*@3R9RmS65Gto3`@#Ge6Upg*~%}y5ceR>13i59;UgEDae&loccXZV zA2$QGhuWqghV;T+V7(=^Xfqe+7O4^*L}an+jO#Qy?8=K+a@VHaY)RaAK z$w%%~BRTqm?9Y6%Q6Ml@PdSQ3f|C^wa-8M|Q}g78)>_VzQ{#*rjTG{i^5kn{274$6 zc+vvpVIytek2us)60eR7`pe4Og6) zF5MFcypA;-hy&*jYny$a|2AmAMHsuwM403Ms|#t6_^N)4=dQ*c;}~?1N&AE%5RIK|Q{giGvO9aBTG~@0eP*T#Jky*3 zST^sjq{@Zh=Ag7oU12Ts@9pe+O2~Ap{7XmK8$3zuyj=zNDufrn|InttlBJhtz-lc? zY6)F1wm4guy)x-KPE@$>6Bd$*{Ez^*RkpH87QI?cjG32{KMumAfg_gBO}+W}Q!U{1qPs+mlxC*J z7K|=h1lJ|fy?QG9Bnl?WCkI3=79;xqZZL;%04L{wQ$!Ejyn;7H0+?O7ueN8sz{=OAX}pxYW} zw7tLyx`kQ26v--vmdF0$TrHzux?%H^uW1^ZH&om@e`i!js(pFgQ(dGOz0 zk%p+;DwUcfH#b4YEHTkue!3%F{`!+7LRh(nxT?bG$3hMw9qH%{Yo;I{RlK@m(1%nk z)P?Ex5z^^!jfw$Ri0Bm%g`_Lm6f~);NBj9jFWa%g^Gq~yAFjlQW<-vuL6+FlbjGoM zPX~%u*!m;g=yV8R_!|J2Y83)Jbv;#Tk*f2nE++i@$-5(-wVA&~;~5OsuN^bgja!Lv z!<$PRx_^-yL12SF)(8vQ4)>l{+PLkQU#68m$cdqWs<1aQ@Q*JUEVn1?6XPoxx*C!q zaXN7%8GHEZr0tDK?zOM$P_g4xc4c&1Gg~0~oi=#)?QO$mjl{*eRtcK3P1yM45^oVEjLA_at$VNPNE+P8N z>?bZ}t}JUa0GmLQ-}{3mHm|*0DJ@<%b_X*Y$K~`&D$$7^nQYnh%ILD7#@&;*Thka1 z+uV&$)=a=buSrqW-!fO`E01auHiF^^TN*FL;Y@~r@Sm0<-TjUo&^<6UE^1+vEf0Me z>X&Vy25Ro*eQ%Mw(*w~m4_&3t>{(33_-ePM-1KR2|!_u8aw;B;Zfu3( zWjDJ=iG}2v7MoVP6*_g%QUCXfgloH03$>!$r~`idM-p_hHOD7Aqifg7&W+*{B-^>F zs5Jo+VO=yD{A6Th2lLxz{`)#8rnZUa7VizO!)km?4iQ38R8H^8p&Z{Or{s`woI@ptS+nFc4J(D5L#D|295aU< zG(*lwh|QLbITbmKP0X^{=XZU7|GBx@&277`*RI#|@p?S&j{-#GNASmRQH3;pt%$kV z|4d&IXgGfg?hy9sdRT@&XvK&Xab14WV`>Uro`DE})m*Q2X9ne6?P$q5_bK0%9Bwp$ zF3rD92_RDIu?&K1o>vj33jr2}2mA1nzH1A6;~MJkv@PVc~3`rRlamHh*u4i~rRq_Vm%p@-p|U zSt8`(U4inkr%gP7A&96`DT7|W>E5+qnBFTqE!4X-2aCfu zG_^E-itAcHxR=I1d;_|QPheU*1y+r%vowRhWoo1Ra~(?}G0Q0-r(``8sWqoO3W)vs zXVT7kohJxXwJrx;3F6kh*&p|VS81a=TIbaTyQv@X^jt+B^aFRj7LwOBNgv@BT)7?x zsRf%g2d@T<`L=hY09W{MKuc!(D74PK^Hj!D6nKAX61R_xhH64T8*ldzE4r#X@&-sZ zsQO77ON~F6UJAH2%9-QK&tl#*($cE`5`v5Ptg88GUt_OatuFpcFT;?6x#_Lqa(5yu~@o82}9(`xp33ki|=~p|+sOU?e zI+Bn9Eg%DtKW6xc&dCeR`s_&@O%~(NDxo?)3J^L55)AxdCP_w@0W7fB2>WY<=?Da%`30(hxkCB{p+G zU{sC_eD$~#Hhj$ffWz$HkZj-pL`(oV!?}SiZ}>wjM5a^`ls?T<+xq9dNU>Mh8$UpO zN;VHfpI^4|>wjFh+q&@F%(Pup@Q@S-*0mdn>(hL+dx`Z|yt^XCbru=-FOcXyQBQ~o zQaoCtA$vRZk<^EbKh#>mk=+zrE6Woi=6C-2;6ir8yqp+KvEw4YsAsSu`sDWs?W2w( z`wp(3aM|x#WWF@bU_LjxV5%C3{6`>(mytAwg`8-x_~C3n^E~1-8<~sO=xkRKL~suF zn8~T}-xL^IM66t>;g^g`K~Td`pM+|J)0{uf<5FDhn5T= zDVC5AS_%{k^pCqqed)KqvbFTr|G9BK*PWMHqx|~!Irn|gXP51tT+iP4KMu??f#o%C zauN2DVE0r-66tRm3H@Sfqv&AvGt}oq?6BD(-FJEq&?3ou|F$+|(U(_uc?*uz|Qs{}VCEc+hX( z)}FIkE7qS9eF+9udNeJmEuuC4U%xqxLR(x>%|G?>%s=ByE!|IhzprMOlccigr9m>uAXBlo<=>d)-2Kwd7qhkp9e{*krBb39CE& z^WWlm@7XS2uRI61?ALDW+2Z)8x3wZ%f0=a_C32b4qiBrNl&`7BRcH&V@T$`E9Ap#1 z^`MGv_pQIo3^)^eQDAg(2i{?&>^-qrEWUgz`FyeX!XKfii>~u-QM(!vHWj45^UpH# zT#=jb3n6*&ZdPwNs4Pb8_4~OvjjC5)Q5&9Dlg^#_$ts5N{pok)+eOiL(x;2w<=*_c}1q z#j~eh$DWN=|4sH<>mWuWRo}Z&nqp^ySm01_ekk6=B||WM{)huqhVWO;U9zO zZ`ok3Or}FfxNeB*C82`w$^e9&+k@P1dUB~lf@H)o?C#z%NZ$mdOp7KV8*l}_-OnuA zrpEhUZK3aE**SD8GYod{FUwc(T$v?+z2)P5{fGtP(CfYZribA^j=WaR;tjSnN3p%8 znwL0_Ph{72dVFzd0NflNN7-QgCNE6|78?7LTsfv$?4RY2>T(|(((3Uc?RxWy*!D)s zujTt1%XmuDcB6WLOP~6Jz2Nexz3F>B3+rVWNoMqQ4OmPkXxHh99>9O1&IR_puXgJW zhkms1J1eWs$>2tg2OG~w`HuKxzy~Vz zR{+emq}Yi;P9k7GmlPg{yY_S7{NC6LoU~utEGdrpt%B^XowoG^+(Cz<0(l-tQ&Kle zaGI_7O~nSc9}i?XUSZrC$$se*VN)_=URGOodkiBmW$1=(O*d zc2}`gOPmuw;XSCIubolH!&prgIr_k#8?JVw-O`hmGozeKAgp$W(4{y5R6#Wb5 zcGmG@I$e@pgP6+3>dBiPb_M`X_h;0*HGqd&N>2Yisu7=WZ=LyG|G0<6#onga#ohO& z=Rk1z{@L{0(C~Ifw}Jfd^SNus?lG4m2_?Kc-$5QtGhhLQBu=xfpuUEC&Y;|vY8?&6;;?KTd{HEvNDvBu!h#Z^Xv1J5&JJ;Q~ z^TcO=7YC+jfZAned#`dMhtqp^T6pH1D-P7)`voU${d6<2Yz=)G^+zr@MTuA1OqWjX zdzzMWpmjQ_P@Z#}{>}6P`(V1jxugQ{xay}s_1@B>d&9rRd=(y5*e6M8UZb^*r>I%H~jH;D7fntLhTHYQMBz-=BQJ(tp`nch8sT zqmWuVknMOh4B)J{G|?V!ot4Rhb!S&h1dd40??xL|{7RL$ac|D$kDQ9c2YrPFvjQah zUI#q^_PpcR7AA2gpvOlEzCO3!(@WiK)hLpOSN`eQRyx2oNYbT6_1stRzdwt zb6{FIP35Uso}k9KQ3F8qF_g`j-!)X;A8j-it%t1l^ss}1tL$n9po;OE-yTZ!hV15^ zhL>8NIopzBvqxnAbA})q5E$G$X7bDk+ug{FB59#PbTB}`=VKCx+}5K)aZX~KBX7M+ z-ZddCO`Bl{L$KPwD&O!he`LACHR5StMbct_WtK$J-8$u zEJ0ZfIeK^C>|)RhiOAHA6S-@^0`xvAU&XTE8}~mj$kz~l_c;r-BNxJtjUPGsvt2anKLALRptQG%?TqGT`==cBh-v*%dhyfw+r!JCtD3XMpZ2Bp zWPR-s@G#*#tFpJe5jJYs;Il&#bC3A+(+nA~sQA`Z!o&^YI;W_Dsr9peY%;zYRMe`B&j~pzLXExsis3;A>x7H}~;wxv4yyF(27E zq~kz)@Y0zg1U6~S34QFkD-Mxnsk-oD!mEE3Dl#WEZxsiBeSvEbIk;g3LC%?Y?9iFe zYr(}3s0X+_8`P4_TfsL3Nq{UW+hs6-qoueYxxrox{~-*2Z4I~L@US6QW;h>BpE(J5l5pKF8Fg7d@rp;&;cZexRV<3i(DWttN7H-;hu;h3)~; zuidS!at|_;r7z@Ell1dU(hK}P+MS1wF?S~BNw!|$Yd`a?Uk(sDTE@og?z(1b&kzV% zYv*NM8WHd22{g)H5*cmm-3%J`qxq3uq!L6izdih#mh1oUf)~6j!c7$JQM9o6=d*8$ z`ogL&5fkrFG*461-E_U~RIpY%aHfo6Bm}5do0w9ud5{Nc&2MV_y;SQa8sij&*{t19 zBG_5-?VBh%bL~Ji++el4k9%*pc#dK$-!5IXYNYg=s`*j*cwxTD*u>3@3vmjGB#s7i zq}shVwAy>>3lyaz-CQ@6-~Rivoick#xQeSDC*yY{=9=%JO#E3+tOWzw9ni$r+CvDvF@Zyf@#UaAXWT-rvB5e(Hif5ispnAs zFP+k_JznoIp!qs?G!VwLD#^hxxiIedI`lCLQXsWjyqAVE`tkYoa_I&Hw4YK=dAy}~ z%P;d+@&a}Ekf2p=a|PlK5Hq1vF|86=2Vdlg%49q~6qm+AE8iXMAiQey9uCOp4f#sP z0ojs?AIncYa+6D+{59dXv1RJmbx5UmXLu>g-E;y6dos2nFIGX? z_hX>$H$zX<3Y)a9TMJci)bEu-u5`#LBBldJ@H##zsfN|u=hK5 zov$uv0sVX{5{`!x)m`xonHd>E%e*X9>94Nqj~aKDYgJ3kYGGV`uJq z$^DBBD#l=y%g*O#Lu<{W!oeTCeLAZrra;TNJN_B4@=Bao`@)!J-6jL77+l^os=Iqx z28FQ9ySk?2U^XZ)uhXglw5&&7rv29B6tLOEpO_RUNqB%01$R(55_6f}x3RWVPQu>H z`?~Nq*>h4N_(sjU-e5<)w3ZeT?$zi!;UT&*`_s{7D|yFi;B?lv(1~T2Q1||l%J)?y zf7&WdWu3K6?8}dWBEaFrX(z%FqqU6X>Brw8OkQp`eTag`d!(bx#(p+n)ppl)>?^9; z#)r`l^m>3Aanv6W;|GX2sIbK84mmee0RZnk!c*x*d78v;1`!^^xiUF1Y9)v=0P0QO z+O0^hshqMYZp&Jt?zE)@8QvzM8kI_5K$O&iysB$=ZWA63sT?<0mW|lKH`yrcCUd>i zKrCDLJjI98u2K_>AK^nW#vB}EzoLsWF6M@B)-kEN&48b}P7wnLI}1&Bqyr8y*7ldiv;kXwJo{%;?Bw)O zUnt^E2RWD|#lKfERWOuR2e1<_@O1>RF^p>RqJ3QdYV0HWt6Ycus|J!@x6Yz0!2USl zFi;-CyDH_(gDLkH{m8w?>3pxEuVWSOBmjH6%AjH1TO?xRk4od(Uv0QSh8A~2f?c>6K9sFi6@y`wr z9vsWAf?sArpF!nqTTPC1JO4Eh9@*j>^2)fl-5S6ISi5%~a~Oc{H7Bml&N+xh+wPVi zAr7grr?KqmE?_$FurEMXndZ;3WNcFgHAY@{oHB+g;)dU|i)Lsq3+H>=^*FaWVaC8n zG((McFWt@LWPr%+*MfZ5cXbj9FLI8WNoWR#>-^ZZX&TqA3oAOX(ta2?Ud$k=RtauW zbvxudfQ-~|Q~SkH()AM~FQRQLr=~&u;JKP}oTD5oRYk)edw&9k+yhAg^BuDA5f=`& z@BjciVIzfybAV7s1^_w`N-qP)mXz32l~o)AOKNyVVn#if4JOcJT|I)|*Z(H&ZqhA3 zZ13o93(v+;$6rDFNNA2x{a^M!}`rSMiRKqZLt7czyayIHaNf;~I~zVW03 zJEe^62aW^MghhyN> z(#osi09v6MM>}^;Jgoy~#X4BCw5_Bv77oiO!X~EOcQ_8m@Gqf(DsRlerPzJ!V`+-N z5?^e#dGmiOGpxAMyiAVkjL!f6tapL;@+g)n8A}GPM65!z3l9nWum^AhxtH@KVMlq* zeag*%R3%l>S?VJ=#KV;ErX5FxmjjuiRA_oeP3$nSJ$tq-SXlwAbb@NEH_H=*RPh|F zsiqsd34m{e6x-Et)Ssf=TvyOe-s#^(k(ZC!f;CB^xLQ>8dOyOhgaHv)P3L-lE{iaut*=j*V-y9+b zFzX@mFU3OhuE|_c%Q@Ew0ac`FYxRDM`|o;=X*Ul&7loj1Pt0!A_iM?ylMG)(}=2zv9< z(zzCp+Z--UgiGax0+$`a3z#)g@<|cO?4pd#AIyc>0t}yLBYiLlp1F6C1L#TCmoNq8 z1g55SKu4ScXfQQsZmJ^Z{uK6sN&n1xUy%$?`36wP$hkWU>s+xlQNGoH`g7M*cO0{( zrT&6dk#gtzx_AOwUhKyHWOt`A>r~k$n&38K9QzT(rS%(^iL^z!0Dp0c))D z*_qv9gt)M1}Jtme*Qf?gA7wRt_35UK%>r|;>kcNzj z&c_R@p6YEvicb?P0jKCN3)sALxPBa;9`Uqx5uVw6HYbJqjmPKc^cXItkw6>x?ga1i^Kz6CF4Q<4Gp7DJd+29w|mpgWDu8 z#g3c@I{9FH3w^P-aE-Rn%2nQNKn6PSR9s5J*&V_o8T<=^aGrDha{D=ISewqgdJ4%5 zIj{T=91yQv5Yv)DwLmr|g?Ngo$@WPP|FNa@l0TCtp)$BN*L%5Zg&VIr+cRlB4I;1+(q8tOd7~aUM5?FaUZbDzCDpW<@{O4jNYdOE4g<3zNUTVI`2P>XZcP>Dlfh z45@yhW|1?I?b@BeJEzk-eSFGi(()hAC6CCSk%@H|JEgJ35fTFtsYk5}5h4Cj&%qnr zT)Oi8GRSlv`!!*D&Q#~rI_)`BE)KHWn3 z+SG3+2q8o`+NB^DjcVOR{Z9lo$T%CG#PwERmE&6uE+%GH-iRFy%upE&G7om*==zGy z6g7zpq;0uTyaA4aP(nPZW1n#-JKf~qokDuI8|BQWHX&p5cJYs8NpxSPf2Li?K4ECF ze;)H5-|XFSY|YMvd6owEK^yOLIN;m?c#P3k(95I3Av1z?BYWr@N9~iQK}Q3PC8_-= z#coQG!Zr`qUJKSIe!+9M_r&e_^q<*3x_mU!rKQC;2&MlW>_RZ~F3~1bdoIgd@$>?h zx$>QxCyf|Dcf17HphhMsq-FvlYebqau`OTnU^X)*+nchNo<(FE?}Py}1S#9o9puLJ zx}tb#KAB;UZ>8!&TtQy`!Y(c+?8_R{ZPV+@_4mH`ab2`GX~mACUE~*I_!X6|yGh2b zynj@1t|?DpDJ-Qx1ypd?KG_}Fgu^$r6DgSblC&GwfgNj>Rq67xgq#?hm&RRUBVm-~ z()~roKt8@{`mFqiXHS>zg3)2$J**C(D{nro)17WAy4W|@xhAR5YWJ^~L#5aAXNoyR zDJV-6neTEf8uen8WM1ZB75ey>;CAekDSCGI-V$Nk^~y?#7E}|XX*ZshP^Ia5A*v3n zSbjd*$`N6-m0};|k8fx$3|xik1lQ{Y z>S^S=YoqH)DMCp*`(Zbi1K-zNU@F%zuC}%=Z5fXpZ4bN)7TiH-Ww*7pCA>vNPI#%z zL1H|aOpnX|6$EG2dYab!V+)be{f?d@FfiCw6O}*59uib00#FO9(v-SjT^8_w=SxDz z9($}?k$kkj?QNEy+FC1t?vr84H&d9_@%V}g{hE)`4!C~Fp60W8p&m=mV)M0DV`C_I zh+;2s5-hJ)9L|SS?+n^`@9Z06>1ZY-)m}gFzOvJ((=R5xcFJd2<~wr$dY}47!z(6X zHhgdGwD;r-#GL5cPjSm!+THK{xk;M?nhC|}J9YG%+XfHL6mEGHV<~cUnr+KJ12}~= z?YV=aB(ulCd#7%tZ)agIH+#F0w+5Fsf2)n{(?1*~_A8a{^Lp(85lF7`$n8^NsVEnN zNK;jEDMcHnkUyXVYO~~u$xMx1fZ`H}j^aMqPI!KJ{6aC!`}Tzjiz8k^1Os3QMURgU z>IMP=rwk!i{xSC{)4P*vl>a3-jI58>DaKkfL?&v7rKF*_cfHmM`+J|ov`yw7w9asp zwK>;bRm$+Z9w|*X{UlTIK;>p8{dzr^pz4^W)iV$i>KzkR`ErrW7OzuC?0he#xzUbj(+fvKmk!fk79 z1Que{k_VOUzlp)`!-Chef`fHd>no@p)WO>n^b92!Nhl0FcieqKSzolW+14zuX>?V+ zE$?M|{CaQ+|@y!WmT*SU5IoZEnrdAXh%)$0~Gl;PI+fxou8Uyh@r1VWUVg zh*2M&68RcUT#@zgOL&J*I~8%Kw8n+oLR0*ydjew0-N4*T z&t5&MdBv{j)2rM zOzI1svLX@ndKJL6GQ2Ibcqbk4{%~W2wa(m_%vYgNYd$F2$M!2c_imvqp#>ZFKM`j; z3q70Ch9qpsMKE`)x5me#Cws}F{;WIjcEf&s8ZQqF#u=@wlxkwD7s!88bWT_tr5Z}J z;i#~zb*Is(*JPR_IZKzI8YsGfo>dT{<@1>JY zHU9{D0m5=lME!8z6kh;k!01z?S}Dv-We_>Xd5j z`)!HVd0h~>HC_IYmDAWG@-J%JJon_-+A0#-@nd*Fp)H^%FF>F>y}Edgr~tV-oDcgb z@MmAv`a){V>iP;1)2f?y$Ib=-WV^Cv%a2rAZtj1|H-t=u(*__~y!e111RyFVR&~r& zrH8LL%gW4BB!^oLt}IQ`*4J4pRXA!=RQ37OCm%gQ&Yb1;R|{HB2uRU4s8#Y?LrvE9 zjKsSmX7CJPxZPTKZkoh1nhhV{vM9JVxHgaR+!7#1kVIVvk@q^Qs9A#^CF2CSlk+A> zvk^V^;_5aUUt}z1Ch~S5*su`zA{se)Vh1{#=1+Fqkdrfb-qI;IY@~E;TYAsdLOhyx zWw!r$<)=a;x2p7Sct5;!AeX#n43%CE!4bgYSAMrJCfISVowN`;r|oqCX$3Y#1@KRK^gos zdT8?3pXt-FSdsOAK$9aMtv>o(Ira7IVD|3nwpoVRch}|VPVrQfytE5SCCsZtI_;~; z0mDr*2U^vyjaw+0i*H0uoj-L9e$s4q=`VF84(H{EgWq&G@1yr*&;3g+NQbIl0Bs_d zBsTvpd}SYset-DOCG29B;S=OY(0HD>#B@-|UnPju!atWjv6F|1AX46m^k$O#DRtlHSo8E7;H)Mwj5p&Nq1C=H8bP|;$3YUM9 z>=c{z?XUlJEad%9#Mt%EARjKIk%9fQEAfiYl>>#=_`gLgiPQix?L+$3uARV^cUUJ) zAQpKnDcIWvdP?W*r)!U{^2?UCu3>)CxZif4x2ykvf91Pm*ml5W7Tlknd0YSe=N295 zFetr;gG>n1qm{{LMX#Tk{_ykXpMjCtO}(7~zUXgRW%k>p(OXsb3r}A@qzrdmD2gXt zs}qi9J?*5tH9xHFoxlxV$4g@XQKi}opacav zk(a?lXW`p0g!S6FgHGVB7}GIj?Wsw-eXeE@#P4XrO`Bl9W3R3>iU=E#4|ieRO6S=L zWsrbVri#4$GOLHiQZc~|q6G%Mk@BW(exmpnM(*aPP`OSwiMZAw#kPe)wyiJsZY+2G zuMr;^Er}YLl}(F1+Vr?G2x zor6gsU5jB)-y;#4GNaGT)d1A=tOF0&eV_VtwO*2nAPb)DRq^ko-r1;+PN+*%(v3jwM1Ps_UZL0q$^ zQPw4h50veR^RDbuI>edo^#Z=2+Z#S@usn$}v*zu(Ii6D`R`Osqx8d<|Ti4S3TTyc7N1{Wkf_=jLk~XRK9xRWI)Mb2jQ<=}*uU z4m^M=lHv=>tXXRY*KQs&xRqMNFOIXk82>KoLqMF>f`LGa(KXjSLrB=B8y^pnl?r-- znSy6Kw&yw>f779pt1#7j>fIwFm8198hGO3@Z9fg%rhX@VY>av>p?c?6qgUNeeR0&{ z6NN;le~TrE&&}MCzBO3n-enK(mgxL~>v__7Hkr>IBMc>49vDcG^g;U!ZpbRPdGF)k za?>^!8#@RI%gha`n>vA*tdRnFckb>f6xm7z8``aV+c$7Z-rz>(hQh(WZ+SX%SE z+Mip+K9-Bbnjkyf%x0LFIOGX*v;*HWwnymzxm{W~?kyeXAQhw}!X|nwA&&{KfVoEN zdaM)C*YH|K-R)P$>*m8px9l#ghOx}=SOb}gf49eiB#@!(P( zgkz#Pf9I!p2r@F%AX!J%LX6uX?Ro1TO-{1N?jDNZ#4Mksw!f7;gR-p+Zy|N5Z5L2& zD&7-()hr|Y@Bh2+)%|(B&jJPB-kC2QK}8eIJ$W%n&7@Iaurqs^M}k35r$sJJOOOmA zkF*bnJXx3TzkI^<;gtjX1UQ!W*%n)*{*>yR6gTW)vbDv@j5qpCB<~MRS)@H~J_pxe zT4umu?3!NYH-jR@cFCGlZP~A;#_WxTzm7Q~Mv{lmBr-Kps}F8uMn2^*f+Atkm%2lB zIbXCemAC|j0_~_`BCQxy>pq|S^?xFk?}9LAoreCg=2D-IRGXhQkcoGpzgt$4z!l*aeBJREW@gB1lHYI7NEq0^tVAh;a6sllN4rpm1-zR1<91zWjK`=44 zs2Fm8_haCesHo_HPgK=WEdOfnF5TTKWPb(S4UAusDS47Bru#jp>BmTD&7@VN)UrgN zrPyoB?9##VDR6{=f$D9t)*K`SWsocRReR=$mii}B3$gW2*pSuT(TH2ot*To?bjR^?E1y)#%$NZ{%!K%ItUG_(P-5Kg0eB9awLwJt8^il~%0l?GmN zsPOpDzm*Ids^T)+%Cr&>`o+|HPxOBTN8)qk@0QWO)tZ(-Oazx9d1*O64YgljZX|P< zDs<$70q{o#5~3O5O7vr~uo1nQuZnIYKyNKoqB>V+X<>F+PL>H{bPYSh-mqygaI6fu ztgWRPl;4b=n3z(ltqqc|uhK~*nLv?xL^#q?WD^37P(X-wMj;=BmeWs0xOq*ueWl93 zrAeOaD>5yKLaXIp?2W1n>Y$J4Zjb%wd^jlA@Y1?bzP+~|4T}kWI4WSsVYcjiZQ`Pk zO(>(kQ0?}?bG;w9s5dd0^s|(+ls{!bPMx(YmqB&Szljl6hj$x4x(9B@Ei-Jgof6i% z#=Zr~F1YK2Glp*cv9(MDS?dHph?~&*nKDROeelM|N;?UO2&jTm%i8Xf-I@PaC%5f* z{S;09u`3GlaVc{9HrpLcM3q!}{P>MzJgbwpe6P4DP^Cr)gA=Q7a;XG-ne*rt=D0|t zjsZih(M`8HJWah@z5qu^^7fL14B%GK2DYR;FM9=djGum_W6RN1%%txIYR zX=97sg=(X_>eliQ?Qd)zXms^&kaj)El>i0b)6hBsH4j zxVFp{rIJ~E*5?(XQy|_|Cj0aOT87@MWWG(F0ingtIa-Gspr1XW)mKQrIrb5r$ai8w zZ*Vmb-~ejk#LryUh6%N*DBti>wZ>DMt2hNpgIgup7mI?PTWq+1jI3r3YFwX_fzQz} zVSLDx{D#6`-R6C=j>vLQA5=?IH|_FaZ)QePMe|En9OCMxO9$n#@tN{q)PyQE+(bs_ z=*XOjVV{D6i5}^CO}`?=F!c)0W$rAaY7*kwZQGFSzySR|n&zG-Yt zJ;Oe0czoY8=t9Y3x_{kwy|!N5D_2*K_|?4g-*qp%-yFn}TE;hLLrYH|ETgZ;gbY>v zj-Xk&X6pFZTd&otwot+rgaed96K%13Cg^cg^$wC7#x~9x`uI`P$&$Kre?z{auq@v`ZmS$Cf$nAoB-LceJoYkDJNN;lo(9 zX1gm6psi1#b7D8NKz8G5wqAw{2xn?NYpV!fSLFX4ck&lS>11?H`tthiF&IoY?Fd4{ zsMXCbz38JZ%_DkriS}I4Bx~Y_=+m8{zeSFX2NrEMw6qpS^}Hla^y!zX=yXP>#H66`pp(wrbd{>xP27I+`et-ftnG%1#8RxW`K;()iKP{PQdfwy_#)kZBUv zA6YR4`bMMIm*by+sZY%POiMyx0U=e>zoV1kdBAfn9;aS7V%6N1f9s`l0zg*`J`}EX z;|5U8^6x%`iRr@5FkB79KtZ)ik#sqBM69rhJ`A&%16>QD}@?29}wATe(TVq}TE?HDTd9an= zrs~v;F9bTPa0v?vMi)xRdWaU|U9pGB+`b*=+SSr_FQthYPr%xq+3a96bYchGyqmUV z*48nTVIWk-0;5u*KritYM`yfnZUPm!}@g5b6_UVtk#%n`{VuI-T zygs^ExOQ|@IWN?n6Pav$rN+EuvN9WE4y=>G3h#61-CU|N+f`i5Gun9f13>t?#U6>* z)i`N-mZv=H+GQrg%jj!hf~#% z#!HOx0p!dF;rY>a4YzJiku%5a70?tD$<77pHMTjSeIB9hMZk1pCdne%R`IgC={_&D z$sl%cS|!~55&Kv9AkvJi%y#kt0_0frzV&$JCTc!=yA!|8zb3fNi((het+UE11DS_8 zvR!i;)CiN*SAFJnl97!Wai%AiBV5gza)H8)`wp-43+2hiZh25biXQR=PqmMEkwc4T z^vG)`sxIw__n67@a$WN6IOo=YIOU1HiaH3r+*RzsznT0FabW9QXDp}R%=u+}y@Iz0 zjPnPh&&}I``b;JBdlLAo!ozI+684fmiSN}m&ZAaPi1!IG2}%Q@VXswud6H&WIt&@A!k^(w)29(q8Str2LCnqGqI z%$aoquQ|(mP{(_Dml~@i2%yDkMjKJRyYI1k*Av5ZZt*m#ghRl)@0cm-U^`9I0T)>R zcI>s?cmKzZkP+OanI7c%b_$g!;o`~y?cIE#Cb&qpT@>DG&+DDN69t|(6CIc>FmR>{ zyf|X*rxy~s&SHjZR-VH2(WMeLV?r$i1ahhypRYQB?srsxKn z`o4`ZOWbE=t=dQk3%QY77{gpO^SY2 zlGt}p{D;n@(o|#Gzon;LFDMU9V`tL=#?K=kf>|QTO24ckc32skyZWaX68o?{5wV*; zFBV`kCb0E=@@5#=+RRQ_IgVwCMnJzaJio9cll}kU(#-zvS&Uw_Br58lS!+ws&y}8EI(!5`aW=__X($;KcMDm`&eR6 zrt-z*#ur;w-Qqm$58fBp^%ypUJIiyJ8ANVxNc=E1lz;3;QC9*ft{x-YUSt_z0}>Q+f`P@NGap z$*F`}+3eOM{b9#>hje{zI&qr_*sVIJ0f$wbGmtXMV7=s636F&E?FA;ZVh$`3>o6h3_2(Hx z?juIyX;O9^^?s$RG-K!kRk<}M5U5uCCH)jsp>zl;HhDn;zR^9=F*7}~H`SIQ#iJd8${gb}b2^83eP1DM2R%mh0FyOfAS?-rh`i`a93z>}4zgTtP|zpq zTwMrjSx=-hvVeP@DZZa)&Xvs;iiYsd^GbJOB71$rIs&I0W`?Sg)-_$t^8uysb2lfT zyYK{!Y|{po7ez|v4`Pmk8_UOTqVhff#KCWOJ2jq1VOBAmy)%8Sd^_ID&#{_pm)sh# z%g(QbB4|5(T+7kZ%iFMD=R@?59zRh{yZFPZalb2Sr_F-I0g87NP{gzUVbNgca3zG?fDikvAZ!WqkUz?uwB>$N@qBpX4KXv0=F4!{=8kJe!}tUAs1 z!Jn;j%f`1DSMN8Z%(T%8{bdt8FL%W`d54) zB~cRGXcgj5#S^jLCsV93*|C{2Wxp6cU`s7uOX7>st=A<@%?WFL5Zi^8mdHlQUX37W zRyd8&M2IRQhui7a7w^pCTPbs6VQsqZw&9T>M=A>t`vu<4O+N~Z2g^SH>HgSO2aV2y zqI0zZ{7Qa!!4t*~*X#15hN#Kgs`f9f45)n3V8}Kh8hJ5 zk4NfB{E^7YK-zXBUcV>+hX6q?=;*nT{;D`MOo4ocqW5nSX#ZZ=}6%w+3e$5_3~T&Vtw#CzWhZ6Qe*7%q|`NxNnHDR_ZW>>x2cSy`BA zF$+0aY-eVkFck&?)0yjc5^ppkrB?`v)`U+S==XzV9uO+hui0P+mrzkvaDzEwLCkO< zlfJMD&##@BBaZ9oV~HS;$*s=S;F;!*Po(kS;wuhOp|xJ%9sl_Fjg<&_FMP(8KxKw} znCqaed#zSTCRm5m2r|YEh{oLTyi4rm@ zLPWq+sqhgiQ4vrPqg)k2A|yati{T!DLT*;%KBEw(WHP>|-{1QOk3Kv=l5^(lv-jF- zuk|cEb9El`w(IRfcRfbHTissLa;E&-DYrA{$a@>AyQd~5d1hI_3JM(umG{mI9M`uHg zcUT?n56h^n$QztV_33`&p5*rPl>Y&qf1XZvP8iY)*|_EXwjp8i>fkuh)Jy%g_(JmY z$cXs4oV=e&5U=mBHh3*w(>47+uiu3yy7xtz$2N^EV{CX9H+QpuxwK1( zaZ^~!!p4Z}rGLkD9R7E%D%7$%A74X1zLB-=Nle-C6IafqcZ96}<)zrNetCh zr9&e+X33$yTYbY0J*M8KnN+6zaMjO!a)KfRR6zj^;&qnFuu_$MSHVQv2m z^l#Wa_*Y$m?h&gjVpm?l_x8;>;!-Bl?e7r#;wqot=9cDtGCYnePxKsnO{u64IBq@Q zFqTPq*EI|@Z4TY_D(V-u_o!UZom)oZvGcE5OaH(`)ph zs2A{iQv*6_+b%qo+}FP?-lp!v&EoiE`@si3YbJinWyE)k2B}BNJ$+M3ox+|y8&0ea zobg@fWkQ*l@=WyzsS&sTQ(4#f*{EWYl34Y6O=qIl;x@du?q-X?F;C$5YiRVgu9G0G zpRoUI6mVlCAqg|`+uX{y^zmc=8)Lrg^!DSA0#|Exa+PQ2lHS~4oxI=U)^B`sgRu9b zb>x<@A2LR5r|yxCJRI|BtGRjl(a_(AwlWjqXhla{1J5kFS89}O-4%Z5 z@Sin4Gn2&|TFlP=kr#Ama)Z0@DGRPT>GoF~+x=v3*tASs_|fr)*tvsmGX0vnfd@lH zeRXU1+Kw6KA8*PRUH8fv#=m+q*Zq6OXh^F6-b+JvU%sq@pCA5Eki93zQuppqW71Q) z+nooz&};8W%T9xlaPOn8xj%0Jk~m|+ibDbMbG_BeYqsoQ=6DC5J5r$7b+G5lUm9{= zz1&jqcaACj)Z@S3#`XIrP3(LBdS~~s7hN-^FOJuR_yBwD{`U#HKCXN4qN8`N@BOpx zlN6STH}_mXZTHenhH@yj%iqH2&83f{qoch|=#!ke;G~twIva&`ZOiDJ{Z99vecu@w zDN`(CG(YJJy083fbPr=PQ1dH-zPXJ2V@dF1=!oMk4Ky&U^RkNkyEXZoKA0=7DJcGH zPiqqE+)n~)&R6?)OEM-VSqYQJuL~)`xW{*&cwJtmHKPATMlv|jW#Y6)Z`@g)`zWMLmpz=kaJ6-3#mzHr+e55Ypo6K=C3>I`CJ5=2PssIl zVX^&%w^L&t5xr35*~(K#R#eidj~;QDh;(FVU2tHhtyfTRxHd@i;zd1w!soB>zflIG zt#W@))&=gb;F1q_B&nO4~dtc-0K?a&Kth$;oq~DetojgSNm}K$y8dncUA&5C}Bg#-!FI{r`*!7C#=9di+#A=c- z2y^xgCf!83u)|mU*JJ+}==43~8DScjWN@=*Evwe{Hyy71-_MWfhYLy2Fm+|S@jJh? zta?f1^Odx}Q_W5O8_ZXXP4lIElY8RoUu7olyvgYgY(Vnv^{ZFhj1`!B{XR1ZOzKWd zIYc|uO+&+_yYx!6{Nhu$`_<>lZ|xdieUo-!mb`!FE=dC7xxv!*%s}Mx^dB>`m!2gL zlkcQHQfE$9P3-C`Ickm77%$P35 zQ0)1&d-+K(w;h%b?r*$taPqsB#l(fPwm>=MS3b6$f?K`Os+sZOfcamwv8D6(^5qM^ zGpZ!j6%C9iY=xJgAA^`<6uagJ@k>T3MvksmWgpQMzW8kP%?d*w$kV?QkyKE&(wqgl z<-VEkE3aSNbM5DjLY0+5AiXz*Kf)YzDKJmDJkfo^?T1rt`i<*v2Uy|9(Wj9bYNBvDpZ#M$nG|^PM(! zl1l#_5$qw2i-Bav7ytL=`tJZToQ5CQZwI!?)h_sQn-<{CIq@wb(AG-jjznK4wu0-( zNmbxBdMfc(f53P#%GAtW5ZNGO3?Ok=&4y#9G6Ad3MOtqbpmh?W+NLN!G$4B>GWqb! z=V(mEv;({#96L0BZuNc1Z(E3_l>y8>Ki4ELtr73qMFtpCs+~H3wMg%*s6^KxG z70plg653*7y2T$V$qF`K6Ns&HFBQhkL6^mjL->l9FPuzc#m@1wHZ)h{cjGC)6}T!4 zYa1;-0% zy>=Hp{^A)LnU{_G!^!s3}Oijl{S)VCU}jK zr30LmD6jD~9Xtx~c(aCYitSe=PsC`amSZV|^&A_bzeMFS%7BNoz^`;X6)Yi8GPcQ1 zu#s7F;kq;(9ZQb*LnZ z8lwsZFK?F={qzsixVhgr8?NN8BznnJHayOn2P5z@PrqHtX2O9%Vl$+3|9Lp2+>Z}Y zaLJu#+I-}@nCuPZ+TEcVcmm)e3(a|2xY=#KYU-lo*jdb9r5w1et{rz(r(?Gg&rWmtoLCm~mwNlqlVzJe-k#)I@UpfP1DBB2>$n#2Yz1qc*l21KwQdzqvr zXK1oWfX1w4YU0mtNwK zA9H_Kno<3b+=Ey_Ob^!>o0tUZ)okC!IbmiZu?q7KRdBr3rt%M#7nXc||AK4O--c^% zCZ}R<=_2ALISm6;`ByIaCk*AaX9w$RYW^OMYjH*SDjvSVK|7q807o+%i4OR=1Z?Xi zcmb4nd&=~Kav=bmr*!qzm|7c53PW3)lolKYQj2c@`ozjti~Lf4N0x}D%L$R1%-5TV&dZ8t5Zt`biW&JQGm@4WpOzEBLa|NM9})0)t4gk z`TkV=L{bTU?j@E=YTk>C(y_dAr{hDE=KsLfqv1JKxgYFz=SG(WJ&y1FIkrv|!no-~ z28$3&bV=-|Nha8Q&|xtYQk_Ady9gn4MvmZFXwyWfE(I->39RD5V8|rkkAT(p^c7qy zbVsJL2|LU<5OefS_nf*IwX~P&iVQT()wTq+xyt4yYrn+qxZk-ua(yfc)_Z^;XG?6s zthB~y;NzacdQ@iTyP74H`7*gB(K$!Ccv6^1%95yxm9|zu4_|adzlVJyA-a)9P6(A2 zz>VZ<@G`D77$p2T)+b-LHdpo9SH9TfjQ(8J1yUCaAmMx9BP4q)T2q_jN$NM=2`|R& zuv2CDaZ~_|yB2{S%$u7Jff8BCje!GL2h#aTHngzV$($S!&^uDY%`b4!j98{*zZ4nY`{1+? z3jq!|Sy8?UYMAD^Q?4q(xsfpLogq6pl$k0Q4KNGN7DXqY!XgnlVRBFZeVoLE`z5{^ z{gttx%E0nBT;RG8GH3~Y?jg4icrDTUzvY}uLYLxD*QDr2rMcLbX+vHLN6XXu3;9T^ zG`SE61tDooWIOjoFW26RQ7f<1Q8IAT(zv0uh7yAWiXmGQoyy8I&IW{>Mv&NG&K*@s z={C)3QZ{!3CPXCuJtwJV8m3vihP((riIG=JR?xoS1V6vl<3-{LY(>BWy>bt7R&xC0 zM=bO}1xRX*zA>Z_OC>a#je<50PgG3A+I1V z2LDmWSUkcQWi{s*2crwz9*@w~W07Zmf|o(6bh2$#x$$l+2APe*lF_tRh2I*kLMH@$ z^EFY=Q3qYQ;U##<37e)|+yp#LA}bBaZj5XsIi|FZqnR0=a^`9IOWD%>FS^7_@k8jW z)Re!N@wN@LQ8H_zY|+G$x^IjpcrqZzK0F?WNHmk>h)WCEwo{?u@~A$$%Zf&#=3}=t zk&C1)$wQ=E&7w@?=_id%hRCjr^Gy}jqY*6piY;hVE=UwbkeY?IvH4qkco|f?#{-PM zKu8WPk*KQSXWlY~5&kuLwjOnEW-O%!p#G{YxAi;dh_(gnvy;!7{;o0?OzUM==_xjm@sHFb>&Q6k3d|)EUBkWkw zN|LTnR?Nu98-UK(gAOQ8zMQiMt!~YpA@ie_brm6`iy~hwDUBdwzAn-H;{2fFc@)J?@>;ZtuU#sFB(hpGni$Mf}c4heV|Kz({C zY)Ny28+|~nA$c49Dr!AqdUkE%8m58gwO1Tl-tDa$N7SPancU(W1u9{b0a(cm8AMkSi9T0VGRD zjt|nCi+s{@dFnQ+5On+0E9Dg%5SvBRbw^HV{)ew{;kCyHyjx?)lqArvK^EM#IK%Lu zir*LTD*Uri4s8<%&YML0?_pMI?`C=yV>uNn#*B5g7^>lC)4oYyHFGy%4oJ=HUQ~E> zl6vsbAof*nYg4a}nMm+xOMrdpcAeImpDKD_;2v--8`uW8MvU$dAchmBJA zYUu-HT@04=7%Tf8l{c3gOYLhfiQR!Vp9Kh9A+H&=;Doqx+byu4>1krCgT(JgxP%XV z#fQ=#de3y7Q;)Gc_Slz{|Jd*J!c%#N<_A7zFxMD2?+|;iTg)U@R0o8t5>B|HzO8%N zONJTU#Hzo+pgqX6FNmGkU1~+T$eilb&#h<0X(SFLbGD$hTx&`;#~fs5e>%kc>5mec z|oE z%iI6VS^2!uxy*GLOvWjCQ=+|~_5vpfk{M$TYI>$}xkPneS>ZAv)HG0D4SAr-EZn&m zW2!=K>755KZPQtU^MGOYDkM!)dU3|i^ISD)5hnorRryNxzB!ip(9=@&zbvY+Ce`LZ zr~9$?>J^dx0q4dCt6uj+)H^Rw1xD=v8uQAxo1O^IyfT!E*HX+;eg7T~8iPP1NP`-2Pn zHG@y?-LSLZKIwu~w-`upo8`d4iVo1nhn_FG9wQ8smooDTV#eJ{i-t8HvcLtM;}6T*Mf0xzRiaM%ETHKYcac~OE(N` zI_D!+D#aCuN#gsQs7Fn8?&#S}gtrF`z`tZ5yQg??MXOAZAs`cEkl_tILifvR$u(ym zp}TuO8>tzYj>WW8Eo`k_E@1v*aeEB!t~V#Vyg$>)h2)r0G2_actZKr7+S zQEhNL?(rae*IY)?BYJen$k|VkL9F?(?|hvEQ{Tn1bZDoDtf%8q*a&)p1?T`vOtrUz zBUH5$&glN;g4(9kAT{OMy>9B_81xgdcntMdr|0KDKgzF=&CYm&+BX@j66TIyqz8LE-OkD<;j2&$NviCh)v{y*k_!^2X^0OS;q&!-K{zlyDe`!T88-)N zrg-)g8D$ywZQ&+hu_IU-UvMV(gTm)dFV9%|K@^#C|M1|}gh#PscSn<#F^QGEX9mBS zKQl_kT#|9Cw8P>KFAO>YzvYK^spf%)wU{)Sa38>BYu@Na=6_t5tYWyjp@egUzY} zAod$^bDhY3hhxZnSKtPNqR@+VT!Dcv(enuvmS z5hkbxT8c;27|IA|JQUbE6w%fa17`^f>>s2)&~Sfz<_{`eb2|vm$OoC`ns~^pnz9mW zYE{+B{9tLp>}Bw7G75@$8C9vC^XGo(Z1#4N$NSZtAlOg?gkY*LN;Vm>-2SproKIzwJg>m_2ure(hYhKZkwR8SXL=N zg_x?Id^((cxXrss^2KYnSO2mL1KMx>L;ebW;x=oeFCt$D5B33hCbWWBKE_x+PFS+R zm0h@?TjUE?^s1p&6s=}h#M+$nG60*HG-56Bx**^Z^~jO3KbC{NLw@=23$viU-}mt5 z!Ploe^Y{GiQ{-}sv#r)}7e3w=qwDJu+j2X|RSJOW==|ED9|Z#$39g!2P!@vJm!8ep zbWWq>v-HPt&#tK%K#^Bwh`$E(e#Vn{u_YFfuo)vaLYD74N)a0#zUf|goX?`dd3wPE zkZ0K4-CY}Q(+FRlbNlmLio2(P7e(rdVXV&Movb-e-Aws4aFd#s7wDQ6Rkrd}Q`Yjm z5BAE(L{dUI{d%rul_pOnUP+O$CYAAu@-*v$!rZR!m864XuUWl6-o`@Z}y->A0 zZHAon*{G(pS&6v}U*?d7?EF?@K^@TOv5ZsNacwvYFtluJ$Ok*F-{|PvTqCL2(oizu zM8fK|gUOm}lJXCdEDFZBs{GUkPnRTSJZMYj=4H=C0B zLI0`$0tJgtsHBNp)tiITwQ@dmV$cKRD#V)jjkhCBzene@;V6t%9Isqb(v{y5E|K1L zV>X^Y?DLcH%!&jxYiZc%jj=rL%Jzh$m$=;bP)(;N<`pPv4`F!kxjN|n?$h$?YyZ85 z>^ymi`5Mqp7=3lKElyq-?;N-^m(`=-aNIicNcscEb7?N_0ddpc%w&8pWfQuC)T!-8_2>ixg&Q`1z_18IIp;}6|a@jPT#a$y-5F5g< zWKALu*4{ZQZIPIQZ_~UwxmL_|TIh$^GJ1Av@x5FBN($O@1(%T$5|?-fqOng~pBbiz5Tx{Y^AO6~VGF`ArBQ zS!ZKEr5_&8ZG7^w|FOrlt3`Kh@sMuXYJ53zy!MLN8w2n|x!!n0Pyz8I@p1>^PddtK z0+wTL8ZtjQ^ekNMH#-hEXp2*Y<0ECnA6&D}8CRASC!lk~3@9}gu#>eV4O&w^>5mn? zxh3qf$eOI3`Ev~6pHqFg#Nq%TBC`Pv&7^sALIG>=1lfV;K3_WFtq(HXxYRY&bu?{<4%n1?oPQ5em&txDi&m{AwZ?^?$Sv;{VzJrzK@=9Pw zjYRTyA(MAM8(E3w1KgbW1{GWa9-P%E#GCG${^KsME2-R>FzI7GVh?QZrI``3d>?_7 zzm&5OyQnp_(tpGGw&z4A719S_dC*cqUMF%*Kt?B$A|zJ;QP$Jo+jQ1vUAGs%MtYa3 zJIg$GKj@T3vQ(lfR921C+zht}b38m>cSZmzUZ9BFsxva6F>bs=SJz}A!|Fj{#u>QZw;4hTu~s_PfNsZAAF>1dC7m2+RW0gX>U8mw z%QztUE${^pR;i|S{G}G~0`Z>^a%Po)r?la0_nx2}i7lv}e2%`>r5f(gr4PaJy5+Pq zb^jh@d%BGH${=v(ns7Fw$=L~slU27(L}5?y?FnR=lWSG?`_!N2Ps&XI=7;Pc+u(;$ zu$sWL+wjX2Z55k4t1%b&RdD}wB6m?8H1INfW8tV?qf8e&ap>JNt1yl`_FT))@XiYd z+}!1&6^Oir=8h&`V2yiOX#E@l>Ys}aMPmUlc>9r9BVw$vnFvI{jW-Tr43*B^AE0xjA;6W*?)L8((dv8a(J_wdlouA_VJziu};ur66 zSR2H`*3_m;j*O*{E=H{dk&y=-gLtsf(<*tU4?P>F>me*xCN=~1cgiKBy70g4*C;NF zzvM3%tL^uDw*^S24FsbwPg7rkZZEmD<$1~4+;5Ekvckc#Us~C$swZDjNES+B>c5$x zWESMVDr1<$!_RE8p3uG}XYI%Qhz&tV&9IImF#t5sa9m5VWUv1QPri#C)vKGuMcT-< zQ$GMA4zt|7tRyH|feAHo++u&Dz)Wln_K8a-{yblMQS22E(8GAmZjK1h)5k|Nik9@QEX5n4Vyb$Q4g-OVqJ6MRwaNGvLRKigRMrdVV5Akv|6E~?xqUpt^Q zXQwp|d31<2Nsw7$@vQzB_s_g%n2qkhO9MX%TEN+DxCI@f^o?oU8&F@u`c0u=UNyNd zKS+T)m$56=^rngA@3G-?;7JA=ZV2P5yB5e@v9h6+RMKO%ugu=MfcO&2)#i9aNFrUj zpcoR^z|Te(;8=<^Hl``Oha40wEcpN`2eZg>w&k+)tQNZ_e?@a?6Px`%>Nv zC8IYEZPft2=vXt&Dj{m>aK;$D9nkCHt5HhrBXKelKYJ{yT(2nRfb+DPLoO& z&yXxY)2j-7!DR5fe;M0Iwu<#ByyE?O$!28GS=H<0&LDZv*?hDa^%hctV1P>KKY^FV#U%dZBF(b+3xluY6Vb zb;QePtV{33^pRYmxxWRClkp$hld>Em_VKi>jf~5=fA@{$7np-at%-FRYHvFe<}NJi zsD`Z!zd)-0Daj!5bJh7l)?x~%nuRU&%gQQND=CLj1<5dFMD0tAUy9`eYnPiqFwi+QT!DS70o8^*J5ei*+6)S3>bGU=+*#62!xi}Gpm9_?C+sfqJ6xg50Zo0T zz4sNIqKR}?k(zRRqmW67dSZuLjI@6;?MWZD+tGh#ucu$QZBnGefv%?sQ)k+Cd@Xzu zbLqY3uiPIvYe67qaJvf_ zbEV-PXHzL#(L8sp0V!&l1G0R4>kp6>$62$O3f=!OwBZG!t9Q9NeNfUnerC`)tQWp} zY~RDHr*3ZFf9;mh{A<7MF@7Ula7uk{C-P z(+dAJ_8JBv9*tYTO5Vxqv;$b^kNmeaLq`KQOZ9FXv#ZR1iCk@x2<9_qiy0phwz*bQMI5p8E6Fg9nn51;V>irR$v@?FD zuOwzPOLl8()Y@K7fR<$WA&T5tB{ajB2PH4W-Q_(AGVZa=e?U)(CsBb1r8P^mR|U>f z1#f&H26b*MSi`$o4RQkVa!4hZWmNKI1MuSb3J`8lQ#H^Z^bLwaBw$%D(Mkc)WrP~Da?t!I~YH-u2V>b zjz4Tt@?l5NgOUv7s^J`ft!HBa9| z8mH^+23&5k^v=XzKLuo;`w3rwg<3{;9cAwgb$a{Q27Z=5z7-bKa$A%Y#gpd^LO{k3 zI3*C?Q5(0xltL@emQPL6`*0OLE}+8KZ~FjO23*DW>X(TaV1MAo)m#WS#hhcbWMgcq zx!|)=3uM7$AoMA|pgMpriG%tf^?S*KNVe5BLzSl>o$H9?zMx{bnV%I-xeRPNFz?bmD17;;*aXvS0h;#X-Z9eEjznTE9W__Ld}YY9?#P`4ol_O+Lko^ zcOS?V8g3hT-db!xL;gY{dx3*zv(NIi%szmJFHM}8fOexcd{K&8nC_D=m1pFEKB34> zli<9Uc66Yaq_>Q z%e$c2aD( z=gAEH8DL)0>1}L*ag!+}b6+?+?QoB~+q7hU)L8hnd#o*)kD@rUYWVHxgNFN}2&8PA z;W2j+=$+O|r;PCxAapduKgUv93qoZV$yaNQ=? zB>?xve;4$Ujff?0|1|%;1~#RdC45P{r*6M~an7FL9x>dJoOFwm9Hpmw)0Sh3B#@6T znXji1((up;t!D5DCnzo%v1%nh=U%+W`3`HMtqtN_XGS@ptceta7?tZz0+*fiH@6?< zL^OT<=|RDb!POFP;xBS#>%@`!+&&%cOZ03$dO8prJC2s=z=Td5*(#3wjNf`^(I*YB zk!AJavynp)2C0?V{|!Cc65VEcAEE;?SHay*x_5j*Ghbc>;Yv5=hzk9Y}P|1qW4^m=}G@ zWn7PJ3%c}k44m#X|6HbD;LWNp-U;^g3E#zNS>yAMBSC;-by*9H!T$hE8rYIIet_75 z4qu=$K}hU^s%9^7ow*8PEkbF`N@cTofEA;ZMUr*b|LfPzyg-VDJn>4R+y0*h$wjd( zJKhnN>iTQaPd|u?`*>bljX3KX6%y6mP$yrsG@a%KI5y(v`lH;}g+ADR4NT&;kR8P| zc4ecaqUN?5y7US6$6|`$#sD`-JOeaPBu!I&fA%Wq$AyHPewP8+O;G`*9(Y;EC?X0c`L+MLyy zJw1ocPb@duyZ!%tg$@I(5ysXi#(mLf4U|Q5VGpa`7e5?@EI5JpF}X%?u}Yo=+XLfm z7p+aUh_*qo3x!+weG<@sJ;S&FfQ$W>BX#>Cu`~@`%CH)(6P>4JIXzE$7*G_)jGLPW zP_)@|`t_VI#C%ytB!_lUgYsJ?Mnu<;a}IT@=6C0A+P54EgBPfxue_WVm2>j8bPTzz z{@AQ;qlRNUW;nh8rZVk#7!PXUkHczj+~VUD_-{>Ax_%e_J*sRnFq_0*{GS|n0K(7U zk}2w|M}rvQm&ii2){3?3sj{!V(SQeeFij-vja0jB)Vv{@FGC(V1A#M z;G$&F8P;+PR+nCM*aNTFQhtP#afWY?6}EXzY$jee`#kJ%NRWBc1 z_akyPtt5VEb0=;b_ms`|=O1k6Y(Munc4#%~b?y0V@$z|}roniL{1sDhG0MwO;DRjw znPY)n<*u#G7MXLlYdV~&0Lefq6VtF83`Qh}UGkc+WLaYp;0-S5tNTZwi;fSDxes`j zBfY(6^QPdwWL3~;c+1nF1&j$tSgYWHKG9&*J9cjKpDfz9?1-j9dxVyz&CdZes)QqI zmN9lZ(8=-AH=;E^7sL-n#d_N)yz@hy4B#O!LrsarIeHs>cdu0>>Mdp2g^liyhirsN zeH<&`g3iO@NY^&d{IE$VQ(}XY(e-k*yqRGd2IktCTH(r;I$ar3+uF{~QvdbCEKodE zr})`Ve%dVnmKmRoKmqW8+u`#xO(ihG>eUh{zfU)yvS|dxfH>InKqnOIw_hVjC+u-& z1?y?mpc{v-hWdjVofaGJs-Cq^b`dLrg%b%XN{+LN1yICl@hW!sa2Ug4jAkvEZMkZM zWyxS|_FwXHu7m4DC}4Bw#6*O; zw5i^2zJ}3EN@FacZb_2#Yt zjl>*#Jf>Ah`Sy4c771{yLg(D3kh=VdHF_tuv=NLeDw?t=R~EW}f6ENklxtiwlzeHN z=+aCu8vDgXMIU_f`#|});8!e+iMsAb#aa3tM7P);;R^NsoVA9>)s#zIaxZP+A&z$s z%?JOIY`&^p6eeVn5Ob~8ICDB@-)r@?EaqbjxWcXQVi~<{dl-LSc^UUo3oH*|^e{vm zlL-Vr7z;)46HKhvpbw1S0wS7~r}7JY z>uuFvuq`#tI5*BbOKnQ#D=owxBTh@(Ej$}cgKL9Q0^MHu%>o#``nIkM1D{KG9WatU z_D-qi^-|#??q5T0sLQnk&soF$Ct|0-GIk=+E(k4f7pN!p&8l54&H#RNfwk~1`v8_x z&9{%j&dRXbNQ9A*{$6lDl1s%GgOuq7cd$3gtee=3A2SqC$tVN@EZD&x8E?~cHP|re z0ztuI8hrwq+U9GBNBV7`J8sAE$O^@dP_7Vbv9T7eCBjIjrZ5q@%tyf!d8<;rrQram zd_uU5My<--Rfo<_&Njk*`_+n96gzH*(^~BGV@wj<4#h!Eji9kFF+Cw}mrLo3Ie{W% z0ssX`T8L*KoXy9H)b$6k;X@KlSwqS{j0{kd z?4n$PNVG5TXhY#ztXA*Oo@!LuLoqN2kQHx)Yk!N2g|?8pT8v;5`WkP8A^FIXOo#ULLr>KBg>)|bm0z;8jX;2PYeZ5iM9A~1#Jb<>-3f39u(XN0F-eVCAw-jo;6{QubHsWC&4;>{rusxXw)F z3bE@5xD$q3=oc9_8^q3mh>t17c9gap?9$OeZg(B&lBeJb*#wG_UoO~vM>POl4eej} zm(#Zu*xn*`U97fyVkRiq|GSN`IiKF=fL$duB55i~quGAl1H|T4g0m*QcASqux*|id z+c85KJ|C%N^olm21qy~3!Ql#06Xe7Wc`aj9h01tHZALNZoKY_SG)kCMAgi5LwR6er z(;8MY!-39XJ>bu3$!w)AqSA1^ zI$?7Jo8z|dvl0H)Pn^xqv-WpQW7S_*$5Kyp#^;XZ`)m!KM+-&-xJA8Mae?alfE@&m z>qMshG}}h{kwZZOUdc1}umdzu;}mI(Y)4yyukWcI;9nMy@CeNWMYUa+h1;A0jP)Vs z9m=+=CT6;~rVvyTITMGtdkCnMG zRW{>oC>k4k8q4N}CCyY@P0p60bSz?0!fC#S&LQoRE+MavQtoM%jO*z}2 zSgb%|nt&fg3;nHzZjA?n)`EU(6z8ylwviLi?Jg#dkS)RbPvy+reE$fY-`pO-_Esy? ziRJ8weGM-}eI+BbWmv>rEFkDo|6l^X<%IYCK_fOGIjBl#YY|AMwD}#t((aEQ5`Qwx z!0F9kJeSCz{>^ZflVb;dBpQ`#o>n8;CVQu1HGE-~W}i3Q3@@splR2SC=ta<9IfXh0 zpl#xpr^}+)47E!Z$Lm3`@U5Ijx6U;@BYs)l7yUr;a#U4YbuA{bzTo;0gEgfGiE?-!sh zDMD}Wjz~>@Mf(ChD^vl5@+cy2l3RkyzMp=Dx>c3BWAc;C|L`ZkPyorho#ZIsm9o29 z#J;HYE$!<&j8<|jxk)gf-&2zniA?)M-9h>4wU_61D@QVhWc_eZ==SpViB@`BMKydZccXmMrrLy_Rz!+p+; zKu{Ao9Rz}?&>vy8ke7K0(tw-{DVxL8AK<=)%n3hy5t`5QWSFo^4`J!*vef-*b&qIW z`Dpks54X}tOz40X_2KbXT{HBh@%BQglQH;X=ea|Lp4f3zDCK$@3W#;=vtum~5~$3U zJfFO-hMY!WBv`v($VIcx6vE+Bbb3Q9wmjH%13 zBIH;Q{X(J(?*R78JOdP`tPUL)KEi3}Y>T6d?iVoeMM{U4Cb`jfU`1%qZcK`dPN`?5 zgVEAi@{!&%0y2AOK(>#4y6Jpg=GpCqrO_I<-A!Y`FQAzEt9Isp{;O;yBm2pH{!L_C z$#MBb+ETQZM|faT-SZBSzZT~0$H`bO;<^v1xem<8+D>{+WzP`}8etaAtfHGBH?C%_x11o%5%JaJ@UkSw`DFa3Zc(5XoYNRtIZdz9)uT4*MZCxOniq&NjYb<8T8TxY zr0t#M)b*TTdyL!ZK+d0(s6f}JhSw~-P%0(7z@O6wp-+qT-r&dtiAW`-S<}isxteiB zwDlAq?qvgmY!}=WX`OH=CH~6yku=@$)s(i%%`hv?bF5V(ykwtzMeGm}#Vja)FY$?w zT2v`ykC$sC8U6V!@DeVVzM0MBl16;S*fEDNK!D5^m?sG``eQPSL}@`V zX*9~GixY4fl!{4SizlkycP3UJnpr3+F!e}ux$%wMH$k63G}CE!j>G_1{x$zp8GHW$ zRF@l*fPPZ@(jQ^uK!muhb!4^_B#ELnY>r~@{{eaWvIr|XfI3zLJ_uvOhq{tp*Z*EM zv8{KM)k3o7%EU#}?PF86nG z^`ZteLhIc^vX5gd68(RbU0YaF*SZB1Q4!+>L_mmI6%`@gQ7(y>DqGo@dZ`EqsZs=_ znB4-B*2P*1ax+4h&QAdQze0lAt4t?TaQNW5`-ixyG4Y^TCK!cvXY+Zc{-2h zJme)0@WGnvpW`3@7~?-f(oB)VMcKL{$LbD807%%`jbz{qI#8?Wv+s18Ap*J=Nc>)w zH}=&qHGi)^6y3sM*8G#428KE&zE5Ky4(s|AkWm*i?#aE6t_9A-L2Djh7&E;!SLg1@ z_3stj<^{C{_>e_aw|LXPjDHo?b6)}tcu#MEuh~sbXdi&;!n4dE5UPe@@9+*Ah7I&Q zIi1(U791wTdB9L3z^@_oq));liCJ~jZY7&4C)zG_>!)v=TzP#us&o4~lPW>-XMP(Y`D5>cHLcpFW3Hvkyp+o>|5A6>>62N` zty8WCe6xR>t0(-RbSOO|KIZ6)wJ^+RyqF1TU1d1Sw7LroqW)>GM`_e_GOC1ia(o70 z{S#^>6`#`lc~4Zrxtn0_STrHjglScVqUset68l7fYJ*prl@66$_2AKbb--dGu<)(l zQ$KCI_KgQpNW8O#fLH>$F^s=C3UC^Ih)slQS&}A!Y;~pl!S%%34yo-B5Vt`IPGLH} zB-6y-8j{^YKS188rR7^B{TumZjC}?B3SuAk4f)hQa-uW>x*bH9V6>(Nd`TwrfjeA$naYmL_z8 zEkaS~WUBHs5GRwD;mLWJT25`aeo`@qb)t&@$RhJdCZkLSjbIV)YjgYOFGb@jsIuul z8L+bPzuwCcOIcGG5s~+KK`Mi!!onrGT8FmSh0KL09Cdpk8;tBZh;w5FtO=+`JosV2 z)LVmN3weAij||j!%sBX-O zj8tB6G|^NdH*;jMG7Z`PWma7j@t-(d$hXFTEZFH^hIh_|L`?Z1>h=muCO{EUmZ~Ui6k~pmk=U9?+i3TNXZ+|nDaz1%708!h9vtw*bh^wG`~e(C(@N#5e(I1f23O&8 z{JrqrpqA*HsD(S!^Px2F*MKdJq(V7(1`j9?VWo2O6%FEx$Ok!fyco)dT-s&L>o`t` zRpWg5rq1l~Y3UUQkk2|~o?invS81#E>z0My$Z*U*;}MiVh$dkl-wAT7K@|Kaje}zZ zhGPPG;0VVP-NLKp`YLVvkBp$RNfaV&of{R_$;!}OsGbA*$gC)0$18=0gyH$ir_2@L zuvt^>iTUzs+e0gzNKR?CSUNP~QS;)}Cv;D=!$2fpGm&W!rME)<;Rzu$sSpNeA!x=v zi~D;N$hZCkeKw)y+%jYRSZLO z=oZW(^`wsWtdXVOdI{LFzQQTmjiA3??^(s}!>#t8&&Vf{Pc2$3{$<$trB7l;OqGw$U5L zFUXgs>4&~cx%V^&j|?&m^QpPSJ8kTO=P!V+wb@WSzG-m&5gSJ>gv+Sa<34B)tm+X< z$ocqYF=jgi)x$cti5E2OCvvpvL^qsK#GIeTa>GgS`Daze1e#ykqySogjE1Qr4#vZ7 z8Al9~pAx^p#887rT5LsS{mI32cVr1X{`a9qfqNcdKtw`+5L=2aj3I^Ab+F@Xux$XT zYJ3xt(4M?5;T zg?RSpIuNLQqb|Y71b3ln@Sri)vYgz(EyA3sII-i|&{}&nvZ{Vm=ma&-($qZk~hIiv}|WLWHflDQI7)qT#ft1_^!eTo!5Djx4iZMLy2Dk9M)#B^-T% zW5IBH0@P;~;0zZSZg4_QvNepeP<5e{(f2l$&lKw-+rDmem2xM%0j1ET=8$nT+L%)iDD+T z#})w!jO+qxd(Pc^ji?`4(aJ4^U44ql!xv^*Ki_2AQhOqxp$1c~g>$_i{GUmIaQ%VV zdJ|G!S4VMgHO8-AW3Q08K|^X7TgmmOVnD1(F%!h9q1-g!ahA~!PVB6er|RzT<}2Ce z@nUElG?^4If?^yA_0S3;yz!}h$T7E4r!Be{&_l!%O104Lm~7lsp%S?Am}(Qe$J+A3 zcG4|=#%o#Db3~3oB`onN)H~h= zS3)o!+#T^M_c6g)-D7ReEHc(C|Ru#Q`-mu z95}!>(^bCiBuj!mfMEm~X1dygYtq2r12=&zhKN!^9-@z*O{2YH&2cP*INDTO!h~~E zlb`?!!{dnxjlvmBu%vHl$$+Z7+Kl+zV#dbBw2M>%{JBa3%a_xT9iRd#GK6z7se?xG zdAcXH>lU(7LZXE@4I*6YS>Rzi6gY;FH?997q`LDG;oK3p*v^FUla_#!dbcvkv_26M zXHt}$_PqeOQ7xT0{_7Qc6en2v$7;HC_igg(aI3G!&jd}S@T8bTk$`lSCS+I`ctE|G zosW6QLr-S$&|ZE2hrXOb3Ea%#Ls)wLg0Ah6 z`CNygp&EIRv_lpde5da7F?v&|5_$<2L4*2;YzeW4ax!=#;bgA$3!panv+(lAILn+d%W34s=9u4WxX9$Dw`+fyL0L${ zn5*rR5qM^EVG(*yKy4?ic?aPkmIhi9Nb!3i_Dr;gC83S+;c!Srr$d`nHgxC@ez47c zkLgA&BBLzM#>f9-3YZrWSO*Lr!kM-#Fqxd1{*|FfEZZhulyD~R$dobNdot>v;uD1n zj{&7&9%szINgTeQ1Ii1$0enrWcnR+rME_Cw3Mg=F$=C<+kw!3wHl63!3U68TQZk`C z)NK;r$ND6}A>88ASDT;Jng1b)WT+=CZ?5(7;B+k5=EL*TLK-Fj6qr+rt;22pex{0& zh@kqkq}hNEQ*V3qQSDE6y~XnT(9Ph1i7`C`b4M3qDs%4#c^{Y}6&l?_uzM8W{X^|O z;%M05@S7l90c+$8EvYqbYEr^W3#Xs2tiBCnosQcdJ6vDB#|}9~EEczi;zKoNjx%qkX2_#c=K2HgjFPzwW=2HSjrLZat_!}>c&+wnR^4lT zB$Q8Un`6iJ*givC$Ul%YyRgQ7l^Fsqj4i_^0 z)VE(_GvKevld$GfJAdl3FcFko7+-*eqst-Ae(aiq`GzUWOj4QJ-zg8>Y<;Am${*_573(*Iq&lPe|_ARn=Rpo zd#D-a#K7$&v(9)e_YhH0^48+r{?+no{@u}}xjo|SGXt67n#tWlJSH@buvqJ^Cq>V$ zKb9(fHF=+(m7DJAd%r60_@DYGRF>n{B$i#oI<9z8fQM=X=l*tDBny*Iv44+22YW-T zMU8Wd!xXL9xyE;NNuXt&EdeB$Eieh=z34sQ(hU&Z*7p@HKb45hm+h(tSIw}WER$t< z7vPt1x3-@|{~?n^n7yWX*W&Rb0|r&6y`EFEb~yRi~NsFkPu^E1t(Rr~|w^ z+0@do(ASIt*73icUc57DRXRV)1LWCOqtBSrKoGf3OvhO&nDR3ZWSDeCC@XJAue*CM zS$hs{CX55gZ7W`@^u6uLqLGS$F_B0_n~apLtJPtU1vg$}J>V3hbo4ph`3>uYAs<}@ zoXuSQ?qU2T<`j_trH1=N5ML#&duW%h_I;Yb;KJAzo=T~ag!O;g`j!?8a^oG2ibh{vPzBBZ? zdX3^G8E8>J05=sB!;KcTmZ0m@X~J6>$+CU{3p}K3IgRBCA_F4MbLecC0e6N>z=zug zXywfYjluOL68H6=?F~s3jhuFg_;qMNrH4F^uNrrKB!7gBgHxABrjh8t$_5vhJRgdk}|fO&LND zko-}1$fn4>nLEaJb$wPVzr0_bQTxr%QJ=_U$g&gsBN8&DNK7=C+Ut|ehM8&AQ^=mH zw~g2=Lp-mm)!`$EEmlMENJ`7IiZx95S`?eeX)Oy%O@qf68_QNla%0O?&jxdXlxyA0 zj8ZI}AqZFv8f?+LcX2S-XCcgI;SjObHi`YBMAskGMxTlJiNP8Cbg7sMAm}Cc`VNzs zZDoq~7g!pt^edfa28FI5%RjV|v-_Ta*Kx??wx@ShwvHYxhK))OS79m=^2N&4705zYbc zZlxUnoV}o5gsdc1)RCz%#7nDh(Up*bWVnB3yRfaXGNSUZ=!cQa+U@nXYhb?mUiGiG z<@T2wK84p)nEjGt@k!1MsnFl?MA)FXIKe%8H9o0|2t7c?D!IkJ_IJvb(4BeAnECra z0*tvi^_uH6GP$vN_vOu(Q%td)qU?T&XmiHO>sM7BxD!?U9Lr?u^Sc}~m)}WK%w?|1 zi06zwF|+k24c3#yf&!vTg*DLH#r+-90mIn`JUOkv;7P@qFz;82Mpmn0z(tMEimz_^ zA%nTR3rVp>yyVR^#B{8o)}`OgxGG=Y>&$}6a47FTZaB83-Em&;Q#64*n@q_ znu&H9+|Z3c&xXKM^6dIMID$-v@7N)GJxqjJa1wK3rIsa_-7J2e+kDWMzHUlxAvRsg z@fk79UNiBSH9KR-vqu(vTUQr9GWq*$V`8|b0($&nGmR|^o+z8S#hB;^BV3v)G^L)O z_*S;d#4VCL5q%|#1??T%?*mLJ_wSH?dmL2Yub}5MV2d;|Jf^XwoK+GK(bPmXm0b$_ znB?%U7i%hCDSD4b_c%E|b}Yb@hoEl)^@nyL#zo=WQ^7fYj0-Rzg|iTSG2}(MwQKD~ zYbbx>3GON=alrMTNx{GsB3&z_U1b@h7HXgSpvm__*qeI1N~hoOAs-JcnxIVsUp#4J zd5Rb?R|}j`3%P7KZ|veU=rrRO6*CZyEqO#JK?`8olYyFdP<@)^2?yI|Bd0(rf=Z0W zRIMnZmE|q-GDiyvp>#)a?#-ra2i1-7r|V5IN$|en?0+s>jw_}v43>Hvt0e^&6vrHk z&|k2JkXb#5?`tA1TTcQvLJAg)5K3X^*IR=?vRSCVtYW&^tI!!B3*vvR*a?{h zMPS1I(J09L*G2*7`{Hw4W>8Z6XspnEBL?X_dJnQw9Lk@?4D5;686cUw(o`G~IBV{U z`=vhLPWiLg`fpfg3(%gXBxaA?opSA?qQ&BJ%y>uW$x7kO23jlD#Y%~ZhTAtBi>MX$ z3)B=+eVAaV5_I~8_5gSLcR7sG1|;z(vgoqdfWTzMgZI*_{hrIROoQ&qN-aN|PaB)X zDdb3_D9@eN+AgLuZ?(Z%0=H!A@YDA1q0*W@7hMUotxQ*Yg?#I1(ngXsRt2|dYylgH z%HK^?BIcA&BBxs3`zfp4pt2`;BYRHhMcG`VJy)q8yb4`fvm7rlHOBz@6`E@5TOOjYWgjn}qqoX)^B=Q(36eFoWp>VG6-M$B-`o>(%O{DMVv&s)5??+n(R&%09S#`~JDy&u@%uIGX;)o;&@L#SmKi*qg9) zVR>&Le&wB|f~c@=APlX#7trqim+d3aq=Gsw6>LGZt(Z5m({?xwO7jCX?urW)0m1ph zIJ1IyYzd&VJW%g+g$vZN=oZ9*KN3bQ8ExpsY$>rCvHgJRAxlz~N<=ohR!dGIA;URsj#vGGsqLsjC$@QpCz6weYQ%7S$)J7Ot z!6?9HPh5*NN{fp0XI`CV&16i_XTLm3MLz8Kv@`p=Y(9I&lUoBLm(#!ctaqIDCG_u`!wMR)^5#mj{8x?Ay509-L+DI~MVbNVdo+>pN z=76k^IyWlj@h->qPKXX_d+E=XaC#taUSn)WQO|#NHsK&qJRY z{34P``d-K#IzhbIiq#PW{`>iaBG7^rzFHCg>JdI_}c1hS{|-klD^WU*LCCE5bfCrKm_>tOp14Ty^YhSTZDR!PUHq<6cdL@0lPOB)0Qs;l}?a+W)`q2-e^I{|(s<`P%>h literal 0 HcmV?d00001 diff --git a/spatfreq/fft2.bmp b/spatfreq/fft2.bmp new file mode 100644 index 0000000000000000000000000000000000000000..826909f3146620331f3c13ed09d4b44e0850acb4 GIT binary patch literal 17206 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�u3iA;$>Z#K&`dX-eE5XtOK%A&kd zp3y8b8iY&^hsc#mq%?u8Ol?p#_l#89L|g`)oEDv!O`?k}^`U%22ePt7rpAG8k5XlJ z*|baw)2OFz{A32E<^07id2v~8K^a?&>VFGD{U500ZJ8A(_YIHuEix8^$P^mgK8H*! zk%~m9&{tD^k&~00%hkyR1_M{EXYqAfp;(|6Gq_S~MY$7YCooh(Ar^zj${d|%LOGsS zz~pR&yLq_7#3svq4ugo#wa9a~u1!+)9~-bRu!J%UT_}}vN<#eMuApS|oH%I^oqxLY zTpE=r)>+JEsZgur3qn(1g29ra?0mXZ=R%UhVALDr3ObI!FJlnOXfhV1lt9Mdh*XZ; zR@-~y1$>U zp^BTkL*;EB=jBwTB*XQ6>_)&23Mi6whdSEii$7{ z9#3nvSmZD3KacNg(x)!pDJ-w^xnyD%kIfPbZ5EYUC_?63CXyh-Q}xT%Qn5my=9P$y zS~`QrC?k}Ta0G#wMIc}^3loZR32IOC!Q21BW<>vRX%@#`Q{UDa&|#8uNphWDL?O}9 zp7I8}Tg63bS*3i1#_jXkWz=HS|I26gT2t31Gxg;jm6*$9F*!1^!(r6RIYP0NFBI{( zROw$zzS+v>P>F1ZmW&fosA4LEQ<$4i$s({wc^I}x8W_3w6MRyJLfSQnSb?#!t+B4! zCCuKHMwJ^(A_|cv(>p3!Tb+cIfz~ zG}@FJ4VS^>(wRIShf7zhM{nI2veUD%QZ1*1iL`)*D`Qeia8J#Frs(qEgpp1}}UdmFd?KO1)ix3y8zdv_&uQ_gI zg3|A|qhq_0JQjtkH5gQKA(Kuf68StHo5Q6M1nR*v`^`B_XTXo*EuT=X607)ZIZ;kg zxSRG~MahrvW5MeSq6u_~%^R$)uJGF3W&wdhQB>8`7*Q;u)OiAKB}&Z}P}ow1)$4O9 zXnCRf=VQm(wQ(ym`BuMA#bx7h#ibmRQY8{H(McSIzz_@B3@)33E2gmd^io{KU|nCM z)?{+jxc$Md!3vwLVdQjZ&$-_R?}g>C^yTHjhU%JNz~}b5Z5+PP*WO-l7V?BLtJSF! zpy#r1G?q~8beS}KWWEhO^UG7+6jBGX7XDp$bch&Ws> zl}@FVW+vuHUCnJ)zq9A^iQ^CMUb-}V^1+j*cTQGCjCp0|cB;l)-GI*js;UDnU%+j1 zap{WcuAUZ?kk3cOfd(m(kt{ApC{bDsW-XhL6RLk}X!J{>H>4^&b+wf?HX%DBkIQ37 zg$yczK%%1xV=|w|=kjSRl~f_doV4gFUuelc3~AJ1Nw%yc*O z_B1rt`>Q<;i^XAd=s0|Rprt)%kO((*Lii-$hoyTr)==Cz0RxjhTxk8~tCe;|sI)ffrWj2FE z%GkMa@$`w4z8*hm#=K3@Daihj`EqMjd$6L$Z4mGU3<`nF67aBvRDsbTW7EiFog&-H%Jsp zwO+49=K{6Ob(Ix$0hdK9qu?@=5_UvH?M%xhas(=aMyb*}t13MS_NAEqjdAh$^uT++| z4edX9>fEKX7cL#yH_%|$>1-B*vAnLW_rRXfBUdgQ+1Jw)v}rY34U@%|seNvrv%JD) zFgs9uB4rTrQsU#{wrt+8WzFJG#?M^1Iy`dI=55J&RGr7`t3(@{%aNJOeL=6PDEC{$ zr;>8E%veEay^JD~f39^kR8+Mey!`Mthy*Nr{^Py#LoKyFx7}IM(mi)6>~iTN5m=s_twII%^zig-oTBh>4}yJ2r%^UAbiHQ{L%5x=M!cwi^ybq Y>O0yS90nOzq_zZt%{8igD@6SN2Po=LXaE2J literal 0 HcmV?d00001 diff --git a/spatfreq/filtimg1.m b/spatfreq/filtimg1.m new file mode 100644 index 0000000..af149a4 --- /dev/null +++ b/spatfreq/filtimg1.m @@ -0,0 +1,137 @@ +% This Matlab (version 4) program demonstrates how gif-images can be filtered. +% Two of Matlab's toolboxes are required: +% - the Signal Processing Toolbox +% - the Image Processing Toolbox +% A one-dimensional Finite Impulse Response filter ('fir1') is +% transformed to a two-dimensional FIR filter using the 'ftrans2' function. +% GIF-image 'original.gif' is filtered and saved as 'lowpass.gif', +% 'bandpass.gif' and 'highpass.gif' +% +% Author: Paul van Diepen - Matlab Filter Program for Full Color Images +% Scene Perception Research, October 1992 - April 1999 +% Laboratory for Experimental Psychology +% University of Leuven, Belgium +% http://psy.van-diepen.com/pvdmatl.html + +% FILTER PARAMETERS +f1 = 1; % lowpass cut-off (cycl/deg) +f2 = 3; % highpass cut-off (cycl/deg) +fnyquist = 23; % Nyquist frequency (pixels/deg) (half the sample frequency) +n = 50; % Filter order + +% LOAD ORIGINAL IMAGE +[X, map] = gifread('original.gif'); + +% SEPARATE Red, Green, AND Blue CHANNELS +[R, G, B] = ind2rgb(X,map); + +% TWO-DIMENSIONAL FOURIER TRANSFORM +FFTr = fft2(R); +FFTg = fft2(G); +FFTb = fft2(B); + +% STORE DC-COMPONENT (THE AVERAGE COLOUR VALUE) +DCr = FFTr(1,1); +DCg = FFTg(1,1); +DCb = FFTb(1,1); + +% REARRANGE QUADRANTS (THE DC-COMPONENT IS MOVED TO THE CENTRE OF THE SPECTRUM) +FFTr = fftshift(FFTr); +FFTg = fftshift(FFTg); +FFTb = fftshift(FFTb); + +% LOWPASS FILTER +% RED +lowFFTr = freqz2( ftrans2(fir1(n, f1/fnyquist)), size(FFTr)) .* FFTr; +lowFFTr = rot90(fftshift(rot90(lowFFTr,2)),2); % restore quadrant positions +lowFFTr(1,1) = DCr; % restore dc-component +lowR = real(ifft2(lowFFTr)); % inverse Fourier transform +d = find(lowR < 0); lowR(d) = zeros(size(d)); % remove negative intensities +d = find(lowR > 1); lowR(d) = ones(size(d)); % remove intensities above 1 + +% GREEN +lowFFTg = freqz2( ftrans2(fir1(n, f1/fnyquist)), size(FFTg)) .* FFTg; +lowFFTg = rot90(fftshift(rot90(lowFFTg,2)),2); % restore quadrant positions +lowFFTg(1,1) = DCg; % restore dc-component +lowG = real(ifft2(lowFFTg)); % inverse Fourier transform +d = find(lowG < 0); lowG(d) = zeros(size(d)); % remove negative intensities +d = find(lowG > 1); lowG(d) = ones(size(d)); % remove intensities above 1 + +% BLUE +lowFFTb = freqz2( ftrans2(fir1(n, f1/fnyquist)), size(FFTb)) .* FFTb; +lowFFTb = rot90(fftshift(rot90(lowFFTb,2)),2); % restore quadrant positions +lowFFTb(1,1) = DCb; % restore dc-component +lowB = real(ifft2(lowFFTb)); % inverse Fourier transform +d = find(lowB < 0); lowB(d) = zeros(size(d)); % remove negative intensities +d = find(lowB > 1); lowB(d) = ones(size(d)); % remove intensities above 1 + +% COMBINE COLOURS TO INDEXED IMAGE +[X, map] = rgb2ind(lowR, lowG, lowB, 256); + +% STORE IMAGE +gifwrite(X, map, 'lowpass.gif'); + + +% BANDPASS FILTER +% RED +midFFTr = freqz2( ftrans2(fir1(n, [f1,f2]/fnyquist)), size(FFTr)) .* FFTr; +midFFTr = rot90(fftshift(rot90(midFFTr,2)),2); % restore quadrant positions +midFFTr(1,1) = DCr; % restore dc-component +midR = real(ifft2(midFFTr)); % inverse Fourier transform +d = find(midR < 0); midR(d) = zeros(size(d)); % remove negative intensities +d = find(midR > 1); midR(d) = ones(size(d)); % remove intensities above 1 + +% GREEN +midFFTg = freqz2( ftrans2(fir1(n, [f1,f2]/fnyquist)), size(FFTg)) .* FFTg; +midFFTg = rot90(fftshift(rot90(midFFTg,2)),2); % restore quadrant positions +midFFTg(1,1) = DCg; % restore dc-component +midG = real(ifft2(midFFTg)); % inverse Fourier transform +d = find(midG < 0); midG(d) = zeros(size(d)); % remove negative intensities +d = find(midG > 1); midG(d) = ones(size(d)); % remove intensities above 1 + +% BLUE +midFFTb = freqz2( ftrans2(fir1(n, [f1,f2]/fnyquist)), size(FFTb)) .* FFTb; +midFFTb = rot90(fftshift(rot90(midFFTb,2)),2); % restore quadrant positions +midFFTb(1,1) = DCb; % restore dc-component +midB = real(ifft2(midFFTb)); % inverse Fourier transform +d = find(midB < 0); midB(d) = zeros(size(d)); % remove negative intensities +d = find(midB > 1); midB(d) = ones(size(d)); % remove intensities above 1 + +% COMBINE COLOURS TO INDEXED IMAGE +[X, map] = rgb2ind(midR, midG, midB, 256); + +% STORE IMAGE +gifwrite(X, map, 'bandpass.gif'); + +% HIGHPASS FILTER +% RED +highFFTr = freqz2( ftrans2(fir1(n, f2/fnyquist, 'high')), size(FFTr)) .* FFTr; +highFFTr = rot90(fftshift(rot90(highFFTr,2)),2); % restore quadrant positions +highFFTr(1,1) = DCr; % restore dc-component +highR = real(ifft2(highFFTr)); % inverse Fourier transform +d = find(highR < 0); highR(d) = zeros(size(d)); % remove negative intensities +d = find(highR > 1); highR(d) = ones(size(d)); % remove intensities above 1 + +% GREEN +highFFTg = freqz2( ftrans2(fir1(n, f2/fnyquist, 'high')), size(FFTg)) .* FFTg; +highFFTg = rot90(fftshift(rot90(highFFTg,2)),2); % restore quadrant positions +highFFTg(1,1) = DCg; % restore dc-component +highG = real(ifft2(highFFTg)); % inverse Fourier transform +d = find(highG < 0); highG(d) = zeros(size(d)); % remove negative intensities +d = find(highG > 1); highG(d) = ones(size(d)); % remove intensities above 1 + +% BLUE +highFFTb = freqz2( ftrans2(fir1(n, f2/fnyquist, 'high')), size(FFTb)) .* FFTb; +highFFTb = rot90(fftshift(rot90(highFFTb,2)),2); % restore quadrant positions +highFFTb(1,1) = DCb; % restore dc-component +highB = real(ifft2(highFFTb)); % inverse Fourier transform +d = find(highB < 0); highB(d) = zeros(size(d)); % remove negative intensities +d = find(highB > 1); highB(d) = ones(size(d)); % remove intensities above 1 + +% COMBINE COLOURS TO INDEXED IMAGE +[X, map] = rgb2ind(highR, highG, highB, 256); + +% STORE IMAGE +gifwrite(X, map, 'highpass.gif'); + +% (p) 1996, Paul van Diepen, Laboratory of Experimental Psychology, Leuven, Belgium \ No newline at end of file diff --git a/spatfreq/lena_bw.bmp b/spatfreq/lena_bw.bmp new file mode 100644 index 0000000000000000000000000000000000000000..fb0a9d04e0fa1db41bf81f5fe886587f60e33fc8 GIT binary patch literal 66614 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


    1q(a~%UVFLBd+zI2yJ+J>;RWQ zU)Yxrm$WTCe)0OvyLT@hvf7GW!XQ*oH-wLJtSQ%?Z@dw-Z~vi#;KN9Vjvx7wD-^Nv zT?46~Z}tHX$mX1;nlhIrF)a9UXq1dRuqXiw0Pky3Y8rj+YWt^Z?$Rv?Gc6FhF*e6$B zPQ*zTGO?1Bk{FdxR$|KWMVt^UW; zl^O~FzHkj=|LN&PY$XEDu{ls{qyX863ZHQ1nZ+m4NhTspy=cCG39PVDEfBxp}o1K}$ zIO-H&Q8Bw%Qh_N+fx(fVZ;m+g?NNW~e?SBSgD{*Z5A?rhiLr)izF;=tS!sS zNs9~(gZw5gDV+v@?0;eTN3yT|O%$U?u6a`|7#AS=jKM>qMgCYSl4IGU+mGow^nMx47bhrvF5vSNda9EI`0YMlK z3@A2`C0U#w@D~f?d=&uVpFE55Hq`$>KN8bX|HMSuffr9bB9r=$RH|^}oP{%p-9zT( zF!W{+eETO$$fq23-rDKpt6)PE|3HAi|8al|;FpM!Y)%Bg_elcc90?wArc{#+IaxL+z@WJ4StDlkkc?!;Ty(9G!pe?gkuQeR!1Z&uv8!SXl3x5}0f zv;YZ6raMEyh8xxEsq`T5v^k~AjnH!%`MJ|hiwgNhzj3?#3ZLY}m_;j}moU-ca^*R> zCN9Y7!t@~}kl|r7`9iBvf*|5Qm68=J3Go>^(BJIF0J z#*uKo%SFts4hMZen^WQ<;sOa1oO$9IL`%3Mo1_dpJl0Oi#qYm7?1dAqyWZ@^e(fY1~zB~KM2NDxMxEjr+G z8gG5caP(nV62*<;Kzyo1F4fpVHIV*4#QEeu4t@Hq8{y$mguP7sQ~4zLiG?Us6iX{X zrSKeM?>W5)wwN^e$aTrNZ4NZtYUO)O9A}gd^qrsb2;7m=CCx$K@8i(KLdcTX2J9y) zK)jr-LC;tSoT{<8%c?)oH;7{*Tf_hJua;>bNqu~C5mbTTiFx+w%^QrLhj1;8^V5k( z{7JchXdnj|Rop8onrDpt`$00mqX8UWoam<;Agc!c4F0DF3|L#O$CaJTR!DX~fJ0#D zlU_6hfVgB<|47$gCnZZnmP8LF7YRp*<$Z13@TZ3tdCwKg@JZ5f6Y}7^vdsx5LWEyF ze3=rzBo#z40bBz`MLpH8^o;bRxJ<#h)ZqS_r9vSgAli?_68K0Bj1OV+tgNgXm(b_U zn^(&%neoXWv?SRSfKE2~mgLizD@Id@LCp?FPTEhW4nYyZpC}FhS4uWTF~!@!{4ewW zy`f-lb~-*FG3 z>rOj>FS{*IgkLP4_y>d^VUc(T85gnV+~O(3BjWryy@b9#@8~NhkozjqQxcN0j83us z#Jp5Jumbpe`PX9i*ovAsZyf^nv3_BE(LXJf={D-j}`R& zJ*b)~{X8o}38sjDsHh@CVCxvKWdRxtP|#mCR5*2Ma{gO1eZ=z<`qTYNq##B1Cmmk{ z>6~QzJgF50K(WwzjGxa4iqfMB$N(3>50C*%y~F9EL@u94Pq-9*nQNhP%D;%DfUCqi z5F{FOdW}*+#*wAZg-D4qN3#(2QvTwtIWv||>R-NA0QbQ!4Xr+T@$&VXSDSYXG4YI| z0?rq`=MN(OmH5-37X=^;CiWyB0kC%K$3TdYWgiFzz*kb@TAPFb1^WN4rb@RyJ^Cgq zFMQcwB5)M&2*hJ)3Q`kpQzCVFTZv-{dBv%7bSLb;PCn$&;tKdrzPWs{4$sc{d3K~k zP#lgt=cniin`h_z$U-hxK^DxjDKk^!lT~IHqi>@9)O`6s>^Uwx02IHy$SeYZHq=&k zE^I%0`TF&no!OGaSeCI-S~U=)Qgzfw`or-h2&d|X_>+#P)!D|f=4vH%-cReYn-MDc zqnUr~gaNA${*$T-lbj#KFF+L9o{aqw`%_Sq%f+RTrSjkeHx7=KBWlc}iqrGCJKh!( z$H84Lr~JnkaalwGxC7t45EocoMNX2yT!$3P2!lkE#1UF3f9SH6ndxz13 zL!|;#2X_VX&hbISas|XMjaY;3rAKo9*N?|ssA?K7q&I*=(Mug20WjjOsKRI^#YDZR hEvd7s?+ljxZ_i);|EKdW{`>sndH<*9uj=jd{J&h0BN6}r literal 0 HcmV?d00001 diff --git a/spatfreq/lena_bw.gif b/spatfreq/lena_bw.gif new file mode 100644 index 0000000000000000000000000000000000000000..eea0233661c82b72413cf18f7e6c292afda51e78 GIT binary patch literal 69732 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

    GM$m7Jpl{;%tQxT&h2sNW|Hs1Md$xk(^QlQI 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 literal 0 HcmV?d00001 diff --git a/spm/nii_for_spm2/spm_conv_vol.mexmaci b/spm/nii_for_spm2/spm_conv_vol.mexmaci new file mode 100644 index 0000000000000000000000000000000000000000..855be1778d75244eba491d1ac199645886ba30bf GIT binary patch literal 258416 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 literal 0 HcmV?d00001 diff --git a/spm/nii_for_spm2/spm_conv_vol.mexs64 b/spm/nii_for_spm2/spm_conv_vol.mexs64 new file mode 100644 index 0000000000000000000000000000000000000000..49d8bdf942c827aec2686c892df49031f6193fe2 GIT binary patch literal 270568 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 H*ZzM4*KWqK literal 0 HcmV?d00001 diff --git a/spm/nii_for_spm2/spm_conv_vol.mexsol b/spm/nii_for_spm2/spm_conv_vol.mexsol new file mode 100755 index 0000000000000000000000000000000000000000..685a529a0b8574df466e743cc3234666851fece5 GIT binary patch literal 245780 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 z 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 F_rKSBP)q;- literal 0 HcmV?d00001 diff --git a/spm/nii_for_spm2/spm_conv_vol.mexw32 b/spm/nii_for_spm2/spm_conv_vol.mexw32 new file mode 100755 index 0000000000000000000000000000000000000000..af65af32484c8c261077c1652cab3fe3ec872cc0 GIT binary patch literal 221696 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

    M3$OKXxw33iE+epLxiGHDwxJ6J%`qD1q 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

    v0auKTf`VX{JbILI^hnWu=TKD=&F<8*R=x@W5 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

    h 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 I#d_es0q8B1t^fc4 literal 0 HcmV?d00001 diff --git a/spm/nii_for_spm2/spm_ecat2nifti.m b/spm/nii_for_spm2/spm_ecat2nifti.m new file mode 100755 index 0000000..c9cc917 --- /dev/null +++ b/spm/nii_for_spm2/spm_ecat2nifti.m @@ -0,0 +1,406 @@ +function N = spm_ecat2nifti(fname,opts) +% Import ECAT 7 images from CTI PET scanners. +% FORMAT N = spm_ecat2nifti(fname) +% fname - name of ECAT file +% _______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% John Ashburner & Roger Gunn +% $Id: spm_ecat2nifti.m 112 2005-05-04 18:20:52Z john $ + + +if nargin==1, + opts = struct('ext','.nii'); +end; + +fp = fopen(fname,'r','ieee-be'); +if fp == -1, + error(['Can''t open "' fname '".']); + return; +end + +mh = ECAT7_mheader(fp); +if ~strcmp(mh.MAGIC_NUMBER,'MATRIX70v') &&... + ~strcmp(mh.MAGIC_NUMBER,'MATRIX71v') &&... + ~strcmp(mh.MAGIC_NUMBER,'MATRIX72v'), + error(['"' fname '" does not appear to be ECAT 7 format.']); + fclose(fp); + return; +end + +if mh.FILE_TYPE ~= 7, + error(['"' fname '" does not appear to be an image file.']); + fclose(fp); + return; +end + +list = s7_matlist(fp); +matches = find((list(:,4) == 1) | (list(:,4) == 2)); +llist = list(matches,:); + +for i=1:size(llist,1), + sh(i) = ECAT7_sheader(fp,llist(i,2)); +end; +fclose(fp); + +for i=1:size(llist,1), + dim = [sh(i).X_DIMENSION sh(i).Y_DIMENSION sh(i).Z_DIMENSION]; + dtype = [4 1]; + off = 512*llist(i,2); + scale = sh(i).SCALE_FACTOR*mh.ECAT_CALIBRATION_FACTOR; + inter = 0; + dati = file_array(fname,dim,dtype,off,scale,inter); + + dircos = diag([-1 -1 -1]); + step = ([sh(i).X_PIXEL_SIZE sh(i).Y_PIXEL_SIZE sh(i).Z_PIXEL_SIZE]*10); + start = -(dim(1:3)'/2).*step'; + mat = [[dircos*diag(step) dircos*start] ; [0 0 0 1]]; + + matnum = sprintf('%.8x',list(i,1)); + [pth,nam,ext] = fileparts(fname); + fnameo = fullfile(pwd,[nam '_' matnum opts.ext]); + dato = file_array(fnameo,dim,[4 spm_platform('bigend')],0,scale,inter); + + N = nifti; + N.dat = dato; + N.mat = mat; + N.mat0 = mat; + N.mat_intent = 'aligned'; + N.mat0_intent = 'scanner'; + N.descrip = sh(i).ANNOTATION; + N.timing = struct('toffset',sh(i).FRAME_START_TIME/1000,'tspace',sh(i).FRAME_DURATION/1000); + create(N); + for j=1:dim(3), + N.dat(:,:,j) = dati(:,:,j); + end; + N.extras = struct('mh',mh,'sh',sh(i)); +end; +return; +%_______________________________________________________________________ + +%_______________________________________________________________________ +%S7_MATLIST List the available matrixes in an ECAT 7 file. +% LIST = S7_MATLIST(FP) lists the available matrixes +% in the file specified by FP. +% +% Columns in LIST: +% 1 - Matrix identifier. +% 2 - Matrix subheader record number +% 3 - Last record number of matrix data block. +% 4 - Matrix status: +% 1 - exists - rw +% 2 - exists - ro +% 3 - matrix deleted +% +function list = s7_matlist(fp) + +% I believe fp should be opened with: +% fp = fopen(filename,'r','ieee-be'); + +fseek(fp,512,'bof'); +block = fread(fp,128,'int'); +if size(block,1) ~= 128 + list = []; + return; +end; +block = reshape(block,4,32); +list = []; +while block(2,1) ~= 2, + if block(1,1)+block(4,1) ~= 31, + list = []; return; + end; + list = [list block(:,2:32)]; + + fseek(fp,512*(block(2,1)-1),'bof'); + block = fread(fp,128,'int'); + if size(block,1) ~= 128, list = []; return; end; + block = reshape(block,4,32); +end +list = [list block(:,2:(block(4,1)+1))]; +list = list'; +return; +%_______________________________________________________________________ + +%_______________________________________________________________________ +function SHEADER=ECAT7_sheader(fid,record) +% +% Sub header read routine for ECAT 7 image files +% +% Roger Gunn, 260298 +off = (record-1)*512; +status = fseek(fid, off,'bof'); +data_type = fread(fid,1,'uint16',0); +num_dimensions = fread(fid,1,'uint16',0); +x_dimension = fread(fid,1,'uint16',0); +y_dimension = fread(fid,1,'uint16',0); +z_dimension = fread(fid,1,'uint16',0); +x_offset = fread(fid,1,'float32',0); +y_offset = fread(fid,1,'float32',0); +z_offset = fread(fid,1,'float32',0); +recon_zoom = fread(fid,1,'float32',0); +scale_factor = fread(fid,1,'float32',0); +image_min = fread(fid,1,'int16',0); +image_max = fread(fid,1,'int16',0); +x_pixel_size = fread(fid,1,'float32',0); +y_pixel_size = fread(fid,1,'float32',0); +z_pixel_size = fread(fid,1,'float32',0); +frame_duration = fread(fid,1,'uint32',0); +frame_start_time = fread(fid,1,'uint32',0); +filter_code = fread(fid,1,'uint16',0); +x_resolution = fread(fid,1,'float32',0); +y_resolution = fread(fid,1,'float32',0); +z_resolution = fread(fid,1,'float32',0); +num_r_elements = fread(fid,1,'float32',0); +num_angles = fread(fid,1,'float32',0); +z_rotation_angle = fread(fid,1,'float32',0); +decay_corr_fctr = fread(fid,1,'float32',0); +corrections_applied = fread(fid,1,'uint32',0); +gate_duration = fread(fid,1,'uint32',0); +r_wave_offset = fread(fid,1,'uint32',0); +num_accepted_beats = fread(fid,1,'uint32',0); +filter_cutoff_frequency = fread(fid,1,'float32',0); +filter_resolution = fread(fid,1,'float32',0); +filter_ramp_slope = fread(fid,1,'float32',0); +filter_order = fread(fid,1,'uint16',0); +filter_scatter_fraction = fread(fid,1,'float32',0); +filter_scatter_slope = fread(fid,1,'float32',0); +annotation = fread(fid,40,'char',0); +mt_1_1 = fread(fid,1,'float32',0); +mt_1_2 = fread(fid,1,'float32',0); +mt_1_3 = fread(fid,1,'float32',0); +mt_2_1 = fread(fid,1,'float32',0); +mt_2_2 = fread(fid,1,'float32',0); +mt_2_3 = fread(fid,1,'float32',0); +mt_3_1 = fread(fid,1,'float32',0); +mt_3_2 = fread(fid,1,'float32',0); +mt_3_3 = fread(fid,1,'float32',0); +rfilter_cutoff = fread(fid,1,'float32',0); +rfilter_resolution = fread(fid,1,'float32',0); +rfilter_code = fread(fid,1,'uint16',0); +rfilter_order = fread(fid,1,'uint16',0); +zfilter_cutoff = fread(fid,1,'float32',0); +zfilter_resolution = fread(fid,1,'float32',0); +zfilter_code = fread(fid,1,'uint16',0); +zfilter_order = fread(fid,1,'uint16',0); +mt_4_1 = fread(fid,1,'float32',0); +mt_4_2 = fread(fid,1,'float32',0); +mt_4_3 = fread(fid,1,'float32',0); +scatter_type = fread(fid,1,'uint16',0); +recon_type = fread(fid,1,'uint16',0); +recon_views = fread(fid,1,'uint16',0); +fill = fread(fid,1,'uint16',0); +annotation = deblank(char(annotation.*(annotation>0))'); + +SHEADER = struct('DATA_TYPE', data_type, ... + 'NUM_DIMENSIONS', num_dimensions, ... + 'X_DIMENSION', x_dimension, ... + 'Y_DIMENSION', y_dimension, ... + 'Z_DIMENSION', z_dimension, ... + 'X_OFFSET', x_offset, ... + 'Y_OFFSET', y_offset, ... + 'Z_OFFSET', z_offset, ... + 'RECON_ZOOM', recon_zoom, ... + 'SCALE_FACTOR', scale_factor, ... + 'IMAGE_MIN', image_min, ... + 'IMAGE_MAX', image_max, ... + 'X_PIXEL_SIZE', x_pixel_size, ... + 'Y_PIXEL_SIZE', y_pixel_size, ... + 'Z_PIXEL_SIZE', z_pixel_size, ... + 'FRAME_DURATION', frame_duration, ... + 'FRAME_START_TIME', frame_start_time, ... + 'FILTER_CODE', filter_code, ... + 'X_RESOLUTION', x_resolution, ... + 'Y_RESOLUTION', y_resolution, ... + 'Z_RESOLUTION', z_resolution, ... + 'NUM_R_ELEMENTS', num_r_elements, ... + 'NUM_ANGLES', num_angles, ... + 'Z_ROTATION_ANGLE', z_rotation_angle, ... + 'DECAY_CORR_FCTR', decay_corr_fctr, ... + 'CORRECTIONS_APPLIED', corrections_applied, ... + 'GATE_DURATION', gate_duration, ... + 'R_WAVE_OFFSET', r_wave_offset, ... + 'NUM_ACCEPTED_BEATS', num_accepted_beats, ... + 'FILTER_CUTOFF_FREQUENCY', filter_cutoff_frequency, ... + 'FILTER_RESOLUTION', filter_resolution, ... + 'FILTER_RAMP_SLOPE', filter_ramp_slope, ... + 'FILTER_ORDER', filter_order, ... + 'FILTER_SCATTER_CORRECTION', filter_scatter_fraction, ... + 'FILTER_SCATTER_SLOPE', filter_scatter_slope, ... + 'ANNOTATION', annotation, ... + 'MT_1_1', mt_1_1, ... + 'MT_1_2', mt_1_2, ... + 'MT_1_3', mt_1_3, ... + 'MT_2_1', mt_2_1, ... + 'MT_2_2', mt_2_2, ... + 'MT_2_3', mt_2_3, ... + 'MT_3_1', mt_3_1, ... + 'MT_3_2', mt_3_2, ... + 'MT_3_3', mt_3_3, ... + 'RFILTER_CUTOFF', rfilter_cutoff, ... + 'RFILTER_RESOLUTION', rfilter_resolution, ... + 'RFILTER_CODE', rfilter_code, ... + 'RFILTER_ORDER', rfilter_order, ... + 'ZFILTER_CUTOFF', zfilter_cutoff, ... + 'ZFILTER_RESOLUTION', zfilter_resolution, ... + 'ZFILTER_CODE', zfilter_code, ... + 'ZFILTER_ORDER', zfilter_order, ... + 'MT_4_1', mt_4_1, ... + 'MT_4_2', mt_4_2, ... + 'MT_4_3', mt_4_3, ... + 'SCATTER_TYPE', scatter_type, ... + 'RECON_TYPE', recon_type, ... + 'RECON_VIEWS', recon_views, ... + 'FILL', fill); +return; +%_______________________________________________________________________ +function [MHEADER]=ECAT7_mheader(fid) +% +% Main header read routine for ECAT 7 image files +% +% Roger Gunn, 260298 + +status = fseek(fid, 0,'bof'); +magic_number = fread(fid,14,'char',0); +original_file_name = fread(fid,32,'char',0); +sw_version = fread(fid,1,'uint16',0); +system_type = fread(fid,1,'uint16',0); +file_type = fread(fid,1,'uint16',0); +serial_number = fread(fid,10,'char',0); +scan_start_time = fread(fid,1,'uint32',0); +isotope_name = fread(fid,8,'char',0); +isotope_halflife = fread(fid,1,'float32',0); +radiopharmaceutical = fread(fid,32,'char',0); +gantry_tilt = fread(fid,1,'float32',0); +gantry_rotation = fread(fid,1,'float32',0); +bed_elevation = fread(fid,1,'float32',0); +intrinsic_tilt = fread(fid,1,'float32',0); +wobble_speed = fread(fid,1,'uint16',0); +transm_source_type = fread(fid,1,'uint16',0); +distance_scanned = fread(fid,1,'float32',0); +transaxial_fov = fread(fid,1,'float32',0); +angular_compression = fread(fid,1,'uint16',0); +coin_samp_mode = fread(fid,1,'uint16',0); +axial_samp_mode = fread(fid,1,'uint16',0); +ecat_calibration_factor = fread(fid,1,'float32',0); +calibration_units = fread(fid,1,'uint16',0); +calibration_units_type = fread(fid,1,'uint16',0); +compression_code = fread(fid,1,'uint16',0); +study_type = fread(fid,12,'char',0); +patient_id = fread(fid,16,'char',0); +patient_name = fread(fid,32,'char',0); +patient_sex = fread(fid,1,'char',0); +patient_dexterity = fread(fid,1,'char',0); +patient_age = fread(fid,1,'float32',0); +patient_height = fread(fid,1,'float32',0); +patient_weight = fread(fid,1,'float32',0); +patient_birth_date = fread(fid,1,'uint32',0); +physician_name = fread(fid,32,'char',0); +operator_name = fread(fid,32,'char',0); +study_description = fread(fid,32,'char',0); +acquisition_type = fread(fid,1,'uint16',0); +patient_orientation = fread(fid,1,'uint16',0); +facility_name = fread(fid,20,'char',0); +num_planes = fread(fid,1,'uint16',0); +num_frames = fread(fid,1,'uint16',0); +num_gates = fread(fid,1,'uint16',0); +num_bed_pos = fread(fid,1,'uint16',0); +init_bed_position = fread(fid,1,'float32',0); +bed_position = zeros(15,1); +for bed=1:15, + tmp = fread(fid,1,'float32',0); + if ~isempty(tmp), bed_position(bed) = tmp; end; +end; +plane_separation = fread(fid,1,'float32',0); +lwr_sctr_thres = fread(fid,1,'uint16',0); +lwr_true_thres = fread(fid,1,'uint16',0); +upr_true_thres = fread(fid,1,'uint16',0); +user_process_code = fread(fid,10,'char',0); +acquisition_mode = fread(fid,1,'uint16',0); +bin_size = fread(fid,1,'float32',0); +branching_fraction = fread(fid,1,'float32',0); +dose_start_time = fread(fid,1,'uint32',0); +dosage = fread(fid,1,'float32',0); +well_counter_corr_factor = fread(fid,1,'float32',0); +data_units = fread(fid,32,'char',0); +septa_state = fread(fid,1,'uint16',0); +fill = fread(fid,1,'uint16',0); + +magic_number = deblank(char(magic_number.*(magic_number>32))'); +original_file_name = deblank(char(original_file_name.*(original_file_name>0))'); +serial_number = deblank(char(serial_number.*(serial_number>0))'); +isotope_name = deblank(char(isotope_name.*(isotope_name>0))'); +radiopharmaceutical = deblank(char(radiopharmaceutical.*(radiopharmaceutical>0))'); +study_type = deblank(char(study_type.*(study_type>0))'); +patient_id = deblank(char(patient_id.*(patient_id>0))'); +patient_name = deblank(char(patient_name.*(patient_name>0))'); +patient_sex = deblank(char(patient_sex.*(patient_sex>0))'); +patient_dexterity = deblank(char(patient_dexterity.*(patient_dexterity>0))'); +physician_name = deblank(char(physician_name.*(physician_name>0))'); +operator_name = deblank(char(operator_name.*(operator_name>0))'); +study_description = deblank(char(study_description.*(study_description>0))'); +facility_name = deblank(char(facility_name.*(facility_name>0))'); +user_process_code = deblank(char(user_process_code.*(user_process_code>0))'); +data_units = deblank(char(data_units.*(data_units>0))'); + +MHEADER = struct('MAGIC_NUMBER', magic_number, ... + 'ORIGINAL_FILE_NAME', original_file_name, ... + 'SW_VERSION', sw_version, ... + 'SYSTEM_TYPE', system_type, ... + 'FILE_TYPE', file_type, ... + 'SERIAL_NUMBER', serial_number, ... + 'SCAN_START_TIME', scan_start_time, ... + 'ISOTOPE_NAME', isotope_name, ... + 'ISOTOPE_HALFLIFE', isotope_halflife, ... + 'RADIOPHARMACEUTICAL', radiopharmaceutical, ... + 'GANTRY_TILT', gantry_tilt, ... + 'GANTRY_ROTATION', gantry_rotation, ... + 'BED_ELEVATION', bed_elevation, ... + 'INTRINSIC_TILT', intrinsic_tilt, ... + 'WOBBLE_SPEED', wobble_speed, ... + 'TRANSM_SOURCE_TYPE', transm_source_type, ... + 'DISTANCE_SCANNED', distance_scanned, ... + 'TRANSAXIAL_FOV', transaxial_fov, ... + 'ANGULAR_COMPRESSION', angular_compression, ... + 'COIN_SAMP_MODE', coin_samp_mode, ... + 'AXIAL_SAMP_MODE', axial_samp_mode, ... + 'ECAT_CALIBRATION_FACTOR', ecat_calibration_factor, ... + 'CALIBRATION_UNITS', calibration_units, ... + 'CALIBRATION_UNITS_TYPE', calibration_units_type, ... + 'COMPRESSION_CODE', compression_code, ... + 'STUDY_TYPE', study_type, ... + 'PATIENT_ID', patient_id, ... + 'PATIENT_NAME', patient_name, ... + 'PATIENT_SEX', patient_sex, ... + 'PATIENT_DEXTERITY', patient_dexterity, ... + 'PATIENT_AGE', patient_age, ... + 'PATIENT_HEIGHT', patient_height, ... + 'PATIENT_WEIGHT', patient_weight, ... + 'PATIENT_BIRTH_DATE', patient_birth_date, ... + 'PHYSICIAN_NAME', physician_name, ... + 'OPERATOR_NAME', operator_name, ... + 'STUDY_DESCRIPTION', study_description, ... + 'ACQUISITION_TYPE', acquisition_type, ... + 'PATIENT_ORIENTATION', patient_orientation, ... + 'FACILITY_NAME', facility_name, ... + 'NUM_PLANES', num_planes, ... + 'NUM_FRAMES', num_frames, ... + 'NUM_GATES', num_gates, ... + 'NUM_BED_POS', num_bed_pos, ... + 'INIT_BED_POSITION', init_bed_position, ... + 'BED_POSITION', bed_position, ... + 'PLANE_SEPARATION', plane_separation, ... + 'LWR_SCTR_THRES', lwr_sctr_thres, ... + 'LWR_TRUE_THRES', lwr_true_thres, ... + 'UPR_TRUE_THRES', upr_true_thres, ... + 'USER_PROCESS_CODE', user_process_code, ... + 'ACQUISITION_MODE', acquisition_mode, ... + 'BIN_SIZE', bin_size, ... + 'BRANCHING_FRACTION', branching_fraction, ... + 'DOSE_START_TIME', dose_start_time, ... + 'DOSAGE', dosage, ... + 'WELL_COUNTER_CORR_FACTOR', well_counter_corr_factor, ... + 'DATA_UNITS', data_units, ... + 'SEPTA_STATE', septa_state, ... + 'FILL', fill); +return; +%_______________________________________________________________________ diff --git a/spm/nii_for_spm2/spm_eeg_marry_scalp_head_quick.m b/spm/nii_for_spm2/spm_eeg_marry_scalp_head_quick.m new file mode 100755 index 0000000..30860a2 --- /dev/null +++ b/spm/nii_for_spm2/spm_eeg_marry_scalp_head_quick.m @@ -0,0 +1,16 @@ +function [col_scalp]=marry_scalp_head_quick(V,C,V3,elec_verts,dv,id); +%_______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% James Kilner +% $Id: spm_eeg_marry_scalp_head_quick.m 112 2005-05-04 18:20:52Z john $ + +col_scalp=zeros(length(dv),1); +P = fullfile(spm('dir'), 'EEGtemplates'); +load(fullfile(P, 'params2.mat')); +col_scalp(col_scalp==0)=NaN; +allid(allid(:,1)==0,:)=''; +alltds(alltds(:,1)==0,:)=''; +alli(alli(:,1)==0,:)=''; + +col_scalp(allid)=sum((alltds(:,:).*C(alli(:,1:10))),2); diff --git a/spm/nii_for_spm2/spm_fileparts.m b/spm/nii_for_spm2/spm_fileparts.m new file mode 100755 index 0000000..2866b59 --- /dev/null +++ b/spm/nii_for_spm2/spm_fileparts.m @@ -0,0 +1,24 @@ +function [pth,nam,ext,num] = spm_fileparts(fname) +% Like fileparts, but separates off a comma separated list at the end +% FORMAT [pth,nam,ext,num] = spm_fileparts(fname) +% fname - original filename +% pth - path +% nam - filename +% ext - extension +% num - comma separated list of values +% +%_______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% John Ashburner +% $Id: spm_fileparts.m 112 2005-05-04 18:20:52Z john $ + + +num = ''; +[pth,nam,ext] = fileparts(fname); +ind = find(ext==','); +if ~isempty(ind), + num = ext(ind(1):end); + ext = ext(1:(ind(1)-1)); +end; + diff --git a/spm/nii_for_spm2/spm_mnc2nifti.m b/spm/nii_for_spm2/spm_mnc2nifti.m new file mode 100644 index 0000000..f188bde --- /dev/null +++ b/spm/nii_for_spm2/spm_mnc2nifti.m @@ -0,0 +1,213 @@ +function [N,cdf] = spm_mnc2nifti(fname,opts) +% Get header information etc. for MINC images. +% FORMAT spm_mnc2nifti(fname) +% fname - a MINC filename. +% +% The MINC file format was developed by Peter Neelin at the Montréal +% Neurological Institute, and is based upon the NetCDF libraries. +% The NetCDF documentation specifically recommends that people do not +% write their own libraries for accessing the data. This suggestion +% was ignored. +% _______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% John Ashburner +% $Id: spm_mnc2nifti.m 946 2007-10-15 16:36:06Z john $ + + +if nargin==1, + opts = struct('dtype',4,'ext','.img'); +end; + +cdf = spm_read_netcdf(fname); +if isempty(cdf), warning(['"' fname '" does not appear to be MINC.']); return; end; + +idat = file_array; + +d_types = [2 2 512 768 16 64 ; 256 256 4 8 16 64]; +dsizes = [1 1 2 4 4 8 ]; +mxs = [255 255 65535 2^32-1 Inf Inf; 127 127 32767 2^31-1 Inf Inf]; +mns = [0 0 0 0 -Inf -Inf;-128 -128 -32768 -2^31 -Inf -Inf]; +space_names = {'xspace','yspace','zspace'}; +img = findvar(cdf.var_array,'image'); +nd = length(img.dimid); +idat.fname = fname; +idat.dim = fliplr(cat(2,cdf.dim_array(:).dim_length)); +signed = findvar(img.vatt_array,'signtype'); +signed = strcmp(signed.val,'signed__'); +idat.dtype = [d_types(signed+1,img.nc_type) 1]; +range = [mns(signed+1,img.nc_type) mxs(signed+1,img.nc_type)]; +idat.offset = img.begin; + + +if img.nc_type <=4, + tmp = findvar(img.vatt_array,'valid_range'); + if isempty(tmp), + disp(['Can''t get valid_range for "' fname '" - having to guess']); + else + range = tmp.val; + end; + + fp = fopen(fname,'r','ieee-be'); + imax = get_imax(fp, cdf, 'image-max', fname, 1, idat.dim); + imin = get_imax(fp, cdf, 'image-min', fname, 0, idat.dim); + fclose(fp); + + scale = (imax-imin)/(range(2)-range(1)); + dcoff = imin-range(1)*scale; +else + scale = 1; + dcoff = 0; +end; + + +% Extract affine transformation from voxel to world co-ordinates +%----------------------------------------------------------------------- +step = [1 1 1]; +start = [0 0 0]'; +dircos = eye(3); +for j=1:3, + nam = cdf.dim_array(img.dimid(nd+1-j)).name; + space = findvar(cdf.var_array,nam); + tmp = findvar(space.vatt_array,'step'); + if ~isempty(tmp), step(j) = tmp.val; end; + tmp = findvar(space.vatt_array,'start'); + if ~isempty(tmp), start(j) = tmp.val; else start(j) = -dim(j)/2*step(j); end; + tmp = findvar(space.vatt_array,'direction_cosines'); + if ~isempty(tmp), dircos(:,j) = tmp.val; end; +end; +shiftm = [1 0 0 -1; 0 1 0 -1; 0 0 1 -1; 0 0 0 1]; +mat = [[dircos*diag(step) dircos*start] ; [0 0 0 1]] * shiftm; + +N = nifti; +dat = file_array; +[pth,nam,ext] = fileparts(fname); +dat.fname = fullfile(pwd,[nam opts.ext]); +dat.dim = idat.dim; +dat.dtype = [opts.dtype spm_platform('bigend')]; +dat.offset = 0; + +if ~spm_type(opts.dtype,'intt'), + dat.scl_slope = 1; + dat.scl_inter = 0; +else + mn = Inf; + mx = -Inf; + for i6=1:size(idat,6), + for i5=1:size(idat,5), + for i4=1:size(idat,4), + for i3=1:size(idat,3), + if size(scale,3)==1, + scale1 = scale(:,:,1,i4,i5,i6); + dcoff1 = dcoff(:,:,1,i4,i5,i6); + else + scale1 = scale(:,:,i3,i4,i5,i6); + dcoff1 = dcoff(:,:,i3,i4,i5,i6); + end + if numel(scale1)==1, + img = double(idat(:,:,i3,i4,i5,i6))*scale1 + dcoff1; + elseif size(scale1,1)>1 && size(scale1,2)>1, + img = double(idat(:,:,i3,i4,i5,i6)).*scale1 + dcoff1; + elseif size(scale1,1)==1, + img = double(idat(:,:,i3,i4,i5,i6)).*repmat(scale1,[size(idat,1) 1]) +... + repmat(dcoff1,[size(idat,1) 1]); + else + img = double(idat(:,:,i3,i4,i5,i6)).*repmat(scale1,[1 size(idat,2)]) +... + repmat(dcoff1,[1 size(idat,2)]); + end; + img = img(isfinite(img)); + if ~isempty(img), + mx = max(mx,max(img)); + mn = min(mn,min(img)); + end; + end; + end; + end; + end; + dat.scl_slope = mx/spm_type(opts.dtype,'maxval'); + if spm_type(opts.dtype,'minval')~=0, + dat.scl_slope = max(dat.scl_slope,mn/spm_type(opts.dtype,'minval')); + end; + dat.scl_inter = 0; +end; + +N.dat = dat; +flp = false; +if det(mat)>0, + flp = true; + mat = mat*[diag([-1 1 1]) [size(dat,1)+1 0 0]' ; 0 0 0 1]; +end; +N.mat = mat; +N.mat0 = mat; +create(N); +for i6=1:size(idat,6), + for i5=1:size(idat,5), + for i4=1:size(idat,4), + for i3=1:size(idat,3), + if size(scale,3)==1, + scale1 = scale(:,:,1,i4,i5,i6); + dcoff1 = dcoff(:,:,1,i5,i5,i6); + else + scale1 = scale(:,:,i3,i4,i5,i6); + dcoff1 = dcoff(:,:,i3,i4,i5,i6); + end + if numel(scale1)==1, + slice = double(idat(:,:,i3,i4,i5,i6))*scale1 + dcoff1; + elseif size(scale1,1)>1 && size(scale1,2)>1, + slice = double(idat(:,:,i3,i4,i5,i6)).*scale1 + dcoff1; + elseif size(scale1,1)==1, + slice = double(idat(:,:,i3,i4,i5,i6)).*repmat(scale1,[size(idat,1) 1]) +... + repmat(dcoff1,[size(idat,1) 1]); + else + slice = double(idat(:,:,i3,i4,i5,i6)).*repmat(scale1,[1 size(idat,2)]) +... + repmat(dcoff1,[1 size(idat,2)]); + end; + if flp, slice = flipud(slice); end; + N.dat(:,:,i3,i4,i5,i6) = slice; + end; + end; + end; +end; +return; +%_______________________________________________________________________ + +%_______________________________________________________________________ +function var = findvar(varlist, name) +% Finds the structure in a list of structures that has a name element +% matching the second argument. +for i=1:numel(varlist) + if strcmp(varlist(i).name,name) + var = varlist(i); + return; + end; +end; +var = []; +%error(['Can''t find "' name '".']); +return; +%_______________________________________________________________________ + +%_______________________________________________________________________ +function str = dtypestr(i) +% Returns a string appropriate for reading or writing the CDF data-type. +types = char('uint8','uint8','int16','int32','float32','float64'); +str = deblank(types(i,:)); +return; +%_______________________________________________________________________ + +%_______________________________________________________________________ +function imax = get_imax(fp, cdf, strng, fname, def, dim) +dim = fliplr(dim); +str = findvar(cdf.var_array,strng); + +if ~isempty(str) && str.nc_type == 6, + fseek(fp,str.begin,'bof'); + nel = str.vsize/(spm_type(dtypestr(str.nc_type),'bits')/8); + imax = fread(fp,nel,dtypestr(str.nc_type))'; + resh = ones(1,numel(dim)); + resh(numel(dim)+1-str.dimid) = dim(str.dimid); + imax = reshape(imax,resh); +else + imax = def; +end; +return; + diff --git a/spm/nii_for_spm2/spm_read_hdr.m b/spm/nii_for_spm2/spm_read_hdr.m new file mode 100755 index 0000000..11bb8b1 --- /dev/null +++ b/spm/nii_for_spm2/spm_read_hdr.m @@ -0,0 +1,141 @@ +function [hdr,otherendian] = spm_read_hdr(fname) +% Read (SPM customised) Analyze header +% FORMAT [hdr,otherendian] = spm_read_hdr(fname) +% fname - .hdr filename +% hdr - structure containing Analyze header +% otherendian - byte swapping necessary flag +%_______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% John Ashburner +% $Id: spm_read_hdr.m 112 2005-05-04 18:20:52Z john $ + + +fid = fopen(fname,'r','native'); +otherendian = 0; +if (fid > 0) + dime = read_dime(fid); + if dime.dim(1)<0 | dime.dim(1)>15, % Appears to be other-endian + % Re-open other-endian + fclose(fid); + if spm_platform('bigend'), fid = fopen(fname,'r','ieee-le'); + else, fid = fopen(fname,'r','ieee-be'); end; + otherendian = 1; + dime = read_dime(fid); + end; + hk = read_hk(fid); + hist = read_hist(fid); + hdr.hk = hk; + hdr.dime = dime; + hdr.hist = hist; + + % SPM specific bit - unused + %if hdr.hk.sizeof_hdr > 348, + % spmf = read_spmf(fid,dime.dim(5)); + % if ~isempty(spmf), + % hdr.spmf = spmf; + % end; + %end; + + fclose(fid); +else, + hdr = []; + otherendian = NaN; + %error(['Problem opening header file (' fopen(fid) ').']); +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function hk = read_hk(fid) +% read (struct) header_key +%----------------------------------------------------------------------- +fseek(fid,0,'bof'); +hk.sizeof_hdr = fread(fid,1,'int32'); +hk.data_type = mysetstr(fread(fid,10,'uchar'))'; +hk.db_name = mysetstr(fread(fid,18,'uchar'))'; +hk.extents = fread(fid,1,'int32'); +hk.session_error = fread(fid,1,'int16'); +hk.regular = mysetstr(fread(fid,1,'uchar'))'; +hk.hkey_un0 = mysetstr(fread(fid,1,'uchar'))'; +if isempty(hk.hkey_un0), error(['Problem reading "hk" of header file (' fopen(fid) ').']); end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function dime = read_dime(fid) +% read (struct) image_dimension +%----------------------------------------------------------------------- +fseek(fid,40,'bof'); +dime.dim = fread(fid,8,'int16')'; +dime.vox_units = mysetstr(fread(fid,4,'uchar'))'; +dime.cal_units = mysetstr(fread(fid,8,'uchar'))'; +dime.unused1 = fread(fid,1,'int16'); +dime.datatype = fread(fid,1,'int16'); +dime.bitpix = fread(fid,1,'int16'); +dime.dim_un0 = fread(fid,1,'int16'); +dime.pixdim = fread(fid,8,'float')'; +dime.vox_offset = fread(fid,1,'float'); +dime.funused1 = fread(fid,1,'float'); +dime.funused2 = fread(fid,1,'float'); +dime.funused3 = fread(fid,1,'float'); +dime.cal_max = fread(fid,1,'float'); +dime.cal_min = fread(fid,1,'float'); +dime.compressed = fread(fid,1,'int32'); +dime.verified = fread(fid,1,'int32'); +dime.glmax = fread(fid,1,'int32'); +dime.glmin = fread(fid,1,'int32'); +if isempty(dime.glmin), error(['Problem reading "dime" of header file (' fopen(fid) ').']); end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function hist = read_hist(fid) +% read (struct) data_history +%----------------------------------------------------------------------- +fseek(fid,148,'bof'); +hist.descrip = mysetstr(fread(fid,80,'uchar'))'; +hist.aux_file = mysetstr(fread(fid,24,'uchar'))'; +hist.orient = fread(fid,1,'uchar'); +hist.origin = fread(fid,5,'int16')'; +hist.generated = mysetstr(fread(fid,10,'uchar'))'; +hist.scannum = mysetstr(fread(fid,10,'uchar'))'; +hist.patient_id = mysetstr(fread(fid,10,'uchar'))'; +hist.exp_date = mysetstr(fread(fid,10,'uchar'))'; +hist.exp_time = mysetstr(fread(fid,10,'uchar'))'; +hist.hist_un0 = mysetstr(fread(fid,3,'uchar'))'; +hist.views = fread(fid,1,'int32'); +hist.vols_added = fread(fid,1,'int32'); +hist.start_field= fread(fid,1,'int32'); +hist.field_skip = fread(fid,1,'int32'); +hist.omax = fread(fid,1,'int32'); +hist.omin = fread(fid,1,'int32'); +hist.smax = fread(fid,1,'int32'); +hist.smin = fread(fid,1,'int32'); +if isempty(hist.smin), error(['Problem reading "hist" of header file (' fopen(fid) ').']); end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function spmf = read_spmf(fid,n) +% Read SPM specific fields +% This bit may be used in the future for extending the Analyze header. + +fseek(fid,348,'bof'); +mgc = fread(fid,1,'int32'); % Magic number +if mgc ~= 20020417, spmf = []; return; end; + +for j=1:n, + spmf(j).mat = fread(fid,16,'double'); % Orientation information + spmf(j).unused = fread(fid,384,'uchar'); % Extra unused stuff + if length(spmf(j).unused)<384, + error(['Problem reading "spmf" of header file (' fopen(fid) ').']); + end; + spmf(j).mat = reshape(spmf(j).mat,[4 4]); +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function out = mysetstr(in) +tmp = find(in == 0); +tmp = min([min(tmp) length(in)]); +out = setstr([in(1:tmp)' zeros(1,length(in)-(tmp))])'; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ diff --git a/spm/nii_for_spm2/spm_read_vols.m b/spm/nii_for_spm2/spm_read_vols.m new file mode 100644 index 0000000..e37ad8f --- /dev/null +++ b/spm/nii_for_spm2/spm_read_vols.m @@ -0,0 +1,59 @@ +function [Y,XYZ] = spm_read_vols(V,mask) +% Read in entire image volumes +% FORMAT [Y,XYZ] = spm_read_vols(V,mask) +% V - vector of mapped image volumes to read in (from spm_vol) +% mask - implicit zero mask? +% Y - 4D matrix of image data, fourth dimension indexes images +% XYZ - 3xn matrix of XYZ locations returned +%_______________________________________________________________________ +% +% For image data types without a representation of NaN (see spm_type), +% implicit zero masking can be used. If mask is set, then zeros are +% treated as masked, and returned as NaN. +%_______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% Andrew Holmes +% $Id: spm_read_vols.m 1096 2008-01-16 12:12:57Z john $ + + + +%-Argument checks +%----------------------------------------------------------------------- +if nargin<2, mask = 0; end +if nargin<1, error('insufficient arguments'), end + +spm_check_orientations(V); + +%-Read in image data +%----------------------------------------------------------------------- +n = prod(size(V)); %-#images +Y = zeros([V(1).dim(1:3),n]); %-image data matrix + +for i=1:n, for p=1:V(1).dim(3) + Y(:,:,p,i) = spm_slice_vol(V(i),spm_matrix([0 0 p]),V(i).dim(1:2),0); +end, end + +%-Apply implicit zero mask for image datatypes without a NaNrep +%----------------------------------------------------------------------- +if mask + %-Work out images without NaNrep + im = logical(zeros(n,1)); + for i=1:n, im(i)=~spm_type(V(i).dt(1),'NaNrep'); end + %-Mask + Y(Y(:,:,:,im)==0)=NaN; +end + +%-Return as 3D matrix if single image +%----------------------------------------------------------------------- +if n==1; Y=Y(:,:,:,1); end + +%-Compute XYZ co-ordinates (if required) +%----------------------------------------------------------------------- +if nargout>1 + [R,C,P]=ndgrid(1:V(1).dim(1),1:V(1).dim(2),1:V(1).dim(3)); + RCP = [R(:)';C(:)';P(:)']; + clear R C P + RCP(4,:)=1; + XYZ = V(1).mat(1:3,:)*RCP; +end diff --git a/spm/nii_for_spm2/spm_render_vol.mexa64 b/spm/nii_for_spm2/spm_render_vol.mexa64 new file mode 100644 index 0000000000000000000000000000000000000000..1a37da6539f65dad71dd5f82a650ea00922a409b GIT binary patch literal 260719 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

    =Szv1>M}`y8)H*7B`-5{iGkq%A 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 z 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 literal 0 HcmV?d00001 diff --git a/spm/nii_for_spm2/spm_render_vol.mexglx b/spm/nii_for_spm2/spm_render_vol.mexglx new file mode 100755 index 0000000000000000000000000000000000000000..8094de709b116d9f338b131528355a9a123c9216 GIT binary patch literal 217471 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

    `NXHb;%Jf2GWtMVNrE*IHB|3# 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 Ym*wX#s4CziRY3`+ycsED*a+?a0!7KP&;S4c literal 0 HcmV?d00001 diff --git a/spm/nii_for_spm2/spm_render_vol.mexmac b/spm/nii_for_spm2/spm_render_vol.mexmac new file mode 100755 index 0000000000000000000000000000000000000000..ec9f2380845a990c8b684cac5b34805aa93f2c82 GIT binary patch literal 296712 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%0ADd@p}tT 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�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ℑ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:LCw5_f3H$(j)X$fxt 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 literal 0 HcmV?d00001 diff --git a/spm/nii_for_spm2/spm_render_vol.mexsol b/spm/nii_for_spm2/spm_render_vol.mexsol new file mode 100755 index 0000000000000000000000000000000000000000..94f7f0e04f45c4784d19d050a4deb0bcf50fac4d GIT binary patch literal 241768 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 z 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

    v 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 literal 0 HcmV?d00001 diff --git a/spm/nii_for_spm2/spm_resels_vol.mexa64 b/spm/nii_for_spm2/spm_resels_vol.mexa64 new file mode 100644 index 0000000000000000000000000000000000000000..b38b0f7a7e8e54bbb7c3216db0095acaf6b64546 GIT binary patch literal 256876 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%0A*e=hSX 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 GN&Xjit92y+ literal 0 HcmV?d00001 diff --git a/spm/nii_for_spm2/spm_resels_vol.mexglx b/spm/nii_for_spm2/spm_resels_vol.mexglx new file mode 100755 index 0000000000000000000000000000000000000000..3a50db85cafede1d7b9a966b13067bbf1b4a2aa2 GIT binary patch literal 214180 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

      (C=b*}FlNQZ2OBs|Y6V|l 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 XDMYSU)#P*YlwX2QZhA_YI9dCDNyH`F literal 0 HcmV?d00001 diff --git a/spm/nii_for_spm2/spm_resels_vol.mexmac b/spm/nii_for_spm2/spm_resels_vol.mexmac new file mode 100755 index 0000000000000000000000000000000000000000..ab086c59ebfc11bf09096bef0f0ad1ed9953457d GIT binary patch literal 296616 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ɪ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 literal 0 HcmV?d00001 diff --git a/spm/nii_for_spm2/spm_resels_vol.mexs64 b/spm/nii_for_spm2/spm_resels_vol.mexs64 new file mode 100644 index 0000000000000000000000000000000000000000..210f26bb407baac4404b5600ca68bc6c375ca74a GIT binary patch literal 258624 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 z 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 iK0W;Zmwu&l4!T^#{=P?bt=}mYIaP5I`(d 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

      j9XX*)}>X(f+t4wkW=c;gmq0)7no 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 z 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 e$J)Jr8Tx-e*- literal 0 HcmV?d00001 diff --git a/spm/nii_for_spm2/spm_resels_vol.mexw32 b/spm/nii_for_spm2/spm_resels_vol.mexw32 new file mode 100755 index 0000000000000000000000000000000000000000..bd530311e67fcb8a3c9034311fc2c16048b1e3e0 GIT binary patch literal 218112 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 literal 0 HcmV?d00001 diff --git a/spm/nii_for_spm2/spm_resels_vol.mexw64 b/spm/nii_for_spm2/spm_resels_vol.mexw64 new file mode 100644 index 0000000000000000000000000000000000000000..c6f518024b6168a3e12e0ccf1769e5291ec4faeb GIT binary patch literal 214016 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 literal 0 HcmV?d00001 diff --git a/spm/nii_for_spm2/spm_sample_vol.mexa64 b/spm/nii_for_spm2/spm_sample_vol.mexa64 new file mode 100644 index 0000000000000000000000000000000000000000..c61d18e7daa23b1444e87b087f4213086a6da12a GIT binary patch literal 255008 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Νij(90fLkvcAyjWmO?}E(p7= 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%0DP6k5f)PO4xI{#)Q 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 z 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 LR*}+Y%+mfJBJP~- literal 0 HcmV?d00001 diff --git a/spm/nii_for_spm2/spm_sample_vol.mexmac b/spm/nii_for_spm2/spm_sample_vol.mexmac new file mode 100755 index 0000000000000000000000000000000000000000..d431a6733a301a654f285d56f1ffa1b03f7dc12c GIT binary patch literal 296508 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 ZA=Lu~6;c%-Xh9yy7^wi5I6%H-{Xc?{K5GB~ literal 0 HcmV?d00001 diff --git a/spm/nii_for_spm2/spm_sample_vol.mexmaci b/spm/nii_for_spm2/spm_sample_vol.mexmaci new file mode 100644 index 0000000000000000000000000000000000000000..9d26cd9a50855917360060d44f51e830deb4d9b4 GIT binary patch literal 254208 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%0A#|5K=kwav@G1wP3-jWZ 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 CdI22( literal 0 HcmV?d00001 diff --git a/spm/nii_for_spm2/spm_sample_vol.mexs64 b/spm/nii_for_spm2/spm_sample_vol.mexs64 new file mode 100644 index 0000000000000000000000000000000000000000..520dfb1c09e8fd0ae74ad3165c7bd40d735468d3 GIT binary patch literal 257904 zcmcG%4SW^VwfDczOW@?KoMe)45&{fpF}}z4wuTqUAOa_WKtzRrKn_@HxQ|K-w50cW z?qvw|auTi&;WYuSFnK)(kP<{BKrIKTB|t3!c?nwKAS$uyCDy9J7W4nD*?Z2(OhfN| zKA(U6eA1cu?zPumd+)W^tl2Y%E*m##yxneNk^da*UYr}CLvTeuI<9ByM}_4u2UFM( zHW=3=8>92v{g1=jq8AxQw|+(S`kJ^T?$Ck6Xc*g!2?pmWpGVI47Ia#W@`Y zi1UFx*dUw_!QY+u`!4?O!k_Wq-QtAi;Q?_@wj2LZ|9kt-1sL0-AOYuz;<}$WCgGSY z&U+sE_qfOq=b1ei&9f}7hvImLIH$ufah@fP_lV}~WE-iAMY8~)E2L>fnUfKNY&|M|sj+Ff-UJ?n478*amQ z-$wsyw~;@58~HbH!%yBu&queB|F7QVZT8#n@7#uaZo@Ng!{y%Xu?@S8`~#RQ?*B&o z(I?FIgWJgGLY}W*X+c|bf^Fk__cz;|+vu5p8~y^g@bjmCB=jQ=*nWN+`SRQFirerF zx8d4tctDPJvd5Y2&~4;@i}B$_rnqZ64bEeSXJ1b}=We6t@@?kvgWJg8xDEd^+U2TD z_YZqqukz2&FB|{U7oJ(NVBrhw`LeN#=FeR+|FMNXo%h`Qg1Ji;EhsB~I6Ipa7cZLs z?1IHh<}WJ#@uIoU&o5r^!jBiS()o)PEnLK)Vceoc1&g13vTO-dA3(fr;3wC37)+qSx82jAZ#f#@J)}{DNvEs*OOdeB^Hx~B1 z@FTqdy})oi^V~vR2OmFlYw=GOVbCT_n)2wFNySsfkAGs^lg2Fk__>9c%*Cb87cZVm zqfz{`h0i?zgBSnspV)H?<~{$w;)M@9#GZI+^7P_|a02w~GtU$+=6J%`vBlqiAX^tK z1JY0CN6zLhA@<-;7cY7c6Fm312T}XM#pvNPKN~KVhK=$jJKa0UcjdT8?pni3?AiK} zmy@*a+wngiRw&9RM9Lx4=h{HJM~XbeuFG;cJuefdI2hSZl>F@($ry6R(U8*Z5f?_8 zyieN_jbEKHRWJvG79uTFT?)5qpNW3eL-;+%I^MMUP>>&%#TDJU>SDlnTy| z5k8_y@G`TW97A3;^AVbUE#+~$qI!aYbA3_XEO?E6jPe%2H46_3&hx=N=hCJ67;Uj!Po|i|tSMacYjPh*3J1pEIc&C{U=hsqRmzj^? zmr}mKhMx})>p;qPi2~vOBMk1gaBhc`&)bjKnNukA$Y%MRHo>zjocCYK&$e)WE#>D} zxL4>=EWAo^ULLV$c#GhL7Wrnui!8iGaG!;T1uwDi4#7*!Tm*Y5zs$^Yczr2fwQznd z<@?Qic$R)G<@5eU`ZZD!oZB1aUcoi}80EbGQhw0FdH;9Qhu9-tNOK+-)`Z4!NV3_C3uI0*9hKe;hNxG79Qk$qQHj#k8Bp)Wsz?Y z+->0@!95n&XUNb+^tY1qL3(frJU4j>x z`I@lcJ~Lm$FQthkW?s>uUrQ59&HSHTf|r^3@=n23Gv5;u+;8HJ5r%w~g&X$QnE6P4 zDNWQY+^{ET;cop}n%HdN9>H5IToycJ;aP&WT6nhLZ5Ey*c)N*9+j)IyV%W?}I)!|P zg?9= z+Z*LAf~)#5%0q&4d!xKnaBi=`Q_{KJ(j;zggxk0s(j-lyNnKw!iFM{+r-z-5*n#V@4-w}o>#sla35Tuv&GEu70q1z8r( z<)nga3+HlDL5_*1#FpsSQh{RP(gJ=d6?iS&rC&=0g(mKJL>9cr!i{!)7S8RH3V8d* z{5l@t_DKb$7I{U#mI}%&oZBZAs20xclM4J6&h3*5sw~{6UrPlw7S8RH3N#Dn_DKan z3+MJp1eo_1i-mLhq=JxzbNi%%Rtx9$Nd;{duIblOLA!->`=o-fg>(C)f({Gk z_DKbuCZ1B(rC&=0{AkQq%3L?+lZCkO|CAXX!Cj(&$Nvy;++dJ#RtbA&vv7vYb~ zg7ag9Kconrr5__ak6%lZv(5Z&uaM{MMC8YCIcc(Dmj4OAlqU0fhP-1`o6u8el6Q=1 z7re;A!-D%PyhHF33-1)X)WW+2FEjH;_@y*iwQ!eyElu`YxLfcl3-<_KW8x_b3_Y5O zOA8D=K?^tZG@H0%v?1SO;f8$3!e#whn%rvPS%SA&c(&l}7M>$`*uoXTJ1pEQc&CLI z3f^VmMVwC&*ogn5eS*70fyjSGmk92*@KV7&7G5T}Y~iZlSr+aWJln#n1kbVX8o?C{ z*97-kcu?>{6HjS3^c0zSs9DJS%)F&V@Dei*8+MkOc}=^JFEjJzkl?DBSA_-V<6*4d zlwhmiyqyU5cL-jiA0u3A6I?U%HhwKl37WX$k&u2ZO=-4pqg~!_L%-t@LqD$<hQQj%!dA%s_5}enI@X@!sen&2^FBN86M9Yu)5KFc{DOCxcq%L9{BeOr{7=nR1b2x7BmOrF z?zZq2!95lp5?r?MR>89@yiM?I3+Lk^J)UFXVIi+rc!%I#3-1)X(89X}FEa7eEM8xF z+-Ks_0&bu5c!`B``=rN9P27=dv|DE3M!Tw+EBsP=+;8D7{aSjw%EH}(*I2kmaLvMH z!Gji_C3v%iXA9n9;W>hbEL;)1)xy1kw^?|h;O!P(BzV}ueS&vbc!}Vh7G5fNmxY&c zK2=~N|4~%IU7|qvzv36%ZQ`lb+-_;A$IO>;JEW=Sd|R7-Eltff^CGTS znwn$grTkKws+hUB-$+xvW?sVkBTX$db1%0;np$M$KDT}?P4$`i>agG?Cho}91Q&)G zFOPCXa9$qeUcq^Jlotxl%cHzV za9$qeKEZi;l$Qw3%cHzha9$qeWrFi^gQtFNaDI&ND^)^X)sGQ=u|{yenRgrQ@_G^Z z{VhVC*NgDen&A8x;k!eE2lZowALrN76U}BGXch9jorwHlE+;(^GRxmkg*>lk$U78n zxAa7tN#3Ct{cX2!P3Q?*xM639g&X#CS~#~`dZNq1L;AJ!q`*eJQbd2BbXnxN-O`h8 z3+HxAPkKx|^}1ibmY$SNTv|{lc$S4137&1@jxk2NITmiTt5|qgzm}f#T6l-xy#2_$ zjOi4dw;$zQg8TGigg?qJr6)@)+@)VjPnKG^TktXq_Xw_9xGcEe!m|XgvhZxdYb-oR zaLvLM!Gjj=6};KP3k7en@FKxO7VZ6|?MYAZen;$lG$iDCzoXn}S2O8xJlZDYgBIQ{ zIPZ5xX;WN@=Ix2iJAX< zhkh+hD>d_HU4oaH`M-4vuA2ETLxTHF-0>*aD^07iaIROHR%79+el1PYES&3=rUfmW z>y@T8TR7J%O>41mO~00=g)E%wm8P{?IM*vpYqM~!SDMyt;%QC1zBDat;?jar{aTvV zVc}(hcbd3ktkG_lg^PBl3mozPkPf8jE{nXdXS&^MSa_G1Be>7PWx-1hLnt1=8b_vdp#(4IhBkY_Z#D)L&7r#4{X7K(+u z!Y9kT-5LBC;SVT+^ZrHnc+uY(*?M_|5AzCn-cE#%=5o>ut}nu0;MJuWyq>`wj};0% z{21j$g7agP`vm95C@&G5AEUffaDI&PGQs&V%2mPnG0Odd^JA1(3C@pEUL!a^8od9H zU4m=+(csboLr>7c4L!{!?igptw^+C#AF^;wzm{gSS~zb{n$c$Aygg|~yM^=iq#0og z59!y^j1CLu?MX8_Eu6O}&FHdl-kvm5U?cvI3+q6d>9WZ4_N19^3+L@gGd&j0+mmL> zW<%(igeo-{MZ!g+hrOvS=w{aTvowQ$~^G_%me`=757@W z@oQ;j(8L|%6#ZJ7*=*rn!Fj)pb{*pi1rJ%|iv(}AaG&7ZuBe_8!P_nJrGkeoyiD*8 z3s(j2v~a)RT_)cD%^>Gb3oQI!TA&H;5(WIZSXyAnyG`6NUKR2l3m5G^En9e%kk7L4 z8o{$IToXLU!VP;A3vU+kUJGv#ywJi!f)`nMtKdEhZxg)4!rKKeweYauWftBcxN6~@ zg8MDJOYkZ)=RbvH($h5-?h^8vg}Vh0TDV8>W)t`PAV=^PGapzec*x9Civ({q^GC7; zZ!_`!UzQ2pZsMLqpWtB=@Bf)9c!!C5VoC(>H1Yl)`UUSY^IN5y&k|Vpzvq5MaF-|$ z`$FS|{j=N_o+IQv7On^`Tew&7EDJ9boVOE+595mj=j}wfPjE#)MtOVw5yu*J0|dJX_nu@`L#5w z%EI}zG^@tK`L#4lvv7Vb%?esLzm{e-TR6X#X0=#2zm{f&ESz6Uvsx{jUrV#vESz6U zv)V13UrV#X7S6AwSsfP6uccX?7S6AwSzQ*+ucg@n8~KlyUrV!HW_d5amS($6+_PTT zIoo6A|3|btTQ>70M!Q*N{-167wKO~1%*A^JX?Bj8KhH0v*@~GL$ojQ3TXe)&*Ygeg z3(fN5xE<2$A~P5LlxF+P{6E6_wKTiL#2pi4!AmXN&|hZZyr0r+)xvo{rP+Q9SM+OX zc9n&D1+TGi-cM<^X5mFbK4{@S!J93-MDP|9_xz$m@Q{g13;4A(yVb(^wKTiU#2sFr zel5*zw{WA~u!WZjdER~`o_Nay@3hFPf_GWCpYu5aNBqA^aF<2CMsT->aEG-~5#{_K6?<^fuNN+Z4eW)x z!RX%|v*>wN1*89XyT-qRB|RI`X@tK(^|8wonB6^H@7r%Fj@QA$UHu_~VkH}}4c|?DwuW*(8RG4qJLX66z3pqWSHo6S5T z-(uzw`H-1MJyQsTZz+-QNojf%WZ$hry)3<<9`=|GuNe&SzxsKKQe8MKCM(fm4jNauw{J zUbqI9&??_tO{gDhFk3TC{z!H1mJ}^%&Tm^IY!Zomad*LB4 zXD>Vq_Tb<0`9O^R?MaKB^JOr)2S)hI*A5pdjOEw>71r`IIxasRb}jE9rqWF+!h9aUN}L z1EV(mZrOH{^xv(p<)_0gvX|`Zc}{wSt!=PXrb(oUhwh@vB%jOBzghCRp*IHlTh7i7 zB~p8kg$=%}PHJNg7WfI)w=!v7Df$#+4=GM+5Av_knCWBHiw!--jjbP~#n7TrH5Mmq zy7|kP_y5DM$gDEim#sF*PO_ogEwdvTjIH>wW>eQNTe4z%_(*Qx%G$nxPZZW?UfY}y zJBxF*&S$eL%=LXwMQ{{}hNLG+D|u%o;_*T>rK!WcM@E(L>4EXnR;2kwULO{sDr_zcvQ za4bf-Z1h*yjByi8VgFb3brZEo0nwi<7olDr>N{~31B6L4N@=Y`k&Z9+izIjr}rPljw9v}NL^$~Mxb1H1S+N80e z3L8cB&Q1t?vL17dI#1&|sqJS9>%pdR=5zI6j|=AR!6pgjfu1pKa}*Zjc8qSD<8uj{ zM&jJvz+303cjhiKJb~De%g9BF-W}mSZ{d1SF)vzs1pH~($`nwp19VuFg z;yiK#nQ!Rq~YXp9npFHQ3N8-OmYjn%HZ){YTEx`Hsn|OKrq@eYSgn(Dhs5d`^y> zqVw`ekkNeh=E+#SYP!ROs5A4fh9d-j22eX%4kMX(lPA8^@Z@DaqJ zYoyTC%IoNTxI@@W?d$6l{TfPgkk1rlze1V5{!u2@|B=HzSYKhQP3Uh%nI2b?r+oXF z(6P4%`;^On`w_{DvCctX{+W;Ux2-(RIbWr@AQ>Z`_lTtm#c{;YL;N0e)$334mEvCD zB-yqZ3iD^k_R0*o9I@89*)6-ueYxE=8L|`CF@Bt@Rf-e$sfRaa+)42`VF2#?HQ<#Q zED_i7>oeqEY$UmLEFNS8UTR^bPLCc{$EU;poKi4jg}hV;qhy zyTU%l(T`(0j&2+evyTmrO8NSdy9>T1(>v)Bo9Pe{(8xdyhh(+fX2d^L|?M6)6j~H^EFJjC||8eO5JGv|T3f*lzbi0gs?1X;Oy@l&O z4BfijG@sCY@$c(i>=L>U7`oeG>sieg!ErVA7Hz#Tg)Z-IgQ2c|6cdw9}C?} zNH@)&;UlzeE~5<^YZ_;oL&Ik1K5Y2N?bpuzhVI#h-RH=^MH}$Jh<@0++h_x0LH-@l z-@6Y!HbvMyKB`+^_mMSE{w;j)uwl3GzyHR+hkhn>KWOL{>r(d<8XrS9Y^HU&pM2$i zraR}P(2d+mX0&E#4ttKdSd+A7j?+9Ew*U9z-M@r%lV0Oq`0}-1cOsS_Sl0cmAI}h8 zitUK^xQA=@v&F&Pmy3h@!^OeFh~fVm@%<)ERO{*L(M^S911g>`BD@DKwTQ{WMp6&M#6Me@Fj~+v~3#D)c{0 z`u`_=_NICy{owf(Y@wXxMqMaZ7WBc>)zlwGS{A1fm^H4j~M)dx3*nO_A zOwSYhmv#U3I`-CngL?)&Pdq5@Lv+8P`w`u5>^S0n!#>54o+mEgNcWo%j&2-x;HcoZ zUfgf~jh@HEGs3gte)BjOJx|bc!wCJ^i9cK10d9Jp7>@hQ_ZjXp{c)d3^F{76iu2d$ z;vVCv!aZjd_LH!u!>UIk51PPO=LFgL%>>!?Rk7^;bFu8fy=3<{xWC}Oa`-FUTmFoD z%cYUQqsAs77RT%vA+v4J*N*z6s};IBq30Xuq5C}L5{OxpOPq#I zU60XrRNvMVu8)0A`iyeY_Z9U08M-e+C-izYp)J_78#YmnPr1E*?=#!Cwq59ZkMvpl zOE&iGFWLF``};3H5&BM$zCEyk#twaUl8u*PANuRY_<6{7*tMJDH2QoPdQZb%%lN%` zPUzcb^cUmmHpWl%*F$!bZPZtq3+O!!y}B-HKRSLd82zm!eN;wv>_>glY0NL?){SwD z%rERZ4BZi(QGL%82>V_##_#XVFWL6@=eH;*^!?oEuReaF&&C|n{E{yj^Gkk1eTHt! z_!S%#`sN$<_4Jwl_xZj1O`-2`(nmf<_KCT5V*E_=3%lsKPxrsx=T~_^=zGNIuVrp^ zU$)LK^hWw>p5H;Ig}%EPv4`yl8+d+Q9rG| zXN+~!U!+3PN4k2%Nu>^PQ*BVvvAI4%ZSvVt6!y>M-|^Yn=E%))N7*yjfAzG@lCy9= zi*jF_yK(-MIQQUuGM(2seKuW>mu0OOW&|K3X5eoY${>}w<*~zXxQ@Z~^b}7zZ z9jA99IKD|`*h{<_S|VeA`d3G&F0MbvHQp`!>J4%IM_gx7`Po@=3(BwIx*6B|MEM0# ze~-96E$Z(Q*R7)d9#Q{@sNYCq27P-){aPWvQ`CQ1TsMgNwP)w(?Y|uQncR%MScW~_ z0QCtvHqw0c*iTb%?|U8lkAMATOt=-cZl?Pl?Uk#wc+a9RCtWLy{Ys3*W@<~6JKzJU zdb#sg|3th}ujixpGyMerA#wN?_EPaq&5J!h(&zkD5pmjs{+^e`B;j0PwmYc}I;VT| z`s5*HkW0gUYFtl0#66StOv&ccusOw_G*`VeNX~vA`=&3~$4)w*sj#bq^HO% zW2;d9Wrm!+N6QGt44;|Mn~AbiP}Ruq<;;bUDlz!PtXRnzv-TA#}%|4Xv}+RBg)^+2xqTrXnnl8 z{=}5k^?3!W>bw)tC;ReR?*wY|MQYQ>T+}yekNWDI(tVt+@qXhwWDCanUOK`@y4Gk+ z!S!9@`l7hLV>#_LF6`b%W7cC%vN0d~FdzR5&+xxMJ$;U`S4!_M=p87lOd8_zyXS^z z?vW#rAN0k(IK$k=D9%HA{tr8b2s@I_VJ@3d*M5}sr}~h2h3fKmJcstsIlTm4J4yE| zyWPi}<;k`1-$RwqiRbV`%_Q?C-hHjnY?yQX{nsaLc+Y}%`ciDGbo)Y(`!})yed?gL zd-RFc1@-BNzl^zxHGJqC=_7x&V{aJmJCJ*kA0N6vKE&Vg9QuS9z0Xw}SQ6so-L1oD zto2y=WO;(mv2K*iI{Uz{?kTmC&$ZfF49RYt%$#XnHt;g!H$dhrWGe1ywr_^aNjr1y z8R=si^Ocwt4`kafKxQlEwjD8Z1AL`9Ka@bSo#2pnoQ3>0lAp|CSKQmF%cCtP$x~bF zA&(e${T$@m#w#?o^u9CHjPY*k1AEa&yyHu}1LJ}IBx@;i{WMpQ+lhH`$DIVj^#zh` zVDV7nTmzdiuHU2nW34A+-VrmL?bOG1E|)NDO7}NHE{o(YbKVdAzLT!0ELzw0Q>shj zfHq=#)TMW89*Sx93Ei*bXmd{J-if2#>1BPk(Ae1-gMGHMFpRFxK?l~VKHeAXETKGK zrFUUlkD{Dn3F3!OaUT3D`~a~I_q^j_*P0IIqIdA{f!DA%wGYn)ub}_mr?xTI!VheC z7b!FRDKvg4UrhOUGsY3;g^*3cSRzK+V2{q8Ax2}V)+uiI#}KSTx{r30C&-RfBYcPh zrS`RUpY0<%>!-_jndhTntRIy%f#V)&-!dx9F58*28hyGv41RC-kuSt=$U)vdjKyz( zFF@7-nZ!*bI}H0)_hyq`C;E?fA&MLG|LKKcIH#DWFd5_S)^eD8J=!2!utpsiwpVM= zpDkn;+9JCU!zo6lpq+lYJt$j+HnzfsEq7GsE%z%Z7M09Ph<#vg^#K5d1srIx`af z?*JbeLTtqK2$f>AYa_n5fQ_y*Ipo8>erHC67}~f|X%D;TdxZ~%$+ACrZfV^KJBflS-aWb)HQ_{J@gz08>#tRGkxF}#a$ox>MF;XbnS z!p~$+Q@-k6)_qe#8*)QFlU9w=2Gg9^Kja&@)~Vv1@WIN)5k3d?_oF^C?um*7KX{Ge z2gS2={B2CGEv=Gx8|Ns zJN!QmzUU&h0e(#NQ2$Z#U&xb9c;Bhp)L+!^L+j!kVh!1HZWzPtm|I_4o2?Ikr~eb|pW9^v=Z z1Lx5`#ZqQh?AhF-_ZGS3ZF5-FC|P;Q1;whrqCaWrPV zP06@0R88EDd-*}+YaiHIN?i=gpy$mG?o<+2K_Bus2lT}pf`6`sJn7p0U};1a?K!C( ztb-1!1KTJbXKfYnnCz{Bt#xRd>|J+Hb|2&uh|TsjbdMvusU5O=BlN5FVT#Y5@X%0f z1zE6yut{HF<6j^M+V>ppoVcx!N!>Ut?&F`1d;p#?u^ZEpvIy)qza-5d8kYqa;sxTMhb7xf}A^RLb90-P73z-!Oghe&4_+=lZBI zYMq({e;7e?O*)}(I_Vt@oqp+R3v~4*zclRKoIDr)N&a=<=de2w_TDS(O(lB|>i6ga z8PtAoih}Q4WE<>FqB7J;Adb8b>+e7kov)|wTxhRQ^a<@hO#11=MJ@SitKw|AF8Y*6 z^5@Yf^e2B6`f^J2Lv0A54=k>M=9%|l80koYj>_aAZP0N6I%23CYs*#cR8uimeH!|x zDIeXb$!(8muFw*V#_vfSTTbG-ZHY$rE#%b)rcj-B&gj0C0G3aT^70GN5q4_Mb4xVO z!Tb>JTVKzIK6z_C8%VkP_L1SiYm;Pk%Mav%7_ZnSM~M0%*64w(?W4jD`%&96K87v7 zp>e_*#dz4B=VR7#81i}!1Xi-Sk8Hy==IJk=t;0Qm`nWY&_M?CBp_cy?bzbdJ$1B(y z#Q2(SG0JIf-mSv8(E4t&v((1>I{TFOQIlJd2VhM(vEB>RTG{qdkuU3mBA@&HBA*9y z?8N$dwLDgK(E8XoF_g0I?rbO4h~tw?7Q5N5xIY=Cy6Ap#eSpukK1NN@W5SXU=^Tjr zXwLOfic37BQ9g#crH&Js%(>Ra+$TicH#6{F`rd5kn~+_H`{R*Jmbl)bxQ~odV>e?R zc4ug=6&Qo=QA#Xgit{g-EM^1lV}BW?#`$fE`|EVsRRh~95Nj&2UTgA0DgGGM*$O*0 zfvp(sW4KP(JW)ycdXy3e9$O7PTc8v9mg9>|7GIgCP@TjtU=zyY5Yyb7Q11q86aB*6 z^L`=z{O$&9y&H3au}*&_RSxpC((+5peHzxvNHNd(G$)v6C&vH5tzu5sVJtDPOKHEl zIn4)uY{|mB(lZiro|b|-%#-HibG7ci>^?i}N=A8%FZ<&RtaHrQ$2kgOmF)Tu-zE8- zn(IRZ2Fm%aX2@>J`L2#q@l5Pt0-I#vXHI-u)<*rn{rUp2X4p#O@Jb(+ zx;|B|T6KSOpKq`SQHA(=X0U=>Q_d!zqdc<-w)f0S$;LBiw+=R*8Kq!ffy$91IAzzX zcArbPc?9w~vU#+k=cna;d@euQqntg<&L(s(rgerjmW4Iu zZo@jm_nNfMa%r8BZ+s4Ws_!XvY)0Fkk5b|(Cqz3jpTRED-@ZhR$NG}ergZy0Lk>!H z-bS56s8f#ew?`=htC3@#hCJ=HoF2gj(*5idWGLr7g>_Vob$A@*)W-1;(gxXhr_aSO7@d#Nn(7TVkZj`CQ_c@JUD-b7uL#g(J}O|(zXS16Ci znu`09+l}?84kWv=e$IUfn=oDvk?)APIW6WUgd^pg`rKTF-cOgP>?-Di=H@->8>0^V5He?%sEHpU$3+{j(3mOC{Y%MT>A3;xz9>v@pJ5#=3Ex zL7xyiV$NgjglB14YAyDRXKC)UvosHUw=T5<`0)l#9_bOeN|-HvSNNpmDg5p*%kDpzbqvn%m7WDm}MI?<`^Q z4VXuo+f|rn$VoKE=Am6kn@<{WZx% zpQnBu63ZBFQq*|=&udm8FpQM zER0+>Oyi9F@b&+s@^?6+To(H>uU8Wze}S9_J`(~@N4^q|GL`H{pR+}uGs$+*=OJ`% ze5Y>rvDo#IIC*pYI>cNW`?bkKki+l$iuw(GdVR!Mm&o65TtJ`Hy0Fcz*ndxYsm`TL z6>}!HA`UxGAr3F!88^=1wf~%K#2mQrn;831^p)m~Y(1DX*G+ap|C6wLp#7-*N5Wo> zY*g{gi{H)siRPVRE7fU8@#%d-oe8Luj2wI}ue0wO$)OJB(KfL>+NY#-h-ciN$mjPR zA%DX0I<>*aWZxe|dz4o<>GKPp*@`)?xVN(pVvjS7xS}@b&(ZgjE?v3bT`V=ST1JwjrLa#eVa-S+Zvf?&0*T@p-12i0AI)&xb4hTCpGfS*Fj0yw>^I z2(@3;M3(t+Ccc%!yFd8Qlj}yvj%xT&6I#6pAA)Te_zlO6cD!HQg1&6Tr`VNtHSvAC zXWM|-yzUVn`mH8;eC#JD7qfnsk#E7)eiWO@E*iJtu=&;gdR~k1-q(e(OSebz+I{QA z*y(vK^@-Xfz0110j@*U1%_(85gMEuIb|SCcSKUDCfX2zGrqyGdUU5=ht7V_Yy3mjt zp3YHdPu_J3ezO+2;i*x0CxSfiIMz(iuDOnnRM^27*>#Na+P*&5G29o>XV)#tYa7D- zZjDg&y!IyY+MAE)_xbEIxeCMkrJOHE_snbU6=Xl`qu+&)-{^TQ^#k`1J+H-mD@`8* z8%wL{Kcr~Y1EqZ~%nl{!d2L2W&ughKl-CAf`x&%Hwo!Wxur$rX-Wavu~%?oYk`spziPo=i39tQ3F}6L;VViUeC6k~MySv5mBh3O-Agx& z^07Zc5B#e68*0<3#9f9Dag0mGlJ_nv{kqfaHT8}7?UyC|NpbPzUo`qiOC)Qu<5%d9b zNHI5E<U7{e02z*t;>t!QsEjW?}f>}#A^2OG%`aXcBrfhPYD4QjrVqAm2gGx0~eaAg4K%w0ihA zd@%|8Q%M(uFV^6F(+BWB_+vs4z8H8!b{s-~)PRx_VshvtFOVBuSaQ)x81oFj=`D&^LA0hvP zzW9n!vV(j{gHHIa1I)IH82pkL`5W4b_1ocV;PmZuIb_gYEcX9nHz0r4;9ulVkc+EC zPPh(omUE^GoQS_gW*>KRGMhtky2;w)uPHeYqpzBxT329vKUDAypDg0ZFCG-v5(8~LLQ`F}w@n%C_l=a1SSS~px~^!(J2Jl6xcQpi!?v3){y zFK@uxFXwqj@8ggc5@%n)$O`5^NPj2-$-1uj|XwYVqy_@(T8Ys1FlcWuI)!+d9k{=3^1{F_=0Radz(=7&FZI ztw(8%P+ONW$Zx`}*y~FCcmSSlcE&yV{VBH|hE9DivMyC_!#+aNXPJ!pG41=s-Mgra z>K{znw%mm=clwikVS3m5Wu{8@&!q7r9~3f!AoGj$$>9#z z6?YT1p#51io;}7P6*4Z^*hx0tf{n0a4viE<#Ki;pdRIIm!~ci21(4svPm?bV3Q6z5SIO5gzdM)L zyMDhlWoR3o@f7m)w+3TR1^!NZ70AW#oAm4q7|5mS z`xeE)mG2h^Plsoz3GWmKFMgJ(Bz!zTNw|=}oEOmMakR5WOLsIjR(NaiUDwsosuYZo zZ8UDkdG^y-;hlC2jr-?=gU2zSH$NY(x{W!&w9I#( z%i((eQOMf6a|0Ji2c8|s_u9W69Jolnw_8i^`xp4$7m(c(@V>jV$$Rnhy8?&5xGQi7 z^{;%A8#s+M^(Q=UoTBlj_AuV<+qCp}%!TV3WZs41>w^QQmp6D%@2mH|tv1M5o5;)Y zcc9)II^Q>N>hr#V!+r4E=P&vO4jo1Rw<1ca4UE2n!f(7;Km1{TL$;kqKWb19`*%0g zTHJI|2eE>^k1^Jqs8fq?h)@ULV!H7c(+*wKck*qlpBrj}Ms?y4cku6Q1dq{vMLBk$ z)Vh!j^|31cz?eGh%vl|tS6-AeEP3!F%t`9fH7KL%W zQcF+j#`S*fMA7@WUZJHAzK-h;a9*pWOYh?RL(J_4Ej@8}Lu}DY^%YYOX?auk)>TY> zu`YIMeXVQ!mO$&HJDXZ(2O3*vtZT@dxvDX5=IVwMQ>yFprW_xr#@-sJ#;w4&l>6`< z<)uMN>}P|NIDFSR7T4*$s%>;lt7xd0^-@DDzA<%;$2y;OWVD*tbDdLsrM3Pl zezT3dZXjaqPJC0g4ePz=^W5MSiWyz_ts~a_{xrol=o|drk+1n@^t?o@`Od$y=DQ+m zexS--dKiKF_6 z0jI8^egN~mt)Vr4OMUAZ#E7?I3t2M7fgzAt2Y;zbxx;mQaPSK5pRVJh)!1XWhaMXp ziaj=3i9Lqrj2B z&rOWc#U#|FHC~xKv~xe!IPP~b-SDS3aliWu`uT?Dzw#2|#9N35&_{9S*sZ$)`@8Q7 z?1hdFiW!tgkdDpL`<;j{6eki9U#@%t1K&a{IE{F<5^-ilqxZKIE9kyOv0_!D_ZVWu zv0Hru`)S?7&-Wr$oLbLgMVyEgaTF`)`Qdi4q7`$a*>Zb}6{n0?L2*H?3rAuFV!^w5 ztZ;NkVg+K(DI-?U`BsV*lN9F#!~zYm0x|Dmm6IiG85w4q5HIMvvTfKesC2T}ayyHC zXJ)A1*_oj@JTtO-lvj_CSvXEfLcBekU*r5F&g-@G0f?nHC?@RG($guHoW(iDJC))A;z&P=Eqf6YD1NMI^q!-( z5Vw;kMjUALUur~**isie=H|$GOXG{RLoOrZ4;GMpvsr88iP2TDCjov4=1ze}LGv!NAt)Joh`qwCaeYG@L-L*8hO>Iy;h*979I5&72&jIY@gy5-@O9Q)`lJ4-( zJ+Xe2#;TH^apSpw@)H-{`MHn4*H`S|`39{=+-oJuH!6^CT*CeK#ah>-6 z($Jt$@7#poA=G;pdQ<*5TV>ncRH*)Gim4P|)+b-fI)ZVg*ort)^VLj+kqu-EwM%-S z;}~?D5^W!*eju-Sw>)`h4PwOJuaHZIivx$t^Kj2z8mLyALT-ma)l?;uanxNpIl#{R-Kd;{s&ky_i1zOBSuJ5OQ`+Gn%) zKNSZ8*rV@vHCwy@4vep->W_=OMp6ggsT3j`K<7iSpo60~AL8h8x9(&De9lh&4{K z$QR=MHhed#ofzjSVRyV-?;nHTt2kScaK4E;6k~2c_GqlnhJN2zuBG?y9^iAe#`^3O zQ}%1IkCMzG#FZ}w_}mm*Q055K`=2JhGmuw|n4{+%?+);J>KjkY!2RqgsudR zS9hWS_Q)^Sw&syNu7%z2SK55ex3C5_+C!NY^%djS-B%46t`DJA!>Mm-E@~*Kl;-S;G3=!Z_eOJ#At4AsU-A zG?sCpSYs@4zq&zVxCgoNuBKQoV#{Qz({sG(o=P$Ya8KPAh@FG>{u5(8vqy|6rI;S=p2{q$3(*=7y~?1~G*4n=D$VBF4Pi zGsYkW9gQzC#hCpy(%f**7=u{yTjVuHjCtFLF^E0Cj~BTN`T9xZYw+-p_M9D1$dbr*^7j>(hhWN(&P#)X*O61CTpF#~=r>E}M~7_ZbD6D2?0NJ2 zXvK!_eSS!NqMWcQd2T1go&or)=^MCCF}BkEOm+w2@W#}6A^NT0dTE~jvveLy@N5@@ z^|}o4ARg-`?z7y$?oXc%F%eruW#TQ>BXAePwh}lBE7-XdCj7&i=^w) z;J|J@#-W`Z(ma)PA;xWs=$jCH8@k@6SVilUbW;qZ_sA5Z)+X0yqn!37VlP0)r!#rK zXk7g6d0J)49rBsMfo&p=I(z7S7xj)oXAJ2)lN$(7?4vP7%+oHQO~l|tJOiCNiui?l zkc_?^$6j;-?Rnx^)rHtK1?&24wU)&pR=kGTwVHHP2PnQOAA^$L*Cy!AblEa=hL(EO1TnB4(<(J57VeisE76%qyc$&qb zEET^=bR8`Y;J4pO$^nhdM89ol`?)u8PUC%{IPmZ*m^bqC9d^7Qfd)r2Z+EvRFwZUJYj-B4_VaiV_UIrRZJiQaKYfqEwsa=i!CrCHq z{)P zK5x?1blH6oF_iKzlsWJ_w&_$4F*=cA?dm4i6Ii>cYa0EJufwz0Tj{dr7-H%M?Zm|G zh@UUlxyChWCkhYO#*SIu=$d*6zx8{uHg-J4*FE@+-!9~1WH)S<5&zCpobAT^(7#8C z{Hl0=V}-J(v7&gb9q}D&;6T7NdtZ~k=#7B?X_~7lyG(Jje!X3F;++OV?s;R^-K7+R zzy5H5l7_jc#=UCwmZplSX9g;1r#uQ<7l@tua=q8PI#4n7BE}fs{Mm4ytVV3!jeV2= z_FwiRN2Il~`i=UEi5u$j#;vG5F%EHm9Q}@EC1UnVjjl1fH2-7!05?!-ji>vXo?-T68Y~Qt#umt!!FG` zjdb+%CyM{qPzUpkZ(V&Z%pZQ2m^TIfG!g!khx>t#>h`!FgeX2-$L}<+4U)ZA2FZn= z43dj*UkKp4mh;$?%*MAeU0=Z_iW{W=Jg?%hY=T?YXYvKbqyybuWZPhj68YrMa&ms$Z^VPFAZYs{2- zZVX^=(}TIbj_1Fnw9gh_$TrsDUWaE4`ZrE(ALa%Y(%#tlOj*8^DQA6|DQDw5$eit3 zUj9p3-lXMP-pu`4-ZVUOU!i9T=sJz(&EsDTRN0<@|H+DgYy4r&uha&-Pv00ESoj5e z;q%-;HQHW{y{(nYf0=(VUSSKb<_4BwYS`()fn~VQbs??>D3?Yby~mMHqhHHbY|kGn z6|%+6?%ZyYMO&+DqXVSCcvTMj)UT(~L`xNGAdAxaHP^e4qkUw=Fc+07@z%w3g?$CN<#pausMD`GhYi5DMhfjg z(YHohekc!aN4Z*$cQ-jKVdq2g-~jCbzobam+x=nny`2uVDU?FrE|%x8jBQl5O6iBP z2RCH<2CGf#K>rkF0NK80q)gx5I;sAu2WWp-B^}@0@(6zSgS;L8KGo^3X5t&x!7S!h zh7!~Eolqh@o6?%WUR2-hjTK||G`i+|K3a+Y0s4i#k!>5(>KN{!3(=?eI;YQe3jNuj zwT?sH@w)*FyEnj>w$c6NZTJW7YczLDDb`awS%YWLl{?~&4_May6Zr0R+Fx#lonH

      $vL_!8~(SW_yEJ=T+g_2hjQYYOX&)>ISL6vLYOgFpFwH?1k$r<{CE;m6#lW2`Bx zr_wL6rjWnJRwdVY_F(-OYibQ*CCv+-JtwckdfHW=HyLYX`kUwvt)VycHRPH`YitK{ zyz^LN_>H!{#%P^jZA`~E!XEs4rTY51CDs?MM_OYThr^UN=<5n=iuSJH)0GF&U*u#i z`1TYUC)$_7IC;)zhGiP3OPS%UW0_&Br*O_qjM<(*MSew~V$$J2#hAT;ifK17zO<(e z|Go$tPQ#Cn?`w1wy;$pi?2zUv-rdwX1Ae;g!@+?mSo77eZ}nf`(<^r6pT#=%-N2fm zT!nI^*Heb#-$tF6bvMpW!B-nnhq^Ief%oxV1pD3-&LF2l%wD!Uc+x8=Hq4d&Z)GA6 z3arFB+uf2hkN!>9WxH{|!kVha-qPVtJWpbOX!R|O7w*Bor+o#ix&8G`-ome8Ph+F^ z@|K3Y-{YBR0L2lkrD}Ta+}3d7_to{@%kZ0puyN%z*!^;S>*bX@@_&bC<~uMq^luqw z;JNz{V!4gJA=uaC%Eve?tZ4FIesO!{Z?Hd~ZbM%%K1;Fx_d7f<8+-bDKLf+P*|9=v z%|D=dFE8I!`3JlcNF@1{J1eOTeJ}5i@TXIlztwoYh9Bf_s`Xxe5#_NyHjwu6Hs$)z z|KQ>6*c&=BT1iPl{m(Jp&B;SOh;>tt&w38__1Qj7*X?_9#V;%WFrc_Qh`HE?>0}f) zUzg^2u!g21CJi`_7=(M!SlIKs0aLpF5AKb$E_Tqmz`oxE#ks2w_Wj0+cLNhH!an3B zW69s?-2h@Ty&G7HXSlP9^Oe6`&ka0F?a)3U;-Clj(q}1V;=a=NJ;Y4Jrlorjud3@? z-@?0rT)Z0?iTVpMzQw5j3HAXe(i+13w%Cs~gP6OJ))(#VAx7x$0`>*`Cuxt5Y&zdJ zFcJHv@t^k%QP_`rH-mhfcrUOQdGHPW-9R0_ z(~G_vh{t>VRO)+g?*?!`J4E%$V{#+=ed`Mpr?KBh=k#uX-U%GY@v&F%PGI|k*^X@^ zd`yc`T?Y_T@vdwey&pI_OUZ1Th3}`4GwpdqW0&Jt@~1BRMss-N9f1e$1e|y$ka#o> zKM;>&p4K>qTsC-`p1JXE;W*`ud*CPZOpSfne)JAte{Dq(<%f7b;ONHnWt<~F98Aya zyKDLT0K6aQPq`>!Lh<`3e-XJT<&pudqKMuJ92Wb5pW+-b#CSjOEWIDthUaPQu_2GF z!21Eb8^Ak)b$BnZ8t(|a=HRz0DPLx=Oyol2UTXA@ zIgzfUbf+tsFE+(Kd8oDm_i+DXxJM(VoS3y2?+125*Dl;g5Ua<2GFnON5v#Lr6RR8X zE!+mYC&)&u&Zbz6=UNe~RsGqg_gFnI$B5PRKJhlOx?eA`dS%aCNW|%WdOm{~jC^4m z;&e6gpw&j4{sw#Mh||juua_cLFBJLCa^xbk z+{XKB#Khe1!k#|8wOW@D{Bi+2ktdVZ9t=SR6bKeA;| z?8bdz`VGWu#ODc!+f!h}Ld4_gh|fzA7jgql-lw64N4|NQ?t6$K(~@^HV?0A@B8TKU)!DnMGA*LPD{F87$cyQ(JlkZ{Q zA&uhfiNS$~mv7hi7?SB4d!T!N3c6A_fw|duv<=gX* zA-2Ab`7a&`*^9Wxz21Lb4(>Jm=st9Qpo-k2HGjp9xD$xg6}aaI5QE>M7@P!ozx$ai zyhCa2g6|?vywNcsaEP8!KPYDUJ_LQUfW6U_jo4${jD6A2(!gEYexuUA$;o!4{eSGe zeSB5bo$tTTNg^jtO-`N&kiY@1)v6uwHLt0Nmh4)UfDfRx8j+F%xp(TlcW$BWv@pNU zltUOMC!t=#Lr8!^P99G{C{W}{uEPPw5}-B+3J9YKLLwo^*Z9fCks-xI(_W!2b6&+n|4K2@ z+o)4Ja6~ip+}jlcs0-=7?=Aj{XPDx8o$-dziA`C-98EC*)n(kaVx-G!_UGF%()%CUXW2Dz@!8WgjSLVGBAVxabijg*$n(HqkMtU7%$934}J3ggf zFkjxo_^SBm7JgdsQO$+wI_UuFeBr3FRKRE_kk#wNUtD=e7^QEvhEY+lXyahhk4 zqdCO;%Ejh*Wc%b3Imcb-$$BR1IbR5$Pq~3}w5x;nb#6cB!_RQcuPM#fTo=C-jX{s` zI%Q9BOzomJQCr}PSZ%pnxu?4Lhbpl5yuVuFw7F65&l;OGwlLqSgUhOC4F4N{+Vn|f znbXDc#uAf=6Z`g3HgSldjFYwg->wQ64-r?`On!A|8~HI}u`P4gCLGGmHyZf9j}hyi zz~5qVYV@3mH~+MiwW6)-Zy1%dI&=N1l{cPGs!IR$KD^BP ze`$zeoV+f0hIvIhbBb0ob`3cq*M!$O$IE;dPX=6v=`&|o?{p80LHqnQRy%(Kf3|fX z?}~TQicV9~V)xfqq5qUr?*9I>)i;b1Tey?4Bl51Y?dP=e820W}z)Qt7+Tm zSxvjn&T49TV;1`!;JY?3XCx0jdhS`~!_-rq2F??y_ZIbDqaJnkQKyAEyK1aDpM+GW zk~%e%kG|GX>a_J*=i^sZr}_@hFkZD$oMWWWhvTX|X3Da3e3;DX`HMK;QpRAu7qJ|1 z>p6Gxk4&RsO!(d+_jQPCyv;SR4bO9by9Ug-@U-5O#sF+i2lm5Jvaam6iE}ReO8;NW zHz=O&l^puuy0YK#jmGK!4e~9E#!OWQ*Kq!)sC#}^u$8>f zWcWd~Uy9mS-#_iMjW$qU+eaU2;afe%GZE)*IzwA^&?Xt`O!JvhpS8yVbBI=L4|^GwqyZE5*0|~K=U(KJ9bqonGpZ+- z9A=oCeu$p{m+TXloK{YMi{g^qy>Q9bonU$sTdZ__~8&likwoD~mRk`^FzKeaA?S;wNK;BcF@(ApJIA!&JZ&d%E)YboC zk?pk!k^WzBN&jd4!WYOXJD9)2DSHdl|34$A?7&Yiz`yG^r#zCjSb$w%PtPZat%*~1 z>>cZi`!pwHd^$E1#~d~8(>r`|Jve3Aix%5nz$rJLz+PM>r`*^=oK>8%)!)m~Jd=pK zT*4_o38(yQpPX{zKNBYtr)-^rxyU4OAdK5xoU#~XV(y!5PWe^*`EHzYgk#1hi6@`2 zcMty0AB$6F-rU70>-R9Hj8-bP!H?}AUv{kA5!R`!a!BtB+kst?-Kb`L>^#{N!*rn4 zXW5S$Y)0K}9>caH*bUZNU^}oG@z{)5Y)QO)`oq|b*=U|ubiDsAKmhXXEz6XZ+o@j3QNx0?t#Oe0i-108C z<@ey0&*g+tcGkz=Mb5_DK+K3W7LP3o=6`nwxmDI#e77#>dY+hHb7lShsY}Ey*N=x= z-hcN(SivoI9G6{FBS z+uU+$6gwzxS+?bvY>R9J?A_>F`B|LR=7;5rZywjQc}1Ehn|Y3dc}Vc(At5WqPP`-e z>|>_I?I}J^Y@2yUFi4F2T=jESjJ)+W&ck~#Jst_&ZX<>JU!WE#T+_0XR&)^Cv&Ka?D9k- z4i>7!*J!cJf5-TJax;DV?Qy|#fose`Z!w?Yc=cI$Di}ZJJi&$JP!GcS&C$G$HZm%| zHY$eeR<1}JIojrz<7uB{@yp}^s?kG21KF2)YLLY+-w(r_^EIC3FR8md)04d}t7tT_ z_#CbkB&I*76P~Xr#^Ycv=wR-%a4|Y+za|E}&h5>XPRd%ek0_t@3XQ=WAi^`l>qU5G zZ?6P_0 z_^4QYD)DuTXC_Vn^VoE(;)YRTCR^Vf43j#MJrCf zjpsQS#WR1qUp(_e%mw{58g_Ag?axfxaNDY zMKhm$gtK=d^WbZ$c(l_SIaib$6K3OXR2SG zCExZ^F0rM{v&{q5CvWGU`q1iuMU$3elRAd6e;w;0n$kv2>KMz~*~zB3sY?1&#?c@7 zlFreG&W#Nvonw7xl-&xc9blgGWVhs3E_RQcB>!YZy2qoqmuy9GdRzHh*l^Z9oRz(J zhPdlHgNf&2H?Thr^=bA@*+Ab)IPg860T*J~68d(SdERYCm}iHWXYSpx8|HcSKyNu~ z@Kd?2jvphAkwR|NVxGk-uT3A>_I80~8~bFQ#Vv_Z);TrS>>kD%Ew`t9sm(pVXmii9 z2?4j)An$DP&RpBav!vR#vjn>rVV+ZTz8CU>!6DS4jU9|J$~%gAPCAO8Bjy?Z&&Tt@ zJo^%xv0?bYaffFG$I(~BJjZ8yjh$je7mfB>v2X0)4CYZTan1+E1>2i4>n4ePepT#q zA>1<@GG+3xk$b6ktK#LA)D2LM_gKQaO-0MXA?_JlH=~q!7yp;^@nm1@iNfG4?47vh z%x8%|EgfJc^WGfX!^|E0&AF_$VgA7R0-k5w9?$C3qO(T}gSYa|Qr9`#7+->gAHzR$ zKNAmNO9L@RlIC^KTo3jTIDX91P`4`og~Ukd}j#v!gKSt#YA&GSXJW@wRN%E z>(N*j=AnJMmtr(h!aTIy-}}i!JCy^pcxY_yB|J2Cw@)4#8{8)kEgSsh^U$uocxV`C zi-m@X77Lwev(WXh&?zgw$+|tQQGPqH(xnUl<)aIe% zA3B!nGZx3Rxv=e5WTE+HD~sdEk7DzC@X(d`A>*20qW96C;Z34g=uiShF`?mkT_SUh!O!B-7$r&oo`*VIO&l}A} zm+Bo`T=YY}RE--~!9_dQN4V&y?@Hsy6}afB8jCK*~CLO^iR5T(M3;)KZ3&% z$Nw3*=-SiWx#;#MF3&~R9{mIFRbwLGEnIZb6XK$=V=l$A_ww$=M2n67fO2y!F1mJq zKe_193lwASor}Ju5-wUywAkp4@X)&dMYnql>Gq3_mUhMp{90nv79UM+DGnd6`h!CD zFP|JrdS_@T>C<6G(t(G}Wc45UTs`>c1dX@iqn}P6>80P!fQ=b!@zH~Q(R}n0IJ0VL z@xdOQS)I}Dh25Hktxq|I-H+mE}D(anTVEAmc>Ws_{48_qS^GJ%kk0cl_L)G z(KY{k^?Y>AW*EjOKDuaj|M=*dy6${*-E3@cH$GZT0;6ssRQu1s^$5U$#Fclu|dwV<^t}_9Qdy)d$VA?Zg)qXCzmVL!2>|SYo2` z62uyF6klZSXtB} ze{Za`=I{|#dI;|W+gkW^dQlaubjMrxDPdL`X0T6Ix~S^Nu*M6-CHi8ekH{V$hKt_r z_W0dgkGcHyyA=b4DgFcTM9pax7qFOTSYcul7At*vDrTtiL+z*^=(!$$JI!LK8;vkSo#YKO z)DIGi#@4Keip$yzwX;`-nmB>VTMV@wmyKqqUn4FchFa@Yx-rz@xa{X)s3UP%S5#c~ zB126(6OV1FV9Zi{Rxw!^>V#;9nz{RRipxeA>Q@UJKNLgF`&C?asrYHdWo?G~8~tLa zAG)Y9v3Ec?Cc7R+E}Ef!jkZ?1sI4P0+0VdG|DX?sdb}9wQpRU7)Z%Q!QGfhMC~h9# z9_w5z?)M=5P8_mW>L`x-F*s^RgrhE2j0%3b`H_%AY@-E%I zGBDN5i_U!lCrxZ$e6&++^@7_xhIne7Pi(c$C$2htJ~V`Hp2@m-i?7b5JFu9NO-A8hsN zsy`35x@h~C$X06%gt_(jzeKkBCNZ1+WUDu+PCwb|=T*mMs~z3hYOnvxWvlB#;(_{R zt4B6pWUK2AU1Y1<51r@xyvSCsdQX1ZRkGFIUf5~}Il*PH!1s_7oZ@$T{eNa`bx`l{ zGHmsabo?@Gb%Kt6L2R}1M`EjK&p-SgJ%gk$TkS3XGh(Z)_PR>8I$Le$|6eB z+o1^C`xZQft@->=ceZ+XdGBoXs=o-c)x*pCW~*0SdnsEzyu5d|dez{5venm~z-Q=# ztsdGBwmMyVQ^8f2kSnrupWi|Ad0(s*%RVNa`c2jpHuSFdyxq;513&4qOto?ye6JO> z`6~5)tIg}pY_0QRZQtp)Y@WJ4by^egaEI2d6sHfbs$|atc)Vz>XPc*-7Py3`yt3AF znzR@3U$3b3tY;$Tc!qsQ1I#1i-pvVpX)TF-C*#47^^*Riyo z;Q{s*J1<-a$1%fN&zz@=sn+j}VyY*=McxtSBCDA@Fz1P10UH@47gQBv#%tY8HL=SD zw-bxKVU%A#PpN=+OG8!w#$OFZo#W0Be6^G9AWOSM*OQ#u)XY>>HPwuJL zEJiz8vl+eSDbiQHsM&0!f0S#abZIu{n(62zrfdppHcx4|sM$PKn$7G*8e{7=7j@?e zcd=(-mp=PaIOvV^*(QsF4)@s&^w(`~!12IBr@-!=fn{*ecdc*M?zs0O)HJjCUuS&C7eOD|reOLOKaUbQPv)WJ1X4xS2#Mn-}CGpr`U%xb) zSJJLxpoen~80h4xjN#f7ElChpx9CM*-4Tb#5h<=NHhc&dc-7Cj9e@K5nNq@%%quU(SO!w{@2-emT!fBCc^K z_9iJRA05$MUIM?2=1cn>z3|IU@yoI^zlVKmOr3UI{aoy__6oO|LG818iRLnWFG_RS zG`nB99V;IMe|I@{nR}`^dy!o}RnR&4BD<{X>O7ny zth>C=;-+`WwtQf*%hL*2@42}XzYgYZSMdWO=`JrM?v|R3KB!{lmGsfK#s$x9yWzZ( zGOZs(X)nX6WVTgUE-2=Vkd(2{>R@#C-kJ@O|rF>-6zoj?M+viYj{{QFkaUn z7X23IPnBkPRSf#-tXo*-Y(rPs;)f5fX>i?DL3-(GpcMvFs+VN6C_Ie9sL2YPinxRdkwceB79P7(*zB}gt>utr8 z?c%3+vg9K?E58r=#*7R9qUWX)$G^+7V+RMQeHO9Dh1#T{*7cCee#$ZN#yTbqWgX-E zPtksT=_n6oZm}5N_@A&HMi1V2228JbW379zwUj+gFh!Ad4}pukaoBeBlQ-772eg#E z0s4KHUi($@#t!n>@Wz3_?m<`08%s;sF@%4XmU6mxqgZ2dLK$$zm(f!8VN)D8$)?B# zus(95Y?sbc1wYI;jh6DX^JlTG%QC0=!dlAH@;{@Nvc(naI^v3TUhLyc`&zwdDNi%| zp`}c&PFl(}=do)&wUiz5x%{cqyk!}~+sM;it(LMwZOR=M&l07?a;Z4Q>Xc0upRWzh{p2YN@H1zZltNK{LLqvgS^f6Hjukv zExc<>qyN!OXe;YnD`LDZ?BVtFbLs6RpQK(pzkir*)lF7id?t4*cA&%INovpbx;h4V zT|v<oC!26#U?Um5u)IN;COXwm0>KLEcn&z(-e-^LZ)ToBhrpZ}ys+@%bg>exyC$ zP&0ng+?vuy<_DS|Vo#)pOKVCea9^G+Xy0ri7p42+d8C{C3ip+&JsK3hpWBFzW@G%F z&8W7~$spC1@s}%r%Xr=!5XSEzM+jz@W`qqP1#iCf|=f)VTGva9Jeek;97t^ zUV|3#Joer}=ZU>Fg5v_*2YKa6w4L^%S+?QLykKxIcKTh|LB;@SN)_pQmFmB&15mc?P1O2-=m!)&E%E7FdO^}e#(}+kMNH8UADYJ@2rJyMtk}ZQyI|0`zmQH z9k-ydbUb?o73lrKy%gxZ>m94E7+~e*&QO-T+;{Yz_$Kb>o48lyRTf)6Q{|)lL$Sb) zUNn*y&AISNJiKBObrk^bEUtNX; z{)?+&fo%?W4!Ff>CW{5`!T-kn72ozj`rERsKnT93OCR|q^(Xd_ zo8NRm`pC=R{;M?~5dX_s<&gMa#q)aTBmd}X_+Ry>UO2LH*!sT;^S^;Z>Q9U@UHq^7 z_eg*0rjN{Clo5{X6#TDz39)3+`pEKixvyR|$0K^l$HF{TH%(;eAv3?)C+@E&SC0;0 zE^&{bxHDoGkJ>uPrO%|o6vEZxv&Zs{Pei}BOK<#BbT;3Y-gpDr$PL(Zbm}$}dt(mW zO&9qb_86P`aI`kEbdk^T4zQ&?wUMWH(3doq(zv)at*!0pRPR~6EA03j-qj)AZ&2?F z4dJ_A7y_$G`MFIK&_*^o)wgb;EYDzRBR6Gvjz_b?k@;muMu5VSyv*VC;_5x4k^jd?H#SxhDhsHQI&wMRjAQ=Q!7yh<#5N1FZc@xyLE< zewFYlx|@v}-}HU%)2?EG zt(ZDC^EkGsOC!0RIT!D3))rzqm398-iK*v(*i9pOS*^_gI~ZRJ%hCeJ#Q^hu<9v-C zL;J{y{oPU1e2GRfv2;r#nOM4|k^F2;b7-}6LmIp7otPaRTkFXHzrni_16&)cSefCr z<7;l>Yhr-Yj`41!k&F-AjR8JO9KM@A@_jz}Yu4V0OZmN)_}WGoU@MM(jlmw##L})Y ztQdOmc8{^bVaB5k;aoG8H52%wb;Q^=F}Fser26b+!>zcwV(rVXpXqFkGqU$gHssf( z>|=j*VrkimtBdJH8*_hxe!!tNd?51H>zYdbHSi?1Ke z?)GkT)4ZJ#oBKy%cYluU{l4t1cwKS3>KF9UApN5ArLeYf(if<0+$XOqJ1e{UO!_H< zn3n4;;!doSkzbQnpV4++ye{+Ow6L}@u^IG`ZEfS!{_whIqqU7;C9$tp)HW9P+NEur z`{ipJ%a@4ox|#t8MX1YKL_vE!Is`o?aqhaY*8nDHcZ6n35`{>XccD}KP2 zbAFb1T_Zc&Ys^p#vSKv+A~DD%H*9n=A8v(59*D+q3hO*un9~n9VQU<#&3BCPSgh`H zX&h$-&XY^8CI+cEV)h$Td`YJ*R+ktg-vH;ot133+);D@B{}yMQ@-NK@oIioyG2dzY z(=lELV|apejt{`?Hq(X`%+pyHF>yY{Gix(z{GG5q>xkF+ z8hu$@$1FqdSnq283SLiB2%zJ4&#T$u5I=F9_ zm|N!mu?>N6EK+$&ake@}9Cw0_asG7;#Bw9HeHr^~EyCs(JB@@Y`Uc}^vailaglq7g zD45fE;BhpIYug-4okm=7f~8rUSMD@oOBowK8VrZZdwqZKuFiuoW*qBl9gKBx{9nwb zy3#2+&-zB!WX9_I-x*`Zsm^}tz^XdSD0?!-bnK=s<03l6UQ4HV9^VRisu)YJcnn-E zWj>@%ag4{Qm`n%dOGh(S5kt{=77(WpOM8TJXSjyu=k1i|{4pmfJCE~4W#u_yb?iv(j!*8)zrm5Tv6|Oi1)Mh zC9Ww=MM4h7D)t*!d`~el;>1tF&Hi7;+vEPUX{lwI)3Wg?|C5{j)8J+uVNK#1kLI1d zaI=o$w3_P~U;5x?mmK)>;AW>~fBD?(@UvecH+#Q$ynb@C?W)s1ZuS$W>h#XdPCNVM za?Qxad z?Eh9-|DZ2albE&>H+!n87jE`D-MQHj=X>X77kk6p?1=NdakHo470lShk6y~njyT_w zn+@w1FTO9#&6?6Ac9+xNqBM!qdeJ2Q>ItsltM~P$Njyy4EPBLgK6Hr{4_O^+CP}Y* z!G8_wJrLuKlOA#D?QPPgE{QO+Z9`T6IE)TKw96s0d3;b)uFwqbsD(Vo8eS?!004;{tNF51>zk9b69|MZ9#1;x+G?+oh^ zPw&L9zN{XxzFTPk^hb}FI(@Qq=m%d>kC^_`;j6#M&mM-KRs65mIVYN@>*8k_dp2u7 zw59ikVjISI4eg6Y474S{zGy3%Q`E*9F71U@anr^a=5QrTZk!oIJTKt{I>g$ivUCi6 zCLbN*d(D*PnpfOGt~B3_ZOb>4PUo}M|6Vh;`Cc>W_`Up2EUY&TV(Ev9rBxhR>-~vY z$FX*8SHH}{rx73y(Kn59%KW3UK+$h#kDBzwKlcLbFt5lOubB> z&+h8;=o6FIcb%f|fAJc`McL}}pIw8vJzFuIV}r@NKUH>cK)#WnK1>|#U^@>Qp9^9%EyDtkVDl94u-eJ_U77Sbj!&lq|AzOV+dYacqPdvij@-m#(hy=aQ;ML#vl zMnt&VgSL%;yDi_*7k4Wg!Q3>8yDk3}G0-Rt;*qJnX%Ih)2Jw@9Y7m#NB6cOM1ItdJ zb&zUvw_O^<5w6zOApVW7-qVe{mF}?ET(S0i=bQhS8pPO~F7{Txhc$@JSk_gS1R@&5 zu}0EL#?Ix~3+*3XedDN@vREUY_(}YU$)OZ3PgIsDuZ zBL()pgT1`X4m5|)Y(f3%KH3f z?AARCr8!);AI;(McRw49zh_JBel&;k$2A1~mgcbZhNU|kVRofAEN+*%&0k`#dea>C z!|eVP?#|x}v-=e>yNW}@-W^0!aYLhf7X(^+$79&(asStLI`q_=0SS{JQP#;hl;(t3;1l%9pKIyqt3mYDyo`tT?yo^~LNeUM~BB zUTD79IoVeBpWOaIRJ>gE?Ra^Z*$rQNm!3oW0T!o?*xt<8{{eGbn6_QT;~BHzjC_rj z-tgbC?ott1IdIYcp#)dA1Veycmx% zr8WHf!XUh^FXhxAi`Sh1_clnj{V(~~lriGC+8NfVgz_21S<^ZL#QxT%O+QY1IDYN$ zy3i6?0;ek-%*UNEsbz; zCa#zG_IJ~HJ{#XV-H5g^{o{LUjotm^d-c6rd~e2x)R%3(_vNv^%kaIX_}*{G_Q)q> z5BXHto+13Z$oCda3iG|w+roVB^rkO{?=70d9!x2ZqtMBEi%bb_T+mf(yyf(oRIA`o)#}D`&l~L%l=pJSp&rOo=(Nib!U6o z|2mrORUMUEi7$iA&62M}A1#q)Fnv!t!!wRPZX|Flb18GCJ%yHLaK;Wag2_oZxc1*X z%kO%R(gt3aIlWEa`C91%b3U$BSmqwl^y;|ay^O7o!to|0a-8!Nu5eFJMH@JUa>?Wn zA4Su~z#dwW!PX@5*S@A?M+cZioHl25!v{k|-s4LqLU1bt3^RXaA}J?bCFZn7rIt^ccQ82_03 zx0l&NwUs<0$DDm?1Ivbf`P#s-eQ5(TX3c^5we*4Eex(nbVe13ep${A@?ib&)V?8-3 zZ19J#4>7ZN7nO`vmOk(cxn@>a8#vz91}=R_?A~IyU&cv``z3!Fgt3&ysx-D2K3i9M zZ$<6+yXV#HzZ<)M_tKa)S1;PY69Z0iAwz=O$#ptB&md6*Pgps(W#cY-s}b#sU8m+~P~bBxBjPI9m)2{U=%z zxQn6b&H+#N{2Ot=`+BmA%o{Ckk#~KD`QI#i@9<@R1P*w{DOf~4Lz@Gho_!e(_{ZHj z;OW3`Ax4h|KazWT0WTU zi(_q79BY8%4m}V`R@_Q`rUwh0sGJhaC@gTT2ZphW1wP30RKTI(cZdb9kQT7UmoN)_ z+S3OM{KFUWdes6>jOtU;0`84Vn@^uI=u;d0X{Sf2Pccq)vA~)7UWR(Sd)c?P8w;FP zJ37Lpi3RRmQxe@po(CRle1!ewKKH@v=4#LQmxp>>&kyya?jP#0xL!2tvY+EQ;I>*n zCEoI8Ua+NeR#UauVA^39eQO(f=o{3hYzDY2!T|GaPgFa>rNK;}9{q2u1svvqGe($C zvrp*PxDR-&IrW(%M)AJ-(gKcCAJe#uU9foI?e6KL;Y$BQX#p?K^IBTKYlsb@1)QOI z=)3fbjcFrBGd51dpU<|mfblJQY5~iZSsZZs^wE8Az<+OZz-yJ84RgQ?cJ{>qEAB_! zCyE1Ju(3M_Jbg4{X%`3l%HWXn%4cn=^S=OB`uw|a!0&bAfENU!Ibc712AkVW3s|`T zX#p!A@L9Bgqw@kiIbh;)oAHBfE#OeJ7H}dQaF-Tv&)mRS@(HTfjRn5nyvPFcyK)1{ z3G5i{HP(?6SOX7NJ;rMU;(V?h*i>?5)yfa-nPTP~nqp=&;4^LdX25uLf|2%izL~c2 zwyqpOHaUSZqdr;peONBki? zaR>UpXKcHT{_p7mnt#@n2^e1fnUyCPZo;!)Y+L!+C z;QsK$a3Z-di;pg;-`}Ef?`nABKScAyL%)2USh0i%Pb}XAds?_QtF{21_`|czTP>bA z*XT|E*Kv$7ZtKA51^6f_#OC_a|9xII*v+~9sYUr^X(P^d;%Dq8zIhhDSaa0Z3fcFF z7$;0`7O~NcWyBa)5El)>5m*0}^NenS{lZlDOY#eFr z4V3l@b(yPcEpM03Z|%SlXQ}7jPW;qY7t(#-TMRq=0&}`f=4_qV!WGQxj$s#7*Jgruh#&`5f=Qk=o zx|g;H&x^b3{GO(*)h=r5NPJZMv0~9&Z`G>0i`+3e<58<02<2?zd5W(w25+Yyq0Nv2 z5AsAGI=^>G=XYlw-0{%7PHFrecp#L^9R6JC%`;;T@_kB=mbF}QI!~0w@5Auau@Q~m zJr9K9q~*(ZExlfcw0)(y%Qy;y?6DYRi#aoyBIFHo-y3?r%j&;+c=vT#(#3je^Oj> ztxt2{-nryjKVwxNT(ZA@ZooC+^`V}bFY{eI8E_q@&zva@IPQ6cSn~We3BMimgz*jH zpsfRM{%I?FFt?W8urX<|`|GO~+%Pk#-2MG$(Q$CGj&vtuM+Y&wju)64m#j@VkUQJB zUf=TEwVWg0xK_V&Z1g-GD+p^>&)2ahjG^|i0=u5Ip3|;3P{*qH=DvI`d1U^V$R&>! z@7PZ+Iix!Mn1$jAM{PhR@Zi%b6Sklx{C zxa3+LzYLdryN-XsT(a78BwX?xVJ^97(w`BRT%q>3N-p_smGytITrzEEamhx6jg8Xt zec0xbo%6owG3wbXz85apX~vd}33JJwNxgB&Vb-|hmMAXSIhVNevUJa+o?LR6VJ`V< zm`nDFOHNZvCyGmU_rfJ#cY^W3S8w*lC42gz=bNcL9K>J3C%2rQY$T!a2b=6mdNapJ zI+|g|9mP+Pk0MsG4_)7_Zo_cOj>zs@1)DsF_FsW5@I~6+W|Mz!v&qi2sop0Ban2y` zpg7%A&<4Vu)vIGXmZ(n@tvv_z~K^AHhyVaff1))wWNmZNqHx zKdroaHu;~*Z8q5{ZC}2>g8s3||KzjTWM`O7_7>n@UzSalEtk#&=XxW;0x|C+RYrFl>r*7a2!GonMF-vi>3?_-`a+G~^(gA`l5+G!*;Fz;It z6N;~bFI<9eUUPdJYYAZw(Hzj8y6Pulln=lt``Hht?sl$~3Zv{cQl_ zDb-9oooXhFci!WMQ+At)N8Q9H8LNBal<8yt9ysOiTm`56b2w$zCA9xzgj0se*#N67 zPI&}&K%BDrzb~r)&+O{|u*mk>gh>CNeM$dk{lXW>DLa|JpQ&|wXMYY(*@>S%8~?7~ zoH8*QCwdq$#Tj0|bb^V`NGCY%Q{pn{1IIB(jr;TtUtE;E5T`7A(PG;RIOX{Zubxw$ z|BvLx#3@_-o&B|D_Qok+ULSb=kBO6sQ?|~*Tx4cXPWcjj;QH>Ia)e`6e|`yOc@KQ> zQe(~qn7jmOk>Fz{@Gtdpb^`|SNFb~a9G$n2CU|f9z?LRBe(6aoZxN*t+(!9`?a=RG zP8m&bY=a-$0V8p&99>}9h$?A=%XVN_WH+jBm>DxqHpMU;-;+B=mkcRMklobzrId)n0rl0Kc)QH}2 zL}$2Vh?#I|h?&w;Z&>5lHuQ!WH>EWkeAK;dMfUkY17j8M@%{+@+b{-fP;g&sBFon)Ax9lzdoZPY# zeZXpTQp%&5!mH$#ondY{P~IPIIg@#y?)imbO)W2s^`+Lw2H0o7r+o%~tLMVDNVZXN zc&GN&zLlTFS#3>da>Pq_ctx6LH1S~vx);HhhlC8p*ok)}pM9)5y9`SgRD3?7H+&oC z;k&J79_rjZ1b%s-r%832(%P;UzpVH?G4VdxW$xQ4n}fbzlh|cqV)59X><=cxlyQBX zOZ9c0F1_L3gv3pY33a0JakAL$$v%L8(8VrKG~&b~`@-z<-!Xol+)UqodtC5b;2Lw# zTg2!&UJdJ43FFrSXSa|X>Opje=af>8HgZ&cZBz``tz3~dvcTq-+1E6bEPk0hK(+Sp zBTk=sYLLY+-w(r_L;m0Sm(<;!=^4E)tEk`vOgPsH64Rg43D4I=ynwvFG=~>1W>4u~ z69Zo7_Krrs+_CmrBboA9uh1BF>|Q)Gx|`Ce?%T|Kmm{rFHl*yfQ>&|f3#C%hN!knuU# z<|1#j9lBb!Sw7y{%sQ|7rE-BUkq4aHlyHFcS}xfVt=H1pyoLtXee;?U4iA}R45Uvw zZs(u+(CUFjGnZqNq+g65mC}?pawZzaNk=D};-)I;PZ>vlFbr`3$%a%a+i$OR!~gs&|_P_N<*4y_0^vjeB=$9meW`-i_=vI!f2&_^~}&*D)-( zLH6h<=Ubc3p2h{1Z4}2T8&!gRIzrha+(!o2%M`a%{hZIxIc2MM55xCC3wWu`J-^6# z-p0e=X)V97#u_y!z)a@7i9w#ZgTFbK)i#0M-(=mM)~La}H;aYNF>-kimFOE=EHt*dk~v>)n1?R4dFc3u zj^+A{#W8KHOGUeQ*yUMhzS+v+IFF+j9=ejgJFW>P`cf7;l)%_f!1ub4v0?91*dO}s zHul-f#OL2YzP%C-BY5TxGn09KHGFht>Wu%_-a6Koi5~Joa)jcdf6mXITy$v{7oFxy z)wrQ=MB~O~xM=742p1joU1=P-0vA11V^I_r)R#W;{|Cdlg?ahg_;j;B;GSd?b^ced zf$AF(hO;{tUF7>W;-W9nbls2bvN$5<=gc=FTy#}!s>guAbFIUUz|71L-zO$WJka}S zy1pY#*TDD*PX@-{H#abz{gcK&!W_R<^A#BD7WTi{_a5KYrXZT%LDxOI0)De5nE!YO z+}(S5VNF-Ay^y$C$=qK}IF@J{KRlHe%|#c@#{TxDkNlJFTy&BD^K;R2E4y>i?f%Pi z(Q_Ac=c0@JSJp?K`}2Ns(W5V;k9^D3=p(PdueCLjuZ87_!^g8Vl0%k8vhfl#ec|dS%#uEIzuYPO@zIyNp-s z+(lK`yos+dSG4%(9N#5;H1WqUADvNDMIY)-Cpp4LuUEea^U=N^Up*h~d!joZT~yUS zKH68%osX`o!uEFKqm_#gAFaH^XW^rxV~oA=(eQjWA8ka(8Ksll#YZ2dOppA;&glHa z(jIJd6Zwhl%1huIl@BnIHsK>HuDAw%OL4_b_{H`3t;EZ#tKo{O2B81>JzvJ2@AspGoN9=artaRgrt7D}bFMJMG+8Ji0J*m=M zmi98Nw51<{zXL1XzFmG1%*hwYO82R~d@XG(d-6<1U1)1YQFC`zx@aT)wl7xN^Y4w7 z)*L>O?b2Sp3@d#^ z_869T=XSTJ$<6hc%jfS_3>2pL55yBSr&U})tTc0=F74&hQ+<|xatLPnM(W{P-%9+V z=@#x&^~phH)MkzB?5a!5mzcsy&gXt@ig;PTGH6&07g$WYVH#PeG!7_%&fnwTsMbwV^l&D{Mu#bqN5^{a)AABv&o{VFcIRQ$B! zve+vz)ZgeAL!EX}V`49Dqj|8jo2A>lD&TrRbM4n?Yqg8oIueup3=H)T`e3NXi=i%M zd=^73&PE*d$628`w3K5$zAnuD9wY}M4p}U96i59S9JM3DQ5P#l1wY-K6>^9L6eC?c zz|0j-ExuYjwHWDLfzrDUs0{m4F)upz2`o0TC-KovvDFK1^H_Ur>3m|Vbv|*`;q!T7 zcEU~De047EajulUCC0j#dl#!Lwpxs}t|7i!*RV96VU?MKofBIvj#@l*gr(NAio4b` zDu={76mvLSu~=dw;;Q%YQ(W~{ep+1hJlf3SsSxG(h^^+{a>b_W-o$;2 zU1B{}%&OYbhhT;#@g4n=7*6iR@xPnq^G{)`f6SQwnm?`RP+4ZtapEBV$yWbKvDI~L znm_l>R@a?n9O{Fu{)hYjJlN`@<6k0M?PLs$TZ&KcC9>5wiP`KYTfIqj`pH&5uR1nc z?JUkQQt(&esywh*>E5OdCf1d zJFwO5=g;$fUetX4`zHBmSIJg;VP2x`e~zyjZc%^Kx*dz=2u*y^C(;bqwB zAL;mI*y;ow|AN?R<&WaR?fHk_6R&Fvv(?^>e@1Mz)m~T0R%fg2{LH1v`8mpl8yU<6 z)5{!QNApb6D80=qKbbjW(>~T6w7fN|=~(lurdGb=Gw-9Rr9BgV6%!h$wPn&G6&pj_ z{cV`7Zm*B9y-{rS)b4Ec@QuB*)jw+wv(>{l_RUuR?8K#P_3(|ov(-Oq=_gx#%L#mj zKG^CTmZSMxe!uC)ryo=n>vOH<*(-_3sJ*Ju^;k)4#<##oapoJtv|op%`#i;$yeHO- z3%It^F`ND(d|Yi6iFsKfn^FM*BI+jo9yK5%wa?zt@}DQlCH$o{YX%e; z*ONCn11FTW7d{t%*YOg3(E3LIgm<}ELxcY~dA!!S>k|%RPpv&b7^^J)dYyY(h;RPwb*Mj_Ge?( z>Ak~Lvo34jG3F33dV4j8c!PiV zyvMz7LihEl#{6UNUdR4@*9CVncWynG7i^*aFfrKTn{++)Gv019*zv?lTTk-*Mo^`5xt*sSMP?&+}V)LL~Cmh$(DykEsitDL(o z_yT5<>XE}q;-7=^4z!~;FCP;y%F$_M&o|eC8%M<~p%3ku%APVe%yc#zX390YjYjk# z5}WzH_w#*k3AkfnwX;eoyI>4j&{NIWmF(}*91}{W?9#G$=0}v(nzuCpcY?}Qk7+Yb zP;TCh=P748C^xUzE~h@Tiavu*qJ7SASv0v+HjA=9jGU8iOa05S!s$y7a)0x<*Cos? zUYu&iH}ij#Eo`*UOFlyVd4R?YX)UrBQ_P~TZ*b&67F=Ja|zE>!8t3r56UJ`Hj%P7M6S=bgs!!F&;`D+Iy5MP zwef{J8%qloHN+Qgu46B{`qIJ;b@7Ey)s_|zOKMpe%+FuanE!Y+`}&|$`yJV`MQY=^ z{ZnL%=GBd#val}xUUVw%C6?hjmCOG0>eZKlJ?b&zKz9^uaA*ikfS9}jY@ECY^; zW8_*po(i}Ja_nDf8uGszeT;7&on5z^W1E-PHBXE_C(qNdB;ZcuSWQK3{`i%;UOI7d z&WWB5`rht1jxB5mG=E#i%F<1TuE)NgdcRp5`-fH7&uWgXNQe3082KW-qpxu6Z`Rbg z?$bNbHCMY$7aWOYzn{c@bNsO_HU4{gv9~{ld1tTGwsMPk$F5*!Bih`4Y_G+ZVe#3sQ?@+XhA#@+aM`-%hz+NGI@Sl=oHuwxwi#QV z(u*D6vf8$dSFq#t5j*}a=Y2Nd&gL1d^J34F7ewrS*p|!gFN&0xea4m!xQs2Iv(&x^ z+2PWG9x zY>>{W`wYsKbDxw=w{7`^NLju+7+d!+;*}>BHM;(FdA;k^l?|?=$^ll=hLvcFH=xsv zeMVb+{I{yVHZulY$urNv<$p57Ox#u5{LnVE(-i;R(76A5!N&35Mu+om*=3j|#)szb zHZ+#r1-q2Aupxfpl7`Z|mIvY|u4;^*SXm!GaZxb-t}VgRyWZfO&(`_#pUVlQe3BDN z+*Rj#Xj^UbPbBL-T{^qqBc|^Mk8u zT?aSS~SVCmXK4|`_18Araar|pRBQB1?$5!mPGmK z_pV`_k$<84S)FFYX)K8vYi?c7SaWeK`R9;0V0`x(qzqJT8=lio3K}=^Q++F*yrk3`xzQib| z*ItX@7#SI}R%Zsb-$;4c*y7CTXnSid!tg5Wuj6HGta4Z6zN(22_FRh)ByUtr4(pP& z2wMk^aP1yqq*`kdsQ-JkiJH<*`IY;Eiw(Xwu4yOrl>5qfd#Y)Osa-+t>*893TGz$3 z2&ZPhHQ9(>ixAX#I7e7V{W-J8T7==Vd#*+J09|~z**U)OdW16NzbZ$2jazx{9XFhJ z{yM8|`-|hky6Wf(Rf=7Fi#*ux;cw|DCDfgP{ncJGPOWWRk>=gbvoBnaJ%GI|A=VAs ztoAXoU}YD4;hUD%xhC4>r`w>5f;-zhJ8Da(>Y2%99jEM+y@S#4b($F! zb@AU41FQDs*bcOMCRFe~wDx-C4I5c6($sn~$4I7)H!P_uy`TL}l9f=!*SdEd`7&7S z4djfsm9URyxx?dvk)5-u&h>5f)!l#=-9gT){MkQgU4yt)Y`t_NZV{WRaj+DdQ{^@s z$GKQM*ZCt^{z=hkIVM%3ftZd6~X4h$nnVeqipP| z3T&*7%O+O2P3!s^V=EbBIe%$IZS%dbvrm!-5<5GGpJHcI^?vz%&^KmWDADhXy{WW& zPUQQL|4zGSeZ>3TQd>Gj-v{kI+5RpPBIWRFRc>et${C}`$N%o`zqINI6q*n(v%qp?H(FRRqp?#oY0llZe+T-rfgCL-`?u1wnM~K z&R|mvf*jvo=-p3)Y++lt#U-_YTkhF>{vHS*Md}+BENF)@+Ck$cU`j z&^cx6cJuC@L2JDnpS}}4zeAj7IRF24p<%4Z81CbHvc@LeH`lgw>=9EgD{N+*m+~4KgKZ=}N^8?Baf|W3Io?W%2=2GmG&PO>f=P%T~GDlT8=j-i2O{T-JkwtK2KSlzEWWqeiSgkW=bCzCNXR&rV;Cjq$1{%@zo4#k{A$)! zpwmBYO@r(1O^vQ^>wN}dJ<_xblsk=#9ef9{9Uana)tZgxh8P*Fv#I3$FMP5g{?7e_ zjEwiV|6L8GcW$fk-^Knzchawn)p7U*h%xV~|}xgog!vAW>+ z$5uA(f2^E+N5mo!<9V>6!F4|xxsUM8j9bTjFwRxJ%G?`vbOZe?2*bVY-MnBmep{Kb5C(?)^P+`w{iN^9cHfHmBz+0 z#=b~go%x?^{HBVaYf9vJWNegQP>Nr`@#=~i*IhbZq55e)tA5nnIltyD z@>A;YQ>g#5iu(Kqd)AlVvoPS++T_2ksA(S8v%bbN?r$i^AJ+Yi=$uoeZ-%ilp0U!a z-#Io;-+I*eq%|PUGebQl%wvA*P|u`uLp=qIQ$K)HUx1GHS-7j?y{rLoMtH~WYe2kh z|3+&-u2vg=)gNIE$WKq@S!+O?#24l;)*i>6_q7IOj=cuNNgrNF*|U5{mBhHUm%}dR zuXp_cd%CN}KZWnJgl}jL`GO4{Lw!a=aQ`ct>s^mCMri#><$GF#Qtv-z#{MKrF`R+C z*9F9#Si^Ys@@qhTl3=d^ancV<%G11O@x!j5jsLfw_Hzx$otM$ZzrCk6{zh9Df6470 z{B%=!@J)aKR2Pav;0n0WQ%ckn+jmwHE9Q{RS%$=h9oTmz8z-6HC?}*w4C<|8XqOS{IU_Jics(`Xu=W^~qN3 z*26D_*NU{G=NPS}KU00M7v}hX!5pt;e*a(nX@T=S*NSL7i}b;6%(2_s8DWkiYekp` zyV%F=fs(rY53H{n{{Z(n@i`oao?80ozV@c2KhfrnyJ_i5M;~8pANIC4E&Y;bXah0F zYuWdM{xA-|yld^%r>wpDNNcYeN+*ynxR0@6>gI;hDZH~*zW-y)BU<>*_npf%t#t_V zHEsl4g&P{1AH!~sJIDKYpKty-zWq=5?&qzaa5!<2;q9Q`>ARPYxHavR@pS4m7gHrC z?{*IB|Iag42jv6KLsR<&UjAOn&uyBJpKdyyP(QqdvKQzBTz^ATmZy{PXwFmcbKFZM zeQ%e1Ugj_h-)H=4Xc+&y&1iVjFSk`UG*6=cRR-(*Ct%@zcbd5{pX2dfBX=&oQe~dkKJKz=iEd5jZ`7KN8@(+?by_wwUjokYT+P9GUhltPLu72|p zjJ2PB@&d=ikNFxshSnJqbAO!sJ)pR|?ul6Y?b0kJZ+i3GTGu}ErZ*}VN-Qt5LYh*I zMlvyU#ov>QQ$5O?EGnl@~4z{EWXbdtC+iD?_F{CLL)kNx+W5Lr|)!Z zVg7!4K=5%p?(TlI(1=|y#$!a~PnWH*ePF(A*{@WcSNYS^oY%^q##;H)`H^z;4dqXB zxp(Wl#NE>=%X!<3d5XJ-%m1j>J}=*O2RXJ_;_lg-qI0N)hH|Lc@@<>!vdXt&$6>X} zi*lcv6nDR2qm%Pzb6%JH+ep4szBu{0xH`(NA@05izmMm05lhdY+&3e!boo-mG2(Sj zJ(uG1j8Dw_jJW1lD~Ebxa;Qsc*BwQ z?_W?--<(fhES!g*x&W==<;295%dVjPN^8cGLlu|1f69|}@sG@Bttq^D%VzSV+sKoC z!d}3qa(&5)%{MfTpY+O5BjMaoBY8_h^8-8T{oh^FSUPo6Fy35FA6*pAqh` zjbVy?(58xg1S*I_EB4)#Zd%vlS=rmx%9Z}_2J)S2IM$eHBr|uk<|M>|oYec-Dq=y= z$FQep4!MVL?0>9j%zsSpTGy=22xY+QN9TMW*+QHs%3sy`oZiOD{|T$x%9`cTWof5U z?e#fvvLmt!nz#2hR{GPXWk<%!$od@YaBTOnlJz;*;~ryWFMAn$ru8{LC*Syejgtph zkE69Y@=5R;g5(~vwI+u-c2f8K3ryA{Gj0YMH-n|$BG2*&cC}Epnz6GId$*AGE!o01 z5RoQQdgIVm(eCdtAW~v;y*PUpu3|JF% z-B;3-@Zyk}#t?lkXW)O%j5V!qhcUmfmTyJvM-0WSH972ot#xFR=Hh!Wwryox*P0wB zwvhb`re(i3tntEG?)yTnVMqgO-meb4%vf3aVO}suENhT#{a;a@-nNtccGJeJTE(G< zDdvNZ;^LXJ4;5N#ax#dkUtE*(Jzv+F9K9dj_pPflPP^ovs;=TwwVBfjUMmR4w^-AY zLENZoT~77C_>2|gB{Ysw_vU5kBV79CG>(>~Pn+2}87&3s=v>Na=pM*x98?TNF*54z zQu{DxI7$DRd5HEq&mIP}OKkB2KG*qKtjP%)+1XyBL^+9u(OzpVviv4&HfwQ~(m%CE zhnP((G3SA&3)#Od1-G{ui`~Oh_pm-kd*f+s4n9RZ{nPQ*R3nS{ z!a(Nu?pN?JxE47HGyPgUht}X^)HD&38c@u77{ znSS@QRK?|KZ^k22_noJ2Onw5aSrgBlRjiyuy`|~#55-Ask(@+VoaWdtYm7=fGdN__ z)s}w8jsZn-5)az3=c(intQ>~MVaBvH#YL1u*ic*gn4Ob&lyT}l%2_!H*-6R`=_4mG zhH~9<5+4l-=Oh&OwsR7bIp6gw?3@JeTjw+O4GQNZ6w}hP6DLtl;#=}N7dy?t3z#R_ zISDt%wh|kGjAPB{LB0v)AxeqST!-JKH9R-qPa2A2tjG3OW%{fffaXlF0h%)%t7z~)5{YxH za~lq^JJ>nJ+A}!*oMP=gk7E})c9JLO_~wdW{`Y#GA3LbAAAJtEAaWSWyV}=}Qw~Jq z{Yu7rjz3vZmw!Jwh{(Q%^LoiaEU#la5MAxe`&Aq3drhhLp2nA3US@ilW;lk8aPG~$XA+F~+HBL*#*FJx|G`N8W8wOV9n|+z z?;Pg{*2O6)h;ShAqcIw;Ly**Ozz5k!RcaMwe$`bt#R1vELhT>&VKuhV2rt>0` zPKQKWPGS;3O*F>DM~sv(b9H__)7q1ntM|_Dj)h(?g-#C|qxcx5ilVCUHE1UWjlT?K zY#Y!R@D)%yf|x`TI|IfTO`QC^-*u`A3IqjP)5&!H;3IO*K97C&UTg2QzH2RCyZ$x4 zcA}(zrt}p7x$ahK{mrc`$mrY$LhbWW~_E@zlY=SFcNRDa<#`co$)I% zR%`iJVyp`IS7NN9O1sst%RI;59HYNFKd1B)`wSWT7gl2@2^`mKuRKc)$6-439RFZ7 zOo#C1|C&;n3U99IXVHDwEdP4`)i0)Fv-}BqDRpuDj5Jxxp%253A=i#3ux1&kD2@`_ zyX)-A1o8sE&VFncc|7+B^PIP_Z(VYx;==b+?{XSO^M7HF9r9hnsjsj!+S0h?$EgwP zNB^;5w5y|L!(4y6L)Z!a-hV99-)>G&yTA1xo8`aX zU;Eoht%v{SKW6f`%L!_i?LU_4Z}(DAyUG4z<7rn%ZHbBgcG7>($?xK)+f5w{Tf9T< zCSEIaL5w34UE58(*6dSx_U}V2j2UUSKVEBy;r7LA{byMoaUi{m*ZR6n+Vn17>#JsI z({;R7j^XykYdO?z;f_SaLnX)eD_pk;19OGrbiQFMx>(uyG#%FuH*6b0ohVNm# z)}U_UwPww~df`EMV5==U$MF4%*ZOk0e8cYJwJyw*gX2J2Clz}w%Ax2@oP`zS|OpT67|k6m2T@= zd`#?=Fdx)zX=$EYc8UE{win*wdgyo7RDE13$ePCgiq0t zzeh*1PMq*LdNP{1;H!m^V(L0x>lbr<@meXS?&Gz7@r!HYwNgx7$7}r}7a!Pf*n9P~ z7RFu2VfVU;*BaQ1S{Sj|1by*ZtPPr`StCA6QX{JLTJEuW>R+UMLJhcXYGCY;eiHd1 zy05z$82GO3m|Fu3R9zfP2^mlW6{8jES*{l2FuTm1)Vgas+zsfbY#RBxa{zZ@D zujHNZNfHZ?VisS(`6REzUp4eH{;HuT@mB>fi2nF1eGuF4kH1pZO210{m8$>S6ZjAI z`27brh`svTI*GkvtE%}A$a#~%{<@wuN2C^e#oig-;>>n`?A1kB{!2rHW3T?!E3sFi z59EHY#hgy?$6oct&(me>mE@U{oKu1LtBcr9#20W0n+c50>x+F1f0)?x(GOw##0KY> zBJW9u`WNC0(BiKg9pbMfkG&RuC4P$|(x$|&`48|(DDN|G7ldBD`o8veUkwbd(^dmR zes@a^4C>_;jc8JArzKnfT0dt19t*FKjRZo762hJ#J#A!vlB!sm4#Hj#RKSSi>-A7y^sgL;c$&uB>U;Zl@KN){HB1W{s;#R${+OW`6@Kxt+b~v1B#&vt%`D zn>}k3_Vj5k?1B!L;l+Z|g6Y31HLA-QcX?bFeV^Mg|9hn(^VJ{M`rnA**~*-K_k4O; zqr?U>XUmzd73>`9`c-OH45m-^jI>6sic8649}q!Zd#PO!`AxdQj~W>? z*8{aH{y+a*C$8pinwaam#P|0B^N6`9zQSE%upcyHAF3C7K6ctO*RNgiH^0G-(oi(D`n2Rtl|DSAI_YYx=mWlrPyXV&w17=hd29bSM-xM z*lzmq(_QTft>%ry>nzY+ZL4`B>&igg3i*Ah%=a-~ebHmsf|&1HnD4|T>SWGuVUEkZ zFJ`{WycfTi%=2RAx6FN+_wZBs+Pt5v8nih-S?$Lh*E8Ss)RK6Z`5nQWe&`bO{RHzJ z84z^^8Gu}fa@zY3CvWk6^yQ<-&j(z{1!Tp~*14(=5gT%ZxWn3=F55BUK&G!Ho+VIg zqIRI#@6clv51`=j;bC#CG{qVV<~ukojdF4wXT}yi(FaHi)`5LGCsY+ zS@ZPj(t@XnKj>E@*Cc+$;i!H#P;)}!SS}?)SHkR-Igw5JaiJ{A#&z(xL3C+cXBkpOMC}9dc2<#4m(Po_1%os z;P?*aUM#G^9v>&HYMgMMaKf2BPMFg}g^ODmn=<}rqN=gA2k@iyB)&szF>oP8W%0S= zcPCp-a58bQ#tOOI_o4d@GM^>(n(?9KcqjM!p+r>)m$_Puzt&%CA}WageOKO#^;DI# z75z=^_zt~HZ{x%TX z@o9$VTm}9M&S@n6!-LPa7~Y?J44xtwf&H|}FJLaqGp)v_&3VPrSA5HSd>tnG$N2k~ z)QGd$9*^vQu>D5~v&%gmUlnGJ6Fbc#{49ntJkN&NEr z(DC%)o5UL6R|#8zTqygIqUW6WkLY)+Ly6>if4)Gc7_FsBqFj4%qJA`7S0hasgd9F?EN3P+TUzAtym0FOM>{z2jFX7y3pTj zI-yu1HhHqLks%MB=xgBaxvvo4`|^^SXSj#ih5uddf#2a%u5XeUrBynm zFZ%vzzmGntsJ zQT!Wx(?YFwghvq`wnSnq_*VOOGLG?6NSwvEPexjkLeJ0N$G%YF7%KT!;uswlf9J08T|5V)+*v>+vkrna7ParCVo9%d{~aTEzZC%KadYz zYBUU!ImjA7@JbNNP3+el^*ql_U578{V)<`SN zlB&Smg&a*+LW1PV-lHSEQVZgKc_)zrOJj0UMSjV3B))(#ZCGr|or5oY|H}AWMPdtH zi_NM2jBg<_0@)VE9F(|%aQuvYCAQ%AFL~Q}{cPe2_QF~R)q;@w?4}lkT(>Mfr`3P2 zdb!pyxHC;|ai5K`IbOLI{<3asLCCc^ULo^cWDo0{IozvC);Us7Lh3-s{RZ=6Gvzcb zj!UuNbJFTT@XXYD)MD3y;|jv$n)voTBGaT#*TgnAy-!>LwvI^l8Corfr7^h{gW4;29>tlWX-L zma)#+e3u%T$hp+aiCVyzLVkuI4=0`PuPWSc9RH8pZdWE-9cl#k8^N_hy5|blAm850<>cI39AYtSciSex*S{dwTBEZVkcq>X z>w4C0ktb+(n%^%aXl?J4wyd2VY(y5cgqR~vCs+(E{VfJ!dS8|te(-${upWM-$ZdO} z&~14BRD#8Hu)oE$+*vbeExG!RCRh?a>u*We;K<6%hf`XIoOC&|#^*T-;F78*kgH^3 zfumqF&t={YgS-s}Ql5+VF(cUY9AU>cKUr#Z9;WhZrM5Zs{UyaxDz4<(uWZ z3o@3t3$oaYPL^-H&0alOzP)^BX)E;`_^x-K;aoUU;-qVyV_ZDLxELen%elz>*>Zl6 z%(p_$zbADZ z{52d>gyYA)?yup1ZFQhGqBc~GlsXRBfQG__KfcXnn0}s`3J>#6`=_aA2U>6wDmf zr+X&t<=K&4jquj>$gkP}gD*MTSqD_Y;#V9%w$%(YCkV@r4MeMpf*oU09(lLIRi&U4 zmp=}MvjdlZ@mel_Dzf$(E+3gXF_E?3QaVh0fWOtCw2 z{3V{pQ))XZT)yN7!*(6S=6eHdejVdwHqRMg^XvR<{*Ml)U(4n*Hb*nZXBW5)b&EDE z{tdo!Egug3G*jAD2EkjdO<9P7SVVe`KlpeQ`!G~xtK!W_Cq$H`t3mKr`kXEO7s ztNIQ9#QIg{##-@fVqect^|N%?+(!BB>lyXP5o~Uw0(?F)S>yAm>32xg&%8{`!%UHx zzT9RxlaWO;*>^@zi>i;r>+{{_ZpXe;n$zBEX{9)^<9o_lZqio(VpG47rEGv8$)U=ht9YAC7(9@V2AcyWYp= zYn(kcwkUoQg6(X=)T65v;p}Z(?_<6dEMrTFUhHN{wBObijjc`UH)wpm*w}m;OW5FT7 z*&eSMq(omGq(m)p8K>=XkXwv(ql0+}&p!QiZ0pMyyYRA8w!xvXhM90~q?!=4hDjB> zOBXWNLJZVVm>!EQrg@IYT-N)+br-tiy3v300y0H(TE{<=8&O1~rtvM&7}7H_u^?X^h^4}4WotY zj_wWDeYOMF%~}>2gAb+_nUHZJ-7^k3`aoW3R_&sVqlLX5#GHRbY;4~q*DY&_K&^#n zen;Ew)Pd$_(rsTCD|is>XG*o`UPnnJ2XtKIOS#U$xl3g%bM* z*UkIDbt}IS*(tKNF2l2HYd_~K>IxWkV=G`^If(a7U`>)F`)+K4@kfHV?hjwKYV{Jl z)LIa^j$C{Mkr(QYPto!h@O-hQu|syWwZ-G*R(tXM0Ab$(CN;r;i z_O`_z!Ci}OlJ$bH*N=ZPQdO`^?zcL$J*=01JKH7q$~*6&@5J_qU2-tbZIGG?3&bvI z_t_=IZ;4G;vrA&r9WBSW{$>|;-GF?*4u5R%@yBK5o%rK);g1W@k6*!&3X5?OmUO>P z4HK3WeRr3{Y;AKEOtZq_ZgCr?qOYI%47OC-2p>$XfzZXAdqSs%?4~Wc@xCRGTHu5W zo|Kq0{8l=tjd0~L>LPH?CE6^|nM1`k)57@$jBl}}$aR)ruaNu$hdJ*I_mDmMY0l^R zx)Yqckn6I(d`_+j2OCPP=QD@7ZY|GSE6>lKrb^BuM-MTYqhOBlshjok>@dVfzTsSC zhnDA7)+C`BS8MrK7+o*_3Zok?zPCL(RXND@(Eo#LK?pI-GlwetTbD6?kr&(Sc8SJ4x+wAj!JxWf-q9Y*{i{V?s?Ueleuho zlaKv$HYppBo1Wno#ep1$^{i)4cpvK%$E|#RlJwR z$#&1tUM!UMx0s`y#(n3)(Vq2jw7GK%crW_yd%)3Nu9W`h8Ato&Vma5=|CVyJ^qp|D z&6_o*vm;0QoS&oB`8ZnhoGv(8B9U@kK91Hrr+bbz_g{lJT3sIYkd?9KIbCzKxyyRV z(f;T-j6^paZE`O-+89}|`+zc%?f+Ji|xj`lhX?N0V0I*}c+KDhxyEB(Emxj#bd z?{0oDd z0ghJqS=mPj5Bp7ms^62KhJTiz8cy|B^{_gIg9&N`zK@7w{mJuAK8kWU5%Nh0BRjdm zQ!v@b$Qof}3%X=vv4Ks6lbssO$bJ*V$bQg~k)7^x8=mzuveP;-vOm8LBl{AJ>`vxn zS%8scE|z}`BMU1ljBFo2BP(;$9yB+Xw$07R<~H+M_`C26%)CC7*kdCv(y= zclGl--ITQql>22h7-#{LBm&Z1Foy#`W zQCcwSgRU9a(a4jo7}%*Wux@0@v}+kyNeMd_4wM@`dqC_Ud*9wkfTL46N=;je)hKRkKIKKHC)o zJ48(--)7*NI*5uAX4t^&;o8 zzn^%~$Gg7gw=4g~Z&!}^ig)98VWX<$J>k3*_*&x@w^8ij28YWwUiv`J6MM5TuKqrn z%5{G&#{%|b`1s(w+DUM(Q{Y^QqdS$hqa)`^{^HJ@Yl=K7c8IKm-&}eR|0eR&&beCZe4MMr z;^SN`!)}su)v?B_ko)*JS4$l=!^ivY)dlBjG53OV)%iGAdmX;7HqLe5R7KcM?S1Ug zX77SniyFkaN)8~C$go4O?D)L29NNXZ8H4!j@C)gv7oo~Ji1VTvU|q>Yge;+^O^n?O zH&|sKqiFfFHLkWje|9+Yx(vTwp>U1DvP#XB-@~{H-%1USxx&06r|RKKv*8W)Ag|KJ z54AdthVjmM$aR*aKeeCOdQ4^8#zg>A*3Hk;?2%dyCnp{g*F!nVfmgf(SNC_Je} zxKsRuB8OmG%@%nNd4ACeaE|G+7O}lP|pD z78kkeT!!c6I%{+q*ZL!w*Dw;1!suLJzPHc@nPzB2?wx^Sb-J>qxLjG2s*rihT{g-2 zJmDB}uoi}Oi8JeYiS4UF1|C2b?tx={oA|zE_L_`!CAOy=_L{Mk4&&o_9^3d`)TvqI zFh0$5n76nOI1y5czvR`AAMGYt|(mVHjd%j3CG%RKkvr6Z49|SGYj1XV~*jgdo9## zfO{%uui=4bsz7dP9P3VJ!PpfgSx-3}HBV7P{iy)QO0Lq;zv5js2!~589KLN4V^ZT- z;aAJyy*!_!`#DzpNxauA##tN3iXXn>G_ryHWcf$%?l3u?M&tt=>t#3_;aJyO?V1m8 zDgTNO&?I#P`CVl7D)~-T1H4hSeB1qeCv;cF+vqPiris+ynJC{>zO#H&X(tS8Bj@pr zo}-@6IKKZFzJFHWJY@NQmve(;d3XHkB>2@~*dYIswa%)Z^Q*b`Z;4;+X%6b|0Kb~M zs^|P_ZrLBsuTKAmj{NHBvg`A!)Bn07znWWiV}5n|v~QbVg{I`xzjzrdfI*)_*Lf9*As~ainGNy zxAvT0&D{}Tsf1q@mI`}aCzc8}mAN0xtD;-0y2&R!=_c|?+d6ZrVyh93szYwfWVI`X zsu<>>1HZcc+T57M$j5OP)30GyJLQx8*Sxja`-8XOdGI4w;u)4M{3I{O%6;gr}TDrfLpbl^mD8IWlc!` z%%$$*H94i>I&`9ccG@#f-YBOu>`WNnfAnfc=E>lk(#z8={+!ZtbSd;3?_cbW%iV$9 zXdL4x_8_|G0AokE)tye;)9|xePU)}GeatG)2D6&G13jrRtDgLjPU&=VN(V8k zFAv8iHb!_abSXKdb9V@{D!zGPRwce&Sk>(w+di>l2Q#bZ{md#kr8Q=Cr^j|v%M|?X*!oINh*tcd(r&>W>D_Xx>;2s7vNYDJaH~s3nl)~9O_*Zb z_A}mPIDS*sKhEK1@QWunJRHV3q1u>rT!IbvD)pz*DyYWO+AooC4L(gl{FypDe4Nb zMGo>sNZ$5MrN(K*Ua4ytpV-_cu`ZdikiBrwx}>{Y!!J-9BNYGMySA9qbvN{|HBGY6|icD+p^Qn;?WGj>8cJQgf_laD1E6&;U zW?b%B;ZtRgBz!7+q&neKMMnDgRBte!+NU>sD$GGL@^jMi(wf?%16il`fKUA*m`_FS z-0r-?vWE@usnRFHr@j%F+i(m%^(^%PG(MF*gy8J$#oMbNl;5!x$~P+MXAa~nj%S}8;8UYNoaWWo)-~iEW}Hde2Uf*4RiiF- z*yJMEucuZ|^-A91c)6BvsqzfMF|p5Y<5OjiPJWCN?8{Rn@9t_4r{o|00$ZWv8kTr@@($MvpZX|$UGSKXPn8<1C3mLO zVH?xlp`op*$ zdtr&6QC)#N+^lahEJtQ;P4UK}`;dRu&bGpSTzj}MsS*PzHZoXLpM9*c1)Qr?q}N;og9q@EGsqw-eULJBlrk`x%8hm3^bbPTfIlfpT6I$EfKt zO8YyuRIdBF91Fu{&fcf*}NEZnJZl){|~J1IF#FD9yb;U~8c|6pa0y@6b&@TO0E z0zmR{HMa1?oCv~;7CI>&NR)U2xGdCV{oQnm{MU$g)5b3gD3r+pC{E~lGw)!Pg>8v z!jtafU*Spn$~zuNWQ=jG081(ysqmx$mh>ZzNgqi5S9~B^AFM+k!%B>&uPeyqs_%v& zeV%-FkFs|!v&H1LE{@N=jP2|PhV;9^km`K7SnZ0eQzeF*+$`_(pR8<!kfH zVn}tYW5ay?d2t6mxl$iPYN`7kF{EduKYGZJz9Z+_`rlH9l(8rbY16DO7}6IyGNgm+ zx@JhH#Q7M~!FAm;q*KDLWk?6tb@sPDGY`c50+GxE`im7?X`W z%dbt!0LJihe1}!Ygr+y}E0fDbawk0~-$ci~-!jcO%Mw(#nRR)a7FQWz+4HCo=j0vzwQ-_1 zhFSO)s(cHt^aJ1GQ)$my;U(4zlT`iWSE#AB&>uhkQvFaZeteR|k2mo{T$03(pK&;C z?{e-e_6X%HH+0z2%Bg?Uu$N=9KNOCkz+u}b%qcbu5An=>XxGSoyb=3bE^Vh=!Cok9 z7a0?;#yA?}o@FtE8}MryJuqqbAB5+R6aOU4(qXvO;zl02+?{!1t+VXM|n4~^)>Te*3u4f<-71H zx0Fg;xl@Vp$Cbz6KZqb6LCf39wdV)f=)xu6$sxYI#HcR~oiBCY402EQl7`o~AOD6N zp-mI*i4)Ivk5%+#A!=;F2#X^7SJ%(X%4>RS%==T7*wQd1vTlO6FZ1Fae2R00OFPuZ zv#?>Z8oM4_9`mGS?eHc=zKLC@Mv|Y=SogeQ;`~AMgYcU9vYy!YvJxxjmW6u5<=ndG z)HrgqF)YT)p1q2E9{JT=_vBP-?5ldKUd~N zD!m#h*DGQ_BJbkFc1Y~H5ubiEah-Fyk6u;A{3mg^^>U9xQ?1d`?lf)cCa5Oj*`p5* z_57T3E*u`}nJMpga(EITz4%pA)mZa+HrCja9oMueo=hCT+V4` zjAqOAu{+hDVjP~}99M~PJa%>u_Ly?)!jafoDzHygkvpUEJb!0Cda!HP%2?r^*$4Z0 ze$G41%qLcUA^zOAHAXk~~WB2q|qq)yBZxge+oml)W z9^2zE=J`BVp^N=0zNpCs%-MB5c^CJ=NcL>Cfw;eax44mWBJV`*;jc+V&IwO59)5Q! zy8A40Fj-@Gndgj!a&nDSAOq*%`>OQGz#K*9zF!8mw#z_oXBl|;8X4Gqj@x0(^E1Re ziwr!`oeachafp43CIeZI`($ABjLRYeKS2gAD(`!QSo1)g%)=k?pu}QgWAmI5-`m^v z>O=Wuqfhpqr$mU{Le3o_$6Y`M?hlfIhZc$q9IwQP41^!j71O6{VsZ?7VRm7{BKMB) zMqN#|DpyBXqppsWA2ljSKJJu0g25T$mybh{k148t(ml-mfAq`8p?>+8BJ<%|`8dgp zeDuhBA|E{^(KA{_KGH|g2ha~e^0By+e7uMIL?a)IM7D~IM7FldJ&}{GB42~#qfunz z1>`OAv8a=LEDVy5`N+rle))Khurl%ERb|p>X!wBd9uhcOvMAohkO}|QJ`6C^T|qLz>Rj~8vR;MnqE@uQpPPGx8Gh61jv-8vR@Bk{R#FeWdMFYlz$nnW)`MY&6o2m}GKs zt6>+Ao5;LK-fa|ZE3jieaI%l*75ZWBN}X4eX)7M{J)iT(TXiB&;jxX#(MgApr|7tP z+HK$(dXbax$yyt^2XfZ5iE}ybIEUQ#zkfzRR%+)wuS79cPeJ7K$ccuBdis9T3l+YqYp@3k#hEqsd=2Hs6^wdPzymSbRNan&>xjy<}}e?(ONXM1R#^ zF>RqHVjgku7mr>`sU0MdqMU(WN$usF^Iv4#_w9cqSQDBE}`=G+?Q|NvfNekoU63vIhe(M+wC=v z!|_G0c4m!nI5M9_uB&iKqqmjX#%{sabf7<+HGQ^$=OcIVv`UxlX{XDeYI2|Pz_S$b zY=z~eneTW!nT_;E70;5-bI#|UwM#r%<6Q2VF$>|U@`d{>%^Fw8y|y?qCv1nW#z#8} z+p5U@`NaH0O1)6YA-c|Odq2-*$VBc(!uV*nWwj!IMDEvRlP2?XgG3_4c zHDQa7JBpqCM)`-?v+4?XX7Vf5l3Ph1B)i|0_hN0kUSv1Egz{+qFKg}v(Suj5k1H8= z7JL8kjI!5XjTyXy^=8y%v7IF=3hN^AVHjV7p%SLBd5Ws#>lEJx^QL$udy_SNJdKy( zm?eJ)+_7$Rzvf}WVrvX`qGBqEIaMat;U4`0?ZiJqTggR?ZvxxRZ>$Q}sg>uHzN7z# z?WW(cxf<8Ro?2BsRcXupLA(8OZ@CuN*%Qe9A$}EE=faj%+OyP<%h?`})uRaGnAHS( zE&YoBB;OuyZ)nK&)WVp}J*Kj2)Xi_g+e}CS9Q|&+EA`Kus$5WW985fTDO{tI-P{kq_cG*{ru1-o%%~KH)i7P7glc z{Z?|gbA2y*uwnz_2H7F}^DF56xRY^;vihFZRJ-0Bsw!E}EE}GhDl!n6G@p8-Eqjtw ze=I$4B%L)T? z_3OE19-d3~s3R|?mz|L@z0aTD_swYYSy{Ji_?cZH&aug%G3>s4bks)E<|uQ}%lapf z<2UgJIew|FTvo9v*3#5l_L;mc@cU)urEyJ7yOUKD-#A8c^pXpFANLRB2;U#b5zgE_ zlsI1LC*$NEeq@}v<1MXMV4sO2EBqRcaJ| z)QgM{nUA02k7W%p|2RC?Mb-eHPFFkS{iVicAJ1u#6Ff(S%*)-#GP$;`kEebCd2n~! zIm6DLeXH~baw9~pOYOaK@xvm=>7P^9&!?ygW4)^!z|q`0ruet2@&&R*`USaBDetly zxq%Ew<^FAq^qTk?_IHp2axeBRzC6Rc6SZT_=vR?Ndzg4b6T}7XVF|VrZ06gwBIYEC_bq&fzH_>a z6B-97N!({P>z{1lhS}qdJCWp7ib`uHP;-0&e(NIkE#=4%58tbT^;Gc|N6jG6#22^$d9s3><@keNd<(xbe0tI6Ly5IxtkQ3uqkL=PH;Q>)kz;G+ z`xP;-c;5=!!OW)%Bb4wK?#r4`+n=@!SHsy0dXQ1~ocoy){s~OfxuKp% zR3$S8+nZ92{5wqFiOeGgCso#tjY(F8y|Yndq43+Kdbr2Bzs%=UdYEupTJG=+|B3ucpl+7mdp4Gl9gVW2WP0cNKU!T#)0PX z=LVW5BRj{!K$fz;VSd;z3%et8T%PZP0g4h%n?RlLZ>U%J)j;z+d1h(^!<=P3`vvEp zf`NR8@wd!lActqov-zc2&l2zUY(3wM=jt+tr!Yyd(l4Sb=GL>X-JkBE{#Sbr&$H-V z>QTZoT${&}eXy3pQ}%j{y}1h|kLP&hU;YR=ac7=FFUefP_CZcu%VoxDcR6wYyRbQO zF3(-ae$()WGsVwit1wF~Y26a`uGEn>ur|I_6VCX$1<54m^~0~|B+>x1(h^EGPUp^JQtV_@UnWqxh7QRiFcm-v`{^Gj7^ zP6p-|b8F5;<`*^=~7rKU+v5oSA30~KR9QuX~b`$h<0KUZw134)Tu2kb%fQ53v))%&#JUZrnD;7%ooggo&Q4KbmYeA4|3m z`zG0*`c<+$jXqAGUq-X999s#K^17>Fnyd}b*R_oCD&}aV$k1~P{_XD0Ida|g=h8ie z%>8_xZT=PH!9wcCGoQ2Bo6jE1Jw>0r88_JchH1ty(bZLad)5Ia#zK*-li+cmI*4rC z!})n-qv4E0GGwmFdWALUT<%}I_0GWw*du3ef<56`@>%1Sqe~Nz;q%X<--xYwPjnYL z#6uk6c=T7kr_|N}Z}J{%b;*yL&0aZQ_AtU8;|FM9TxIj@bFtmdC!VFjpC5O~VVFX` z)d1FtyEHo;-{@EPDrz`?k;B%oXaj7)EG0qKTI|PX`Z?n``M>1HrH^Ma-gM}!(J-(L ze$H64$K`KYEG)C%9_KlS?Ji90cB}2x4Z)1DFr2l_8|OW+F(cJzS*tMD3l~QZPJoB2 zzsy=i_+o(^VW!9Vb2e^378lBx{D|-TCS&k4X;n-2v&-A+4S`w+6}24>c+gG==i3CR>lra zkoP-+Jb0D&J2AitU+u}JR{49P%NW;p)}qVgnZ#bl{E}R>%JLG!{-b@|d$u{MYxrhS zVn;ilu5obZf z+g+T`cIP=K{ox(zzS=77+?vf#^KD?e+w~5A+ab0)yPD+pZ6ry4Uh!=lq7D6bZutn< zV{CWZSRlpu!Vg^@xGVUaGpzt z4vDR((X{U61Q+flOpHuz^*6X5ZRc7-_Xdp zzoV}*SQiz;_qDwLk~i{0uD?<5jgj+L|IDIYU;J4cbxH*CC7m|4_+8?U^l@L6b6CIj zm1`ErJ-CkeY4AIUpCguUHi`51@H`2!cWIrfYQ7fD$MK@#_i?!QbIx0bhq|lY;hZM? zH*#J44K1v>l9(Ie9RD@)Zk+afunZ%fSKx}fm zN{r94-XaIPXDDmT2Uu%9!raXG3J#ZfI~3VARK`zzA9v-!KJNX8`?#x+AL9z_S<@F` zW2^KSCzeyAZ&L{}vBdZ^`2wD$M&A=_J=HI}V7Jl1ch)n0g?D40QYGK2u(V(tYp5~g zg&nsITh_5eCA=z8iA6uvF2t_4#BP}U%>ZjSbw6UaII_lWpl%Pdw_S{<`HYdd=&RZO_22(SZ%M9goyY|k z?nYs{ZLvAS@Ix>4bKUiD-LNPN`0-p6kDR+ehyoI%zSpDmL4a6-lvwuC-w;s>X)?}O)dpE=I8{x}9Q6 zxU(AD9Bm9`rWw^+u_e%#=NLC}i$hi8XVTW~)xULzH4MAK(9hD{SL>4$^sxIX<2#D| zy6kHWjP0y9?yx4veb}qEGRC9io#a`!^PD2*w%xx~?A7%x1FQ+`xud@3UC#~lCb-;L zlTHm%wRH&ohC#^Bq1YnFmxb948~wIO;>!npI#g_tTHLSLB8MW+g|D88ym5q+ zvUIZ|{x%nTp1O%%bn26R$SDzm9y%xO_cQ*!8kl~WHDuWNft8nqwf1w?+2ksYk@j!- z+pEf`%UmN_+AiQ4=&y4%v?a%HoCIr8+8*FN#h<6WCwr`OwX{WF_o<>?ASW<($!eJRWae^;tc|U9 zjoaSCzrt;A*MhiT6j#y#E$^ri4lA8~!wBDzyte;qMYD}21R zoxBk8JRA6CTE8;}&K=}j@(G93aPA`V2@3J?|8(6G0k%7R!p*VW=}&Tv zYuWC{18jHth;wj1U9#Py0&I8sL-;$pX1nRTcD6hH?$bZ8-QOp+dz6puZjKA^qV4Nm z<#(T?cV@eD(uD2aValD?k?nTQxdGdqByo#3X1h<%xp}tRIj6U5_p*E2*lt4?Z1=y4 zZ2~_?5ZgWE^7=TZ4IaYJcBdq;-i-QMY-h;==EV(lH%-8{Z~rhCZv8!_E3YR2Mo z?275W5B8x8raRaCMr_X6oyZO0t&kls-8tizAUpn;Ot&!X_&Z^`@xze^n)p(U=|+!S z%XAO0{!y6j)qbWMX2{2Mr>POF|B5aQS0muhJnu8F;Ao3m9`jNYp^fQ|XlJ@dl#9HR zeX}s#Vjl>w-17HMEI0f45zd=rxj*!?+$)fgJz=>YhvhEAe)lA?4wHr5k-06;cYT&S z$MOTqt#o3!b0)*%^pNGwnFR}01bbBh8&)a)VwlN@N{4M4JeaVi^I=ZQTVN<%o|+F{ zFEzZx8mADRrHDOFeqO1qUe#xO){*6Io^^eeJL6PGmOEz_eCK@Rt3mAFd-(SC=yu{m z4~SnVuOxFn_c-!xv)tr|4!u6hU1C8l_mJgow!o6~g5@5*SN4xGPj3;+t-^BGF~2^v z#Wc^6`PDO)yJTK~jr;Di+|BcDKg&JD`i3dTChGwo%iTPucP#gic`!Xcu-wr@=JlNA zE}7Rmmiy_N-m%O9=lZ{>d!jci_hUIc z&$TS~)4?qFV^g|hxt|QM+>bp6FL*7>Jt~;xek}6`mfJj@I6sN)8wtz(BbbDrps$wp zoaN4070_4TEtWgiwjg#$Yu7Ay$*MmN%iV1EvD{j1Z$Hajvh+GE_lN(LS#II2h2?%L zwt1DX+}IP6eJrPvn~&wrX}x)t8@pi#mb;|k(_XXOr)`P6 zvybKGS*G4fmV3xjv5j5Ja+fUaisjyh?C(9xojYtBHiQ7nz2wJpLT?4jU4b1ySne(N zx9VDbEO$R)xxdg@Zl#~baI2j&XVnW#&9RgH@ptRJw=XRhv9CO?tYCFr) z#&GN3GFi&t7xg}d+q|lsUu!J8+>cwac?!d=_c7d-RXu08AOGbQAH%JWraifrR{0oi^D2Bef07J0xhdN7bH@n7 z{qWcuG2CtWxnqUlepu~_;cm;%9UWk}AO0ye((5wZZTY!lh2ehq$Gu{>qlMvqIJrxP zJ0_UnemJg6hC4REa6c@0Gp=R0V}lv)hjl+N+**F_M|});bLI^gZjphKn?E1}x3#w_=eAV$g5gF!cFxbOj_NtXt)?L#h2a+YNPce1 z)}Axm>fOjkVYv12{E%C1tB>KfZ~aqdxVz5J{gcqTn4U4*_O|@o4_%~9j~MQ@{M@m^ za6fdWrwn&ne(sn6!~Iah%`w~$?W>FFg5i!1Fx(Hl(=Edt8(_E}+Sx6`P2aUM+z*xi zz;J)B@^h#881AO=T`=6i`MLGJ{M@;#eEGR84St4O@5|3^ue$-meWU!`dYDY|b2ru9 z1jDT--wFA-?RC9mxb@e`&n;`);QZYByO5&`qRo@n$Jx)eGu-BRtT+FV{M`D@{hH4D z8E(Dg*)54_((0#jkDlb`*5fmh_3znshP!F<%`n`Tv%AaB?OY|c{s6-*%&U*#PFd9z z!`;LD+V%ZbLVdD#B%?E`ME7tAIt6BI-EJgxYoC`+?Grq%WZDG36@)AndIlz z`}1?_efhbYT6f%Ov9M>K1G}Y1ZgjzNH>tf~xuph)W=Vl9~+^K<8J{UJa1 zw~?Rw!H2NDT|YlJval1=9jYo1-gT2qcW?7^KkU!XJ?GUJ=Z>B;-MQW$nC{L@ckYg! zGu^q>e>l@UVqQn4`*ij7neGvP(~;@Ut-dkSJtF(tX1Z^jpWEr}In#aG+dHN^HG{q2 ztz^2HUy`4@$z+Q;UDtD_+u0CcHe>npId)ze(sdZJuLMIDxO2zpKJNK zd6rwsbf>h6E%I8X+u7O`)7``T++s@z%Fk`F{nyCPec#xC9kPdP_wCEi9Vcw}eZvAg za!=Xr_Wa!U^{Z{f}G9cE@$f z&mAXh_q|60eR*TH``&#w$##qFRZkq$gTDOS&G*`3ET+ZrmV_VJ?(YoSt=~=ED_gwX z+zs2UUl3y-);-&uHt74oc3To|BipTqu?TyOdZxFL?fzrr=hiPsQlgl{VXMq))XG@P z+1tx@+s)mu-Qr6nKeu@p>p}UQ{M`0b*lxWqKX+4VN4EQs|M>^9-6FG6{rS1)QB(Tt z_l)iSF6HOeV|xhm^{3?LZW`ueyDhEXBewgGouB&wAKTsh`!3k-?9a(v2- z$5vPT>E+ZQWJZ4-iWrY)7)r;Ks=PD>^K4>e(>=5?NYFY5Q`-KHFa?7JN!EDH6o zE*D8HApEx<3{t}nasWTOpLxttu3;W;%zej7enaZigy2_KwovQvJlAx&Yz@>&Ys}ly z_Yk!XlBHi{Jto|DS$vKnb?7f9S(W_qzQ?I)7%#`Ef4EE9QVT{$y|pt^+wg6vZCEz? zBsCMY8i)%wEk537a-arc+3VEv3nM;pG_`XZ@-{85GE&Dc&#pYXoOkqpCnL3TL!?in z*0uBlT;pSMzYm6bG8%_^MydLrMq(R%MHs^m`gk&6YN=c386|lVsB70Eb?n$1o}q5x zyPO+}T~um}3fBnpbLz5ijpA=Vz{at_VcRG6QtFWvRZ`Q2c8%22YOEdV8KdeSz5v%) zD7r)XoSLnd<(|}Hy^KG-Z%}Sp!-f9TL3CvvCU;O_BM)8f&OAX}>_O^c=37hcii}Zs z=lLh7gLVh+e6){ejMS@__bAh97h0wMpwupmllq0F)VTSOV}aU*pYmQ-mqnqiR=ZGY z5K8UB%~FHVsr2*LF6`&4U0C+I^av^QDtCWWr&ILI zll`wTXJnqxe=W=G(F)J@N-=G0)G&O1x*E0YQM1$+_7Hm;T0PX0OWXJBLcKAx-Ttb- z?MJ@0F-1SKX!rY&`>kO9(yj`QaT0B6>h#vWv~k${ZO;4JBpl&>%emjiFtzXT>8f!F z*Pva+YMz&NyXtgm1nvHLy}unb<>WaB9`ECs{SLX@O2e#?oL^tSyKW(_bs2T|cwYl; zH}j7DX#3v-?})EN+J;FT$A%0~?iOl76_IP>@)M}*7Sdl@}pZFlN4tiS2_#SU8U4PEGbZn9fr&zPxTX0QAEv zIoWCpU0Kgj5Am6NYDq0Asd;8kq8jm4q8jsiWaM^gi6I-FI-hKf_$1jHv&B>JxQiND zdB{yiscj<9GneO?Utl*r`>Rsp*bT^oMGnK$rrH1hH+x4~F%mFKZN;NPW<9`m+Lk@izJHD*gJx+#mVm(-%WbZTbTK zy_3F3Xww(T)^KE);Z(93E;10A*ti=RM!ofYQct)~rZNnfC^@`EU!3SpUr-PCQ2)t3 zeIYtk^u^+sAqnVNBfNUmQNggLcvvb@`$%=K1smI^hua_SeiU+aIJa z>d+-s#_>vw=nIj@r^rdaE_uYSOCIW4m;7Y5=n}cN=#r%|IVRC1(vJt|yX)wZ;oQd} zxt#?0_z* z7Me*_#q@hpQ;g!1dtD;ZPCE zaVOpKPKM_dYAR;GGuM-`8rh32HUm8qA@`+y6>U?|F>>#^fV>ar7~cOCbm#05H5$ex3S=n><yvERP{~dNG-5ek6poY>`herR=RBCce#yE zw=)>lLiE6VWaC`!ozH!1 zdB*&8j_QNUoz({xI%}SCQ9pQtv*5+IOR}crJ8GWF;~BQutJT%^%n1&cVfwG!#urbe z6HDeaPAhO5-d*INMtQm#gMO?=KgNhYS{zGmVDw6me*D)w^rPsU^~7W_*MjwaB?@XimNPxPp?@n~5u^8B|tVGg8%e$_+1jT$kuOe{OhxT*2TS>*Go? z+v5rbH=x7*z_^0u%#Lvdr>b|S_c%AaKdzu8vpufh)Q(#eSD&dsr+b@eRFDR}+ z?~5xiU+yZdpoh8PJH-{uXb8#;pS0rMu5-hmNLCc&cOtvV5#M+<$(n>rD?|@^t_)C6B)$X*aLq+tzBz4wQ;=?d%J<$@T_07-0*uxs+79@^Iu~B z&~n4eUczq!F<}FV68RwW5gUkMf0C-O-=CjfQt%WytdTXA)ZHD$`c&%fwon6Ha>Z9k ze~T?7X#Rh`g83h`&JE;`XZ~J!276!Hbz%-C`18l>$$x$tIhX%t^k9Rm#XRg$*^?_W zcd_MEVkn90%Uae~+kv&I!!UN?#>MZ1PgcxwoF$i@{OlF0 zVrMA5jX4k&LDuG_@q?9{h&j+ZdG@mS87AiOna{~}O`V1~=5zF+Y*iu0brJcwiVyvr zH<~%-IrMkd=tHk4(Vg(`iOH2Go<9H!ws-=qAr#~jQk^Tiwt8Gq|y z4w}dBxO0ZLB+gMM{eeumHs&CHu>Iy^4ob$a?2tcxhF4+^<&tY;*Cw(%|s(i|K4djn6)2lI7ml{FN_}P4KeS$@~m)|9Se218W zH~5afj-65F&mXUE((=d8Fa_n0AMh2nJbRcD_7%0{WghbHID!$2oB?V&(N(-K5jW!9rVW?!kFZr-vM~)!7XK!#BvdMX!Ezd~aHUo3} zR9Tpkgj}jZF6|Myq~(uCE=m6QbL5ZbeG6G<7QOB=zR)s2N#Z`UksI0d397P|_0Sn) zpVM9QLUD=l1@g-ku?{OomUvhbRbZDW-r~%9etBuu7;Fe*cCsegc!%BaYh;+zA{QC6 z&25{u&SiMpDf>)*R2 z-@C{~S=%EQ2OdtcDy;R!6_ylaNgM2;89~<>P{lVX)!Pj#xt6S}Y%$hwJ9Rkx*E0pK z#~M+t7v6%LMV3$6R8lYrIeSLrtjKb|oNYjcPeRTXAZP1E&Z1LV*4m?^_}k|zoSDxA z?9kXP#14II1?#dP+3S^99&(SWrw5ud8wZ-lpY!D*y&J~*Q}XI;el^CC&_xV`zILG5 zvS*-q);IWqzZz(sw-$S=EqaFeTZ&<*V(wNR74{68nwW%!^{iTM_q`-0=B%|2DclTy2YC z&@-;*qel`L``zWGw@PkN(K+0!kaLF}VBb>1JmCH}nU_8_!ym(-U!vI-P7Px}SNJ8i zYR1w0(*wQAI#<@={X^NWIc*13ed=)JUyJBa#!fyubQpWa8|0-=z5iCnFl-&CCR*Lr zWZNCq1bP07;bvviqh`f}FJ~J$DTDLUlP4$a=%BJnYQcv|-_g&-^qrag$85>tFZP~; z$XVn?=AsRw_ax4~x(@kJEWQlh?TkBqhM9HOT(N64A~TDend9;|?yE_hfAxQ0mp#LJ zd4TwXk!Qt6rYcCSAKi;hHd5xl>`j>Wk+x{fhS74~H%}9ac*bLclmy~}YZp3=qc7rv z*<#O}%A9NXY@|9s=Ap<4?0^IL+r<^^bAslfBJ+?rs4@pLPcRSt`GAKp4-M>Teq@Vr zq;`&V2$y&F&qL}Vj?LDoaa)Mt^Y&mv!+txtE??qzKhkf|Fy zh8M9RRJp0Acx<4ez&;ob3^bc$ZtWiAuGQuieM3JsB4_K-QMCsdHy!37b5QJt%z=Wp z=s#lgW!~vwMXS)wZS(FM<{fh{ty7G{RK|QG4>YRVx?PcC!Ps?ApW%MEDdJcY;`C?ZS-T7wBDYe8jBU5Z`jEnjBd{U9i9$D`! z#0GtkfwPD6^U6kN#;PIfWF9`qx!gOSxmdoj-{3Ut2J_49h9{5#Mf6`hx}x#|bLkZG z)8(ppufmg=jlNTg-I?PrAqU8fdX%;Ndt%>YUzJGyg4x*1i=@`p+S07|@OjlSR%eSp zh<$b8I%j5G{)T<;;!FF{;dIYj#%?rg(_I_HFUR=Z<}!??KW4MnuUoWX@o(^_4Ujb| z{WN!BdEa;Osr8fpu2_48B z3sGf`eK6Er(Kyuox~hMmzjvtns@%(GhjmwAKg6f!eqHRaBBLsNKE1j4^iFVoQ1P(2}Ss*irj@M1O2@RUdPf+72^bhVor!qbmog`undE>#$3nlkYodCC@2y zk9P`Zj-@s7PRQ%oE1cEu3EzZ{x%2PXpBw$)wN+F#w5R%-TF_oeq~vvI6f-( zWnJ`t*?SxKsET`i{OoQv+06@i1@a0Ds9@E~z14~kk^@0W6eTK_ z2ue0Y1pXHx6%=~gds(2|Y@n9#W&#wldEFp#6I2AHHVbHzfSM>s0;pL)4N7f-Z^T;Q z_k7Qpv*(*NLG0Dr+x|J9PqMqu%$YMYXXcsj`OY(Q=p5ozoD++BGVQ}PD@>AMCC;V! zHssx0fa~G>@$V0n!glnmeT0g98~r7fgq*)miJKB0+fF({HEl)MH({? zXG@^0!Ow|#w}sUr53on+{P-foKRmwb%-SDynzmtY-DAi<&P&BPRq%t-yxVlmcZ&Xv zeia!6p3J6t-j`j~wwmV0;BRfsmN1t%cmsU2H0OD@<~xN=PS**hd>t5HbsT;N(|Y)l zQNQ81&P1H^8`LTJTSsbnL))cu`hX{I1TPJREj<-nF%h=IEvWCwXz$~3&t`(FQqeDl zcEJ9I9dKi1sWp33X+idCSHV;_d&tbLb$Z zH73bncm}pCue3f`S6Yy@i=HXm4Knf+ggk|Bt7?3xrYa+IC%C4vGJW!nAJ<}D?ZSs2 zq{y*kV^>5=F*N4}n?;$g!<(5#!0SG0Mo8!62M|IhpwYY@nO ztjBXR%AUGx>m6y)nbIf!gzo|fxCm|3#S~exq7Kl8vt5ycvR)ln)dBlBg5Cvi7B({G zHM5p%4c>?G`vu(JY}!u(eZgH3b0X`DfmP?a9`Z!(g|P)YQpSAnG1vnIXe(~m(k>e3 z$qpNZKiH;NKXfu(T{n_b3%g434`FVhKWsA#beRd>2k@@8Y#dObn=Z&v^4(A<5nh~Cb?xCk2lBJSYB?;*?QC@H^ z){UG@r*^8o3jleU^1ZwZAOQa5ppAV8Ws>icts4m_9c7oEi7L(PY2CGu z9kCc^!vBIj|2qbFV!_?8!)2)yy!|I&;OL>7E3Efcn_ZTtRbTVn;L(8}E52q)^ED3z z_YVdC&BXc$NtSFa%|YtB0GQT}{LG5h4!ofFQ6}42PNd;3w@xm(6}m?Fdsf0;Ih7(Oz6hHf=R0w}`I>!_{E}*`v}25?^Wz$Eeqn&k z_$ty^Qf?hrG}4Cmcv!}6z_roG6LB8qd~J&;AGrQ7F*n|heoyBIzKQd3Z4>5U)7RqK zN0H8YyknxW(lP<(24j4NHE)|m?~BCw5yHl6hYdsL2k*!E=W))9<<>Fe*V^f}Orv@r z9p4u?7kpqRJB`jGZOuzjo>2Jc+F|dtZ->2y`_{Rk()lyW7tLpGjP{tJlWB*|&<>qU z1j1Xf-|gL0ZZR*J1ny`@+iKqh`xN1x4OIta=0dVs!s#d|sjI ze%i?|B*y&TZoruDaM>l}_={`tX$Tm9aWOuPXcLN7bT#f}5$sRA2ZHX!)uS9DUMddFyzPyS>0=xE;FLQ_HsozXrd;PobS{ z>wn`DZR?`EZ3RE74P6s?!)Cl&CbTee=F25N8g9Wpd3#8&M$am5!Y7pfv{FCAs4p*h z8fm0USJQWdjaWuuS<)c-y_~{kNdrV!mN-xGV~F$mQ&=(D+z-FrW^nosxZb7@?a}@n z-=$!rbH3xd6r$-I@h%0w^)}Pff7talrH)Ilw;ALl+YB6!^){u2^jrhpnX)wML}B;! zHb2_(9oE}K!T%A%d5ip>;H)8wFMhTcoMmxFPEY^GT5ogi9=eCUzf0k>E(-7cT?(Hq zr||FTT?*QI8>-JoG5@|>U2iiz^B-ru&AAS$kIQ?P!WTcHxX$nQT?&KM^){z|MbF%{ zzT{*0^Zo?i9PG>cy05nxlG*cmn`@7%>urW)_P*Zc+U9Srw;7Vz^Lm?WU;Uxh+d!*n zfqzjd!E-{vt#+vhb2R0!OR?T2Bno~|=n{?aAJ?DldA&_I@jd2Z^Suu`B4sJ*L!Sr6J!oJuj5Sl-vI| z-l4D;_h4^|9Le-xVl9nyjP3z!gsHF*Cd&cWj)azXnb1E^RsHVn$>ewU;;;K!nrm-_ z-<`cfAs4;^cRM{pzaFpn3AD8|ZojoOxwwuE^ED;pZ;!+u_Q%VjYgR`MAs^L0^*a=b zlI>E}1StT|)%n#1IogfqugcLjX*GQAZ%uJTSD2-+{YF}Ak~9GR?}gA9FM~!Qf1_D4 zl%QV_7q?FJMAQ3XF?X~J@nZIyqzLH$^T~cV@Q@UZxe41=Jm($9JRZVB_Z#Gx!$=1{ z+x$YTnZewkZ5f^i-}7p)L)yY$dPYC*0s|H?-54%Ss#5UaTxg+gWM7yPz!NwwEicw=FL#NPDu( z3g2`3v>LbNUcBFZ3Z9dT;0G_k`mS*(Yg4(q@d39xo-= z3fkc!mu2F?I5~QMoE({t^(-$#8@v<#hwab>qc7(pPpfDR3+6Jj3T~)1ZC3f}>UW{T zWPD}kd{rGGC19R45jM;qtnHXX>;Eb+?|PQjcT~zWhg|cyy1pY9dq*kjJHTNM#$kCr z4g*hhU*D0t$j4y=#QKgNILvQ-2XWYCt?wY)OX0A{+(pD;A0@)Z$NV2(-|+!5>bJh* zt^DrmI}Z1pL({c!=g4*U&nRa>**@7p;ElI~E>697mZ~Ti-Fi z8SUaEuq{T+OIFC?w7vuDE9Ms~^Na5JSnIIRi}fAvVNFFL_>K6(WsoiKSDDJ;C&9c( z_J-^8jL?H+mjy^+ho^W9@OMmxE9L$DMn@>}`Ae0bq<~|HQ=<5*;%rBz$YG`Mc|li$ zbqxte_ZvMCE5V_yh=VjI5|1LyiF?2i;LlK6&w#qG2Zv%!V#4cqRvk}iL&K5(6y8+z z9?n}i(ko#O*#=EVcsb_R&Vm0BcOUW*3BFy3dDQtEjk1Bx*#N$M4slDc1_6G@$wzR` zXW&5OgXWiR#Cgvj!#WS}L_Rq3#BiP9Icyuk;F}(Q z27HL{O7I@$BCGS^bG;Ed3ESbRo)F5%iJ_RImJ&9f8tP5}zwLr=wF&;eW=i`+kbB^% zAa?@VV?EkU4W7kYz?pTMD;t_i%A5_GT+ZY0Jrx}VC#@|nxVNYxW5UYv#(P(lXH4+o zI>>ALTd0R+&>6o7|73lf9DzA}>rSk<*y^@U1DEB4XBQV#6=XeGY5f`XJ?IzQ@J*KN z!8$zfCavw*^)>n)t@B$AeIDi>-Qd&`_)ptcx(c#4gGci*Z?Zp5ia_35)>l~X#9Uk* z{KVO)kB+sKjrUYmG~V-7oYZfpt8on0YDH{-#;>$A{qE&ePWk1s^gDJ`i1!)9%Kf%t zjUeWqnhL5gk67uvy}Z&QQ(n+6>XDZs-S(ZM2dujT;FJi6|erDqE#b1F-wpJBPTMZuHT;)8nq|%c9 zF1Q7Ak{`i_3S(UEzwTfl)*XC}c{)cA>kf{6lv>?HoJn@5y6#|l7RL9I7b0?tp$V!X zTS$GML3!b~tUDmx@_tpf>``>f&^7N1Nmq5tG^cS=d7r_l!oOwR0md1;&!D^zb2t&S z&im~|hqSCZ_y9B}78knn`J&a=cbZ^lHldCzjE7&Na={O< zkkgpL@vS?k1P_9rTlQREYXV=jor{-~;A>xX0A>9w!IM-2 z{lJ+xXl}3;_#`mmhjR2aT6eG;b3yc8Xj*sBrOXAX>kcrc?b?8L1MX1P9bjGpa}x1( zJO@fNXRseL3@?N4zCb=-ME|FGAJV;IF8e{Dd;J2s2wGq8I@K%do7!4}0qB$c&?oc7SC7gK-U7?CTh zYYEEos0~x!)7KK@TF`I9p6@@T_WN5)aH@89|6CiMmxoh*fG57WmLTSo^tZ1iD6a*d z)KeLb%&)H5*f-ZkYYFID7zg#W1Tm*v7*oH)T7n_2@3xlUWM1!U2?po!wFDoc?;q~I zmLOVNOHlRAwFEa{4*nZ!37*A$d_FqY(tRz#^gOnfAnvOK5Bj2~r?mv}7n5YEaiA<= z&Tukpk%<_0=V7eR-d0)won}Ec=Bbpm1U1kX(K-T}zl7~XYYAvwWVf{hRRJE~S^`=- zp{ymq+#;pWA>sLK;_*a}R8-M;?^^6{@HygP z9LY131}U`R`ALCa!VH0yZ`K^0IzQ?0dy`k zq+P%>?>E*G&|U{9FWGjhU~ANz8zG0{nYQS&QF3Spt_%COgq|-uMtVZ=Z0-g}U3X@* z6iVDi>i}da?wT`0-M6$QI!H@MI^>D;t^()?@1!`;5Kcs#Bw8atTuAGz@(~Yq%H(CP zMp|p7tO0;7X$)~8cyKbU9l%LKz z7v|Er#=jOC|1Y3HhGxKgGwK=kO373G${gs|^S*>umo)xw5Acw#_>Se(2VNtMKkOD| zk011@cI>U-z`a>WV^Rxl)N1rE%stkV#vgr#H2!^P4hqkaB?qQBV9v(M@N0#AGEhob zhW-YdrwKltG3Vpu1n97*pQU!S={=MwJzceifkgE4l|-zv7zx zrBTz1HQnBc)ap8++cWrS_S<{V?d^gN?@SNcy>@8tNxRo_5YJEGw0dw_ou=J83XTK6 zxuM%DX-st&`Rn%gEv#yRRJ`Oxf zHKBdiV+^Pxo;(8|$Ihy6X!zUk9K7m_RA}wdkG@WYo?X@OXXCkf9?Vu%!ykRp^?bzi zYI<(K{hwDBQ&xz6Rz!X$-1qeT@IB$4&x5XfOtfscNz?Gdu3U)gmpt8Xa54Oe^R_{E zioE2*Zmq#HL0|Cn;6xnqys#x|aB zZ3C;?u@)iWg8>fG9^Jd-nLC;em2&;y@7Eo0ByT}6UH&hmjnJZ z_`Vh0{@OCjLFo4P?}~ z{2376fVr158i1XpsA5o7?2l4ivM#_A)rGikP4OhI9O;54F3(7QeE7Ryq4Yb8`c8%Y8C!sK zIuYM}E%rsB>rRuR!9SA`=QOU_7j{z#%^Q52B}czGwCW(eYXD&<_M4SwkrJ|Xj$xhy z{Tg;qzvKH&atuA&)3uIFm7?E98$dbfnuqBf1K@+nXb(5;!JGm5)llMY*h)8|Ki%>b z^t`YIhk}nL(mihps;Wa@AOB_$t{YTUUr<>vsm^VkRO8B+w!FeRZC7c*-B@Qi343Kt zDKE8*-Q;d~sIn@3DxPWkqaO`)L+^<3qMqt?RYk!>>~nSJ68MyMz^8OH-eInfcSO^; zu?*`}SGp`&7(2|+#z${2&6t2ShG|bi7r(sH@*wgQ1YQW=Q(7>k4ttmspx@G3B4|Kq zZ>i{W-N%{U^x+uFx*o#ZfyEhhvX;h~!)W*FIO9;XT|JF6c&^>8jWhqZ^x?VHOQVNG z^sEmr+x_>^hoAJQ`tZRGWP^bFp${*sMVsqQAAa(mQ6H|oSAcjM&wJS~MW5VF?-c-V zCy_q95xk9cvE!E19C(-X;qc8W`tY+DSK+_d{VnemSWnzYX^+BRVPr1W37o1{_2Ib@ zf4e>${=9Db@UpItzq3C4R5gkw^x??MlXq) zINEnOo}=%?bEk#Yg>rp3INYZX--YLN@L74=_T#a`$z#m9DaM(%{994=A{EPFciVpk@s#D!YuajSZ-kWsar1{34 z^peA^XufB@&otl9>zeOzr1@Tpv2`^x&wJp9*bl7>@^UNfeg747x3KMOX9v`EVc%EG z`8uuEq~X`Z0U1%QKxq{Nf;+i8KQ1N7 zDKyWdfj)>&(m?OQr|~9P3P*mWWhm=P*c0W$lsYoYQcNe+(FaJI?1&Rc8~WsX*Q?s( zXRuZ~5^>t;y#P-loOI@mh_k%3Af5J`z_|A(*sKz}cgh%c9r_>U$>Z=ofHL?h5@G8# zL*v*=HXio-GarSGhkah4JC&r;GUscXUDCbp!yY>c+Y0lN`{9FWg3h!#DP5}k5N)-x z+;Rtg)Bd)lRK^f!UhyuIHtgRSx~1>-&6}?43@whH`7-umvp@^Ght_<(Jy6lS+Jvrk zEuANHt%dX*npYXmmIsm+Nxz}rLf2X#!tCO_FGbieoYxE;?Lu+h zTX=q0Ai~ncd3o%-M@85yalP5%JksAjBEllXdDGc>*&=MXIBzODFH3}F;=E>*Z<08V z>hB)XYB6b9&Y%=T`r)`s6Y%`3By_c(&x36>p%8nyHQc3)(0+4etXOe46Yye1|@{tW(z~ zpX}^J`#b>-SyJK5lmiAnK{iRgD_$;2wM+G|t!a zX7-{_j>Y=`9*2EC%Ndzl{g0(j?ix?`uy=j(7cL6#U7!5LtrY$}>yxQI2SJ}4tLl?; zYe^G#AgKBj=F!`W;p;*jA8LSJr4Y~0TW_@w+>wIuzSJ&lALUi{62Y@`2cBhXV7u(X zb6Opq{fA;M?A%g=-LRd;Sd7a%FoweBv|wzBpgG!YI2Y><+jh95DM-s;#Q3-G=e=u< zKU`SdM)QjAFRbo3xv=_NCu|iwryZmExIBIGmyc6i=l82m?yu^TPrlQGKKWjuPd2OX z1F+Thpief-0RwiZ`ea*e@A~8c)l8pkUV{GfRJ5(OXMOU3&3{LIvZ)t+vKidahd4vm zCr8!qO;q*C@I$naK3Qm!i-bcKeT&7n>-*Ie!0uk zCJ(T{zew6-><{FwDUP=9#`EfBYLk<`f*+c+$>vD>;obJT$#+;CW!rrK^I}Z97sK}X zFY(jv6_Kxz_6LElbQPXy^2xtQdR1umXrKROTU7s|qN9UvF`W4qmuddRlNe_l6?O^d zD*nX}Vat+#u_E9i`4^*ve=$mE_q2Wf7oVo{Ds6OL2>BP^68roY8W)p)QPB&+w`f@h z8=QQMhljcs!$y7%e3(x@#wz$13zF^9R`{VRlf9%(HbGk#QxO22CH8lar6}6#T-PQC zcnk;NJ0z{#Ud)x2!bd4fkzZpE@d()3iY~d~K~D_A>Phd6u+Om{V>En}K20*T$T7=C z+UWc`Xp&8P6G)F7`LU))hE74zBV!JDVG+0)@mk7}R>K4IJQ3+ddP0jV$(k0KG{^`$ zx;I{mfWI*x=QNQP8EHoYSK61VLzZMshYTMrlo64yK|d4)|Bg?G44`r}E^A5S9f@vaKXw4U_GCD0$2 z3;nTM)gQ-b`eSI1?^}guW$2G%E=PY%+GEln<5}wZcT*gfqd)Ek`(**f)K|Fv7+SEJ zFID|<*5?=}P3RBcDKF!xW*<*MV=VN?8c((AJmpb&3L0hF7ae`ExBcb6i~d;UsYrV( z@zf8gKPDbojrv>>Z9jbi`^*0c&zmQpa})Yw3-rhMb8y`o>5su-24$ZmaG1~^C!R## zzfApcBIex`{V{QvyDZY)OdNK3`s2g~a2V;2&BSBizh+f`>}vjJ*B@hS&ffwKYk&?J z`s45-a2V;2@y?L!wb$oiPgtQpo+=rzUVO4FMZCE;-eElkjWBV{7HE^35vKt5-jN4n z3)Wefz#BD)SHCyj6R{q=2;UNESo1f*r}9>o9Exy+k={7s52QE7zV1p|&>KVB3!Uvq zuXOkUIh=eXh+Btv&*Per;BrN4>@>=u(7EK3f9F89oBTTzuZ6V6q%p>I;a7=pH$Z3n zR<`>_;^~Gd4lCm90$vL4Q}_*fKt*GW{EtUi6T&uv$C$oYl1X1o+G5fbBYX|z^yA<~MQ^*Zyda%)wAkk^J-;mD zPOdF}8Ct@f@F$(c81Pk`Y=wR|3wq)Q!JGM*hgn<)UGZ}4la6b;NnZ>dLJ8&on<~|J zi|qOU+G1#g?^|7waThcVEyzn9=Cj(1T-N)bIleEy4D-Gy_j>G^tZ9nRGc7UvkR>mc zHrxU2?|r02u7jp{LxuIO*P$gYfW~=8S;GX<8pB83guQ=n-&$oQeR0cn%>THdE#3u< zG3JR|P!C1Oo3cOsa}^D3+faVwb20MMwAP(*Pf=ClUCXL6rsZS5`Bl&}LuY*#bj8qB zH{7-Z;cjgo3F;-(n#B)Eit8*(>_hHtr#}j|3p)4ey0B*3pj-| z#b#Ag>~*4VU9P6sjQI@Mkfjz~Q|w*z{c4KKcY}|5&=lwH?nP5>Wl{76%b=O&!%rzvhqk|bzu7Q;STwX!T@JZXw)kHv}reJ}I< zu!oU$9r*U3zoz&zXo|r-il(>&nqt~traexkcWF&NneBF?AMPrt$hc3_6w`j4{+i+z za3X1n(RUS1G5qS#6o+Tw*^M;Cmh*|S^ab+xqRTps<`4Yz#9u-aM0#Ss`GY^?IblcR z87>-n;vb#;PQ|3OXX5B>jJ37Jy=ThNy2x|@*q zm!eNKV?MAQVKhkZWHBVi9^Wq zn8$VuSD z^rd>&-HwD$(O*#)s~lCzx`Vmiq3%PZ-Bk7{kIZfEO;`LeYza5!x1Nm}(hR#c?)``4 zXxL7N(BBr~-CPpwL5O;e+?Va1Sry<2huwgKYx|QYe4Qjquj0DUMbO@b z6ETj{-l^H{QtMc}+u)&#!0}9I+2*Ew1JkGC{ayEwRt%cr`=Jj~G{w-b)Bfb|LsOhY z^#iWiza7t}C~F7#9Pmtf?t>&b@-Nx$Ra9>!&=f;I9Es~Fn&NuOXL-Y2Ed!yCs>rx^ z6*R>te-ZQ~^!%r2iZMr7Qd$bzrz|6FC)S)o+mW_KvRf)J&k5bun5|WoDbN)EjP~St zA;@ll9an@lzaM4wX^Kg6=P38kyyx$}#JwX;G0M9OzR6XvF={@8b{PK2qK=VrC~P1% zY{U}SOC@JUNs7P23y0FG5fJkxpqMpeH`%1y?J2;zJ<4Rb`e8 z(teVj7@B?36Nk~95yp0*Cl0%HZ=9+pCQTUWh@qF2g_hWRnOfpV^r=wT9oYZ7yz}z4 z#JOFX#_L4tKZ2Gxx9bPg5}RrKe<9i{Js)AWz$Oq{;_^=1*WR?mH+)k|JfsWU|Nkp3 zG3~KWT4K^};8`nx?EB)VAzkNHEpaGmi4Ve?K(_ue%ma{?nCyJAckw&`Epcww*@3E- zxMcv=>)`o#$<_m{q$Nh5AuaJC>ND5_Txf}7V_{!krj|HX+5emDQdLWQstaT1Won80 zU+AtSKGj8e#xo`NQn0j$_d0mW;+cD$+;zcuYfd?M=XSG!3y?^4Abkyj1wmV3CA; zf_LG>7T|nn%1USs0%4P(AI9DxAs^y8LOU$U&<@Aw+ToBkS|3STX2hQe?J&hZM%Sf$ zt%pr?WU3THx?$1_BkT~$mWaN8<5$oEqfbv>?zX05ty=mX__Z(|426w;3;h167*~C| zVNEj({qT*@4Nrj|YYOzjna~SQ=}9k)v7#rv@C4EeW4+gev#MS=hQOUKs1WNH6S- zZ5*iXCr!R4tf9$ThIe1ke$seuqWz@T(U?JLkHR0cd(e%au&8=rdmgkKf6IQ-X6*ZI z5qe?87vPF=Ws#qOp8u77sK;M&KWQ`0#eUK!Ec84KA5Q4fXyj$e_o5dztNTgYz_Z=- z!sb#u+ddQJis(fzY{L9l_?OvoJou6JIu3n4&=VVrJp{m$;mz4q1= z5%y~6g-xi-Gw|)iVE<3kCvdf|i6{NfoogZyi>hXB_L zWBo?2df_wB3uCNS^uixNFI(+dYb zv=`6qut_YW7gn{x)}FM&Uka^o6V)fvoA#y^p3;j}cqD0sS7PiW-7aZgFx^ z{-fb0+J_o(A`v%=bim49&4>%1=okm|zqD5~d~~f?b4eQDMDo!=^Gh1wL--^O@T>SF z4R9FJQ8d6bx22ZddY;lgfqcG;G&i`cnRu_l-SDaIepAu@TE8IeFYR6YI>Iqeeh1CJ zAiM*6I{E8<&E!*q-?a~Pzpufkfaf3k@D+XSBd+Lv6`gS&-5a4Z{(I?u&8qI#CHJEH zH7|{FrT4D;?TdE}{Uhv4Y#;Fh>VD1e)tcAg9i~5^?)O)uC;X1O-*@Po@2LCz3!Ni$ zzviWhQaJVDVw)WPRCJEzht>VMWYU84p!=oyLg;>N>12=LJ9NLUOq$aJ?_ghIZ)SJh zZ_FaHEiO~{yPyZ%FL72FbgCuL`{m(%h?aja-S0HIhnLX(Hd6Q{bicV2{yph_&Dfhq ztIs>}++tI8zc~^AK)T;fs*lUm{T5MN=l7udrFx^eFSNx_q5FM*T})~1n#jSi%keyn zF*F!p`UgdKpLiEGCV!FfkuF|CQ2ZfQjx9A%zRJh(rCbFE}UL!W3~5uH=J1@Aw@{E``c zEua-|CUcZaA^l|O+M@$x$vM~)y24~LI7;P^K-uu?rB<7XFz97EihnY&}@U{5fupGBIcefa$%-e1^+-)jbY%sa}h z4`S_?^)2Agj7-^%_38%^Rth}}-Y;s#^#bTxu%AvtZ*uP%qu0=2LVnBS0F>)G=+kLW zvw)>WuhD71```viR%kKnxCVGU z;2fSWu&=>!tXqmBoUXsLUyg@tiLY^eq}>N;KeP$@+~X)0=9oJiuv4LXHSfE|1DjQj zzXoeRUO=0#2ukgO9?$mvLRspD7E)qXbzd?P+?K9y%mdOUK@1Xx1sPB~F`S7W$Wa>MS$iKWI z*sJ89+B@YRYY+P(|JYB$@*4FWbB|$F`Rjw0?4{EUhruEtDSSi$7aqv>RZ@%%OhaD7DkGStCB_*{lwq#;ejL zE0veh2j>{DUntZM?77Odr$X;+8o+c2)KM2g%Ph& zS*V<-$4I32-v}F{mgRyd%LQLqsQptqD2sVN&Ov)I5T^7aq|*ot{Ri)@xG)##jK$bz zT#qr&Im8o3{k`a4Yy*`(Ymh>o4U~ha?-iomlFz4nl-+Rm$wziA{?M(LU5(GO-`Wkv zbmKf4qp{`yWANgO&OvhArhjz}qvbl(IyMiH#Mo?C#%AzH2=UERL$G!R?^nVeC)Oi4uM|824pTU%vcftEdySZJJ&euB zhcY%7q+`4{Gz7}v7l$%7gIj_z<^>Qph_N}?p^nX;Dq}OtH}MS83PBs}E5_z&D-ga^ z%Qx~%eFc~po9!mz9sE|tX6*TvL_9;`Ys;;7p>HNodBu3Fa1E`=#@OFq6p95u)b+bi@aW>moa6V&l4ax9Hk8*!htF%vde z*b(I8Ef7hu(PO3YipD2LIgM3@Z8UDt{U_Uu?)w_Fj|z-aXx~HQfUoj;zpy# zg8GQZn!BV;k9eZ6UZAFIh#avi*)A3QxRb`K0Cl`F9QpDIheY=X%~i4Oi9cYg417Lj z(CHPCgF2DlH(~3=qkWpmzI!*`Bb7kRy=2>gH?V&D&~}$)LK*tXI+O$c%1G?d6bwC& zbS!}Kt+c(LY{mNT3fvE;()J{@J*jqSsLON=*QLHqZ4di)EA3%drLzt1m2Ru7D0m2C z-BnvFty3u;wZTJ(y9W1?^1B&ri`rVD$!;QE1#c?tYkj5jjSVi#8_mEl?wN}!3MQ;UA6QK7^fd0%KC?t^mBzxGMO!scTYVN|W$`sKwbeM3bJ~g!n^I0{U!t6G)K+;p z(Pn++q&mWV#+a`5G4xjx-4k$PIF0FOs{^U+mQ*@lqc(~*nLuqDW65jie@SaA3hr20 zX`Q&L()l{ps=Q9`f_)Ko<+4iWevI||3$S-*wb zD=o$Suc;_F3Vt$lq)K?_9Q+lPmbcmwR$iI$8vK}+%@rAk3gO#*H&rIPAR*Rn7)$M+ zIB};-n($K0iB^oQNi=6bTz(w+r2X2_|HIZ9U4~^YO9S3j-LTS?o|W&mWR+GF-1c&% zH4PlG7aH|<3MvZzSchvij+C(%Pvfz#aW2MM%krwmcjK~iu7@%BBO0q=KPomW z#wi*zx8sw>%u;;P7#u?Fi1I;pOAGR{4SZEz>3rMmvb>GJrsw=ExK12b0TXh zo%^11S@t3P0gREqMV!g%ICgO=J}Lc)_@pt}Lg&)GL;3-3+%N1!aR@fsA;Mo3X<{D< zR`Ct9D5DOL9I-+?*=Z}i1Fpx_Ts|HqVPExk{z z>)xk+OLik&>!7&SdbEFPzndzZ2TEL)1GwHq(WmaBcr+GJS)WB)Q}_<$rFyAGoBI^u zG^SG@L)&>3HdheYj;Db?Sy7ONxz;fSmCpBGg#AeU5_M^?X`c!$r1cmnl92)N{eTn+f^9CFGkwX~+(3j%Tmr}I8ME!y8 z4>({TwOjOsH_-myK>6+veQ6?wQ zjjbm{Ti#kx&`fRlqmjP0{9kHYmga%xHGA@P(U$l2)Rw~blOfvje~7mHbKaIepmWid!7EW{%g4}`k5QUbhmxvHtdYxjc5aOKd60zuOngiJxF2HPU+h1z|p>=Bj%mIfUrB%a-4r#EysD< zA4Rc~sgF=PD2Hxa0#kSZ=`3@B5?Y0{BF2uo(mBxo1h4GYe@e@6;rpEA6w;`?GvbSDE@Of8r#2mOnoLbur8E z&UuU*#A{#S+UufD48wD9@E7qiJ!_m^;P>>zu(5cc#I*wa;mac_R&9k}%-@4z)CPgrq)C&n3*wSFy>k7vo|R(p?vkGYmFO@P61cOI(&QazGzyw$c`)vl8V#h;qZX5l;5l z0h~}~Bz-jbGYr2ud5uRZ!?nmqfO``8R-i|EAGTLD>e*@ZL?I6`j#4SU9`#uOU%~UR z$*SO6s)i3|SF)G($80ZE?XU>4!!T~HCcgpQXX4$b0=F|TyRp~8f5@ZSQwTWMiXW{1q zT^87+>2G2me5}W9L7BH=U5J66q2O~nUJ;cu4C%DsK18i`SsPlAp-p9$^!2bGu*RzG zZRprrhGH#SK%ep@T=BRQcKtnS=zYJqK)?U>o zd!*P|($m=Ul=gQwQ@)>#95jOZ_aWpL{1plRNZk6e^!kGXu?Ym?tj4)z$e&-He^p_I zon^L}==zjzTwlpIU4JL?P5WfOO!>wdH{_>niMwDg%lm?t0$s5vcR+E>i7slN=!>Bz zksqv&lQ8~{Aim#gZmoV=702$mg19U;SG5AfFdezso6~?u=|`?Aw;O3SOan;@mr_o~eA~w-NQL zy!#t@9&rxk!+j3Hm@}2~PWM~OAFa_TDJ!@ibM@`zxc^0%lg7He``S`4Ky}aGk0^gs zeyT&1Td6}TFEr>4*f&vHNqK`^49!g2lG2QNk+-WTZ`4kyTr_^7TwUPeFzV;vAS>3@ z9XJmTqWg^TXquL{Lfr4I&}Myryn%}>ODdd`Xio!ZAKK|1A0MT-BDDGw?bCoZYa^bZ zJXS@kdBm6!{Uq|Z8Tq4UlJ%tp6QJi|?KAA_CFKRm9u9PE^d+Tz(lZR!BDFW7&r|yZ z=Z>o@D;P`rJYGzdCD@Tu&LJ;qpOl`Vep^j_5q)p>)l#CPRE`OTR>K)?8~7PG4(p@Z zYpODici`Sr-=Z|p#>JC=t#8y-Sl_@tlW#QQ zyjO8wE~4CDr?_INtdzzuyeFH^hfirGrG*VbKXH`WECDkwp2BCuRkNfJ(q`a$T)%l2 z+bQmG{6-&bhmNhO1?eL1W{LyNcsurZd;1hH@a`eXr}6A!o1@f@^e&vmXG8$jj^H!y zr}Hi*&^>z#_m+IR`*ClPP5_?G4At;)5qE-y&G5Okt*&Z(NV`{ZKv)soFFKd*6YjIp zmcYT5a@?o(M31zM+7j9!G$#B$tzJ@@(Xu6GP#4`N+}CjGV{N#n(5m9y(3XisWd(Pw zDr?AS!B|3?ZsHB{caU9L+%IPYY?B~rchnDPJty^H*vs+Mo`~DDJgV&}>`^Me*5--p zUP8Oyj69Yif3UTa&^FuNr04Vs>s#xgJyhF#*i35keQ{m%Q}UU2VJr)K8)F%sHSf^c z_mgD>6G%VtdNS4Zg^gIl+K$gBJlDXV1G|EF+If{DZZ+(RwXi9!#&5ig#kwU>wp3%> zslu3o{zrQ^7?6JS%28g-)hKD*b6~J6VHh$K7j41VMLwaU;C8ZqmXzWBFHK$xwUy1M z>LQ5GVRK-l{K^iS<5$WUAC)6N6`A9t@ey?u(io3tG6Tv9zai#zXs#)EZ)){o8jtB7 z8@rFc13w)EpQkqGG)Rur=A71Gj%f?#oSZR(r47h0#^7qY2Dmu*IgG!`T+!t1%-ViY8(}gc$7D@@)1wm=P2(BS2@D>qCc!p@eKenhNY`rJETmcB$=qq?N=-~-eK^GNpysV^JBeq5Ez^US!J0o9L_1 zd#6LwBSGuk1P+M+m%aHc_5r0biZazv8D9#p8K@nX4V4pLh$@}cg}Ild#9b~q=_oP} zeoNjMY9C1RF>j+BmE{EopuKJS0Qvt2{gU#6`L!c-jU^S9x4^@%(L2t;dv3bkCpZs$ z)KXbl@CI~-%6aHBEp%=>K5=dv<(SW|X< zTV>;GXHx8zH^HGLW!6I&$0AM*ut#9;@BJ9tny{wowL>ZPh=br)_&Vy(gGcuduq#}O z`HlUQPiUy#+fh~U+SwF)Eb65d`Luysl|B88pDjM@C?_tw_zFIWTcch@8StI>@C_=@ zDg061@$Rle;8=xUk5gU1kAV5RCTQpnt}JbQ?I7xb>VVFtGE;e|4$h@Gq^~JH>aKga zsQ+H9qH}1hcSa4eqi+V_{a@%4)@kSyV`yw7+XTAkc6ye@Z|r*&XWrnpJV@b7%B`)$ zXLL_1qE6twcBSd)dst8GZur;McY{34@?JLU~XwK3=;bbGw7=U%( z>F^!OxX*254^lq|uZ6$pYTSE1-XpyU+k)(Z9q>bKhmQ^UY}pJw-Y2LhNi5J4(kMdfJ!3WQ&ycxP4{`60Wc#zhGQ-=qmyn-8l zXQ~_C{FAK{8$z~D+!wMnv*EhxKkU1%`sL>Ps$X8R-)s5ZkhOI}ky_|DQ-Ot%SWb1uMcM`61pSaHb`290}oxOhN>>cGR z;d5=$@GebG;o9E5Sd18hw zciy9UISXbZmj87qAG7AodDwQ1ZF)}bzs-DX-s4Cy=dqc%*4za@mL|=bJ>Rx)-rNQF z_t?ML9+~%;ZSIVm$7Uc$b7#z+Icxg7#~(v}=I1<`H)r9BKxY{>bbZa~}4^XZ6TZktD6$ zkIc<^l%>24!>347#?+sh&?IN51@P_fe}#EkKX2*1N1`Q<6n~

      0<6lQ8_40?>Ue%ro9NGxg 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�pm*ahJD@>!K)-|zsN(6bL@yNUNzn-% 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 literal 0 HcmV?d00001 diff --git a/spm/nii_for_spm2/spm_slice_vol.mexmac b/spm/nii_for_spm2/spm_slice_vol.mexmac new file mode 100755 index 0000000000000000000000000000000000000000..1337b193a1e0aa578f821fa881a15ad710bff601 GIT binary patch literal 296508 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� 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 $hM8V(|Zzk^<?-@ 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 literal 0 HcmV?d00001 diff --git a/spm/nii_for_spm2/spm_slice_vol.mexmaci b/spm/nii_for_spm2/spm_slice_vol.mexmaci new file mode 100644 index 0000000000000000000000000000000000000000..f810cdc496045b463ee9d48e3387f448e5a8f17e GIT binary patch literal 254208 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 Of-)FA8iBl})c*r)k&g=i literal 0 HcmV?d00001 diff --git a/spm/nii_for_spm2/spm_slice_vol.mexs64 b/spm/nii_for_spm2/spm_slice_vol.mexs64 new file mode 100644 index 0000000000000000000000000000000000000000..07d7ab68206d5f6ffa9e84d9dda53850f6905b52 GIT binary patch literal 257672 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�+n*3yZMD}G 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

      >d)ZgX$cAJLi5 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 Ri)DVaCIPW$#=1oRe*pvEm|6e; literal 0 HcmV?d00001 diff --git a/spm/nii_for_spm2/spm_slice_vol.mexsol b/spm/nii_for_spm2/spm_slice_vol.mexsol new file mode 100755 index 0000000000000000000000000000000000000000..4b6cdee2815bfcdb0eda609524eccfb7af3dba34 GIT binary patch literal 237040 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 PxFqrDgJtNu-^I9J7T3Fs literal 0 HcmV?d00001 diff --git a/spm/nii_for_spm2/spm_slice_vol.mexw32 b/spm/nii_for_spm2/spm_slice_vol.mexw32 new file mode 100755 index 0000000000000000000000000000000000000000..61abc7f19e3b0f467637e47abad8bc5e0714cfdd GIT binary patch literal 217088 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 U9^W)RcMR>vU=J?*Iqxa(zjX)_TL1t6 literal 0 HcmV?d00001 diff --git a/spm/nii_for_spm2/spm_slice_vol.mexw64 b/spm/nii_for_spm2/spm_slice_vol.mexw64 new file mode 100644 index 0000000000000000000000000000000000000000..05a5113041a5c680b08d92a3f48535abf03741ab GIT binary patch literal 212992 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

      DmCB(-XobZTy$=(842M$j6_+ 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 literal 0 HcmV?d00001 diff --git a/spm/nii_for_spm2/spm_vol.m b/spm/nii_for_spm2/spm_vol.m new file mode 100644 index 0000000..c85c0b1 --- /dev/null +++ b/spm/nii_for_spm2/spm_vol.m @@ -0,0 +1,179 @@ +function V = spm_vol(P) +% Get header information etc for images. +% FORMAT V = spm_vol(P) +% P - a matrix of filenames. +% V - a vector of structures containing image volume information. +% The elements of the structures are: +% V.fname - the filename of the image. +% V.dim - the x, y and z dimensions of the volume +% V.dt - A 1x2 array. First element is datatype (see spm_type). +% The second is 1 or 0 depending on the endian-ness. +% V.mat - a 4x4 affine transformation matrix mapping from +% voxel coordinates to real world coordinates. +% V.pinfo - plane info for each plane of the volume. +% V.pinfo(1,:) - scale for each plane +% V.pinfo(2,:) - offset for each plane +% The true voxel intensities of the jth image are given +% by: val*V.pinfo(1,j) + V.pinfo(2,j) +% V.pinfo(3,:) - offset into image (in bytes). +% If the size of pinfo is 3x1, then the volume is assumed +% to be contiguous and each plane has the same scalefactor +% and offset. +%____________________________________________________________________________ +% +% The fields listed above are essential for the mex routines, but other +% fields can also be incorporated into the structure. +% +% The images are not memory mapped at this step, but are mapped when +% the mex routines using the volume information are called. +% +% Note that spm_vol can also be applied to the filename(s) of 4-dim +% volumes. In that case, the elements of V will point to a series of 3-dim +% images. +% +% This is a replacement for the spm_map_vol and spm_unmap_vol stuff of +% MatLab4 SPMs (SPM94-97), which is now obsolete. +%_______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% John Ashburner +% $Id: spm_vol.m 982 2007-10-26 14:01:54Z john $ + +if nargin==0, + V = struct('fname', {},... + 'dim', {},... + 'dt', {},... + 'pinfo', {},... + 'mat', {},... + 'n', {},... + 'descrip', {},... + 'private',{}); + return; +end; + +% If is already a vol structure then just return; +if isstruct(P), V = P; return; end; + +V = subfunc2(P); +return; +%_______________________________________________________________________ + +%_______________________________________________________________________ +function V = subfunc2(P) +if iscell(P), + V = cell(size(P)); + for j=1:numel(P), + if iscell(P{j}), + V{j} = subfunc2(P{j}); + else + V{j} = subfunc1(P{j}); + end; + end; +else + V = subfunc1(P); +end; +return; +%_______________________________________________________________________ + +%_______________________________________________________________________ +function V = subfunc1(P) +if isempty(P), + V = []; + return; +end; + +counter = 0; +for i=1:size(P,1), + v = subfunc(P(i,:)); + [V(counter+1:counter+size(v, 2),1).fname] = deal(''); + [V(counter+1:counter+size(v, 2),1).mat] = deal([0 0 0 0]); + [V(counter+1:counter+size(v, 2),1).mat] = deal(eye(4)); + [V(counter+1:counter+size(v, 2),1).mat] = deal([1 0 0]'); + if isempty(v), + hread_error_message(P(i,:)); + error(['Can''t get volume information for ''' P(i,:) '''']); + end + + f = fieldnames(v); + for j=1:size(f,1) + eval(['[V(counter+1:counter+size(v,2),1).' f{j} '] = deal(v.' f{j} ');']); + end + counter = counter + size(v,2); +end + +return; +%_______________________________________________________________________ + +%_______________________________________________________________________ +function V = subfunc(p) +[pth,nam,ext,n1] = spm_fileparts(deblank(p)); +p = fullfile(pth,[nam ext]); +n = str2num(n1); +if ~exist(p,'file'), + existance_error_message(p); + error('File "%s" does not exist.', p); +end +switch ext, + case {'.nii','.NII'}, + % Do nothing + + case {'.img','.IMG'}, + if ~exist(fullfile(pth,[nam '.hdr']),'file') && ~exist(fullfile(pth,[nam '.HDR']),'file'), + existance_error_message(fullfile(pth,[nam '.hdr'])), + error('File "%s" does not exist.', fullfile(pth,[nam '.hdr'])); + end + + case {'.hdr','.HDR'}, + ext = '.img'; + p = fullfile(pth,[nam ext]); + if ~exist(p,'file'), + existance_error_message(p), + error('File "%s" does not exist.', p); + end + + otherwise, + error('File "%s" is not of a recognised type.', p); +end + +if isempty(n), + V = spm_vol_nifti(p); +else + V = spm_vol_nifti(p,n); +end; + + +if isempty(n) && length(V.private.dat.dim) > 3 + V0(1) = V; + for i = 2:V.private.dat.dim(4) + V0(i) = spm_vol_nifti(p, i); + end + V = V0; +end +%KND : SPM2 wants a 4th dim +for i=1:numel(V) + V(i).dim(4)=4; % +end + +if ~isempty(V), return; end; +return; +%_______________________________________________________________________ + +%_______________________________________________________________________ +function hread_error_message(q) +str = {... + 'Error reading information on:',... + [' ',spm_str_manip(q,'k40d')],... + ' ',... + 'Please check that it is in the correct format.'}; +spm('alert*',str,mfilename,sqrt(-1)); +return; +%_______________________________________________________________________ +function existance_error_message(q) +str = {... + 'Unable to find file:',... + [' ',spm_str_manip(q,'k40d')],... + ' ',... + 'Please check that it exists.'}; +spm('alert*',str,mfilename,sqrt(-1)); +return; + diff --git a/spm/nii_for_spm2/spm_vol_check.m b/spm/nii_for_spm2/spm_vol_check.m new file mode 100755 index 0000000..d25175f --- /dev/null +++ b/spm/nii_for_spm2/spm_vol_check.m @@ -0,0 +1,72 @@ +function [samef, msg, chgf] = spm_vol_check(varargin) +% FORMAT [samef, msg, chgf] = spm_vol_check(V1, V2, ...) +% checks spm_vol structs are in same space +% +% V1, V2, etc - arrays of spm_vol structs +% +% samef - true if images have same dims, mats +% msg - cell array containing helpful message if not +% chgf - logical Nx2 array of difference flags +%_______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% Matthew Brett +% $Id: spm_vol_check.m 184 2005-05-31 13:23:32Z john $ + + +[fnames samef msg] = deal({},1,{}); + +if nargin < 1, + return; +end; + +for i = 1:numel(varargin), + vols = varargin{i}; + if ~isempty(vols), + if i == 1, + dims = cat(3,vols(:).dim); + mats = cat(3,vols(:).mat); + else, + dims = cat(3,dims,vols(:).dim); + mats = cat(3,mats,vols(:).mat); + end; + fnames = {fnames{:}, vols(:).fname}; + end; +end; + +nimgs = size(dims, 3); +if nimgs < 2, + return; +end; + +labs = {'dimensions', 'orientation & voxel size'}; + +dimf = any(diff(dims(:,1:3,:),1,3)); +matf = any(any(diff(mats,1,3))); +chgf = logical([dimf(:) matf(:)]); +chgi = find(any(chgf, 2)); +if ~isempty(chgi), + samef = 0; + e1 = chgi(1); + msg = {['Images don''t all have the same ' ... + sepcat(labs(chgf(e1,:)),', ')],... + 'First difference between image pair:',... + fnames{e1},... + fnames{e1+1}}; +end; +return; + +function s = sepcat(strs, sep) +% returns cell array of strings as one char string, separated by sep +if nargin < 2, + sep = ';'; +end +if isempty(strs), + s = ''; + return; +end +strs = strs(:)'; +strs = [strs; repmat({sep}, 1, length(strs))]; +s = [strs{1:end-1}]; +return; + diff --git a/spm/nii_for_spm2/spm_vol_nifti.m b/spm/nii_for_spm2/spm_vol_nifti.m new file mode 100755 index 0000000..8290a66 --- /dev/null +++ b/spm/nii_for_spm2/spm_vol_nifti.m @@ -0,0 +1,39 @@ +function V = spm_vol_nifti(fname,n) +% Get header information for a NIFTI-1 image. +% FORMAT V = spm_vol_nifti(P) +% P - filename. +% n - volume id (a 1x2 array, e.g. [3,1]) +% V - a structure containing the image volume information. +%____________________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% John Ashburner +% $Id: spm_vol_nifti.m 112 2005-05-04 18:20:52Z john $ + + +if nargin<2, n = [1 1]; end; +if ischar(n), n = str2num(n); end; +N = nifti(fname); +n = [n 1 1]; +n = n(1:2); +dm = [N.dat.dim 1 1 1 1]; +if any(n>dm(4:5)), V = []; return; end; + +dt = struct(N.dat); +dt = double([dt.dtype dt.be]); + +if isfield(N.extras,'mat') && size(N.extras.mat,3)>=n(1) && sum(sum(N.extras.mat(:,:,n(1))))~=0, + mat = N.extras.mat(:,:,n(1)); +else + mat = N.mat; +end; + +off = (n(1)-1+dm(4)*(n(2)-1))*ceil(spm_type(dt(1),'bits')*dm(1)*dm(2)/8)*dm(3) + N.dat.offset; +V = struct('fname', N.dat.fname,... + 'dim', dm(1:3),... + 'dt', dt,... + 'pinfo', [N.dat.scl_slope N.dat.scl_inter off]',... + 'mat', mat,... + 'n', n,... + 'descrip', N.descrip,... + 'private',N); diff --git a/spm/spm2/myspm_get_data_xyz.m b/spm/spm2/myspm_get_data_xyz.m new file mode 100644 index 0000000..caff5a7 --- /dev/null +++ b/spm/spm2/myspm_get_data_xyz.m @@ -0,0 +1,90 @@ +function [K,XYZvox,rXYZmm] = myspm_get_data_xyz(V,XYZmm,approx) +%MYSPM_GET_DATA_XYZ - gets data from image files at locations given in mm +% FORMAT [K,XYZvox,rXYZmm] = myspm_get_data(V,XYZmm,approx); +% +%INPUTS +% V: (cell) array of M filenames or volume structures from sspm_vol +% XYZmm: 3-by-N voxels locations to read in millimeters +% approx: Approximation mode. if approx==NaN (default) returns NaN for +% voxels out of the image else approximates see spm_sample_vol() +% +%OUPUTS +% K: M-by-N values read in the files +% XYZvox: 3-by-M-by-N matrix of voxels locations read in voxel space +% rXYZmm: 3-by-M-by-N matrix of actual coordinates of the voxels read +% +% Example +% >> spm_get_data_xyz(P,[45 -50 +50]) retrieves voxel intensity in +% the VMPFC of the MNI template... +% +% See also: spm_get_data + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2008 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2008-10-22 Creation +% +% ----------------------------- Script History --------------------------------- + +if ~isstruct(V) + V = spm_vol(V); + try + V = cat(2,V{:}); + end +end +if ~isequal(size(XYZmm,1),3) + error('XYZ input should be 3-by-N') +end +if nargin<3 + approx=NaN; +end +if isempty(approx) + approx=NaN; +end +if ~ischar(XYZmm) + + % number of voxels to retrieve + %--------------------------------------------------------------- + n=size(XYZmm,2); + % preallocate outputs + %--------------------------------------------------------------- + K = zeros( length(V),n); + XYZvox= zeros(3,length(V),n); + rXYZmm= zeros(3,length(V),n); + % expand approximation mode to match size of inputs + %--------------------------------------------------------------- + if prod(size(approx))==1 + approx=repmat(approx,length(V),1); + end + xyz = [XYZmm; ones(1,n)]; + xyz(isinf(xyz)) = 1e10*sign(xyz(isinf(xyz))); +else + switch (XYZmm) + case 'roi' + % To do: retrieve a region from a given atlas: + % aal:sup front gyrus:Right; + % brodmannn:4:Left etc. + end +end +for i = 1:length(V) + % trick from spm_XYZreg + vox = round(inv(V(i).mat)*xyz); + j = all(vox(1:3,:)<=repmat(V(i).dim(1:3)',1,n)) & all(vox(1:3,:)>zeros(3,n)); + if any(j) + %-Load mask image within current mask & update mask + %------------------------------------------------------- + K(i,j) = spm_sample_vol(V(i),vox(1,j),vox(2,j),vox(3,j),0); + end + if isnan(approx(i)) + K(i,~j)=NaN; + else + K(i,~j) = spm_sample_vol(V(i),vox(1,~j),vox(2,~j),vox(3,~j),approx(i)); + end + XYZvox(1:3,i,1:n) = vox(1:3,:); + rXYZmm(1:3,i,1:n) = V(i).mat(1:3,:)*xyz; +end diff --git a/spm/spm2/spm_P_RF.m b/spm/spm2/spm_P_RF.m new file mode 100644 index 0000000..289fc5c --- /dev/null +++ b/spm/spm2/spm_P_RF.m @@ -0,0 +1,188 @@ +function [P,p,Em,En,EN] = spm_P_RF(c,k,Z,df,STAT,R,n) +% Returns the [un]corrected P value using unifed EC theory +% FORMAT [P p Em En EN] = spm_P_RF(c,k,Z,df,STAT,R,n) +% +% c - cluster number +% k - extent {RESELS} +% Z - height {minimum over n values} +% df - [df{interest} df{error}] +% STAT - Statisical feild +% 'Z' - Gaussian feild +% 'T' - T - feild +% 'X' - Chi squared feild +% 'F' - F - feild +% R - RESEL Count {defining search volume} +% n - number of component SPMs in conjunction +% +% P - corrected P value - P(n > kmax} +% p - uncorrected P value - P(n > k} +% Em - expected total number of maxima {m} +% En - expected total number of resels per cluster {n} +% EN - expected total number of voxels {N} +% +%___________________________________________________________________________ +% +% spm_P_RF returns the probability of c or more clusters with more than +% k voxels in volume process of R RESELS thresholded at u. All p values +% can be considered special cases: +% +% spm_P_RF(1,0,Z,df,STAT,1,n) = uncorrected p value +% spm_P_RF(1,0,Z,df,STAT,R,n) = corrected p value {based on height Z) +% spm_P_RF(1,k,u,df,STAT,R,n) = corrected p value {based on extent k at u) +% spm_P_RF(c,k,u,df,STAT,R,n) = corrected p value {based on number c at k and u) +% spm_P_RF(c,0,u,df,STAT,R,n) = omnibus p value {based on number c at u) +% +% If n > 1 a conjunction probility over the n values of the statistic +% is returned +% +% Ref: Hasofer AM (1978) Upcrossings of random fields +% Suppl Adv Appl Prob 10:14-21 +% Ref: Friston et al (1993) Comparing functional images: Assessing +% the spatial extent of activation foci +% Ref: Worsley KJ et al 1996, Hum Brain Mapp. 4:58-73 +%___________________________________________________________________________ +% @(#)spm_P_RF.m 2.12 Karl Friston 02/10/16 + + +% get expectations +%=========================================================================== + +% get EC densities +%--------------------------------------------------------------------------- +D = max(find(R)); +R = R(1:D); +G = sqrt(pi)./gamma(([1:D])/2); +EC = spm_ECdensity(STAT,Z,df); +EC = EC([1:D]) + eps; + +% corrected p value +%--------------------------------------------------------------------------- +P = triu(toeplitz(EC'.*G))^n; +P = P(1,:); +EM = (R./G).*P; % over D dimensions +Em = sum(EM); % +EN = P(1)*R(D); % +En = EN/EM(D); % En = EN/EM(D); + +% get P{n > k} +%=========================================================================== + +% assume a Gaussian form for P{n > k} ~ exp(-beta*k^(2/D)) +% Appropriate for SPM{Z} and high d.f. SPM{T} +%--------------------------------------------------------------------------- +D = D - 1; +if isnan(k) || isnan(D) || ~k | ~D + + p = 1; + +elseif STAT == 'Z' + + beta = (gamma(D/2 + 1)/En)^(2/D); + p = exp(-beta*(k^(2/D))); + +elseif STAT == 'T' + + beta = (gamma(D/2 + 1)/En)^(2/D); + p = exp(-beta*(k^(2/D))); + +elseif STAT == 'X' + + beta = (gamma(D/2 + 1)/En)^(2/D); + p = exp(-beta*(k^(2/D))); + +elseif STAT == 'F' + + beta = (gamma(D/2 + 1)/En)^(2/D); + p = exp(-beta*(k^(2/D))); + +end + +% Poisson clumping heuristic {for multiple clusters} +%=========================================================================== +P = 1 - spm_Pcdf(c - 1,(Em + eps)*p); + + +% set P and p = [] for non-implemented cases +%+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +if k > 0 & n > 1 + P = []; p = []; +end +if k > 0 & (STAT == 'X' | STAT == 'F') + P = []; p = []; +end +%+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + + + +% spm_ECdensity +%=========================================================================== + +function [EC] = spm_ECdensity(STAT,t,df) +% Returns the EC density +%___________________________________________________________________________ +% +% Reference : Worsley KJ et al 1996, Hum Brain Mapp. 4:58-73 +% +%--------------------------------------------------------------------------- + +% EC densities (EC} +%--------------------------------------------------------------------------- +t = t(:)'; +if STAT == 'Z' + + % Gaussian Field + %------------------------------------------------------------------- + a = 4*log(2); + b = exp(-t.^2/2); + + EC(1,:) = 1 - spm_Ncdf(t); + EC(2,:) = a^(1/2)/(2*pi)*b; + EC(3,:) = a/((2*pi)^(3/2))*b.*t; + EC(4,:) = a^(3/2)/((2*pi)^2)*b.*(t.^2 - 1); + +elseif STAT == 'T' + + % T - Field + %------------------------------------------------------------------- + v = df(2); + a = 4*log(2); + b = exp(gammaln((v+1)/2) - gammaln(v/2)); + c = (1+t.^2/v).^((1-v)/2); + + EC(1,:) = 1 - spm_Tcdf(t,v); + EC(2,:) = a^(1/2)/(2*pi)*c; + EC(3,:) = a/((2*pi)^(3/2))*c.*t/((v/2)^(1/2))*b; + EC(4,:) = a^(3/2)/((2*pi)^2)*c.*((v-1)*(t.^2)/v - 1); + +elseif STAT == 'X' + + % X - Field + %------------------------------------------------------------------- + v = df(2); + a = (4*log(2))/(2*pi); + b = t.^(1/2*(v - 1)).*exp(-t/2-gammaln(v/2))/2^((v-2)/2); + + EC(1,:) = 1 - spm_Xcdf(t,v); + EC(2,:) = a^(1/2)*b; + EC(3,:) = a*b.*(t-(v-1)); + EC(4,:) = a^(3/2)*b.*(t.^2-(2*v-1)*t+(v-1)*(v-2)); + +elseif STAT == 'F' + + % F Field + %------------------------------------------------------------------- + k = df(1); + v = df(2); + a = (4*log(2))/(2*pi); + b = gammaln(v/2) + gammaln(k/2); + + EC(1,:) = 1 - spm_Fcdf(t,df); + EC(2,:) = a^(1/2)*exp(gammaln((v+k-1)/2)-b)*2^(1/2)... + *(k*t/v).^(1/2*(k-1)).*(1+k*t/v).^(-1/2*(v+k-2)); + EC(3,:) = a*exp(gammaln((v+k-2)/2)-b)*(k*t/v).^(1/2*(k-2))... + .*(1+k*t/v).^(-1/2*(v+k-2)).*((v-1)*k*t/v-(k-1)); + EC(4,:) = a^(3/2)*exp(gammaln((v+k-3)/2)-b)... + *2^(-1/2)*(k*t/v).^(1/2*(k-3)).*(1+k*t/v).^(-1/2*(v+k-2))... + .*((v-1)*(v-2)*(k*t/v).^2-(2*v*k-v-k-1)*(k*t/v)+(k-1)*(k-2)); +end diff --git a/spm/spm2/spm_create_vol.m b/spm/spm2/spm_create_vol.m new file mode 100644 index 0000000..8b960a9 --- /dev/null +++ b/spm/spm2/spm_create_vol.m @@ -0,0 +1,363 @@ +function V = spm_create_vol(V,varargin) +% Create an image file. +% FORMAT Vo = spm_create_vol(Vi,['noopen']) +% Vi - data structure containing image information. +% - see spm_vol for a description. +% 'noopen' - optional flag to say "don't open/create the image file". +% Vo - data structure after modification for writing. +%_______________________________________________________________________ +% @(#)spm_create_vol.m 2.14 John Ashburner 03/07/31 +for i=1:prod(size(V)), + if nargin>1, + v = create_vol(V(i),varargin{:}); + else, + v = create_vol(V(i)); + end; + f = fieldnames(v); + for j=1:size(f,1), + %eval(['V(i).' f{j} ' = v.' f{j} ';']); + V = setfield(V,{i},f{j},getfield(v,f{j})); + end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function V = create_vol(V,varargin) +if ~isfield(V,'n') | isempty(V.n), V.n = 1; end; +if ~isfield(V,'descrip') | isempty(V.descrip), V.descrip = 'SPM2 compatible'; end; +V.private = struct('hdr',[]); + +% Orientation etc... +M = V.mat; +if spm_flip_analyze_images, M = diag([-1 1 1 1])*M; end; +vx = sqrt(sum(M(1:3,1:3).^2)); +if det(M(1:3,1:3))<0, vx(1) = -vx(1); end; +origin = M\[0 0 0 1]'; +origin = round(origin(1:3)); + +[pth,nam,ext] = fileparts(V.fname); +fname = fullfile(pth,[nam, '.hdr']); +try, + [hdr,swapped] = spm_read_hdr(fname); +catch, + warning(['Could not read "' fname '"']); + swapped = 0; + hdr = []; +end; + +if ~isempty(hdr) & (hdr.dime.dim(5)>1 | V.n>1), + % cannot simply overwrite the header + + hdr.dime.dim(5) = max(V.n,hdr.dime.dim(5)); + if any(V.dim(1:3) ~= hdr.dime.dim(2:4)) + error('Incompatible image dimensions'); + end; + + if sum((vx-hdr.dime.pixdim(2:4)).^2)>1e-6, + error('Incompatible voxel sizes'); + end; + + V.dim(4) = spm_type(spm_type(hdr.dime.datatype)); + mach = 'native'; + if swapped, + V.dim(4) = V.dim(4)*256; + if spm_platform('bigend'), + mach = 'ieee-le'; + else, + mach = 'ieee-be'; + end; + end; + + if finite(hdr.dime.funused1) & hdr.dime.funused1, + scal = hdr.dime.funused1; + if finite(hdr.dime.funused2), + dcoff = hdr.dime.funused2; + else, + dcoff = 0; + end; + else + if hdr.dime.glmax-hdr.dime.glmin & hdr.dime.cal_max-hdr.dime.cal_min, + scal = (hdr.dime.cal_max-hdr.dime.cal_min)/(hdr.dime.glmax-hdr.dime.glmin); + dcoff = hdr.dime.cal_min - scal*hdr.dime.glmin; + else, + scal = 1; + dcoff = 0; + warning(['Assuming a scalefactor of 1 for "' V.fname '".']); + end; + end; + V.pinfo(1:2) = [scal dcoff]'; + V.private.hdr = hdr; +else, + + V.private.hdr = create_defaults; + + swapped = spm_type(V.dim(4),'swapped'); + dt = spm_type(spm_type(V.dim(4))); + if any(dt == [128+2 128+4 128+8]), + % Convert to a form that Analyze will support + dt = dt - 128; + end; + V.dim(4) = dt; + mach = 'native'; + if isnan(swapped) + warning('Big/Little endian issue unsolved!'); + elseif swapped, + V.dim(4) = V.dim(4)*256; + if spm_platform('bigend'), + mach = 'ieee-le'; + else, + mach = 'ieee-be'; + end; + end; + V.private.hdr.dime.datatype = dt; + V.private.hdr.dime.bitpix = spm_type(dt,'bits'); + + if spm_type(dt,'intt'), + + V.private.hdr.dime.glmax = spm_type(dt,'maxval'); + V.private.hdr.dime.glmin = spm_type(dt,'minval'); + + if 0, % Allow DC offset + V.private.hdr.dime.cal_max = max(V.private.hdr.dime.glmax*V.pinfo(1,:) + V.pinfo(2,:)); + V.private.hdr.dime.cal_min = min(V.private.hdr.dime.glmin*V.pinfo(1,:) + V.pinfo(2,:)); + V.private.hdr.dime.funused1 = 0; + scal = (V.private.hdr.dime.cal_max - V.private.hdr.dime.cal_min)/... + (V.private.hdr.dime.glmax - V.private.hdr.dime.glmin); + dcoff = V.private.hdr.dime.cal_min - V.private.hdr.dime.glmin*scal; + V.pinfo = [scal dcoff 0]'; + else, % Don't allow DC offset + cal_max = max(V.private.hdr.dime.glmax*V.pinfo(1,:) + V.pinfo(2,:)); + cal_min = min(V.private.hdr.dime.glmin*V.pinfo(1,:) + V.pinfo(2,:)); + V.private.hdr.dime.funused1 = cal_max/V.private.hdr.dime.glmax; + if V.private.hdr.dime.glmin, + V.private.hdr.dime.funused1 = max(V.private.hdr.dime.funused1,... + cal_min/V.private.hdr.dime.glmin); + end; + V.private.hdr.dime.cal_max = V.private.hdr.dime.glmax*V.private.hdr.dime.funused1; + V.private.hdr.dime.cal_min = V.private.hdr.dime.glmin*V.private.hdr.dime.funused1; + V.pinfo = [V.private.hdr.dime.funused1 0 0]'; + end; + else, + V.private.hdr.dime.glmax = 1; + V.private.hdr.dime.glmin = 0; + V.private.hdr.dime.cal_max = 1; + V.private.hdr.dime.cal_min = 0; + V.private.hdr.dime.funused1 = 1; + end; + + V.private.hdr.dime.pixdim(2:4) = vx; + V.private.hdr.dime.dim(2:4) = V.dim(1:3); + V.private.hdr.dime.dim(5) = V.n; + V.private.hdr.hist.origin(1:3) = origin; + + d = 1:min([length(V.descrip) 79]); + V.private.hdr.hist.descrip = char(zeros(1,80)); + V.private.hdr.hist.descrip(d) = V.descrip(d); + V.private.hdr.hk.db_name = char(zeros(1,18)); + [pth,nam,ext] = fileparts(V.fname); + d = 1:min([length(nam) 17]); + V.private.hdr.hk.db_name(d) = nam(d); +end; + +V.pinfo(3) = prod(V.private.hdr.dime.dim(2:4))*V.private.hdr.dime.bitpix/8*(V.n-1); + +fid = fopen(fname,'w',mach); +if (fid == -1), + error(['Error opening ' fname '. Check that you have write permission.']); +end; + +write_hk(fid,V.private.hdr.hk); +write_dime(fid,V.private.hdr.dime); +write_hist(fid,V.private.hdr.hist); +fclose(fid); + +fname = fullfile(pth,[nam, '.mat']); +off = -vx'.*origin; +mt = [vx(1) 0 0 off(1) ; 0 vx(2) 0 off(2) ; 0 0 vx(3) off(3) ; 0 0 0 1]; +if spm_flip_analyze_images, mt = diag([-1 1 1 1])*mt; end; + +if sum((V.mat(:) - mt(:)).*(V.mat(:) - mt(:))) > eps*eps*12 | exist(fname)==2, + if exist(fname)==2, + clear mat + str = load(fname); + if isfield(str,'mat'), + mat = str.mat; + elseif isfield(str,'M'), + mat = str.M; + if spm_flip_analyze_images, + for i=1:size(mat,3), + mat(:,:,i) = diag([-1 1 1 1])*mat(:,:,i); + end; + end; + end; + mat(:,:,V.n) = V.mat; + mat = fill_empty(mat,mt); + M = mat(:,:,1); if spm_flip_analyze_images, M = diag([-1 1 1 1])*M; end; + try, + save(fname,'mat','M','-append'); + catch, % Mat-file was probably Matlab 4 + save(fname,'mat','M'); + end; + else, + clear mat + mat(:,:,V.n) = V.mat; + mat = fill_empty(mat,mt); + M = mat(:,:,1); if spm_flip_analyze_images, M = diag([-1 1 1 1])*M; end; + save(fname,'mat','M'); + end; +end; + +if nargin==1 | ~strcmp(varargin{1},'noopen'), + fname = fullfile(pth,[nam, '.img']); + V.private.fid = fopen(fname,'r+',mach); + if (V.private.fid == -1), + V.private.fid = fopen(fname,'w',mach); + if (V.private.fid == -1), + error(['Error opening ' fname '. Check that you have write permission.']); + end; + end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function Mo = fill_empty(Mo,Mfill) +todo = []; +for i=1:size(Mo,3), + if ~any(any(Mo(:,:,i))), + todo = [todo i]; + end; +end; +if ~isempty(todo), + for i=1:length(todo), + Mo(:,:,todo(i)) = Mfill; + end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function write_hk(fid,hk) +% write (struct) header_key +%----------------------------------------------------------------------- +fseek(fid,0,'bof'); +fwrite(fid,hk.sizeof_hdr, 'int32'); +fwrite(fid,hk.data_type, 'char' ); +fwrite(fid,hk.db_name, 'char' ); +fwrite(fid,hk.extents, 'int32'); +fwrite(fid,hk.session_error,'int16'); +fwrite(fid,hk.regular, 'char' ); +if fwrite(fid,hk.hkey_un0, 'char' )~= 1, + error(['Error writing ' fopen(fid) '. Check your disk space.']); +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function write_dime(fid,dime) +% write (struct) image_dimension +%----------------------------------------------------------------------- +fseek(fid,40,'bof'); +fwrite(fid,dime.dim, 'int16'); +fwrite(fid,dime.vox_units, 'uchar' ); +fwrite(fid,dime.cal_units, 'uchar' ); +fwrite(fid,dime.unused1, 'int16' ); +fwrite(fid,dime.datatype, 'int16'); +fwrite(fid,dime.bitpix, 'int16'); +fwrite(fid,dime.dim_un0, 'int16'); +fwrite(fid,dime.pixdim, 'float'); +fwrite(fid,dime.vox_offset, 'float'); +fwrite(fid,dime.funused1, 'float'); +fwrite(fid,dime.funused2, 'float'); +fwrite(fid,dime.funused2, 'float'); +fwrite(fid,dime.cal_max, 'float'); +fwrite(fid,dime.cal_min, 'float'); +fwrite(fid,dime.compressed, 'int32'); +fwrite(fid,dime.verified, 'int32'); +fwrite(fid,dime.glmax, 'int32'); +if fwrite(fid,dime.glmin, 'int32')~=1, + error(['Error writing ' fopen(fid) '. Check your disk space.']); +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function write_hist(fid,hist) +% write (struct) data_history +%----------------------------------------------------------------------- +fseek(fid,148,'bof'); +fwrite(fid,hist.descrip, 'uchar'); +fwrite(fid,hist.aux_file, 'uchar'); +fwrite(fid,hist.orient, 'uchar'); +fwrite(fid,hist.origin, 'int16'); +fwrite(fid,hist.generated, 'uchar'); +fwrite(fid,hist.scannum, 'uchar'); +fwrite(fid,hist.patient_id, 'uchar'); +fwrite(fid,hist.exp_date, 'uchar'); +fwrite(fid,hist.exp_time, 'uchar'); +fwrite(fid,hist.hist_un0, 'uchar'); +fwrite(fid,hist.views, 'int32'); +fwrite(fid,hist.vols_added, 'int32'); +fwrite(fid,hist.start_field,'int32'); +fwrite(fid,hist.field_skip, 'int32'); +fwrite(fid,hist.omax, 'int32'); +fwrite(fid,hist.omin, 'int32'); +fwrite(fid,hist.smax, 'int32'); +if fwrite(fid,hist.smin, 'int32')~=1, + error(['Error writing ' fopen(fid) '. Check your disk space.']); +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function hdr = create_defaults +hk.sizeof_hdr = 348; +hk.data_type = ['dsr ' 0]; +hk.db_name = char(zeros(1,18)); +hk.extents = 0; +hk.session_error= 0; +hk.regular = 'r'; +hk.hkey_un0 = 0; + +dime.dim = [4 0 0 0 1 0 0 0]; +dime.vox_units = ['mm ' 0]; +dime.cal_units = char(zeros(1,8)); +dime.unused1 = 0; +dime.datatype = -1; +dime.bitpix = 0; +dime.dim_un0 = 0; +dime.pixdim = [0 1 1 1 1 0 0 0]; +dime.vox_offset = 0; +dime.funused1 = 1; +dime.funused2 = 0; +dime.funused3 = 0; +dime.cal_max = 1; +dime.cal_min = 0; +dime.compressed = 0; +dime.verified = 0; +dime.glmax = 1; +dime.glmin = 0; + +hist.descrip = char(zeros(1,80)); +hist.descrip(1:length('SPM2 compatible')) = 'SPM2 compatible'; +hist.aux_file = char(zeros(1,24)); +hist.orient = char(0); +hist.origin = [0 0 0 0 0]; +hist.generated = char(zeros(1,10)); +hist.scannum = char(zeros(1,10)); +hist.patient_id = char(zeros(1,10)); +hist.exp_date = char(zeros(1,10)); +hist.exp_time = char(zeros(1,10)); +hist.hist_un0 = char(zeros(1,3)); +hist.generated(1:5) = 'today'; +hist.views = 0; +hist.vols_added = 0; +hist.start_field= 0; +hist.field_skip = 0; +hist.omax = 0; +hist.omin = 0; +hist.smax = 0; +hist.smin = 0; + +hdr.hk = hk; +hdr.dime = dime; +hdr.hist = hist; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ + diff --git a/spm/spm2/spm_dcm_display.m b/spm/spm2/spm_dcm_display.m new file mode 100644 index 0000000..52ecd5e --- /dev/null +++ b/spm/spm2/spm_dcm_display.m @@ -0,0 +1,266 @@ +function spm_dcm_display(varargin) +% region and anatomical graph display +% FORMAT spm_dcm_display(xY,a,c,u,M,U,img) +% xY - cell of region structures (see spm_regions) +% a - connections of directed graph a(i,j,1) = p value +% c - node-specific inputs +% u - (3 x 2) projection matrix [default = [] ] +% M - margin (mm) [default = 24 ] +% U - theshold for plotting connections [default = 0.9] +% beta - image contrast ( 0 < beta < 1 ) +% img - image to plot on +% markerstyle - Marker size +% markerstyle2 - Marker style +%___________________________________________________________________________ +% @(#)spm_dcm_display.m 2.5 Karl Friston 02/10/29 + + +% null input arguments +%--------------------------------------------------------------------------- +% Fgraph = spm_figure('GetWin','Graphics'); +n = length(varargin); + +if n < 1; xY = []; else, xY = varargin{1}; end +if n < 2; a = []; else, a = varargin{2}; end +if n < 3; c = []; else, c = varargin{3}; end +if n < 4; u = []; else, u = varargin{4}; end +if n < 5; M = 16; else, M = varargin{5}; end +if n < 6; U = .9; else, U = varargin{6}; end +if n < 7; beta=.24; else, beta = varargin{7}; end +if n < 8; img = []; else, img=varargin{8}; end +if isempty(img) + fullfile(spm('Dir'),'canonical','single_subj_T1.mnc'); +end +markerstyle = {'MarkerSize',64,'Marker', '.'}; +if n >= 9; markerstyle=[markerstyle varargin{9}]; end +markerstyle2 = {'MarkerSize',4,'Marker', '.'}; +if n >= 10; markerstyle2=[markerstyle2 varargin{10}]; end +if n < 11; imgori='sa'; else imgori=varargin{11}; end + +col = [0 0 0]; +rad = 6; +w = 2; + +% enforce orientation for 1 or 2 regions +%--------------------------------------------------------------------------- +if length(xY) < 3 + switch imgori + case 'ax' + u = [[1 0 0];[0 1 0]]'; + case 'sa' + u = [[0 1 0];[0 0 1]]'; + case 'co' + u = [[1 0 0];[0 0 1]]'; + end +end + +% get dimensions +%--------------------------------------------------------------------------- +m = size(xY,2); +L = []; +for i = 1:m + L = [L xY(i).xyz]; + name{i} = xY(i).name; +end +L = [L; ones(1,m)]; +o = mean(L,2); +M1 = spm_matrix(-o(1:3)'); + +% compute projection matrix for 'principal' plane +%--------------------------------------------------------------------------- +if ~length(u) + [u s v] = svd(M1*L); + u = u(:,[2 1]); + [i j] = max(abs(u)); + u = u*diag(sign([u(j(1),1) u(j(2),2)])); +end +M2 = u'; +M2(4,4) = 1; +L = M2*M1*L; + +% coordinates +%--------------------------------------------------------------------------- +i = (min(L(1,:)) - M):(max(L(1,:)) + M); +j = (min(L(2,:)) - M):(max(L(2,:)) + M); +x = kron(ones(size(j)),i); +y = kron(j,ones(size(i))); +xyz = [x; y; zeros(1,length(x)); ones(1,length(x))]; +xyz = pinv(M1)*pinv(M2)*xyz; +M3 = [1 0 0 -min(i); 0 -1 0 -min(j); 0 0 0 0; 0 0 0 1]; +L = M3*L; + + +% get T1 background +%--------------------------------------------------------------------------- +if ~exist(img, 'file') + img = fullfile(spm('Dir'),'canonical','single_subj_T1.mnc'); +end +try + V = spm_vol(img); +catch + V = spm_vol('mask.img'); +end +ijk = inv(V.mat)*xyz; +t1 = spm_sample_vol(V,ijk(1,:),ijk(2,:),ijk(3,:),2); +t1 = size(colormap,1)*((1 - beta) + beta*t1/max(t1(:))); + + +% Watermark and regions +%--------------------------------------------------------------------------- +str = get(get(gca,'Title'),'String'); +image(rot90(reshape(t1,length(i),length(j)))) +axis image off +title(str) + + +% Connections +%--------------------------------------------------------------------------- +Q = [-pi:pi/32:pi]; +Q = rad*[sin(Q); cos(Q)]; +x = mean(L(1,:)); +y = mean(L(2,:)); +q = 1/3; +for i = 1:length(a) + for j = 1:length(a) + if ~isnan(a(i,j,1)) + + % show connection + %----------------------------------------------------------- + if i ~= j + + % line + %--------------------------------------------------- + h = line(L(1,[i j]),L(2,[i j]),... + 'Color',col,... + 'LineStyle',':',... + 'LineWidth',w); + + % if significant + %--------------------------------------------------- + if a(i,j,1) > U + set(h,'LineStyle','-','LineWidth',w) + + % text + %------------------------------------------- + u = q*(L(1,j) - L(1,i)) + L(1,i); + v = q*(L(2,j) - L(2,i)) + L(2,i);; + str = {}; + for k = 1:size(a,3) + str{k} = sprintf('%0.2f ',a(i,j,k)); + end + h = text(u,v,1,str(:),'FontSize',10,... + 'HorizontalAlignment','Center'); + if a(i,j,2) < 0 + set(h, 'Color','r') + end + end + + % self connection + %----------------------------------------------------------- + else + + % line + %--------------------------------------------------- + u = (L(1,i) - x); + v = (L(2,i) - y); + l = sqrt(u^2 + v^2); + l = (l + rad)/l; + u = Q(1,:) + x + l*u; + v = Q(2,:) + y + l*v; + h = line(u,v,... + 'Color',col,... + 'LineStyle',':',... + 'LineWidth',w); + + % if significant + %--------------------------------------------------- + if a(i,j,1) > U + set(h,'LineStyle','-','LineWidth',w) + + % text + %------------------------------------------- + u = u(48); + v = v(48); + str = {}; + for k = 1:size(a,3) + str{k} = sprintf('%0.2f ',a(i,j,k)); + end + h = text(u,v,1,str(:),'FontSize',10,... + 'HorizontalAlignment','Center'); + if a(i,j,2) < 0 + set(h, 'Color','r') + end + end + end + end + end +end + +% Extrinsic influences +%--------------------------------------------------------------------------- +for i = 1:size(c,1) + if ~isnan(c(i,1)) + + % line + %----------------------------------------------------------- + u = (L(1,i) - x); + v = (L(2,i) - y); + l = sqrt(u^2 + v^2); + l = (l + rad)/l; + u = [u l*u] + x; + v = [v l*v] + y; + h = line(u,v,... + 'Color',col,... + 'LineStyle',':',... + 'LineWidth',w); + + % if significant + %---------------------------------------------------------- + if c(i,1) > U + set(h,'LineStyle','-','LineWidth',w) + + % patch + %-------------------------------------------------- + u = u(2); + v = v(2); + + % text + %-------------------------------------------------- + str = {}; + for k = 1:size(c,2) + str{k} = sprintf('%0.2f ',c(i,k)); + end + h = text(u,v,str(:),'FontSize',10,... + 'HorizontalAlignment','Center'); + if c(i,2) < 0 + set(h, 'Color','r') + end + + end + end +end + + +% projected coordinates of voxels within region[s] +%--------------------------------------------------------------------------- +hold on +for i = 1:m + l = xY(i).XYZmm; + n = size(l,2); + l = [l; ones(1,n)]; + l = M3*M2*M1*l; + plot(l(1,:),l(2,:),'.r',markerstyle2{:}) +end + +line(L(1,:),L(2,:),... + 'Color',[0 0 0],... + 'Marker','.',... + 'LineStyle','none',... + markerstyle{:}); + +text(L(1,:),L(2,:),name,'FontSize',8,... + 'FontWeight','Bold',... + 'Color','w',... + 'HorizontalAlignment','center',... + 'FontAngle','italic') +hold off diff --git a/spm/spm2/spm_defaults.m b/spm/spm2/spm_defaults.m new file mode 100644 index 0000000..0b3b75d --- /dev/null +++ b/spm/spm2/spm_defaults.m @@ -0,0 +1,126 @@ + +% Sets the defaults which are used by SPM +% +% FORMAT spm_defaults +%_______________________________________________________________________ +% +% This file is intended to be customised for the site. +% Individual users can make copies which can be stored in their own +% matlab subdirectories. If ~/matlab is ahead of the SPM directory +% in the MATLABPATH, then the users own personal defaults are used. +% +% Care must be taken when modifying this file +%_______________________________________________________________________ +% @(#)spm_defaults.m 2.23 John Ashburner, Andrew Holmes 03/04/16 + +global defaults + +% Misc +%======================================================================= +defaults.grid = 0.4; +defaults.cmdline = 0; +defaults.logfile = ''; +defaults.printstr = [];%spm_figure('DefPrintCmd'),'spm2.ps']; + +% File format specific +%======================================================================= +defaults.analyze.multivol = 0; +defaults.analyze.flip = 1; % <<= Very important. Relates to L/R +% Changed by KND: +% defaults.analyze.flip = 0; % <<= Very important. Relates to L/R +% but now in the CMU it's 1... + +% Stats defaults +%======================================================================= +defaults.stats.maxmem = 2^20; +defaults.stats.maxres = 64; +defaults.stats.fmri.ufp = 0.001; +defaults.stats.fmri.t = 16; +defaults.stats.fmri.t0 = 8; +defaults.stats.pet.ufp = 0.05; + +defaults.stats.fmri.t0 = 1; + +defaults.modality = 'FMRI'; + + +% Mask defaults +%======================================================================= +defaults.mask.thresh = 0.8; + +% Realignment defaults +%======================================================================= +defaults.realign.estimate.quality = 0.75; +defaults.realign.estimate.weight = 0; +defaults.realign.estimate.interp = 2; +defaults.realign.estimate.wrap = [0 0 0]; +defaults.realign.write.mask = 1; +defaults.realign.write.interp = 4; +defaults.realign.write.wrap = [0 0 0]; + +% Unwarp defaults +%======================================================================= +defaults.unwarp.estimate.fwhm = 4; +defaults.unwarp.estimate.basfcn = [10 10]; +defaults.unwarp.estimate.regorder= 1; +defaults.unwarp.estimate.regwgt = 1e5; +defaults.unwarp.estimate.soe = 1; +defaults.unwarp.estimate.rem = 1; +defaults.unwarp.estimate.jm = 0; +defaults.unwarp.estimate.noi = 5; +defaults.unwarp.estimate.expround= 'Average'; +% +% Unwarp uses defaults.realign.write +% defaults for writing. +% + +% Coregistration defaults +%======================================================================= +defaults.coreg.estimate.cost_fun = 'nmi'; +defaults.coreg.estimate.sep = [4 2]; +defaults.coreg.estimate.tol = [0.02 0.02 0.02 0.001 0.001 0.001 0.01 0.01 0.01 0.001 0.001 0.001]; +defaults.coreg.estimate.fwhm = [7 7]; +defaults.coreg.write.interp = 1; +defaults.coreg.write.wrap = [0 0 0]; +defaults.coreg.write.mask = 0; + +% Spatial Normalisation defaults +%======================================================================= +defaults.normalise.estimate.smosrc = 8; +defaults.normalise.estimate.smoref = 0; +defaults.normalise.estimate.regtype = 'mni'; +defaults.normalise.estimate.weight = ''; +defaults.normalise.estimate.cutoff = 25; +defaults.normalise.estimate.nits = 16; +defaults.normalise.estimate.reg = 1; +defaults.normalise.estimate.wtsrc = 0; +defaults.normalise.write.preserve = 0; +defaults.normalise.write.bb = [[-78 -112 -50];[78 76 85]]; +%defaults.normalise.write.vox = [2 2 2]; +defaults.normalise.write.vox = [3 3 3]; +defaults.normalise.write.interp = 1; +defaults.normalise.write.wrap = [0 0 0]; + +% Segmentation defaults +%======================================================================= +defaults.segment.estimate.priors = str2mat(... + fullfile(spm('Dir'),'apriori','gray.mnc'),... + fullfile(spm('Dir'),'apriori','white.mnc'),... + fullfile(spm('Dir'),'apriori','csf.mnc')); +defaults.segment.estimate.reg = 0.01; +defaults.segment.estimate.cutoff = 30; +defaults.segment.estimate.samp = 3; +defaults.segment.estimate.bb = [[-88 88]' [-122 86]' [-60 95]']; +defaults.segment.estimate.affreg.smosrc = 8; +defaults.segment.estimate.affreg.regtype = 'mni'; +%defaults.segment.estimate.affreg.weight = fullfile(spm('Dir'),'apriori','brainmask.mnc'); +defaults.segment.estimate.affreg.weight = ''; +defaults.segment.write.cleanup = 1; +defaults.segment.write.wrt_cor = 1; + +% Bias field estimation defaults +%======================================================================= +defaults.bias.nbins = 256; % Number of histogram bins +defaults.bias.reg = 0.01; % Regularisation +defaults.bias.cutoff = 30; % DCT frequency cutoff (mm) + diff --git a/spm/spm2/spm_figure.m b/spm/spm2/spm_figure.m new file mode 100644 index 0000000..34753b4 --- /dev/null +++ b/spm/spm2/spm_figure.m @@ -0,0 +1,875 @@ +function varargout=spm_figure(varargin) +% Setup and callback functions for Graphics window +% FORMAT varargout=spm_figure(varargin) +% - An embedded callback, multi-function function +% - For detailed programmers comments, see format specifications +% in main body of code +%_______________________________________________________________________ +% +% spm_figure creates and manages the 'Graphics' window. This window and +% these facilities may be used independently of SPM, and any number of +% Graphics windows my be used within the same MatLab session. (Though +% only one SPM 'Graphics' 'Tag'ed window is permitted. +% +% The Graphics window is provided with a menu bar at the top that +% facilitates editing and printing of the current graphic display, +% enabling interactive editing of graphic output prior to printing +% (e.g. selection of color maps, deleting, moving and editing graphics +% objects or adding text). (This menu is also provided as a figure +% background "ContextMenu" - right-clicking on the figure background +% should bring up the menu.) +% +% Print: Creates a footnote (detailing the SPM version, user & date) +% and evaluates defaults.printstr (see spm_defaults.m). Graphics windows with +% multi-page axes are printed page by page. +% +% Clear: Clears the Graphics window. If in SPM usage (figure 'Tag'ed as +% 'Graphics') then all SPM windows are cleared and reset. +% +% Colormap options: +% * gray, hot, pink: Sets the colormap to its default values and loads +% either a grayscale, 'hot metal' or color map. +% * gray-hot, etc: Creates a 'split' colormap {128 x 3 matrix}. +% The lower half is a gray scale and the upper half +% is 'hot metal' or 'pink'. This color map is used for +% viewing 'rendered' SPMs on a PET, MRI or other background images +% +% Colormap effects: +% * Invert: Inverts (flips) the current color map. +% * Brighten and Darken: Brighten and Darken the current colourmap +% using the MatLab BRIGHTEN command, with beta's of +0.2 and -0.2 +% respectively. +% +% Editing: Right button ('alt' button) cancels operations +% * Cut : Deletes the graphics object next selected (if deletable) +% Select with middle mouse button to delete blocks of text, +% or to delete individual elements from a plot. +% * Move : To re-position a text, uicontrol or axis object using a +% 'drag and drop' implementation (i.e. depress - move - release) +% Using the middle 'extend' mouse button on a text object moves +% the axes containing the text - i.e. blocks of text. +% * Size : Re-sizes the text, uicontrol or axis object next selected +% {left button - decrease, middle button - increase} by a factor +% of 1.24 (or increase/decrease FontSize by 2 dpi) +% * Text : Creates an editable text widget that produces a text object as +% its CallBack. +% The text object is provided with a ContextMenu, obtained by +% right-clicking ('alt') on the text, allowing text attributes +% to be changed. Alternatively, the edit facilities on the window +% menu bar or ContextMenu can be used. +% * Edit : To edit text, select a text object with the circle cursor, +% and edit the text in the editable text widget that appears. +% A middle 'extend' mouse click places a context menu on the text +% object, facilitating easy modification of text atributes. +% +% For SPM usage, the figure should be 'Tag'ed as 'Graphics'. +% +% For SPM power users, and programmers, spm_figure provides utility +% routines for using the SPM graphics interface. Of particular use are +% the GetWin, FindWin and Clear functions See the embedded callback +% reference in the main body of spm_figure, below the help text. +% +% See also: spm_print, spm_clf +% +%_______________________________________________________________________ +% @(#)spm_figure.m 2.39 Andrew Holmes 03/05/16 + +%======================================================================= +% - FORMAT specifications for embedded CallBack functions +%======================================================================= +%( This is a multi function function, the first argument is an action ) +%( string, specifying the particular action function to take. Recall ) +%( MatLab's command-function duality: `spm_figure Create` is ) +%( equivalent to `spm_figure('Create')`. ) +% +% FORMAT F = spm_figure +% [ShortCut] Defaults to Action 'Create' +% +% FORMAT F = spm_figure(F) - numeric F +% [ShortCut] Defaults to spm_figure('CreateBar',F) +% +% FORMAT F = spm_figure('Create',Tag,Name,Visible) +% Create a full length WhiteBg figure 'Tag'ed Tag (if specified), +% with a ToolBar and background context menu. +% Equivalent to spm_figure('CreateWin','Tag') and spm_figure('CreateBar') +% Tag - 'Tag' string for figure. +% Name - Name for window +% Visible - 'on' or 'off' +% F - Figure used +% +% FORMAT F = spm_figure('FindWin',F) +% Finds window with 'Tag' or figure numnber F - returns empty F if not found +% F - (Input) Figure to use [Optional] - 'Tag' string or figure number. +% - Defaults to 'Graphics' +% F - (Output) Figure number (if found) or empty (if not). +% +% FORMAT F = spm_figure('GetWin',Tag) +% Like spm_figure('FindWin',Tag), except that if 'Tag' is 'Graphics' or +% 'Interactive' and no such 'Tag'ged figure is found, one is created. Further, +% the "got" window is made current. +% Tag - Figure 'Tag' to get, defaults to 'Graphics' +% F - Figure number (if found/created) or empty (if not). +% +% FORMAT F = spm_figure('ParentFig',h) +% Finds window containing the object whose handle is specified +% h - Handle of object whose parent figure is required +% - If a vector, then first object handle is used +% F - Number or parent figure +% +% FORMAT spm_figure('Clear',F,Tags) +% Clears figure, leaving ToolBar (& other objects with invisible handles) +% Optional third argument specifies 'Tag's of objects to delete. +% If figure F is 'Tag'ged 'Interactive' (SPM usage), then the window +% name and pointer are reset. +% F - 'Tag' string or figure number of figure to clear, defaults to gcf +% Tags - 'Tag's (string matrix or cell array of strings) of objects to delete +% *regardless* of 'HandleVisibility'. Only these objects are deleted. +% '!all' denotes all objects +% +% FORMAT str = spm_figure('DefPrintCmd') +% Returns default print command for SPM, as a string +% +% FORMAT spm_figure('Print',F) +% SPM print function: Appends footnote & executes PRINTSTR +% F - [Optional] Figure to print. ('Tag' or figure number) +% Defaults to figure 'Tag'ed as 'Graphics'. +% If none found, uses CurrentFigure if avaliable. +% defaults.printstr - global variable holding print command to be evaluated +% Defaults to 'print -dps2 fig.ps' +% If objects 'Tag'ed 'NextPage' and 'PrevPage' are found, then the +% pages are shown and printed in order. In breif, pages are held as +% seperate axes, with ony one 'Visible' at any one time. The handles of +% the "page" axes are stored in the 'UserData' of the 'NextPage' +% object, while the 'PrevPage' object holds the current page number. +% See spm_help('!Disp') for details on setting up paging axes. +% +% FORMAT [hNextPage, hPrevPage, hPageNo] = spm_figure('NewPage',hPage) +% SPM pagination function: Makes objects with handles hPage paginated +% Creates pagination buttons if necessary. +% hPage - Handles of objects to stick to this page +% hNextPage, hPrevPage, hPageNo - Handles of pagination controls +% +% FORMAT spm_figure('TurnPage',move,F) +% SPM pagination function: Turn to specified page +% +% FORMAT spm_figure('DeletePageControls',F) +% SPM pagination function: Deletes page controls +% F - [Optional] Figure in which to attempt to turn the page +% Defaults to 'Graphics' 'Tag'ged window +% +% FORMAT n = spm_figure('#page') +% Returns the current page number. +% +% FORMAT spm_figure('WaterMark',F,str,Tag,Angle,Perm) +% Adds watermark to figure windows. +% F - Figure for watermark. Defaults to gcf +% str - Watermark string. Defaults (missing or empty) to SPM +% Tag - Tag for watermark axes. Defaults to '' +% Angle - Angle for watermark. Defaults to -45 +% Perm - If specified, then watermark is permanent (HandleVisibility 'off') +% +% FORMAT F = spm_figure('CreateWin',Tag,Name,Visible) +% Creates a full length WhiteBg figure 'Tag'ged Tag (if specified). +% F - Figure created +% Tag - Tag for window +% Name - Name for window +% Visible - 'on' or 'off' +% +% FORMAT WS = spm_figure('GetWinScale') +% Returns ratios of current display dimensions to that of a 1152 x 900 +% Sun display. WS=[Xratio,Yratio,Xratio,Yratio]. Used for scaling other +% GUI elements. +% (Function duplicated in spm.m, repeated to reduce inter-dependencies.) +% +% FORMAT FS = spm_figure('FontSizes',FS) +% Returns fontsizes FS scaled for the current display. +% FS - (vector of) Font sizes to scale +% [default [08,09,11,13,14,6:36]] +% +% FORMAT spm_figure('CreateBar',F) +% Creates toolbar in figure F (defaults to gcf). F can be a 'Tag' +% If the figure is 'Tag'ed as 'Graphics' (SPM usage), then the Print button +% callback is set to attempt to clear an 'Interactive' figure too. +% +% FORMAT spm_figure('ColorMap') +% Callback for "ColorMap" buttons +% +% FORMAT h = spm_figure('GraphicsHandle',F) +% GUI choose object for handle identification. LeftMouse 'normal' returns +% handle, MiddleMouse 'extend' returns parents handle, RightMouse 'alt' cancels. +% F - figure to do a GUI "handle ID" in [Default gcbf] +%_______________________________________________________________________ + + +%-Condition arguments +%----------------------------------------------------------------------- +if (nargin==0), Action = 'Create'; else, Action = varargin{1}; end + +switch lower(Action), case 'create' +%======================================================================= +% F = spm_figure('Create',Tag,Name,Visible) +%-Condition arguments +if nargin<4, Visible='on'; else, Visible=varargin{4}; end +if nargin<3, Name=''; else, Name=varargin{3}; end +if nargin<2, Tag=''; else, Tag=varargin{2}; end + +F = spm_figure('CreateWin',Tag,Name,Visible); +spm_figure('CreateBar',F); +spm_figure('FigContextMenu',F); +varargout = {F}; + + +case 'findwin' +%======================================================================= +% F=spm_figure('FindWin',F) +% F=spm_figure('FindWin',Tag) +%-Find window: Find window with FigureNumber# / 'Tag' attribute +%-Returns empty if window cannot be found - deletes multiple tagged figs. + +if nargin<2, F='Graphics'; else, F=varargin{2}; end + +if isempty(F) + % Leave F empty +elseif ischar(F) + % Finds Graphics window with 'Tag' string - delete multiples + Tag=F; + F = findobj(get(0,'Children'),'Flat','Tag',Tag); + if length(F) > 1 + % Multiple Graphics windows - close all but most recent + close(F(2:end)) + F = F(1); + end +else + % F is supposed to be a figure number - check it + if ~any(F==get(0,'Children')), F=[]; end +end +varargout = {F}; + +case 'getwin' +%======================================================================= +% F=spm_figure('GetWin',Tag) + +if nargin<2, Tag='Graphics'; else, Tag=varargin{2}; end +F = spm_figure('FindWin',Tag); + +if isempty(F) + if ischar(Tag) + switch Tag, case 'Graphics' + F = spm_figure('Create','Graphics','Graphics'); + case 'Interactive' + F = spm('CreateIntWin'); + end + end +else + set(0,'CurrentFigure',F); +end +varargout = {F}; + +case 'parentfig' +%======================================================================= +% F=spm_figure('ParentFig',h) +if nargin<2, error('No object specified'), else, h=varargin{2}; end +F = get(h(1),'Parent'); +while ~strcmp(get(F,'Type'),'figure'), F=get(F,'Parent'); end +varargout = {F}; + + +case 'clear' +%======================================================================= +% spm_figure('Clear',F,Tags) + +%-Sort out arguments +%----------------------------------------------------------------------- +if nargin<3, Tags=[]; else, Tags=varargin{3}; end +if nargin<2, F=get(0,'CurrentFigure'); else, F=varargin{2}; end +F = spm_figure('FindWin',F); +if isempty(F), return, end + +%-Clear figure +%----------------------------------------------------------------------- +if isempty(Tags) + %-Clear figure of objects with 'HandleVisibility' 'on' + pos = get(F,'Position'); + delete(findobj(get(F,'Children'),'flat','HandleVisibility','on')); + drawnow + set(F,'Position',pos); + + %-Reset figures callback functions + set(F,'KeyPressFcn','',... + 'WindowButtonDownFcn','',... + 'WindowButtonMotionFcn','',... + 'WindowButtonUpFcn','') + %-If this is the 'Interactive' window, reset name & UserData + if strcmp(get(F,'Tag'),'Interactive') + set(F,'Name','','UserData',[]), end +else + %-Clear specified objects from figure + cSHH = get(0,'ShowHiddenHandles'); + set(0,'ShowHiddenHandles','on') + if ischar(Tags); Tags=cellstr(Tags); end + if any(strcmp(Tags(:),'!all')) + delete(get(F,'Children')) + else + for tag = Tags(:)' + delete(findobj(get(F,'Children'),'flat','Tag',tag{:})); + end + end + set(0,'ShowHiddenHandles',cSHH) +end +set(F,'Pointer','Arrow') + + +case 'defprintcmd' +%======================================================================= +% spm_figure('DefPrintCmd') +varargout = {'print -dpsc2 -painters -append -noui '}; + + +case 'print' +%======================================================================= +% spm_figure('Print',F) + +%-Arguments & defaults +if nargin<2, F='Graphics'; else, F=varargin{2}; end + +%-Find window to print, default to gcf if specified figure not found +% Return if no figures +F=spm_figure('FindWin',F); +if isempty(F), F = get(0,'CurrentFigure'); end +if isempty(F), return, end + +%-Note current figure, & switch to figure to print +cF = get(0,'CurrentFigure'); +set(0,'CurrentFigure',F) + +%-See if window has paging controls +hNextPage = findobj(F,'Tag','NextPage'); +hPrevPage = findobj(F,'Tag','PrevPage'); +hPageNo = findobj(F,'Tag','PageNo'); +iPaged = ~isempty(hNextPage); + +%-Construct print command +%----------------------------------------------------------------------- +global defaults +if ~isempty(defaults), + PRINTSTR = defaults.printstr; +else, + PRINTSTR = [spm_figure('DefPrintCmd'),'spm2.ps']; +end + +%-Create footnote with SPM version, username, date and time. +%----------------------------------------------------------------------- +FNote = sprintf('%s%s: %s',spm('ver'),spm('GetUser',' (%s)'),spm('time')); +%-Delete old tag lines, and print new one +delete(findobj(F,'Tag','SPMprintFootnote')); +axes('Position',[0.005,0.005,0.1,0.1],... + 'Visible','off',... + 'Tag','SPMprintFootnote') +text(0,0,FNote,'FontSize',6); + +%-Temporarily change all units to normalized prior to printing +% (Fixes bizzarre problem with stuff jumping around!) +%----------------------------------------------------------------------- +H = findobj(get(F,'Children'),'flat','Type','axes'); +un = cellstr(get(H,'Units')); +set(H,'Units','normalized') + + +%-Print +%----------------------------------------------------------------------- +err = 0; +if ~iPaged + printstr = clean_PRINTSTR(PRINTSTR); + try, eval(printstr), catch, err=1; end +else + hPg = get(hNextPage,'UserData'); + Cpage = get(hPageNo, 'UserData'); + nPages = size(hPg,1); + + set([hNextPage,hPrevPage,hPageNo],'Visible','off') + if Cpage~=1 + set(hPg{Cpage,1},'Visible','off'), end + for p = 1:nPages + set(hPg{p,1},'Visible','on'); + printstr = clean_PRINTSTR(PRINTSTR); + try, eval(printstr), catch, err=1; end + set(hPg{p,1},'Visible','off') + end + set(hPg{Cpage,1},'Visible','on') + set([hNextPage,hPrevPage,hPageNo],'Visible','on') +end + +if err + errstr = lasterr; + tmp = [find(abs(errstr)==10),length(errstr)+1]; + str = {errstr(1:tmp(1)-1)}; + for i = 1:length(tmp)-1 + if tmp(i)+1 < tmp(i+1) + str = [str, {errstr(tmp(i)+1:tmp(i+1)-1)}]; + end + end + str = {str{:}, '','- print command is:',[' ',printstr],... + '','- current directory is:',[' ',pwd],... + '',' * nothing has been printed *'}; + spm('alert!',str,'printing problem...',sqrt(-1)); +end + +set(H,{'Units'},un) +set(0,'CurrentFigure',cF) + + +case 'newpage' +%======================================================================= +% [hNextPage, hPrevPage, hPageNo] = spm_figure('NewPage',h) +if nargin<2 | isempty(varargin{2}), error('No handles to paginate') + else, h=varargin{2}(:)'; end + +%-Work out which figure we're in +F = spm_figure('ParentFig',h(1)); + +hNextPage = findobj(F,'Tag','NextPage'); +hPrevPage = findobj(F,'Tag','PrevPage'); +hPageNo = findobj(F,'Tag','PageNo'); + +%-Create pagination widgets if required +%----------------------------------------------------------------------- +if isempty(hNextPage) + WS = spm('WinScale'); + FS = spm('FontSizes'); + SatFig = findobj('Tag','Satellite'); + if ~isempty(SatFig) + SatFigPos = get(SatFig,'Position'); + hNextPagePos = [SatFigPos(3)-25 15 15 15]; + hPrevPagePos = [SatFigPos(3)-40 15 15 15]; + hPageNo = [SatFigPos(3)-40 5 30 10]; + else + hNextPagePos = [580 022 015 015].*WS; + hPrevPagePos = [565 022 015 015].*WS; + hPageNo = [550 005 060 015].*WS; + end + + hNextPage = uicontrol(F,'Style','Pushbutton',... + 'HandleVisibility','on',... + 'String','>','FontSize',FS(10),... + 'ToolTipString','next page',... + 'Callback','spm_figure(''TurnPage'',''+1'',gcbf)',... + 'Position',hNextPagePos,... + 'ForegroundColor',[0 0 0],... + 'Tag','NextPage','UserData',[]); + hPrevPage = uicontrol(F,'Style','Pushbutton',... + 'HandleVisibility','on',... + 'String','<','FontSize',FS(10),... + 'ToolTipString','previous page',... + 'Callback','spm_figure(''TurnPage'',''-1'',gcbf)',... + 'Position',hPrevPagePos,... + 'Visible','on',... + 'Enable','off',... + 'Tag','PrevPage'); + hPageNo = uicontrol(F,'Style','Text',... + 'HandleVisibility','on',... + 'String','1',... + 'FontSize',FS(6),... + 'HorizontalAlignment','center',... + 'BackgroundColor','w',... + 'Position',hPageNo,... + 'Visible','on',... + 'UserData',1,... + 'Tag','PageNo','UserData',1); +end + +%-Add handles for this page to UserData of hNextPage +%-Make handles for this page invisible if PageNo>1 +%----------------------------------------------------------------------- +mVis = strcmp('on',get(h,'Visible')); +hPg = get(hNextPage,'UserData'); +if isempty(hPg) + hPg = {h(mVis), h(~mVis)}; +else + hPg = [hPg; {h(mVis), h(~mVis)}]; + set(h(mVis),'Visible','off') +end +set(hNextPage,'UserData',hPg) + +%-Return handles to pagination controls if requested +if nargout>0, varargout = {[hNextPage, hPrevPage, hPageNo]}; end + + +case 'turnpage' +%======================================================================= +% spm_figure('TurnPage',move,F) +if nargin<3, F='Graphics'; else, F=varargin{3}; end +if nargin<2, move=1; else, move=varargin{2}; end +F = spm_figure('FindWin',F); +if isempty(F), error('No Graphics window'), end + +hNextPage = findobj(F,'Tag','NextPage'); +hPrevPage = findobj(F,'Tag','PrevPage'); +hPageNo = findobj(F,'Tag','PageNo'); +if isempty(hNextPage), return, end +hPg = get(hNextPage,'UserData'); +Cpage = get(hPageNo, 'UserData'); +nPages = size(hPg,1); + +%-Sort out new page number +if ischar(move), Npage = Cpage+eval(move); else, Npage = move; end +Npage = max(min(Npage,nPages),1); + +%-Make current page invisible, new page visible, set page number string +set(hPg{Cpage,1},'Visible','off') +set(hPg{Npage,1},'Visible','on') +set(hPageNo,'UserData',Npage,'String',sprintf('%d / %d',Npage,nPages)) + +%-Disable appropriate page turning control if on first/last page (for neatness) +if Npage==1, set(hPrevPage,'Enable','off') +else, set(hPrevPage,'Enable','on'), end +if Npage==nPages, set(hNextPage,'Enable','off') +else, set(hNextPage,'Enable','on'), end + + + +case 'deletepagecontrols' +%======================================================================= +% spm_figure('DeletePageControls',F) +if nargin<2, F='Graphics'; else, F=varargin{2}; end +F = spm_figure('FindWin',F); +if isempty(F), error('No Graphics window'), end + +hNextPage = findobj(F,'Tag','NextPage'); +hPrevPage = findobj(F,'Tag','PrevPage'); +hPageNo = findobj(F,'Tag','PageNo'); + +delete([hNextPage hPrevPage hPageNo]) + + +case '#page' +%======================================================================= +% n = spm_figure('#Page',F) +if nargin<2, F='Graphics'; else, F=varargin{2}; end +F = spm_figure('FindWin',F); +if isempty(F), error('No Graphics window'), end + +hNextPage = findobj(F,'Tag','NextPage'); +if isempty(hNextPage) + n = 1; +else + n = size(get(hNextPage,'UserData'),1)+1; +end +varargout = {n}; + + +case 'watermark' +%======================================================================= +% spm_figure('WaterMark',F,str,Tag,Angle,Perm) +if nargin<6, HVis='on'; else, HVis='off'; end +if nargin<5, Angle=-45; else, Angle=varargin{5}; end +if nargin<4 | isempty(varargin{4}), Tag = 'WaterMark'; else, Tag=varargin{4}; end +if nargin<3 | isempty(varargin{3}), str = 'SPM'; else, str=varargin{3}; end +if nargin<2, if any(get(0,'Children')), F=gcf; else, F=''; end + else, F=varargin{2}; end +F = spm_figure('FindWin',F); +if isempty(F), return, end + +%-Specify watermark color from background colour +%----------------------------------------------------------------------- +Colour = get(F,'Color'); +%-Only mess with grayscale backgrounds +if ~all(Colour==Colour(1)), return, end +%-Work out colour - lighter unless grey value > 0.9 +Colour = Colour+(2*(Colour(1)<0.9)-1)*0.02; + +cF = get(0,'CurrentFigure'); +set(0,'CurrentFigure',F) +Units=get(F,'Units'); +set(F,'Units','normalized'); +h = axes('Position',[0.45,0.5,0.1,0.1],... + 'Units','normalized',... + 'Visible','off',... + 'Tag',Tag); +set(F,'Units',Units) +text(0.5,0.5,str,... + 'FontSize',spm('FontSize',80),... + 'FontWeight','Bold',... + 'FontName',spm_platform('Font','times'),... + 'Rotation',Angle,... + 'HorizontalAlignment','Center',... + 'VerticalAlignment','middle',... + 'EraseMode','normal',... + 'Color',Colour,... + 'ButtonDownFcn',[... + 'if strcmp(get(gcbf,''SelectionType''),''open''),',... + 'delete(get(gcbo,''Parent'')),',... + 'end']) +set(h,'HandleVisibility',HVis) +set(0,'CurrentFigure',cF) + + +case 'createwin' +%======================================================================= +% F=spm_figure('CreateWin',Tag,Name,Visible) + +%-Condition arguments +%----------------------------------------------------------------------- +if nargin<4 | isempty(varargin{4}), Visible='on'; else, Visible=varargin{4}; end +if nargin<3, Name=''; else, Name = varargin{3}; end +if nargin<2, Tag=''; else, Tag = varargin{2}; end + +WS = spm('WinScale'); %-Window scaling factors +FS = spm('FontSizes'); %-Scaled font sizes +PF = spm_platform('fonts'); %-Font names (for this platform) +Rect = spm('WinSize','Graphics','raw').*WS; %-Graphics window rectangle + +F = figure(... + 'Tag',Tag,... + 'Position',Rect,... + 'Resize','off',... + 'Color','w',... + 'ColorMap',gray(64),... + 'DefaultTextColor','k',... + 'DefaultTextInterpreter','none',... + 'DefaultTextFontName',PF.helvetica,... + 'DefaultTextFontSize',FS(10),... + 'DefaultAxesColor','w',... + 'DefaultAxesXColor','k',... + 'DefaultAxesYColor','k',... + 'DefaultAxesZColor','k',... + 'DefaultAxesFontName',PF.helvetica,... + 'DefaultPatchFaceColor','k',... + 'DefaultPatchEdgeColor','k',... + 'DefaultSurfaceEdgeColor','k',... + 'DefaultLineColor','k',... + 'DefaultUicontrolFontName',PF.helvetica,... + 'DefaultUicontrolFontSize',FS(10),... + 'DefaultUicontrolInterruptible','on',... + 'PaperType','A4',... + 'PaperUnits','normalized',... + 'PaperPosition',[.0726 .0644 .854 .870],... + 'InvertHardcopy','off',... + 'Renderer','zbuffer',... + 'Visible','off'); +if ~isempty(Name) + set(F,'Name',sprintf('%s%s: %s',spm('ver'),... + spm('GetUser',' (%s)'),Name),'NumberTitle','off') +end +set(F,'Visible',Visible) +varargout = {F}; + + +case 'getwinscale' +%======================================================================= +% WS = spm_figure('GetWinScale') +warning('spm_figure(''GetWinScale''... is Grandfathered: use spm(''WinScale''') +varargout = {spm('WinScale')}; + + +case 'fontsizes' +%======================================================================= +% FS = spm_figure('FontSizes',FS) +warning('spm_figure(''FontSizes''... is Grandfathered: use spm(''FontSizes''') +if nargin<2, FS=[08,09,11,13,14,6:36]; else, FS=varargin{2}; end +varargout = {round(FS*min(spm('WinScale')))}; + + +%======================================================================= + case 'createbar' +%======================================================================= +% spm_figure('CreateBar',F) +if nargin<2, if any(get(0,'Children')), F=gcf; else, F=''; end + else, F=varargin{2}; end +F = spm_figure('FindWin',F); +if isempty(F), return, end + +cSHH = get(0,'ShowHiddenHandles'); +set(0,'ShowHiddenHandles','on') + +t0 = findobj(get(F,'Children'),'Flat','Label','&Help'); +if isempty(t0), t0 = uimenu( F,'Label','&Help'); end; + +set(findobj(t0,'Position',1),'Separator','on'); +t1 = uimenu(t0,'Position',1,... + 'Label','SPM web',... + 'CallBack','web(''http://www.fil.ion.ucl.ac.uk/spm'');'); +t1 = uimenu(t0,'Position',1,... + 'Label','SPM help','ForegroundColor',[0 1 0],... + 'CallBack','spm_help'); + +t0=uimenu( F,'Label','Colours','HandleVisibility','off'); +t1=uimenu(t0,'Label','ColorMap'); +t2=uimenu(t1,'Label','Gray','CallBack','spm_figure(''ColorMap'',''gray'')'); +t2=uimenu(t1,'Label','Hot','CallBack','spm_figure(''ColorMap'',''hot'')'); +t2=uimenu(t1,'Label','Pink','CallBack','spm_figure(''ColorMap'',''pink'')'); +t2=uimenu(t1,'Label','Gray-Hot','CallBack','spm_figure(''ColorMap'',''gray-hot'')'); +t2=uimenu(t1,'Label','Gray-Pink','CallBack','spm_figure(''ColorMap'',''gray-pink'')'); +t1=uimenu(t0,'Label','Effects'); +t2=uimenu(t1,'Label','Invert','CallBack','spm_figure(''ColorMap'',''invert'')'); +t2=uimenu(t1,'Label','Brighten','CallBack','spm_figure(''ColorMap'',''brighten'')'); +t2=uimenu(t1,'Label','Darken','CallBack','spm_figure(''ColorMap'',''darken'')'); +t0=uimenu( F,'Label','Clear','HandleVisibility','off','CallBack','if ~isequal(questdlg(''Sure you want to clear ?'', ''...'', ''Yes'', ''No'', ''No''), ''Yes'');return;end;spm_figure(''Clear'',gcbf)'); +t0=uimenu('Parent', F,'Label','PRINT-OK','HandleVisibility','off','CallBack','print -painters -dwin'); %SOSART +% ### CODE FOR SATELLITE FIGURE ### +% Code checks if there is a satellite window and if results are currently displayed +% It assumes that if hReg is invalid then there are no results currently displayed +% Modified by DRG to display a satellite figure 02/14/01. +cb = ['global SatWindow,',... + 'try,',... + 'tmp = get(hReg);,',... + 'ResFlag = 1;',... + 'catch,',... + 'ResFlag = 0;',... + 'end,',... + 'if SatWindow,',... + 'figure(SatWindow),',... + 'else,',... + 'if ResFlag,',... + 'spm_setup_satfig,',... + 'end,',... + 'end']; +t0=uimenu( F,'Label','Results-Fig','HandleVisibility','off','Callback',cb); +% ### END NEW CODE ### + + +set(0,'ShowHiddenHandles',cSHH) +%======================================================================= + + +case 'figcontextmenu' +%======================================================================= +% h = spm_figure('FigContextMenu',F) +if nargin<2 + F = get(0,'CurrentFigure'); + if isempty(F), error('no figure'), end +else + F = spm_figure('FindWin',varargin{2}); + if isempty(F), error('no such figure'), end +end +h = uicontextmenu('Parent',F,'HandleVisibility','CallBack'); +cSHH = get(0,'ShowHiddenHandles'); +set(0,'ShowHiddenHandles','on') +copy_menu(F,h); +set(0,'ShowHiddenHandles',cSHH) +set(F,'UIContextMenu',h) +varargout = {h}; + + +case 'colormap' +%======================================================================= +% spm_figure('ColorMap',ColAction,h) +if nargin<3, h=[]; else, h=varargin{3}; end +if nargin<2, ColAction='gray'; else, ColAction=varargin{2}; end + +switch lower(ColAction), case 'gray' + colormap(gray(64)) +case 'hot' + colormap(hot(64)) +case 'pink' + colormap(pink(64)) +case 'gray-hot' + tmp = hot(64 + 16); tmp = tmp([1:64] + 16,:); + colormap([gray(64); tmp]) +case 'gray-pink' + tmp = pink(64 + 16); tmp = tmp([1:64] + 16,:); + colormap([gray(64); tmp]) +case 'invert' + colormap(flipud(colormap)) +case 'brighten' + colormap(brighten(colormap, 0.2)) +case 'darken' + colormap(brighten(colormap, -0.2)) +otherwise + error('Illegal ColAction specification') +end + +case 'graphicshandle' +%======================================================================= +% h = spm_figure('GraphicsHandle',F) +if nargin<2, F=gcbf; else, F=spm_figure('FindWin',varargin{2}); end +if isempty(F), return, end + +tmp = get(F,'Name'); +set(F,'Name',... + 'Handle: Select item to identify, MiddleMouse=parent, RightMouse=cancel...'); +set(F,'Pointer','CrossHair') +waitforbuttonpress; +h = gco(F); +hType = get(h,'Type'); +SelnType = get(gcf,'SelectionType'); +set(F,'Pointer','Arrow','Name',tmp) + +if ~strcmp(SelnType,'alt') & ~isempty(h) & gcf==F + str = sprintf('Selected (%s) object',get(h,'Type')); + if strcmp(SelnType,'normal') + str = sprintf('%s: handle',str); + else + h = get(h,'Parent'); + str = sprintf('%s: handle of parent (%s) object',str,get(h,'Type')); + end + if nargout==0 + assignin('base','ans',h) + fprintf('\n%s: \n',str) + ans = h + else + varargout={h}; + end +else + varargout={[]}; +end + + + +otherwise +%======================================================================= +warning(['Illegal Action string: ',Action]) +end +return; +%======================================================================= + + +%======================================================================= +function copy_menu(F,G) +%======================================================================= +handles = findobj(get(F,'Children'),'Flat','Type','uimenu','Visible','on'); +if length(handles)==0, return; end; +for F1=handles', + if ~strcmp(get(F1,'Label'),'&Window'), + G1 = uimenu(G,'Label',get(F1,'Label'),... + 'CallBack',get(F1,'CallBack'),... + 'Position',get(F1,'Position'),... + 'Separator',get(F1,'Separator')); + copy_menu(F1,G1); + end; +end; +return; +%======================================================================= + + +%======================================================================= +function PRINTSTR = clean_PRINTSTR(PRINTSTR) +%======================================================================= +% Matlab 6.5 printing doesn't like the -append option if the file does +% not already exist +%----------------------------------------------------------------------- +off = findstr('-append',PRINTSTR); +if ~isempty(off), + bl = [0 find(isspace(PRINTSTR)) (length(PRINTSTR)+1)]; + for i=1:(length(bl)-1), + ca{i} = PRINTSTR((bl(i)+1):(bl(i+1)-1)); + end; + ca = strvcat(ca); + off1 = find(ca(:,1)~='-'); % either 'print' or a filename + if length(off1)>1, + fname = deblank(ca(off1(end),:)); + + % If there is no path to the file, then make it the + % current directory + [pth,nam,ext] = fileparts(fname); + if isempty(pth), fname = ['.' filesep nam ext]; end; + + fd = fopen(fname,'r'); + if fd~=-1, + % File exists, so -append can be used + fclose(fd); + else, + % File does not exist, so remove -append option + PRINTSTR(off:(off+7))=''; + end; + end; +end; diff --git a/spm/spm2/spm_fmri_spm_ui.m b/spm/spm2/spm_fmri_spm_ui.m new file mode 100644 index 0000000..939290f --- /dev/null +++ b/spm/spm2/spm_fmri_spm_ui.m @@ -0,0 +1,478 @@ +function [SPM] = spm_fmri_spm_ui(SPM) +% Setting up the general linear model for fMRI time-series +% FORMAT [SPM] = spm_fmri_spm_ui(SPM) +% +% creates SPM with the following fields +% +% xY: [1x1 struct] - data stucture +% nscan: [double] - vector of scans per session +% xBF: [1x1 struct] - Basis function stucture (see spm_fMRI_design) +% Sess: [1x1 struct] - Session stucture (see spm_fMRI_design) +% xX: [1x1 struct] - Design matric stucture (see spm_fMRI_design) +% xGX: [1x1 struct] - Global variate stucture +% xVi: [1x1 struct] - Non-sphericity stucture +% xM: [1x1 struct] - Masking stucture +% xsDes: [1x1 struct] - Design description stucture +% +% +% SPM.xY +% P: [n x ? char] - filenames +% VY: [n x 1 struct] - filehandles +% RT: Repeat time +% +% SPM.xGX +% +% iGXcalc: {'none'|'Scaling'} - Global normalization option +% sGXcalc: 'mean voxel value' - Calculation method +% sGMsca: 'session specific' - Grand mean scaling +% rg: [n x 1 double] - Global estimate +% GM: 100 - Grand mean +% gSF: [n x 1 double] - Global scaling factor +% +% SPM.xVi +% Vi: {[n x n sparse]..} - covariance components +% form: {'none'|'AR(1)'} - form of non-sphericity +% +% SPM.xM +% T: [n x 1 double] - Masking index +% TH: [n x 1 double] - Threshold +% I: 0 +% VM: - Mask filehandles +% xs: [1x1 struct] - cellstr description +% +% (see also spm_spm_ui) +% +%____________________________________________________________________________ +% +% spm_fmri_spm_ui configures the design matrix, data specification and +% filtering that specify the ensuing statistical analysis. These +% arguments are passed to spm_spm that then performs the actual parameter +% estimation. +% +% The design matrix defines the experimental design and the nature of +% hypothesis testing to be implemented. The design matrix has one row +% for each scan and one column for each effect or explanatory variable. +% (e.g. regressor or stimulus function). The parameters are estimated in +% a least squares sense using the general linear model. Specific profiles +% within these parameters are tested using a linear compound or contrast +% with the T or F statistic. The resulting statistical map constitutes +% an SPM. The SPM{T}/{F} is then characterized in terms of focal or regional +% differences by assuming that (under the null hypothesis) the components of +% the SPM (i.e. residual fields) behave as smooth stationary Gaussian fields. +% +% spm_fmri_spm_ui allows you to (i) specify a statistical model in terms +% of a design matrix, (ii) associate some data with a pre-specified design +% [or (iii) specify both the data and design] and then proceed to estimate +% the parameters of the model. +% Inferences can be made about the ensuing parameter estimates (at a first +% or fixed-effect level) in the results section, or they can be re-entered +% into a second (random-effect) level analysis by treating the session or +% subject-specific [contrasts of] parameter estimates as new summary data. +% Inferences at any level obtain by specifying appropriate T or F contrasts +% in the results section to produce SPMs and tables of p values and statistics. +% +% spm_fmri_spm calls spm_fMRI_design which allows you to configure a +% design matrix in terms of events or epochs. +% +% spm_fMRI_design allows you to build design matrices with separable +% session-specific partitions. Each partition may be the same (in which +% case it is only necessary to specify it once) or different. Responses +% can be either event- or epoch related, The only distinction is the duration +% of the underlying input or stimulus function. Mathematically they are both +% modeled by convolving a series of delta (stick) or box functions (u), +% indicating the onset of an event or epoch with a set of basis +% functions. These basis functions model the hemodynamic convolution, +% applied by the brain, to the inputs. This convolution can be first-order +% or a generalized convolution modeled to second order (if you specify the +% Volterra option). [The same inputs are used by the hemodynamic model or +% or dynamic causal models which model the convolution explicitly in terms of +% hidden state variables (see spm_hdm_ui and spm_dcm_ui).] +% Basis functions can be used to plot estimated responses to single events +% once the parameters (i.e. basis function coefficients) have +% been estimated. The importance of basis functions is that they provide +% a graceful transition between simple fixed response models (like the +% box-car) and finite impulse response (FIR) models, where there is one +% basis function for each scan following an event or epoch onset. The +% nice thing about basis functions, compared to FIR models, is that data +% sampling and stimulus presentation does not have to be sychronized +% thereby allowing a uniform and unbiased sampling of peri-stimulus time. +% +% Event-related designs may be stochastic or deterministic. Stochastic +% designs involve one of a number of trial-types occurring with a +% specified probably at successive intervals in time. These +% probabilities can be fixed (stationary designs) or time-dependent +% (modulated or non-stationary designs). The most efficient designs +% obtain when the probabilities of every trial type are equal. +% A critical issue in stochastic designs is whether to include null events +% If you wish to estimate the evoke response to a specific event +% type (as opposed to differential responses) then a null event must be +% included (even if it is not modelled explicitly). +% +% The choice of basis functions depends upon the nature of the inference +% sought. One important consideration is whether you want to make +% inferences about compounds of parameters (i.e. contrasts). This is +% the case if (i) you wish to use a SPM{T} to look separately at +% activations and deactivations or (ii) you with to proceed to a second +% (random-effect) level of analysis. If this is the case then (for +% event-related studies) use a canonical hemodynamic response function +% (HRF) and derivatives with respect to latency (and dispersion). Unlike +% other bases, contrasts of these effects have a physical interpretation +% and represent a parsimonious way of characterising event-related +% responses. Bases such as a Fourier set require the SPM{F} for +% inference. +% +% See spm_fMRI_design for more details about how designs are specified. +% +% Serial correlations in fast fMRI time-series are dealt with as +% described in spm_spm. At this stage you need to specify the filtering +% that will be applied to the data (and design matrix) to give a +% generalized least squares (GLS) estimate of the parameters required. +% This filtering is important to ensure that the GLS estimate is +% efficient and that the error variance is estimated in an unbiased way. +% +% The serial correlations will be estimated with a ReML (restricted +% maximum likelihood) algorithm using an autoregressive AR(1) model +% during parameter estimation. This estimate assumes the same +% correlation structure for each voxel, within each session. The ReML +% estimates are then used to correct for non-sphericity during inference +% by adjusting the statistics and degrees of freedom appropriately. The +% discrepancy between estimated and actual intrinsic (i.e. prior to +% filtering) correlations are greatest at low frequencies. Therefore +% specification of the high-pass filter is particularly important. +% +% High-pass filtering is implemented at the level of the +% filtering matrix K (as opposed to entering as confounds in the design +% matrix). The default cutoff period is 128 seconds. Use 'explore design' +% to ensure this cuttof is not removing too much experimental variance. +% Note that high-pass filtering uses a residual forming matrix (i.e. +% it is not a convolution) and is simply to a way to remove confounds +% without estimating their parameters explicitly. The constant term +% is also incorportated into this filter matrix. +% +%----------------------------------------------------------------------- +% Refs: +% +% Friston KJ, Holmes A, Poline J-B, Grasby PJ, Williams SCR, Frackowiak +% RSJ & Turner R (1995) Analysis of fMRI time-series revisited. NeuroImage +% 2:45-53 +% +% Worsley KJ and Friston KJ (1995) Analysis of fMRI time-series revisited - +% again. NeuroImage 2:178-181 +% +% Friston KJ, Frith CD, Frackowiak RSJ, & Turner R (1995) Characterising +% dynamic brain responses with fMRI: A multivariate approach NeuroImage - +% 2:166-172 +% +% Frith CD, Turner R & Frackowiak RSJ (1995) Characterising evoked +% hemodynamics with fMRI Friston KJ, NeuroImage 2:157-165 +% +% Josephs O, Turner R and Friston KJ (1997) Event-related fMRI, Hum. Brain +% Map. 0:00-00 +% +%_______________________________________________________________________ +% @(#)spm_fmri_spm_ui.m 2.52 Karl Friston, Jean-Baptiste Poline, Christian Buchel 03/12/18 +SCCSid = '2.52'; + +%-GUI setup +%----------------------------------------------------------------------- +[Finter,Fgraph,CmdLine] = spm('FnUIsetup','fMRI stats model setup',0); +spm_help('!ContextHelp',mfilename) + +global defaults + +% get design matrix and/or data +%======================================================================= +if ~nargin + + str = 'specify design or data'; + if spm_input(str,1,'b',{'design','data'},[1 0]); + + % specify a design + %------------------------------------------------------- + if sf_abort, spm_clf(Finter), return, end + SPM = spm_fMRI_design; + spm_fMRI_design_show(SPM); + return + + else + + % get design + %------------------------------------------------------- + load(spm_get(1,'SPM.mat','Select SPM.mat')); + + end + +else + + % get design matrix + %--------------------------------------------------------------- + SPM = spm_fMRI_design(SPM); + +end + +% get Repeat time +%----------------------------------------------------------------------- +try + RT = SPM.xY.RT; +catch + RT = spm_input('Interscan interval {secs}','+1'); + SPM.xY.RT = RT; +end + + +% session and scan number +%----------------------------------------------------------------------- +nscan = SPM.nscan; +nsess = length(nscan); + +% check data are specified +%----------------------------------------------------------------------- +try + SPM.xY.P; +catch + + % get filenames + %--------------------------------------------------------------- + P = []; + for i = 1:nsess + str = sprintf('select scans for session %0.0f',i); + q = spm_get(nscan(i),'.img',str); + P = strvcat(P,q); + end + + % place in data field + %--------------------------------------------------------------- + SPM.xY.P = P; + +end + + + +% Assemble remaining design parameters +%======================================================================= +spm_help('!ContextHelp',mfilename) +SPM.SPMid = spm('FnBanner',mfilename,SCCSid); + + +% Global normalization +%----------------------------------------------------------------------- +nsess = length(SPM.nscan); +try + SPM.xGX.iGXcalc; +catch + spm_input('Global intensity normalisation...',1,'d',mfilename) + str = 'remove Global effects'; + SPM.xGX.iGXcalc = spm_input(str,'+1','scale|none',{'Scaling' 'None'}); +end +SPM.xGX.sGXcalc = 'mean voxel value'; +SPM.xGX.sGMsca = 'session specific'; + + +% High-pass filtering and serial correlations +%======================================================================= + +% low frequency confounds +%----------------------------------------------------------------------- +try + HParam = SPM.xX.K(1).HParam; + HParam = HParam*ones(1,nsess); +catch + % specify low frequnecy confounds + %--------------------------------------------------------------- + spm_input('Temporal autocorrelation options','+1','d',mfilename) + switch spm_input('High-pass filter?','+1','b','none|specify'); + + case 'specify' % default 128 seconds + %------------------------------------------------------- + HParam = 128*ones(1,nsess); + str = 'cutoff period (secs)'; + HParam = spm_input(str,'+1','e',HParam,[1 nsess]); + + case 'none' % Inf seconds (i.e. constant term only) + %------------------------------------------------------- + HParam = Inf*ones(1,nsess); + end +end + +% create and set filter struct +%--------------------------------------------------------------- +for i = 1:nsess + K(i) = struct( 'HParam', HParam(i),... + 'row', SPM.Sess(i).row,... + 'RT', SPM.xY.RT); +end +SPM.xX.K = spm_filter(K); + + +% intrinsic autocorrelations (Vi) +%----------------------------------------------------------------------- +try + cVi = SPM.xVi.form; +catch + % Contruct Vi structure for non-sphericity ReML estimation + %=============================================================== + str = 'Correct for serial correlations?'; + cVi = {'none','AR(1)'}; + cVi = spm_input(str,'+1','b',cVi); +end + +% create Vi struct +%----------------------------------------------------------------------- + +if ~ischar(cVi) % AR coeficient[s] specified +%----------------------------------------------------------------------- + SPM.xVi.Vi = spm_Ce(nscan,cVi(1:3)); + cVi = ['AR( ' sprintf('%0.1f ',cVi) ')']; + +else + switch lower(cVi) + + case 'none' % xVi.V is i.i.d + %--------------------------------------------------------------- + SPM.xVi.V = speye(sum(nscan)); + cVi = 'i.i.d'; + + + otherwise % otherwise assume AR(0.2) in xVi.Vi + %--------------------------------------------------------------- + SPM.xVi.Vi = spm_Ce(nscan,0.2); + cVi = 'AR(0.2)'; + + end +end +SPM.xVi.form = cVi; + + + +%======================================================================= +% - C O N F I G U R E D E S I G N +%======================================================================= +spm_clf(Finter); +spm('FigName','Configuring, please wait...',Finter,CmdLine); +spm('Pointer','Watch'); + + +% get file identifiers +%======================================================================= + +%-Map files +%----------------------------------------------------------------------- +fprintf('%-40s: ','Mapping files') %-# +VY = spm_vol(SPM.xY.P); +fprintf('%30s\n','...done') %-# + +%-check internal consistency of images +%----------------------------------------------------------------------- +if any(any(diff(cat(1,VY.dim),1,1),1) & [1,1,1,0]) +error('images do not all have the same dimensions'), end +if any(any(any(diff(cat(3,VY.mat),1,3),3))) +error('images do not all have same orientation & voxel size'), end + +%-place in xY +%----------------------------------------------------------------------- +SPM.xY.VY = VY; + + +%-Compute Global variate +%======================================================================= +GM = 100; +q = length(VY); +g = zeros(q,1); +fprintf('%-40s: %30s','Calculating globals',' ') %-# +for i = 1:q + fprintf('%s%30s',char(sprintf('\b')*ones(1,30)),sprintf('%4d/%-4d',i,q)) %-# + g(i) = spm_global(VY(i)); +end +fprintf('%s%30s\n',sprintf('\b')*ones(1,30),'...done') %-# + +% scale if specified (otherwise session specific grand mean scaling) +%----------------------------------------------------------------------- +gSF = GM./g; +if strcmp(lower(SPM.xGX.iGXcalc),'none') + for i = 1:nsess + gSF(SPM.Sess(i).row) = GM./mean(g(SPM.Sess(i).row)); + end +end + +%-Apply gSF to memory-mapped scalefactors to implement scaling +%----------------------------------------------------------------------- +for i = 1:q + SPM.xY.VY(i).pinfo(1:2,:) = SPM.xY.VY(i).pinfo(1:2,:)*gSF(i); +end + +%-place global variates in global structure +%----------------------------------------------------------------------- +SPM.xGX.rg = g; +SPM.xGX.GM = GM; +SPM.xGX.gSF = gSF; + + +%-Masking structure automatically set to 80% of mean +%======================================================================= +try + TH = g.*gSF*defaults.mask.thresh; +catch + TH = g.*gSF*0.8; +end +SPM.xM = struct( 'T', ones(q,1),... + 'TH', TH,... + 'I', 0,... + 'VM', {[]},... + 'xs', struct('Masking','analysis threshold')); + + +%-Design description - for saving and display +%======================================================================= +for i = 1:nsess, ntr(i) = length(SPM.Sess(i).U); end +Fstr = sprintf('[min] Cutoff period %d seconds',min(HParam)); +SPM.xsDes = struct(... + 'Basis_functions', SPM.xBF.name,... + 'Number_of_sessions', sprintf('%d',nsess),... + 'Trials_per_session', sprintf('%-3d',ntr),... + 'Interscan_interval', sprintf('%0.2f {s}',SPM.xY.RT),... + 'High_pass_Filter', sprintf('Cutoff: %d {s}',SPM.xX.K(1).HParam),... + 'Serial_correlations', SPM.xVi.form,... + 'Global_calculation', SPM.xGX.sGXcalc,... + 'Grand_mean_scaling', SPM.xGX.sGMsca,... + 'Global_normalisation', SPM.xGX.iGXcalc); + + +%-Save SPM.mat +%----------------------------------------------------------------------- +fprintf('%-40s: ','Saving SPM configuration') %-# +save SPM SPM; +fprintf('%30s\n','...SPM.mat saved') %-# + + +%-Display Design report +%======================================================================= +fprintf('%-40s: ','Design reporting') %-# +fname = cat(1,{SPM.xY.VY.fname}'); +spm_DesRep('DesMtx',SPM.xX,fname,SPM.xsDes) +fprintf('%30s\n','...done') %-# + + +%-End: Cleanup GUI +%======================================================================= +spm_clf(Finter) +spm('FigName','Stats: configured',Finter,CmdLine); +spm('Pointer','Arrow') +fprintf('\n\n') + + +%======================================================================= +%- S U B - F U N C T I O N S +%======================================================================= + +function abort = sf_abort +%======================================================================= +if exist(fullfile('.','SPM.mat')) + str = { 'Current directory contains existing SPM file:',... + 'Continuing will overwrite existing file!'}; + + abort = spm_input(str,1,'bd','stop|continue',[1,0],1,mfilename); + if abort, fprintf('%-40s: %30s\n\n',... + 'Abort... (existing SPM files)',spm('time')), end +else + abort = 0; +end diff --git a/spm/spm2/spm_get.m b/spm/spm2/spm_get.m new file mode 100644 index 0000000..d32ae09 --- /dev/null +++ b/spm/spm2/spm_get.m @@ -0,0 +1,1842 @@ +function varargout = spm_get(varargin) +% User interface : filename selection +% FORMAT P = spm_get(n,Filter,Prompt,NewWDir,CmdLine); +% n - 0 - returns with empty P +% - [mn,mx] : prompts for between mn & mx items +% - scalar n => [n,n], i.e. must select n items +% - all(n) positive : prompts for files +% - any(n) negative : prompts for directories +% Filter - Filename filter {'*' is prepended} +% - numeric 0 for previous filtering +% Prompt - Prompt string +% NewWDir - New working directory +% CmdLine - CmdLine override switch, 0 for GUI, 1 for Command line. +% +% P - string matrix of full pathnames {one string per row} +% returned as a cellstr if iscellstr(Prompt) +%_______________________________________________________________________ +% +% spm_get allows interactive selection of filepaths/directory names +% from disk. +% +% if global CMDLINE exists and is >0, the command line is used. The +% CmdLine parameter overrides this choice. Note that since spm_input +% uses the command line if CMDLINE is true (unless overridden), setting +% CMDLINE to -1 results in the command line being used for questions, +% but the GUI for file selection. +% +% Otherwise, selection of files is via GUI. The select files window, +% enables the interactive directory display and selection of +% filenames. Directories are displated in red in the left listing, +% files in the right listing. File lists are filtered according to the +% current filter string, using the usual UNIX conventions (*,?,etc.). +% +% Arrows at the right of the listings pane allow the lists to be +% scrolled up and down. The bar button above the "up" arrow scrolls to +% the top of the list. +% +% Selecting files +% --------------- +% Clicking on an item (black) adds it to the list of those selected, +% the item is numbered and changes color (dark blue). The item last +% selected can be unselected by clicking it again. If a specific number +% of items have been requested (n), then this number of files must be +% selected. A status message at the foot of the window explains the +% current status. +% +% Clicking on a directory name (red) changes to that directory. The +% current directory name window can edited to change directory. Also, +% pull down menus of previous directories and subdirectories of the +% current directory are maintained. (On Windows platforms, an +% additional pull down menu of drives is also provided.) The red "pwd" +% button changes directory to the current MatLab working directory, the +% "home" button to the users home directory. +% +% All files in the current window can be selected *in the order in which +% they appear* with the "All" button, and the "Reset" button clears the +% current list. The "Edit" button enables interactive editing of the +% filename list, and the "Keybd" button invokes a command line +% interface. +% +% Click "Done" when finished. As a short cut, selecting items +% using the right mouse button is equivalent to selecting the item and +% pressing "Done". +% +% Summary views of directories +% ---------------------------- +% Directories with over 48 entries (after filtering) are shown +% summarised, with a single wildcarded entry for blocks of similar +% filenames shown alongside a small white number indicating the number +% of files in the block. Such blocks can be expanded by clicking +% "Extend" (middle mouse button) on the item, or by clicking the number +% of files text, whence the whole directory is shown with the +% appropriate filter. The entire directory can be shown by clicking the +% white "Summary View" text at the top of the item window. +% +% Filter string +% --------------------- +% The filter string for filenames is built as ['*',Filter] if Filter +% doesn't contain a wildcard '*', and appears in a single editable +% window. Subsequent calls to spm_get callbacks with the same filter +% string do not change the value of the filter string, allowing user +% edits of the filer string to persist. Clicking the Filter button +% resets the filter to '*', resulting in all files/directories being +% displayed, in summary view if there are sufficient files. Clicking +% the outline of the filter editable widget resets the filter string to +% the original filter string passed by the calling program. +% +% Filter can also be a cell array of the form {'*.img', 'noexpand'}. +% When it is a cell array, the first element is taken as the filter +% to use, and subsequent elements are passed as the third argument to +% spm_list_files_expand.m. +% +% Selecting Directories +% --------------------- +% The same interface is occasionally used for selecting directories, in +% which case the list of selectable items contains a list of +% subdirectories of the current directory. Use the current directory +% window and PullDown menu's to navigate to the required directories +% parent, and select the chosen subdirectory from the list presented. +% The Filter doesn't apply to the directory list. +% +% Keyboard Accelerators +% --------------------- +% Keyboard accelerators are available for item selection, editing the +% current directory and filter windows, and for some of the interface +% buttons: These work providing no GUI widget has the focus (click in +% the window background to remove focus from any GUI widgets). +% +% - Item selection - Items are highlighted (in italic) using ^K & ^J to +% move Up and Down the item list. The highlighted item is (de)selected +% using ^S. +% +% - CurrentDir & Filter editing - Initially, all typed characters, act +% on the CurrentDirectory widget, characters are appended, delete & +% backspace delete the rightmost character. ^E (Edit) toggles between +% the CurrentDir and Filter editable widgets. Return changes to the +% specifiec directory (CurrentDir widget) or applys the current filter +% (Fitler widget). ^U deletes the whole entry, ^W deletes the last +% pathname component, ^I resets the string to it's previous value. +% +% - General Accelerators - +% Space / ^D - Done +% ^P - pwd +% ^B - Keybd +% Esc - Reset +% +% - Tab - In addition, the usual tabbing between GUI objects is +% provided by the window interface. +% +% Programmers notes +% ----------------- +% The 'Select Files Window', created by the first call to spm_get, is +% 'Tag'ged 'SelFileWin'. This window is hidden at the end of the spm_get +% transaction, and is used by subsequent calls. This saves time, and +% also permits the storage of previously visited directories from +% transaction to transaction. +% +% CallBacks are handled as embedded functions within spm_get, format +% specifications for these embedded functions are given in the main +% body of the code. +% +%_______________________________________________________________________ +% @(#)spm_get.m 2.43 Andrew Holmes (X-platform stuff with Matthew Brett) 02/10/16 + +%======================================================================= +% - FORMAT specifications for embedded CallBack functions +%======================================================================= +% +% Callback and setup function for spm_get. +% FORMAT varargout = spm_get(varargin) +% - A multi function function! +% +% FORMAT F = spm_get('CreateFig',LastDirs,Filter) +% Sets up the file selection window as next avaliable figure. +% LastDirs - String matrix for directory history +% Filter - Filename filter +% F - Figure used +% +% FORMAT spm_help('FigKeyPressFcn',ch,F) +% Callback for handling keyboard accelerators +% ch - Character [default get(F,'CurrentCharacter')] +% F - Figure used [default gcbf] +% +% FORMAT F = spm_get('Initialise',Vis,n,Prompt,Filter,WDir) +% (Re)Initialise SelFileWin, create one if necessary. +% F - Figure used. +% Vis - Figure visibility (string, 'on'|'off'|'close') +% n - Items struct: .fi (get files), .mn (min #items), .mx (max #items) +% Prompt - Prompt string. +% Filter - Filename filter. +% WDir - [optional] New Working directory +% F - Figure used +% +% FORMAT spm_get('StatusLine',nP,n,F) +% Updates FileSelWin status line +% nP - #Items already selected +% n - Items struct: .fi (get files), .mn (min #items), .mx (max #items) +% F - Figure used [defaults to 'SelFileWin' 'Tag'ged figure] +% +% FORMAT spm_get('cd',NewDir,bNoDir) +% Callback function for change directory objects +% NewDir - New Working directory (if not held in current object) +% bNoDir - Supresses listing of new directory if true +% +% FORMAT spm_get('dir',WDir,Filter,NoComp) +% Lists current working directory and attatches callbacks to contents +% WDir - Working directory +% Defaults (unspecified or empty) to UserData of WDir Tagged object +% Filter - Filename Filter +% Overrides and sets the Filter object +% Defaults (unspecified or empty) to UserData of Filter object +% NoComp - If specified, forces full directory listing - no compression +% +% [sS,I] = FORMAT spm_get('StrSort',S) +% Simple string sort routine for string matrices +% S - String matrix to be sorted +% sS - Sorted string matrix +% I - Sort index: sS = S(I) +% +% FORMAT fS = spm_get('strfliplr',S) +% fliplr for string matrices, ignores trailing padding spaces +% S - String matrix to be flipped +% fS - left-right flipped version of S, with last non-space characters +% of S in first column +% +% FORMAT [FSpecs,FnamePos]=spm_get('FileSummary',Fnames,Cend,Filter,len) +% File summary routine +% Fnames - String matrix of filenames (all unique) +% Cend - CommonEND - 'front', 'end' or 'both' +% Specifies which end to take as common for file summary +% Defaults to 'both', which gives 'front' precedence over 'back' +% Filter - Filename filter - suffix filter is appended to 'front' items +% len - Length of section to define common files +% Defaults to 3 +% FSpecs - String matrix of file patterns, with n rows +% FnamePos - Matrix of n rows. +% Each row contains the indicies of all Fnames matching the pattern +% given in the corresponding row of FSpecs. Padded with zeros. +% +% FORMAT spm_get('Add',h) +% Adds current object (of object h) to selection +% h - Object handle of an item +% +% FORMAT spm_get('Delete',h) +% Deletes current object from the selection, if it was the last chosen +% h - Handle of a deletable item (Defaults to gcbo) +% +% FORMAT spm_get('Reset',F) +% Resets the selection and redisplays the directory +% F - Figure used [defaults to 'SelFileWin' 'Tag'ged figure] +% +% FORMAT spm_get('All',F) +% Adds all remaining unselected items to the selection +% F - Figure used [defaults to 'SelFileWin' 'Tag'ged figure] +% +% FORMAT P = spm_get('CmdLine',n,Prompt,P,WDir) +% Uses command line to get items +% n - Items struct: .fi (get files), .mn (min #items), .mx (max #items) +% Prompt - Prompt string +% P - Currently selected items +% WDir - Working directory to use (defaults to pwd) +% +% FORMAT P = spm_get('GUI2CmdLine') +% GUI gateway to command line input. +% P - Currently selected items +% +% FORMAT spm_get('Edit') +% Invokes window for editing currently selected items +% +% FORMAT spm_get('EditDone',OK) +% Processes the results of the edit. +% OK - 'OK' or 'CANCEL' - Edit status. +% +% FORMAT spm_get('Done') +% Callback for "Done" button. +% +% FORMAT cpath = spm_get('CPath',path,cwd) +% function to canonicalise paths: Prepends cwd to relative paths, processes +% '..' & '.' directories embedded in path. +% path - string matrix (or cell array of strings) containing path names +% cwd - current working directory [defaut '.'] +% cpath - conditioned paths, in same format as input path argument +% +% FORMAT str = spm_get('DrivesPullDownStr') +% returns string for Drives pull down menu on Windows platforms +% +% FORMAT [P,dir] = spm_get('Files',dir,fil) +% Extended gateway to spm_list_files: Returns full pathnames & canonicalises dir +% dir - directory within which list files (a string) +% parameter is canonicalised by spm_get('CPath',dir) +% [defaults to '.', the current directory] +% fil - file filter string E.g. 'sn*.img' [default '*'] +% P - string matrix of files in directory +% if nargout<2 (i.e. if dir is not returned), then these are +% full pathnames +% dir - full pathname of directory listed (after canonicalisation) +% +%----------------------------------------------------------------------- +% SUBFUNCTIONS: +% +% FORMAT so = dstrtrim(si) +% Double tailed strtrim - strips blanks from both ends of string matrix +% +% FORMAT rootf = isroot(path) +% Tests filepath in string path: Returns true if path is root directory +% +% FORMAT absf = isabspath(path) +% Tests filepath in string path: Returns true if path is absolute +%_______________________________________________________________________ +% Andrew Holmes (X-platform stuff with Matthew Brett) + +% Options to pass to spm_list_files_expand +%======================================================================= +persistent ListOpts +if isempty(ListOpts), ListOpts = {''}; end; + +%-Parameters +%======================================================================= +%-Default to unlimited file get when no arguments +if nargin == 0, Action=+Inf'; else, Action = varargin{1}; end +PJump = 1; %-Allow pointer jumping? + +if ~ischar(Action) +%======================================================================= +% P = spm_get(n,Filter,Prompt,NewWDir,CmdLine) + +%-Condition arguments +%----------------------------------------------------------------------- +if nargin<5 CmdLine=[]; else CmdLine=varargin{5}; end +CmdLine = spm('CmdLine',CmdLine); +if nargin<4 NewWDir=''; else NewWDir=varargin{4}; end +if nargin<3 Prompt='Select files...'; else Prompt=varargin{3}; end +if nargin<2 | isempty(varargin{2}), + Filter=0; +else + Filter = varargin{2}; + ListOpts = {''}; + if any(Action(:)<0), + ListOpts = {'noexpand'}; + end; + if iscell(Filter), + ListOpts = Filter(2:end); + Filter = Filter{1}; + end; +end + +%-Parse number of items parameter (Passed as "Action" parameter) +%----------------------------------------------------------------------- +n = struct( 'fi', ~any(Action(:)<0),... %-Get files flag + 'mn', min(abs(Action(:))),... %-Min # items + 'mx', max(abs(Action(:))) ); %-Max # items +if isinf(n.mn), n.mn=0; end + +if n.mx==0 + if iscellstr(Prompt), varargout={{}}; else, varargout={''}; end + return +end + +%-NB: spm_get callbacks use Filter=0 for previous filtering +%----------------------------------------------------------------------- +if ischar(Filter) + if ~any(Filter=='*'), Filter = ['*',Filter]; end +else + Filter=0; +end + +%-Computation +%======================================================================= + +if CmdLine>0 + + %-Command line version, if global CMDLINE is true, & not overridden + %--------------------------------------------------------------- + P = spm_get('CmdLine',n,char(Prompt),[],NewWDir); + +else + + %-GUI version + %--------------------------------------------------------------- + %-Set up FileSelWin + [F,cF] = spm_get('Initialise','on',n,char(Prompt),Filter,NewWDir); + + %-Jump cursor to SelFileWin + if PJump + PLoc = get(0,'PointerLocation'); + FRec = get(F,'Position'); + set(0,'PointerLocation',[FRec(1)+FRec(3)/2, FRec(2)+FRec(2)/2]) + end + + %-Wait until filenames have been selected + hDone = findobj(F,'Tag','Done'); + waitfor(hDone,'UserData') + + %-Exit with error if SelFileWin deleted + if ~ishandle(hDone), error('SPM file selector window was quit!'), end + + %-Get P & exit status + status = get(hDone,'UserData'); + P = get(findobj(F,'Tag','P'),'UserData'); + + %-Reset and hide SelFileWin + spm_get('Initialise','off'); + + %-Return focus to previous figure (if any) + set(0,'CurrentFigure',cF) + + %-Jump cursor back to previous location + if PJump, set(0,'PointerLocation',PLoc), end + + %-Exit with error if reset (status=-1) + if status == -1, error('SPM-GUI reset: spm_get bailing out!'), end +end + +%-Convert P to cellstr if Prompt is one +%----------------------------------------------------------------------- +if iscellstr(Prompt), if isempty(P), P={}; else, P = cellstr(P); end, end + +%-Log the transaction +%----------------------------------------------------------------------- +if exist('spm_log')==2 + spm_log([mfilename,' : ',char(Prompt),' :'],P); end + +varargout = {P}; +return +end + +%======================================================================= +% - Callbacks & Utility embedded functions +%======================================================================= + +switch lower(Action), case 'createfig' +%======================================================================= +% F = spm_get('CreateFig',LastDirs,Filter) + +%-Condition arguments +%----------------------------------------------------------------------- +if nargin<3 Filter=[]; +else + Filter=varargin{3}; +end +if nargin<2 | isempty(varargin{2}), LastDirs=pwd; else LastDirs=varargin{2}; end +LastDirs=strvcat(LastDirs,getenv('HOME')); +if (exist('spm.m')==2), LastDirs=strvcat(LastDirs,spm('Dir')); end + +%-Save current figure +cF = get(0,'CurrentFigure'); + +%-Create window, compute scaling for screen size +%----------------------------------------------------------------------- +WS = spm('WinScale'); %-Window scaling factors +FS = spm('FontSizes'); %-Scaled font sizes +PF = spm_platform('fonts'); %-Font names (for this platform) +S0 = get(0,'ScreenSize'); %-Screen size + +F = figure('IntegerHandle','off',... + 'Tag','SelFileWin',... + 'Name',sprintf('%s%s: SPMget',spm('ver'),spm('GetUser',' (%s)')),... + 'NumberTitle','off',... + 'Position',[S0(3),S0(4),0,0]/2 + [-400/2,-395/2,400,395].*WS,... + 'Resize','off',... + 'Color',[1 1 1]*.8,... + 'Units','Pixels',... + 'MenuBar','none',... + 'DefaultTextInterpreter','none',... + 'DefaultTextFontName',PF.helvetica,... + 'DefaultTextFontSize',FS(10),... + 'DefaultAxesFontName',PF.helvetica,... + 'DefaultUicontrolFontName',PF.helvetica,... + 'DefaultUicontrolFontSize',FS(10),... + 'DefaultUicontrolInterruptible','on',... + 'Visible','off'); +varargout = {F}; + +%-Reset CurrentFigure to previous figure +set(0,'CurrentFigure',cF) + + +%-User control objects with callbacks +%----------------------------------------------------------------------- + +uicontrol(F,'Style','Frame','Tag','P','UserData',[],... + 'Position',[001 271 400 124].*WS); + +% uicontrol(F,'Style','Frame',... +% 'BackgroundColor',[1 1 1]*.8,... +% 'Position',[010 355 355 030].*WS); + +uicontrol(F,'Style','Text','Tag','Prompt','UserData',[],... + 'String','',... + 'ToolTipString','',... + 'FontName',PF.times,... + 'FontWeight','Bold',... + 'FontAngle','Italic',... + 'FontSize',FS(16),... + 'ForegroundColor','k',... + 'HorizontalAlignment','Center',... + 'Position',[010 370 380 022].*WS); + +uicontrol(F,'Style','PopUp','Tag','LastDirsPopup',... + 'String',strvcat('Previous Directories...',LastDirs),... + 'ToolTipString','cd to previously visited directories',... + 'HorizontalAlignment','Left',... + 'ForegroundColor','r',... + 'UserData',size(LastDirs,1),... + 'Callback','spm_get(''cd'')',... + 'Position',[010 347 335 022].*WS); + +uicontrol(F,'Style','PushButton','Tag','CDpwd',... + 'String','pwd',... + 'ToolTipString','change to current Matlab working directory',... + 'ForegroundColor','r',... + 'UserData',0,... + 'CallBack','spm_get(''cd'',pwd)',... + 'Position',[345 347 045 022].*WS); + +WDir=strtrim(LastDirs(1,:)); +uicontrol(F,'Style','Edit','String',WDir,... + 'ToolTipString','current directory - edit (& press RETURN) to cd',... + 'Tag','WDir','UserData',WDir,... + 'ForegroundColor','r','BackgroundColor',[.8,.8,1],... + 'HorizontalAlignment','Left',... + 'CallBack','spm_get(''cd'')',... + 'Position',[010 325 380 022].*WS); + +if strcmp(spm_platform('filesys'),'win') + off=100; + uicontrol(F,'Style','PopUp','Tag','DrivesPopup',... + 'ToolTipString','drives',... + 'HorizontalAlignment','Left',... + 'ForegroundColor','r',... + 'String',spm_get('DrivesPullDownStr'),... + 'Callback',['spm_get(''cd''),'... + 'set(gcbo,''String'',spm_get(''DrivesPullDownStr''))'],... + 'Position',[010+335-off 303 off 022].*WS); +else + off=0; +end +uicontrol(F,'Style','PopUp','Tag','SubDirsPopup',... + 'ToolTipString','cd to subdirectories of this directory',... + 'HorizontalAlignment','Left',... + 'ForegroundColor','r',... + 'String','SubDirectories...',... + 'Callback','spm_get(''cd'')',... + 'Position',[010 303 335-off 022].*WS); + +uicontrol(F,'Style','PushButton','Tag','CDhome',... + 'String','home',... + 'ToolTipString','cd to your home directory',... + 'ForegroundColor','r',... + 'CallBack',['spm_get(''cd'',''',strtrim(LastDirs(2,:)),''')'],... + 'Position',[345 303 045 022].*WS); + +uicontrol(F,'Style','Pushbutton',... + 'String','Filter',... + 'ToolTipString','reset filter to ''*''',... + 'CallBack','spm_get(''dir'',[],''*'')',... + 'UserData','*',... + 'Position',[010 276 040 026].*WS); + +uicontrol(F,'Style','Pushbutton','String','',... + 'ToolTipString','click to reset filter to initial value',... + 'CallBack',[ 'spm_get(''dir'',[],get(findobj(gcbf,''Tag'',',... + '''Filter''),''UserData''))'],... + 'Position',[048 276 099 026].*WS); + +uicontrol(F,'Style','Edit','Tag','Filter',... + 'String','*',... + 'ToolTipString','UNIX style file filter string - edit to change',... + 'UserData','*',... + 'BackgroundColor',[.8,.8,1],... + 'Callback','spm_get(''dir'')',... + 'Position',[050 278 095 022].*WS); + +uicontrol(F,'Style','Pushbutton','String','All',... + 'ToolTipString','select all items in this directory',... + 'Callback','spm_get(''All'',gcbf)',... + 'Position',[155 278 030 022].*WS); + +uicontrol(F,'Style','Pushbutton','String','Edit',... + 'ToolTipString','edit list of items',... + 'ForegroundColor','b',... + 'Callback','spm_get(''Edit'')',... + 'Position',[190 278 040 022].*WS); + +uicontrol(F,'Style','Pushbutton','String','Keybd',... + 'ToolTipString','switch to keyboard input of items',... + 'ForegroundColor','k',... + 'Callback','spm_get(''GUI2CmdLine'')',... + 'Position',[235 278 050 022].*WS); + +uicontrol(F,'Style','Pushbutton','String','Reset',... + 'ToolTipString','reset selection',... + 'ForegroundColor','r',... + 'Callback','spm_get(''Reset'',gcbf)',... + 'Position',[290 278 050 022].*WS); + +uicontrol(F,'Style','Pushbutton','String','Done',... + 'ToolTipString','done - press when completed selection of items',... + 'ForegroundColor','m',... + 'Tag','Done','UserData',1,... + 'Callback','spm_get(''Done'')',... + 'Interruptible','off','BusyAction','Cancel',... + 'Position',[345 278 045 022].*WS); + +uicontrol(F,'Style','Slider','Tag','ScrollBar',... + 'ToolTipString','scroll',... + 'Callback',[... + 'set(gca,''Units'',''Points''),',... + 'set(gca,''Position'',',... + 'get(gcbo,''UserData'')-[0,get(gcbo,''Value''),0,0])'],... + 'Position',[380 030 020 240].*WS) + +uicontrol(F,'Style','Frame','Tag','StatusArea',... + 'Position',[001 000 400 030].*WS); + +if exist('spm_help.m')==2 + uicontrol(F,'Style','Pushbutton','String','?',... + 'ToolTipString','help on using spm_get',... + 'ForegroundColor','g',... + 'Callback','spm_help(''spm_get.m'')',... + 'Position',[370 005 020 020].*WS); +end + +uicontrol(F,'Style','Text','Tag','StatusLine',... + 'String','',... + 'FontAngle','Italic',... + 'HorizontalAlignment','Center',... + 'ForegroundColor','w',... + 'Position',[010 005 355 020].*WS); + +set(F,'KeyPressFcn',... + 'spm_get(''FigKeyPressFcn'',get(gcbf,''CurrentCharacter''),gcbf)') + + +case 'figkeypressfcn' +%======================================================================= +% spm_help('FigKeyPressFcn',ch,F) +if nargin<3, F = gcbf; else, F = varargin{3}; end +if nargin<2, ch=get(F,'CurrentCharacter'); else, ch=varargin{2}; end + +%-Empty ch - shift/control/&c. +if isempty(ch), return, end + +%-Keyboard accelerators for item selection +%----------------------------------------------------------------------- +if any(abs(ch)==[10,11,19]) % ^J,^K,^S + H = flipud(findobj(get(get(F,'CurrentAxes'),'Children'),... + 'Flat','HandleVisibility','on')); + if isempty(H), return, end + h = max([0,findobj(H,'FontWeight','bold')]); + hPos = max([0;find(H==h)]); + if abs(ch)==19 & hPos + %- ^S - Select + switch get(h,'Tag') + case 'IName', spm_get('Add',h) + case 'SelIName', spm_get('Delete',h) + case 'DirName', spm_get('cd',get(h,'UserData')) + end + return + end + nhPos = max(1,min(hPos-2*(abs(ch)-10.5),length(H))); + if hPos, set(h,'FontWeight','normal'), end + set(H(nhPos),'FontWeight','bold') + return +end + +%-General Accelerators +%----------------------------------------------------------------------- +if any(abs(ch)==[32,4,16,2,27]) % Space,^D,^P,^B,Esc + switch abs(ch) + case {32,4} %- Space/^D - Done + spm_get('Done') + case 16 %- ^P - Change to MatLab pwd + spm_get('cd',pwd) + case 2 %- ^B - Keyboard + spm_get('GUI2CmdLine') + case 27 %- Esc - Reset + spm_get('Reset') + end +end + +%-Accelerators for WDir & Filter Edit widgets +%----------------------------------------------------------------------- +%-Edit which widget? WDir or Filter? +hCDpwd = findobj(F,'Tag','CDpwd'); +EditWDir = get(hCDpwd,'UserData'); +if EditWDir, h = findobj(F,'Tag','WDir'); + else, h = findobj(F,'Tag','Filter'); end +str = get(h,'String'); + +if abs(ch)==9 %- ^I - reset string + str = get(h,'UserData'); +elseif abs(ch)==5 %- ^E - switch between Edit widgets, reset strings + set(hCDpwd,'UserData',~EditWDir) + str = get(h,'UserData'); +elseif abs(ch)==23 %- ^W - word delete + tmp = max(find(str==filesep)); + if ~isempty(tmp), str(tmp:end)=[]; end +elseif abs(ch)==13 %- Return - Goto topic / apply filter + if EditWDir, spm_get('cd',str) + else, spm_get('dir'), end + return +elseif any(abs(ch)==[32:126]) + str = [str, ch]; +elseif abs(ch)==21 %- ^U - kill + str = ''; +elseif any(abs(ch)==[8,127]) %-BackSpace or Delete + if length(str), str(length(str))=''; end +else %-Other character + return +end +set(h,'String',str) + + +case 'initialise' +%======================================================================= +% [F,cF] = spm_get('Initialise',Vis,n,Prompt,Filter,WDir) +% (Re)Initialise SelFileWin, create one if necessary. + +if nargin<6 WDir=''; else WDir=varargin{6}; end +if nargin<5 + Filter=0; +else + Filter=varargin{5}; +end +if nargin<4 Prompt='Select files...'; else Prompt=varargin{4}; end +if nargin<3 n=struct('fi',1,'mn',0,'mx',Inf); else n=varargin{3}; end +if nargin<2 Vis='on'; else Vis=varargin{2}; end + +%-Recover SelFileWin figure number +F = findobj(get(0,'Children'),'Flat','Tag','SelFileWin'); + +cF = get(0,'CurrentFigure'); %-Save current figure + +switch lower(Vis), case 'close' + close(F) + varargout = {[],cF}; + return +case {'off','reset'} + varargout = {F,cF}; %-Return figure handles + if isempty(F), return, end %-...if no SelFileWin + varargout = {F,cF}; %-Return figure handles + set(F,'Visible','off') %-Make window Invisible + set(findobj(F,'Tag','Done'),'UserData',1) %-Set Done UserData to 1 + if strcmp(lower(Vis),'reset') + set(findobj(F,'Tag','Done'),'UserData',-1) + end + delete(get(F,'CurrentAxes')) %-delete 'dir' axes + drawnow + return +case 'on' + if isempty(F), F=spm_get('CreateFig'); end %-Create SelFileWin + varargout = {F,cF}; %-Return figure handles + set(findobj(F,'Tag','Done'),'UserData',0) %-Init. Done UserData + delete(get(F,'CurrentAxes')) %-delete 'dir' axes + set(findobj(F,'Tag','P'),'UserData','') %-clear P + set(findobj(F,'Tag','Prompt'),... + 'String',Prompt,... + 'ToolTipString',Prompt,... + 'UserData',n) + %-Reset prompt, & n + spm_get('StatusLine',0,n,F) %-Reset StatusLine + set(findobj(F,'Tag','CDpwd'),'UserData',1) %-KeyPressFcn to edit + % WDir widget initially + %-Filter string: Only set if different to previously passed one + % (held as UserData of Filter Edit uicontrol) + if ischar(Filter) + if ~strcmp(Filter,get(findobj(F,'Tag','Filter'),'UserData')) + set(findobj(F,'Tag','Filter'),'String',Filter,'UserData',Filter) + end + end + + %-Change to new working directory, if specified, else display current + if ~isempty(WDir) + spm_get('cd',WDir) %-Change directory + else + spm_get('dir') %-Show current dir. + end + + %-Popup figure, retaining CurrentFigure + figure(F),drawnow %-PopUp figure + set(0,'CurrentFigure',cF) %-Return to prev. figure + +otherwise + error('Unrecognised ''Vis'' option') +end + + + +case 'statusline' +%======================================================================= +% spm_get('StatusLine',nP,n,F) +if nargin<4, F = findobj(get(0,'Children'),'Flat','Tag','SelFileWin'); + else, F = varargin{4}; end +if nargin<3, n=get(findobj(F,'Tag','Prompt'),'UserData');else,n=varargin{3}; end +if nargin<2, nP=size(get(findobj(F,'Tag','P'),'UserData'),1);else,nP=varargin{2}; end + +str=['Selected ',int2str(nP)]; +if (n.mn==n.mx), str=sprintf('%s/%d',str,abs(n.mx)); end +if n.fi + str = [str,' file']; + if nP~=1, str=[str,'s']; end +else + str = [str,' director']; + if nP==1, str=[str,'y']; else, str=[str,'ies']; end +end + +if (n.mn~=n.mx) + if n.mn==0 + if isfinite(n.mx) + str=sprintf('%s (max %d)',str,n.mx); + end + else + if isinf(n.mx) + str=sprintf('%s (min %d)',str,n.mn); + else + str=sprintf('%s (min %d, max %d)',str,n.mn,n.mx); + end + end +end + + +if (nP>=n.mn) & (nP<=n.mx) + str = [str,', press "Done" when finished.']; +else + str = [str,'.']; +end + +set(findobj(F,'Tag','StatusLine'),'String',str) + + +case 'cd' +%======================================================================= +% spm_get('cd',NewDir,bNoDirList) + +%-Condition arguments +%----------------------------------------------------------------------- +if (nargin<3), bDirList=1; else, bDirList = ~varargin{3}; end +if (nargin<2), NewDir=''; else, NewDir=varargin{2}; end + +F = findobj(get(0,'Children'),'Flat','Tag','SelFileWin'); +set(F,'Pointer','Watch') +WDir=get(findobj(F,'Tag','WDir'),'UserData'); + +if nargin<2 + %-Current object contains NewDir, either UIEdit, UIPopMenu or text + NewDir=get(gcbo,'String'); + if strcmp(get(gcbo,'Style'),'popupmenu') + val = get(gcbo,'Value'); + if val==1 set(F,'Pointer','Arrow'), return, end + NewDir=NewDir(val,:); + set(gcbo,'Value',1) + end +else + NewDir=varargin{2}; +end + + +%-Condition directory path +%----------------------------------------------------------------------- +NewDir = spm_get('CPath',NewDir,WDir); + + +%-Changing directory, delete current directory listing (Done again in +% Action=='dir', but neater to do here in advance on this occasion) +%----------------------------------------------------------------------- +delete(get(F,'CurrentAxes')) + + +%-Set up LastDirs +%----------------------------------------------------------------------- +%-Recover LastDirs & size of fixed part from popup menu object +LastDirs = get(findobj(F,'Tag','LastDirsPopup'),'String'); LastDirs(1,:)=[]; +Fs = get(findobj(F,'Tag','LastDirsPopup'),'UserData'); + +%-Add new directory +LastDirs = strvcat(NewDir,LastDirs); + +%-Extract fixed and variable parts of LastDirs +FLastDirs = LastDirs(end-Fs+1:end,:); +LastDirs(end-Fs+1:end,:)=[]; + +%-Delete any replications of NewDir within the variable part of LastDirs +if size(LastDirs,1)>1 + IDRows = 1+find(all( LastDirs(2:end,:) == ... + repmat(LastDirs(1,:),size(LastDirs,1)-1,1) ,2 ) ); + LastDirs(IDRows,:)=[]; +end + +%-Delete NewDir from top of LastDirs if present in fixed part of LastDirs +if any(all(FLastDirs==repmat(LastDirs(1,:),Fs,1),2)), LastDirs(1,:)=[]; end + +%-Lose directories from the bottom of variable LastDirs if oversize +Vs = 10; %-Maximum allowable number of variable LastDirs entries +if size(LastDirs,1)>Vs, LastDirs(Vs+1:end,:)=[]; end + +%-Put LastDirs and FLastDirs back together +LastDirs = [LastDirs; FLastDirs]; + + +%-Write LastDirs to LastDirsPopup, write new directory to WDir Edit widget +%----------------------------------------------------------------------- +set(findobj(F,'Tag','LastDirsPopup'),... + 'String',strvcat('Previous Directories...',LastDirs)) +set(findobj(F,'Tag','WDir'),'String',NewDir,'UserData',NewDir) + + +%-Display new directory, unless supressed +%----------------------------------------------------------------------- +if bDirList + spm_get('dir') +else + set(findobj(F,'Tag','SubDirsPopup'),'Value',1,... + 'String','SubDirectories...') + set(F,'Pointer','Arrow') + drawnow +end + + +case 'dir' +%======================================================================= +% spm_get('dir',WDir,Filter,NoComp) + +F = findobj(get(0,'Children'),'Flat','Tag','SelFileWin'); +set(F,'Pointer','Watch') +delete(get(F,'CurrentAxes')), drawnow + +%-Condition parameters and setup variables +%----------------------------------------------------------------------- +if nargin<4, NoComp=0; else, NoComp=1; end +if nargin<3, + Filter=''; +else, + Filter=varargin{3}; +end +if nargin<2 | isempty(varargin{2}) + WDir = get(findobj(F,'Tag','WDir'),'UserData'); +else + WDir = strtrim(varargin{2}); +end + +n = get(findobj(F,'Tag','Prompt'),'UserData'); + +%-Sort out Filter +%----------------------------------------------------------------------- +if isempty(Filter) + Filter = get(findobj(F,'Tag','Filter'),'String'); + if isempty(Filter), Filter='*'; end +end +set(findobj(F,'Tag','Filter'),'String',Filter) + +%-Write current directory to WDir widget, if new directory specified +%----------------------------------------------------------------------- +if nargin>1 + set(findobj(F,'Tag','WDir'),'String',WDir,'UserData',WDir) +end + +%-Set up axis +%----------------------------------------------------------------------- +Rec = [0.02 0.086 0.92 0.58]; +hAxes = axes('Parent',F,'Tag','hAxes',... + 'Position',Rec,... + 'DefaultTextInterpreter','none',... + 'Units','Points',... + 'Visible','off'); +y = get(hAxes,'Position'); +y0 = floor(y(3)); +dy = 22; +set(hAxes,'Ylim',[0,y0]) + + +%-List current directory +%----------------------------------------------------------------------- +[Files,Dirs] = spm_list_files_expand(WDir,Filter,ListOpts); +if isempty(Dirs) + text(0,y0,'Permission denied, or non-existent directory',... + 'Parent',hAxes,'FontWeight','bold','Color','r'); + set(findobj(F,'Tag','ScrollBar'),'Visible','off') + set(F,'Pointer','Arrow') + return +else %-Exclude "." directory from Dirs, exclude ".." if in root + %-Exclude ".." from DirItems (used when selecting directories) + Dirs(Dirs(:,1)=='.',:)=[]; + DirItems = strvcat('.',Dirs); + if ~isroot(WDir), Dirs=strvcat('..',Dirs); end +end + +%-Create list of directories in pulldown menu +%----------------------------------------------------------------------- +set(findobj(F,'Tag','SubDirsPopup'),'Value',1,... + 'String',strvcat('SubDirectories...',Dirs)) + +%-Create list of directories with appropriate 'ButtonDownFcn': - +%----------------------------------------------------------------------- +y = y0-dy; +if size(Dirs,1) + for i = 1:size(Dirs,1) + h = text(0,y,strtrim(Dirs(i,:)),... + 'Parent',hAxes,... + 'Tag','DirName',... + 'FontWeight','bold','Color','r',... + 'UserData',strtrim(Dirs(i,:)),... + 'ButtonDownFcn','spm_get(''cd'',get(gcbo,''UserData''))',... + 'HandleVisibility','off'); + y = y - dy; + end + %-Note lowest position used (in points) + set(h,'Units','Points') + my = get(h,'Position')*[0;1;0]; +else + my = 0; +end + +%-Files or directories? +%----------------------------------------------------------------------- +if n.fi + %-Select file(s) - omit ".*" dot files + if ~isempty(Files), Files(Files(:,1)=='.',:)=[]; end + Items = Files; +else %-Select directory/ies - don't compress the listing + Items = DirItems; + NoComp = 1; +end + +%-Compressed summary view, or full view? +%----------------------------------------------------------------------- +if ~NoComp & size(Items,1)>32 + %-Use a compressed summary view + if Filter(end)=='*' + [IName,ItemPos] = spm_get('FileSummary',Items); + else + [IName,ItemPos] = spm_get('FileSummary',Items,'front',Filter); + end + text(0.45,y0,'Summary View - Click to expand',... + 'Parent',hAxes,... + 'Color','w',... + 'FontSize',spm('FontSize',9),... + 'HorizontalAlignment','Center',... + 'FontAngle','Italic',... + 'ButtonDownFcn','spm_get(''dir'',[],[],1)',... + 'HandleVisibility','off'); +else + %-Use a standard view, each item with it's own representation + IName = Items; ItemPos = [1:size(Items,1)]'; +end + +%-Create list of Items with appropriate 'ButtonDownFcn': - +%----------------------------------------------------------------------- +y = y0-dy; +if size(IName,1) + for i = 1:size(IName,1) + cIName = IName(i,IName(i,:)~=' '); + cItemPos = ItemPos(i,ItemPos(i,:)>0); + nItems = length(cItemPos); + cItems = strtrim(Items(cItemPos,:)); + h = text(0.4,y,cIName,... + 'Parent',hAxes,... + 'Tag','IName',... + 'UserData',cItems,... + 'Color','k',... + 'ButtonDownFcn','spm_get(''Add'')',... + 'HandleVisibility','on'); + if nItems>1; + h = text(0.39,y-3,int2str(sum(ItemPos(i,:)>0)),... + 'Parent',hAxes,... + 'Color','w',... + 'FontSize',spm('FontSize',8),... + 'FontAngle','Italic',... + 'HorizontalAlignment','right',... + 'UserData',cIName,... + 'ButtonDownFcn',... + 'spm_get(''dir'',[],get(gcbo,''UserData''),1)',... + 'HandleVisibility','off'); + end + y = y - dy; + end + %-Note lowest position used (in points) + set(h,'Units','Points') + my = min(my,get(h,'Position')*[0;1;0]); +end + +%-Setup scrollbar +%----------------------------------------------------------------------- +h = findobj(F,'Tag','ScrollBar'); +if my>0 + set(h,'Visible','off') +else + set(h, 'UserData', get(hAxes,'Position'),... + 'Value', 0,... + 'Max', 0,... + 'Min', my-dy,... + 'SliderStep', min([0.5,1],[3*dy,(y0-dy)]/(-my+dy)),... + 'Visible', 'on') +end + +%-Done +%----------------------------------------------------------------------- +set(F,'Pointer','Arrow') + + +case 'strsort' +%======================================================================= +% [sS,I]=spm_get('StrSort',S) +% Utility string sorting routine for string matrices +if nargin<2, error('Sort what?'), else, S = varargin{2}; end + +if size(S,2)<=3 + a = min(abs(S(:)')); + b = max(abs(S(:)'))+1; + i = (b-a).^[size(S,2)-1:-1:0]; + [null,I] = sort(abs(S)*i'); + sS = S(I,:); +else + lS = size(S,2); + hS = S(:,1:3); + tS = S; + tS(:,1:3) = []; + [null,tI] = spm_get('StrSort',tS); + [null,hI] = spm_get('StrSort',hS(tI,:)); + I = tI(hI); + sS = S(I,:); +end +varargout = {sS,I}; + + +case 'strfliplr' +%======================================================================= +% fS = spm_get('strfliplr',S) +if nargin<2, error('Flip what?'), else, S = varargin{2}; end + +%-FlipLR the Fnames matrix, but watch out for trailing blanks/spaces +[nS,Sl] = size(S); +fS = char(zeros(nS,Sl)); +for i = 1:nS + c = max(find(S(i,:)~=' ')); + fS(i,:) = S(i,[c:-1:1,Sl:-1:c+1]); +end +varargout = {fS}; + + +case 'filesummary' +%======================================================================= +% [FSpecs,FnamePos]=spm_get('FileSummary',Fnames,Cend,Filter,len) +if nargin<5, len = 3; else len = varargin{5}; end +if nargin<4 | isempty(varargin{4}), + Filter = '*'; +else + Filter = varargin{4}; +end +if nargin<3, Cend='both'; else, Cend = varargin{3}; end +if nargin<2 | isempty(varargin{2}), error('Specify Fnames to summarise') +else Fnames = varargin{2}; end +%-Implicit assumption in this code is that no two files have the same name + +if strcmp(Cend,'front') + if size(Fnames,1)==1 | size(Fnames,2)0); + if length(chI) == 1 + %-Single file + %FSpecs = strvcat(FSpecs,hSpecs(i,hSpecs(i,:)~=' ')); + %KND: + FSpecs = strvcat(FSpecs,hSpecs(i,:)); + tmp = max([1,size(FnamePos,2)]); + FnamePos = [FnamePos,... + zeros(size(FnamePos).*[1 -1]+[0,tmp]);... + chI, zeros(1,tmp-1)]; + else + %-Multiple files - process by common endings + Cfnames = Fnames(chI,:); + %-Chop off common beginning + Cfnames(:,1:find(hSpecs(i,:)=='*')-1) = []; + %-Watch out for blank lines (e.g for files abc & abcde) + bI = find(all(Cfnames==' ',2)); + if ~isempty(bI) + %-Assumming unique input => one blank line at most + % NB: This may change ordering of groups by first member + FSpecs = strvcat(FSpecs,... + hSpecs(i,1:find(hSpecs(i,:)=='*')-1) ); + tmp = max([1,size(FnamePos,2)]); + FnamePos = [FnamePos,... + zeros(size(FnamePos).*[1 -1]+[0,tmp]);... + chI(bI), zeros(1,tmp-1)]; + Cfnames(bI,:) = []; + chI(bI) = []; + end + %-Even if we've deleted a blank line, there'll be some left + %-Look for common endings + [tSpecs,tI] = spm_get('FileSummary',Cfnames,'end',Filter,len); + %-If no common endings then don't expand to individual files! + if size(tI,1)>1 & size(tI,2)==1 + tSpecs = '*'; + tI = tI'; + end + for j = 1:size(tSpecs,1) + cI = chI(tI(j,tI(j,:)>0)); + if length(cI) == 1 + %-Unique, don't bother with '*'s + cFSpec = [hSpecs(i,1:find(hSpecs(i,:)=='*')-1),... + tSpecs(j,1:max(find(tSpecs(j,:)~=' ')) ) ]; + else + %-Don't double '*'s + cFSpec = [hSpecs(i,hSpecs(i,:)~=' '),... + tSpecs(j,2:max(find(tSpecs(j,:)~=' ')) ) ]; + end + FSpecs = strvcat(FSpecs,cFSpec); + tmp = max([length(cI),size(FnamePos,2)]); + FnamePos = [FnamePos,... + zeros(size(FnamePos).*[1 -1]+[0,tmp]);... + cI, zeros(1,tmp-length(cI))]; + end + end + end +else + error('Invalid Cend specifier') +end +varargout = {FSpecs,FnamePos}; + + +case 'add' +%======================================================================= +% spm_get('Add',H) +% Add filename to current list +%-H - [Optional] (vector of) handles to file/dir name objects +% + +%-Recover variables from holding objects +%----------------------------------------------------------------------- +F = findobj(get(0,'Children'),'Flat','Tag','SelFileWin'); +SelType = get(F,'SelectionType'); +n = get(findobj(F,'Tag','Prompt'),'UserData'); +P = get(findobj(F,'Tag','P'),'UserData'); +WDir = get(findobj(F,'Tag','WDir'),'UserData'); + +if (nargin==2) + %-Multiple handles passed by 'All' CallBack + H=varargin{2}; +else + %-'Add' called by item callback - get handle & check selection + H=gcbo; + if strcmp(SelType,'extend') + IName = get(H,'String'); + if any(IName=='*') + spm_get('dir','',IName,1) + return + else + return + end + elseif strcmp(SelType,'alt') + spm_get('Add',H) + spm_get('Done') + return + end +end + +%-Add items to P (until P would be overfull) +%----------------------------------------------------------------------- +for h = H' + IName = get(h,'String'); + Items = get(h,'UserData'); + nItems = size(Items,1); + nP = size(P,1); + + %-Don't add any more if #items has been specified, + % and P would be overfull + if (nP+nItems>n.mx), break, end + + %-Add items to P + if WDir(end)~=filesep, WDir = [WDir,filesep]; end + FPath = [repmat(WDir,nItems,1),Items]; + P = strvcat(P,FPath); + if nItems==1 + tmp = [int2str(nP+1),' :',IName]; + else + tmp = [int2str(nP+1),'-',int2str(nP+nItems),' :',IName]; + end + set(h,'String',tmp,'Color','b',... + 'Tag','SelIName',... + 'ButtonDownFcn','spm_get(''Delete'')') +end + +%-Return new P to holding object +%----------------------------------------------------------------------- +set(findobj(F,'Tag','P'),'UserData',strtrim(P)); + +%-Update StatusLine +%----------------------------------------------------------------------- +spm_get('StatusLine',size(P,1),n,F) + + +case 'delete' +%======================================================================= +% spm_get('Delete',h) +F = findobj(get(0,'Children'),'Flat','Tag','SelFileWin'); +if nargin<2, h=gcbo; else, h=varargin{2}; end +WDir = get(findobj(F,'Tag','WDir'),'UserData'); +if WDir(end)~=filesep, WDir = [WDir,filesep]; end +P = get(findobj(F,'Tag','P'),'UserData'); +n = get(findobj(F,'Tag','Prompt'),'UserData'); +IName = get(h,'String'); +IName(1:find(IName==':')) = []; +Items = get(h,'UserData'); +nItems = size(Items,1); +FPath = [repmat(WDir,nItems,1),Items]; +nP = size(P,1); + +%-Compare Items with end of P (Can only delete from the end) +tmp = strvcat(P(nP-(nItems-1):nP,:),FPath); +if all(all(tmp(1:nItems,:)==tmp(nItems+1:2*nItems,:))) + P(nP-(nItems-1):nP,:)=[]; + if ~isempty(P), P(:,all(P==' ',1))=[]; else, P=''; end + set(findobj(F,'Tag','P'),'UserData',P); + set(h,'String',IName,'Color','k',... + 'Tag','IName',... + 'ButtonDownFcn','spm_get(''Add'')') + spm_get('StatusLine',size(P,1),n,F) +end + + +case 'reset' +%======================================================================= +% spm_get('Reset',F) +if nargin<2, F = findobj(get(0,'Children'),'Flat','Tag','SelFileWin'); + else, F = varargin{2}; end +set(findobj(F,'Tag','P'),'UserData',[]); +spm_get('dir') +spm_get('StatusLine') + + +case 'all' +%======================================================================= +% spm_get('All',F) +if nargin<2, F = findobj(get(0,'Children'),'Flat','Tag','SelFileWin'); + else, F = varargin{2}; end +set(F,'Pointer','Watch') +H = flipud(findobj(get(get(F,'CurrentAxes'),'Children'),'Flat','Tag','IName')); +spm_get('Add',H); +set(F,'Pointer','Arrow') + + +case 'cmdline' +%======================================================================= +% P = spm_get('CmdLine',n,Prompt,P,WDir,AllowEnd) +if nargin<6, AllowEnd=0; else AllowEnd=varargin{6}; end +if nargin<5, WDir=''; else WDir=varargin{5}; end +if isempty(WDir), WDir=pwd; else, WDir=strtrim(WDir); end +if nargin<4, P=[]; else P=varargin{4}; end +if nargin<3, Prompt='Select files...'; else Prompt=varargin{3}; end +if nargin<2, n=struct('fi',1,'mn',0,'mx',Inf); else n=varargin{2}; end + +if n.mx==0, varargout={''}; return, end +nP=size(P,1); + +%-Print prompt +%----------------------------------------------------------------------- +fprintf('\n'), fprintf('%c',repmat('=',1,70)), fprintf('\n') +fprintf('\t%s\n',Prompt) +fprintf('%c',repmat('=',1,70)), fprintf('\n') +fprintf('Current working directory: %s\n',WDir) +fprintf('(Prepended to relative paths)\n') +if nP>0 + fprintf('\nAlready selected:\n') + for pP = [1:size(P,1)], fprintf('\t%s\n',P(pP,:)), end +end + +if nP>=n.mx + fprintf('\nMax # items already selected: Returning...\n\n' ) + varargout={P}; + return +end + +%-Print prompts for input +%----------------------------------------------------------------------- +fprintf('\nEnter paths ') +if n.fi + str = 'file'; if n.mx>1, str=[str,'s']; end +else + str = 'director'; if n.mx==1, str=[str,'y']; else, str=[str,'ies']; end +end +if AllowEnd | n.mn==0 + if isfinite(n.mx) + fprintf('to at most %d %s:\n',n.mx,str) + else + fprintf('to %s:\n',str) + end +else + if (n.mn==n.mx) + fprintf('to %d %s:\n',n.mx,str) + else + if isinf(n.mx) + fprintf('to at least %d %s:\n',n.mn,str) + else + fprintf('to %d-%d %s:\n',n.mn,n.mx,str) + end + end +end + +%-AllowEnd if asked for, or if a range of item numbers is OK +%----------------------------------------------------------------------- +Tstr = 'END'; +if AllowEnd | (n.mn~=n.mx) + fprintf(' (Type "%s" to terminate input.)\n',Tstr) +end + +%-Get files +%----------------------------------------------------------------------- +Done=0; +while ~( Done | (nP>=n.mx) ) + %-Get input + str = []; while isempty(str) + str=dstrtrim(input(sprintf(' %3d : ',nP+1),'s'));end + %-Prepend WDir to incomplete pathnames + if (~isabspath(str))&(~strcmp(str,Tstr)), str=fullfile(WDir,str); end + if n.fi,OK=exist(str)==2;else,OK=exist(str)==7;end + OKend = AllowEnd | (nP>=n.mn&nP<=n.mx); + while ~( OK | (strcmp(str,Tstr) & OKend) ) + if strcmp(str,Tstr) + fprintf('%c\tSelect %d-%d items!\n',7,n.mn,n.mx) + else, fprintf('%c\t%s isn''t valid!\n',7,str), end + str=[]; while isempty(str) + str=dstrtrim(input(sprintf(' %3d : ',nP+1),'s'));end + if (~isabspath(str))&(~strcmp(str,Tstr)) + str = fullfile(WDir,str); end + if n.fi,OK=exist(str)==2;else,OK=exist(str)==7;end + OKend = AllowEnd | (nP>=n.mn&nP<=n.mx); + end + if ~strcmp(str,Tstr), P=strvcat(P,str); end + nP = nP+1; + Done = strcmp(str,Tstr); +end + +varargout = {P}; + + +case 'gui2cmdline' +%======================================================================= +% P = spm_get('GUI2CmdLine') + +F = findobj(get(0,'Children'),'Flat','Tag','SelFileWin'); +Prompt = get(findobj(F,'Tag','Prompt'),'String'); +n = get(findobj(F,'Tag','Prompt'),'UserData'); +WDir = get(findobj(F,'Tag','WDir'),'UserData'); +P = get(findobj(F,'Tag','P'),'UserData'); + +WS = get(F,'Position'); WS = [WS(3)/400,WS(4)/395,WS(3)/400,WS(4)/395]; +Handles = []; +h = uicontrol(F,'Style','Frame',... + 'Position',[000 000 400 395].*WS); +Handles = [Handles, h]; + +h = uicontrol(F,'Style','Text',... + 'String',Prompt,... + 'ToolTipString',Prompt,... + 'FontName',spm_platform('font','times'),... + 'FontWeight','Bold',... + 'FontAngle','Italic',... + 'FontSize',spm('FontSize',16),... + 'ForegroundColor','k',... + 'HorizontalAlignment','Center',... + 'Position',[010 370 380 022].*WS); +Handles = [Handles, h]; + +h = uicontrol(F,'Style','Frame',... + 'Position',[001 001 400 030].*WS); +Handles = [Handles, h]; + +h = uicontrol(F,'Style','Text',... + 'String','Use command window...',... + 'FontAngle','Italic',... + 'HorizontalAlignment','Center',... + 'ForegroundColor','w',... + 'Position',[020 005 360 020].*WS); +Handles = [Handles, h]; + +PNew = spm_get('CmdLine',n,Prompt,P,WDir,1); + +if ~strcmp(P(:),PNew(:)) + set(findobj(F,'Tag','P'),'UserData',PNew); + spm_get('dir') + spm_get('StatusLine',size(PNew,1),n,F) +end + +delete(Handles); + + +case 'edit' +%======================================================================= +% spm_get('Edit') + +F = findobj(get(0,'Children'),'Flat','Tag','SelFileWin'); +n = get(findobj(F,'Tag','Prompt'),'UserData'); + +set(F,'Units','Pixels'); +WS = get(F,'Position'); WS = [WS(3)/400,WS(4)/395,WS(3)/400,WS(4)/395]; + +EditHandles = []; + +h = uicontrol(F,'Style','Frame',... + 'Tag','EditHandles',... + 'Position',[001 001 400 395].*WS); +EditHandles = [EditHandles, h]; + +h = uicontrol(F,'Style','Text',... + 'String',get(findobj(F,'Tag','Prompt'),'String'),... + 'FontName',spm_platform('font','times'),... + 'FontWeight','Bold',... + 'FontAngle','Italic',... + 'FontSize',spm('FontSize',16),... + 'ForegroundColor','k',... + 'HorizontalAlignment','Center',... + 'Position',[010 370 380 022].*WS); +EditHandles = [EditHandles, h]; + +h = uicontrol(F,'Style','Text',... + 'String','Edit the filename window...',... + 'ForegroundColor','w',... + 'HorizontalAlignment','Left',... + 'Position',[010 347 210 022].*WS); +EditHandles = [EditHandles, h]; + +h = uicontrol(F,'Style','Pushbutton','String','Cancel',... + 'ForegroundColor','r',... + 'Callback','spm_get(''EditDone'',''Cancel'')',... + 'Position',[220 347 070 022].*WS); +EditHandles = [EditHandles, h]; + +h = uicontrol(F,'Style','Pushbutton','String','OK',... + 'ForegroundColor','m',... + 'Callback','spm_get(''EditDone'',''OK'')',... + 'Position',[310 347 070 022].*WS); +EditHandles = [EditHandles, h]; + +h = uicontrol(F,'Style','Edit',... + 'String',cellstr(char(get(findobj(F,'Tag','P'),'UserData'))),... + 'Tag','EditWindow','UserData',0,... + 'HorizontalAlignment','Left',... + 'ForegroundColor','b','BackgroundColor',[.8,.8,1],... + 'Max',2,... + 'Callback','set(gco,''UserData'',1)',... + 'Position',[010 040 380 300].*WS); +EditHandles = [EditHandles, h]; + +h = uicontrol(F,'Style','Frame',... + 'Position',[001 001 400 030].*WS); +EditHandles = [EditHandles, h]; + +str=[]; if isfinite(n.mx), str=['Use only top ',int2str(n.mx),' lines. ']; end +h = uicontrol(F,'Style','Text',... + 'String',[str,'(Blank lines will be removed)'],... + 'FontAngle','Italic',... + 'HorizontalAlignment','Center',... + 'ForegroundColor','w',... + 'Position',[020 005 360 020].*WS); +EditHandles = [EditHandles, h]; + +set(findobj(F,'Tag','EditHandles'),'UserData',EditHandles) + + +case 'editdone' +%======================================================================= +% spm_get('EditDone',OK) +if nargin<2, OK=0; else, OK=strcmp(varargin{2},'OK'); end +F = findobj(get(0,'Children'),'Flat','Tag','SelFileWin'); + +EditHandles = get(findobj(F,'Tag','EditHandles'),'UserData'); +set(EditHandles,'Visible','off') +h_EditWindow = findobj(F,'Tag','EditWindow'); + +if OK & get(h_EditWindow,'UserData') + %-Only update if OK was pressed, and there was an edit. + P = get(h_EditWindow,'String'); + n = get(findobj(F,'Tag','Prompt'),'UserData'); + %-Condition P, by removing blank lines and truncating to n items. + P = P(~strcmp(P,'')); + if (isfinite(n.mx))&(~isempty(P)), P=P(1:min(length(P),n.mx)); end + %-Write P to 'P' 'Tag'ged object + set(findobj(F,'Tag','P'),'UserData',char(P)); + %-Redo directory listing + spm_get('dir') + %-Redo status line + spm_get('StatusLine',size(P,1),n,F) +end +delete(EditHandles), drawnow + + +case 'done' +%======================================================================= +% spm_get('Done') +F = findobj(get(0,'Children'),'Flat','Tag','SelFileWin'); +n = get(findobj(F,'Tag','Prompt'),'UserData'); + +%-If #files was specified, and not enough have been selected, then return +nP = size(get(findobj(F,'Tag','P'),'UserData'),1); +if (nPn.mx) + if (n.mn==n.mx) + str = sprintf('Must select %d item(s)!',n.mx); + else + str = sprintf('Must select %d-%d items!',n.mn,n.mx); + end + spm('Beep') + msgbox(str,sprintf('%s%s: %s...',spm('ver'),... + spm('GetUser',' (%s)'),mfilename),'error','non-modal') + drawnow + + % Not sure about this bit - JA + % set(F,'windowstyle','modal') + + return +end + +%-Done, set Done UserData tag for handling +set(findobj(F,'Tag','Done'),'UserData',1) + + +case 'cpath' +%======================================================================= +% cpath = spm_get('cpath',path,cwd) +if nargin<3, cwd=pwd; else, cwd=spm_get('CPath',varargin{3}); end +if nargin<2, error('insufficient arguments'), end +cpath = cellstr(varargin{2}); + +sepchar = filesep; %-File seperator character +dubsep = [sepchar,sepchar]; %-Double file separator character +rootlen = spm_platform('rootlen'); %-Length of root paths ('/' & 'C:\' &c) + +for i=1:prod(size(cpath)) + + %-Prepend cwd to relative pathnames + %--------------------------------------------------------------- + if ~isabspath(cpath{i}), cpath{i}=fullfile(cwd,cpath{i}); end + + %-Sort out stationary relative pathnames './' & '/.' + %--------------------------------------------------------------- + %-Remove midpath '/./' + cpath{i}=strrep(cpath{i},[sepchar,'.',sepchar], sepchar); + + %-Remove '.' from trailing '/.' + if length(cpath{i})>=2 & strcmp(cpath{i}(end-1:end),[sepchar,'.']) + cpath{i}(end)=''; end + + %-Remove trailing '/'s (unless root, in which case ensure trailing '/') + if isroot(cpath{i}) + if cpath{i}(end)~=sepchar, cpath{i}(end+1)=sepchar; end + else + while(length(cpath{i})>rootlen) & cpath{i}(end)==sepchar + cpath{i}(end)=''; + end + end + + %-Remove any residual '//'s + while length(cpath{i})>=2 & length(findstr(cpath{i},dubsep)) + if (ispc & strncmp(dubsep, cpath{i},2)) + %initial '\\' is used for network drive in Windows' world + cpath{i}= [sepchar , strrep(cpath{i},dubsep,sepchar)]; + else + cpath{i}=strrep(cpath{i},dubsep,sepchar); + end + end + + %-Sort out relative pathnames '/..' + %--------------------------------------------------------------- + %-Process midpath '/../' + t0 = 0; + downstr = [sepchar,'..',sepchar]; + while length(cpath{i})>=rootlen+4 & max([0,findstr(cpath{i},downstr)])>t0 + t2 = t0+min(findstr(cpath{i}(t0+1:end),downstr)); + t1 = t0+max([0,find(cpath{i}(t0+1:t2-1)==sepchar)]); + if t1==t0 | strcmp(cpath{i}(t1:t2),downstr) + t0=t2; + else + cpath{i}(t1:t2+2)=''; + end + end + + %-Process trailing '/..' (Don't remove first '/') + if length(cpath{i})>rootlen+2 & ... + strcmp(cpath{i}(end-2:end),[sepchar,'..']) + t2 = length(cpath{i})-2; + t1 = t0+max([0,find(cpath{i}(t0+1:t2-1)==sepchar)]); + if t1~=t0 & ~strcmp(cpath{i}(t1:t2),downstr) + cpath{i}(max(t1,rootlen+1):t2+2)=''; + end + end + + %-Remove any leading '/../'s + while length(cpath{i})>=rootlen+3 & ... + strcmp(cpath{i}(rootlen+[0:3]),downstr) + cpath{i}(rootlen+[1:3])=''; + end + + %-Canonicalise '/..' to '/' + if length(cpath{i})==rootlen+2 & ... + strcmp(cpath{i}(rootlen+[0:2]),[sepchar,'..']) + cpath{i}(rootlen+[1:2])=''; + end + + %-Remove any residual '//'s + while length(cpath{i})>=2 & length(findstr(cpath{i},dubsep)) + cpath{i}=strrep(cpath{i},dubsep,sepchar); end + + + %-Final canonicalisations for relative pathnames + %--------------------------------------------------------------- + % %-Remove leading './' + % if length(cpath{i})>2 & strcmp(cpath{i}(1:2),['.',sepchar]) + % cpath{i}(1:2)=''; end + + %-Canonicalise leading './../' to '../' + if length(cpath{i})>5 & strcmp(cpath{i}(1:5),['.',downstr]) + cpath{i}(1:2)=''; end + %-Canonicalise './..' to '..' + if strcmp(cpath{i},['.',sepchar,'..']), cpath{i}='..'; end + +end + +if ischar(varargin{2}), varargout={char(cpath)}; else, varargout={cpath}; end + + +case 'drivespulldownstr' +%======================================================================= +% str = spm_get('DrivesPullDownStr') +drivestr = spm_win32utils('drives'); +n = length(drivestr); +varargout = ... + {['Drives...',reshape(['|'*ones(1,n);drivestr;':'*ones(1,n)],1,3*n)]}; + + +case 'files' +%======================================================================= +% FORMAT [P,dir] = spm_get('Files',dir,fil) +if nargin<3, + fil='*'; +else, + fil = varargin{3}; +end +if nargin<2, dir='.'; else, dir = varargin{2}; end +dir = spm_get('CPath',dir); +[P,null] = spm_list_files_expand(dir,fil,ListOpts); + +%-Make into full pathnames if nargout<2 +if ~isempty(P) & nargout<2 + nP = size(P,1); + P = [repmat(dir,nP,1), repmat(filesep,nP,1), P]; +end + +varargout = {P,dir}; + + +otherwise +%======================================================================= +error('Illegal Action string') + + +%======================================================================= +end + + +%======================================================================= +%- S U B - F U N C T I O N S +%======================================================================= + +function so = dstrtrim(si) +%======================================================================= +if nargin==0, error('insufficient arguments'), end +if isempty(si), so=''; return, end +if ~ischar(si), error('input must be a string'), end + +b = any((si~=' ' & si~=0),1); +if all(~b), so=''; return, end +so = si(:,max(1,min(find(b))):min(max(find(b)),length(b))); + + +function rootf = isroot(path) %%-Platform specifics (MBrett) +%======================================================================= +%-Returns true if path is root directory, else false +switch (spm_platform('filesys')) +case 'unx' + rootf = strcmp(path,'/'); +case 'win' + %-Accepts e.g e: and e:\ as root paths + if (length(path)==2 & path(2)==':') | ... + (length(path)>2 & strcmp(path(2:end),':\')) + rootf = 1; + else + rootf = 0; + end +otherwise + error('isroot not coded for this filesystem'); +end + + +function absf = isabspath(path) %%-Platform specifics (MBrett) +%======================================================================= +%-Returns true if path is absolute, false if relative (or empty) +switch (spm_platform('filesys')) +case 'unx' + if (~isempty(path) & path(1)=='/'), absf=1; else, absf=0; end +case 'win' + if (length(path)>1 & path(2)==':'), absf=1; else, absf=0; end +otherwise + error('isabspath not coded for this filesystem'); +end +return + + +function [T]=strtrim(S); +% strtrim - remove blank spaces + +if iscell(S) + T=S; + for i=1:numel(S) + + T{i}=strtrim(S{i}); + end +elseif ischar(S) + T=fliplr(deblank(fliplr(deblank(S)))); +end + \ No newline at end of file diff --git a/spm/spm2/spm_imcalc_ui.m b/spm/spm2/spm_imcalc_ui.m new file mode 100644 index 0000000..774981c --- /dev/null +++ b/spm/spm2/spm_imcalc_ui.m @@ -0,0 +1,172 @@ +function [Q,Vo] = spm_imcalc_ui(P,Q,f,flags,varargin) +% Perform algebraic functions on images +% FORMAT Q = spm_imcalc_ui(P,Q,f,flags,Xtra_vars...) +% P - matrix of input image filenames +% [user prompted to select files if arg missing or empty] +% Q - name of output image +% [user prompted to enter filename if arg missing or empty] +% f - expression to be evaluated +% [user prompted to enter expression if arg missing or empty] +% flags - cell vector of flags: {dmtx,mask,type,hold} +% dmtx - Read images into data matrix? +% [defaults (missing or empty) to 0 - no] +% mask - implicit zero mask? +% [defaults (missing or empty) to 0] +% type - data type for output image (see spm_type) +% [defaults (missing or empty) to 4 - 16 bit signed shorts] +% hold - interpolation hold (see spm_slice_vol) +% [defaults (missing or empty) to 0 - nearest neighbour] +% Xtra_vars... - additional variables which can be used in expression +% Q (output) - full pathname of image written +% Vo - structure containing information on output image (see spm_vol) +% +%_______________________________________________________________________ +% +% spm_imcalc_ui uses spm_imcalc as an engine to perform user-specified +% algebraic manipulations on a set of images, with the result being +% written out as an image. The user is prompted to supply images to +% work on, a filename for the output image, and the expression to +% evaluate. The expression should be a standard matlab expression, +% within which the images should be referred to as i1, i2, i3,... etc. +% +% If the dmtx flag is set, then images are read into a data matrix X +% (rather than into seperate variables i1, i2, i3,...). The data matrix +% should be referred to as X, and contains images in rows. +% +% Computation is plane by plane, so in data-matrix mode, X is a NxK +% matrix, where N is the number of input images [prod(size(Vi))], and K +% is the number of voxels per plane [prod(Vi(1).dim(1:2))]. +% +% For data types without a representation of NaN, implicit zero masking +% assummes that all zero voxels are to be treated as missing, and +% treats them as NaN. NaN's are written as zero (by spm_write_plane), +% for data types without a representation of NaN. +% +% With images of different sizes and orientations, the size and +% orientation of the first is used for the output image. A warning is +% given in this situation. Images are sampled into this orientation +% using the interpolation specified by the hold parameter. [default - +% nearest neighbour] +% +% The image Q is written to current working directory unless a valid +% full pathname is given. +% +% Example expressions (f): +% +% i) Mean of six images (select six images) +% f = '(i1+i2+i3+i4+i5+i6)/6' +% ii) Make a binary mask image at threshold of 100 +% f = 'i1>100' +% iii) Make a mask from one image and apply to another +% f = 'i2.*(i1>100)' +% - here the first image is used to make the mask, which is +% applied to the second image +% iv) Sum of n images +% f = 'i1 + i2 + i3 + i4 + i5 + ...' +% v) Sum of n images (when reading data into a data-matrix - use dmtx arg) +% f = 'sum(X)' +% +% Parameters can be passed as arguments to override internal defaults +% (for hold, mask & type), or to pre-specify images (P), output +% filename (Q), or expression (f). Pass empty matrices for arguments +% not to be set. +% E.g. Q = spm_imcalc_ui({},'test','',{[],[],[],1}) +% ... pre-specifies the output filename as 'test.img' in the current +% working directory, and sets the interpolation hold to tri-linear. +% +% Further, additional variables for use in the computation can be +% passed at the end of the argument list. These should be referred to +% by the names of the arguments passed in the expression to be +% avaluated. E.g. if c is a 1xn vector of weights, then for n images, +% using the (dmtx) data-matrix version, the weighted sum can be +% computed using: +% Q = spm_imcalc_ui({},'test','c*X',{1},c) +% Here we've pre-specified the output filename, expression and passed +% the vector c as an additional variable (you'll be prompted to select +% the n images). +% +%_______________________________________________________________________ +% @(#)spm_imcalc_ui.m 2.7 John Ashburner, Andrew Holmes 02/09/05 + +%-GUI setup +%----------------------------------------------------------------------- +SCCSid = '2.7'; +[Finter,Fgraph,CmdLine] = spm('FnUIsetup','ImCalc',0); +SPMid = spm('FnBanner',mfilename,SCCSid); +spm_help('!ContextHelp',[mfilename,'.m']) + +%-Condition arguments +%----------------------------------------------------------------------- +if nargin<4, flags={}; end +if nargin<3, f=''; end +if nargin<2, Q=''; end +if nargin<1, P={}; end + +if isempty(P), P = spm_get(Inf,'IMAGE',{'Select images to work on'}); end +if isempty(P), error('no input images specified'), end +if isempty(Q), Q = spm_input('Output filename',1,'s'); end +if isempty(f), f = spm_input('Evaluated Function',2,'s'); end + +if length(flags)<4, hold=[]; else, hold=flags{4}; end +if isempty(hold), hold=0; end +if length(flags)<3, type=[]; else, type=flags{3}; end +if isempty(type), type=4; end, if ischar(type), type=spm_type(type); end +if length(flags)<2, mask=[]; else, mask=flags{2}; end +if isempty(mask), mask=0; end +if length(flags)<1, dmtx=[]; else, dmtx=flags{1}; end +if isempty(dmtx), dmtx=0; end + +spm('FigName','ImCalc: working',Finter,CmdLine); +spm('Pointer','Watch') + + +%-Map input files +%----------------------------------------------------------------------- +Vi = spm_vol(char(P)); +if isempty(Vi), error('no input images specified'), end + +%-Check for consistency of image dimensions and orientation / voxel size +%----------------------------------------------------------------------- +if length(Vi)>1 & any(any(diff(cat(1,Vi.dim),1,1),1)&[1,1,1,0]) + warning(['images don''t all have same dimensions',... + ' - using those of 1st image']), end +if any(any(any(diff(cat(3,Vi.mat),1,3),3))) + warning(['images don''t all have same orientation & voxel size',... + ' - using 1st image']), end + + +%-Work out filename for output image +%------------------------------------------------------------------ +Qdir = spm_str_manip(Q,'Hv'); +Qfil = [spm_str_manip(Q,'stv'),'.img']; +if ~exist(Qdir,'dir') + warning('Invalid directory: writing to current directory') + Qdir = '.'; +end +Q = spm_get('CPath',Qfil,Qdir); + +Vo = struct( 'fname', Q,... + 'dim', [Vi(1).dim(1:3),type],... + 'mat', Vi(1).mat,... + 'descrip', 'spm - algebra'); + + +%-Call spm_imcalc to handle computations +%------------------------------------------------------------------ +% args = {{dmtx,mask,hold},varargin{:}}; +% Vo = spm_imcalc(Vi,Vo,f,args{:}); +% +% KND: the way spm_imcalc_ui calls spm_imcalc does not preserve inputname! +% +Xtra_vars = ''; +for i=5:nargin + eval([inputname(i),' = varargin{i-4};']) + Xtra_vars = [Xtra_vars ',' inputname(i) ]; +end +Vo=eval(['spm_imcalc(Vi,Vo,f,{dmtx,mask,hold}' Xtra_vars ')']); + + +%-End +%------------------------------------------------------------------ +spm('Pointer'); +spm('FigName','ImCalc: done',Finter,CmdLine); diff --git a/spm/spm2/spm_list_fullfiles.m b/spm/spm2/spm_list_fullfiles.m new file mode 100644 index 0000000..13445fe --- /dev/null +++ b/spm/spm2/spm_list_fullfiles.m @@ -0,0 +1,33 @@ +function [Files]= spm_list_fullfiles(Dirs,Filter) +% spm_list_fullfiles() - List full files (including path) +% FORMAT [Files] = spm_list_fullfiles(Dirs [,Filter]) +% Dirs - directories to list (possibly more than one). +% Default: current directory ('.') +% Filter - e.g. '*.img' (default) +% Files - full filenames including paths +% Dirs - directories +%_______________________________________________________________________ +% +% See also: spm_list_files.m +%_______________________________________________________________________ +% @(#)spm_list_fullfiles.m 1.0 Karim N'Diaye 06/12/07 +if nargin<2 + Filter='*.img'; + if nargin<1 + Dirs={cd}; + end +end +if ischar(Dirs) + Dirs=cellstr(Dirs); +end +Files=[]; +for i=1:length(Dirs) + d=cd; + cd(Dirs{i}) + Dirs{i}=cd; + cd(d); + [f,d] = spm_list_files(Dirs{i},Filter); + f=[repmat([Dirs{i}, filesep], size(f,1), 1) f]; + Files=[Files; f]; +end + diff --git a/spm/spm2/spm_mask2.m b/spm/spm2/spm_mask2.m new file mode 100644 index 0000000..06add0a --- /dev/null +++ b/spm/spm2/spm_mask2.m @@ -0,0 +1,132 @@ +function spm_mask(P1,P2, thresh2) +% Masks Images. +% FORMAT spm_mask(P1,P2, thresh2) +% P1 - matrix of input image filenames from which +% to compute the mask. +% P2 - matrix of input image filenames on which +% to apply the mask. +% thresh2 - optional threshold(s) for defining the mask. If +% * thresh2=[x] keeps values ABOVE x (same as spm_mask) +% * thresh2=[x y] keeps masking values BETWEEN x and y (included) +% * thresh2=[x x] keeps masking voxels EXACTLY equal to x +% * thresh2={x [y z] t} keeps values matching x, t, OR falling +% between y and z (inclusive masking) +% +% The masked images are prepended with the prefix `m'. +% +% If any voxel in the series of images is zero (for data types without +% a floating point representation) or does not have a finite value (for +% floating point and double precision images), then that voxel is set to +% NaN or zero in all the images. +% +% If a list of masking files is passed, then if necessary, the threshold is +% expanded to match the length of this list. The resulting masking is then +% the INTERSECTION of all the (threshold-dependent) masks. +% +% Images sampled in different orientations and positions can be passed +% to the routine. Providing the `.mat' files are correct, then these +% should be handled appropriately. +%_______________________________________________________________________ +% @(#)spm_mask.m 2.12 John Ashburner 03/02/03 + +if nargin==0, + P1=spm_get(Inf,'IMAGE','Images to compute mask from'); + P2=spm_get(Inf,'IMAGE','Images to apply mask to'); +end; +if nargin==1, + P2 = P1; +end; +thresh={0}; +if nargin==3, + thresh=thresh2; + if not(iscell(thresh)) && isnumeric(thresh) + % makes it backward compatible with spm_mask + if size(thresh,2) == 1; thresh=[thresh repmat(Inf,size(thresh,1),1)]; end + thresh=mat2cell(mat2cell(thresh, ones(size(thresh,1),1),2 ), size(thresh,1),1); + elseif not(iscell(thresh{1})) + thresh={thresh}; + end +end; + +V1=spm_vol(P1); +V2=spm_vol(P2); + +m1=prod(size(V1)); +m2=prod(size(V2)); + +if size(thresh,1)==1, + thresh = repmat(thresh,m1,1); +end +if size(P1,1) ~= size(thresh,1), + error('threshold input is wrong size.'); +end; + + +% Create headers +VO=V2; +for i=1:m2, + [pth,nm,xt,vr] = fileparts(deblank(VO(i).fname)); + %VO(i).fname = fullfile(pth,['m' nm xt vr]); + VO(i).fname = ['m' nm '.img']; + VO(i).descrip = 'Masked'; + VO(i).mat = VO(1).mat; + VO(i).dim(1:3) = VO(1).dim(1:3); +end; +VO = spm_create_vol(VO); +M = VO(1).mat; +dim = VO(1).dim(1:3); + +spm_progress_bar('Init',VO(1).dim(3),'Masking','planes completed') +for j=1:dim(3), + + msk = zeros(dim(1:2)); + Mi = spm_matrix([0 0 j]); + + % Load slice j from all images + for i=1:m1 + M1 = M\V1(i).mat\Mi; + %if sum((M1(:)-Mi(:)).^2=thresh{i}{v}(1) & img<=thresh{i}{v}(2)) & finite(img)); + end + + end + end; + + end; + + msk = find(msk~=m1); + + % Write the images. + for i=1:m2, + M1 = M\V2(i).mat\Mi; + img = spm_slice_vol(V2(i),M1,dim(1:2),[1 0]); + img(msk) = NaN; + VO(i) = spm_write_plane(VO(i),img,j); +% if numel(msk)> spm_mat2hdr +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2008 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2008-09-18 Creation +% +% ----------------------------- Script History --------------------------------- + + +http://www.jiscmail.ac.uk/cgi-bin/webadmin?A2=ind00&L=SPM&P=R220047 + + + +if nargin<1 + error('No data!') +elseif nargin==1 || isnumeric(action) + varargin=[{action} varargin ]; + action='init'; +end + +try + varargout={eval(sprintf('action_%s(varargin{:});',action))}; +catch + eval(sprintf('action_%s(varargin{:});',action)); +end + + +function [ha]=action_init(data, varargin) +% ------------------------------------------------------------------------- +% Check OPTIONS and default values when optional arguments are not +% specified +Def_OPTIONS = struct(... + 'option1',[],... + ); +if nargin < 2 + OPTIONS = Def_OPTIONS; +else + if length(varargin)>1 + OPTIONS = cell2struct(varargin(2:2:end),varargin(1:2:end),2); + % struct(varargin{:}) bugs with something like {'string1' 'string2'} in the inputs! + else + OPTIONS = varargin{1}; + end + % Check field names of passed OPTIONS and fill missing ones with default values + DefFieldNames = fieldnames(Def_OPTIONS); + for k = 1:length(DefFieldNames) + if ~isfield(OPTIONS,DefFieldNames{k}) + OPTIONS = setfield(OPTIONS,DefFieldNames{k},getfield(Def_OPTIONS,DefFieldNames{k})); + end + end + clear DefFieldNames +end +clear Def_OPTIONS +% ------------------------------------------------------------------------- \ No newline at end of file diff --git a/spm/spm2/spm_mat_into_hdr.m b/spm/spm2/spm_mat_into_hdr.m new file mode 100644 index 0000000..792f50e --- /dev/null +++ b/spm/spm2/spm_mat_into_hdr.m @@ -0,0 +1,262 @@ +%SPM_MAT_INTO_HDR +% Based on: MAT_INTO_HDR + +function dsr=spm_mat_into_hdr(files) + +file_lst = dir(files); +file_lst = {file_lst.name}; +file1 = file_lst{1}; +[p n e]= fileparts(file1); + +for i=1:length(file_lst) + [p n e]= fileparts(file_lst{i}); + disp(['working on file ', num2str(i) ,' of ', num2str(length(file_lst)), ': ', n,e]); + process=1; + + if isequal(e,'.hdr') + mat=fullfile(p, [n,'.mat']); + hdr=file_lst{i}; + dsr=spm_read_hdr(hdr); + return + + if ~exist(mat,'file') + warning(['Cannot find file "',mat , '". File "', n, e, '" will not be processed.']); + process=0; + end + elseif isequal(e,'.mat') + hdr=fullfile(p, [n,'.hdr']); + mat=file_lst{i}; + + if ~exist(hdr,'file') + warning(['Can not find file "',hdr , '". File "', n, e, '" will not be processed.']); + process=0; + end + else + warning(['Input file must have .mat or .hdr extension. File "', n, e, '" will not be processed.']); + process=0; + end + + if process + load(mat); + R=M(1:3,1:3); + T=M(1:3,4); + T=R*ones(3,1)+T; + M(1:3,4)=T; + + [h filetype fileprefix machine]=load_nii_hdr(hdr); + h.hist.qform_code=0; + h.hist.sform_code=1; + h.hist.srow_x=M(1,:); + h.hist.srow_y=M(2,:); + h.hist.srow_z=M(3,:); + h.hist.magic='ni1'; + + fid = fopen(hdr,'w',machine); + save_nii_hdr(h,fid); + fclose(fid); + end +end + +return; % mat_into_hdr + + +function [ dsr ] = read_header(fid) + +% Original header structures +% struct dsr +% { +% struct header_key hk; /* 0 + 40 */ +% struct image_dimension dime; /* 40 + 108 */ +% struct data_history hist; /* 148 + 200 */ +% }; /* total= 348 bytes*/ + +dsr.hk = header_key(fid); +dsr.dime = image_dimension(fid); +dsr.hist = data_history(fid); + +% For Analyze data format +% +if ~strcmp(dsr.hist.magic, 'n+1') & ~strcmp(dsr.hist.magic, 'ni1') + dsr.hist.qform_code = 0; + dsr.hist.sform_code = 0; +end + +return % read_header + + + +%--------------------------------------------------------------------- +function [ hk ] = header_key(fid) + +fseek(fid,0,'bof'); + +% Original header structures +% struct header_key /* header key */ +% { /* off + size */ +% int sizeof_hdr /* 0 + 4 */ +% char data_type[10]; /* 4 + 10 */ +% char db_name[18]; /* 14 + 18 */ +% int extents; /* 32 + 4 */ +% short int session_error; /* 36 + 2 */ +% char regular; /* 38 + 1 */ +% char dim_info; % char hkey_un0; /* 39 + 1 */ +% }; /* total=40 bytes */ +% +% int sizeof_header Should be 348. +% char regular Must be 'r' to indicate that all images and +% volumes are the same size. + +hk.sizeof_hdr = fread(fid, 1,'int32')'; % should be 348! +hk.data_type = deblank(fread(fid,10,'*char')'); +hk.db_name = deblank(fread(fid,18,'*char')'); +hk.extents = fread(fid, 1,'int32')'; +hk.session_error = fread(fid, 1,'int16')'; +hk.regular = fread(fid, 1,'*char')'; +hk.dim_info = fread(fid, 1,'char')'; + +return % header_key + + +%--------------------------------------------------------------------- +function [ dime ] = image_dimension(fid) + +% Original header structures +% struct image_dimension +% { /* off + size */ +% short int dim[8]; /* 0 + 16 */ +% /* +% dim[0] Number of dimensions in database; usually 4. +% dim[1] Image X dimension; number of *pixels* in an image row. +% dim[2] Image Y dimension; number of *pixel rows* in slice. +% dim[3] Volume Z dimension; number of *slices* in a volume. +% dim[4] Time points; number of volumes in database +% */ +% float intent_p1; % char vox_units[4]; /* 16 + 4 */ +% float intent_p2; % char cal_units[8]; /* 20 + 4 */ +% float intent_p3; % char cal_units[8]; /* 24 + 4 */ +% short int intent_code; % short int unused1; /* 28 + 2 */ +% short int datatype; /* 30 + 2 */ +% short int bitpix; /* 32 + 2 */ +% short int slice_start; % short int dim_un0; /* 34 + 2 */ +% float pixdim[8]; /* 36 + 32 */ +% /* +% pixdim[] specifies the voxel dimensions: +% pixdim[1] - voxel width, mm +% pixdim[2] - voxel height, mm +% pixdim[3] - slice thickness, mm +% pixdim[4] - volume timing, in msec +% ..etc +% */ +% float vox_offset; /* 68 + 4 */ +% float scl_slope; % float roi_scale; /* 72 + 4 */ +% float scl_inter; % float funused1; /* 76 + 4 */ +% short slice_end; % float funused2; /* 80 + 2 */ +% char slice_code; % float funused2; /* 82 + 1 */ +% char xyzt_units; % float funused2; /* 83 + 1 */ +% float cal_max; /* 84 + 4 */ +% float cal_min; /* 88 + 4 */ +% float slice_duration; % int compressed; /* 92 + 4 */ +% float toffset; % int verified; /* 96 + 4 */ +% int glmax; /* 100 + 4 */ +% int glmin; /* 104 + 4 */ +% }; /* total=108 bytes */ + +dime.dim = fread(fid,8,'int16')'; +dime.intent_p1 = fread(fid,1,'float32')'; +dime.intent_p2 = fread(fid,1,'float32')'; +dime.intent_p3 = fread(fid,1,'float32')'; +dime.intent_code = fread(fid,1,'int16')'; +dime.datatype = fread(fid,1,'int16')'; +dime.bitpix = fread(fid,1,'int16')'; +dime.slice_start = fread(fid,1,'int16')'; +dime.pixdim = fread(fid,8,'float32')'; +dime.vox_offset = fread(fid,1,'float32')'; +dime.scl_slope = fread(fid,1,'float32')'; +dime.scl_inter = fread(fid,1,'float32')'; +dime.slice_end = fread(fid,1,'int16')'; +dime.slice_code = fread(fid,1,'char')'; +dime.xyzt_units = fread(fid,1,'char')'; +dime.cal_max = fread(fid,1,'float32')'; +dime.cal_min = fread(fid,1,'float32')'; +dime.slice_duration = fread(fid,1,'float32')'; +dime.toffset = fread(fid,1,'float32')'; +dime.glmax = fread(fid,1,'int32')'; +dime.glmin = fread(fid,1,'int32')'; + +return % image_dimension + + +%--------------------------------------------------------------------- +function [ hist ] = data_history(fid) + +% Original header structures +% struct data_history +% { /* off + size */ +% char descrip[80]; /* 0 + 80 */ +% char aux_file[24]; /* 80 + 24 */ +% short int qform_code; /* 104 + 2 */ +% short int sform_code; /* 106 + 2 */ +% float quatern_b; /* 108 + 4 */ +% float quatern_c; /* 112 + 4 */ +% float quatern_d; /* 116 + 4 */ +% float qoffset_x; /* 120 + 4 */ +% float qoffset_y; /* 124 + 4 */ +% float qoffset_z; /* 128 + 4 */ +% float srow_x[4]; /* 132 + 16 */ +% float srow_y[4]; /* 148 + 16 */ +% float srow_z[4]; /* 164 + 16 */ +% char intent_name[16]; /* 180 + 16 */ +% char magic[4]; % int smin; /* 196 + 4 */ +% }; /* total=200 bytes */ + +hist.descrip = deblank(fread(fid,80,'*char')'); +hist.aux_file = deblank(fread(fid,24,'*char')'); +hist.qform_code = fread(fid,1,'int16')'; +hist.sform_code = fread(fid,1,'int16')'; +hist.quatern_b = fread(fid,1,'float32')'; +hist.quatern_c = fread(fid,1,'float32')'; +hist.quatern_d = fread(fid,1,'float32')'; +hist.qoffset_x = fread(fid,1,'float32')'; +hist.qoffset_y = fread(fid,1,'float32')'; +hist.qoffset_z = fread(fid,1,'float32')'; +hist.srow_x = fread(fid,4,'float32')'; +hist.srow_y = fread(fid,4,'float32')'; +hist.srow_z = fread(fid,4,'float32')'; +hist.intent_name = deblank(fread(fid,16,'*char')'); +hist.magic = deblank(fread(fid,4,'*char')'); + +fseek(fid,253,'bof'); +hist.originator = fread(fid, 5,'int16')'; + +return % data_history + + + + + +% MAT_INTO_HDR The old versions of SPM (any version before SPM5) store +% an affine matrix of the SPM Reoriented image into a matlab file +% (.mat extension). The file name of this SPM matlab file is the +% same as the SPM Reoriented image file (.img/.hdr extension). +% +% This program will convert the ANALYZE 7.5 SPM Reoriented image +% file into NIfTI format, and integrate the affine matrix in the +% SPM matlab file into its header file (.hdr extension). +% +% WARNING: Before you run this program, please save the header +% file (.hdr extension) into another file name or into another +% folder location, because all header files (.hdr extension) +% will be overwritten after they are converted into NIfTI +% format. +% +% Usage: mat_into_hdr(filename); +% +% filename: file name(s) with .hdr or .mat file extension, like: +% '*.hdr', or '*.mat', or a single .hdr or .mat file. +% e.g. mat_into_hdr('T1.hdr') +% mat_into_hdr('*.mat') +% + +% - Jimmy Shen (pls@rotman-baycrest.on.ca) +% +%------------------------------------------------------------------------- \ No newline at end of file diff --git a/spm/spm2/spm_mip.m b/spm/spm2/spm_mip.m new file mode 100644 index 0000000..6d05653 --- /dev/null +++ b/spm/spm2/spm_mip.m @@ -0,0 +1,77 @@ +function spm_mip(Z,XYZ,M,DIM) +% SPM maximum intensity projection +% FORMAT spm_mip(Z,XYZ,M,DIM); +% Z - {1 x ?} vector point list of SPM values for MIP +% XYZ - {3 x ?} matrix of coordinates of points (Talairach coordinates) +% M - voxels - > mm matrix +% DIM - image dimensions {voxels} +%_______________________________________________________________________ +% +% If the data are 2 dimensional [DIM(3) = 1] the projection is simply an +% image, otherwise: +% +% spm_mip creates and displays a maximum intensity projection of a point +% list of voxel values (Z) and their location (XYZ) in three orthogonal +% views of the brain. It is assumed voxel locations conform to the space +% defined in the atlas of Talairach and Tournoux (1988). +% +% This routine loads a mip putline from MIP.mat. This is an image with +% contours and grids defining the space of Talairach & Tournoux (1988). +% mip95 corresponds to the Talairach atlas, mip96 to the MNI templates. +% The outline and grid are superimposed at intensity defaults.grid, +% defaulting to 0.6. +% +% A default colormap of 64 levels is assumed. The pointlist image is +% scaled to fit in the interval [0.25,1]*64 for display. Flat images +% are scaled to 1*64. +% +%_______________________________________________________________________ +% @(#)spm_mip.m 2.7 Karl Friston et al. 02/07/31 + + +%-Get GRID value +%----------------------------------------------------------------------- +global defaults +if ~isempty(defaults) & isfield(defaults,'grid'), + GRID = defaults.grid; +else, + GRID = 0.6; +end; + +%-Scale & offset point list values to fit in [0.25,1] +%----------------------------------------------------------------------- +Z = Z(:)'; +Z = Z - min(Z); +mZ = max(Z); +if mZ %-Scale + Z = (1 + 3*Z/mZ)/4; +else %-Image is flat: set to ones: + Z = ones(1,length(Z)); +end + +%-Single slice case +%======================================================================= +if DIM(3) == 1, + VOX = sqrt(sum(M(1:3,1:3).^2)); + XYZ = round(M\[XYZ; ones(1,size(XYZ,2))]); + mip = full(sparse(XYZ(1,:),XYZ(2,:),Z,DIM(1),DIM(2))); + image([1 DIM(1)]*VOX(1),[1 DIM(2)]*VOX(2),(1-mip'/max(mip(:)))*64); + axis xy image; + set(gca,'FontSize',8,'TickDir','in','XTick',[],'YTick',[]) + %xlabel('x'), ylabel('y') + return +end + +%-3d case +%======================================================================= +%-Load mip and create maximum intensity projection +%----------------------------------------------------------------------- +load MIP +mip = mip96*GRID; +c = [0 0 0 ; 0 0 1 ; 0 1 0 ; 0 1 1 + 1 0 0 ; 1 0 1 ; 1 1 0 ; 1 1 1] -0.5; +c = (M(1:3,1:3)*c')'; +dim = [(max(c)-min(c)) size(mip)]; +d = spm_project(Z,round(XYZ),dim); +mip = max(d,mip); +image(rot90((1 - mip)*64)); axis image; axis off; diff --git a/spm/spm2/spm_mip_ui.m b/spm/spm2/spm_mip_ui.m new file mode 100644 index 0000000..c008b96 --- /dev/null +++ b/spm/spm2/spm_mip_ui.m @@ -0,0 +1,711 @@ +function varargout=spm_mip_ui(varargin) +% GUI for displaying MIPs with interactive pointers +% FORMAT hMIPax = spm_mip_ui(Z,XYZ,M,DIM,F) +% Z - {1 x ?} vector point list of SPM values for MIP +% XYZ - {3 x ?} matrix of coordinates of points (Talairach coordinates) +% M - voxels - > mm matrix +% DIM - image dimensions {voxels} +% F - Figure (or axes) to work in [Defaults to gcf] +% hMIPax - handle of MIP axes +% +% FORMAT xyz = spm_mip_ui('GetCoords',h) +% h - Handle of MIP axes, or figure containing MIP axis [default gcf] +% xyz - Current Talairach coordinates of cursor +% +% FORMAT [xyz,d] = spm_mip_ui('SetCoords',xyz,h,hC) +% xyz - (Input) {3 x 1} vector of desired Talairach coordinates +% h - Handle of MIP axes, or figure containing MIP axis [default gcf] +% hC - Handle of calling object, if used as a callback. [Default 0] +% xyz - (Output) {3 x 1} vector of voxel centre nearest desired xyz co-ords +% d - Euclidian distance from desired co-ords & nearest voxel +%_______________________________________________________________________ +% +% spm_mip_ui displays a maximum intensity projection (using spm_mip) +% with draggable cursors. +% +% See spm_mip.m for details of MIP construction, display, and the brain +% outlines used. +% ---------------- +% +% The cursor can be dragged to new locations in three ways: +% +% (1) Point & drop: Using the primary "select" mouse button, click on a +% cursor and drag the crosshair which appears to the desired location. +% On dropping, the cursors jump to the voxel centre nearest the drop +% site. +% +% (2) Dynamic drag & drop: Using the middle "extend" mouse button, click on +% a cursor and drag it about. The cursor follows the mouse, jumping to +% the voxel centre nearest the pointer. A dynamically updating +% information line appears above the MIP and gives the current +% co-ordinates. If the current voxel centre is in the XYZ pointlist, +% then the corresponding image value is also printed. +% +% (3) Magnetic drag & drop: As with "Dynamic drag & drop", except the cursors +% jump to the voxel centre in the pointlist nearest to the cursor. Use +% the right "alt" mouse button for "magnetic drag & drop". +% +% In addition a ContextMenu is provided, giving the option to jump the +% cursors to the nearest suprathreshold voxel, the nearest local +% maxima, or to the global maxima. (Right click on the MIP to bring up +% the ContextMenu.) A message in the MatLab command window describes the +% jump. +% +% ---------------- +% +% The current cursor position (constrained to lie on a voxel) can be +% obtained by xyz=spm_mip_ui('GetCoords',hMIPax), and set with +% xyz=spm_mip_ui('SetCoords',xyz,hMIPax), where hMIPax is the handle of +% the MIP axes, or of the figure containing a single MIP [default gcf]. +% The latter rounds xyz to the nearest voxel center, returning the +% result. +% +% spm_mip_ui handles all the callbacks required for moving the cursors, and +% is "registry" enabled (See spm_XYZreg.m). Programmers help is below in the +% main body of the function. +% +%_______________________________________________________________________ +% @(#)spm_mip_ui.m 2.12 Andrew Holmes 00/02/21 + +%======================================================================= +% - FORMAT specifications for embedded CallBack functions +%======================================================================= +%( This is a multi function function, the first argument is an action ) +%( string, specifying the particular action function to take. ) +% +% FORMAT hMIPax = spm_mip_ui(Z,XYZ,V,F) +% [ShortCut] Defaults to hMIPax=spm_mip_ui('Display',Z,XYZ,V,F) +% +% FORMAT hMIPax = spm_mip_ui('Display',Z,XYZ,M,DIM,F) +% Displays the MIP and sets up cursors +% Z - {1 x ?} vector point list of SPM values for MIP +% XYZ - {3 x ?} matrix of coordinates of points (Talairach coordinates) +% M - voxels - > mm matrix +% DIM - image dimensions {voxels} +% F - Handle of figure (or axes) to work in [Defaults to gcf] +% hMIPax - handle of MIP axes +% +% FORMAT xyz = spm_mip_ui('GetCoords',h) +% Returns coordinates of current cursor position +% h - Handle of MIP axes [defaults to spm_mip_ui('FindMIPax')] +% xyz - Current Talairach coordinates of cursor +% +% FORMAT [xyz,d] = spm_mip_ui('SetCoords',xyz,h,hC) +% Sets cursor position +% xyz - (Input) {3 x 1} vector of desired Talairach coordinates +% h - Handle of MIP axes [defaults to spm_mip_ui('FindMIPax')] +% hC - Handle of calling object, if used as a callback. [Default 0] +% xyz - (Output) {3 x 1} vector of voxel centre nearest desired xyz co-ords +% d - Euclidian distance from desired co-ords & nearest voxel +% +% FORMAT spm_mip_ui('PosnMarkerPoints',xyz,h,r) +% Utility routine: Positions cursor markers +% xyz - {3 x 1} vector of Talairach coordinates for cursor +% h - Handle of MIP axes [defaults to spm_mip_ui('FindMIPax')] +% r - 'r' to move visible red cursors, 'g' to move invisible green cursors +% +% +% FORMAT [xyz,d] = spm_mip_ui('Jump',h,loc) +% Utility routine (CallBack of UIcontextMenu) to jump cursor +% h - Handle of MIP axes [defaults to spm_mip_ui('FindMIPax')] +% loc - String defining jump: 'dntmv' - don't move +% 'nrvox' - nearest suprathreshold voxel +% 'nrmax' - nearest local maxima +% 'glmax' - global maxima +% xyz - co-ordinates of voxel centre jumped to {3 x 1} vector +% d - (square) Euclidian distance jumped +% +% FORMAT spm_mip_ui('ShowGreens',xyz,h) +% Shows green secondary cursors at location xyz +% xyz - {3 x 1} vector of Talairach coordinates for cursor +% [Default UserData of gco] +% h - Handle of MIP axes, or figure containing MIP axis [default gcf] +% +% FORMAT spm_mip_ui('HideGreens',h) +% Hides green secondary marker points +% h - Handle of MIP axes, or figure containing MIP axis [default gcf] +% +% FORMAT hMIPax = spm_mip_ui('FindMIPax',h) +% Looks for / checks MIP axes 'Tag'ged 'hMIPax'... errors if no valid MIP axes +% h - Handle of MIP axes, or figure containing MIP axis [default gcf] +% hMIPax - Handle of valid MIP axis found (errors if multiple found) +% +% FORMAT spm_mip_ui('MoveStart') +% Utility routine: CallBack for starting cursor dragging +% This is the ButtonDownFcn for the cursor markers +% +% FORMAT spm_mip_ui('Move',DragType) +% Utility routine: Initiate cursor move +% DragType - 0 = Point & drop (no dragging) +% 1 = Drag'n'drop with dynamic coordinate/value updating +% 2 = Magnetic drag'n'drop with dynamic coordinate updating +% +% FORMAT spm_mip_ui('MoveEnd') +% Utility routine: End cursor move +%_______________________________________________________________________ + + +%-Condition arguments +%======================================================================= +if nargin==0 + error('Insufficient arguments') +elseif ~isstr(varargin{1}) + varargout={spm_mip_ui('Display',varargin{1:end})}; return +end + + +%-Axis offsets for 3d MIPs: +%======================================================================= +%-MIP pane dimensions and Talairach origin offsets +%-See spm_project.c for derivation +DXYZ = [182 218 182]; +CXYZ = [091 127 073]; +% DMIP = [DXYZ(2)+DXYZ(1), DXYZ(1)+DXYZ(3)]; +%-Coordinates of Talairach origin in multipane MIP image (Axes are 'ij' + rot90) +% Transverse: [Po(1), Po(2)] +% Saggital : [Po(1), Po(3)] +% Coronal : [Po(4), Po(3)] +% 4 voxel offsets in Y since using character '<' as a pointer. +Po(1) = CXYZ(2) -2; +Po(2) = DXYZ(3)+DXYZ(1) -CXYZ(1) +2; +Po(3) = DXYZ(3) -CXYZ(3) +2; +Po(4) = DXYZ(2) +CXYZ(1) -2; + + + +%======================================================================= +switch lower(varargin{1}), case 'display' +%======================================================================= +% hMIPax = spm_mip_ui('Display',Z,XYZ,M,DIM,F) +if nargin<5 + F = gcf; + hMIPax = []; +else + F = varargin{6}; + if isstr(F), F=spm_figure('FindWin',F); end + if ~ishandle(F), error('Invalid handle'), end + switch get(F,'Type'), case 'figure' + hMIPax = []; + case 'axes' + hMIPax = F; + F = get(hMIPax,'Parent'); + otherwise + error('F not a figure or axis handle') + end +end +if nargin<4, error('Insufficient arguments'), end +DIM = varargin{5}; +M = varargin{4}; +XYZ = varargin{3}; +Z = varargin{2}; + +xyz = spm_XYZreg('RoundCoords',[0;0;0],M,DIM); + + +%-Display MIP +%----------------------------------------------------------------------- +Funits = get(F,'Units'); +set(F,'Units','normalized') +if isempty(hMIPax) + hMIPax = axes('Position',[0.24 0.54 0.62 0.42],'Parent',F); +else + axes(hMIPax), cla reset +end + +%-NB: spm_mip's `image` uses a newplot, & screws stuff without the figure. +figure(F) +spm_mip(Z,XYZ,M,DIM); + +%-Canonicalise axis positioning & save +%----------------------------------------------------------------------- +%-We're assumming the MIP fills the MIP axes bounding box, which is only +% true if stretch-to-fit is on (which it isn't with `axis image`), or that +% the axis bounding box itself has the correct aspect ratio. +%-So, shrink axes to the correct AspectRatio. +% Get aspect ratio from MIP graphic in case 2D +AR = diff(get(hMIPax,'Xlim'))/diff(get(hMIPax,'Ylim')); +set(hMIPax,'Units','pixels') +MIPaxPos = get(hMIPax,'Position'); +if (MIPaxPos(3)/MIPaxPos(4) > AR), MIPaxPos(3) = MIPaxPos(4)*AR; + else, MIPaxPos(4) = MIPaxPos(3)./AR; end +set(hMIPax,'Position',MIPaxPos) +hMIPim = get(gca,'Children'); + +%-Print coordinates +%----------------------------------------------------------------------- +hMIPxyz = text(0,max(get(hMIPax,'YLim'))/2,... + {'{\bfSPM}{\itmip}',sprintf('[%g, %g, %g]',xyz(1:3))},... + 'Interpreter','TeX','FontName',spm_platform('font','times'),... + 'Color',[1,1,1]*.7,... + 'HorizontalAlignment','Center',... + 'VerticalAlignment','Bottom',... + 'Rotation',90,... + 'Tag','hMIPxyz',... + 'UserData',xyz); + +%-Create point markers +%----------------------------------------------------------------------- +if DIM(3) == 1 + %-2 dimensional data + %--------------------------------------------------------------- + hXr = text(xyz(1),xyz(2),'<','Color','r','Fontsize',16,... + 'Tag','hX1r',... + 'ButtonDownFcn','spm_mip_ui(''MoveStart'')'); + hXg = text(xyz(1),xyz(2),'<','Color','g','Fontsize',12,... + 'Tag','hX1g','Visible','off'); +else + %-Create point markers + %-------------------------------------------------------------- + hX1r = text(Po(1)+xyz(2),Po(2)+xyz(1),'<',... + 'Color','r','Fontsize',16,... + 'Tag','hX1r',... + 'ButtonDownFcn','spm_mip_ui(''MoveStart'')'); + hX2r = text(Po(1)+xyz(2),Po(3)-xyz(3),'<',...l + 'Color','r','Fontsize',16,... + 'Tag','hX2r',... + 'ButtonDownFcn','spm_mip_ui(''MoveStart'')'); + hX3r = text(Po(4)+xyz(1),Po(3)-xyz(3),'<',.... + 'Color','r','Fontsize',16,... + 'Tag','hX3r',... + 'ButtonDownFcn','spm_mip_ui(''MoveStart'')'); + hXr = [hX1r,hX2r,hX3r]; + + hX1g = text(Po(1),Po(2),'<','Color','g','Fontsize',12,... + 'Tag','hX1g','Visible','off'); + hX2g = text(Po(1),Po(3),'<','Color','g','Fontsize',12,... + 'Tag','hX2g','Visible','off'); + hX3g = text(Po(4),Po(3),'<','Color','g','Fontsize',12,... + 'Tag','hX3g','Visible','off'); + hXg = [hX1g,hX2g,hX3g]; +end + +%-Create UIContextMenu for marker jumping +%----------------------------------------------------------------------- +h = uicontextmenu('Tag','MIPconmen','UserData',hMIPax); +uimenu(h,'Label','MIP') +if isempty(XYZ), str='off'; else, str='on'; end +uimenu(h,'Separator','on','Label','goto nearest suprathreshold voxel',... + 'CallBack',['spm_mip_ui(''Jump'',',... + 'get(get(gcbo,''Parent''),''UserData''),''nrvox'');'],... + 'Interruptible','off','BusyAction','Cancel','Enable',str); +uimenu(h,'Separator','off','Label','goto nearest local maxima',... + 'CallBack',['spm_mip_ui(''Jump'',',... + 'get(get(gcbo,''Parent''),''UserData''),''nrmax'');'],... + 'Interruptible','off','BusyAction','Cancel','Enable',str); +% KND: +uimenu(h,'Separator','off','Label','goto maximum in a sphere',... + 'CallBack',['spm_mip_ui(''Jump'',',... + 'get(get(gcbo,''Parent''),''UserData''),''sphmax'');'],... + 'Interruptible','off','BusyAction','Cancel','Enable',str); +uimenu(h,'Separator','off','Label','goto global maxima',... + 'CallBack',['spm_mip_ui(''Jump'',',... + 'get(get(gcbo,''Parent''),''UserData''),''glmax'');'],... + 'Interruptible','off','BusyAction','Cancel','Enable',str); +uimenu(h,'Separator','on','Label','help',... + 'CallBack','spm_help(''spm_mip_ui'')',... + 'Interruptible','off','BusyAction','Cancel','Enable',str); +set(hMIPim,'UIContextMenu',h) + +%-Save handles and data +%----------------------------------------------------------------------- +set(hMIPax,'Tag','hMIPax','UserData',... + struct( 'hReg', [],... + 'hMIPxyz', hMIPxyz,... + 'XYZ', XYZ,... + 'Z', Z,... + 'M', M,... + 'DIM', DIM,... + 'hXr', hXr,... + 'hXg', hXg)) + +varargout = {hMIPax}; + + + +%======================================================================= +case 'getcoords' +%======================================================================= +% xyz = spm_mip_ui('GetCoords',h) +if nargin<2, h=spm_mip_ui('FindMIPax'); else h=varargin{2}; end +varargout = {get(getfield(get(h,'UserData'),'hMIPxyz') ,'UserData')}; + + + +%======================================================================= +case 'setcoords' +%======================================================================= +% [xyz,d] = spm_mip_ui('SetCoords',xyz,h,hC) +if nargin<4, hC=0; else, hC=varargin{4}; end +if nargin<3, h=spm_mip_ui('FindMIPax'); else, h=varargin{3}; end +if nargin<2, error('Set co-ords to what!'), else, xyz=varargin{2}; end + +%KND if many MIP windows... +if length(h)>1 + h=h(strmatch('Graphics',get(cell2mat(get(h, 'Parent')), 'Tag'))); + h=h(end); +end + +MD = get(h,'UserData'); + +%-Check validity of coords only when called without a caller handle +%----------------------------------------------------------------------- +if hC<=0 + [xyz,d] = spm_XYZreg('RoundCoords',xyz,MD.M,MD.DIM); + if d>0 & nargout<2, warning(sprintf(... + '%s: Co-ords rounded to neatest voxel center: Discrepancy %.2f',... + mfilename,d)), end +else + d = []; +end + +%-Move marker points, update internal cache in hMIPxyz +%----------------------------------------------------------------------- +spm_mip_ui('PosnMarkerPoints',xyz,h,'r'); +set(MD.hMIPxyz,'UserData',reshape(xyz(1:3),3,1)) +set(MD.hMIPxyz,'String',{'{\bfSPM}{\itmip}',sprintf('[%g, %g, %g]',xyz(1:3))}) + +%-Tell the registry, if we've not been called by the registry... +%----------------------------------------------------------------------- +if (~isempty(MD.hReg) & MD.hReg~=hC), spm_XYZreg('SetCoords',xyz,MD.hReg,h); end + +%-Return arguments +%----------------------------------------------------------------------- +varargout = {xyz,d}; + + + +%======================================================================= +case 'posnmarkerpoints' +%======================================================================= +% spm_mip_ui('PosnMarkerPoints',xyz,h,r) +if nargin<4, r='r'; else, r=varargin{4}; end +if ~any(strcmp(r,{'r','g'})), error('Invalid pointer colour spec'), end +if nargin<3, h=spm_mip_ui('FindMIPax'); else, h=varargin{3}; end +if nargin<2, xyz = spm_mip_ui('GetCoords',h); else, xyz = varargin{2}; end + +%-Get handles of marker points of appropriate colour from UserData of hMIPax +%----------------------------------------------------------------------- +hX = getfield(get(h,'UserData'),['hX',r]); + +%-Set marker points +%----------------------------------------------------------------------- +set(hX,'Units','Data') +if length(hX)==1 + tmp = get(varargin{3},'UserData'); + vx = sqrt(sum(tmp.M(1:3,1:3).^2)); + tmp = tmp.M\[xyz ; 1]; + tmp = tmp(1:2).*vx(1:2)'; + set(hX,'Position',[tmp(1), tmp(2), 1]) +else + set(hX(1),'Position',[ Po(1) + xyz(2), Po(2) + xyz(1), 0]) + set(hX(2),'Position',[ Po(1) + xyz(2), Po(3) - xyz(3), 0]) + set(hX(3),'Position',[ Po(4) + xyz(1), Po(3) - xyz(3), 0]) +end + + +%======================================================================= +case 'jump' +%======================================================================= +% [xyz,d] = spm_mip_ui('Jump',h,loc) +if nargin<3, loc='nrvox'; else, loc=varargin{3}; end +if nargin<2, h=spm_mip_ui('FindMIPax'); else, h=varargin{2}; end + +%-Get current location & MipData +%----------------------------------------------------------------------- +oxyz = spm_mip_ui('GetCoords',h); +MD = get(h,'UserData'); + + +%-Compute location to jump to +%----------------------------------------------------------------------- +if isempty(MD.XYZ), loc='dntmv'; end +switch lower(loc), case 'dntmv' + spm('alert!','No suprathreshold voxels to jump to!',mfilename,0); + varargout = {oxyz, 0}; + return +case 'nrvox' + str = 'nearest suprathreshold voxel'; + [xyz,i,d] = spm_XYZreg('NearestXYZ',oxyz,MD.XYZ); +case 'nrmax' + str = 'nearest local maxima'; + iM = inv(MD.M); + XYZvox = iM(1:3,:)*[MD.XYZ; ones(1,size(MD.XYZ,2))]; + [null,null,XYZvox,null] = spm_max(MD.Z,XYZvox); + XYZ = MD.M(1:3,:)*[XYZvox; ones(1,size(XYZvox,2))]; + [xyz,i,d] = spm_XYZreg('NearestXYZ',oxyz,XYZ); +case 'glmax' + str = 'global maxima'; + i = find(MD.Z==max(MD.Z)); + xyz = MD.XYZ(:,i); + d = sqrt(sum((oxyz-xyz).^2)); +case 'sphmax' + str = 'maxima in sphere'; + sph_r=inputdlg('Radius of the sphere?','Max in sphere',1,{'12'}); + if isempty(sph_r) + return + end + sph_r=str2num(sph_r{1}); + + iM = inv(MD.M); + XYZvox = iM(1:3,:)*[MD.XYZ; ones(1,size(MD.XYZ,2))]; + [d] = spm_XYZreg('Edist',oxyz,MD.XYZ); + i=find(d<=sph_r); + XYZvox = iM(1:3,:)*[MD.XYZ(:,i); ones(1,length(i))]; + + i_max = i(find(MD.Z(i)==max(MD.Z(i)))); + if ~isempty(i_max) + xyz = MD.XYZ(:,i_max); + d = sqrt(sum((oxyz-xyz).^2)); + else + xyz=NaN; + d = NaN; + end + +otherwise + warning('Unknown jumpmode') + varargout = {xyz,0}; + return +end + +%-Write jump report, jump, and return arguments +%----------------------------------------------------------------------- +fprintf(['\n\t%s:\tJumped %0.2fmm from [%3.0f, %3.0f, %3.0f],\n\t\t\t',... + 'to %s at [%3.0f, %3.0f, %3.0f]'],... + mfilename, d, oxyz, str, xyz) +try + fprintf(' -> voxel #[%d %d %d]', round(inv(MD.M)*[xyz; 1])); +end +fprintf('\n'); + +spm_mip_ui('SetCoords',xyz,h,h); +varargout = {xyz, d}; + + +%======================================================================= +case 'showgreens' +%======================================================================= +% spm_mip_ui('ShowGreens',xyz,h) +if nargin<3, h=get(0,'CurrentFigure'); else, h=varargin{3}; end +h = spm_mip_ui('FindMIPax',h); +if nargin<2, xyz=get(gcbo,'UserData'); else, xyz=varargin{2}; end + +spm_mip_ui('PosnMarkerPoints',xyz,h,'g') +set(getfield(get(h,'UserData'),'hXg'),'Visible','on') + +%-If called as a callback (xyz from gco), then set WindowButtonUpFcn +%----------------------------------------------------------------------- +if nargin<2 + set(gcbf,'WindowButtonUpFcn',[... + 'set(gcbf,''WindowButtonUpFcn'',''''),',... + 'spm_mip_ui(''HideGreens'',gcbf)']) +end + + + +%======================================================================= +case 'hidegreens' +%======================================================================= +% spm_mip_ui('HideGreens',h) +if nargin<2, h=get(0,'CurrentFigure'); else, h=varargin{2}; end +h = spm_mip_ui('FindMIPax',h); +set(getfield(get(h,'UserData'),'hXg'),'Visible','off') + + + +%======================================================================= +case 'findmipax' +%======================================================================= +% hMIPax = spm_mip_ui('FindMIPax',h) +% Checks / finds hMIPax handles +%-**** h is handle of hMIPax, or figure containing MIP (default gcf) +if nargin<2, h=get(0,'CurrentFigure'); else, h=varargin{2}; end +if isstr(h), h=spm_figure('FindWin',h); end +if ~ishandle(h), error('invalid handle'), end +if ~strcmp(get(h,'Tag'),'hMIPax'), h=findobj(h,'Tag','hMIPax'); end +if isempty(h), error('MIP axes not found'), end +if length(h)>1, error('Multiple MIPs in this figure'), end +varargout = {h}; + + + +%======================================================================= +case 'movestart' +%======================================================================= +% spm_mip_ui('MoveStart') +[cO,cF] = gcbo; +hMIPax = get(cO,'Parent'); +MD = get(hMIPax,'UserData'); + +%-Store useful quantities in UserData of gcbo, the object to be dragged +%----------------------------------------------------------------------- +set(hMIPax,'Units','Pixels') +set(cO,'UserData',struct(... + 'hReg', MD.hReg,... + 'xyz', spm_mip_ui('GetCoords',hMIPax),... + 'MIPaxPos', get(hMIPax,'Position')*[1,0;0,1;0,0;0,0],... + 'hMIPxyz', MD.hMIPxyz,... + 'M', MD.M,... + 'DIM', MD.DIM,... + 'hX', MD.hXr)) + +%-Initiate dragging +%----------------------------------------------------------------------- +if strcmp(get(cF,'SelectionType'),'normal') | isempty(MD.XYZ) + %-Set Figure callbacks for drop but no drag (DragType 0) + %--------------------------------------------------------------- + set(MD.hMIPxyz,'Visible','on','String',... + {'{\bfSPM}{\itmip}','\itPoint & drop...'}) + set(cF,'WindowButtonUpFcn', 'spm_mip_ui(''Move'',0)',... + 'Interruptible','off') + set(cF,'Pointer','CrossHair') +elseif strcmp(get(cF,'SelectionType'),'extend') + %-Set Figure callbacks for drag'n'drop (DragType 1) + %--------------------------------------------------------------- + set(MD.hMIPxyz,'Visible','on','String',... + {'{\bfSPM}{\itmip}','\itDynamic drag & drop...'}) + set(cF,'WindowButtonMotionFcn','spm_mip_ui(''Move'',1)',... + 'Interruptible','off') + set(cF,'WindowButtonUpFcn', 'spm_mip_ui(''MoveEnd'')',... + 'Interruptible','off') + set(cF,'Pointer','Fleur') +elseif strcmp(get(cF,'SelectionType'),'alt') + %-Set Figure callbacks for drag'n'drop with co-ord updating (DragType 2) + %--------------------------------------------------------------- + set(MD.hMIPxyz,'Visible','on','String',... + {'{\bfSPM}{\itmip}','\itMagnetic drag & drop...'}) + set(cF,'WindowButtonMotionFcn','spm_mip_ui(''Move'',2)',... + 'Interruptible','off') + set(cF,'WindowButtonUpFcn', 'spm_mip_ui(''MoveEnd'')',... + 'Interruptible','off') + set(cF,'Pointer','Fleur') +end + + + +%======================================================================= +case 'move' +%======================================================================= +% spm_mip_ui('Move',DragType) +if nargin<2, DragType = 2; else, DragType = varargin{2}; end +cF = gcbf; +cO = gco(cF); + +%-Get useful data from UserData of gcbo, the object to be dragged +%----------------------------------------------------------------------- +MS = get(cO,'UserData'); +b2d = MS.DIM(3) == 1; + +%-Work out where we are moving to - Use HandleGraphics to give positon +%----------------------------------------------------------------------- +set(cF,'Units','pixels') +d = get(cF,'CurrentPoint') - MS.MIPaxPos; +set(cO,'Units','pixels') +set(cO,'Position',d) +set(cO,'Units','data') +d = get(cO,'Position'); + +%-Work out xyz, depending on which view is being manipulated +%----------------------------------------------------------------------- +sMarker = get(cO,'Tag'); +if strcmp(sMarker,'hX1r') + if b2d + vx = sqrt(sum(MS.M(1:3,1:3).^2)); + tmp = [d(1)/vx(1) d(2)/vx(2) 1]'; + xyz = MS.M(1:3,:)*[tmp' 1]'; + else + xyz = [d(2) - Po(2); d(1) - Po(1); MS.xyz(3)]; + end +elseif strcmp(sMarker,'hX2r') + xyz = [MS.xyz(1); d(1) - Po(1); Po(3) - d(2)]; +elseif strcmp(sMarker,'hX3r') + xyz = [d(1) - Po(4); MS.xyz(2); Po(3) - d(2)]; +else + error('Can''t work out which marker point') +end + +%-Round coordinates according to DragType & set in hMIPxyz's UserData +%----------------------------------------------------------------------- +if DragType==0 + xyz = spm_XYZreg('RoundCoords',xyz,MS.M,MS.DIM); +elseif DragType==1 + xyz = spm_XYZreg('RoundCoords',xyz,MS.M,MS.DIM); + hMIPax = get(cO,'Parent'); + MD = get(hMIPax,'UserData'); + i = spm_XYZreg('FindXYZ',xyz,MD.XYZ); +elseif DragType==2 + hMIPax = get(cO,'Parent'); + MD = get(hMIPax,'UserData'); + [xyz,i,d] = spm_XYZreg('NearestXYZ',xyz,MD.XYZ); +end +set(MS.hMIPxyz,'UserData',xyz) + +%-Move marker points +%----------------------------------------------------------------------- +% spm_mip_ui('PosnMarkerPoints',xyz,hMIPax,'r') +set(MS.hX,'Units','Data') +if b2d + vx = sqrt(sum(MS.M(1:3,1:3).^2)); + tmp = MS.M\[xyz ; 1]; + tmp = tmp(1:2).*vx(1:2)'; + set(MS.hX,'Position',[tmp(1), tmp(2), 1],'Visible','on') +else + set(MS.hX(1),'Position',[ Po(1) + xyz(2), Po(2) + xyz(1), 0]) + set(MS.hX(2),'Position',[ Po(1) + xyz(2), Po(3) - xyz(3), 0]) + set(MS.hX(3),'Position',[ Po(4) + xyz(1), Po(3) - xyz(3), 0]) +end + + +%-Update dynamic co-ordinate strings (if appropriate DragType) +%----------------------------------------------------------------------- +if DragType==0 + spm_mip_ui('MoveEnd') +elseif DragType==1 + if isempty(i) + str = {'{\bfSPM}{\itmip}',sprintf('[%g, %g, %g]',xyz)}; + else + str = {'{\bfSPM}{\itmip}: ',... + sprintf('[%g, %g, %g]: %.4f',xyz,MD.Z(i))}; + end + set(MD.hMIPxyz,'String',str) +elseif DragType==2 + set(MD.hMIPxyz,'String',... + {'{\bfSPM}{\itmip}',sprintf('[%g, %g, %g]: %.4f',xyz,MD.Z(i))}) +else + error('Illegal DragType') +end + + + +%======================================================================= +case 'moveend' +%======================================================================= +% spm_mip_ui('MoveEnd') +cF = gcbf; +cO = gco(cF); +hMIPax = get(cO,'Parent'); +MS = get(cO,'UserData'); + +%-Reset WindowButton functions, pointer & SPMmip info-string +%----------------------------------------------------------------------- +set(gcbf,'WindowButtonMotionFcn',' ') +set(gcbf,'WindowButtonUpFcn',' ') +set(gcbf,'Pointer','arrow') +set(MS.hMIPxyz,'String',... + {'{\bfSPM}{\itmip}',sprintf('[%g, %g, %g]',get(MS.hMIPxyz,'UserData'))}) + +%-Set coordinates after drag'n'drop, tell registry +%----------------------------------------------------------------------- +% don't need to set internal coordinates 'cos 'move' does that +if ~isempty(MS.hReg) + spm_XYZreg('SetCoords',get(MS.hMIPxyz,'UserData'),MS.hReg,hMIPax); +end + + + +%======================================================================= +otherwise +%======================================================================= +error('Unknown action string') + +%======================================================================= +end diff --git a/spm/spm2/spm_move_spm.m b/spm/spm2/spm_move_spm.m new file mode 100644 index 0000000..dde3c9e --- /dev/null +++ b/spm/spm2/spm_move_spm.m @@ -0,0 +1,42 @@ +function [SPM]=spm_move_spm +[f,p]=uigetfile('SPM.mat', 'SPM file?') +load(fullfile(p,f)); +swd = SPM.swd; +[d2,f,ext] = fileparts(SPM.xY.VY(1).fname); +if ~exist(d2, 'dir') + d2=cd; +end +[d2]=uigetdir(d2,'New directory of functional data') +for i=1:size(SPM.xY.VY,1) + [d0,f,ext] = fileparts(SPM.xY.VY(i).fname); + if isempty(ext) + if ~exist(fullfile(d2,[f,'.img']),'file') + ext='.img'; + errordlg(sprintf('Data (%s.img) are not in this directory', [f ext])); + return + elseif ~exist(fullfile(d2,[f,'.hdr']),'file') + ext='.hdr'; + errordlg(sprintf('Data (%s.hdr) are not in this directory', [f ext])); + return + end + else + if ~exist(fullfile(d2,[f,ext]),'file') + errordlg('Data (%s) are not in this directory', [f,ext]); + return + end + end +end +% +% if ~exist(fullfile(p,'SPM.mat.bak')) +% copyfile(fullfile(p,'SPM.mat'), fullfile(p,'SPM.mat.bak')) +% end + +for i=1:size(SPM.xY.VY,1) + [d0,f,ext] = fileparts(SPM.xY.VY(i).fname); + if isempty(ext) + ext='.img'; + end + SPM.xY.VY(i).fname=fullfile(d2,[f, ext]); +end + +msgbox('You may now save your SPM data by typing in: ''save SPM.mat SPM'''); diff --git a/spm/spm2/spm_peristimulus_sampling.m b/spm/spm2/spm_peristimulus_sampling.m new file mode 100644 index 0000000..8b9d84b --- /dev/null +++ b/spm/spm2/spm_peristimulus_sampling.m @@ -0,0 +1,51 @@ +function [x,h]=spm_pst_smp(RT,onsets,NV,ST,nslices) +% check sampling of the HRF +try + addpath('D:\mtoolbox\spm2\') +end +if nargin < 1 + RT=2.5; +end +if nargin < 2 + onsets=cumsum(2.5+rand(1,10)/10) +end +if nargin < 3 + NV=(onsets(end)+32)/2.5; +end +if nargin < 4 + ST=1; +end +if ST >= RT + error('acquisition time can''t be longer than RT!!') +end +if isvector(onsets) + onsets=onsets(:)'; +end +figure +ha=axes; +line([0 32], [0 0], 'color','k'); +hold on +t=[0:32/.15]*.15; +y=normalize(spm_hrf(.15),inf); +plot(t,y) +set(ha,'Xlim', [0 32]) +% set(ha,'Xlim', [-RT*.1 RT*1.25], 'Ylim', [-.2 1.2], 'box', 'on') +% rectangle('position',[ ST 0 RT-ST .25], 'FaceColor', .85*[1 1 1]); +% line([-RT 2*RT], [0 0], 'color','k'); +% x=mod(onsets,RT); +x=RT*[0:NV-1]'*ones(1,length(onsets)) - ones(NV,1)*(onsets) + ST; +% x=x(:); +% x(x>32)=[]; +% x(x<0)=[]; +x(x>32)=NaN; +x(x<0)=NaN; +for j=1:length(onsets) + h(j)=plot(x(:,j),interp1(t,y,x(:,j)),'.', 'color', rand(1,3)); +end +str = sprintf('TR = %0.2f secs ',RT); +if ST>0 + str=[str sprintf('(including silent time = %0.2f secs)',ST)]; +end +xlabel('time (secs)') +title(str) +return diff --git a/spm/spm2/spm_resss.m b/spm/spm2/spm_resss.m new file mode 100644 index 0000000..de049d6 --- /dev/null +++ b/spm/spm2/spm_resss.m @@ -0,0 +1,125 @@ +function Vo = spm_resss(Vi,Vo,R,flags) +% Create residual sum of squares image (ResSS) +% FORMAT Vo = spm_resss(Vi,Vo,R,flags) +% Vi - vector of mapped image volumes to work on (from spm_vol) +% Vo - handle structure for mapped output image volume +% R - residual forming matrix +% flags - 'm' for implicit zero masking +% Vo (output) - handle structure of output image volume after modifications +% for writing +% +% Note that spm_create_image needs to be called external to this function - +% the header is not created! +%_______________________________________________________________________ +% +% Residuals are computed as R*Y, where Y is the data vector read from +% images mapped as Vi. The residual sum of squares image (mapped as Vo) +% is written. +% +%----------------------------------------------------------------------- +% +% For a simple linear model Y = X*B * E, with design matrix X, +% (unknown) parameter vector(s) B, and data matrix Y, the least squares +% estimates of B are given by b = inv(X'*X)*X'*Y. If X is rank +% deficient, then the Moore-Penrose pseudoinverse may be used to obtain +% the least squares parameter estimates with the lowest L2 norm: b = +% pinv(X)*Y. +% +% The fitted values are then y = X*b = X*inv(X'*X)*X'*Y, (or +% y=X*pinv(X)*Y). Since the fitted values y are usually known as +% "y-hat", X*inv(X'*X)*X' is known as the "hat matrix" for this model, +% denoted H. +% +% The residuals for this fit (estimates of E) are e = Y - y. +% Substituting from the above, e = (I-H)*Y, where I is the identity +% matrix (see eye). (I-H) is called the residual forming matrix, +% denoted R. +% +% Geometrically, R is a projection matrix, projecting the data into the +% subspace orthogonal to the design space. +% +% ---------------- +% +% For temporally smoothed fMRI models with convolution matrix K, R is a +% little more complicated: +% K*Y = K*X * B + K*E +% KY = KX * B + KE +% ...a little working shows that hat matrix is H = KX*inv(KX'*KX)*KX' +% (or KX*pinv(KX)), where KX=K*X. The smoothed residuals KE (=K*E) are +% then given from the temporally smoothed data KY (=K*Y) by y=H*KY. +% Thus the residualising matrix for the temporally smoothed residuals +% from the temporally smoothed data is then (I-H). +% +% Usually the image time series is not temporally smoothed, in which +% case the hat and residualising matrices must incorporate the temporal +% smoothing. The hat matrix for the *raw* (unsmoothed) time series Y is +% H*K, and the corresponding residualising matrix is R=(K-H*K). +% In full, that's +% R = (K - KX*inv(KX'*KX)*KX'*K) +% or R = (K - KX*pinv(KX)*K) when using a pseudoinverse +% +%----------------------------------------------------------------------- +% +% This function can also be used when the b's are images. The residuals +% are then e = Y - X*b, so let Vi refer to the vector of images and +% parameter estimates ([Y;b]), and then R is ([eye(n),-X]), where n is +% the number of Y images. +% +%----------------------------------------------------------------------- +% +% Don't forget to either apply any image scaling (grand mean or +% proportional scaling global normalisation) to the image scalefactors, +% or to combine the global scaling factors in the residual forming +% matrix. +%_______________________________________________________________________ +% @(#)spm_resss.m 2.9 Andrew Holmes, John Ashburner 02/08/13 + + +%-Argument checks +%----------------------------------------------------------------------- +if nargin<4, flags=''; end, if isempty(flags), flags='-'; end +mask = any(flags=='m'); +if nargin<3, error('insufficient arguments'); end +ni = size(R,2); %-ni = #images +if ni~=prod(size(Vi)), error('incompatible dimensions'); end +if Vo.dim(4)~=16, error('only float output images supported'), end + +%-Image dimension, orientation and voxel size checks +%----------------------------------------------------------------------- +[samef msg] = spm_vol_check(Vi, Vo); +if ~samef, disp(char(msg)),error('Cannot use images'),end; + + +%======================================================================= +% - C O M P U T A T I O N +%======================================================================= +fprintf('%-14s%16s',['(',mfilename,')'],'...initialising') %-# + +Y = zeros([Vo.dim(1:2),ni]); %-PlaneStack data + +im = logical(zeros(ni,1)); +for j=1:ni, im(j)=~spm_type(Vi(j).dim(4),'NaNrep'); end %-Images without NaNrep + +%-Loop over planes computing ResSS +for p=1:Vo.dim(3) + fprintf('%s%16s',char(sprintf('\b')*ones(1,16)),... + sprintf('...plane %3d/%-3d',p,Vo.dim(3))) %-# + + M = spm_matrix([0 0 p]); %-Sampling matrix + + %-Read plane data + for j=1:ni, Y(:,:,j) = spm_slice_vol(Vi(j),M,Vi(j).dim(1:2),0); end + + %-Apply implicit zero mask for image types without a NaNrep + if mask, Y(Y(:,:,im)==0)=NaN; end + + e = R*reshape(Y,prod(Vi(1).dim(1:2)),ni)'; %-residuals as DataMtx + ss = reshape(sum(e.^2,1),Vi(1).dim(1:2)); %-ResSS plane + Vo = spm_write_plane(Vo,ss,p); %-Write plane +end + + +%-End +%----------------------------------------------------------------------- +fprintf('%s%30s\n',char(sprintf('\b')*ones(1,30)),... + sprintf('...written %s',spm_str_manip(Vo.fname,'t'))) %-# diff --git a/spm/spm2/spm_results_ui.m b/spm/spm2/spm_results_ui.m new file mode 100644 index 0000000..5749ffd --- /dev/null +++ b/spm/spm2/spm_results_ui.m @@ -0,0 +1,1053 @@ +function varargout = spm_results_ui(varargin) +% User interface for SPM/PPM results: Display and analysis of regional effects +% FORMAT [hReg,xSPM,SPM] = spm_results_ui +% +% hReg - handle of MIP XYZ registry object +% (see spm_XYZreg for details) +% xSPM - structure containing specific SPM, distribution & filtering details +% (see spm_getSPM.m for contents) +% SDM - SPM structure containing generic parameters +% (see spm_spm.m for contents... +% +% NB: Results section GUI CallBacks use these data structures by name, which +% therefore *must* be assigned to the correctly named variables. +%_______________________________________________________________________ +% +% The SPM results section is for the interactive exploration and +% characterisation of the results of a statistical analysis. +% +% The user is prompted to select a SPM{T} or SPM{F}, that is +% thresholded at user specified levels. The specification of the +% contrasts to use and the height and size thresholds are described in +% spm_getSPM.m. The resulting SPM is then displayed in the graphics +% window as a maximum intensity projection, alongside the design matrix +% and contrasts employed. +% +% The cursors in the MIP can be moved (dragged) to select a particular +% voxel. The three mouse buttons give different drag and drop behaviour: +% Button 1 - point & drop; Button 2 - "dynamic" drag & drop with +% co-ordinate & SPM value updating; Button 3 - "magnetic" drag & drop, +% where the cursor jumps to the nearest suprathreshold voxel in the +% MIP, and shows the value there. (See spm_mip_ui.m, the MIP GUI handling +% function for further details.) +% +% The design matrix and contrast pictures are "surfable": Click and +% drag over the images to report associated data. Clicking with +% different buttons produces different results. Double-clicking +% extracts the underlying data into the base workspace. +% See spm_DesRep for further details. +% +% The current voxel specifies the voxel, suprathreshold cluster, or +% orthogonal planes (planes passing through that voxel) for subsequent +% localised utilities. +% +% A control panel in the interactive window enables interactive +% exploration of the results. +% +% p-value buttons: +% (i) volume - Tabulates p-values and statistics for entire volume. +% - see spm_list.m +% (ii) cluster - Tabulates p-values and statistics for nearest cluster +% - Note that the cursor will jump to the nearest +% suprathreshold voxel, if it is not already at a +% location with suprathreshold statistic. +% - see spm_list.m +% (iii) voxel - (not implemented yet) +% - see spm_****.m +% +% p-values for VOI button: +% S.V.C - Small Volume Correction: +% Tabulates p-values corrected for a small specified +% volume of interest. (Tabulation by spm_list.m) +% - see spm_VOI.m +% +% Data extraction buttons: +% V.O.I. - Extracts Eigentimeseries for small volumes of interest. +% - Data can be adjusted or not. +% - If temporal filtering was specified (fMRI), then it is the +% filtered data that is returned. +% - Choose a VOI of radius 0 to extract the (filtered &) +% adjusted data for a single voxel. Note that this vector +% will be scaled to have a 2-norm of 1. (See spm_regions.m +% for further details.) +% - The plot button also returns fitted and adjusted +% (after any filtering) data for the voxel being plotted.) +% - Note that the cursor will jump to the nearest voxel for +% which raw data was saved. +% - see spm_regions.m +% +% Visualisation buttons: +% (i) plot - Graphs of adjusted and fitted activity against +% various ordinates. +% - Note that the cursor will jump to the nearest +% suprathreshold voxel, if it is not already at a +% location with suprathreshold statistic. +% - Additionally, returns fitted and adjusted data to the +% MatLab base workspace. +% - see spm_graph.m +% (ii) overlays - Popup menu: Overlays of filtered SPM on a structural image +% - slices - Slices of the thresholded statistic image overlaid +% on a secondary image chosen by the user. Three +% transverse slices are shown, being those at the +% level of the cursor in the z-axis and the two +% adjacent to it. - see spm_transverse.m +% - sections - Orthogonal sections of the thresholded statistic +% image overlaid on a secondary image chosen by the user. +% The sections are through the cursor position. +% - see spm_sections.m +% - render - Render blobs on previously extracted cortical surface +% - see spm_render.m +% (iii) write filtered - Write out thresholded SPM as image +% - see spm_write_filtered.m +% +% +% The current cursor location can be set by editing the co-ordinate +% widgets at the bottom of the interactive window. (Note that many of the +% results section facilities are "linked" and can update co-ordinates. E.g. +% clicking on the co-ordinates in a p-value listing jumps to that location.) +% +% Graphics appear in the bottom half of the graphics window, additional +% controls and questions appearing in the interactive window. +% +% ---------------- +% +% The MIP uses a template outline in Talairach space. Consequently for +% the results section to display properly the input images to the +% statistics section should either be in Talairach space (with the +% ORIGIN correctly specified), or the ORIGIN header fields should be +% set to the voxel coordinates of the anterior commissure in the input +% images. See spm_format.man ("Data Format" in the help facility) for +% further details of the Analyze image format used by SPM. +% +% Similarly, secondary images should be aligned with the input images +% used for the statistical analysis. In particular the ORIGIN must +% correspond to (0,0,0) in XYZ, the vector of locations. +% +% ---------------- +% +% In addition to setting up the results section, spm_results_ui.m sets +% up the results section GUI and services the CallBacks. FORMAT +% specifications for embedded CallBack functions are given in the main +% body of the code. +%_______________________________________________________________________ +% @(#)spm_results_ui.m 2.42 Karl Friston, Andrew Holmes 03/02/28 +SCCSid = '2.42'; + +%======================================================================= +% - FORMAT specifications for embedded CallBack functions +%======================================================================= +%( This is a multi function function, the first argument is an action ) +%( string, specifying the particular action function to take. ) +% +% spm_results_ui sets up and handles the SPM results graphical user +% unterface, initialising an XYZ registry (see spm_XYZreg.m) to co-ordinate +% locations between various location controls. +% +%_______________________________________________________________________ +% +% FORMAT hReg = spm_results_ui('SetupGUI',M,DIM,xSPM,Finter) +% Setup results GUI in Interactive window +% M - 4x4 transformation matrix relating voxel to "real" co-ordinates +% DIM - 3 vector of image X, Y & Z dimensions +% xSPM - structure containing xSPM. Required fields are: +% .Z - minimum of n Statistics {filtered on u and k} +% .XYZmm - location of voxels {mm} +% Finter - handle (or 'Tag') of Interactive window (default 'Interactive') +% hReg - handle of XYZ registry object +% +% FORMAT spm_results_ui('DrawButts',hReg,DIM,Finter,WS,FS) +% Draw GUI buttons +% hReg - handle of XYZ registry object +% DIM - 3 vector of image X, Y & Z dimensions +% Finter - handle of Interactive window +% WS - WinScale [Default spm('WinScale') ] +% FS - FontSizes [Default spm('FontSizes')] +% +% FORMAT hFxyz = spm_results_ui('DrawXYZgui',M,DIM,xSPM,xyz,Finter) +% Setup editable XYZ control widgets at foot of Interactive window +% M - 4x4 transformation matrix relating voxel to "real" co-ordinates +% DIM - 3 vector of image X, Y & Z dimensions +% xSPM - structure containing SPM. Required fields are: +% .Z - minimum of n Statistics {filtered on u and k} +% .XYZmm - location of voxels {mm} +% xyz - Initial xyz location {mm} +% Finter - handle of Interactive window +% hFxyz - handle of XYZ control - the frame containing the edit widgets +% +% FORMAT spm_results_ui('EdWidCB') +% Callback for editable XYZ control widgets +% +% FORMAT spm_results_ui('UpdateSPMval',hFxyz) +% FORMAT spm_results_ui('UpdateSPMval',UD) +% Updates SPM value string in Results GUI (using data from UserData of hFxyz) +% hFxyz - handle of frame enclosing widgets - the Tag object for this control +% UD - XYZ data structure (UserData of hFxyz). +% +% FORMAT xyz = spm_results_ui('GetCoords',hFxyz) +% Get current co-ordinates from editable XYZ control +% hFxyz - handle of frame enclosing widgets - the Tag object for this control +% xyz - current co-ordinates {mm} +% NB: When using the results section, should use XYZregistry to get/set location +% +% FORMAT [xyz,d] = spm_results_ui('SetCoords',xyz,hFxyz,hC) +% Set co-ordinates to XYZ widget +% xyz - (Input) desired co-ordinates {mm} +% hFxyz - handle of XYZ control - the frame containing the edit widgets +% hC - handle of calling object, if used as a callback. [Default 0] +% xyz - (Output) Desired co-ordinates are rounded to nearest voxel if hC +% is not specified, or is zero. Otherwise, caller is assummed to +% have checked verity of desired xyz co-ordinates. Output xyz returns +% co-ordinates actually set {mm}. +% d - Euclidean distance between desired and set co-ordinates. +% NB: When using the results section, should use XYZregistry to get/set location +% +% FORMAT hFxyz = spm_results_ui('FindXYZframe',h) +% Find/check XYZ edit widgets frame handle, 'Tag'ged 'hFxyz' +% h - handle of frame enclosing widgets, or containing figure [default gcf] +% If isstr(h), then uses spm_figure('FindWin',h) to locate named figures +% hFxyz - handle of confirmed XYZ editable widgets control +% Errors if hFxyz is not an XYZ widget control, or a figure containing +% a unique such control +% +% FORMAT spm_results_ui('PlotUi',hAx) +% GUI for adjusting plot attributes - Sets up controls just above results GUI +% hAx - handle of axes to work with +% +% FORMAT spm_results_ui('PlotUiCB') +% CallBack handler for Plot attribute GUI +% +% FORMAT Fgraph = spm_results_ui('Clear',F,mode) +% Clears results subpane of Graphics window, deleting all but semi-permanent +% results section stuff +% F - handle of Graphics window [Default spm_figure('FindWin','Graphics')] +% mode - 1 [default] - clear results subpane +% - 0 - clear results subpane and hide results stuff +% - 2 - clear, but respect 'NextPlot' 'add' axes +% (which is set by `hold on`) +% Fgraph - handle of Graphics window +% +% FORMAT hMP = spm_results_ui('LaunchMP',M,DIM,hReg,hBmp) +% Prototype callback handler for integrating MultiPlanar toolbox +% +% FORMAT spm_results_ui('Delete',h) +% deletes HandleGraphics objects, but only if they're valid, thus avoiding +% warning statements from MatLab! +%_______________________________________________________________________ + + +%-Condition arguments +%----------------------------------------------------------------------- +if nargin == 0, Action='SetUp'; else, Action=varargin{1}; end + + +%======================================================================= +switch lower(Action), case 'setup' %-Set up results +%======================================================================= + +%-Initialise +%----------------------------------------------------------------------- +SPMid = spm('FnBanner',mfilename,SCCSid); +[Finter,Fgraph,CmdLine] = spm('FnUIsetup','Stats: Results'); +FS = spm('FontSizes'); + +% clear satfig if it exists +%----------------------------------------------------------------------- +hSat = findobj('tag','Satellite'); +spm_figure('clear',hSat); + +%-Get thresholded xSPM data and parameters of design +%======================================================================= +[SPM,xSPM] = spm_getSPM; +M = SPM.xVol.M; +DIM = SPM.xVol.DIM; + +% ensure pwd = swd so that relative filenames are valid +%----------------------------------------------------------------------- +cd(SPM.swd) + +%-Setup Results User Interface; Display MIP, design matrix & parameters +%======================================================================= +spm('FigName',['SPM{',xSPM.STAT,'}: Results'],Finter,CmdLine); + + +%-Setup results GUI +%----------------------------------------------------------------------- +spm_figure('Clear',Finter) +hReg = spm_results_ui('SetupGUI',M,DIM,xSPM,Finter); + +%-Setup design interrogation menu +%----------------------------------------------------------------------- +hDesRepUI = spm_DesRep('DesRepUI',SPM); +figure(Finter) + +%-Setup Maximium intensity projection (MIP) & register +%----------------------------------------------------------------------- +hMIPax = axes('Parent',Fgraph,'Position',[0.05 0.60 0.55 0.36],'Visible','off'); +hMIPax = spm_mip_ui(xSPM.Z,xSPM.XYZmm,M,DIM,hMIPax); +spm_XYZreg('XReg',hReg,hMIPax,'spm_mip_ui'); +if xSPM.STAT == 'P' + str = xSPM.STATstr; +else + str = ['SPM\{',xSPM.STATstr,'\}']; +end +text(240,260,str,... + 'Interpreter','TeX',... + 'FontSize',FS(14),'Fontweight','Bold',... + 'Parent',hMIPax) + + +%-Print comparison title +%----------------------------------------------------------------------- +hTitAx = axes('Parent',Fgraph,... + 'Position',[0.02 0.95 0.96 0.02],... + 'Visible','off'); + +text(0.5,0,xSPM.title,'Parent',hTitAx,... + 'HorizontalAlignment','center',... + 'VerticalAlignment','baseline',... + 'FontWeight','Bold','FontSize',FS(14)) + + +%-Print SPMresults: Results directory & thresholding info +%----------------------------------------------------------------------- +hResAx = axes('Parent',Fgraph,... + 'Position',[0.05 0.55 0.45 0.05],... + 'DefaultTextVerticalAlignment','baseline',... + 'DefaultTextFontSize',FS(9),... + 'DefaultTextColor',[1,1,1]*.7,... + 'Units','points',... + 'Visible','off'); +AxPos = get(hResAx,'Position'); set(hResAx,'YLim',[0,AxPos(4)]) +h = text(0,24,'SPMresults:','Parent',hResAx,... + 'FontWeight','Bold','FontSize',FS(14)); +text(get(h,'Extent')*[0;0;1;0],24,spm_str_manip(SPM.swd,'a30'),'Parent',hResAx) +text(0,12,sprintf('Height threshold %c = %0.2f',xSPM.STAT,xSPM.u),'Parent',hResAx) +text(0,00,sprintf('Extent threshold k = %0.0f voxels',xSPM.k), 'Parent',hResAx) + + +%-Plot design matrix +%----------------------------------------------------------------------- +hDesMtx = axes('Parent',Fgraph,'Position',[0.65 0.55 0.25 0.25]); +hDesMtxIm = image((SPM.xX.nKX + 1)*32); +xlabel('Design matrix') +set(hDesMtxIm,'ButtonDownFcn','spm_DesRep(''SurfDesMtx_CB'')',... + 'UserData',struct(... + 'X', SPM.xX.xKXs.X,... + 'fnames', {reshape({SPM.xY.VY.fname},size(SPM.xY.VY))},... + 'Xnames', {SPM.xX.name})) + +%-Plot contrasts +%----------------------------------------------------------------------- +nPar = size(SPM.xX.X,2); +xx = [repmat([0:nPar-1],2,1);repmat([1:nPar],2,1)]; +nCon = length(xSPM.Ic); +xCon = SPM.xCon; +if nCon + dy = 0.15/max(nCon,2); + hConAx = axes('Position',[0.65 (0.80 + dy*.1) 0.25 dy*(nCon-.1)],... + 'Tag','ConGrphAx','Visible','off'); + title('contrast(s)') + htxt = get(hConAx,'title'); + set(htxt,'Visible','on','HandleVisibility','on') +end + +for ii = nCon:-1:1 + axes('Position',[0.65 (0.80 + dy*(nCon - ii +.1)) 0.25 dy*.9]) + if xCon(xSPM.Ic(ii)).STAT == 'T' & size(xCon(xSPM.Ic(ii)).c,2) == 1 + + %-Single vector contrast for SPM{t} - bar + %--------------------------------------------------------------- + yy = [zeros(1,nPar);repmat(xCon(xSPM.Ic(ii)).c',2,1);zeros(1,nPar)]; + h = patch(xx,yy,[1,1,1]*.5); + set(gca,'Tag','ConGrphAx',... + 'Box','off','TickDir','out',... + 'XTick',spm_DesRep('ScanTick',nPar,10) - 0.5,'XTickLabel','',... + 'XLim', [0,nPar],... + 'YTick',[-1,0,+1],'YTickLabel','',... + 'YLim',[min(xCon(xSPM.Ic(ii)).c),max(xCon(xSPM.Ic(ii)).c)] +... + [-1 +1] * max(abs(xCon(xSPM.Ic(ii)).c))/10 ) + + else + + %-F-contrast - image + %--------------------------------------------------------------- + h = image((xCon(xSPM.Ic(ii)).c'/max(abs(xCon(xSPM.Ic(ii)).c(:)))+1)*32); + set(gca,'Tag','ConGrphAx',... + 'Box','on','TickDir','out',... + 'XTick',spm_DesRep('ScanTick',nPar,10),'XTickLabel','',... + 'XLim', [0,nPar]+0.5,... + 'YTick',[0:size(SPM.xCon(xSPM.Ic(ii)).c,2)]+0.5,.... + 'YTickLabel','',... + 'YLim', [0,size(xCon(xSPM.Ic(ii)).c,2)]+0.5 ) + + end + ylabel(num2str(xSPM.Ic(ii))) + set(h,'ButtonDownFcn','spm_DesRep(''SurfCon_CB'')',... + 'UserData', struct( 'i', xSPM.Ic(ii),... + 'h', htxt,... + 'xCon', xCon(xSPM.Ic(ii)))) +end + + +%-Store handles of results section Graphics window objects +%----------------------------------------------------------------------- +H = get(Fgraph,'Children'); +H = findobj(H,'flat','HandleVisibility','on'); +H = findobj(H); +Hv = get(H,'Visible'); +set(hResAx,'Tag','PermRes','UserData',struct('H',H,'Hv',{Hv})) + + +%-Finished results setup +%----------------------------------------------------------------------- +varargout = {hReg,xSPM,SPM}; +spm('Pointer','Arrow') + + +%======================================================================= +case 'setupgui' %-Set up results section GUI +%======================================================================= +% hReg = spm_results_ui('SetupGUI',M,DIM,xSPM,Finter) +if nargin < 5, Finter='Interactive'; else, Finter = varargin{5}; end +if nargin < 4, error('Insufficient arguments'), end +M = varargin{2}; +DIM = varargin{3}; +Finter = spm_figure('GetWin',Finter); +WS = spm('WinScale'); +FS = spm('FontSizes'); + +%-Create frame for Results GUI objects +%----------------------------------------------------------------------- +hReg = uicontrol(Finter,'Style','Frame','Position',[001 001 400 190].*WS,... + 'BackgroundColor',spm('Colour')); +hFResUi = uicontrol(Finter,'Style','Frame','Position',[008 007 387 178].*WS); + +%-Initialise registry in hReg frame object +%----------------------------------------------------------------------- +[hReg,xyz] = spm_XYZreg('InitReg',hReg,M,DIM,[0;0;0]); + +%-Setup editable XYZ widgets & cross register with registry +%----------------------------------------------------------------------- +hFxyz = spm_results_ui('DrawXYZgui',M,DIM,varargin{4},xyz,Finter); +spm_XYZreg('XReg',hReg,hFxyz,'spm_results_ui'); + +%-Set up buttons for results functions +%----------------------------------------------------------------------- +spm_results_ui('DrawButts',hReg,DIM,Finter,WS,FS); + +varargout = {hReg}; + + + +%======================================================================= +case 'drawbutts' %-Draw results section buttons in Interactive window +%======================================================================= +% spm_results_ui('DrawButts',hReg,DIM,Finter,WS,FS) +% +if nargin<3, error('Insufficient arguments'), end +hReg = varargin{2}; +DIM = varargin{3}; +if nargin<4, Finter = spm_figure('FindWin','Interactive'); + else, Finter = varargin{4}; end +if nargin < 5, WS = spm('WinScale'); else, WS = varargin{5}; end +if nargin < 6, FS = spm('FontSizes'); else, FS = varargin{6}; end +PF = spm_platform('fonts'); + +%-p-values +%----------------------------------------------------------------------- +uicontrol(Finter,'Style','Frame','Position',[010 090 110 085].*WS) +uicontrol(Finter,'Style','Text','String','p-values',... + 'Position',[020 168 050 015].*WS,... + 'FontName',PF.times,'FontWeight','Normal','FontAngle','Italic',... + 'FontSize',FS(10),... + 'HorizontalAlignment','Left',... + 'ForegroundColor','w') +uicontrol(Finter,'Style','PushButton','String','volume','FontSize',FS(10),... + 'ToolTipString',... + 'tabulate summary of local maxima, p-values & statistics',... + 'Callback','spm_list(''List'',xSPM,hReg);',... + 'Interruptible','on','Enable','on',... + 'Position',[015 145 100 020].*WS) +uicontrol(Finter,'Style','PushButton','String','cluster','FontSize',FS(10),... + 'ToolTipString',... + 'tabulate p-values & statistics for local maxima of nearest cluster',... + 'Callback','spm_list(''ListCluster'',xSPM,hReg);',... + 'Interruptible','on','Enable','on',... + 'Position',[015 120 100 020].*WS) +uicontrol(Finter,'Style','PushButton','String','S.V.C.','FontSize',FS(10),... + 'ToolTipString',['Small Volume Correction - corrected p-values ',... + 'for a small search region'],... + 'Callback','spm_VOI(SPM,xSPM,hReg);',... + 'Interruptible','on','Enable','on',... + 'Position',[015 095 100 020].*WS) + + +%-SPM area - used for Volume of Interest +%----------------------------------------------------------------------- +uicontrol(Finter,'Style','Frame','Position',[125 090 150 085].*WS) +uicontrol(Finter,'Style','Text','String','Regional responses',... + 'Position',[135 168 94 015].*WS,... + 'FontName',PF.times,'FontWeight','Normal','FontAngle','Italic',... + 'FontSize',FS(10),... + 'HorizontalAlignment','Left',... + 'ForegroundColor','w') +uicontrol(Finter,'Style','PushButton','String','VOI',... + 'Position',[130 100 140 060].*WS,... + 'ToolTipString',... + 'Responses in volume of interest',... + 'Callback','[Y,xY] = spm_regions(xSPM,SPM,hReg)',... + 'Interruptible','on','Enable','on',... + 'FontName',PF.times,'FontWeight','Bold','FontAngle','Italic',... + 'FontSize',FS(32),... + 'HorizontalAlignment','Center',... + 'ForegroundColor',[1,1,1]*.5) + +%-Hemodynamic modeling +%----------------------------------------------------------------------- +uicontrol(Finter,'Style','Frame','Position',[125 050 150 030].*WS) +global defaults +if strcmp(defaults.modality,'FMRI') +uicontrol(Finter,'Style','PushButton','String','Hemodynamics',... + 'FontSize',FS(10),... + 'ToolTipString','Hemodynamic modeling of regional response',... + 'Callback','[Ep,Cp,K1,K2] = spm_hdm_ui(xSPM,SPM,hReg);',... + 'Interruptible','on','Enable','on',... + 'Position',[130 055 140 020].*WS,... + 'ForegroundColor','r'); +end + +%-Not currently used +%----------------------------------------------------------------------- +uicontrol(Finter,'Style','Frame','Position',[010 050 110 030].*WS) +if strcmp(defaults.modality,'FMRI') +uicontrol(Finter,'Style','PushButton','String',' ','FontSize',FS(10),... + 'ToolTipString',' ',... + 'Callback',' ',... + 'Interruptible','on','Enable','on',... + 'Position',[015 055 100 020].*WS) +end + +%-Visualisation +%----------------------------------------------------------------------- +uicontrol(Finter,'Style','Frame','Position',[280 090 110 085].*WS) +uicontrol(Finter,'Style','Text','String','visualisation',... + 'Position',[290 168 065 015].*WS,... + 'FontName',PF.times,'FontWeight','Normal','FontAngle','Italic',... + 'FontSize',FS(10),... + 'HorizontalAlignment','Left',... + 'ForegroundColor','w') +uicontrol(Finter,'Style','PushButton','String','plot','FontSize',FS(10),... + 'ToolTipString','plot data & contrasts at current voxel',... + 'Callback','[Y,y,beta,Bcov] = spm_graph(xSPM,SPM,hReg);',... + 'Interruptible','on','Enable','on',... + 'Position',[285 145 100 020].*WS) + +str = { 'overlays...','slices','sections','render', 'colin27'}; +tstr = { 'overlay filtered SPM on another image: ',... + '3 slices / ','ortho sections / ','render', 'sections from colin27''s canonical brain'}; +tmp = { 'spm_transverse(''set'',xSPM,hReg)',... + 'spm_sections(xSPM,hReg)',... + ['spm_render( struct( ''XYZ'', xSPM.XYZ,',... + '''t'', xSPM.Z'',',... + '''mat'', xSPM.M,',... + '''dim'', xSPM.DIM))'],... + 'spm_sections(xSPM,hReg,fullfile(spm(''dir''),''canonical'',''single_subj_T1.mnc''))'}; + % 'spm_sections_colin27(xSPM,hReg)'}; +if DIM(3) == 1, str(2 + 1) = []; tstr(2 + 1) = []; tmp(2) = []; end +uicontrol(Finter,'Style','PopUp','String',str,'FontSize',FS(10),... + 'ToolTipString',cat(2,tstr{:}),... + 'Callback','spm(''PopUpCB'',gcbo)',... + 'UserData',tmp,... + 'Interruptible','on','Enable','on',... + 'Position',[285 120 100 020].*WS) +uicontrol(Finter,'Style','PushButton','String','save','FontSize',FS(10),... + 'ToolTipString','save thresholded SPM as image',... + 'Callback',['spm_write_filtered(xSPM.Z,xSPM.XYZ,xSPM.DIM,xSPM.M,',... + 'sprintf(''SPM{%c}-filtered: u = %5.3f, k = %d'',',... + 'xSPM.STAT,xSPM.u,xSPM.k));'],... + 'Interruptible','on','Enable','on',... + 'Position',[285 095 100 020].*WS) + +%-ResultsUI controls +%----------------------------------------------------------------------- +uicontrol(Finter,'Style','Frame','Position',[280 050 110 030].*WS) + +hClear = uicontrol(Finter,'Style','PushButton','String','clear',... + 'ToolTipString','clears results subpane',... + 'FontSize',FS(9),'ForegroundColor','b',... + 'Callback',['spm_results_ui(''Clear''); ',... + 'spm_input(''!DeleteInputObj''),',... + 'spm_clf(''Satellite'')'],... + 'Interruptible','on','Enable','on',... + 'DeleteFcn','clc,spm_clf(''Graphics'')',... + 'Position',[285 055 035 018].*WS); + +hExit = uicontrol(Finter,'Style','PushButton','String','exit',... + 'ToolTipString','exit the results section',... + 'FontSize',FS(9),'ForegroundColor','r',... + 'Callback',['spm_clf(''Interactive''), spm_clf(''Graphics''),'... + 'close(spm_figure(''FindWin'',''Satellite'')),'... + 'clear'],... + 'Interruptible','on','Enable','on',... + 'Position',[325 055 035 018].*WS); + +hHelp = uicontrol(Finter,'Style','PushButton','String','?',... + 'ToolTipString','results section help',... + 'FontSize',FS(9),'ForegroundColor','g',... + 'Callback','spm_help(''spm_results_ui'')',... + 'Interruptible','on','Enable','on',... + 'Position',[365 055 020 018].*WS); + + +%======================================================================= +case 'drawxyzgui' %-Draw XYZ GUI area +%======================================================================= +% hFxyz = spm_results_ui('DrawXYZgui',M,DIM,xSPM,xyz,Finter) +if nargin<6, Finter=spm_figure('FindWin','Interactive'); + else, Finter=varargin{6}; end +if nargin < 5, xyz=[0;0;0]; else, xyz=varargin{5}; end +if nargin < 4, error('Insufficient arguments'), end +DIM = varargin{3}; +M = varargin{2}; +xyz = spm_XYZreg('RoundCoords',xyz,M,DIM); + +%-Locate windows etc... +%----------------------------------------------------------------------- +WS = spm('WinScale'); +FS = spm('FontSizes'); +PF = spm_platform('fonts'); + +%-Create XYZ control objects +%----------------------------------------------------------------------- +hFxyz = uicontrol(Finter,'Style','Frame','Position',[010 010 265 030].*WS); +uicontrol(Finter,'Style','Text','String','co-ordinates',... + 'Position',[020 033 078 016].*WS,... + 'FontName',PF.times,'FontWeight','Normal','FontAngle','Italic',... + 'FontSize',FS(10),... + 'HorizontalAlignment','Left',... + 'ForegroundColor','w') + +uicontrol(Finter,'Style','Text','String','x =',... + 'Position',[020 015 024 018].*WS,... + 'FontName',PF.times,'FontSize',FS(10),'FontAngle','Italic',... + 'HorizontalAlignment','Center'); +hX = uicontrol(Finter,'Style','Edit','String',sprintf('%.2f',xyz(1)),... + 'ToolTipString','enter x-coordinate',... + 'Position',[044 015 056 020].*WS,... + 'FontSize',FS(10),'BackGroundColor',[.8,.8,1],... + 'HorizontalAlignment','Right',... + 'Tag','hX',... + 'Callback','spm_results_ui(''EdWidCB'')'); + +uicontrol(Finter,'Style','Text','String','y =',... + 'Position',[105 015 024 018].*WS,... + 'FontName',PF.times,'FontSize',FS(10),'FontAngle','Italic',... + 'HorizontalAlignment','Center') +hY = uicontrol(Finter,'Style','Edit','String',sprintf('%.2f',xyz(2)),... + 'ToolTipString','enter y-coordinate',... + 'Position',[129 015 056 020].*WS,... + 'FontSize',FS(10),'BackGroundColor',[.8,.8,1],... + 'HorizontalAlignment','Right',... + 'Tag','hY',... + 'Callback','spm_results_ui(''EdWidCB'')'); + +uicontrol(Finter,'Style','Text','String','z =',... + 'Position',[190 015 024 018].*WS,... + 'FontName',PF.times,'FontSize',FS(10),'FontAngle','Italic',... + 'HorizontalAlignment','Center') +hZ = uicontrol(Finter,'Style','Edit','String',sprintf('%.2f',xyz(3)),... + 'ToolTipString','enter z-coordinate',... + 'Position',[214 015 056 020].*WS,... + 'FontSize',FS(10),'BackGroundColor',[.8,.8,1],... + 'HorizontalAlignment','Right',... + 'Tag','hZ',... + 'Callback','spm_results_ui(''EdWidCB'')'); + +%-Statistic value reporting pane +%----------------------------------------------------------------------- +hFconB = uicontrol(Finter,'Style','Frame','Position',[280 010 110 030].*WS); +uicontrol(Finter,'Style','Text','String','statistic value',... + 'Position',[285 035 085 016].*WS,... + 'FontName',PF.times,'FontWeight','Normal','FontAngle','Italic',... + 'FontSize',FS(10),... + 'HorizontalAlignment','Left',... + 'ForegroundColor','w') +hSPM = uicontrol(Finter,'Style','Text','String','',... + 'Position',[285 012 100 020].*WS,... + 'FontSize',FS(10),... + 'HorizontalAlignment','Center'); + + +%-Store data +%----------------------------------------------------------------------- +set(hFxyz,'Tag','hFxyz','UserData',struct(... + 'hReg', [],... + 'M', M,... + 'DIM', DIM,... + 'XYZ', varargin{4}.XYZmm,... + 'Z', varargin{4}.Z,... + 'hX', hX,... + 'hY', hY,... + 'hZ', hZ,... + 'hSPM', hSPM,... + 'xyz', xyz )); + +set([hX,hY,hZ],'UserData',hFxyz) +varargout = {hFxyz}; + + + +%======================================================================= +case 'edwidcb' %-Callback for editable widgets +%======================================================================= +% spm_results_ui('EdWidCB') + +hC = gcbo; +d = find(strcmp(get(hC,'Tag'),{'hX','hY','hZ'})); +hFxyz = get(hC,'UserData'); +UD = get(hFxyz,'UserData'); +xyz = UD.xyz; +nxyz = xyz; + +o = evalin('base',['[',get(hC,'String'),']'],'sprintf(''error'')'); +if ischar(o) | length(o)>1 + warning(sprintf('%s: Error evaluating ordinate:\n\t%s',... + mfilename,lasterr)) +else + nxyz(d) = o; + nxyz = spm_XYZreg('RoundCoords',nxyz,UD.M,UD.DIM); +end + +if abs(xyz(d)-nxyz(d))>0 + UD.xyz = nxyz; set(hFxyz,'UserData',UD) + if ~isempty(UD.hReg), spm_XYZreg('SetCoords',nxyz,UD.hReg,hFxyz); end + set(hC,'String',sprintf('%.3f',nxyz(d))) + spm_results_ui('UpdateSPMval',UD) +end + +%======================================================================= +case 'updatespmval' %-Update SPM value in GUI +%======================================================================= +% spm_results_ui('UpdateSPMval',hFxyz) +% spm_results_ui('UpdateSPMval',UD) +if nargin<2, error('insufficient arguments'), end +if isstruct(varargin{2}), UD=varargin{2}; else, UD = get(varargin{2},'UserData'); end +i = spm_XYZreg('FindXYZ',UD.xyz,UD.XYZ); +if isempty(i), str = ''; else, str = sprintf('%6.2f',UD.Z(i)); end +set(UD.hSPM,'String',str); + + +%======================================================================= +case 'getcoords' % Get current co-ordinates from XYZ widget +%======================================================================= +% xyz = spm_results_ui('GetCoords',hFxyz) +if nargin<2, hFxyz='Interactive'; else, hFxyz=varargin{2}; end +hFxyz = spm_results_ui('FindXYZframe',hFxyz); +varargout = {getfield(get(hFxyz,'UserData'),'xyz')}; + + + +%======================================================================= +case 'setcoords' % Set co-ordinates to XYZ widget +%======================================================================= +% [xyz,d] = spm_results_ui('SetCoords',xyz,hFxyz,hC) +if nargin<4, hC=0; else, hC=varargin{4}; end +if nargin<3, hFxyz=spm_results_ui('FindXYZframe'); else, hFxyz=varargin{3}; end +if nargin<2, error('Set co-ords to what!'), else, xyz=varargin{2}; end + +%-If this is an internal call, then don't do anything +if hFxyz==hC, return, end + +UD = get(hFxyz,'UserData'); + +%-Check validity of coords only when called without a caller handle +%----------------------------------------------------------------------- +if hC <= 0 + [xyz,d] = spm_XYZreg('RoundCoords',xyz,UD.M,UD.DIM); + if d>0 & nargout<2, warning(sprintf(... + '%s: Co-ords rounded to neatest voxel center: Discrepancy %.2f',... + mfilename,d)), end +else + d = []; +end + +%-Update xyz information & widget strings +%----------------------------------------------------------------------- +UD.xyz = xyz; set(hFxyz,'UserData',UD) +set(UD.hX,'String',sprintf('%.2f',xyz(1))) +set(UD.hY,'String',sprintf('%.2f',xyz(2))) +set(UD.hZ,'String',sprintf('%.2f',xyz(3))) +spm_results_ui('UpdateSPMval',UD) + +%-Tell the registry, if we've not been called by the registry... +%----------------------------------------------------------------------- +if (~isempty(UD.hReg) & UD.hReg~=hC) + spm_XYZreg('SetCoords',xyz,UD.hReg,hFxyz); +end + +%-Return arguments +%----------------------------------------------------------------------- +varargout = {xyz,d}; + + + +%======================================================================= +case 'findxyzframe' % Find hFxyz frame +%======================================================================= +% hFxyz = spm_results_ui('FindXYZframe',h) +% Sorts out hFxyz handles +if nargin<2, h='Interactive'; else, h=varargin{2}; end +if isstr(h), h=spm_figure('FindWin',h); end +if ~ishandle(h), error('invalid handle'), end +if ~strcmp(get(h,'Tag'),'hFxyz'), h=findobj(h,'Tag','hFxyz'); end +if isempty(h), error('XYZ frame not found'), end +if length(h)>1, error('Multiple XYZ frames found'), end +varargout = {h}; + + + +%======================================================================= +case 'plotui' %-GUI for plot manipulation +%======================================================================= +% spm_results_ui('PlotUi',hAx) +if nargin<2, hAx=gca; else, hAx=varargin{2}; end + +WS = spm('WinScale'); +FS = spm('FontSizes'); +Finter=spm_figure('FindWin','Interactive'); +figure(Finter) + +%-Check there aren't already controls! +%----------------------------------------------------------------------- +hGraphUI = findobj(Finter,'Tag','hGraphUI'); +if ~isempty(hGraphUI) %-Controls exist + hBs = get(hGraphUI,'UserData'); + if hAx==get(hBs(1),'UserData') %-Controls linked to these axes + return + else %-Old controls remain + delete(findobj(Finter,'Tag','hGraphUIbg')) + end +end + +%-Frames & text +%----------------------------------------------------------------------- +hGraphUIbg = uicontrol(Finter,'Style','Frame','Tag','hGraphUIbg',... + 'BackgroundColor',spm('Colour'),... + 'Position',[001 196 400 055].*WS); +hGraphUI = uicontrol(Finter,'Style','Frame','Tag','hGraphUI',... + 'Position',[008 202 387 043].*WS); +hGraphUIButtsF = uicontrol(Finter,'Style','Frame',... + 'Position',[010 205 380 030].*WS); +hText = uicontrol(Finter,'Style','Text','String','plot controls',... + 'Position',[020 227 080 016].*WS,... + 'FontName',spm_platform('font','times'),'FontWeight','Normal',... + 'FontAngle','Italic','FontSize',FS(10),... + 'HorizontalAlignment','Left',... + 'ForegroundColor','w'); + +%-Controls +%----------------------------------------------------------------------- +h1 = uicontrol(Finter,'Style','CheckBox','String','hold',... + 'ToolTipString','toggle hold to overlay plots',... + 'FontSize',FS(10),... + 'Value',strcmp(get(hAx,'NextPlot'),'add'),... + 'Callback',[... + 'if get(gcbo,''Value''), ',... + 'set(get(gcbo,''UserData''),''NextPlot'',''add''), ',... + 'else, ',... + 'set(get(gcbo,''UserData''),''NextPlot'',''replace''), ',... + 'end'],... + 'Interruptible','on','Enable','on',... + 'Position',[015 210 070 020].*WS); +h2 = uicontrol(Finter,'Style','CheckBox','String','grid',... + 'ToolTipString','toggle axes grid',... + 'FontSize',FS(10),... + 'Value',strcmp(get(hAx,'XGrid'),'on'),... + 'Callback',[... + 'if get(gcbo,''Value''), ',... + 'set(get(gcbo,''UserData''),''XGrid'',''on'','... + '''YGrid'',''on'',''ZGrid'',''on''), ',... + 'else, ',... + 'set(get(gcbo,''UserData''),''XGrid'',''off'','... + '''YGrid'',''off'',''ZGrid'',''off''), ',... + 'end'],... + 'Interruptible','on','Enable','on',... + 'Position',[090 210 070 020].*WS); +h3 = uicontrol(Finter,'Style','CheckBox','String','Box',... + 'ToolTipString','toggle axes box',... + 'FontSize',FS(10),... + 'Value',strcmp(get(hAx,'Box'),'on'),... + 'Callback',[... + 'if get(gcbo,''Value''), ',... + 'set(get(gcbo,''UserData''),''Box'',''on''), ',... + 'else, ',... + 'set(get(gcbo,''UserData''),''Box'',''off''), ',... + 'end'],... + 'Interruptible','on','Enable','on',... + 'Position',[165 210 070 020].*WS); +h4 = uicontrol(Finter,'Style','PopUp',... + 'ToolTipString','edit axis text annotations',... + 'FontSize',FS(10),... + 'String','text|Title|Xlabel|Ylabel',... + 'Callback','spm_results_ui(''PlotUiCB'')',... + 'Interruptible','on','Enable','on',... + 'Position',[240 210 070 020].*WS); +h5 = uicontrol(Finter,'Style','PopUp',... + 'ToolTipString','change various axes attributes',... + 'FontSize',FS(10),... + 'String','attrib|LineWidth|XLim|YLim|handle',... + 'Callback','spm_results_ui(''PlotUiCB'')',... + 'Interruptible','off','Enable','on',... + 'Position',[315 210 070 020].*WS); + +%-Handle storage for linking, and DeleteFcns for linked deletion +%----------------------------------------------------------------------- +set(hGraphUI,'UserData',[h1,h2,h3,h4,h5]) +set([h1,h2,h3,h4,h5],'UserData',hAx) + +set(hGraphUIbg,'UserData',... + [hGraphUI,hGraphUIButtsF,hText,h1,h2,h3,h4,h5],... + 'DeleteFcn','spm_results_ui(''Delete'',get(gcbo,''UserData''))') +set(hAx,'UserData',hGraphUIbg,... + 'DeleteFcn','spm_results_ui(''Delete'',get(gcbo,''UserData''))') + + + + +%======================================================================= +case 'plotuicb' +%======================================================================= +% spm_results_ui('PlotUiCB') +hPM = gcbo; +v = get(hPM,'Value'); +if v==1, return, end +str = cellstr(get(hPM,'String')); +str = str{v}; + +hAx = get(hPM,'UserData'); +switch str +case 'Title' + h = get(hAx,'Title'); + set(h,'String',spm_input('Enter title:',-1,'s+',get(h,'String'))) +case 'Xlabel' + h = get(hAx,'Xlabel'); + set(h,'String',spm_input('Enter X axis label:',-1,'s+',get(h,'String'))) +case 'Ylabel' + h = get(hAx,'Ylabel'); + set(h,'String',spm_input('Enter Y axis label:',-1,'s+',get(h,'String'))) +case 'LineWidth' + lw = spm_input('Enter LineWidth',-1,'e',get(hAx,'LineWidth'),1); + set(hAx,'LineWidth',lw) +case 'XLim' + XLim = spm_input('Enter XLim',-1,'e',get(hAx,'XLim'),[1,2]); + set(hAx,'XLim',XLim) +case 'YLim' + YLim = spm_input('Enter YLim',-1,'e',get(hAx,'YLim'),[1,2]); + set(hAx,'YLim',YLim) +case 'handle' + varargout={hAx}; +otherwise + warning(['Unknown action: ',str]) +end + +set(hPM,'Value',1) + + +%======================================================================= +case {'clear','clearpane'} %-Clear results subpane +%======================================================================= +% Fgraph = spm_results_ui('Clear',F,mode) +% mode 1 [default] usual, mode 0 - clear & hide Res stuff, 2 - RNP +if strcmp(lower(Action),'clearpane') + warning('''ClearPane'' action is grandfathered, use ''Clear'' instead') +end + +if nargin<3, mode=1; else, mode=varargin{3}; end +if nargin<2, F='Graphics'; else, F=varargin{2}; end +F = spm_figure('FindWin',F); + +%-Clear input objects from 'Interactive' window +%----------------------------------------------------------------------- +%spm_input('!DeleteInputObj') + + +%-Get handles of objects in Graphics window & note permanent results objects +%----------------------------------------------------------------------- +H = get(F,'Children'); %-Get contents of window +H = findobj(H,'flat','HandleVisibility','on'); %-Drop GUI components +h = findobj(H,'flat','Tag','PermRes'); %-Look for 'PermRes' object + +if ~isempty(h) + %-Found 'PermRes' object + % This has handles of permanent results objects in it's UserData + tmp = get(h,'UserData'); + HR = tmp.H; + HRv = tmp.Hv; +else + %-No trace of permanent results objects + HR = []; + HRv = {}; +end +H = setdiff(H,HR); %-Drop permanent results obj + + +%-Delete stuff as appropriate +%----------------------------------------------------------------------- +if mode==2 %-Don't delete axes with NextPlot 'add' + H = setdiff(H,findobj(H,'flat','Type','axes','NextPlot','add')); +end + +delete(H) + +if mode==0 %-Hide the permanent results section stuff + set(HR,'Visible','off') +else + set(HR,{'Visible'},HRv) +end + + +%======================================================================= +case 'launchmp' %-Launch multiplanar toolbox +%======================================================================= +% hMP = spm_results_ui('LaunchMP',M,DIM,hReg,hBmp) +if nargin<5, hBmp = gcbo; else, hBmp = varargin{5}; end +hReg = varargin{4}; +DIM = varargin{3}; +M = varargin{2}; + +%-Check for existing MultiPlanar toolbox +hMP = get(hBmp,'UserData'); +if ishandle(hMP) + figure(spm_figure('ParentFig',hMP)) + varargout = {hMP}; + return +end + +%-Initialise and cross-register MultiPlanar toolbox +hMP = spm_XYZreg_Ex2('Create',M,DIM); +spm_XYZreg('Xreg',hReg,hMP,'spm_XYZreg_Ex2'); + +%-Setup automatic deletion of MultiPlanar on deletion of results controls +set(hBmp,'Enable','on','UserData',hMP) +set(hBmp,'DeleteFcn','spm_results_ui(''delete'',get(gcbo,''UserData''))') + +varargout = {hMP}; + + + +%======================================================================= +case 'delete' %-Delete HandleGraphics objects +%======================================================================= +% spm_results_ui('Delete',h) +h = varargin{2}; +delete(h(ishandle(h))); + + +%======================================================================= +otherwise +%======================================================================= +error('Unknown action string') + +%======================================================================= +end + diff --git a/spm/spm2/spm_sections.m b/spm/spm2/spm_sections.m new file mode 100644 index 0000000..8967ce3 --- /dev/null +++ b/spm/spm2/spm_sections.m @@ -0,0 +1,36 @@ +function spm_sections(SPM,hReg,spms) +% rendering of regional effects [SPM{Z}] on orthogonal sections of +% (a brain from an img file if given) +% FORMAT spm_sections(SPM,hReg) +% +% SPM - xSPM structure containing details of excursion set +% hReg - handle of MIP register +% +% spm_sections(SPM,hReg,imgfile) +% +% SPM - xSPM structure containing details of excursion set +% hReg - handle of MIP register +% +% see spm_getSPM for details +%_______________________________________________________________________ +% +% spm_sections is called by spm_results and uses variables in SPM and +% VOL to create three orthogonal sections though a background image. +% Regional foci from the selected SPM are rendered on this image. +% +%_______________________________________________________________________ +% @(#)spm_sections.m 2.14 John Ashburner 02/09/05 + +Fgraph = spm_figure('FindWin','Graphics'); +if nargin<3 +spms = spm_get(1,'IMAGE','select image for rendering on'); +end +spm_results_ui('Clear',Fgraph); +spm_orthviews('Reset'); +global st +st.Space = spm_matrix([0 0 0 0 0 -pi/2])*st.Space; +spm_orthviews('Image',spms,[0.05 0.05 0.9 0.45]); +spm_orthviews MaxBB; +spm_orthviews('register',hReg); +spm_orthviews('addblobs',1,SPM.XYZ,SPM.Z,SPM.M); +spm_orthviews('Redraw'); diff --git a/spm/spm2/spm_spm.m b/spm/spm2/spm_spm.m new file mode 100644 index 0000000..4ca45cd --- /dev/null +++ b/spm/spm2/spm_spm.m @@ -0,0 +1,948 @@ +function [SPM] = spm_spm(SPM) +% [Re]ML Estimation of a General Linear Model +% FORMAT [SPM] = spm_spm(SPM) +% +% required fields of SPM: +% +% xY.VY - nScan x 1 struct array of mapped image volumes +% Images must have the same orientation, voxel size and data type +% - Any scaling should have already been applied via the image handle +% scalefactors. +% +% xX - Structure containing design matrix information +% - Required fields are: +% xX.X - Design matrix (raw, not temporally smoothed) +% xX.name - cellstr of parameter names corresponding to columns +% of design matrix +% - Optional fields are: +% xX.K - cell of session-specific structures (see spm_filter) +% - Design & data are pre-multiplied by K +% (K*Y = K*X*beta + K*e) +% - Note that K should not smooth across block boundaries +% - defaults to speye(size(xX.X,1)) +% xX.W - Optional whitening/weighting matrix used to give +% weighted least squares estimates (WLS). If not specified +% spm_spm will set this to whiten the data and render +% the OLS estimtes maximum likelihood +% i.e. W*W' = inv(xVi.V). +% +% xVi - structure describing intrinsic temporal non-sphericity +% - required fields are: +% xVi.Vi - array of non-sphericity components +% - defaults to {speye(size(xX.X,1))} - i.i.d. +% - specifying a cell array of contraints (Qi) +% These contraints invoke spm_reml to estimate +% hyperparameters assuming V is constant over voxels. +% that provide a high precise estimate of xX.V +% - Optional fields are: +% xX.V - Optional non-sphericity matrix. Cov(e) = sigma^2*V +% If not specified spm_spm will compute this using +% a 1st pass to identify signifcant voxels over which +% to estimate V. A 2nd pass is then used to re-estimate +% the parameters with WLS and save the ML estimates +% (unless xX.W is already specified) +% +% xM - Structure containing masking information, or a simple column vector +% of thresholds corresponding to the images in VY. +% - If a structure, the required fields are: +% xM.TH - nVar x nScan matrix of analysis thresholds, one per image +% xM.I - Implicit masking (0=>none, 1 => implicit zero/NaN mask) +% xM.VM - struct array of mapped explicit mask image volumes +% - (empty if no explicit masks) +% - Explicit mask images are >0 for valid voxels to assess. +% - Mask images can have any orientation, voxel size or data +% type. They are interpolated using nearest neighbour +% interpolation to the voxel locations of the data Y. +% - Note that voxels with constant data (i.e. the same value across +% scans) are also automatically masked out. +% +% +% In addition, the global default UFp is used to set a critical +% F-threshold for selecting voxels over which the non-sphericity +% is estimated (if required) +% +%_______________________________________________________________________ +% +% spm_spm is the heart of the SPM package. Given image files and a +% General Linear Model, it estimates the model parameters, variance +% hyerparameters, and smoothness of standardised residual fields, writing +% these out to disk in the current working directory for later +% interrogation in the results section. (NB: Existing analyses in the +% current working directory are overwritten). This directory +% now becomes the working directory for this analysis and all saved +% images are relative to this directory. +% +% The model is expressed via the design matrix (xX.X). The basic model +% at each voxel is of the form is Y = X*B + e, for data Y, design +% matrix X, (unknown) parameters B, and residual errors e. The errors +% are assummed to have a normal distribution. +% +% Sometimes confounds (e.g. drift terms in fMRI) are necessary. These +% can be specified directly in the design matrix or implicitly, in terms +% of a residual forming matrix K to give a generalised linear model +% K*Y = K*X*B + K*e. In fact K can be any matrix (e.g. a convolution +% matrix). +% +% In some instances i.i.d. assumptions about errors do not hold. For +% example, with serially correlated (fMRI) data or correlations among the +% levels of a factor in repeated measures designs. This non-sphericity +% can be specified in terms of components (SPM.xVi.Vi{i}). If specified +% these covariance components will then be estimated with ReML (restricted +% maximum likelihood) hyperparameters. This estimation assumes the same +% non-sphericity for voxels that exceed the global F-threshold. The ReML +% estimates can then used to whiten the data giving maximum likelihood (ML) +% or Gauss-Markov estimators. This entails a second pass of the data +% with an augmented model K*W*Y = K*W*X*B + K*W*e where W*W' = inv(xVi.V). +% xVi.V is the non-sphericity based on the hyperparameter esimates. +% W is stored in xX.W and cov(K*W*e) in xX.V. The covariance of the +% parameter estimates is then xX.Bcov = pinv(K*W*X)*xX.V*pinv(K*W*X)'; +% +% If you do not want ML estimates but want to use ordinary least squares +% (OLS) then simply set SPM.xX.W to the identity matrix. Any non-sphericity +% V will still be estimated but will be used to adjust the degrees of freedom +% of the ensuing statistics using the Satterthwaite approximation (c.f. +% the Greenhouse-Giesser corrections) +% +% If [non-spherical] variance components Vi are not specified xVi.Vi and +% xVi.V default to the identity matrix (i.e. i.i.d). The parameters are +% then estimated by OLS. In this instance the OLS and ML estimates are +% the same. +% +% Note that only a single voxel-specific hyperaprameter (i.e. variance +% component) is estimated, even if V is not i.i.d. This means spm_spm +% always implements a fixed-effects model. +% Random effects models can be emulated using a multi-stage procedure: +% This entails summarising the data with contrasts such that the fixed +% effects in a second model on the summary data are those effects of +% interest (i.e. the population effects). This means contrasts are +% re-entered into spm_spm to make an inference (SPM) at the next +% level. At this higher hierarchial level the residual variance for the +% model contains the appropriate variance components from lower levels. +% See spm_RandFX.man for further details and below. +% +% Under the additional assumption that the standardised residual images +% are non-stationary standard Gaussian random fields, results from +% Random field theory can be applied to estimate the significance +% statistic images (SPM's) adjusting p values for the multiple tests +% at all voxels in the search volume. The parameters required for +% this random field correction are the volume, and Lambda, the covariance +% matrix of partial derivatives of the standardised error fields. +% +% spm_est_smoothness estimates the variances of the partial derivatives +% in the axis directions (the diagonal of Lambda). The covariances (off +% diagonal elements of Lambda) are assumed to be zero. +% +% ---------------- +% +% +% The volume analsed is the intersection of the threshold masks, +% explicit masks and implicit masks. See spm_spm_ui for further details +% on masking options. +% +% +%----------------------------------------------------------------------- +% +% The output of spm_spm takes the form of an SPM.mat file of the analysis +% parameters, and 'float' flat-file images of the parameter and variance +% [hyperparameter] estimates. An 8bit zero-one mask image indicating the +% voxels assessed is also written out, with zero indicating voxels outside +% tha analysed volume. +% +% ---------------- +% +% The following SPM.fields are set by spm_spm (unless specified) +% +% xVi.V - estimated non-sphericity trace(V) = rank(V) +% xVi.h - hyperparameters xVi.V = xVi.h(1)*xVi.Vi{1} + ... +% xVi.Cy - spatially whitened (used by ReML to estimate h) +% xVi.CY - <(Y - )*(Y - )'> (used by spm_spm_Bayes) +% +% ---------------- +% +% Vbeta - struct array of beta image handles (relative) +% VResMS - file struct of ResMS image handle (relative) +% VM - file struct of Mask image handle (relative) +% +% ---------------- +% xX.W - if not specified W*W' = inv(x.Vi.V) +% xX.V - V matrix (K*W*Vi*W'*K') = correlations after K*W is applied +% xX.xKXs - space structure for K*W*X, the 'filtered and whitened' +% design matrix +% - given as spm_sp('Set',xX.K*xX.W*xX.X) - see spm_sp +% xX.pKX - pseudoinverse of K*W*X, computed by spm_sp +% xX.Bcov - xX.pKX*xX.V*xX.pKX - variance-covariance matrix of +% parameter estimates +% (when multiplied by the voxel-specific hyperparameter ResMS) +% (of the parameter estimates. (ResSS/xX.trRV = ResMS) ) +% xX.trRV - trace of R*V, computed efficiently by spm_SpUtil +% xX.trRVRV - trace of RVRV +% xX.erdf - effective residual degrees of freedom (trRV^2/trRVRV) +% xX.nKX - design matrix (xX.xKXs.X) scaled for display +% (see spm_DesMtx('sca',... for details) +% +% ---------------- +% +% xVol.M - 4x4 voxel->mm transformation matrix +% xVol.iM - 4x4 mm->voxel transformation matrix +% xVol.DIM - image dimensions - column vector (in voxels) +% xVol.XYZ - 3 x S vector of in-mask voxel coordinates +% xVol.S - Lebesgue measure or volume (in voxels) +% xVol.R - vector of resel counts (in resels) +% xVol.FWHM - Smoothness of components - FWHM, (in voxels) +% +% ---------------- +% +% xCon - See Christensen for details of F-contrasts. These are specified +% at the end of spm_spm after the non-sphericity V has been defined +% or estimated. The fist contrast tests for all effects of interest +% assuming xX.iB and xX.iG index confounding or nuisance effects. +% +% xCon - Contrast structure (created by spm_FcUtil.m) +% xCon.name - Name of contrast +% xCon.STAT - 'F', 'T' or 'P' - for F/T-contrast ('P' for PPMs) +% xCon.c - (F) Contrast weights +% xCon.X0 - Reduced design matrix (spans design space under Ho) +% It is in the form of a matrix (spm99b) or the +% coordinates of this matrix in the orthogonal basis +% of xX.X defined in spm_sp. +% xCon.iX0 - Indicates how contrast was specified: +% If by columns for reduced design matrix then iX0 contains the +% column indices. Otherwise, it's a string containing the +% spm_FcUtil 'Set' action: Usuall one of {'c','c+','X0'} +% (Usually this is the input argument F_iX0.) +% xCon.X1o - Remaining design space (orthogonal to X0). +% It is in the form of a matrix (spm99b) or the +% coordinates of this matrix in the orthogonal basis +% of xX.X defined in spm_sp. +% xCon.eidf - Effective interest degrees of freedom (numerator df) +% xCon.Vcon - ...for handle of contrast/ESS image (empty at this stage) +% xCon.Vspm - ...for handle of SPM image (empty at this stage) +% +% ---------------- +% +% +% The following images are written to file +% +% mask.{img,hdr} - analysis mask image +% 8-bit (uint8) image of zero-s & one's indicating which voxels were +% included in the analysis. This mask image is the intersection of the +% explicit, implicit and threshold masks specified in the xM argument. +% The XYZ matrix contains the voxel coordinates of all voxels in the +% analysis mask. The mask image is included for reference, but is not +% explicitly used by the results section. +% +% ---------------- +% +% beta_????.{img,hdr} - parameter images +% These are 16-bit (float) images of the parameter estimates. The image +% files are numbered according to the corresponding column of the +% design matrix. Voxels outside the analysis mask (mask.img) are given +% value NaN. +% +% ---------------- +% +% ResMS.{img,hdr} - estimated residual variance image +% This is a 32-bit (double) image of the residual variance estimate. +% Voxels outside the analysis mask are given value NaN. +% +% ---------------- +% +% RVP.{img,hdr} - estimated resels per voxel image +% This is a 32-bit (double) image of the RESELs per voxel estimate. +% Voxels outside the analysis mask are given value 0. These images +% reflect the nonstationary aspects the spatial autocorrelations. +% +% +%----------------------------------------------------------------------- +% +% References: +% +% Christensen R (1996) Plane Answers to Complex Questions +% Springer Verlag +% +% Friston KJ, Holmes AP, Worsley KJ, Poline JP, Frith CD, Frackowiak RSJ (1995) +% ``Statistical Parametric Maps in Functional Imaging: +% A General Linear Approach'' +% Human Brain Mapping 2:189-210 +% +% Worsley KJ, Friston KJ (1995) +% ``Analysis of fMRI Time-Series Revisited - Again'' +% Neuroimage 2:173-181 +% +%----------------------------------------------------------------------- +% +% For those interested, the analysis proceeds a "block" at a time, +% The block size conforms to maxMem that can be set as a global variable +% MAXMEM (in bytes) [default = 2^20] +% +%_______________________________________________________________________ +% @(#)spm_spm.m 2.66 Andrew Holmes, Jean-Baptiste Poline, Karl Friston 03/03/27 +SCCSid = '2.66'; + +%-Say hello +%----------------------------------------------------------------------- +SPMid = spm('FnBanner',mfilename,SCCSid); +Finter = spm('FigName','Stats: estimation...'); spm('Pointer','Watch') + +%-Get SPM.mat if necessary +%----------------------------------------------------------------------- +if nargin ==0 + swd = spm_str_manip(spm_get(1,'SPM.mat','Select SPM.mat'),'H'); + load(fullfile(swd,'SPM.mat')); + SPM.swd = swd; +end + +%-Change to SPM.swd if specified +%----------------------------------------------------------------------- +try + cd(SPM.swd); +end + +%-Ensure data are assigned +%----------------------------------------------------------------------- +try + SPM.xY.VY; +catch + helpdlg({ 'Please assign data to this design',... + 'Use fMRI under model specification'}); + spm('FigName','Stats: done',Finter); spm('Pointer','Arrow') + return +end + +%-Delete files from previous analyses +%----------------------------------------------------------------------- +if exist(fullfile('.','mask.img'),'file') == 2 + + str = {'Current directory contains SPM estimation files:',... + 'pwd = ',pwd,... + 'Existing results will be overwritten!'}; + + abort = spm_input(str,1,'bd','stop|continue',[1,0],1); + if abort + spm('FigName','Stats: done',Finter); spm('Pointer','Arrow') + return + else + str = sprintf('Overwriting old results\n\t (pwd = %s) ',pwd) + warning(str) + drawnow + end +end + +files = { 'mask.???','ResMS.???','RVP.???',... + 'beta_????.???','con_????.???','ResI_????.???',... + 'ess_????.???', 'spm?_????.???'}; + +for i=1:length(files) + if any(files{i} == '*'|files{i} == '?' ) + [j,null] = spm_list_files(pwd,files{i}); + for i=1:size(j,1) + spm_unlink(deblank(j(i,:))) + end + else + spm_unlink(files{i}) + end +end + + +%======================================================================= +% - A N A L Y S I S P R E L I M I N A R I E S +%======================================================================= + +%-Initialise +%======================================================================= +fprintf('%-40s: %30s','Initialising parameters','...computing') %-# +xX = SPM.xX; +[nScan nBeta] = size(xX.X); + + +%-If xM is not a structure then assumme it's a vector of thresholds +%----------------------------------------------------------------------- +try + xM = SPM.xM; +catch + xM = -ones(nScan,1)/0; +end +if ~isstruct(xM) + xM = struct( 'T', [],... + 'TH', xM,... + 'I', 0,... + 'VM', {[]},... + 'xs', struct('Masking','analysis threshold')); +end + +%-Check confounds (xX.K) and non-sphericity (xVi) +%----------------------------------------------------------------------- +if ~isfield(xX,'K') + xX.K = 1; +end +try + %-If covariance components are specified use them + %--------------------------------------------------------------- + xVi = SPM.xVi; +catch + + %-otherwise assume i.i.d. + %--------------------------------------------------------------- + xVi = struct( 'form', 'i.i.d.',... + 'V', speye(nScan,nScan)); +end + + +%-Get non-sphericity V +%======================================================================= +try + %-If xVi.V is specified proceed directly to parameter estimation + %--------------------------------------------------------------- + V = xVi.V; + str = 'parameter estimation'; + + +catch + % otherwise invoke ReML selecting voxels under i.i.d assumptions + %--------------------------------------------------------------- + V = speye(nScan,nScan); + str = '[hyper]parameter estimation'; +end + +%-Get whitening/Weighting matrix: If xX.W exists we will save WLS estimates +%----------------------------------------------------------------------- +try + %-If W is specified, use it + %------------------------------------------------------- + W = xX.W; +catch + if isfield(xVi,'V') + + % otherwise make W a whitening filter W*W' = inv(V) + %------------------------------------------------------- + [u s] = spm_svd(xVi.V); + s = spdiags(1./sqrt(diag(s)),0,nScan,nScan); + W = u*s*u'; + W = W.*(abs(W) > 1e-6); + xX.W = sparse(W); + else + % unless xVi.V has not been estimated - requiring 2 passes + %------------------------------------------------------- + W = speye(nScan,nScan); + str = 'hyperparameter estimation (1st pass)'; + end +end + + +%-Design space and projector matrix [pseudoinverse] for WLS +%======================================================================= +xX.xKXs = spm_sp('Set',spm_filter(xX.K,W*xX.X)); % KWX +xX.pKX = spm_sp('x-',xX.xKXs); % projector + +%-If xVi.V is not defined compute Hsqr and F-threshold under i.i.d. +%----------------------------------------------------------------------- +if ~isfield(xVi,'V') + Fcname = 'effects of interest'; + iX0 = [SPM.xX.iB SPM.xX.iG]; + xCon = spm_FcUtil('Set',Fcname,'F','iX0',iX0,xX.xKXs); + X1o = spm_FcUtil('X1o', xCon(1),xX.xKXs); + Hsqr = spm_FcUtil('Hsqr',xCon(1),xX.xKXs); + trRV = spm_SpUtil('trRV',xX.xKXs); + trMV = spm_SpUtil('trMV',X1o); + UFp = spm('GetGlobal','UFp'); + UF = spm_invFcdf(1 - UFp,[trMV,trRV]); +end + + +%-Image dimensions and data +%======================================================================= +VY = SPM.xY.VY; +M = VY(1).mat; +DIM = VY(1).dim(1:3)'; +xdim = DIM(1); ydim = DIM(2); zdim = DIM(3); + +% KND: 3D / 4D bug... +if numel(VY(1).dim)<4 + %VY(1).dim(end:4) = 1; + YNaNrep = NaN; +else + YNaNrep = spm_type(VY(1).dim(4),'nanrep'); +end + +%-Maximum number of residual images for smoothness estimation +%----------------------------------------------------------------------- +global defaults +MAXRES = defaults.stats.maxres; + +%-maxMem is the maximum amount of data processed at a time (bytes) +%----------------------------------------------------------------------- +MAXMEM = defaults.stats.maxmem; +nSres = min(nScan,MAXRES); +blksz = ceil(MAXMEM/8/nScan); %-block size +nbch = ceil(xdim*ydim/blksz); %-# blocks + + +fprintf('%s%30s\n',sprintf('\b')*ones(1,30),'...done') %-# + + +%-Initialise output images (unless this is a 1st pass for ReML) +%======================================================================= +if isfield(xX,'W') +fprintf('%-40s: %30s','Output images','...initialising') %-# + +%-Intialise the name of the new mask : current mask & conditions on voxels +%----------------------------------------------------------------------- +VM = struct( 'fname', 'mask.img',... + 'dim', [DIM',spm_type('uint8')],... + 'mat', M,... + 'pinfo', [1 0 0]',... + 'descrip', 'spm_spm:resultant analysis mask'); +VM = spm_create_vol(VM); + + +%-Intialise beta image files +%----------------------------------------------------------------------- +Vbeta(1:nBeta) = deal(struct(... + 'fname', [],... + 'dim', [DIM',spm_type('float')],... + 'mat', M,... + 'pinfo', [1 0 0]',... + 'descrip', '')); +for i = 1:nBeta + Vbeta(i).fname = sprintf('beta_%04d.img',i); + Vbeta(i).descrip = sprintf('spm_spm:beta (%04d) - %s',i,xX.name{i}); + spm_unlink(Vbeta(i).fname) +end +Vbeta = spm_create_vol(Vbeta,'noopen'); + + +%-Intialise residual sum of squares image file +%----------------------------------------------------------------------- +VResMS = struct( 'fname', 'ResMS.img',... + 'dim', [DIM',spm_type('double')],... + 'mat', M,... + 'pinfo', [1 0 0]',... + 'descrip', 'spm_spm:Residual sum-of-squares'); +VResMS = spm_create_vol(VResMS,'noopen'); + + +%-Intialise residual images +%----------------------------------------------------------------------- +VResI(1:nSres) = deal(struct(... + 'fname', [],... + 'dim', [DIM',spm_type('double')],... + 'mat', M,... + 'pinfo', [1 0 0]',... + 'descrip', 'spm_spm:Residual image')); + +for i = 1:nSres + VResI(i).fname = sprintf('ResI_%04d.img', i); + VResI(i).descrip = sprintf('spm_spm:ResI (%04d)', i); + spm_unlink(VResI(i).fname); +end +VResI = spm_create_vol(VResI); +end % (xX,'W') + +fprintf('%s%30s\n',sprintf('\b')*ones(1,30),'...initialised') %-# + + +%======================================================================= +% - F I T M O D E L & W R I T E P A R A M E T E R I M A G E S +%======================================================================= + + +%-Intialise variables used in the loop +%======================================================================= +xords = [1:xdim]'*ones(1,ydim); xords = xords(:)'; % plane X coordinates +yords = ones(xdim,1)*[1:ydim]; yords = yords(:)'; % plane Y coordinates +S = 0; % Volume (voxels) +s = 0; % Volume (voxels > UF) +Cy = 0; % spatially whitened +CY = 0; % for ReML +EY = 0; % for ReML +i_res = round(linspace(1,nScan,nSres))'; % Indices for residual + +%-Initialise XYZ matrix of in-mask voxel co-ordinates (real space) +%----------------------------------------------------------------------- +XYZ = zeros(3,xdim*ydim*zdim); + +%-Cycle over bunches blocks within planes to avoid memory problems +%======================================================================= +spm_progress_bar('Init',100,str,''); + +for z = 1:zdim %-loop over planes (2D or 3D data) + + % current plane-specific parameters + %------------------------------------------------------------------- + zords = z*ones(xdim*ydim,1)'; %-plane Z coordinates + CrBl = []; %-parameter estimates + CrResI = []; %-normalized residuals + CrResSS = []; %-residual sum of squares + Q = []; %-in mask indices for this plane + + for bch = 1:nbch %-loop over blocks + + %-# Print progress information in command window + %--------------------------------------------------------------- + str = sprintf('Plane %3d/%-3d, block %3d/%-3d',z,zdim,bch,nbch); + fprintf('\r%-40s: %30s',str,' ') %-# + + %-construct list of voxels in this block + %--------------------------------------------------------------- + I = [1:blksz] + (bch - 1)*blksz; %-voxel indices + I = I(I <= xdim*ydim); %-truncate + xyz = [xords(I); yords(I); zords(I)]; %-voxel coordinates + nVox = size(xyz,2); %-number of voxels + + %-Get data & construct analysis mask + %=============================================================== + fprintf('%s%30s',sprintf('\b')*ones(1,30),'...read & mask data')%-# + Cm = logical(ones(1,nVox)); %-current mask + + + %-Compute explicit mask + % (note that these may not have same orientations) + %--------------------------------------------------------------- + for i = 1:length(xM.VM) + + %-Coordinates in mask image + %------------------------------------------------------- + j = xM.VM(i).mat\M*[xyz;ones(1,nVox)]; + + %-Load mask image within current mask & update mask + %------------------------------------------------------- + Cm(Cm) = spm_get_data(xM.VM(i),j(:,Cm)) > 0; + end + + %-Get the data in mask, compute threshold & implicit masks + %--------------------------------------------------------------- + Y = zeros(nScan,nVox); + for i = 1:nScan + + %-Load data in mask + %------------------------------------------------------- + if ~any(Cm), break, end %-Break if empty mask + Y(i,Cm) = spm_get_data(VY(i),xyz(:,Cm)); + + Cm(Cm) = Y(i,Cm) > xM.TH(i); %-Threshold (& NaN) mask + if xM.I & ~YNaNrep & xM.TH(i) < 0 %-Use implicit mask + Cm(Cm) = abs(Y(i,Cm)) > eps; + end + end + + %-Mask out voxels where data is constant + %--------------------------------------------------------------- + Cm(Cm) = any(diff(Y(:,Cm),1)); + Y = Y(:,Cm); %-Data within mask + CrS = sum(Cm); %-# current voxels + + + %=============================================================== + %-Proceed with General Linear Model (if there are voxels) + %=============================================================== + if CrS + + %-Whiten/Weight data and remove filter confounds + %------------------------------------------------------- + fprintf('%s%30s',sprintf('\b')*ones(1,30),'filtering') %-# + + KWY = spm_filter(xX.K,W*Y); + + %-General linear model: Weighted least squares estimation + %------------------------------------------------------ + fprintf('%s%30s',sprintf('\b')*ones(1,30),' estimation') %-# + + beta = xX.pKX*KWY; %-Parameter estimates + res = spm_sp('r',xX.xKXs,KWY); %-Residuals + ResSS = sum(res.^2); %-Residual SSQ + clear KWY %-Clear to save memory + + + %-If ReML hyperparameters are needed for xVi.V + %------------------------------------------------------- + if ~isfield(xVi,'V') + + %-F-threshold & accumulate spatially whitened Y*Y' + %----------------------------------------------- + j = sum((Hsqr*beta).^2,1)/trMV > UF*ResSS/trRV; + j = find(j); + if length(j) + q = size(j,2); + s = s + q; + q = spdiags(sqrt(trRV./ResSS(j)'),0,q,q); + Y = Y(:,j)*q; + Cy = Cy + Y*Y'; + end + + end % (xVi,'V') + + + %-if we are saving the WLS parameters + %------------------------------------------------------- + if isfield(xX,'W') + + %-sample covariance and mean of Y (all voxels) + %----------------------------------------------- + CY = CY + Y*Y'; + EY = EY + sum(Y,2); + + %-Save betas etc. for current plane as we go along + %----------------------------------------------- + CrBl = [CrBl, beta]; + CrResI = [CrResI, res(i_res,:)]; + CrResSS = [CrResSS, ResSS]; + + end % (xX,'W') + clear Y %-Clear to save memory + + end % (CrS) + + %-Append new inmask voxel locations and volumes + %--------------------------------------------------------------- + XYZ(:,S + [1:CrS]) = xyz(:,Cm); %-InMask XYZ voxel coords + Q = [Q I(Cm)]; %-InMask XYZ voxel indices + S = S + CrS; %-Volume analysed (voxels) + + end % (bch) + + + %-Plane complete, write plane to image files (unless 1st pass) + %=================================================================== + if isfield(xX,'W') + + fprintf('%s%30s',sprintf('\b')*ones(1,30),'...saving plane') %-# + + %-Write Mask image + %------------------------------------------------------------------- + j = sparse(xdim,ydim); + if length(Q), j(Q) = 1; end + VM = spm_write_plane(VM, j, z); + + %-Write beta images + %------------------------------------------------------------------- + j = NaN*ones(xdim,ydim); + for i = 1:nBeta + if length(Q), j(Q) = CrBl(i,:); end + Vbeta(i) = spm_write_plane(Vbeta(i), j, z); + end + + %-Write residual images + %------------------------------------------------------------------- + for i = 1:nSres + if length(Q), j(Q) = CrResI(i,:); end + VResI(i) = spm_write_plane(VResI(i), j, z); + end + + %-Write ResSS into ResMS (variance) image scaled by tr(RV) above + %------------------------------------------------------------------- + if length(Q), j(Q) = CrResSS; end + VResMS = spm_write_plane(VResMS,j,z); + + end % (xX,'W') + + %-Report progress + %------------------------------------------------------------------- + fprintf('%s%30s',sprintf('\b')*ones(1,30),'...done') %-# + spm_progress_bar('Set',100*(bch + nbch*(z - 1))/(nbch*zdim)); + + +end % (for z = 1:zdim) +fprintf('\n') %-# +spm_progress_bar('Clear') + +%======================================================================= +% - P O S T E S T I M A T I O N C L E A N U P +%======================================================================= +if S == 0, warning('No inmask voxels - empty analysis!'), end + +%-close written image files (unless 1st pass) +%======================================================================= +if isfield(xX,'W') + + fprintf('%s%30s\n',sprintf('\b')*ones(1,30),'...closing files') %-# + VM = spm_close_vol(VM); + Vbeta = spm_close_vol(Vbeta); + VResI = spm_close_vol(VResI); + VResMS = spm_close_vol(VResMS); + +end % (xVi,'V') + +%-average sample covariance and mean of Y (over voxels) +%----------------------------------------------------------------------- +CY = CY/S; +EY = EY/S; +CY = CY - EY*EY'; + +%-If not defined, compute non-sphericity V using ReML Hyperparameters +%======================================================================= +if ~isfield(xVi,'V') + + %-REML estimate of residual correlations through hyperparameters (h) + %--------------------------------------------------------------- + str = 'Temporal non-sphericity (over voxels)'; + fprintf('%-40s: %30s\n',str,'...REML estimation') %-# + Cy = Cy/s; + + % ReML for separable designs and covariance components + %--------------------------------------------------------------- + if isstruct(xX.K) + m = length(xVi.Vi); + h = zeros(m,1); + V = sparse(nScan,nScan); + for i = 1:length(xX.K) + + % extract blocks from bases + %----------------------------------------------- + q = xX.K(i).row; + p = []; + Qp = {}; + for j = 1:m + if nnz(xVi.Vi{j}(q,q)) + Qp{end + 1} = xVi.Vi{j}(q,q); + p = [p j]; + end + end + + % design space for ReML (with confounds in filter) + %----------------------------------------------- + Xp = xX.X(q,:); + try + Xp = [Xp xX.K(i).X0]; + end + + % ReML + %----------------------------------------------- + fprintf('%-30s- %i\n',' ReML Block',i); + [Vp,hp] = spm_reml(Cy(q,q),Xp,Qp); + V(q,q) = V(q,q) + Vp; + h(p) = hp; + end + else + [V,h] = spm_reml(Cy,xX.X,xVi.Vi); + end + + % normalize non-sphericity and save hyperparameters + %--------------------------------------------------------------- + V = V*nScan/trace(V); + xVi.h = h; + xVi.V = V; % Save non-sphericity xVi.V + xVi.Cy = Cy; %-spatially whitened + SPM.xVi = xVi; % non-sphericity structure + + % If xX.W is not specified use W*W' = inv(V) to give ML estimators + %--------------------------------------------------------------- + if ~isfield(xX,'W') + save SPM SPM + clear + load SPM + SPM = spm_spm(SPM); + return + end +end + + +%-Use non-sphericity xVi.V to compute [effective] degrees of freedom +%======================================================================= +xX.V = spm_filter(xX.K,spm_filter(xX.K,W*V*W')'); % KWVW'K' +[trRV trRVRV] = spm_SpUtil('trRV',xX.xKXs,xX.V); % trRV (for X) +xX.trRV = trRV; % +xX.trRVRV = trRVRV; %-Satterthwaite +xX.erdf = trRV^2/trRVRV; % approximation +xX.Bcov = xX.pKX*xX.V*xX.pKX'; % Cov(beta) + + +%-Set VResMS scalefactor as 1/trRV (raw voxel data is ResSS) +%----------------------------------------------------------------------- +VResMS.pinfo(1) = 1/xX.trRV; +VResMS = spm_create_vol(VResMS,'noopen'); + + +%-Create 1st contrast for 'effects of interest' (all if not specified) +%======================================================================= +Fcname = 'effects of interest'; +try + iX0 = [xX.iB xX.iG]; +catch + iX0 = []; +end +xCon = spm_FcUtil('Set',Fcname,'F','iX0',iX0,xX.xKXs); + +%-Append contrasts for fMRI - specified by SPM.Sess(s).Fc(i) +%----------------------------------------------------------------------- +if isfield(SPM,'Sess') + for s = 1:length(SPM.Sess) + for i = 1:length(SPM.Sess(s).Fc) + iX0 = 1:nBeta; + iX = SPM.Sess(s).col(SPM.Sess(s).Fc(i).i); + iX0(iX) = []; + Fcname = sprintf('Sess(%d):%s',s,SPM.Sess(s).Fc(i).name); + xCon(end + 1) = spm_FcUtil('Set',Fcname,'F','iX0',iX0,xX.xKXs); + end + end +end + +%-Smoothness estimates of component fields and RESEL counts for volume +%======================================================================= +try + FWHM = SPM.xVol.FWHM; + VRpv = SPM.xVol.VRpv; + R = SPM.xVol.R; +catch + [FWHM,VRpv] = spm_est_smoothness(VResI,VM); + R = spm_resels_vol(VM,FWHM)'; +end + +%-Delete the residuals images +%======================================================================= +for i = 1:nSres, + spm_unlink([spm_str_manip(VResI(i).fname,'r') '.img']); + spm_unlink([spm_str_manip(VResI(i).fname,'r') '.hdr']); + spm_unlink([spm_str_manip(VResI(i).fname,'r') '.mat']); +end + + +%-Compute scaled design matrix for display purposes +%----------------------------------------------------------------------- +xX.nKX = spm_DesMtx('sca',xX.xKXs.X,xX.name); + + +fprintf('%s%30s\n',sprintf('\b')*ones(1,30),'...done') %-# + +%-Save remaining results files and analysis parameters +%======================================================================= +fprintf('%-40s: %30s','Saving results','...writing') %-# + +%-place fields in SPM +%----------------------------------------------------------------------- +SPM.xVol.XYZ = XYZ(:,1:S); %-InMask XYZ coords (voxels) +SPM.xVol.M = M; %-voxels -> mm +SPM.xVol.iM = inv(M); %-mm -> voxels +SPM.xVol.DIM = DIM; %-image dimensions +SPM.xVol.FWHM = FWHM; %-Smoothness data +SPM.xVol.R = R; %-Resel counts +SPM.xVol.S = S; %-Volume (voxels) +SPM.xVol.VRpv = VRpv; %-Filehandle - Resels per voxel + + +SPM.Vbeta = Vbeta; %-Filehandle - Beta +SPM.VResMS = VResMS; %-Filehandle - Hyperparameter +SPM.VM = VM; %-Filehandle - Mask + +SPM.xVi = xVi; % non-sphericity structure +SPM.xVi.CY = CY; %-<(Y - )*(Y - )'> + +SPM.xX = xX; %-design structure +SPM.xM = xM; %-mask structure + +SPM.xCon = xCon; %-contrast structure + +SPM.SPMid = SPMid; +SPM.swd = pwd; + + +%-Save analysis parameters in SPM.mat file +%----------------------------------------------------------------------- +save SPM SPM + +%======================================================================= +%- E N D: Cleanup GUI +%======================================================================= +fprintf('%s%30s\n',sprintf('\b')*ones(1,30),'...done') %-# +spm('FigName','Stats: done',Finter); spm('Pointer','Arrow') +fprintf('%-40s: %30s\n','Completed',spm('time')) %-# +fprintf('...use the results section for assessment\n\n') %-# diff --git a/spm/spm2/spm_vol.m b/spm/spm2/spm_vol.m new file mode 100644 index 0000000..e703e7c --- /dev/null +++ b/spm/spm2/spm_vol.m @@ -0,0 +1,152 @@ +function V = spm_vol(P) +% Get header information etc for images. +% FORMAT V = spm_vol(P) +% P - a matrix of filenames. +% V - a vector of structures containing image volume information. +% The elements of the structures are: +% V.fname - the filename of the image. +% V.dim - the x, y and z dimensions of the volume, and the +% datatype of the image. +% V.mat - a 4x4 affine transformation matrix mapping from +% voxel coordinates to real world coordinates. +% V.pinfo - plane info for each plane of the volume. +% V.pinfo(1,:) - scale for each plane +% V.pinfo(2,:) - offset for each plane +% The true voxel intensities of the jth image are given +% by: val*V.pinfo(1,j) + V.pinfo(2,j) +% V.pinfo(3,:) - offset into image (in bytes). +% If the size of pinfo is 3x1, then the volume is assumed +% to be contiguous and each plane has the same scalefactor +% and offset. +%____________________________________________________________________________ +% +% The fields listed above are essential for the mex routines, but other +% fields can also be incorporated into the structure. +% +% The images are not memory mapped at this step, but are mapped when +% the mex routines using the volume information are called. +% +% This is a replacement for the spm_map_vol and spm_unmap_vol stuff of +% MatLab4 SPMs (SPM94-97), which is now obsolete. +%_______________________________________________________________________ +% @(#)spm_vol.m 2.16 John Ashburner 03/05/23 + +% If is already a vol structure then just return; +if isstruct(P), V = P; return; end; + +V = subfunc2(P); +return; + +function V = subfunc2(P) +try + if iscell(P), + V = cell(size(P)); + for j=1:prod(size(P)), + if iscell(P{j}), + V{j} = subfunc2(P{j}); + else, + V{j} = subfunc1(P{j}); + end; + end; + else + V = subfunc1(P); + end; +catch + warning('Possible disk swapping in spm_vol.m'); + if iscell(P) + if P{1}(2)==':' + for i=1:size(P(:),1) + P{i}=P{i}(:,3:end); + end + end + else + if P(1,2)==':' + P=P(:,3:end); + end + end + if iscell(P), + V = cell(size(P)); + for j=1:prod(size(P)), + if iscell(P{j}), + V{j} = subfunc2(P{j}); + else, + V{j} = subfunc1(P{j}); + end; + end; + else + V = subfunc1(P); + end; + +end + +return; + +function V = subfunc1(P) +if size(P,1)==0, + V=[]; +else, + V(size(P,1),1) = struct('fname','', 'dim', [0 0 0 0], 'mat',eye(4), 'pinfo', [1 0 0]'); +end; +for i=1:size(P,1), + v = subfunc(P(i,:)); + if isempty(v), + hread_error_message(P(i,:)); + error(['Can''t get volume information for ''' P(i,:) '''']); + end; + f = fieldnames(v); + for j=1:size(f,1), + eval(['V(i).' f{j} ' = v.' f{j} ';']); + %V(i) = setfield(V(i),f{j},getfield(v,f{j})); + end; +end; +return; + +function V = subfunc(p) +p = deblank(p); +[pth,nam,ext] = fileparts(deblank(p)); +t = find(ext==','); + +n = []; +if ~isempty(t), + if length(t)==1, + n1 = ext((t+1):end); + if ~isempty(n1), + n = str2num(n1); + ext = ext(1:(t-1)); + end; + end; +end; +p = fullfile(pth,[nam ext]); + +if strcmp(ext,'.img') & exist(fullfile(pth,[nam '.hdr'])) == 2, + if isempty(n), V = spm_vol_ana(p); + else, V = spm_vol_ana(p,n); end; + if ~isempty(V), return; end; +else, % Try other formats + + % Try MINC format + if isempty(n), V=spm_vol_minc(p); + else, V=spm_vol_minc(p,n); end; + if ~isempty(V), return; end; + + % Try Ecat 7 + if isempty(n), V=spm_vol_ecat7(p); + else, V=spm_vol_ecat7(p,n); end; + if ~isempty(V), return; end; +end; +return; + + +%_______________________________________________________________________ +function hread_error_message(q) + +str = {... + 'Error reading information on:',... + [' ',spm_str_manip(q,'k40d')],... + ' ',... + 'Please check that it is in the correct format.'}; + +spm('alert*',str,mfilename,sqrt(-1)); + +return; +%_______________________________________________________________________ diff --git a/spm/spm5/spm_2008Oct17.ps b/spm/spm5/spm_2008Oct17.ps new file mode 100644 index 0000000000000000000000000000000000000000..e0b34fef62391183643b36de9c19574dd226f4ed GIT binary patch literal 13404 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 V(&l+Dy6oT0;EtU&y?=9f@PDzHo0b3o literal 0 HcmV?d00001 diff --git a/spm/spm5/spm_config_realign_log.m b/spm/spm5/spm_config_realign_log.m new file mode 100644 index 0000000..d766ec2 --- /dev/null +++ b/spm/spm5/spm_config_realign_log.m @@ -0,0 +1,225 @@ +function logtxt = spm_config_realign_log(prog,val,options) +% spm_config_realign(prog,val) - Generates log for spm_realign +% [logtxt] = spm_config_realign(prog,val) +% logtxt is a cell array of string +% Example +% >> spm_log2html +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2008 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2008-10-17 Creation +% +% ----------------------------- Script History --------------------------------- + +% TODO : +% [logtxt] = spm_config_realign(job) +% [logtxt] = spm_config_realign(jobs) + +prog_infos=functions(prog) +jobname=sprintf('%s/', prog_infos.parentage{end:-1:1}); +jobname(end)=[]; +logtxt={}; +logtxt{end+1}=['SPM logging for: ' jobname]; +htmlfile = fullfile(fileparts(getfield(spm_vol(val.data{1}{1}),'fname')),'log',['log_' datestr(now,30)],[jobname '.html']); +logtxt{end+1}=['Into: ']; +logtxt{end+1}={'file' htmlfile}; +htmldir = fileparts(htmlfile); +if not(exist(htmldir, 'dir')) + logtxt{end+1}=['Creating new directory: ' ]; + logtxt{end+1}={'folder', htmldir}; + mkdir(htmldir) +end +try + switch jobname + case 'spm_config_realign/estimate' + mfile = which('spm_realign'); + if ~isequal(textread(mfile,'%s',4,'headerlines',82,'delimiter',' '),... + {'%', '$Id:', 'spm_realign.m', '433'}') % The native version at the time + logtxt{end+1}=sprintf('The spm_realign.m (%s) has changed! Default flags may also have changed.', mfile); + warning(logtxt(1)); + end + disp('REALIGNING IS COOL!') + def_flags = struct('quality',1,'fwhm',5,'sep',4,'interp',2,'wrap',[0 0 0],'rtm',0,'PW','','graphics',1,'lkp',1:6); + flags =mergestructs(def_flags,val.eoptions); + if flags.graphics + opts = {fullfile(htmldir,'realign_estimate.png'),'-noui','-painters','-dpng'}; + fg = spm_figure('FindWin','Graphics'); + logtxt{end+1} = {'Plot of the Movement Parameters'}; + print(fg,opts{:}); + logtxt{end+1} = {'picture',opts{1}}; + + else + logtxt{end+1}='Graphics must be generated for logging. Sorry...'; + + % rp_file = [spm_str_manip(prepend(getfield(spm_vol(val.data{1}{1}),'fname'),'rp_'),'s') '.txt']; + % P=textread(rp_file); + % plot_parameters(P) + end + case 'spm_config_realign/reslice'; + % to do + case 'spm_config_realign/estwrite_fun'; + % to do + otherwise + logtxt{end+1}=['Unknown function to log: ' jobname]; +end +catch + logtxt{end+1}=['Error while logging! ' lasterr]; +end +spm_log2html(htmlfile,logtxt) +return + + +% spm_realign +%_______________________________________________________________________ +function PO = prepend(PI,pre) +[pth,nm,xt,vr] = fileparts(deblank(PI)); +PO = fullfile(pth,[pre nm xt vr]); +return; + + +%_______________________________________________________________________ +function plot_parameters(P) +fg=spm_figure('FindWin','Graphics'); +if ~isempty(fg), + P = cat(1,P{:}); + if length(P)<2, return; end; + Params = zeros(numel(P),12); + for i=1:numel(P), + Params(i,:) = spm_imatrix(P(i).mat/P(1).mat); + end + + % display results + % translation and rotation over time series + %------------------------------------------------------------------- + spm_figure('Clear','Graphics'); + ax=axes('Position',[0.1 0.65 0.8 0.2],'Parent',fg,'Visible','off'); + set(get(ax,'Title'),'String','Image realignment','FontSize',16,'FontWeight','Bold','Visible','on'); + x = 0.1; + y = 0.9; + for i = 1:min([numel(P) 12]) + text(x,y,[sprintf('%-4.0f',i) P(i).fname],'FontSize',10,'Interpreter','none','Parent',ax); + y = y - 0.08; + end + if numel(P) > 12 + text(x,y,'................ etc','FontSize',10,'Parent',ax); end + + ax=axes('Position',[0.1 0.35 0.8 0.2],'Parent',fg,'XGrid','on','YGrid','on'); + plot(Params(:,1:3),'Parent',ax) + s = ['x translation';'y translation';'z translation']; + %text([2 2 2], Params(2, 1:3), s, 'Fontsize',10,'Parent',ax) + legend(ax, s, 0) + set(get(ax,'Title'),'String','translation','FontSize',16,'FontWeight','Bold'); + set(get(ax,'Xlabel'),'String','image'); + set(get(ax,'Ylabel'),'String','mm'); + + + ax=axes('Position',[0.1 0.05 0.8 0.2],'Parent',fg,'XGrid','on','YGrid','on'); + plot(Params(:,4:6)*180/pi,'Parent',ax) + s = ['pitch';'roll ';'yaw ']; + %text([2 2 2], Params(2, 4:6)*180/pi, s, 'Fontsize',10,'Parent',ax) + legend(ax, s, 0) + set(get(ax,'Title'),'String','rotation','FontSize',16,'FontWeight','Bold'); + set(get(ax,'Xlabel'),'String','image'); + set(get(ax,'Ylabel'),'String','degrees'); + + % print realigment parameters + spm_print +end +%_______________________________________________________________________ + + + + +%------------------------------------------------------------------------ +function estimate(varargin) +job = varargin{1}; +P = {}; +for i=1:length(job.data), + P{i} = strvcat(job.data{i}); +end; +flags.quality = job.eoptions.quality; +flags.fwhm = job.eoptions.fwhm; +flags.sep = job.eoptions.sep; +flags.rtm = job.eoptions.rtm; +flags.PW = strvcat(job.eoptions.weight); +flags.interp = job.eoptions.interp; +flags.wrap = job.eoptions.wrap; +spm_realign(P,flags); +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function reslice(varargin) +job = varargin{1}; +P = strvcat(job.data); +flags.mask = job.roptions.mask; +flags.mean = job.roptions.which(2); +flags.interp = job.roptions.interp; +flags.which = job.roptions.which(1); +flags.wrap = job.roptions.wrap; +spm_reslice(P,flags); +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function estwrite_fun(varargin) +job = varargin{1}; +P = {}; +for i=1:length(job.data), + P{i} = strvcat(job.data{i}); +end; +flags.quality = job.eoptions.quality; +flags.fwhm = job.eoptions.fwhm; +flags.sep = job.eoptions.sep; +flags.rtm = job.eoptions.rtm; +flags.PW = strvcat(job.eoptions.weight); +flags.interp = job.eoptions.interp; +flags.wrap = job.eoptions.wrap; +spm_realign(P,flags); + +P = strvcat(P); +flags.mask = job.roptions.mask; +flags.mean = job.roptions.which(2); +flags.interp = job.roptions.interp; +flags.which = job.roptions.which(1); +flags.wrap = job.roptions.wrap; +spm_reslice(P,flags); +return; + +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function vf = vfiles_reslice(job) +P = job.data; +if numel(P)>0 && iscell(P{1}), + P = cat(1,P{:}); +end; + +switch job.roptions.which(1), + case 0, + vf = {}; + case 1, + vf = cell(numel(P)-1,1); + for i=1:length(vf), + [pth,nam,ext,num] = spm_fileparts(P{i+1}); + vf{i} = fullfile(pth,['r', nam, ext, num]); + end; + otherwise, + vf = cell(numel(P),1); + for i=1:length(vf), + [pth,nam,ext,num] = spm_fileparts(P{i}); + vf{i} = fullfile(pth,['r', nam, ext, num]); + end; +end; +if job.roptions.which(2), + [pth,nam,ext,num] = spm_fileparts(P{1}); + vf = {vf{:}, fullfile(pth,['mean', nam, ext, num])}; +end; diff --git a/spm/spm5/spm_dicom_convert.m b/spm/spm5/spm_dicom_convert.m new file mode 100644 index 0000000..2b13239 --- /dev/null +++ b/spm/spm5/spm_dicom_convert.m @@ -0,0 +1,1180 @@ +function spm_dicom_convert(hdr,opts,root_dir,format) + +% Convert DICOM images into something that SPM can use +% FORMAT spm_dicom_convert(hdr,opts,root_dir,format) +% hdr - a cell array of DICOM headers from spm_dicom_headers +% opts - options +% 'all' - all DICOM files (default) +% 'mosaic' - the mosaic images +% 'standard' - standard DICOM files +% 'spect' - SIEMENS Spectroscopy DICOMs (position only) +% This will write out a mask image volume with 1's +% set at the position of spectroscopy voxel(s). +% 'raw' - convert raw FIDs (not implemented) +% root_dir - 'flat' - SPM5 standard, do not produce file tree +% With all other options, files will be sorted into +% directories according to their sequence/protocol names +% 'date_time' - Place files under ./ +% 'patid' - Place files under ./ +% 'patid_date' - Place files under ./ +% 'name' - Place files under ./ +% format - output format +% 'img' Two file (hdr+img) NIfTI format +% 'nii' Single file NIfTI format +% All images will contain a single 3D dataset, 4D images +% will not be created. +%_______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience +% +% John Ashburner & Jesper Andersson +% $Id: spm_dicom_convert.m 917 2007-09-14 09:28:55Z volkmar $ +% +% This version of spm_dicom_convert has been hacked so that it can deal +% with MOSAIC type functional images whose headers have been stripped of +% the CSA header information. CSA refers to private DICOM header fields +% used by SIEMENS, which can go missing when images are archived to a DICOM +% server. The result is that SPM's DICOM import produces volumes that are +% very large, very thin, and contain a collection of seperate slices of +% brain. Now it seems to work for the images I have tested it with, but it +% may not work for yours. In particular, it is important to carefully check +% that your images are correctly oriented (not right-left flipped for +% example). +% +% I have also changed the default setting for the output file format +% from 'img' to 'nii'. +% +% SWR 2008-08-26 +% Sebastian Rieger, LabNIC, University of Geneva + + + +if nargin<2, opts = 'all'; end; +if nargin<3, root_dir='flat';end; +if nargin<4, format='nii';end; % Changed this from 'img' to 'nii'. + % SWR 2008-08-26 + +[images,guff] = select_tomographic_images(hdr); +[spect,images] = select_spectroscopy_images(images); +[mosaic,standard] = select_mosaic_images(images); + +if (strcmp(opts,'all') || strcmp(opts,'mosaic')) && ~isempty(mosaic), + convert_mosaic(mosaic,root_dir,format); +end; +if (strcmp(opts,'all') || strcmp(opts,'standard')) && ~isempty(standard), + convert_standard(standard,root_dir,format); +end; +if (strcmp(opts,'all') || strcmp(opts,'spect')) && ~isempty(spect), + convert_spectroscopy(spect,root_dir,format); +end; +return; +%_______________________________________________________________________ + +%_______________________________________________________________________ +function convert_mosaic(hdr,root_dir,format) +spm_progress_bar('Init',length(hdr),'Writing Mosaic', 'Files written'); + +for i=1:length(hdr), + + % Output filename + %------------------------------------------------------------------- + fname = getfilelocation(hdr{i},root_dir,'f',format); + + % Image dimensions and data + %------------------------------------------------------------------- + nc = hdr{i}.Columns; + nr = hdr{i}.Rows; + + dim = [0 0 0]; + + try + dim(3) = read_NumberOfImagesInMosaic(hdr{i}); + % This will fail if the CSA header information is missing from a + % mosaic type DICOM file, so I have put it in a "try" statement. + % SWR 2008-08-20 + catch + dim(3) = (nc*nr) / ((max(hdr{i}.AcquisitionMatrix))^2); + % This is an attempt to guess the number of images in the mosaic if + % it cannot be read from CSA header information. Instead, the + % overall dimensions of the mosaic in pixels and the size of the + % acquisition matrix ("Columns", "Rows", and "AcquisitionMatrix" + % in the DICOM header) are used. + % SWR 2008-08-20 + end + + np = [nc nr]/ceil(sqrt(dim(3))); + dim(1:2) = np; + if ~all(np==floor(np)), + warning('%s: dimension problem [Num Images=%d, Num Cols=%d, Num Rows=%d].',... + hdr{i}.Filename,dim(3), nc,nr); + continue; + end; + + % Apparently, this is not the right way of doing it. + %np = read_AcquisitionMatrixText(hdr{i}); + %if rem(nc, np(1)) || rem(nr, np(2)), + % warning('%s: %dx%d wont fit into %dx%d.',hdr{i}.Filename,... + % np(1), np(2), nc,nr); + % return; + %end; + %dim = [np read_NumberOfImagesInMosaic(hdr{i})]; + + mosaic = read_image_data(hdr{i}); + volume = zeros(dim); + for j=1:dim(3), + img = mosaic((1:np(1))+np(1)*rem(j-1,nc/np(1)), (np(2):-1:1)+np(2)*floor((j-1)/(nc/np(1)))); + if ~any(img(:)), + volume = volume(:,:,1:(j-1)); + break; + end; + volume(:,:,j) = img; + end; + dim = size(volume); + dt = [spm_type('int16') spm_platform('bigend')]; + + % Orientation information + %------------------------------------------------------------------- + % Axial Analyze voxel co-ordinate system: + % x increases right to left + % y increases posterior to anterior + % z increases inferior to superior + + % DICOM patient co-ordinate system: + % x increases right to left + % y increases anterior to posterior + % z increases inferior to superior + + % T&T co-ordinate system: + % x increases left to right + % y increases posterior to anterior + % z increases inferior to superior + + analyze_to_dicom = [diag([1 -1 1]) [0 (dim(2)-1) 0]'; 0 0 0 1]*[eye(4,3) [-1 -1 -1 1]']; + + vox = [hdr{i}.PixelSpacing hdr{i}.SpacingBetweenSlices]; + pos = hdr{i}.ImagePositionPatient'; + orient = reshape(hdr{i}.ImageOrientationPatient,[3 2]); + orient(:,3) = null(orient'); + if det(orient)<0, orient(:,3) = -orient(:,3); end; + + % The image position vector is not correct. In dicom this vector points to + % the upper left corner of the image. Perhaps it is unlucky that this is + % calculated in the syngo software from the vector pointing to the center of + % the slice (keep in mind: upper left slice) with the enlarged FoV. + dicom_to_patient = [orient*diag(vox) pos ; 0 0 0 1]; + truepos = dicom_to_patient *[(size(mosaic)-dim(1:2))/2 0 1]'; + dicom_to_patient = [orient*diag(vox) truepos(1:3) ; 0 0 0 1]; + patient_to_tal = diag([-1 -1 1 1]); + mat = patient_to_tal*dicom_to_patient*analyze_to_dicom; + + + + % Maybe flip the image depending on SliceNormalVector from 0029,1010 + %------------------------------------------------------------------- + try + SliceNormalVector = read_SliceNormalVector(hdr{i}); + % This will fail if CSA header information is missing from a + % mosaic type DICOM file, so I have put it into a "try" statement. + % SWR 2008-08-20 + if det([reshape(hdr{i}.ImageOrientationPatient,[3 2]) SliceNormalVector(:)])<0; + volume = volume(:,:,end:-1:1); + mat = mat*[eye(3) [0 0 -(dim(3)-1)]'; 0 0 0 1]; + end; + catch + + % Ideally, another way to check orientation (without relying on + % CSA header information) should be implemented here. Until then, + % image orientation must be carefully checked manually! + % SWR 2008-08-20 + + end; + + + % Possibly useful information + %------------------------------------------------------------------- + tim = datevec(hdr{i}.AcquisitionTime/(24*60*60)); + descrip = sprintf('%gT %s %s TR=%gms/TE=%gms/FA=%gdeg %s %d:%d:%.5g Mosaic',... + hdr{i}.MagneticFieldStrength, hdr{i}.MRAcquisitionType,... + deblank(hdr{i}.ScanningSequence),... + hdr{i}.RepetitionTime,hdr{i}.EchoTime,hdr{i}.FlipAngle,... + datestr(hdr{i}.AcquisitionDate),tim(4),tim(5),tim(6)); + + % descrip = [deblank(descrip) ' ' hdr{i}.PatientsName]; + + if ~true, % LEFT-HANDED STORAGE + mat = mat*[-1 0 0 (dim(1)+1); 0 1 0 0; 0 0 1 0; 0 0 0 1]; + volume = flipdim(volume,1); + end; + + %if isfield(hdr{i},'RescaleSlope') && hdr{i}.RescaleSlope ~= 1, + % volume = volume*hdr{i}.RescaleSlope; + %end; + %if isfield(hdr{i},'RescaleIntercept') && hdr{i}.RescaleIntercept ~= 0, + % volume = volume + hdr{i}.RescaleIntercept; + %end; + %V = struct('fname',fname, 'dim',dim, 'dt',dt, 'mat',mat, 'descrip',descrip); + %spm_write_vol(V,volume); + + % Note that data are no longer scaled by the maximum amount. + % This may lead to rounding errors in smoothed data, but it + % will get around other problems. + RescaleSlope = 1; + RescaleIntercept = 0; + if isfield(hdr{i},'RescaleSlope') && hdr{i}.RescaleSlope ~= 1, + RescaleSlope = hdr{i}.RescaleSlope; + end; + if isfield(hdr{i},'RescaleIntercept') && hdr{i}.RescaleIntercept ~= 0, + RescaleIntercept = hdr{i}.RescaleIntercept; + end; + N = nifti; + N.dat = file_array(fname,dim,dt,0,RescaleSlope,RescaleIntercept); + N.mat = mat; + N.mat0 = mat; + N.mat_intent = 'Scanner'; + N.mat0_intent = 'Scanner'; + N.descrip = descrip; + create(N); + + % Write the data unscaled + dat = N.dat; + dat.scl_slope = []; + dat.scl_inter = []; + % write out volume at once - see spm_write_plane.m for performance comments + dat(:,:,:) = volume; + + spm_progress_bar('Set',i); +end; +spm_progress_bar('Clear'); +return; +%_______________________________________________________________________ + +%_______________________________________________________________________ +function convert_standard(hdr,root_dir,format) +hdr = sort_into_volumes(hdr); +for i=1:length(hdr), + write_volume(hdr{i},root_dir,format); +end; +return; +%_______________________________________________________________________ + +%_______________________________________________________________________ +function vol = sort_into_volumes(hdr) + +% +% First of all, sort into volumes based on relevant +% fields in the header. +% + +vol{1}{1} = hdr{1}; +for i=2:length(hdr), + orient = reshape(hdr{i}.ImageOrientationPatient,[3 2]); + xy1 = hdr{i}.ImagePositionPatient*orient; + match = 0; + if isfield(hdr{i},'CSAImageHeaderInfo') && isfield(hdr{i}.CSAImageHeaderInfo,'name') + ice1 = sscanf( ... + strrep(get_numaris4_val(hdr{i}.CSAImageHeaderInfo,'ICE_Dims'), ... + 'X', '-1'), '%i_%i_%i_%i_%i_%i_%i_%i_%i')'; + dimsel = logical([1 1 1 1 1 1 0 0 1]); + else + ice1 = []; + end; + for j=1:length(vol), + orient = reshape(vol{j}{1}.ImageOrientationPatient,[3 2]); + xy2 = vol{j}{1}.ImagePositionPatient*orient; + dist2 = sum((xy1-xy2).^2); + + % This line is a fudge because of some problematic data that Bogdan, + % Cynthia and Stefan were trying to convert. I hope it won't cause + % problems for others -JA + dist2 = 0; + + if strcmp(hdr{i}.Modality,'CT') && ... + strcmp(vol{j}{1}.Modality,'CT') % Our CT seems to have shears in slice positions + dist2 = 0; + end; + if ~isempty(ice1) && isfield(vol{j}{1},'CSAImageHeaderInfo') && isfield(vol{j}{1}.CSAImageHeaderInfo(1),'name') + % Replace 'X' in ICE_Dims by '-1' + ice2 = sscanf( ... + strrep(get_numaris4_val(vol{j}{1}.CSAImageHeaderInfo,'ICE_Dims'), ... + 'X', '-1'), '%i_%i_%i_%i_%i_%i_%i_%i_%i')'; + if ~isempty(ice2) + identical_ice_dims=all(ice1(dimsel)==ice2(dimsel)); + else + identical_ice_dims = 0; % have ice1 but not ice2, -> + % something must be different + end, + else + identical_ice_dims = 1; % No way of knowing if there is no CSAImageHeaderInfo + end; + try + match = hdr{i}.SeriesNumber == vol{j}{1}.SeriesNumber &&... + hdr{i}.Rows == vol{j}{1}.Rows &&... + hdr{i}.Columns == vol{j}{1}.Columns &&... + sum((hdr{i}.ImageOrientationPatient - vol{j}{1}.ImageOrientationPatient).^2)<1e-5 &&... + sum((hdr{i}.PixelSpacing - vol{j}{1}.PixelSpacing).^2)<1e-5 && ... + identical_ice_dims && dist2<1e-3; + %if (hdr{i}.AcquisitionNumber ~= hdr{i}.InstanceNumber) || ... + % (vol{j}{1}.AcquisitionNumber ~= vol{j}{1}.InstanceNumber) + % match = match && (hdr{i}.AcquisitionNumber == vol{j}{1}.AcquisitionNumber) + %end; + % For raw image data, tell apart real/complex or phase/magnitude + if isfield(hdr{i},'ImageType') && isfield(vol{j}{1}, 'ImageType') + match = match && strcmp(hdr{i}.ImageType, vol{j}{1}.ImageType); + end; + if isfield(hdr{i},'SequenceName') && isfield(vol{j}{1}, 'SequenceName') + match = match && strcmp(hdr{i}.SequenceName,vol{j}{1}.SequenceName); + end; + if isfield(hdr{i},'SeriesInstanceUID') && isfield(vol{j}{1}, 'SeriesInstanceUID') + match = match && strcmp(hdr{i}.SeriesInstanceUID,vol{j}{1}.SeriesInstanceUID); + end; + if isfield(hdr{i},'EchoNumbers') && isfield(vol{j}{1}, 'EchoNumbers') + match = match && hdr{i}.EchoNumbers == vol{j}{1}.EchoNumbers; + end; + catch + match = 0; + end + if match + vol{j}{end+1} = hdr{i}; + break; + end; + end; + if ~match, + vol{end+1}{1} = hdr{i}; + end; +end; + +dcm = vol; +save('dicom_headers.mat','dcm'); + +% +% Secondly, sort volumes into ascending/descending +% slices depending on .ImageOrientationPatient field. +% + +vol2 = {}; +for j=1:length(vol), + orient = reshape(vol{j}{1}.ImageOrientationPatient,[3 2]); + proj = null(orient'); + if det([orient proj])<0, proj = -proj; end; + + z = zeros(length(vol{j}),1); + for i=1:length(vol{j}), + z(i) = vol{j}{i}.ImagePositionPatient*proj; + end; + [z,index] = sort(z); + vol{j} = vol{j}(index); + if length(vol{j})>1, + % dist = diff(z); + if any(diff(z)==0) + tmp = sort_into_vols_again(vol{j}); + vol{j} = tmp{1}; + vol2 = {vol2{:} tmp{2:end}}; + end; + end; +end; +vol = {vol{:} vol2{:}}; +for j=1:length(vol), + if length(vol{j})>1, + orient = reshape(vol{j}{1}.ImageOrientationPatient,[3 2]); + proj = null(orient'); + if det([orient proj])<0, proj = -proj; end; + z = zeros(length(vol{j}),1); + for i=1:length(vol{j}), + z(i) = vol{j}{i}.ImagePositionPatient*proj; + end; + [z,index] = sort(z); + dist = diff(z); + if sum((dist-mean(dist)).^2)/length(dist)>1e-4, + fprintf('***************************************************\n'); + fprintf('* VARIABLE SLICE SPACING *\n'); + fprintf('* This may be due to missing DICOM files. *\n'); + if checkfields(vol{j}{1},'PatientID','SeriesNumber','AcquisitionNumber','InstanceNumber'), + fprintf('* %s / %d / %d / %d \n',... + deblank(vol{j}{1}.PatientID), vol{j}{1}.SeriesNumber, ... + vol{j}{1}.AcquisitionNumber, vol{j}{1}.InstanceNumber); + fprintf('* *\n'); + end; + fprintf('* %20.4g *\n', dist); + fprintf('***************************************************\n'); + end; + end; +end; +return; +%_______________________________________________________________________ + +%_______________________________________________________________________ +function vol2 = sort_into_vols_again(volj) +if ~isfield(volj{1},'InstanceNumber'), + fprintf('***************************************************\n'); + fprintf('* The slices may be all mixed up and the data *\n'); + fprintf('* not really usable. Talk to your physicists *\n'); + fprintf('* about this. *\n'); + fprintf('***************************************************\n'); + vol2 = {volj}; + return; +end; + +fprintf('***************************************************\n'); +fprintf('* The AcquisitionNumber counter does not appear *\n'); +fprintf('* to be changing from one volume to another. *\n'); +fprintf('* Another possible explanation is that the same *\n'); +fprintf('* DICOM slices are used multiple times. *\n'); +%fprintf('* Talk to your MR sequence developers or scanner *\n'); +%fprintf('* supplier to have this fixed. *\n'); +fprintf('* The conversion is having to guess how slices *\n'); +fprintf('* should be arranged into volumes. *\n'); +if checkfields(volj{1},'PatientID','SeriesNumber','AcquisitionNumber'), + fprintf('* %s / %d / %d\n',... + deblank(volj{1}.PatientID), volj{1}.SeriesNumber, ... + volj{1}.AcquisitionNumber); +end; +fprintf('***************************************************\n'); + +z = zeros(length(volj),1); +t = zeros(length(volj),1); +d = zeros(length(volj),1); +orient = reshape(volj{1}.ImageOrientationPatient,[3 2]); +proj = null(orient'); +if det([orient proj])<0, proj = -proj; end; + +for i=1:length(volj), + z(i) = volj{i}.ImagePositionPatient*proj; + t(i) = volj{i}.InstanceNumber; +end; +% msg = 0; +[t,index] = sort(t); +volj = volj(index); +z = z(index); +msk = find(diff(t)==0); +if any(msk), + % fprintf('***************************************************\n'); + % fprintf('* These files have the same InstanceNumber: *\n'); + % for i=1:length(msk), + % [tmp,nam1,ext1] = fileparts(volj{msk(i)}.Filename); + % [tmp,nam2,ext2] = fileparts(volj{msk(i)+1}.Filename); + % fprintf('* %s%s = %s%s (%d)\n', nam1,ext1,nam2,ext2, volj{msk(i)}.InstanceNumber); + % end; + % fprintf('***************************************************\n'); + index = [true ; diff(t)~=0]; + t = t(index); + z = z(index); + d = d(index); + volj = volj(index); +end; + +%if any(diff(sort(t))~=1), msg = 1; end; +[z,index] = sort(z); +volj = volj(index); +t = t(index); +vol2 = {}; +while ~all(d), + i = find(~d); + i = i(1); + i = find(z==z(i)); + [t(i),si] = sort(t(i)); + volj(i) = volj(i(si)); + for i1=1:length(i), + if length(vol2)1, + z = zeros(length(hdr),1); + for i=1:length(hdr), + z(i) = hdr{i}.ImagePositionPatient*orient(:,3); + end; + z = mean(diff(z)); +else + if checkfields(hdr{1},'SliceThickness'), + z = hdr{1}.SliceThickness; + else + z = 1; + end; +end; +vox = [hdr{1}.PixelSpacing z]; +pos = hdr{1}.ImagePositionPatient'; +dicom_to_patient = [orient*diag(vox) pos ; 0 0 0 1]; +patient_to_tal = diag([-1 -1 1 1]); +mat = patient_to_tal*dicom_to_patient*analyze_to_dicom; +if strcmp(hdr{1}.Modality,'CT') && numel(hdr)>1 + shear = (hdr{1}.ImagePositionPatient-hdr{2}.ImagePositionPatient) ... + * reshape(hdr{1}.ImageOrientationPatient,[3 2]); + if shear(1) + warning('shear(1) = %f not applied\n', shear(1)); + end; + warning('shear(2) = %f applied\n', shear(2)); + try + prms = spm_imatrix(mat); + prms(12) = shear(2); + mat = spm_matrix(prms); + end; +end; + +% Possibly useful information +%------------------------------------------------------------------- +if checkfields(hdr{1},'AcquisitionTime','MagneticFieldStrength','MRAcquisitionType',... + 'ScanningSequence','RepetitionTime','EchoTime','FlipAngle',... + 'AcquisitionDate'), + tim = datevec(hdr{1}.AcquisitionTime/(24*60*60)); + descrip = sprintf('%gT %s %s TR=%gms/TE=%gms/FA=%gdeg %s %d:%d:%.5g',... + hdr{1}.MagneticFieldStrength, hdr{1}.MRAcquisitionType,... + deblank(hdr{1}.ScanningSequence),... + hdr{1}.RepetitionTime,hdr{1}.EchoTime,hdr{1}.FlipAngle,... + datestr(hdr{1}.AcquisitionDate),tim(4),tim(5),tim(6)); +else + descrip = hdr{1}.Modality; +end; + +if ~true, % LEFT-HANDED STORAGE + mat = mat*[-1 0 0 (dim(1)+1); 0 1 0 0; 0 0 1 0; 0 0 0 1]; +end; + +pinfo = [1 0 0]'; +if isfield(hdr{1},'RescaleSlope') || isfield(hdr{1},'RescaleIntercept'), + pinfo = repmat(pinfo,1,length(hdr)); + bytepix = spm_type('int16','bits')/8; + for i=1:length(hdr), + if isfield(hdr{i},'RescaleSlope'), pinfo(1,i) = hdr{i}.RescaleSlope; end; + if isfield(hdr{i},'RescaleIntercept'), pinfo(2,i) = hdr{i}.RescaleIntercept; end; + pinfo(3,i) = dim(1)*dim(2)*(i-1)*bytepix; + end; +end; + +% Write the image volume +%------------------------------------------------------------------- +spm_progress_bar('Init',length(hdr),['Writing ' fname], 'Planes written'); +%V = struct('fname',fname, 'dim',dim, 'dt',dt, 'pinfo',pinfo, 'mat',mat, 'descrip',descrip); +%V = spm_create_vol(V); +N = nifti; +N.dat = file_array(fname,dim,dt,0,pinfo(1),pinfo(2)); +N.mat = mat; +N.mat0 = mat; +N.mat_intent = 'Scanner'; +N.mat0_intent = 'Scanner'; +N.descrip = descrip; +create(N); +volume = zeros(dim); + +for i=1:length(hdr), + plane = read_image_data(hdr{i}); + + if isfield(hdr{i},'RescaleSlope'), plane = plane*hdr{i}.RescaleSlope; end; + if isfield(hdr{i},'RescaleIntercept'), plane = plane+hdr{i}.RescaleIntercept; end; + + plane = fliplr(plane); + if ~true, plane = flipud(plane); end; % LEFT-HANDED STORAGE + %V = spm_write_plane(V,plane,i); + % collect all planes before writing to disk + volume(:,:,i) = plane; + spm_progress_bar('Set',i); +end; +% write volume at once - see spm_write_plane for performance comments +N.dat(:,:,:) = volume; +spm_progress_bar('Clear'); +return; +%_______________________________________________________________________ + +%_______________________________________________________________________ +function convert_spectroscopy(hdr,root_dir,format) + +for i=1:length(hdr), + write_spectroscopy_volume(hdr(i),root_dir,format); +end; +return; +%_______________________________________________________________________ + +%_______________________________________________________________________ +function write_spectroscopy_volume(hdr,root_dir,format) +% Output filename +%------------------------------------------------------------------- +fname = getfilelocation(hdr{1}, root_dir,'S',format); + +% Image dimensions +%------------------------------------------------------------------- +nc = get_numaris4_numval(hdr{1}.Private_0029_1210,'Columns'); +nr = get_numaris4_numval(hdr{1}.Private_0029_1210,'Rows'); + +dim = [nc nr numel(hdr)]; +dt = [spm_type('int16') spm_platform('bigend')]; + +% Orientation information +%------------------------------------------------------------------- +% Axial Analyze voxel co-ordinate system: +% x increases right to left +% y increases posterior to anterior +% z increases inferior to superior + +% DICOM patient co-ordinate system: +% x increases right to left +% y increases anterior to posterior +% z increases inferior to superior + +% T&T co-ordinate system: +% x increases left to right +% y increases posterior to anterior +% z increases inferior to superior + +analyze_to_dicom = [diag([1 -1 1]) [0 (dim(2)-1) 0]'; 0 0 0 1]*[eye(4,3) [-1 -1 -1 1]']; +orient = reshape(get_numaris4_numval(hdr{1}.Private_0029_1210,... + 'ImageOrientationPatient'),[3 2]); +orient(:,3) = null(orient'); +if det(orient)<0, orient(:,3) = -orient(:,3); end; +if length(hdr)>1, + z = zeros(length(hdr),1); + for i=1:length(hdr), + z(i) = get_numaris4_numval(hdr{i}.Private_0029_1210,... + 'ImagePositionPatient')*orient(:,3); + end; + z = mean(diff(z)); +else + try + z = get_numaris4_numval(hdr{1}.Private_0029_1210,... + 'SliceThickness'); + catch + z = 1; + end; +end; + +vox = [get_numaris4_numval(hdr{1}.Private_0029_1210,'PixelSpacing') z]; +pos = get_numaris4_numval(hdr{1}.Private_0029_1210,'ImagePositionPatient')'; +%dicom_to_patient = [orient*diag(vox) pos-1.5*orient*([0 vox(2) 0]') ; 0 0 0 1]; +dicom_to_patient = [orient*diag(vox) pos ; 0 0 0 1]; +patient_to_tal = diag([-1 -1 1 1]); +warning('Don''t know exactly what positions in spectroscopy files should be - just guessing!') +mat = patient_to_tal*dicom_to_patient*analyze_to_dicom; +if strcmp(hdr{1}.Modality,'CT') && numel(hdr)>1 + shear = (hdr{1}.ImagePositionPatient-hdr{2}.ImagePositionPatient) ... + * reshape(hdr{1}.ImageOrientationPatient,[3 2]); + if shear(1) + warning('shear(1) = %f not applied\n', shear(1)); + end; + warning('shear(2) = %f applied\n', shear(2)); + try + prms = spm_imatrix(mat); + prms(12) = shear(2); + mat = spm_matrix(prms); + end; +end; + +% Possibly useful information +%------------------------------------------------------------------- +if checkfields(hdr{1},'AcquisitionTime','MagneticFieldStrength','MRAcquisitionType',... + 'ScanningSequence','RepetitionTime','EchoTime','FlipAngle',... + 'AcquisitionDate'), + tim = datevec(hdr{1}.AcquisitionTime/(24*60*60)); + descrip = sprintf('%gT %s %s TR=%gms/TE=%gms/FA=%gdeg %s %d:%d:%.5g',... + hdr{1}.MagneticFieldStrength, hdr{1}.MRAcquisitionType,... + deblank(hdr{1}.ScanningSequence),... + hdr{1}.RepetitionTime,hdr{1}.EchoTime,hdr{1}.FlipAngle,... + datestr(hdr{1}.AcquisitionDate),tim(4),tim(5),tim(6)); +else + descrip = hdr{1}.Modality; +end; + +if ~true, % LEFT-HANDED STORAGE + mat = mat*[-1 0 0 (dim(1)+1); 0 1 0 0; 0 0 1 0; 0 0 0 1]; +end; + +pinfo = [1 0 0]'; +if isfield(hdr{1},'RescaleSlope') || isfield(hdr{1},'RescaleIntercept'), + pinfo = repmat(pinfo,1,length(hdr)); + bytepix = spm_type('int16','bits')/8; + for i=1:length(hdr), + if isfield(hdr{i},'RescaleSlope'), pinfo(1,i) = hdr{i}.RescaleSlope; end; + if isfield(hdr{i},'RescaleIntercept'), pinfo(2,i) = hdr{i}.RescaleIntercept; end; + pinfo(3,i) = dim(1)*dim(2)*(i-1)*bytepix; + end; +end; + +% Write the image volume +%------------------------------------------------------------------- +spm_progress_bar('Init',length(hdr),['Writing ' fname], 'Planes written'); +%V = struct('fname',fname, 'dim',dim, 'dt',dt, 'pinfo',pinfo, 'mat',mat, 'descrip',descrip); +%V = spm_create_vol(V); +N = nifti; +N.dat = file_array(fname,dim,dt,0,pinfo(1),pinfo(2)); +N.mat = mat; +N.mat0 = mat; +N.mat_intent = 'Scanner'; +N.mat0_intent = 'Scanner'; +N.descrip = descrip; +create(N); +volume = zeros(dim); + +for i=1:length(hdr), + plane = read_spect_data(hdr{i}); + + if isfield(hdr{i},'RescaleSlope'), plane = plane*hdr{i}.RescaleSlope; end; + if isfield(hdr{i},'RescaleIntercept'), plane = plane+hdr{i}.RescaleIntercept; end; + + plane = fliplr(plane); + if ~true, plane = flipud(plane); end; % LEFT-HANDED STORAGE + %V = spm_write_plane(V,plane,i); + % collect all planes before writing to disk + volume(:,:,i) = plane; + spm_progress_bar('Set',i); +end; +% write volume at once - see spm_write_plane for performance comments +N.dat(:,:,:) = volume; +spm_progress_bar('Clear'); +return; +%_______________________________________________________________________ + +%_______________________________________________________________________ +function [images,guff] = select_tomographic_images(hdr) +images = {}; +guff = {}; +for i=1:length(hdr), + if ~checkfields(hdr{i},'Modality') || ~(strcmp(hdr{i}.Modality,'MR') ||... + strcmp(hdr{i}.Modality,'PT') || strcmp(hdr{i}.Modality,'CT')) + disp(['Cant find appropriate modality information for "' hdr{i}.Filename '".']); + guff = {guff{:},hdr{i}}; + elseif ~checkfields(hdr{i},'StartOfPixelData','SamplesperPixel',... + 'Rows','Columns','BitsAllocated','BitsStored','HighBit','PixelRepresentation'), + disp(['Cant find "Image Pixel" information for "' hdr{i}.Filename '".']); + guff = {guff{:},hdr{i}}; + elseif isfield(hdr{i},'Private_2001_105f'), + % This field corresponds to: > Stack Sequence 2001,105F SQ VNAP, COPY + % http://www.medical.philips.com/main/company/connectivity/mri/index.html + % No documentation about this private field is yet available. + disp('Cant yet convert Phillips Intera DICOM.'); + guff = {guff{:},hdr{i}}; + elseif ~(checkfields(hdr{i},'PixelSpacing','ImagePositionPatient','ImageOrientationPatient')||isfield(hdr{i},'Private_0029_1210')), + disp(['Cant find "Image Plane" information for "' hdr{i}.Filename '".']); + guff = {guff{:},hdr{i}}; + elseif ~checkfields(hdr{i},'PatientID','SeriesNumber','AcquisitionNumber','InstanceNumber'), + disp(['Cant find suitable filename info for "' hdr{i}.Filename '".']); + if ~isfield(hdr{i},'SeriesNumber') + disp('Setting SeriesNumber to 1'); + hdr{i}.SeriesNumber=1; + images = {images{:},hdr{i}}; + end; + if ~isfield(hdr{i},'AcquisitionNumber') + disp('Setting AcquisitionNumber to 1'); + hdr{i}.AcquisitionNumber=1; + images = {images{:},hdr{i}}; + end; + if ~isfield(hdr{i},'InstanceNumber') + disp('Setting InstanceNumber to 1'); + hdr{i}.InstanceNumber=1; + images = {images{:},hdr{i}}; + end; + else + images = {images{:},hdr{i}}; + end; +end; +return; +%_______________________________________________________________________ + +%_______________________________________________________________________ + +function [mosaic,standard] = select_mosaic_images(hdr) +% This is the patched version of SPM's select_mosaic_images function. The +% original (see below) fails to recognise mosaic type DICOM files if the +% CSA image header info is missing. SWR 2008-08-26 +mosaic = {}; +standard = {}; +for i=1:length(hdr), + if (~checkfields(hdr{i},'ImageType','CSAImageHeaderInfo') ||... + isfield(hdr{i}.CSAImageHeaderInfo,'junk') ||... + isempty(read_AcquisitionMatrixText(hdr{i})) ||... + isempty(read_NumberOfImagesInMosaic(hdr{i}))) ... + && (isempty(strfind(hdr{i}.ImageType, 'MOSAIC'))) + % SWR: This last line identifies (at least some) mosaics even + % when there is no CSA image header present. SWR + + standard = {standard{:},hdr{i}}; + + else + mosaic = {mosaic{:},hdr{i}}; + end; +end; +return; +%_______________________________________________________________________ + +%{ +This is the original version of SPM's select_mosaic_images function: +%_______________________________________________________________________ +function [mosaic,standard] = (hdr) +mosaic = {}; +standard = {}; +for i=1:length(hdr), + if ~checkfields(hdr{i},'ImageType','CSAImageHeaderInfo') ||... + isfield(hdr{i}.CSAImageHeaderInfo,'junk') ||... + isempty(read_AcquisitionMatrixText(hdr{i})) ||... + isempty(read_NumberOfImagesInMosaic(hdr{i})) + standard = {standard{:},hdr{i}}; + else + mosaic = {mosaic{:},hdr{i}}; + end; +end; +return; +%_______________________________________________________________________ +%} + +%_______________________________________________________________________ +function [spect,images] = select_spectroscopy_images(hdr) +spectsel = zeros(1,numel(hdr)); +for i=1:length(hdr), + if isfield(hdr{i},'SOPClassUID') && isfield(hdr{i},'Private_0029_1210') + spectsel(i) = strcmp(hdr{i}.SOPClassUID,'1.3.12.2.1107.5.9.1'); + end; +end; +spect = hdr(logical(spectsel)); +images= hdr(~logical(spectsel)); +return; +%_______________________________________________________________________ + +%_______________________________________________________________________ +function ok = checkfields(hdr,varargin) +ok = 1; +for i=1:(nargin-1), + if ~isfield(hdr,varargin{i}), + ok = 0; + break; + end; +end; +return; +%_______________________________________________________________________ + +%_______________________________________________________________________ +function clean = strip_unwanted(dirty) +msk = find((dirty>='a'&dirty<='z') | (dirty>='A'&dirty<='Z') |... + (dirty>='0'&dirty<='9') | dirty=='_'); +clean = dirty(msk); +return; +%_______________________________________________________________________ + +%_______________________________________________________________________ +function img = read_image_data(hdr) +img = []; + +if hdr.SamplesperPixel ~= 1, + warning([hdr.Filename ': SamplesperPixel = ' num2str(hdr.SamplesperPixel) ' - cant be an MRI']); + return; +end; + +prec = ['ubit' num2str(hdr.BitsAllocated) '=>' 'uint32']; + +if isfield(hdr,'TransferSyntaxUID') && strcmp(hdr.TransferSyntaxUID,'1.2.840.10008.1.2.2') && strcmp(hdr.VROfPixelData,'OW'), + fp = fopen(hdr.Filename,'r','ieee-be'); +else + fp = fopen(hdr.Filename,'r','ieee-le'); +end; +if fp==-1, + warning([hdr.Filename ': cant open file']); + return; +end; + +fseek(fp,hdr.StartOfPixelData,'bof'); +img = fread(fp,hdr.Rows*hdr.Columns,prec); +fclose(fp); +if length(img)~=hdr.Rows*hdr.Columns, + error([hdr.Filename ': cant read whole image']); +end; + +img = bitshift(img,hdr.BitsStored-hdr.HighBit-1); + +if hdr.PixelRepresentation, + % Signed data - done this way because bitshift only + % works with signed data. Negative values are stored + % as 2s complement. + neg = logical(bitshift(bitand(img,uint32(2^hdr.HighBit)),-hdr.HighBit)); + msk = (2^hdr.HighBit - 1); + img = double(bitand(img,msk)); + img(neg) = img(neg)-2^(hdr.HighBit); +else + % Unsigned data + msk = (2^(hdr.HighBit+1) - 1); + img = double(bitand(img,msk)); +end; + +img = reshape(img,hdr.Columns,hdr.Rows); +return; +%_______________________________________________________________________ + +%_______________________________________________________________________ +function img = read_spect_data(hdr) +% Image dimensions +%------------------------------------------------------------------- +nc = get_numaris4_numval(hdr.Private_0029_1210,'Columns'); +nr = get_numaris4_numval(hdr.Private_0029_1210,'Rows'); +img = ones(nr,nc); +%_______________________________________________________________________ + +%_______________________________________________________________________ +function nrm = read_SliceNormalVector(hdr) +str = hdr.CSAImageHeaderInfo; +val = get_numaris4_val(str,'SliceNormalVector'); +for i=1:3, + nrm(i,1) = sscanf(val(i,:),'%g'); +end; +return; +%_______________________________________________________________________ + +%_______________________________________________________________________ +function n = read_NumberOfImagesInMosaic(hdr) +str = hdr.CSAImageHeaderInfo; +val = get_numaris4_val(str,'NumberOfImagesInMosaic'); +n = sscanf(val','%d'); +if isempty(n), n=[]; end; +return; +%_______________________________________________________________________ + +%_______________________________________________________________________ +function dim = read_AcquisitionMatrixText(hdr) +str = hdr.CSAImageHeaderInfo; +val = get_numaris4_val(str,'AcquisitionMatrixText'); +dim = sscanf(val','%d*%d')'; +if length(dim)==1, + dim = sscanf(val','%dp*%d')'; +end; +if isempty(dim), dim=[]; end; +return; +%_______________________________________________________________________ + +%_______________________________________________________________________ +function val = get_numaris4_val(str,name) +name = deblank(name); +val = {}; +for i=1:length(str), + if strcmp(deblank(str(i).name),name), + for j=1:str(i).nitems, + if str(i).item(j).xx(1), + val = {val{:} str(i).item(j).val}; + end; + end; + break; + end; +end; +val = strvcat(val{:}); +return; +%_______________________________________________________________________ + +%_______________________________________________________________________ +function val = get_numaris4_numval(str,name) +val1 = get_numaris4_val(str,name); +for k = 1:size(val1,1) + val(k)=str2num(val1(k,:)); +end; +return; +%_______________________________________________________________________ + +%_______________________________________________________________________ + +function fname = getfilelocation(hdr,root_dir,prefix,format) + +if nargin < 3 + prefix = 'f'; +end; + +if strncmp(root_dir,'ice',3) + root_dir = root_dir(4:end); + imtype = textscan(hdr.ImageType,'%s','delimiter','\\'); + try + imtype = imtype{1}{3}; + catch + imtype = ''; + end; + prefix = [prefix imtype get_numaris4_val(hdr.CSAImageHeaderInfo,'ICE_Dims')]; +end; + +if strcmp(root_dir, 'flat') + % Standard SPM5 file conversion + %------------------------------------------------------------------- + if checkfields(hdr,'SeriesNumber','AcquisitionNumber') + if checkfields(hdr,'EchoNumbers') + fname = sprintf('%s%s-%.4d-%.5d-%.6d-%.2d.%s', prefix, strip_unwanted(hdr.PatientID),... + hdr.SeriesNumber, hdr.AcquisitionNumber, hdr.InstanceNumber,... + hdr.EchoNumbers, format); + else + fname = sprintf('%s%s-%.4d-%.5d-%.6d.%s', prefix, strip_unwanted(hdr.PatientID),... + hdr.SeriesNumber, hdr.AcquisitionNumber, ... + hdr.InstanceNumber, format); + end; + else + fname = sprintf('%s%s-%.6d.%s',prefix, ... + strip_unwanted(hdr.PatientID),hdr.InstanceNumber, format); + end; + + fname = fullfile(pwd,fname); + return; +end; + +% more fancy stuff - sort images into subdirectories +if ~isfield(hdr,'ProtocolName') + if isfield(hdr,'SequenceName') + hdr.ProtocolName = hdr.SequenceName; + else + hdr.ProtocolName='unknown'; + end; +end; +if ~isfield(hdr,'SeriesDescription') + hdr.SeriesDescription = 'unknown'; +end; +if ~isfield(hdr,'EchoNumbers') + hdr.EchoNumbers = 0; +end; + +m = sprintf('%02d', floor(rem(hdr.StudyTime/60,60))); +h = sprintf('%02d', floor(hdr.StudyTime/3600)); +studydate = sprintf('%s_%s-%s', datestr(hdr.StudyDate,'yyyy-mm-dd'), ... + h,m); +switch root_dir + case 'date_time', + id = studydate; + case {'patid', 'patid_date', 'patname'}, + id = strip_unwanted(hdr.PatientID); +end; +serdes = strrep(strip_unwanted(hdr.SeriesDescription),... + strip_unwanted(hdr.ProtocolName),''); +protname = sprintf('%s%s_%.4d',strip_unwanted(hdr.ProtocolName), ... + serdes, hdr.SeriesNumber); +switch root_dir + case 'date_time', + dname = fullfile(pwd, id, protname); + case 'patid', + dname = fullfile(pwd, id, protname); + case 'patid_date', + dname = fullfile(pwd, id, studydate, protname); + case 'patname', + dname = fullfile(pwd, strip_unwanted(hdr.PatientsName), ... + id, protname); + otherwise + error('unknown file root specification'); +end; +if ~exist(dname,'dir'), + mkdir_rec(dname); +end; + +% some non-product sequences on SIEMENS scanners seem to have problems +% with image numbering in MOSAICs - doublettes, unreliable ordering +% etc. To distinguish, always include Acquisition time in image name +sa = sprintf('%02d', floor(rem(hdr.AcquisitionTime,60))); +ma = sprintf('%02d', floor(rem(hdr.AcquisitionTime/60,60))); +ha = sprintf('%02d', floor(hdr.AcquisitionTime/3600)); +fname = sprintf('%s%s-%s%s%s-%.5d-%.5d-%d.%s', prefix, id, ha, ma, sa, ... + hdr.AcquisitionNumber,hdr.InstanceNumber, ... + hdr.EchoNumbers,format); + +fname = fullfile(dname, fname); + +%_______________________________________________________________________ + +%_______________________________________________________________________ + +function suc = mkdir_rec(str) +% works on full pathnames only +opwd=pwd; +if str(end) ~= filesep, str = [str filesep];end; +pos = findstr(str,filesep); +suc = zeros(1,length(pos)); +for g=2:length(pos) + if ~exist(str(1:pos(g)-1),'dir'), + cd(str(1:pos(g-1)-1)); + suc(g) = mkdir(str(pos(g-1)+1:pos(g)-1)); + end; +end; +cd(opwd); +return; +%_______________________________________________________________________ + +%_______________________________________________________________________ +function ret = read_ascconv(hdr) +% In SIEMENS data, there is an ASCII text section with +% additional information items. This section starts with a code +% ### ASCCONV BEGIN ### +% and ends with +% ### ASCCONV END ### +% It is read by spm_dicom_headers into an entry 'MrProtocol' in CSASeriesHeaderInfo +% The additional items are assignments in C syntax, here they are just +% translated according to +% [] -> () +% " -> ' +% 0xX -> hex2dec('X') +% and collected in a struct. +ret=struct; + +% get ascconv data +X=get_numaris4_val(hdr.CSASeriesHeaderInfo,'MrProtocol'); + +ascstart = findstr(X,'### ASCCONV BEGIN ###'); +ascend = findstr(X,'### ASCCONV END ###'); + +if ~isempty(ascstart) && ~isempty(ascend) + tokens = textscan(char(X((ascstart+22):(ascend-1))),'%s', ... + 'delimiter',char(10)); + for k = 1:numel(tokens{1}) + tokens{1}{k}=regexprep(tokens{1}{k},'\[([0-9]*)\]','($1+1)'); + tokens{1}{k}=regexprep(tokens{1}{k},'"(.*)"','''$1'''); + tokens{1}{k}=regexprep(tokens{1}{k},'0x([0-9a-fA-F]*)','hex2dec(''$1'')'); + try + eval(['ret.' tokens{1}{k} ';']); + catch + disp(['AscConv: Error evaluating ''ret.' tokens{1}{k} ''';']); + end; + end; +end; +%_______________________________________________________________________ diff --git a/spm/spm5/spm_jobman.m b/spm/spm5/spm_jobman.m new file mode 100644 index 0000000..1baf882 --- /dev/null +++ b/spm/spm5/spm_jobman.m @@ -0,0 +1,2934 @@ +function varargout = spm_jobman(varargin) +% UI/Batching stuff +%_______________________________________________________________________ +% This code is based on an earlier version by Philippe Ciuciu and +% Guillaume Flandin of Orsay, France. +% +% FORMAT spm_jobman +% spm_jobman('interactive') +% spm_jobman('interactive',job) +% spm_jobman('interactive',job,node) +% spm_jobman('interactive','',node) +% Runs the user interface in interactive mode. +% +% FORMAT spm_jobman('serial') +% spm_jobman('serial',job) +% spm_jobman('serial',job,node) +% spm_jobman('serial','',node) +% Runs the user interface in serial mode. +% +% FORMAT spm_jobman('run',job) +% Runs a job. +% +% FORMAT spm_jobman('help',node) +% spm_jobman('help',node,width) +% Creates a cell array containing help information. This is justified +% to be 'width' characters wide. e.g. +% h = spm_jobman('help','jobs.spatial.coreg.estimate'); +% for i=1:numel(h),fprintf('%s\n',h{i}); end; +% +% node - indicates which part of the configuration is to be used. +% For example, it could be 'jobs.spatial.coreg'. +% +% job - can be the name of a jobfile (as a .mat or a .xml), or a +% 'jobs' variable loaded from a jobfile. +% +% +% FORMAT spm_jobman('defaults') +% Runs the interactive defaults editor. +% +% FORMAT spm_jobman('pulldown') +% Creates a pulldown 'TASKS' menu in the Graphics window. +% +% FORMAT spm_jobman('chmod') +% Changes the modality for the TASKS pulldown. +% +% FORMAT [tag,dat] = spm_jobman('harvest',c) +% Take a data structure, and extract what is needed to save it +% as a batch job (for experts only). +% +%_______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% John Ashburner +% $Id: spm_jobman.m 639 2006-09-28 17:09:17Z volkmar $ + + +if nargin==0 + setup_ui; +else + switch lower(varargin{1}) + case {'interactive'} + if nargin>=2 + setup_ui(varargin{2:nargin}); + else + setup_ui; + end; + + case {'serial'} + if nargin>=2, + serial(varargin{2:nargin}); + else + serial; + end; + + case {'run'} + if nargin<2 + error('Nothing to run'); + end; + run_job(varargin{2}); + + case {'defaults'}, + setup_ui('defaults'); + + case {'pulldown'} + pulldown; + + case {'chmod'} + if nargin>1, + chmod(varargin{2}); + end; + + case {'help'} + if nargin>=2, + varargout{1} = showdoc(varargin{2:nargin}); + else + varargout{1} = showdoc; + end; + + case {'harvest'} + [varargout{1:nargout}] = harvest(varargin{2:nargin}); + + otherwise + error(['"' varargin{1} '" - unknown option']); + end; +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function defaults_edit(varargin) +setup_ui('defaults'); +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function interactive(varargin) +ud = get(varargin{1},'UserData'); +if iscell(ud) + setup_ui(ud{:}); +else + setup_ui; +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function setup_ui(varargin) +% Configure the user interface + +fg = clearwin; +fs = getdef('ui.fs'); +if numel(fs)~=1 || ~isnumeric(fs{1}) || numel(fs{1})~=1, + fs = 12; +else + fs = fs{1}; +end; + +col1 = getdef('ui.colour1'); +if numel(col1)~=1 || ~isnumeric(col1{1}) || numel(col1{1})~=3, + col1 = [0.8 0.8 1]; +else + col1 = col1{1}; +end; + +col2 = getdef('ui.colour2'); +if numel(col2)~=1 || ~isnumeric(col2{1}) || numel(col2{1})~=3, + col2 = [1 1 0.8]; +else + col2 = col2{1}; +end; + +col3 = getdef('ui.colour3'); +if numel(col3)~=1 || ~isnumeric(col3{1}) || numel(col3{1})~=3, + col3 = [0 0 0]; +else + col3 = col3{1}; +end; + +t=uicontrol(fg,... + 'Style','listbox',... + 'Units','normalized',... + 'Position',[0.02 0.31 0.48 0.67],... + 'Callback',@click_batch_box,... + 'Tag','batch_box',... + 'ForegroundColor', col3,... + 'BackgroundColor',col1,... + 'FontSize',fs,... + 'Value',1); +c0 = cntxtmnu(t); +c1 = uimenu('Label','Exp/Con All', 'Parent',c0); +uimenu('Label','Expand All', 'Parent',c1,'Callback',@expandall); +uimenu('Label','Contract All', 'Parent',c1,'Callback',@contractall); +uimenu('Label','Expand All Undefined Inputs', 'Parent',c1,'Callback',@expandallopen); + +t=uicontrol(fg,... + 'Style','listbox',... + 'ListBoxTop',1,... + 'Units','normalized',... + 'Position',[0.02 0.02 0.96 0.28],... + 'Tag','help_box',... + 'FontName','fixedwidth',... + 'FontSize',fs,... + 'ForegroundColor', col3,... + 'BackgroundColor',col2); +set(t,'Value',[], 'Enable', 'inactive', 'Max',2, 'Min',0); +workaround(t); +cntxtmnu(t); + +t=uicontrol(fg,... + 'Style','listbox',... + 'Units','normalized',... + 'Position',[0.51 0.74 0.47 0.24],... + 'ForegroundColor', col3,... + 'BackgroundColor',col1,... + 'FontSize',fs,... + 'Tag','opts_box'); +cntxtmnu(t); + +t=uicontrol(fg,... + 'Style','listbox',... + 'Units','normalized',... + 'Position',[0.51 0.49 0.47 0.24],... + 'Tag','val_box',... + 'ForegroundColor', col3,... + 'BackgroundColor',col2,... + 'Enable', 'inactive',... + 'Value',[],... + 'FontSize',fs,... + 'Max',2, 'Min',0); +set(t,'Value',[], 'Enable', 'on', 'Max',2, 'Min',0,'ListBoxTop',1); +cntxtmnu(t); + +t=uicontrol(fg,... + 'Style','listbox',... + 'Units','normalized',... + 'Position',[0.51 0.38 0.47 0.10],... + 'Tag','msg_box',... + 'ForegroundColor', col3,... + 'BackgroundColor',col2,... + 'Enable', 'inactive',... + 'Value',[],... + 'FontSize',fs,... + 'Max',2, 'Min',0); +cntxtmnu(t); + +if ~(nargin==1 && ischar(varargin{1}) && strcmp(varargin{1},'defaults')), + t=uicontrol(fg,... + 'style','pushbutton',... + 'units','normalized',... + 'Position',[0.51 0.31 0.15 0.06],... + 'ForegroundColor', col3,... + 'BackgroundColor',col1,... + 'String','Save',... + 'Callback',@save_job,... + 'FontSize',fs,... + 'Tag','save'); + cntxtmnu(t); + + t=uicontrol(fg,... + 'style','pushbutton',... + 'units','normalized',... + 'Position',[0.67 0.31 0.15 0.06],... + 'ForegroundColor', col3,... + 'BackgroundColor',col1,... + 'String','Load',... + 'Callback',@load_job,... + 'FontSize',fs,... + 'Tag','load'); + cntxtmnu(t); + + t=uicontrol(fg,... + 'style','pushbutton',... + 'units','normalized',... + 'Position',[0.83 0.31 0.15 0.06],... + 'ForegroundColor', col3,... + 'BackgroundColor',col1,... + 'String','Run',... + 'Callback',@run_struct,... + 'Tag','run',... + 'FontSize',fs,... + 'Enable', 'off'); + cntxtmnu(t); + if nargin>0 + initialise(varargin{:}); + else + initialise; + end; +else + t=uicontrol(fg,... + 'style','pushbutton',... + 'units','normalized',... + 'Position',[0.51 0.31 0.15 0.06],... + 'ForegroundColor', col3,... + 'BackgroundColor',col1,... + 'String','OK',... + 'FontSize',fs,... + 'Callback',@ok_defaults); + cntxtmnu(t); + + t=uicontrol(fg,... + 'style','pushbutton',... + 'units','normalized',... + 'Position',[0.67 0.31 0.15 0.06],... + 'ForegroundColor', col3,... + 'BackgroundColor',col1,... + 'String','Reset',... + 'FontSize',fs,... + 'Callback',@reset_defaults); + cntxtmnu(t); + + t=uicontrol(fg,... + 'style','pushbutton',... + 'units','normalized',... + 'Position',[0.83 0.31 0.15 0.06],... + 'ForegroundColor', col3,... + 'BackgroundColor',col1,... + 'String','Cancel',... + 'FontSize',fs,... + 'Callback',@clearwin); + cntxtmnu(t); + initialise('defaults'); +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function ok_defaults(varargin) +harvest_def(get(batch_box,'UserData')); +clearwin; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function reset_defaults(varargin) +global defaults + +modality = []; +if isfield(defaults,'modality'), + modality = defaults.modality; +end; +spm_defaults; +if ~isfield(defaults,'modality') && ~isempty(modality), + defaults.modality = modality; +end; +pulldown; +clearwin; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function fg = clearwin(varargin) + +fg = spm_figure('findwin','Graphics'); +if isempty(fg), fg = spm_figure('Create','Graphics'); end; +delete(findobj(fg,'Parent',fg)); +delete(batch_box); +delete(opts_box); +spm('Pointer'); +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function expandall(varargin) +c = expcon(get(gco,'UserData'),1); +str = get_strings(c); +val = min(get(gco,'Value'),length(str)); +set(gco,'String',str,'Value',val,'UserData',c); +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function contractall(varargin) +c = expcon(get(gco,'UserData'),0); +str = get_strings(c); +val = min(get(gco,'Value'),length(str)); +set(gco,'String',str,'Value',val,'UserData',c); +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function expandallopen(varargin) +c = expopen(get(gco,'UserData')); +str = get_strings(c); +val = min(get(gco,'Value'),length(str)); +set(gco,'String',str,'Value',val,'UserData',c); +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function [c,sts] = expopen(c) +sts = 0; +if isfield(c,'expanded'), + sts=~all_set(c); +end; +if isfield(c,'val'), + for i=1:length(c.val), + [c.val{i} sts1] = expopen(c.val{i}); + sts = sts||sts1; + end; +end; +if isfield(c,'expanded') + c.expanded = sts; +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function c = expcon(c,val) +if isfield(c,'expanded'), c.expanded = val; end; +if isfield(c,'val'), + for i=1:length(c.val), + c.val{i} = expcon(c.val{i},val); + end; +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function initialise(job,node) +% load the config file, possibly adding a job +% to it, and generally tidy it up. The batch box +% is updated to contain the current structure. +files_select_list('init'); +if nargin<1, job = ''; end; +if nargin<2, node = 'jobs'; end; +c = initialise_struct(job); +set(batch_box,'UserData',start_node(c,node)); +update_ui; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function run_job(job) +% Run a job. This function is not called via the UI. +c = initialise_struct(job); +if all_set(c), + run_struct1(c); +else + error('This job is not ready yet'); +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function click_batch_box(varargin) +% Called when the batch box is clicked + +remove_string_box; +if strcmp(get(get(varargin{1},'Parent'),'SelectionType'),'open') + run_in_current_node(@expand_contract,false); + str = get_strings(get(batch_box,'UserData')); + val = min(get(batch_box,'Value'),length(str)); + set(batch_box,'String',str,'Value',val); +else + run_in_current_node(@click_batch_box_fun,false); +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function update_ui +[str,sts] = get_strings(get(batch_box,'UserData')); +val = min(get(batch_box,'Value'),length(str)); +set(batch_box,'String',str,'Value',val); +run_in_current_node(@click_batch_box_fun,false); + +run_but = findobj(0,'Tag','run'); +if sts + set(run_but,'Enable', 'on'); +else + set(run_but,'Enable', 'off'); +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function [str,sts] = get_strings(c) +[unused,str,sts] = start_node(c,@get_strings1,0); +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function [c,str,sts] = get_strings1(c,l) +% Return the cell vector of strings displayed in the batch +% box, and also whether the job is runable. This is a recursive +% function that a branch of the data structure is passed to. + +if isfield(c,'hidden'), + sts = 1; + str = ''; + return; +end; + +sts = 1; +mrk = ' '; +nam = ''; +if isfield(c,'datacheck') && ~isempty(c.datacheck), + mrk = ' <-@'; + sts = 0; +end; +if isfield(c,'name'), nam = c.name; end; + +if isfield(c,'expanded') % && isfield(c,'val') && ~isempty(c.val) + + if strcmp(c.type,'repeat') && isfield(c,'num') + sts = (length(c.val) >= c.num(1)) &&... + (length(c.val) <= c.num(2)); + if ~sts + mrk = ' <-X'; + end; + end; + + if c.expanded + str = {[repmat('. ',1,l) '-' nam]}; + for i=1:length(c.val) + [c.val{i},str1,sts1] = get_strings1(c.val{i},l+1); + sts = sts && sts1; + if ~isempty(str1), + str = {str{:},str1{:}}; + end; + end; + else + str = {[repmat('. ',1,l) '+' nam]}; + for i=1:length(c.val) + [c.val{i},unused,sts1] = get_strings1(c.val{i},l+1); + sts = sts && sts1; + end; + if ~sts, + mrk = ' <-X'; + end; + end; + str{1} = [str{1} mrk]; +else + switch c.type + case 'files' + % If files are selected via "Input to/Output from" shortcuts, + % there is no guarantee that an allowed number of files is + % specified. This is checked here. + cn = c.num; + if (numel(cn) == 1) + if isfinite(cn) + cn = [cn cn]; + else + cn = [0 cn]; + + end; + end; + if isempty(c.val) || (numel(c.val{1}) < cn(1)) || ... + (numel(c.val{1}) > cn(2)) + mrk = ' <-X'; + sts = 0; + end; + case {'menu','entry','const','choice'} + if isempty(c.val) + mrk = ' <-X'; + sts = 0; + end; + case 'repeat' % don't know, whether this ever gets executed + if isfield(c,'num') + sts = (length(c.val) >= c.num(1)) &&... + (length(c.val) <= c.num(2)); + if ~sts + mrk = ' <-X'; + end; + end; + end; + str = {[repmat('. ',1,l) ' ' nam mrk]}; +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function ok = all_set(c) +% Return whether all the necessary fields have been filled in. +% This is a recursive function that a branch of the data structure +% is passed to. + +if isfield(c,'hidden'), + ok = true; + return; +end; + +ok = true; + +switch c.type +case {'files','menu','entry','const','choice'} + if isempty(c.val) + ok = false; + end; + +case {'branch','repeat'} + if strcmp(c.type,'repeat') && isfield(c,'num') + ok = ok && (length(c.val) >= c.num(1)) && ... + (length(c.val) <= c.num(2)); + end; + if ok, + for i=1:length(c.val), + ok1 = all_set(c.val{i}); + ok = ok && ok1; + if ~ok, break; end; + end; + end; +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function click_options_box(varargin) +% The function called when the options box is clicked + +dat = get(opts_box,'UserData'); +if ~isempty(dat) + fun = dat{get(opts_box,'Value')}; + run_in_current_node(fun.fun,true,fun.args{:}); + if fun.redraw, update_ui; end; +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function varargout = run_in_current_node(varargin) +% Get the current node, and run a job in it +varargout = {}; +c = get(batch_box,'UserData'); +n = get(batch_box,'Value'); +show_msg(''); +va = {c,@run_in_current_node1,n,varargin{:}}; +[c,unused,varargout{:}] = start_node(va{:}); +set(batch_box,'UserData',c); +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function [c,varargout] = start_node(c,fun,varargin) +persistent node; +if isempty(node) + node = {'jobs'}; +end; +varargout = {}; +if nargin==2 + tmp = [0 find([fun '.']=='.')]; + node = {}; + for i=1:length(tmp)-1 + node = {node{:},fun((tmp(i)+1):(tmp(i+1)-1))}; + end; + c = make_nodes(c,node); + return; +end; +va = {c,node,fun,varargin{:}}; +[c,unused,varargout{1:nargout-1}] = start_node1(va{:}); +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function c = make_nodes(c,node) +if isfield(c,'tag') && ~strcmp(gettag(c),node{1}) + return; +end; +if isfield(c,'tag') + node = {node{2:end}}; + if isempty(node), return; end; +end; +if isfield(c,'values') && (~isfield(c,'val') ||... + isempty(c.val) || ~iscell(c.val) ||... + ~strcmp(gettag(c.val{1}),node{1})) + for i=1:length(c.values) + if strcmp(gettag(c.values{i}),node{1}) + c.val = {c.values{i}}; + c.val{1} = make_nodes(c.val{1},node); + end; + end; +end; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function [c,sts,varargout] = start_node1(c,node,fun,varargin) +varargout = {}; +if nargin<4, varargin = {}; end; +if isfield(c,'tag') && ~strcmp(gettag(c),node{1}) + varargout = cell(1,nargout-2); + sts = 0; + return; +end; +if isfield(c,'tag'), + node = {node{2:end}}; +end; +if isempty(node) + [c,varargout{1:nargout-2}] = feval(fun,c,varargin{:}); + sts = 1; + return; +end; +if isfield(c,'val'), + for i=1:length(c.val) + [c.val{i},sts,varargout{1:nargout-2}] = start_node1(c.val{i},node,fun,varargin{:}); + if sts, return; end; + end; +end; +keyboard +error('No such node'); % Should never execute +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function [c,n,varargout] = run_in_current_node1(c,n,fun,modify,varargin) +% Satellite for run_in_current_node + +varargout = {}; +if nargin<5, varargin = {}; end; +if isfield(c,'hidden'), return; end; + +n = n-1; +if n<0, return; end; +if n==0, + if ~isfield(c,'datacheck'), + show_msg(''); + else + show_msg(c.datacheck); + end; + [c,varargout{:}] = feval(fun,c,varargin{:}); +end; + +if isfield(c,'expanded') && ~isempty(c.val) + if c.expanded, + val = c.val; + c.val = {}; + for i=1:length(val) + [tmp,n,varargout{:}] = run_in_current_node1(val{i},n,fun,modify,varargin{:}); + if ~isempty(tmp) + if iscell(tmp) + c.val = {c.val{:}, tmp{:}}; + else + c.val = {c.val{:}, tmp}; + end; + end; + end; + if modify && isfield(c,'check'), + if all_set(c), + [unused,val] = harvest(c); + c.datacheck = feval(c.check,val); + end; + end; + if isfield(c,'datacheck') && ~isempty(c.datacheck), + show_msg(c.datacheck); + end; + end; +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function c = click_batch_box_fun(c,unused) +% Mostly set up the options box, but also update the help and value boxes + +if ~isempty(opts_box) + dat = {}; + str = {}; + + switch c.type + case {'const'} + % do nothing + + case {'files'} + spm('pointer','watch'); + str = {str{:}, 'Specify Files'}; + dat = {dat{:}, struct('fun',@file_select,'args',{{}},'redraw',1)}; + addvfiles(c.id,[],c); + addinfiles(c); + [filestr filedat] = files_select_list('listall'); + str = {str{:}, filestr{:}}; + dat = {dat{:}, filedat{:}}; + spm('pointer','arrow'); + + case {'menu'} + str = {str{:}, 'Specify Menu Item'}; + dat = {dat{:}, struct('fun',@menu_entry,'args',{{}},'redraw',0)}; + + case {'entry'} + str = {str{:}, 'Specify Text'}; + dat = {dat{:}, struct('fun',@text_entry,'args',{{}},'redraw',1)}; + + case {'branch','choice','repeat'} + if strcmp(c.type,'repeat') + for i=1:length(c.values) + if ~isfield(c.values{i},'hidden'), + str = {str{:},['New "' c.values{i}.name '"']}; + dat = {dat{:}, struct('fun',@series,'args',{{c.values{i}}},'redraw',1)}; + end; + end; + + elseif strcmp(c.type,'choice') + for i=1:length(c.values) + if ~isfield(c.values{i},'hidden'), + str = {str{:},['Choose "' c.values{i}.name '"']}; + dat = {dat{:}, struct('fun',@choose,'args',{{c.values{i}}},'redraw',1)}; + end; + end; + end; + end; + + if isfield(c,'removable') + str = {str{:},['Remove Item "' c.name '"'],['Replicate Item "' c.name '"']}; + dat = {dat{:}, struct('fun',@remove,'args',{{}},'redraw',1),... + struct('fun',@replicate,'args',{{}},'redraw',1)}; + end; + + if ~same(get(opts_box,'String')',str) + val = 1; + else + val = get(opts_box,'Value'); + end; + val = max(min(length(str),val),min(1,length(str))); + set(opts_box,'String',str,'UserData',dat,'Callback',@click_options_box,'Value',val); +end; + +show_value(c); + +% Update help +txt = ''; +if isfield(c,'help'), txt = c.help; end; +help_box = findobj(0,'tag','help_box'); +if ~isempty(help_box) + set(help_box,'String',' '); + workaround(help_box); + ext = get(help_box,'Extent'); + pos = get(help_box,'position'); + pw = floor(pos(3)/ext(3)*21-4); + set(help_box,'String',spm_justify(help_box,txt)); + workaround(help_box); +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function c = expand_contract(c,unused) +% Expand/contract a node (for visualisation) + +if isfield(c,'expanded') && ~isempty(c.val) + if c.expanded + c.expanded = false; + else + c.expanded = true; + end; +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function c = menu_entry(c,unused) +% Setup opts box for menu entry + +if isfield(c,'labels') && isfield(c,'values') + str = {}; + dat = {}; + if ~isempty(c.val) + val = c.val{1}; + else + val = ''; + end; + dv = 1; + for i=1:length(c.values) + if ~(ischar(val) && strcmp(val,'')) && same(c.values{i},val) + str{i} = ['* ' c.labels{i}]; + dv = i; + else + str{i} = [' ' c.labels{i}]; + end; + dat{i} = struct('fun',@menuval,'args',{{c.values{i}}},'redraw',1); + end; + set(opts_box,'String',str,'UserData',dat,'Callback',@click_options_box,'Value',dv); +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function [c] = file_select(c) +% Select files +try + set(batch_box,'Enable', 'inactive'); + set(opts_box, 'Enable', 'inactive'); + + if ~isempty(c.val), + sel = c.val{1}; + else + sel = ''; + end; + if isfield(c,'dir'), + dr = c.dir; + else + dr = pwd; + end; + if isfield(c,'ufilter') + uf = c.ufilter; + else + uf = '.*'; + end; + [s,ok] = spm_select(c.num,c.filter,c.name,sel,dr,uf); + if ok, c.val{1} = cellstr(s); end; + files_select_list('addinfiles', sprintf('Input to "%s"', c.name), ... + c.val{1}, c.id); +catch +end; +spm_select('clearvfiles'); +set(batch_box,'Enable', 'on'); +set(opts_box, 'Enable', 'on'); +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function c = text_entry(c) +% Create a string box and prompt, while hiding the opts box + +opts_box = findobj(gcf,'tag','opts_box'); +pos = get(opts_box,'Position'); +set(opts_box,'Visible','off'); +fs = get(opts_box,'FontSize'); +fa = get(opts_box,'FontAngle'); +fw = get(opts_box,'FontWeight'); +clf = get(opts_box,'ForegroundColor'); +clb = get(opts_box,'BackgroundColor'); + +n = []; +m = []; +if isfield(c,'num'), n = c.num; end; +if isfield(c,'extras'), m = c.extras; end; + +newstring = uicontrol(gcf,... + 'Style','edit',... + 'Units','normalized',... + 'Position',[pos(1:3) pos(4)/2],... + 'Tag','string_box',... + 'HorizontalAlignment','left',... + 'FontSize',fs,... + 'FontAngle',fa,... + 'FontWeight',fw,... + 'ForegroundColor',clf,... + 'BackgroundColor',clb,... + 'Callback',@accept_string); +cntxtmnu(newstring); + +strM=''; +switch lower(c.strtype) +case 's', TTstr='enter string'; +case 'e', TTstr='enter expression - evaluated'; +case 'n', TTstr='enter expression - natural number(s)'; + if ~isempty(m), strM=sprintf(' (in [1,%d])',m); TTstr=[TTstr,strM]; end +case 'w', TTstr='enter expression - whole number(s)'; + if ~isempty(m), strM=sprintf(' (in [0,%d])',m); TTstr=[TTstr,strM]; end +case 'i', TTstr='enter expression - integer(s)'; +case 'r', TTstr='enter expression - real number(s)'; + if ~isempty(m), TTstr=[TTstr,sprintf(' in [%g,%g]',min(m),max(m))]; end +case 'c', TTstr='enter indicator vector e.g. 0101... or abab...'; + if ~isempty(m) && isfinite(m), strM=sprintf(' (%d)',m); end +otherwise, TTstr='enter expression'; +end + +if isempty(n), n=NaN; end +n=n(:); if length(n)==1, n=[n,1]; end; dn=length(n); +if any(isnan(n)) || (prod(n)==1 && dn<=2) || (dn==2 && min(n)==1 && isinf(max(n))) + str = ''; + lstr = ''; +elseif dn==2 && min(n)==1 + str = sprintf('[%d]',max(n)); + lstr = [str,'-vector: ']; +elseif dn==2 && sum(isinf(n))==1 + str = sprintf('[%d]',min(n)); + lstr = [str,'-vector(s): ']; +else + str=''; + for i = 1:dn, + if isfinite(n(i)), + str = sprintf('%s,%d',str,n(i)); + else + str = sprintf('%s,*',str); + end + end + str = ['[',str(2:end),']']; + lstr = [str,'-matrix: ']; +end +strN = sprintf('%s',lstr); +col = get(gcf,'Color'); +uicontrol(gcf,'Style','text',... + 'Units','normalized',... + 'BackgroundColor',col,... + 'String',[strN,strM,TTstr],... + 'Tag','string_prompt',... + 'HorizontalAlignment','Left',... + 'FontSize',fs,... + 'FontAngle',fa,... + 'FontWeight',fw,... + 'Position',[pos(1:3)+[0 pos(4)/2 0] pos(4)/2]); + +if isfield(c,'val') && ~isempty(c.val) + val = c.val{1}; + if ischar(val) + tmp = val'; + tmp = tmp(:)'; + set(newstring,'String',tmp); + elseif isnumeric(val) + if ndims(val)>2, + set(newstring,'String',['reshape([', num2str(val(:)'), '],[' num2str(size(val)) '])']); + else + if size(val,1)==1, + set(newstring,'String',num2str(val(:)')); + elseif size(val,2)==1, + set(newstring,'String',['[' num2str(val(:)') ']''']); + else + str = ''; + for i=1:size(val,1), + str = [str ' ' num2str(val(i,:)) ';']; + end; + set(newstring,'String',str); + end; + end; + end; +end; + +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function accept_string(varargin) +% Accept a typed in string? +run_in_current_node(@stringval,true,get(varargin{1},'String')); +update_ui; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function remove_string_box +% Delete the string box and prompt, making the opts box +% visible again +delete(findobj(0,'Tag','string_box')); +delete(findobj(0,'Tag','string_prompt')); +set(opts_box,'Visible','on'); +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function show_value(c) +% Update the value box + +valtxt = {''}; +% col = [1 0 0]; + +switch c.type +case {'menu'} + if isfield(c,'val') && ~isempty(c.val) + if isfield(c,'labels') && isfield(c,'values') + val = c.val{1}; + for i=1:length(c.values) + if same(c.values{i},val) + valtxt = c.labels{i}; + % col = [0 0 0]; + break; + end; + end; + end; + end; + +case {'const','entry'} + if isfield(c,'val') && ~isempty(c.val) + val = c.val{1}; + if isempty(val) + valtxt = ''; + else + if isnumeric(val) + sz = size(val); + if length(sz)>2 + valtxt = sprintf('%dx',sz); + valtxt = [valtxt(1:(end-1)) ' Numeric Array']; + else + valtxt = cell(size(val,1),1); + for i=1:size(val,1) + valtxt{i} = sprintf('%14.8g ',val(i,:)); + end; + end; + elseif(ischar(val)) + valtxt = val; + else + valtxt = 'Can not display'; + end; + end; + % col = [0 0 0]; + end; + +case {'files'} + if isfield(c,'val') && ~isempty(c.val) + if isempty(c.val{1}) || isempty(c.val{1}{1}) + valtxt = ''; + else + valtxt = c.val{1}; + end; + % col = [0 0 0]; + end; + +case {'choice'} + if isfield(c,'val') && length(c.val)==1 + valtxt = {'A choice, where',['"' c.val{1}.name '"'], 'is selected.'}; + % col = [0.5 0.5 0.5]; + else + valtxt = {'A choice, with', 'nothing selected.'}; + % col = [0.5 0.5 0.5]; + end; + +case {'repeat'} + ln = length(c.val); s = 's'; if ln==1, s = ''; end; + valtxt = ['A series containing ' num2str(length(c.val)) ' item' s '.']; + % col = [0.5 0.5 0.5]; + +case {'branch'} + ln = length(c.val); s = 's'; if ln==1, s = ''; end; + valtxt = {['A branch holding ' num2str(length(c.val)) ' item' s '.']}; + if isfield(c,'prog'), + valtxt = {valtxt{:}, '', ' User specified values',... + ' from this branch will be',... + ' collected and passed to',... + ' an executable function',... + ' when the job is run.'}; + end; + % col = [0.5 0.5 0.5]; + +otherwise + valtxt = 'What on earth is this???'; +end; +val_box = findobj(0,'tag','val_box'); +if ~isempty(val_box) + % set(val_box,'String',valtxt,'ForegroundColor',col); + set(val_box,'String',valtxt); + workaround(val_box); +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function c = remove(c,varargin) +% Remove node c +if strcmp(questdlg(['Remove "' c.name '"?'],'Confirm','Yes','No','Yes'),'Yes'), + c = {}; +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function c = replicate(c,varargin) +% Replicate node c +c = {c,uniq_id(c)}; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function c = choose(c,val) +% Specify the value of c to be val +c.val{1} = uniq_id(val); +c.expanded = true; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function c = menuval(c,val) +% Specify the value of c to be val +c.val{1} = val; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function c = stringval(c,val) +% Accept (or not) a typed in string + +msg = 'SUCCESS: Values accepted'; + +switch c.strtype +case {'s'} + c.val{1} = val; + show_value(c); + remove_string_box; + +case {'s+'} + msg = 'FAILURE: Cant do s+ yet'; + beep; + remove_string_box; + +otherwise + n = Inf; + if isfield(c,'num') + n = c.num; + end; + if isfield(c,'extras') + [val,msg] = spm_eeval(val,c.strtype,n,c.extras); + else + [val,msg] = spm_eeval(val,c.strtype,n); + end; + + if ischar(val) + beep; + msg = ['FAILURE: ' msg]; + else + c.val{1} = val; + show_value(c); + remove_string_box; + msg = ['SUCCESS: ' msg]; + end; +end; +show_msg(msg); +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function c = series(c,new) +% Add a new repeat +c.expanded = true; +new.removable = true; +if isfield(c,'val') + c.val = {c.val{:},uniq_id(new)}; +else + c.val = {uniq_id(new)}; +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function setdef(strin,val) +global defaults + +if isempty(defaults), spm_defaults; end; +if ischar(val) && strcmp(val,''), return; end; +o = find(strin=='.'); +df = cell(1,length(o)+1); +prev = 1; +for i=1:length(o), + df{i} = strin(prev:(o(i)-1)); + prev = o(i)+1; +end; +df{end} = strin(prev:end); +defaults = setdef_sub(defaults,df,val); +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function d = setdef_sub(d,df,val) +if isempty(df), + d = val; +else + if ~isfield(d,df{1}),d.(df{1}) = []; end; + d.(df{1}) = setdef_sub(d.(df{1}),df(2:end),val); +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function val = getdef(strin) +% Get value of one of the SPM defaults + +global defaults + +val = getdef_sub(defaults,strin); +if ischar(val) && strcmp(val,'') + val = {}; +else + val = {val}; +end; +%fprintf('%s\n',strin); +%disp(val) +%disp('----'); +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function c = getdef_sub(defs,field) +% Satellite function for getdef + +o = find(field=='.'); +if isempty(o) + if isfield(defs,field) + c = defs.(field); + else + c = ''; + end; + return; +end; +if isfield(defs,field(1:(o-1))) + c = getdef_sub(defs.(field(1:(o-1))),field((o+1):end)); +else + c = ''; +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function t = same(a,b) +% Are two data structures identical? + +% Innocent until proven guilty +t = true; + +% Check the dimensions +if isempty(a) && isempty(b) + t = true; return; +end; +sa = size(a); +sb = size(b); +if length(sa) ~= length(sb) + t = false; return; +end; +if ~all(sa==sb) + t = false; return; +end; + +% Check the classes +ca = class(a); +if ~strcmp(ca,class(b)), t = false; return; end; + +% Recurse through data structure +switch ca +case {'double','single','sparse','char','int8','uint8',... + 'int16','uint16','int32','uint32','logical'} + msk = ((a==b) | (isnan(a)&isnan(b))); + if ~all(msk(:)), t = false; return; end; + +case {'struct'} + fa = fieldnames(a); + fb = fieldnames(b); + if length(fa) ~= length(fb), t = false; return; end; + for i=1:length(fa) + if ~strcmp(fa{i},fb{i}), t = false; return; end; + for j=1:length(a) + if ~same(a(j).(fa{i}),b(j).(fb{i})) + t = false; return; + end; + end; + end; + +case {'cell'} + for j=1:length(a(:)) + if ~same(a{j},b{j}), t = false; return; end; + end; + +case {'function_handle'} + if ~strcmp(func2str(a),func2str(b)) + t = false; return; + end; + +otherwise + warning(['Unknown class "' ca '"']); + t = false; +end; + +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function t = batch_box +t = findobj(0,'tag','batch_box'); +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function t = opts_box +t = findobj(0,'tag','opts_box'); +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function save_job(varargin) +% Save a batch job +c = get(batch_box,'UserData'); spm('Pointer','Watch'); +[unused,jobs] = harvest(c); +spm('Pointer'); +%eval([tag '=val;']); +cll = {'*.mat','Matlab .mat file';'*.m','Matlab script file';'*.xml','XML file'}; +[filename, pathname, FilterIndex] = uiputfile(cll,'Save job as'); +if ischar(filename) + [unused,unused,ext] = fileparts(filename); + if isempty(ext) + ext = cll{FilterIndex}(2:end); + filename = [filename ext]; + end + switch ext + case '.xml', + spm('Pointer','Watch'); + savexml(fullfile(pathname,filename),'jobs'); + spm('Pointer'); + case '.mat', + if spm_matlab_version_chk('7') >= 0, + save(fullfile(pathname,filename),'-V6','jobs'); + else + save(fullfile(pathname,filename),'jobs'); + end; + case '.m', + treelist('jobs',struct('exps',1, 'dval',2, 'fname', ... + fullfile(pathname,filename))); + otherwise + questdlg(['Unknown extension (' ext ')'],'Nothing saved','OK','OK'); + end; +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function load_job(varargin) +% Load a set of batch jobs and possibly merge it + +[filenames sts] = spm_select([1 Inf], 'batch', 'Load job file(s)'); +if sts + filenames = cellstr(filenames); + newjobs = {}; + for cf = 1:numel(filenames) + [p,nam,ext] = fileparts(filenames{cf}); + switch ext + case '.xml', + spm('Pointer','Watch'); + try + loadxml(filenames{cf},'jobs'); + catch + questdlg('LoadXML failed',filenames{cf},'OK','OK'); + return; + end; + spm('Pointer'); + case '.mat' + try + load(filenames{cf},'jobs'); + catch + questdlg('Load failed',filenames{cf},'OK','OK'); + end; + case '.m' + opwd = pwd; + try + cd(p); + eval(nam); + catch + questdlg('Load failed',filenames{cf},'OK','OK'); + end; + cd(opwd); + otherwise + questdlg(['Job ' nam ': Unknown extension (' ext ')'],... + 'This job not loaded','OK','OK'); + end; + if exist('jobs','var') + newjobs = {newjobs{:} jobs{:}}; + clear jobs; + else + questdlg(['No jobs (' nam ext ')'],'No jobs','OK','OK'); + end; + end; + if ~isempty(newjobs) + spm('Pointer','Watch'); + initialise(newjobs); + spm('Pointer'); + end; +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function run_struct(varargin) +% Get data structure from handle, and run it +% If an error occured, then return to user interface +c = get(batch_box,'UserData'); +[unused,job] = harvest(c); +try +run_struct1(c); +catch + disp('Error running job:'); + disp(lasterr); + setup_ui(job); +end; +disp('--------------------------'); +disp('Done.'); +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function run_struct1(c) +% Run a batch job from a data structure + +if isfield(c,'prog') + prog = c.prog; + [unused,val] = harvest(c); + disp('--------------------------'); + disp(['Running "' c.name '"']); + [Finter,unused,CmdLine] = spm('FnUIsetup',c.name); + spm('Pointer','Watch'); + spm('FigName',[c.name ': running'],Finter,CmdLine); + + if 0 + try + feval(prog,val); + spm('FigName',[c.name ': done'],Finter,CmdLine); + catch + disp(['An error occurred when running "' c.name '"']); + disp( '--------------------------------'); + disp(lasterr); + disp( '--------------------------------'); + spm('FigName',[c.name ': failed'],Finter,CmdLine); + errordlg({['An error occurred when running "' c.name '"'],lasterr},'SPM Jobs'); + end; + spm('Pointer'); + else + feval(prog,val); + spm('FigName',[c.name ': done'],Finter,CmdLine); + %prog_infos=functions(prog) + %prog_log=[prog_infos.parentage{end} '_log']; + %if exist(prog_log) > 1 + try + spm_log2html(prog,val); + disp(sprintf('Generating log on %s',c.name)); + catch + disp(sprintf('Error while generating log on %s',c.name)); + end + %else + % prog_msg=sprintf('No function found for generating log on %s. Try making a: %s',c.name, prog_log); + %end + %disp(prog_msg); + spm('Pointer'); + end; + +else + if isfield(c,'val') + for i=1:length(c.val) + run_struct1(c.val{i}); + end; + end; +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function harvest_def(c) +switch c.type, +case{'const','menu','files','entry'}, + if isfield(c,'def') && numel(c.val)==1, + setdef(c.def,c.val{1}); + end; + +otherwise + for i=1:length(c.val), + harvest_def(c.val{i}); + end; +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function [tag,val,typ] = harvest(c) +% Take a data structure, and extract what is needed to save it +% as a batch job + +tag = 'unknown'; +val = []; +typ = c.type; +switch(typ) +case {'const','menu','files','entry'} + tag = gettag(c); + if ~isempty(c.val) + val = c.val{1}; + else + val = ''; + end; + +case {'branch'} + tag = gettag(c); + if isfield(c,'val') + val = []; + for i=1:length(c.val) + [tag1,val1] = harvest(c.val{i}); + val.(tag1) = val1; + end; + end; + +case {'repeat'} + tag = gettag(c); + if length(c.values)==1 && strcmp(c.values{1}.type,'branch'), + cargs = {}; + for i=1:numel(c.values{1}.val), + cargs = {cargs{:},gettag(c.values{1}.val{i}),{}}; + end; + val = struct(cargs{:}); + + if isfield(c,'val') + for i=1:length(c.val), + [tag1,val1,typ1] = harvest(c.val{i}); + val(i) = val1; + end; + end; + else + val = {}; + if isfield(c,'val') + for i=1:length(c.val), + [tag1,val1,typ1] = harvest(c.val{i}); + if length(c.values)>1, + if iscell(val1) + val1 = struct(tag1,{val1}); + else + val1 = struct(tag1,val1); + end; + end; + val = {val{:}, val1}; + end; + end; + end; + +case {'choice'} + if isfield(c,'tag'), tag = gettag(c); end; + [tag1,val1] = harvest(c.val{1}); + if iscell(val1) + val = struct(tag1,{val1}); + else + val = struct(tag1,val1); + end; +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function tag = gettag(c) +% Get a tag field - possibly from one of the kids + +if (strcmp(c.type,'repeat') || strcmp(c.type,'choice')) && numel(c.values)>0 + tag = gettag(c.values{1}); + for i=2:length(c.values) + if ~strcmp(tag,gettag(c.values{i})) + tag = c.tag; + return; + end; + end; +else + tag = c.tag; +end; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function c0 = cntxtmnu(ob) +c0 = uicontextmenu('Parent',get(ob,'Parent')); +set(ob,'uicontextmenu',c0); +c1 = uimenu('Label','Font', 'Parent',c0); + uimenu('Label','Plain', 'Parent',c1,'Callback','set(gco,''FontWeight'',''normal'',''FontAngle'',''normal'');'); + uimenu('Label','Bold', 'Parent',c1,'Callback','set(gco,''FontWeight'',''bold'', ''FontAngle'',''normal'');'); + uimenu('Label','Italic', 'Parent',c1,'Callback','set(gco,''FontWeight'',''normal'',''FontAngle'',''italic'');'); + uimenu('Label','Bold-Italic','Parent',c1,'Callback','set(gco,''FontWeight'',''bold'', ''FontAngle'',''italic'');'); +c1 = uimenu('Label','Fontsize','Parent',c0); +fs = [8 9 10 12 14 16 18]; % [20 24 28 32 36 44 48 54 60 66 72 80 88 96]; +for i=fs, + uimenu('Label',sprintf('%-3d',i),'Parent',c1,'Callback',@fszoom,'UserData',i); +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function workaround(t) +set(t,'Value',[], 'Enable', 'on', 'Max',2, 'Min',0,'ListBoxTop',1); +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function addvfiles(id,c,c0) +if (nargin<2)||isempty(c), + c = get(batch_box,'UserData'); +end; +if nargin<3 + c0 = []; +end; +files_select_list('clearvfiles'); +vf =addvfiles1(c,id,{},c0); +spm_select('clearvfiles'); +spm_select('addvfiles',vf); +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function [vf,sts]=addvfiles1(c,id,vf,c0) +sts = 0; +if ~isstruct(c) || ~isfield(c,'type'), + return; +end; + +if isfield(c,'vfiles'), + if ~find_id(c,id), + [c,unused,ok] = get_strings1(c,0); + if ok, + [unused,job] = harvest(c); + vf1 = feval(c.vfiles,job); + if ~isempty(c0) + files = filter_files(c0, vf1); + else + files = vf1; + end; + if ~isempty(files) + files_select_list('addvfiles', sprintf('Output from "%s"', ... + c.name), ... + files, c.id); + end; + vf = {vf{:}, vf1{:}}; + end; + else + sts = 1; + end; + return; +end; + +switch c.type, +case {'repeat','choice','branch'}, + for i=1:length(c.val), + [vf,sts]=addvfiles1(c.val{i},id,vf,c0); + if sts, return; end; + end; +end; + +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function addinfiles(c0,c) +id = c0.id; +if nargin<2, + c = get(batch_box,'UserData'); +end; +files_select_list('clearinfiles'); +addinfiles1(c,id,'',c0); +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function sts=addinfiles1(c,id,progname,c0) +sts = 0; +if ~isstruct(c) || ~isfield(c,'type'), + return; +end; + +if find_id(c,id), + sts = 1; +end; + +switch c.type, +case 'files' + if ~isempty(c.val) && ~isempty(c.val{1}) + files = filter_files(c0,c.val{1}); + if ~isempty(files) + files_select_list('addinfiles', ... + sprintf('Input to "%s->%s"', ... + progname, c.name), ... + files, c.id); + end; + end; +case {'repeat','choice','branch'}, + if isfield(c,'prog') + progname = c.name; + oldin = files_select_list('getinnum'); + end; + for i=1:length(c.val), + sts=addinfiles1(c.val{i},id,progname,c0); + if sts, return; end; + end; + if isfield(c,'prog') + newin = files_select_list('getinnum'); + if (newin-oldin > 1) + files_select_list('allinfiles', ... + sprintf('All inputs to %s',progname),... + oldin+1, newin); + end; + end; +end; + +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function ffiles = filter_files(c,files) +if strcmp(c.filter, 'image')||strcmp(c.filter,'dir') + filter = ['ext' c.filter]; +else + filter = c.filter; +end; +if isfield(c,'ufilter') + uf = c.ufilter; + if uf(1) == '^' % This will not work with full pathnames + uf=uf(2:end); + end; +else + uf = '.*'; +end; +ffiles = spm_select('filter', files, ... + filter, uf); +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function varargout = files_select_list(c,varargin) +persistent vfstr; +persistent vfdat; +persistent vffiles; +persistent vfid; +persistent instr; +persistent indat; +persistent infiles; +persistent inid; +if isstruct(c) + switch lower(varargin{1}) + case 'getvf' + c.val{1} = vffiles{varargin{2}}; + case 'getin' + c.val{1} = infiles{varargin{2}}; + end; + varargout{1} = c; + return; +end; +if ~iscell(vfstr) && ~strcmp(lower(c),'init') + files_select_list('init'); +end; +switch lower(c) +case 'init' + vfstr = {}; + vfdat = {}; + vffiles = {}; + vfid = []; + instr = {}; + indat = {}; + infiles = {}; + inid = []; +case 'clearvfiles' + vfstr = {}; + vfdat = {}; + vffiles = {}; + vfid = []; +case 'clearinfiles' + instr = {}; + indat = {}; + infiles = {}; + inid = []; +case 'addvfiles' + nvfind = numel(vfstr)+1; + vfid(nvfind) = varargin{3}; + vfstr{nvfind} = varargin{1}; + vfdat{nvfind} = struct('fun',@files_select_list,'args',{{'getvf', nvfind}}, ... + 'redraw',1); + vffiles{nvfind} = varargin{2}(:); +case 'addinfiles' + ninind = numel(instr)+1; + inid(ninind) = varargin{3}; + instr{ninind} = varargin{1}; + indat{ninind} = struct('fun',@files_select_list,'args',{{'getin', ninind}}, ... + 'redraw',1); + infiles{ninind} = varargin{2}(:); +case 'allinfiles' + ninind = numel(instr)+1; + inid(ninind) = -1; + instr{ninind} = varargin{1}; + indat{ninind} = struct('fun',@files_select_list,'args',{{'getin', ninind}}, ... + 'redraw',1); + infiles{ninind} = cat(1,infiles{varargin{2}:varargin{3}}); +case 'getinnum' + varargout{1} = numel(instr); +case 'listall' + varargout{1} = {vfstr{:} instr{:}}; + varargout{2} = {vfdat{:} indat{:}}; +end +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function ok = find_id(c,id) +ok = 0; +if ~isstruct(c) || ~isfield(c,'type'), + return; +end; +if c.id==id, + ok = 1; + return; +end; +if isfield(c,'val'), + for i=1:length(c.val), + ok = find_id(c.val{i},id); + if ok, return; end; + end; +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function c = uniq_id(c) +if ~isstruct(c) || ~isfield(c,'type'), + return; +end; +c.id = rand(1); +if isfield(c,'val'), + for i=1:length(c.val), + c.val{i} = uniq_id(c.val{i}); + end; +end; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function show_msg(txt) +lb = findobj('tag','msg_box'); +if isempty(txt), + set(lb,'String',{}); +else + msg = get(lb,'String'); + if iscell(txt), + msg = {msg{:} txt{:}}; + else + msg = {msg{:} txt}; + end; + set(lb,'String',msg); +end; +drawnow; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +%function beep +%fprintf('%c',7); +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function pulldown +% Create a pulldown for individual jobs +c = initialise_struct; +fg = spm_figure('findwin','Graphics'); +if isempty(fg), return; end; +set(0,'ShowHiddenHandles','on'); +delete(findobj(fg,'tag','jobs')); +set(0,'ShowHiddenHandles','off'); +f0 = uimenu(fg,'Label','TASKS','HandleVisibility','off','tag','jobs'); +pulldown1(f0,c,c.tag); +uimenu(f0,'Label','Batch','CallBack',@interactive,'Separator','on'); +uimenu(f0,'Label','Defaults','CallBack',@defaults_edit,'Separator','off'); +f1 = uimenu(f0,'Label','Sequential'); +pulldown2(f1,c,c.tag); + +if 0, % Currently unused + f1 = uimenu(f0,'Label','Modality'); + modalities = {'FMRI','PET','EEG'}; + for i=1:length(modalities) + tmp = modalities{i}; + if strcmp(tmp,getdef('modality')), + tmp = ['*' tmp]; + else + tmp = [' ' tmp]; + end; + uimenu(f1,'Label',tmp,'CallBack',@chmod); + end; +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function pulldown1(f0,c0,tag0) +if ~isfield(c0,'values'), return; end; +for i=1:length(c0.values), + c1 = c0.values{i}; + if isstruct(c1) && ~isfield(c1,'hidden'), + tag1 = tag0; + if isfield(c1,'tag'), + tag1 = [tag1 '.' c1.tag]; + end; + if isfield(c1,'prog'), + uimenu(f0,'Label',c1.name,'CallBack',@interactive,'UserData',{'',tag1}); + else + f1 = uimenu(f0,'Label',c1.name); + pulldown1(f1,c1,tag1); + end; + end; +end; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function pulldown2(f0,c0,tag0) +if ~isfield(c0,'values'), return; end; +for i=1:length(c0.values), + c1 = c0.values{i}; + if isstruct(c1) && ~isfield(c1,'hidden'), + tag1 = tag0; + if isfield(c1,'tag'), + tag1 = [tag1 '.' c1.tag]; + end; + if isfield(c1,'prog'), + if findcheck(c1), + uimenu(f0,'Label',c1.name,'Enable','off'); + else + uimenu(f0,'Label',c1.name,'CallBack',@run_serial,'UserData',{'',tag1}); + end; + else + f1 = uimenu(f0,'Label',c1.name); + pulldown2(f1,c1,tag1); + end; + end; +end; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function hascheck = findcheck(c) +hascheck = false; +if ~isstruct(c) || ~isfield(c,'type'), return; end; +if isfield(c,'check'), + hascheck = true; + return; +end; +if isfield(c,'values'), + for i=1:numel(c.values), + hascheck = findcheck(c.values{i}); + if hascheck, return; end; + end; +end; +if isfield(c,'val'), + for i=1:numel(c.val), + hascheck = findcheck(c.val{i}); + if hascheck, return; end; + end; +end; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function chmod(mod,varargin) +global defaults +if isempty(defaults), spm_defaults; end; +if ischar(mod), + %if strcmpi(defaults.modality,mod), + % spm('ChMod',mod); + %end; + defaults.modality = mod; +else + tmp = get(mod,'Label'); + defaults.modality = tmp(2:end); +end; +pulldown; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function fszoom(varargin) +fs = sscanf(get(varargin{1},'Label'),'%d'); +set(gco,'FontSize',fs); +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function run_serial(varargin) +ud = get(varargin{1},'UserData'); +if iscell(ud) + serial(ud{:}); +else + serial; +end; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function serial(job,node) + +if nargin<2, node = 'jobs'; end; + +fg = spm_figure('FindWin','Interactive'); +if isempty(fg), fg = spm('CreateIntWin'); end; +delete(findobj(fg,'Parent',fg)); +t=uicontrol(fg,... + 'Style','listbox',... + 'Units','normalized',... + 'Position',[0.02 0.02 0.96 0.62],... + 'Tag','help_box2',... + 'FontName','fixedwidth',... + 'FontSize',12,... + 'BackgroundColor',[1 1 1]); +set(t,'Value',[], 'Enable', 'inactive', 'Max',2, 'Min',0); +workaround(t); +cntxtmnu(t); +spm('Pointer'); +drawnow; + +if nargin>0, + c = initialise_struct(job); +else + c = initialise_struct; +end; + +c = start_node(c,node); +c = start_node(c,@run_ui,{}); +spm_input('!DeleteInputObj'); +delete(findobj(fg,'Parent',fg)); +[unused,jobs] = harvest(c); +%savexml('job_last.xml','jobs'); +run_job(jobs); +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function c = run_ui(c,varargin) + +nnod=1; +while(1), + nod = nnod; + [ci,unused,hlp] = get_node(c,nod); + if isempty(ci), break; end; + + help_box = findobj(0,'tag','help_box2'); + if ~isempty(help_box), + set(help_box,'String',' '); + workaround(help_box); + ext = get(help_box,'Extent'); + pos = get(help_box,'position'); + pw = floor(pos(3)/ext(3)*21-4); + set(help_box,'String',spm_justify(pw,hlp)); + workaround(help_box); + + if isfield(c,'prog'), + try + set(help_box,'HandleVisibility','off'); + [Finter,unused,CmdLine] = spm('FnUIsetup',c.name); + spm('FigName',[c.name ': setup'],Finter,CmdLine); + catch + end; + set(help_box,'HandleVisibility','on'); + end; + end; + + pos = 1; + + switch ci.type, + case {'const','files','menu','entry'} + nnod = nod + 1; + + vl = {''}; + if isfield(ci,'def'), vl = getdef(ci.def); end; + if numel(vl)~=0 && (~ischar(vl{1}) || ~strcmp(vl{1},'')), + getit = 0; + if ~isfield(ci,'val') || ~iscell(ci.val) || isempty(ci.val), + ci.val = vl; + end; + else + getit = 1; + end; + + switch ci.type, + case {'const'} + case {'files'} + num = ci.num; + + if getit, + if ~isempty(ci.val), + sel = ci.val{1}; + else + sel = ''; + end; + addvfiles(ci.id,c); + if isfield(ci,'dir'), + dr = ci.dir; + else + dr = pwd; + end; + if isfield(ci,'ufilter') + uf = ci.ufilter; + else + uf = '.*'; + end; + [ci.val{1},ok] = spm_select(num,ci.filter,ci.name,sel,dr,uf); + if ~ok, + error('File Selector was deleted.'); + end; + spm_select('clearvfiles'); + ci.val{1} = cellstr(ci.val{1}); + end; + + case {'menu'} + dv = []; + if getit, + if isfield(ci,'val') && ~isempty(ci.val), + for i=1:length(ci.values) + if same(ci.values{i},ci.val{1}) + dv = i; + end; + end; + end; + lab = ci.labels{1}; + for i=2:length(ci.values), + lab = [lab '|' ci.labels{i}]; + end; + if isempty(dv), + ind = spm_input(ci.name,pos,'m',lab,1:length(ci.values)); + else + ind = spm_input(ci.name,pos,'m',lab,1:length(ci.values),dv); + end; + ci.val = {ci.values{ind}}; + end; + + case {'entry'} + n1 = Inf; + if isfield(ci,'num'), n1 = ci.num; end; + if getit, + val = ''; + if isfield(ci,'val') && ~isempty(ci.val) && ~strcmp(ci.val{1},''), + val = ci.val{1}; + end; + if isfield(ci,'extras') + val = spm_input(ci.name,pos,ci.strtype,val,n1,ci.extras); + else + val = spm_input(ci.name,pos,ci.strtype,val,n1); + end; + ci.val{1} = val; + end; + end; + + case {'repeat'}, + lab = 'Done'; + for i=1:length(ci.values) + lab = [lab '|New "' ci.values{i}.name '"']; + end; + tmp = spm_input(ci.name,pos,'m',lab,0:length(ci.values)); + if tmp, + ci.val = {ci.val{:}, uniq_id(ci.values{tmp})}; + nnod = nod; + else + nnod = nod + 1; + end; + + case {'choice'} + nnod = nod + 1; + lab = ci.values{1}.name; + for i=2:length(ci.values) + lab = [lab '|' ci.values{i}.name]; + end; + tmp = spm_input(ci.name,pos,'m',lab,1:length(ci.values)); + ci.val = {uniq_id(ci.values{tmp})}; + otherwise + error('This should not happen.'); + end; + c = set_node(c,nod,ci); +end; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function [ci,n,hlp] = get_node(c,n) +ci = []; +hlp = ''; +switch c.type, +case {'const','files','menu','entry'} + n = n-1; + if n==0, + ci = c; + hlp = {['* ' upper(c.name)],c.help{:},'',''}; + return; + end; + +case 'choice' + n = n-1; + if n==0, + ci = c; + hlp = {['* ' upper(c.name)],c.help{:},'',''}; + return; + end; + [ci,n,hlp] = get_node(c.val{1},n); + if ~isempty(ci), + hlp = {hlp{:},repmat('=',1,20),'',['* ' upper(c.name)],c.help{:},'',''}; + return; + end; + +case 'branch', + for i=1:numel(c.val), + [ci,n,hlp] = get_node(c.val{i},n); + if ~isempty(ci), + hlp = {hlp{:},repmat('=',1,20),'',['* ' upper(c.name)],c.help{:},'',''}; + return; + end; + end; + +case 'repeat', + for i=1:numel(c.val), + [ci,n,hlp] = get_node(c.val{i},n); + if ~isempty(ci), + hlp = {hlp{:},repmat('=',1,20),'',['* ' upper(c.name)],c.help{:},'',''}; + return; + end; + end; + n = n-1; + if n==0, + ci = c; + hlp = {['* ' upper(c.name)],c.help{:},'',''}; + return; + end; +otherwise + error('This should not happen'); + +end; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function [c,n] = set_node(c,n,ci) +switch c.type, +case {'const','files','menu','entry'} + n = n-1; + if n==0, + c = ci; + return; + end; + +case 'choice' + n = n-1; + if n==0, + c = ci; + return; + end; + [c.val{1},n] = set_node(c.val{1},n,ci); + if n<0,return; end; + +case 'branch', + for i=1:numel(c.val), + [c.val{i},n] = set_node(c.val{i},n,ci); + if n<0,return; end; + end; + +case 'repeat', + for i=1:numel(c.val), + [c.val{i},n] = set_node(c.val{i},n,ci); + if n<0,return; end; + end; + n = n-1; + if n==0, + c = ci; + return; + end; + +end; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function c = initialise_struct(job) +% load the config file, possibly adding a job +% to it, and generally tidy it up. The batch box +% is updated to contain the current structure. + +persistent c0 +if isempty(c0), + c0 = spm_config; + c0 = tidy_struct(c0); +end; +c = insert_defs(c0); +if nargin==1 && ischar(job) && strcmp(job,'defaults'), + c = defsub(c,{}); + c.name = 'SPM Defaults'; +else + c = hide_null_jobs(c); + if nargin>0 && ~isempty(job), + job = fromfile(job); + c = job_to_struct(c,job,'jobs'); + c = uniq_id(c); + end; +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function [c,defused] = defsub(c,defused) +if nargin<2, defused = {}; end; +if isfield(c,'prog'), c = rmfield(c,'prog'); end; +switch c.type, +case {'const'} + c = []; + +case {'menu','entry','files'} + if ~isfield(c,'def') || any(strcmp(c.def,defused)), + c = []; + else + defused = {defused{:},c.def}; + end; + +case {'branch'} + msk = true(1,length(c.val)); + for i=1:length(c.val), + [c.val{i},defused] = defsub(c.val{i},defused); + msk(i) = ~isempty(c.val{i}); + end; + c.val = c.val(msk); + if isempty(c.val), c = []; end; + +case {'choice','repeat'} + c.type = 'branch'; + c.val = c.values; + c = rmfield(c,'values'); + [c,defused] = defsub(c,defused); + +end; +if isfield(c,'vfiles'), c = rmfield(c,'vfiles'); end; +if isfield(c,'check'), c = rmfield(c,'check'); end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function c = insert_defs(c) +% Recursively descend through the tree structure, +% and assigning default values. +if ~isstruct(c) || ~isfield(c,'type'), + return; +end; +switch c.type +case {'menu','entry','files'} + if isfield(c,'def') + c.val = getdef(c.def); + if strcmp(c.type,'files') && ~isempty(c.val) + if ~isempty(c.val{1}) + c.val = {cellstr(c.val{1})}; + else + c.val = {{}}; + end; + end; + end; + +case {'repeat','choice'}, + if isfield(c,'values') + for i=1:numel(c.values) + c.values{i} = insert_defs(c.values{i}); + end; + end; +end; +if isfield(c,'val') + for i=1:numel(c.val) + c.val{i} = insert_defs(c.val{i}); + end; +end; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function c = tidy_struct(c) +% Recursively descend through the tree structure, cleaning up +% fields that may be missing and adding an 'expanded' field +% where necessary. + +if ~isstruct(c) || ~isfield(c,'name') || ~isfield(c,'type') + return; +end; +c.id = rand(1); + +if ~isfield(c,'help'), c.help = {}; end; +if ischar(c.help), c.help = {c.help}; end; + +switch c.type +case {'const'} + if ~isfield(c,'tag') + disp(c); warning(['No tag field for "' c.name '"']); + c.tag = 'unknown'; + end; + if ~isfield(c,'val') + disp(c); warning(['No val field for "' c.name '"']); + c.val = {''}; + end; + +case {'menu'} + if ~isfield(c,'tag') + disp(c); warning(['No tag field for "' c.name '"']); + c.tag = 'unknown'; + end; + + if ~isfield(c,'labels') || ~isfield(c,'values') + disp(c); warning(['No labels and values field for "' c.name '"']); + c.labels = {}; + c.values = {}; + end; + if length(c.labels) ~= length(c.values) + disp(c); warning(['Labels and values fields incompatible for "' c.name '"']); + c.labels = {}; + c.values = {}; + end; + if ~isfield(c,'help'), c.help = {'Option selection by menu'}; end; + +case {'entry'} + if ~isfield(c,'tag') + disp(c); warning(['No tag field for "' c.name '"']); + c.tag = 'unknown'; + end; + if ~isfield(c,'strtype') + disp(c); warning(['No strtype field for "' c.name '"']); + c.strtype = 'e'; + end; + if ~isfield(c,'num') + disp(c); warning(['No num field for "' c.name '"']); + c.num = [1 1]; + end; + if length(c.num)~=2 + disp(c); warning(['Num field for "' c.name '" is wrong length']); + c.num = [Inf 1]; + end; + if ~isfield(c,'help'), c.help = {'Option selection by text entry'}; end; + +case {'files'} + if ~isfield(c,'tag') + disp(c); warning(['No tag field for "' c.name '"']); + c.tag = 'unknown'; + end; + if ~isfield(c,'filter') + disp(c); warning(['No filter field for "' c.name '"']); + c.filter = '*'; + end; + if ~isfield(c,'num') + disp(c); warning(['No num field for "' c.name '"']); + c.num = Inf; + end; + if length(c.num)~=1 && length(c.num)~=2 + disp(c); warning(['Num field for "' c.name '" is wrong length']); + c.num = Inf; + end; + if isfield(c,'val') && iscell(c.val) && numel(c.val)>=1, + if ischar(c.val{1}) + c.val{1} = cellstr(c.val{1}); + end; + end; + if ~isfield(c,'help'), c.help = {'File selection'}; end; + +case {'branch'} + if ~isfield(c,'tag') + disp(c); warning(['No tag field for "' c.name '"']); + c.tag = 'unknown'; + end; + if ~isfield(c,'val') + disp(c); warning(['No val field for "' c.name '"']); + c.val = {}; + end; + + c.expanded = false; + if ~isfield(c,'help'), c.help = {'Branch structure'}; end; + +case {'choice'} + if ~isfield(c,'tag') + disp(c); warning(['No tag field for "' c.name '"']); + c.tag = 'unknown'; + end; + if ~isfield(c,'values') || ~iscell(c.values) + disp(c); error(['Bad values for "' c.name '"']); + end; + for i=1:length(c.values) + c.values{i} = tidy_struct(c.values{i}); + end; + if ~isfield(c,'val') || ~iscell(c.val) || length(c.val) ~= 1 + c.val = {c.values{1}}; + end; + c.expanded = true; + if ~isfield(c,'help'), c.help = {'Choice structure'}; end; + +case {'repeat'} + if ~isfield(c,'values') || ~iscell(c.values) + disp(c); error(['Bad values for "' c.name '"']); + end; + for i=1:length(c.values) + c.values{i} = tidy_struct(c.values{i}); + end; + if length(c.values)>1 && ~isfield(c,'tag') + disp(c); warning(['No tag field for "' c.name '"']); + c.tag = 'unknown'; + end; + if length(c.values)==1 && isfield(c,'tag') + % disp(c); warning(['"' c.name '" has unused tag']); + c = rmfield(c,'tag'); + end; + c.expanded = true; + if ~isfield(c,'help'), c.help = {'Repeated structure'}; end; + + if isfield(c,'num') && numel(c.num)==1, + if finite(c.num), + c.num = [c.num c.num]; + else + c.num = [0 c.num]; + end; + end; + +end; + +if ~isfield(c,'val'), c.val = {}; end; + +%switch c.type +%case {'menu','entry','files'} +% %if isempty(c.val) +% if isfield(c,'def') +% c.val = getdef(c.def); +% if strcmp(c.type,'files') && ~isempty(c.val) +% c.val = {cellstr(c.val{1})}; +% end; +% end; +% %end; +%end; + +if isfield(c,'val') + for i=1:length(c.val) + c.val{i} = tidy_struct(c.val{i}); + end; +end; +if isfield(c,'values') && strcmp(c.type,'repeat') + for i=1:length(c.values) + c.values{i} = tidy_struct(c.values{i}); + end; +end; + +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function job = fromfile(job) +if ischar(job) + [pth,nam,ext] = fileparts(job); + if strcmp(ext,'.xml') + spm('Pointer','Watch'); + try + loadxml(job,'jobs'); + catch + questdlg('LoadXML failed',job,'OK','OK'); + return; + end; + spm('Pointer'); + elseif strcmp(ext,'.mat') + try + load(job,'jobs'); + catch + questdlg('Load failed',job,'OK','OK'); + return; + end; + else + questdlg(['Unknown extension (' ext ')'],'Nothing loaded','OK','OK'); + return; + end; + job = jobs; +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function c = hide_null_jobs(c) +c = hide_null_jobs1(c); +c = hide_null_jobs2(c); +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function [c,flg] = hide_null_jobs1(c) +if ~isstruct(c) || ~isfield(c,'type'), + flg = true; + return; +end; + +if ~include(c), + flg = false; + c.hidden = true; + return; +end; + +switch c.type, +case {'repeat','branch','choice'}, + msk1 = true; + msk2 = true; + if isfield(c,'val') && ~isempty(c.val), + msk1 = true(1,numel(c.val)); + for i=1:length(c.val) + [c.val{i},msk1(i)] = hide_null_jobs1(c.val{i}); + end; + end; + if isfield(c,'values') && ~isempty(c.values), + msk2 = true(1,numel(c.values)); + for i=1:length(c.values) + [c.values{i},msk2(i)] = hide_null_jobs1(c.values{i}); + end; + end; + flg = any(msk1) || any(msk2); + if ~flg, c.hidden = true; end; +otherwise + flg = true; +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function ok = include(c) +% Check that the modality is OK +ok = true; +if isfield(c,'modality'), + mod = getdef('modality'); + if ~isempty(mod), + mod = mod{1}; + ok = false; + for i=1:length(c.modality), + if strcmpi(c.modality{i},mod), + ok = true; + return; + end; + end; + end; +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function [c,flg] = hide_null_jobs2(c) +if ~isstruct(c) || ~isfield(c,'type') + flg = 0; + return; +end; +if isfield(c,'prog'), + flg = 1; + return; +end; + +switch c.type, +case {'repeat','branch','choice'}, + flg = 0; + msk1 = []; + msk2 = []; + if isfield(c,'val'), + msk1 = ones(1,numel(c.val)); + for i=1:length(c.val) + [c.val{i},msk1(i)] = hide_null_jobs2(c.val{i}); + end; + end; + if isfield(c,'values'), + msk2 = ones(1,numel(c.values)); + for i=1:length(c.values) + [c.values{i},msk2(i)] = hide_null_jobs2(c.values{i}); + end; + end; + if (sum(msk1) + sum(msk2))>0, flg = 1; end; + if ~flg, c.hidden = 1; end; +otherwise + flg = 0; +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function c = job_to_struct(c,job,tag) +% Modify a structure based on a batch job +if isstruct(c) && isfield(c,'hidden'), + return; +end; +switch c.type +case {'const','menu','files','entry'} + if ~strcmp(gettag(c),tag), return; end; + if ischar(job) && strcmp(job,'') + c.val = {}; + else + c.val{1} = job; + end; + +case {'branch'} + if ~strcmp(gettag(c),tag), return; end; + if ~isstruct(job), return; end; + + tag = fieldnames(job); + for i=1:length(tag) + for j=1:length(c.val) + if strcmp(gettag(c.val{j}),tag{i}) + c.val{j} = job_to_struct(c.val{j},job.(tag{i}),tag{i}); + break; + end; + end; + end; + +case {'choice'} + if ~strcmp(gettag(c),tag), return; end; + if ~isstruct(job), return; end; + tag = fieldnames(job); + if length(tag)>1, return; end; + tag = tag{1}; + for j=1:length(c.values) + if strcmp(gettag(c.values{j}),tag) + c.val = {job_to_struct(c.values{j},job.(tag),tag)}; + end; + end; + +case {'repeat'} + if ~strcmp(gettag(c),tag), return; end; + if length(c.values)==1 && strcmp(c.values{1}.type,'branch') + if ~isstruct(job), return; end; + for i=1:length(job) + if strcmp(gettag(c.values{1}),tag) + c.val{i} = job_to_struct(c.values{1},job(i),tag); + c.val{i}.removable = true; + end; + end; + elseif length(c.values)>1 + if ~iscell(job), return; end; + for i=1:length(job) + tag = fieldnames(job{i}); + if length(tag)>1, return; end; + tag = tag{1}; + for j=1:length(c.values) + if strcmp(gettag(c.values{j}),tag) + c.val{i} = job_to_struct(c.values{j},job{i}.(tag),tag); + c.val{i}.removable = true; + break; + end; + end; + end; + else + if ~iscell(job), return; end; + for i=1:length(job) + c.val{i} = job_to_struct(c.values{1},job{i},tag); + c.val{i}.removable = true; + end; + end; + +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function doc = showdoc(str,wid) +if nargin<1, str = ''; end; +if nargin<2, wid = 60; end; + +tmp = [0 find([str '.']=='.')]; +node = {}; +for i=1:length(tmp)-1, + tmp1 = str((tmp(i)+1):(tmp(i+1)-1)); + if ~isempty(tmp1), + node = {node{:},tmp1}; + end; +end; +if numel(node)>1 && strcmp(node{1},'jobs'), + node = node(2:end); +end; + +c = initialise_struct; +doc = showdoc1(node,c,wid); +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function doc = showdoc1(node,c,wid) +doc = {}; +if isempty(node), + doc = showdoc2(c,'',wid); + return; +end; + +if isfield(c,'values'), + for i=1:numel(c.values), + if isfield(c.values{i},'tag') && strcmp(node(1),c.values{i}.tag), + doc = showdoc1(node(2:end),c.values{i},wid); + return; + end; + end; +end; + +if isfield(c,'val'), + for i=1:numel(c.val), + if isfield(c.val{i},'tag') && strcmp(node(1),c.val{i}.tag), + doc = showdoc1(node(2:end),c.val{i},wid); + return; + end; + end; +end; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function doc = showdoc2(c,lev,wid) +doc = {''}; +if ~isempty(lev) && sum(lev=='.')==1, + % doc = {doc{:},repmat('_',1,80),''}; +end; + +if isfield(c,'name'), + str = sprintf('%s %s', lev, c.name); + %under = repmat('-',1,length(str)); + doc = {doc{:},str}; + % if isfield(c,'modality'), + % txt = 'Only for '; + % for i=1:numel(c.modality), + % txt = [txt ' ' c.modality{i}]; + % end; + % doc = {doc{:},'',txt, ''}; + %end; + if isfield(c,'help'); + hlp = spm_justify(wid,c.help); + doc = {doc{:},hlp{:}}; + end; + + switch (c.type), + case {'repeat'}, + if length(c.values)==1, + doc = {doc{:}, '', sprintf('Repeat "%s", any number of times.',c.values{1}.name)}; + else + doc = {doc{:}, '', 'Any of the following options can be chosen, any number of times'}; + i = 0; + for ii=1:length(c.values), + if isstruct(c.values{ii}) && isfield(c.values{ii},'name'), + i = i+1; + doc = {doc{:}, sprintf(' %2d) %s', i,c.values{ii}.name)}; + end; + end; + end; + doc = {doc{:},''}; + + case {'choice'}, + doc = {doc{:}, '', 'Any one of these options can be selected:'}; + i = 0; + for ii=1:length(c.values), + if isstruct(c.values{ii}) && isfield(c.values{ii},'name'), + i = i+1; + doc = {doc{:}, sprintf(' %2d) %s', i,c.values{ii}.name)}; + end; + end; + doc = {doc{:},''}; + + case {'branch'}, + doc = {doc{:}, '', sprintf('This item contains %d fields:', length(c.val))}; + i = 0; + for ii=1:length(c.val), + if isstruct(c.val{ii}) && isfield(c.val{ii},'name'), + i = i+1; + doc = {doc{:}, sprintf(' %2d) %s', i,c.val{ii}.name)}; + end; + end; + doc = {doc{:},''}; + + case {'menu'}, + doc = {doc{:}, '', 'One of these values is chosen:'}; + for k=1:length(c.labels), + doc = {doc{:}, sprintf(' %2d) %s', k, c.labels{k})}; + end; + doc = {doc{:},''}; + + case {'files'}, + if length(c.num)==1 && isfinite(c.num(1)) && c.num(1)>=0, + tmp = spm_justify(wid,sprintf('A "%s" file is selected by the user.',c.filter)); + else + tmp = spm_justify(wid,sprintf('"%s" files are selected by the user.\n',c.filter)); + end; + doc = {doc{:}, '', tmp{:}, ''}; + + case {'entry'}, + switch c.strtype, + case {'e'}, + d = 'Evaluated statements'; + case {'n'}, + d = 'Natural numbers'; + case {'r'}, + d = 'Real numbers'; + case {'w'}, + d = 'Whole numbers'; + otherwise, + d = 'Values'; + end; + tmp = spm_justify(wid,sprintf('%s are entered.',d)); + doc = {doc{:}, '', tmp{:}, ''}; + end; + + i = 0; + doc = {doc{:},''}; + if isfield(c,'values'), + for ii=1:length(c.values), + if isstruct(c.values{ii}) && isfield(c.values{ii},'name'), + i = i+1; + lev1 = sprintf('%s%d.', lev, i); + doc1 = showdoc2(c.values{ii},lev1,wid); + doc = {doc{:}, doc1{:}}; + end; + end; + end; + if isfield(c,'val'), + for ii=1:length(c.val), + if isstruct(c.val{ii}) && isfield(c.val{ii},'name'), + i = i+1; + lev1 = sprintf('%s%d.', lev, i); + doc1 = showdoc2(c.val{ii},lev1,wid); + doc = {doc{:}, doc1{:}}; + end; + end; + end; + doc = {doc{:}, ''}; +end; + + diff --git a/spm/spm5/spm_log2html.m b/spm/spm5/spm_log2html.m new file mode 100644 index 0000000..09ea46f --- /dev/null +++ b/spm/spm5/spm_log2html.m @@ -0,0 +1,154 @@ +function htmlfile = spm_log2html(prog, val, options) +%SPM_LOG2HTML - Log report on SPM processing to HTML file +% [] = spm_log2html(prog, val, options) print log to the html file named in output +% +% Example +% >> spm_log2html +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2008 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2008-10-17 Creation +% +% ----------------------------- Script History --------------------------------- + +% I guess there exists an xml -> html thingy I could use somewhere... + +prog_infos=functions(prog) +% jobname=sprintf('%s:', prog_infos.parentage{end:-1:1}); +% jobname(end)=[]; +jobname=sprintf('%s', prog_infos.parentage{1}); +logtxt={}; +logtxt{end+1}=['SPM logging for: ' jobname]; +htmlfile = fullfile(fileparts(getfield(spm_vol(val.data{1}{1}),'fname')),'log',[jobname '.html']); +logtxt{end+1}=['Into: ']; +logtxt{end+1}={'file' htmlfile}; +htmldir = fileparts(htmlfile); +% if exist(htmldir, 'dir') +% if length(dir(htmldir)) > 2 +% htmldir_old=fullfile(fileparts(htmldir), ['log_' datestr(now,30)]); +% movefile(htmldir,-) +% logtxt{end+1}=['Renaming older directory: ' ]; +% logtxt{end+1}={'folder', htmldir_old}; +% mkdir(htmldir) +% end +% end +if not(exist(htmldir, 'dir')) + logtxt{end+1}=['Creating new directory: ' ]; + logtxt{end+1}={'folder', htmldir}; + mkdir(htmldir) +end + +switch jobname + case 'estimate' + logtxt=log_realign(val,htmldir,logtxt); + case 'reslice'; + % to do + case 'estwrite_fun'; + % to do + otherwise + logtxt{end+1}=['Unknown function to log: ' jobname]; +end +log2html(htmlfile,logtxt) +return + +function [spname]=shortpathname(pname) +% shortens file names +spname=[pname(1:10) '...' pname(end-15:end)]; + +function [hpname]=htmlpathname(pname) +% Replace filesep in file path with slashes (/) for HTML compatibility +hpname = strrep(pname,filesep,'/'); + +function []=log2html(htmlfile, log) +% print out to html file +save(fullfile(fileparts(htmlfile),'spm_log2html.mat'),'log'); +fid=fopen(htmlfile,'wt'); +html_header(fid); +fprintf(fid,'

      '); +for i=1:length(log); + if ischar(log{i}) + if i>1 + fprintf(fid, '

      \n

      '); + end + fprintf(fid, '%s', log{i}); + else + switch log{i}{1} + case 'picture' + fprintf(fid, '',htmlpathname(log{i}{2}),htmlpathname(log{i}{2}),shortpathname(log{i}{2})); + case {'folder' 'file'} + fprintf(fid, '%s',htmlpathname(log{i}{2}),shortpathname(log{i}{2})); + end + end +end +fprintf(fid,'Done.

      \n\n'); +if fid > 2 + fclose(fid); + try + browser = '"C:\Documents and Settings\ndiayek\Local Settings\Application Data\Google\Chrome\Application\chrome.exe"' + system([browser ' "' htmlfile '"']); + catch + browser = '"C:\program Files\Mozilla Firefox\firefox.exe" '; + end; +end +return + +function [logtxt]=html_header(fid) +fprintf(fid,'\n'); +fprintf(fid,'\n'); +fprintf(fid,'

      Processing steps:

        '); +fprintf(fid,'
      1. realign'); +fprintf(fid,'
      2. segment'); +fprintf(fid,'
      3. normalize'); +fprintf(fid,'
      4. smooth'); +fprintf(fid,'
      5. design'); +fprintf(fid,'

      '); + + + +function [logtxt]=html_footer(htmlfile, logtxt) + + + +function [logtxt] = log_design(val,htmldir,logtxt) +% Log of DESIGN results + + +function [logtxt] = log_realign(val,htmldir,logtxt) +% Log of REALIGN results +mfile = which('spm_realign'); +if ~isequal(textread(mfile,'%s',4,'headerlines',82,'delimiter',' '),... + {'%', '$Id:', 'spm_realign.m', '433'}') % The native version at the time + logtxt{end+1}=sprintf('The spm_realign.m (%s) has changed! Default flags may also have changed.', mfile); + warning(logtxt(1)); +end +def_flags = struct('quality',1,'fwhm',5,'sep',4,'interp',2,'wrap',[0 0 0],'rtm',0,'PW','','graphics',1,'lkp',1:6); +flags =mergestructs(def_flags,val.eoptions); +if flags.graphics + opts = {fullfile(htmldir,'realign_estimate.png'),'-noui','-painters','-dpng'}; + fg = spm_figure('FindWin','Graphics'); + logtxt{end+1} = {'Plot of the Movement Parameters'}; + % export figure in image file + print(fg,opts{:}); + logtxt{end+1} = {'picture','realign_estimate.png'}; +else + logtxt{end+1}='Graphics must be generated for logging. Sorry...'; + % rp_file = [spm_str_manip(prepend(getfield(spm_vol(val.data{1}{1}),'fname'),'rp_'),'s') '.txt']; + % P=textread(rp_file); + % plot_parameters(P) +end +return + + +function [logtxt] = log_smooth(val,htmldir,logtxt) +% Log of SMOOTH results +mfile = which('spm_smooth'); +logtxt(end+1)='todo'; +return diff --git a/spm/spm5/spm_vol.m b/spm/spm5/spm_vol.m new file mode 100644 index 0000000..ddd3179 --- /dev/null +++ b/spm/spm5/spm_vol.m @@ -0,0 +1,185 @@ +function V = spm_vol(P) +% Get header information etc for images. +% FORMAT V = spm_vol(P) +% P - a matrix of filenames. +% V - a vector of structures containing image volume information. +% The elements of the structures are: +% V.fname - the filename of the image. +% V.dim - the x, y and z dimensions of the volume +% V.dt - A 1x2 array. First element is datatype (see spm_type). +% The second is 1 or 0 depending on the endian-ness. +% V.mat - a 4x4 affine transformation matrix mapping from +% voxel coordinates to real world coordinates. +% V.pinfo - plane info for each plane of the volume. +% V.pinfo(1,:) - scale for each plane +% V.pinfo(2,:) - offset for each plane +% The true voxel intensities of the jth image are given +% by: val*V.pinfo(1,j) + V.pinfo(2,j) +% V.pinfo(3,:) - offset into image (in bytes). +% If the size of pinfo is 3x1, then the volume is assumed +% to be contiguous and each plane has the same scalefactor +% and offset. +%____________________________________________________________________________ +% +% The fields listed above are essential for the mex routines, but other +% fields can also be incorporated into the structure. +% +% The images are not memory mapped at this step, but are mapped when +% the mex routines using the volume information are called. +% +% Note that spm_vol can also be applied to the filename(s) of 4-dim +% volumes. In that case, the elements of V will point to a series of 3-dim +% images. +% +% This is a replacement for the spm_map_vol and spm_unmap_vol stuff of +% MatLab4 SPMs (SPM94-97), which is now obsolete. +%_______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% John Ashburner +% $Id: spm_vol.m 504 2006-04-25 13:44:50Z volkmar $ + +if nargin==0, + V = struct('fname', {},... + 'dim', {},... + 'dt', {},... + 'pinfo', {},... + 'mat', {},... + 'n', {},... + 'descrip', {},... + 'private',{}); + return; +end; + +% If is already a vol structure then just return; +if isstruct(P), V = P; return; end; +try + V = subfunc2(P); +catch + P=myspm_updatepath(P); + V = subfunc2(P); +end +return; + +function V = subfunc2(P) +if iscell(P), + V = cell(size(P)); + for j=1:numel(P), + try + if iscell(P{j}), + V{j} = subfunc2(P{j}); + else + V{j} = subfunc1(P{j}); + end; + catch + % Handling of files being moved around... + persistent pwd + if ~isempty(pwd) + end +warning('Not found: %s\n Try changing filesep...', P{j}) + P{j} = translocate(P{j}); + if iscell(P{j}), + V{j} = subfunc2(P{j}); + else + V{j} = subfunc1(P{j}); + end; + warning('ok! filesep has been changed in %s', P{j}) + end + end; +else + try + V = subfunc1(P); + catch + V = subfunc1(translocate(P)); + warning('ok! filesep has been changed in %s', P) + end +end; +return; + +function Q=translocate(P) +% from Linux to Win & vice-versa. +if isunix + Q = strrep(P, '\', filesep); +else + Q = strrep(P, '/', filesep); +end +return + +function V = subfunc1(P) +if isempty(P), + V = []; + return; +end; + +counter = 0; +for i=1:size(P,1), + v = subfunc(P(i,:)); + [V(counter+1:counter+size(v, 2),1).fname] = deal(''); + [V(counter+1:counter+size(v, 2),1).mat] = deal([0 0 0 0]); + [V(counter+1:counter+size(v, 2),1).mat] = deal(eye(4)); + [V(counter+1:counter+size(v, 2),1).mat] = deal([1 0 0]'); + if isempty(v), + hread_error_message(P(i,:)); + error(['Can''t get volume information for ''' P(i,:) '''']); + end + + f = fieldnames(v); + for j=1:size(f,1) + eval(['[V(counter+1:counter+size(v,2),1).' f{j} '] = deal(v.' f{j} ');']); + end + counter = counter + size(v,2); +end + +return; + +function V = subfunc(p) +V = []; +p = deblank(p); +[pth,nam,ext] = fileparts(deblank(p)); +t = find(ext==','); + +n = []; +if ~isempty(t), + t = t(1); + n1 = ext((t+1):end); + if ~isempty(n1), + n = str2num(n1); + ext = ext(1:(t-1)); + end; +end; +p = fullfile(pth,[nam ext]); + +if strcmpi(ext,'.nii') || (strcmpi(ext,'.img') && ... + (exist(fullfile(pth,[nam '.hdr']),'file') || exist(fullfile(pth,[nam '.HDR']),'file'))), + if isempty(n), V = spm_vol_nifti(p); + else V = spm_vol_nifti(p,n); end; + + if isempty(n) && length(V.private.dat.dim) > 3 + V0(1) = V; + for i = 2:V.private.dat.dim(4) + V0(i) = spm_vol_nifti(p, i); + end + V = V0; + end + + if ~isempty(V), return; end; + +else % Try other formats + error('%s: Unknown file format.',p); +end; +return; + + +%_______________________________________________________________________ +function hread_error_message(q) + +str = {... + 'Error reading information on:',... + [' ',spm_str_manip(q,'k40d')],... + ' ',... + 'Please check that it is in the correct format.'}; + +spm('alert*',str,mfilename,sqrt(-1)); + +return; +%_______________________________________________________________________ diff --git a/spm/spm_check_realign.m b/spm/spm_check_realign.m new file mode 100644 index 0000000..0d8c4e4 --- /dev/null +++ b/spm/spm_check_realign.m @@ -0,0 +1,109 @@ +function varargout = spm_check_realign(SPM) +%SPM_CHECK_REALIGN - One line description goes here. +% [] = spm_check_realign(SPM) +% +% Example +% >> spm_check_realign +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2008 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2008-10-13 Creation +% +% ----------------------------- Script History --------------------------------- + +if nargin<1 + SPM=uigetpathfile('rp*.txt'); +end +if ischar(SPM) + if exist(SPM,'file') + SPM=load(SPM); + elseif exist(SPM,'dir') + SPM=load(fullfile(SPM, 'SPM.mat')); + else + error('Unknown file/directory: %s', SPM); + end +end +if isstruct(SPM) + if isfield(SPM,'SPM') + SPM=SPM.SPM; + end + lcd=pwd; + try;cd(SPM.swd);end + [p,f,e]=fileparts(SPM.xY.VY(1).fname); + try + P=load(fullfile(p, 'rp_f_0001.txt')); + catch + try + P=load(fullfile(fileparts(p), 'rp_f_0001.txt')); + catch + error('Can''t find rp_*.txt'); + end + end +elseif size(SPM,2)==6 + P=SPM; +else + error('Wrong type of data') +end +[ax]=plot_parameters2(P); +if nargout>0 + varargout={P,ax}; +end + +% Adapted from spm_realign.m/plot_parameters (SPM2) +%_______________________________________________________________________ +function ax=plot_parameters2(Params) +if iscell(Params) + Params = cat(1,Params{:}); +end +if length(Params)<2, return; end; + +% +% for i=1:numel(P), +% Params(i,:) = spm_imatrix(P(i).mat/P(1).mat); +% end + +% display results +% translation and rotation over time series +%------------------------------------------------------------------- +% spm_figure('Clear','Graphics'); +fg=gcf; +set(fg,'Name','Image realignment'); +%ax=axes('Position',[0.1 0.65 0.8 0.2])%,'Parent',fg,'Visible','off'); +% set(get(ax,'Title'),'String','Image realignment (check)','FontSize',16,'FontWeight','Bold','Visible','on'); +%x = 0.1; +%y = 0.9; +% for i = 1:min([numel(P) 12]) +% text(x,y,[sprintf('%-4.0f',i) ],'FontSize',10,'Interpreter','none','Parent',ax); +% y = y - 0.08; +% end +% if numel(P) > 12 +% text(x,y,'................ etc','FontSize',10,'Parent',ax); end + +ax(1)=subplot(2,1,1,'Parent',fg,'XGrid','on','YGrid','on'); +hp=plot(Params(:,1:3),'Parent',ax(1)); +s = {'x translation' 'y translation' 'z translation'}; +%text([2 2 2], Params(2, 1:3), s, 'Fontsize',10,'Parent',ax) +set(get(ax(1),'Title'),'String','translation','FontSize',16,'FontWeight','Bold'); +%set(get(ax,'Xlabel'),'String','image'); +set(get(ax(1),'Ylabel'),'String','mm'); +legend(hp, s, 0) + +%ax=axes('Position',[0.1 0.05 0.8 0.2],'Parent',fg,'XGrid','on','YGrid','on'); +ax(2)=subplot(2,1,2,'Parent',fg,'XGrid','on','YGrid','on'); +hp=plot(Params(:,4:6)*180/pi,'Parent',ax(2)); +s = {'pitch';'roll ';'yaw '}; +%text([2 2 2], Params(2, 4:6)*180/pi, s, 'Fontsize',10,'Parent',ax) +set(get(ax(2),'Title'),'String','rotation','FontSize',16,'FontWeight','Bold'); +set(get(ax(2),'Xlabel'),'String','image'); +set(get(ax(2),'Ylabel'),'String','degrees'); +legend(hp, s, 0) +return; +%_______________________________________________________________________ diff --git a/spm/spm_log2html/spm_jobman.m b/spm/spm_log2html/spm_jobman.m new file mode 100644 index 0000000..1baf882 --- /dev/null +++ b/spm/spm_log2html/spm_jobman.m @@ -0,0 +1,2934 @@ +function varargout = spm_jobman(varargin) +% UI/Batching stuff +%_______________________________________________________________________ +% This code is based on an earlier version by Philippe Ciuciu and +% Guillaume Flandin of Orsay, France. +% +% FORMAT spm_jobman +% spm_jobman('interactive') +% spm_jobman('interactive',job) +% spm_jobman('interactive',job,node) +% spm_jobman('interactive','',node) +% Runs the user interface in interactive mode. +% +% FORMAT spm_jobman('serial') +% spm_jobman('serial',job) +% spm_jobman('serial',job,node) +% spm_jobman('serial','',node) +% Runs the user interface in serial mode. +% +% FORMAT spm_jobman('run',job) +% Runs a job. +% +% FORMAT spm_jobman('help',node) +% spm_jobman('help',node,width) +% Creates a cell array containing help information. This is justified +% to be 'width' characters wide. e.g. +% h = spm_jobman('help','jobs.spatial.coreg.estimate'); +% for i=1:numel(h),fprintf('%s\n',h{i}); end; +% +% node - indicates which part of the configuration is to be used. +% For example, it could be 'jobs.spatial.coreg'. +% +% job - can be the name of a jobfile (as a .mat or a .xml), or a +% 'jobs' variable loaded from a jobfile. +% +% +% FORMAT spm_jobman('defaults') +% Runs the interactive defaults editor. +% +% FORMAT spm_jobman('pulldown') +% Creates a pulldown 'TASKS' menu in the Graphics window. +% +% FORMAT spm_jobman('chmod') +% Changes the modality for the TASKS pulldown. +% +% FORMAT [tag,dat] = spm_jobman('harvest',c) +% Take a data structure, and extract what is needed to save it +% as a batch job (for experts only). +% +%_______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% John Ashburner +% $Id: spm_jobman.m 639 2006-09-28 17:09:17Z volkmar $ + + +if nargin==0 + setup_ui; +else + switch lower(varargin{1}) + case {'interactive'} + if nargin>=2 + setup_ui(varargin{2:nargin}); + else + setup_ui; + end; + + case {'serial'} + if nargin>=2, + serial(varargin{2:nargin}); + else + serial; + end; + + case {'run'} + if nargin<2 + error('Nothing to run'); + end; + run_job(varargin{2}); + + case {'defaults'}, + setup_ui('defaults'); + + case {'pulldown'} + pulldown; + + case {'chmod'} + if nargin>1, + chmod(varargin{2}); + end; + + case {'help'} + if nargin>=2, + varargout{1} = showdoc(varargin{2:nargin}); + else + varargout{1} = showdoc; + end; + + case {'harvest'} + [varargout{1:nargout}] = harvest(varargin{2:nargin}); + + otherwise + error(['"' varargin{1} '" - unknown option']); + end; +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function defaults_edit(varargin) +setup_ui('defaults'); +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function interactive(varargin) +ud = get(varargin{1},'UserData'); +if iscell(ud) + setup_ui(ud{:}); +else + setup_ui; +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function setup_ui(varargin) +% Configure the user interface + +fg = clearwin; +fs = getdef('ui.fs'); +if numel(fs)~=1 || ~isnumeric(fs{1}) || numel(fs{1})~=1, + fs = 12; +else + fs = fs{1}; +end; + +col1 = getdef('ui.colour1'); +if numel(col1)~=1 || ~isnumeric(col1{1}) || numel(col1{1})~=3, + col1 = [0.8 0.8 1]; +else + col1 = col1{1}; +end; + +col2 = getdef('ui.colour2'); +if numel(col2)~=1 || ~isnumeric(col2{1}) || numel(col2{1})~=3, + col2 = [1 1 0.8]; +else + col2 = col2{1}; +end; + +col3 = getdef('ui.colour3'); +if numel(col3)~=1 || ~isnumeric(col3{1}) || numel(col3{1})~=3, + col3 = [0 0 0]; +else + col3 = col3{1}; +end; + +t=uicontrol(fg,... + 'Style','listbox',... + 'Units','normalized',... + 'Position',[0.02 0.31 0.48 0.67],... + 'Callback',@click_batch_box,... + 'Tag','batch_box',... + 'ForegroundColor', col3,... + 'BackgroundColor',col1,... + 'FontSize',fs,... + 'Value',1); +c0 = cntxtmnu(t); +c1 = uimenu('Label','Exp/Con All', 'Parent',c0); +uimenu('Label','Expand All', 'Parent',c1,'Callback',@expandall); +uimenu('Label','Contract All', 'Parent',c1,'Callback',@contractall); +uimenu('Label','Expand All Undefined Inputs', 'Parent',c1,'Callback',@expandallopen); + +t=uicontrol(fg,... + 'Style','listbox',... + 'ListBoxTop',1,... + 'Units','normalized',... + 'Position',[0.02 0.02 0.96 0.28],... + 'Tag','help_box',... + 'FontName','fixedwidth',... + 'FontSize',fs,... + 'ForegroundColor', col3,... + 'BackgroundColor',col2); +set(t,'Value',[], 'Enable', 'inactive', 'Max',2, 'Min',0); +workaround(t); +cntxtmnu(t); + +t=uicontrol(fg,... + 'Style','listbox',... + 'Units','normalized',... + 'Position',[0.51 0.74 0.47 0.24],... + 'ForegroundColor', col3,... + 'BackgroundColor',col1,... + 'FontSize',fs,... + 'Tag','opts_box'); +cntxtmnu(t); + +t=uicontrol(fg,... + 'Style','listbox',... + 'Units','normalized',... + 'Position',[0.51 0.49 0.47 0.24],... + 'Tag','val_box',... + 'ForegroundColor', col3,... + 'BackgroundColor',col2,... + 'Enable', 'inactive',... + 'Value',[],... + 'FontSize',fs,... + 'Max',2, 'Min',0); +set(t,'Value',[], 'Enable', 'on', 'Max',2, 'Min',0,'ListBoxTop',1); +cntxtmnu(t); + +t=uicontrol(fg,... + 'Style','listbox',... + 'Units','normalized',... + 'Position',[0.51 0.38 0.47 0.10],... + 'Tag','msg_box',... + 'ForegroundColor', col3,... + 'BackgroundColor',col2,... + 'Enable', 'inactive',... + 'Value',[],... + 'FontSize',fs,... + 'Max',2, 'Min',0); +cntxtmnu(t); + +if ~(nargin==1 && ischar(varargin{1}) && strcmp(varargin{1},'defaults')), + t=uicontrol(fg,... + 'style','pushbutton',... + 'units','normalized',... + 'Position',[0.51 0.31 0.15 0.06],... + 'ForegroundColor', col3,... + 'BackgroundColor',col1,... + 'String','Save',... + 'Callback',@save_job,... + 'FontSize',fs,... + 'Tag','save'); + cntxtmnu(t); + + t=uicontrol(fg,... + 'style','pushbutton',... + 'units','normalized',... + 'Position',[0.67 0.31 0.15 0.06],... + 'ForegroundColor', col3,... + 'BackgroundColor',col1,... + 'String','Load',... + 'Callback',@load_job,... + 'FontSize',fs,... + 'Tag','load'); + cntxtmnu(t); + + t=uicontrol(fg,... + 'style','pushbutton',... + 'units','normalized',... + 'Position',[0.83 0.31 0.15 0.06],... + 'ForegroundColor', col3,... + 'BackgroundColor',col1,... + 'String','Run',... + 'Callback',@run_struct,... + 'Tag','run',... + 'FontSize',fs,... + 'Enable', 'off'); + cntxtmnu(t); + if nargin>0 + initialise(varargin{:}); + else + initialise; + end; +else + t=uicontrol(fg,... + 'style','pushbutton',... + 'units','normalized',... + 'Position',[0.51 0.31 0.15 0.06],... + 'ForegroundColor', col3,... + 'BackgroundColor',col1,... + 'String','OK',... + 'FontSize',fs,... + 'Callback',@ok_defaults); + cntxtmnu(t); + + t=uicontrol(fg,... + 'style','pushbutton',... + 'units','normalized',... + 'Position',[0.67 0.31 0.15 0.06],... + 'ForegroundColor', col3,... + 'BackgroundColor',col1,... + 'String','Reset',... + 'FontSize',fs,... + 'Callback',@reset_defaults); + cntxtmnu(t); + + t=uicontrol(fg,... + 'style','pushbutton',... + 'units','normalized',... + 'Position',[0.83 0.31 0.15 0.06],... + 'ForegroundColor', col3,... + 'BackgroundColor',col1,... + 'String','Cancel',... + 'FontSize',fs,... + 'Callback',@clearwin); + cntxtmnu(t); + initialise('defaults'); +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function ok_defaults(varargin) +harvest_def(get(batch_box,'UserData')); +clearwin; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function reset_defaults(varargin) +global defaults + +modality = []; +if isfield(defaults,'modality'), + modality = defaults.modality; +end; +spm_defaults; +if ~isfield(defaults,'modality') && ~isempty(modality), + defaults.modality = modality; +end; +pulldown; +clearwin; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function fg = clearwin(varargin) + +fg = spm_figure('findwin','Graphics'); +if isempty(fg), fg = spm_figure('Create','Graphics'); end; +delete(findobj(fg,'Parent',fg)); +delete(batch_box); +delete(opts_box); +spm('Pointer'); +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function expandall(varargin) +c = expcon(get(gco,'UserData'),1); +str = get_strings(c); +val = min(get(gco,'Value'),length(str)); +set(gco,'String',str,'Value',val,'UserData',c); +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function contractall(varargin) +c = expcon(get(gco,'UserData'),0); +str = get_strings(c); +val = min(get(gco,'Value'),length(str)); +set(gco,'String',str,'Value',val,'UserData',c); +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function expandallopen(varargin) +c = expopen(get(gco,'UserData')); +str = get_strings(c); +val = min(get(gco,'Value'),length(str)); +set(gco,'String',str,'Value',val,'UserData',c); +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function [c,sts] = expopen(c) +sts = 0; +if isfield(c,'expanded'), + sts=~all_set(c); +end; +if isfield(c,'val'), + for i=1:length(c.val), + [c.val{i} sts1] = expopen(c.val{i}); + sts = sts||sts1; + end; +end; +if isfield(c,'expanded') + c.expanded = sts; +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function c = expcon(c,val) +if isfield(c,'expanded'), c.expanded = val; end; +if isfield(c,'val'), + for i=1:length(c.val), + c.val{i} = expcon(c.val{i},val); + end; +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function initialise(job,node) +% load the config file, possibly adding a job +% to it, and generally tidy it up. The batch box +% is updated to contain the current structure. +files_select_list('init'); +if nargin<1, job = ''; end; +if nargin<2, node = 'jobs'; end; +c = initialise_struct(job); +set(batch_box,'UserData',start_node(c,node)); +update_ui; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function run_job(job) +% Run a job. This function is not called via the UI. +c = initialise_struct(job); +if all_set(c), + run_struct1(c); +else + error('This job is not ready yet'); +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function click_batch_box(varargin) +% Called when the batch box is clicked + +remove_string_box; +if strcmp(get(get(varargin{1},'Parent'),'SelectionType'),'open') + run_in_current_node(@expand_contract,false); + str = get_strings(get(batch_box,'UserData')); + val = min(get(batch_box,'Value'),length(str)); + set(batch_box,'String',str,'Value',val); +else + run_in_current_node(@click_batch_box_fun,false); +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function update_ui +[str,sts] = get_strings(get(batch_box,'UserData')); +val = min(get(batch_box,'Value'),length(str)); +set(batch_box,'String',str,'Value',val); +run_in_current_node(@click_batch_box_fun,false); + +run_but = findobj(0,'Tag','run'); +if sts + set(run_but,'Enable', 'on'); +else + set(run_but,'Enable', 'off'); +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function [str,sts] = get_strings(c) +[unused,str,sts] = start_node(c,@get_strings1,0); +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function [c,str,sts] = get_strings1(c,l) +% Return the cell vector of strings displayed in the batch +% box, and also whether the job is runable. This is a recursive +% function that a branch of the data structure is passed to. + +if isfield(c,'hidden'), + sts = 1; + str = ''; + return; +end; + +sts = 1; +mrk = ' '; +nam = ''; +if isfield(c,'datacheck') && ~isempty(c.datacheck), + mrk = ' <-@'; + sts = 0; +end; +if isfield(c,'name'), nam = c.name; end; + +if isfield(c,'expanded') % && isfield(c,'val') && ~isempty(c.val) + + if strcmp(c.type,'repeat') && isfield(c,'num') + sts = (length(c.val) >= c.num(1)) &&... + (length(c.val) <= c.num(2)); + if ~sts + mrk = ' <-X'; + end; + end; + + if c.expanded + str = {[repmat('. ',1,l) '-' nam]}; + for i=1:length(c.val) + [c.val{i},str1,sts1] = get_strings1(c.val{i},l+1); + sts = sts && sts1; + if ~isempty(str1), + str = {str{:},str1{:}}; + end; + end; + else + str = {[repmat('. ',1,l) '+' nam]}; + for i=1:length(c.val) + [c.val{i},unused,sts1] = get_strings1(c.val{i},l+1); + sts = sts && sts1; + end; + if ~sts, + mrk = ' <-X'; + end; + end; + str{1} = [str{1} mrk]; +else + switch c.type + case 'files' + % If files are selected via "Input to/Output from" shortcuts, + % there is no guarantee that an allowed number of files is + % specified. This is checked here. + cn = c.num; + if (numel(cn) == 1) + if isfinite(cn) + cn = [cn cn]; + else + cn = [0 cn]; + + end; + end; + if isempty(c.val) || (numel(c.val{1}) < cn(1)) || ... + (numel(c.val{1}) > cn(2)) + mrk = ' <-X'; + sts = 0; + end; + case {'menu','entry','const','choice'} + if isempty(c.val) + mrk = ' <-X'; + sts = 0; + end; + case 'repeat' % don't know, whether this ever gets executed + if isfield(c,'num') + sts = (length(c.val) >= c.num(1)) &&... + (length(c.val) <= c.num(2)); + if ~sts + mrk = ' <-X'; + end; + end; + end; + str = {[repmat('. ',1,l) ' ' nam mrk]}; +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function ok = all_set(c) +% Return whether all the necessary fields have been filled in. +% This is a recursive function that a branch of the data structure +% is passed to. + +if isfield(c,'hidden'), + ok = true; + return; +end; + +ok = true; + +switch c.type +case {'files','menu','entry','const','choice'} + if isempty(c.val) + ok = false; + end; + +case {'branch','repeat'} + if strcmp(c.type,'repeat') && isfield(c,'num') + ok = ok && (length(c.val) >= c.num(1)) && ... + (length(c.val) <= c.num(2)); + end; + if ok, + for i=1:length(c.val), + ok1 = all_set(c.val{i}); + ok = ok && ok1; + if ~ok, break; end; + end; + end; +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function click_options_box(varargin) +% The function called when the options box is clicked + +dat = get(opts_box,'UserData'); +if ~isempty(dat) + fun = dat{get(opts_box,'Value')}; + run_in_current_node(fun.fun,true,fun.args{:}); + if fun.redraw, update_ui; end; +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function varargout = run_in_current_node(varargin) +% Get the current node, and run a job in it +varargout = {}; +c = get(batch_box,'UserData'); +n = get(batch_box,'Value'); +show_msg(''); +va = {c,@run_in_current_node1,n,varargin{:}}; +[c,unused,varargout{:}] = start_node(va{:}); +set(batch_box,'UserData',c); +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function [c,varargout] = start_node(c,fun,varargin) +persistent node; +if isempty(node) + node = {'jobs'}; +end; +varargout = {}; +if nargin==2 + tmp = [0 find([fun '.']=='.')]; + node = {}; + for i=1:length(tmp)-1 + node = {node{:},fun((tmp(i)+1):(tmp(i+1)-1))}; + end; + c = make_nodes(c,node); + return; +end; +va = {c,node,fun,varargin{:}}; +[c,unused,varargout{1:nargout-1}] = start_node1(va{:}); +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function c = make_nodes(c,node) +if isfield(c,'tag') && ~strcmp(gettag(c),node{1}) + return; +end; +if isfield(c,'tag') + node = {node{2:end}}; + if isempty(node), return; end; +end; +if isfield(c,'values') && (~isfield(c,'val') ||... + isempty(c.val) || ~iscell(c.val) ||... + ~strcmp(gettag(c.val{1}),node{1})) + for i=1:length(c.values) + if strcmp(gettag(c.values{i}),node{1}) + c.val = {c.values{i}}; + c.val{1} = make_nodes(c.val{1},node); + end; + end; +end; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function [c,sts,varargout] = start_node1(c,node,fun,varargin) +varargout = {}; +if nargin<4, varargin = {}; end; +if isfield(c,'tag') && ~strcmp(gettag(c),node{1}) + varargout = cell(1,nargout-2); + sts = 0; + return; +end; +if isfield(c,'tag'), + node = {node{2:end}}; +end; +if isempty(node) + [c,varargout{1:nargout-2}] = feval(fun,c,varargin{:}); + sts = 1; + return; +end; +if isfield(c,'val'), + for i=1:length(c.val) + [c.val{i},sts,varargout{1:nargout-2}] = start_node1(c.val{i},node,fun,varargin{:}); + if sts, return; end; + end; +end; +keyboard +error('No such node'); % Should never execute +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function [c,n,varargout] = run_in_current_node1(c,n,fun,modify,varargin) +% Satellite for run_in_current_node + +varargout = {}; +if nargin<5, varargin = {}; end; +if isfield(c,'hidden'), return; end; + +n = n-1; +if n<0, return; end; +if n==0, + if ~isfield(c,'datacheck'), + show_msg(''); + else + show_msg(c.datacheck); + end; + [c,varargout{:}] = feval(fun,c,varargin{:}); +end; + +if isfield(c,'expanded') && ~isempty(c.val) + if c.expanded, + val = c.val; + c.val = {}; + for i=1:length(val) + [tmp,n,varargout{:}] = run_in_current_node1(val{i},n,fun,modify,varargin{:}); + if ~isempty(tmp) + if iscell(tmp) + c.val = {c.val{:}, tmp{:}}; + else + c.val = {c.val{:}, tmp}; + end; + end; + end; + if modify && isfield(c,'check'), + if all_set(c), + [unused,val] = harvest(c); + c.datacheck = feval(c.check,val); + end; + end; + if isfield(c,'datacheck') && ~isempty(c.datacheck), + show_msg(c.datacheck); + end; + end; +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function c = click_batch_box_fun(c,unused) +% Mostly set up the options box, but also update the help and value boxes + +if ~isempty(opts_box) + dat = {}; + str = {}; + + switch c.type + case {'const'} + % do nothing + + case {'files'} + spm('pointer','watch'); + str = {str{:}, 'Specify Files'}; + dat = {dat{:}, struct('fun',@file_select,'args',{{}},'redraw',1)}; + addvfiles(c.id,[],c); + addinfiles(c); + [filestr filedat] = files_select_list('listall'); + str = {str{:}, filestr{:}}; + dat = {dat{:}, filedat{:}}; + spm('pointer','arrow'); + + case {'menu'} + str = {str{:}, 'Specify Menu Item'}; + dat = {dat{:}, struct('fun',@menu_entry,'args',{{}},'redraw',0)}; + + case {'entry'} + str = {str{:}, 'Specify Text'}; + dat = {dat{:}, struct('fun',@text_entry,'args',{{}},'redraw',1)}; + + case {'branch','choice','repeat'} + if strcmp(c.type,'repeat') + for i=1:length(c.values) + if ~isfield(c.values{i},'hidden'), + str = {str{:},['New "' c.values{i}.name '"']}; + dat = {dat{:}, struct('fun',@series,'args',{{c.values{i}}},'redraw',1)}; + end; + end; + + elseif strcmp(c.type,'choice') + for i=1:length(c.values) + if ~isfield(c.values{i},'hidden'), + str = {str{:},['Choose "' c.values{i}.name '"']}; + dat = {dat{:}, struct('fun',@choose,'args',{{c.values{i}}},'redraw',1)}; + end; + end; + end; + end; + + if isfield(c,'removable') + str = {str{:},['Remove Item "' c.name '"'],['Replicate Item "' c.name '"']}; + dat = {dat{:}, struct('fun',@remove,'args',{{}},'redraw',1),... + struct('fun',@replicate,'args',{{}},'redraw',1)}; + end; + + if ~same(get(opts_box,'String')',str) + val = 1; + else + val = get(opts_box,'Value'); + end; + val = max(min(length(str),val),min(1,length(str))); + set(opts_box,'String',str,'UserData',dat,'Callback',@click_options_box,'Value',val); +end; + +show_value(c); + +% Update help +txt = ''; +if isfield(c,'help'), txt = c.help; end; +help_box = findobj(0,'tag','help_box'); +if ~isempty(help_box) + set(help_box,'String',' '); + workaround(help_box); + ext = get(help_box,'Extent'); + pos = get(help_box,'position'); + pw = floor(pos(3)/ext(3)*21-4); + set(help_box,'String',spm_justify(help_box,txt)); + workaround(help_box); +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function c = expand_contract(c,unused) +% Expand/contract a node (for visualisation) + +if isfield(c,'expanded') && ~isempty(c.val) + if c.expanded + c.expanded = false; + else + c.expanded = true; + end; +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function c = menu_entry(c,unused) +% Setup opts box for menu entry + +if isfield(c,'labels') && isfield(c,'values') + str = {}; + dat = {}; + if ~isempty(c.val) + val = c.val{1}; + else + val = ''; + end; + dv = 1; + for i=1:length(c.values) + if ~(ischar(val) && strcmp(val,'')) && same(c.values{i},val) + str{i} = ['* ' c.labels{i}]; + dv = i; + else + str{i} = [' ' c.labels{i}]; + end; + dat{i} = struct('fun',@menuval,'args',{{c.values{i}}},'redraw',1); + end; + set(opts_box,'String',str,'UserData',dat,'Callback',@click_options_box,'Value',dv); +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function [c] = file_select(c) +% Select files +try + set(batch_box,'Enable', 'inactive'); + set(opts_box, 'Enable', 'inactive'); + + if ~isempty(c.val), + sel = c.val{1}; + else + sel = ''; + end; + if isfield(c,'dir'), + dr = c.dir; + else + dr = pwd; + end; + if isfield(c,'ufilter') + uf = c.ufilter; + else + uf = '.*'; + end; + [s,ok] = spm_select(c.num,c.filter,c.name,sel,dr,uf); + if ok, c.val{1} = cellstr(s); end; + files_select_list('addinfiles', sprintf('Input to "%s"', c.name), ... + c.val{1}, c.id); +catch +end; +spm_select('clearvfiles'); +set(batch_box,'Enable', 'on'); +set(opts_box, 'Enable', 'on'); +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function c = text_entry(c) +% Create a string box and prompt, while hiding the opts box + +opts_box = findobj(gcf,'tag','opts_box'); +pos = get(opts_box,'Position'); +set(opts_box,'Visible','off'); +fs = get(opts_box,'FontSize'); +fa = get(opts_box,'FontAngle'); +fw = get(opts_box,'FontWeight'); +clf = get(opts_box,'ForegroundColor'); +clb = get(opts_box,'BackgroundColor'); + +n = []; +m = []; +if isfield(c,'num'), n = c.num; end; +if isfield(c,'extras'), m = c.extras; end; + +newstring = uicontrol(gcf,... + 'Style','edit',... + 'Units','normalized',... + 'Position',[pos(1:3) pos(4)/2],... + 'Tag','string_box',... + 'HorizontalAlignment','left',... + 'FontSize',fs,... + 'FontAngle',fa,... + 'FontWeight',fw,... + 'ForegroundColor',clf,... + 'BackgroundColor',clb,... + 'Callback',@accept_string); +cntxtmnu(newstring); + +strM=''; +switch lower(c.strtype) +case 's', TTstr='enter string'; +case 'e', TTstr='enter expression - evaluated'; +case 'n', TTstr='enter expression - natural number(s)'; + if ~isempty(m), strM=sprintf(' (in [1,%d])',m); TTstr=[TTstr,strM]; end +case 'w', TTstr='enter expression - whole number(s)'; + if ~isempty(m), strM=sprintf(' (in [0,%d])',m); TTstr=[TTstr,strM]; end +case 'i', TTstr='enter expression - integer(s)'; +case 'r', TTstr='enter expression - real number(s)'; + if ~isempty(m), TTstr=[TTstr,sprintf(' in [%g,%g]',min(m),max(m))]; end +case 'c', TTstr='enter indicator vector e.g. 0101... or abab...'; + if ~isempty(m) && isfinite(m), strM=sprintf(' (%d)',m); end +otherwise, TTstr='enter expression'; +end + +if isempty(n), n=NaN; end +n=n(:); if length(n)==1, n=[n,1]; end; dn=length(n); +if any(isnan(n)) || (prod(n)==1 && dn<=2) || (dn==2 && min(n)==1 && isinf(max(n))) + str = ''; + lstr = ''; +elseif dn==2 && min(n)==1 + str = sprintf('[%d]',max(n)); + lstr = [str,'-vector: ']; +elseif dn==2 && sum(isinf(n))==1 + str = sprintf('[%d]',min(n)); + lstr = [str,'-vector(s): ']; +else + str=''; + for i = 1:dn, + if isfinite(n(i)), + str = sprintf('%s,%d',str,n(i)); + else + str = sprintf('%s,*',str); + end + end + str = ['[',str(2:end),']']; + lstr = [str,'-matrix: ']; +end +strN = sprintf('%s',lstr); +col = get(gcf,'Color'); +uicontrol(gcf,'Style','text',... + 'Units','normalized',... + 'BackgroundColor',col,... + 'String',[strN,strM,TTstr],... + 'Tag','string_prompt',... + 'HorizontalAlignment','Left',... + 'FontSize',fs,... + 'FontAngle',fa,... + 'FontWeight',fw,... + 'Position',[pos(1:3)+[0 pos(4)/2 0] pos(4)/2]); + +if isfield(c,'val') && ~isempty(c.val) + val = c.val{1}; + if ischar(val) + tmp = val'; + tmp = tmp(:)'; + set(newstring,'String',tmp); + elseif isnumeric(val) + if ndims(val)>2, + set(newstring,'String',['reshape([', num2str(val(:)'), '],[' num2str(size(val)) '])']); + else + if size(val,1)==1, + set(newstring,'String',num2str(val(:)')); + elseif size(val,2)==1, + set(newstring,'String',['[' num2str(val(:)') ']''']); + else + str = ''; + for i=1:size(val,1), + str = [str ' ' num2str(val(i,:)) ';']; + end; + set(newstring,'String',str); + end; + end; + end; +end; + +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function accept_string(varargin) +% Accept a typed in string? +run_in_current_node(@stringval,true,get(varargin{1},'String')); +update_ui; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function remove_string_box +% Delete the string box and prompt, making the opts box +% visible again +delete(findobj(0,'Tag','string_box')); +delete(findobj(0,'Tag','string_prompt')); +set(opts_box,'Visible','on'); +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function show_value(c) +% Update the value box + +valtxt = {''}; +% col = [1 0 0]; + +switch c.type +case {'menu'} + if isfield(c,'val') && ~isempty(c.val) + if isfield(c,'labels') && isfield(c,'values') + val = c.val{1}; + for i=1:length(c.values) + if same(c.values{i},val) + valtxt = c.labels{i}; + % col = [0 0 0]; + break; + end; + end; + end; + end; + +case {'const','entry'} + if isfield(c,'val') && ~isempty(c.val) + val = c.val{1}; + if isempty(val) + valtxt = ''; + else + if isnumeric(val) + sz = size(val); + if length(sz)>2 + valtxt = sprintf('%dx',sz); + valtxt = [valtxt(1:(end-1)) ' Numeric Array']; + else + valtxt = cell(size(val,1),1); + for i=1:size(val,1) + valtxt{i} = sprintf('%14.8g ',val(i,:)); + end; + end; + elseif(ischar(val)) + valtxt = val; + else + valtxt = 'Can not display'; + end; + end; + % col = [0 0 0]; + end; + +case {'files'} + if isfield(c,'val') && ~isempty(c.val) + if isempty(c.val{1}) || isempty(c.val{1}{1}) + valtxt = ''; + else + valtxt = c.val{1}; + end; + % col = [0 0 0]; + end; + +case {'choice'} + if isfield(c,'val') && length(c.val)==1 + valtxt = {'A choice, where',['"' c.val{1}.name '"'], 'is selected.'}; + % col = [0.5 0.5 0.5]; + else + valtxt = {'A choice, with', 'nothing selected.'}; + % col = [0.5 0.5 0.5]; + end; + +case {'repeat'} + ln = length(c.val); s = 's'; if ln==1, s = ''; end; + valtxt = ['A series containing ' num2str(length(c.val)) ' item' s '.']; + % col = [0.5 0.5 0.5]; + +case {'branch'} + ln = length(c.val); s = 's'; if ln==1, s = ''; end; + valtxt = {['A branch holding ' num2str(length(c.val)) ' item' s '.']}; + if isfield(c,'prog'), + valtxt = {valtxt{:}, '', ' User specified values',... + ' from this branch will be',... + ' collected and passed to',... + ' an executable function',... + ' when the job is run.'}; + end; + % col = [0.5 0.5 0.5]; + +otherwise + valtxt = 'What on earth is this???'; +end; +val_box = findobj(0,'tag','val_box'); +if ~isempty(val_box) + % set(val_box,'String',valtxt,'ForegroundColor',col); + set(val_box,'String',valtxt); + workaround(val_box); +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function c = remove(c,varargin) +% Remove node c +if strcmp(questdlg(['Remove "' c.name '"?'],'Confirm','Yes','No','Yes'),'Yes'), + c = {}; +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function c = replicate(c,varargin) +% Replicate node c +c = {c,uniq_id(c)}; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function c = choose(c,val) +% Specify the value of c to be val +c.val{1} = uniq_id(val); +c.expanded = true; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function c = menuval(c,val) +% Specify the value of c to be val +c.val{1} = val; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function c = stringval(c,val) +% Accept (or not) a typed in string + +msg = 'SUCCESS: Values accepted'; + +switch c.strtype +case {'s'} + c.val{1} = val; + show_value(c); + remove_string_box; + +case {'s+'} + msg = 'FAILURE: Cant do s+ yet'; + beep; + remove_string_box; + +otherwise + n = Inf; + if isfield(c,'num') + n = c.num; + end; + if isfield(c,'extras') + [val,msg] = spm_eeval(val,c.strtype,n,c.extras); + else + [val,msg] = spm_eeval(val,c.strtype,n); + end; + + if ischar(val) + beep; + msg = ['FAILURE: ' msg]; + else + c.val{1} = val; + show_value(c); + remove_string_box; + msg = ['SUCCESS: ' msg]; + end; +end; +show_msg(msg); +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function c = series(c,new) +% Add a new repeat +c.expanded = true; +new.removable = true; +if isfield(c,'val') + c.val = {c.val{:},uniq_id(new)}; +else + c.val = {uniq_id(new)}; +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function setdef(strin,val) +global defaults + +if isempty(defaults), spm_defaults; end; +if ischar(val) && strcmp(val,''), return; end; +o = find(strin=='.'); +df = cell(1,length(o)+1); +prev = 1; +for i=1:length(o), + df{i} = strin(prev:(o(i)-1)); + prev = o(i)+1; +end; +df{end} = strin(prev:end); +defaults = setdef_sub(defaults,df,val); +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function d = setdef_sub(d,df,val) +if isempty(df), + d = val; +else + if ~isfield(d,df{1}),d.(df{1}) = []; end; + d.(df{1}) = setdef_sub(d.(df{1}),df(2:end),val); +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function val = getdef(strin) +% Get value of one of the SPM defaults + +global defaults + +val = getdef_sub(defaults,strin); +if ischar(val) && strcmp(val,'') + val = {}; +else + val = {val}; +end; +%fprintf('%s\n',strin); +%disp(val) +%disp('----'); +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function c = getdef_sub(defs,field) +% Satellite function for getdef + +o = find(field=='.'); +if isempty(o) + if isfield(defs,field) + c = defs.(field); + else + c = ''; + end; + return; +end; +if isfield(defs,field(1:(o-1))) + c = getdef_sub(defs.(field(1:(o-1))),field((o+1):end)); +else + c = ''; +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function t = same(a,b) +% Are two data structures identical? + +% Innocent until proven guilty +t = true; + +% Check the dimensions +if isempty(a) && isempty(b) + t = true; return; +end; +sa = size(a); +sb = size(b); +if length(sa) ~= length(sb) + t = false; return; +end; +if ~all(sa==sb) + t = false; return; +end; + +% Check the classes +ca = class(a); +if ~strcmp(ca,class(b)), t = false; return; end; + +% Recurse through data structure +switch ca +case {'double','single','sparse','char','int8','uint8',... + 'int16','uint16','int32','uint32','logical'} + msk = ((a==b) | (isnan(a)&isnan(b))); + if ~all(msk(:)), t = false; return; end; + +case {'struct'} + fa = fieldnames(a); + fb = fieldnames(b); + if length(fa) ~= length(fb), t = false; return; end; + for i=1:length(fa) + if ~strcmp(fa{i},fb{i}), t = false; return; end; + for j=1:length(a) + if ~same(a(j).(fa{i}),b(j).(fb{i})) + t = false; return; + end; + end; + end; + +case {'cell'} + for j=1:length(a(:)) + if ~same(a{j},b{j}), t = false; return; end; + end; + +case {'function_handle'} + if ~strcmp(func2str(a),func2str(b)) + t = false; return; + end; + +otherwise + warning(['Unknown class "' ca '"']); + t = false; +end; + +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function t = batch_box +t = findobj(0,'tag','batch_box'); +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function t = opts_box +t = findobj(0,'tag','opts_box'); +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function save_job(varargin) +% Save a batch job +c = get(batch_box,'UserData'); spm('Pointer','Watch'); +[unused,jobs] = harvest(c); +spm('Pointer'); +%eval([tag '=val;']); +cll = {'*.mat','Matlab .mat file';'*.m','Matlab script file';'*.xml','XML file'}; +[filename, pathname, FilterIndex] = uiputfile(cll,'Save job as'); +if ischar(filename) + [unused,unused,ext] = fileparts(filename); + if isempty(ext) + ext = cll{FilterIndex}(2:end); + filename = [filename ext]; + end + switch ext + case '.xml', + spm('Pointer','Watch'); + savexml(fullfile(pathname,filename),'jobs'); + spm('Pointer'); + case '.mat', + if spm_matlab_version_chk('7') >= 0, + save(fullfile(pathname,filename),'-V6','jobs'); + else + save(fullfile(pathname,filename),'jobs'); + end; + case '.m', + treelist('jobs',struct('exps',1, 'dval',2, 'fname', ... + fullfile(pathname,filename))); + otherwise + questdlg(['Unknown extension (' ext ')'],'Nothing saved','OK','OK'); + end; +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function load_job(varargin) +% Load a set of batch jobs and possibly merge it + +[filenames sts] = spm_select([1 Inf], 'batch', 'Load job file(s)'); +if sts + filenames = cellstr(filenames); + newjobs = {}; + for cf = 1:numel(filenames) + [p,nam,ext] = fileparts(filenames{cf}); + switch ext + case '.xml', + spm('Pointer','Watch'); + try + loadxml(filenames{cf},'jobs'); + catch + questdlg('LoadXML failed',filenames{cf},'OK','OK'); + return; + end; + spm('Pointer'); + case '.mat' + try + load(filenames{cf},'jobs'); + catch + questdlg('Load failed',filenames{cf},'OK','OK'); + end; + case '.m' + opwd = pwd; + try + cd(p); + eval(nam); + catch + questdlg('Load failed',filenames{cf},'OK','OK'); + end; + cd(opwd); + otherwise + questdlg(['Job ' nam ': Unknown extension (' ext ')'],... + 'This job not loaded','OK','OK'); + end; + if exist('jobs','var') + newjobs = {newjobs{:} jobs{:}}; + clear jobs; + else + questdlg(['No jobs (' nam ext ')'],'No jobs','OK','OK'); + end; + end; + if ~isempty(newjobs) + spm('Pointer','Watch'); + initialise(newjobs); + spm('Pointer'); + end; +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function run_struct(varargin) +% Get data structure from handle, and run it +% If an error occured, then return to user interface +c = get(batch_box,'UserData'); +[unused,job] = harvest(c); +try +run_struct1(c); +catch + disp('Error running job:'); + disp(lasterr); + setup_ui(job); +end; +disp('--------------------------'); +disp('Done.'); +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function run_struct1(c) +% Run a batch job from a data structure + +if isfield(c,'prog') + prog = c.prog; + [unused,val] = harvest(c); + disp('--------------------------'); + disp(['Running "' c.name '"']); + [Finter,unused,CmdLine] = spm('FnUIsetup',c.name); + spm('Pointer','Watch'); + spm('FigName',[c.name ': running'],Finter,CmdLine); + + if 0 + try + feval(prog,val); + spm('FigName',[c.name ': done'],Finter,CmdLine); + catch + disp(['An error occurred when running "' c.name '"']); + disp( '--------------------------------'); + disp(lasterr); + disp( '--------------------------------'); + spm('FigName',[c.name ': failed'],Finter,CmdLine); + errordlg({['An error occurred when running "' c.name '"'],lasterr},'SPM Jobs'); + end; + spm('Pointer'); + else + feval(prog,val); + spm('FigName',[c.name ': done'],Finter,CmdLine); + %prog_infos=functions(prog) + %prog_log=[prog_infos.parentage{end} '_log']; + %if exist(prog_log) > 1 + try + spm_log2html(prog,val); + disp(sprintf('Generating log on %s',c.name)); + catch + disp(sprintf('Error while generating log on %s',c.name)); + end + %else + % prog_msg=sprintf('No function found for generating log on %s. Try making a: %s',c.name, prog_log); + %end + %disp(prog_msg); + spm('Pointer'); + end; + +else + if isfield(c,'val') + for i=1:length(c.val) + run_struct1(c.val{i}); + end; + end; +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function harvest_def(c) +switch c.type, +case{'const','menu','files','entry'}, + if isfield(c,'def') && numel(c.val)==1, + setdef(c.def,c.val{1}); + end; + +otherwise + for i=1:length(c.val), + harvest_def(c.val{i}); + end; +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function [tag,val,typ] = harvest(c) +% Take a data structure, and extract what is needed to save it +% as a batch job + +tag = 'unknown'; +val = []; +typ = c.type; +switch(typ) +case {'const','menu','files','entry'} + tag = gettag(c); + if ~isempty(c.val) + val = c.val{1}; + else + val = ''; + end; + +case {'branch'} + tag = gettag(c); + if isfield(c,'val') + val = []; + for i=1:length(c.val) + [tag1,val1] = harvest(c.val{i}); + val.(tag1) = val1; + end; + end; + +case {'repeat'} + tag = gettag(c); + if length(c.values)==1 && strcmp(c.values{1}.type,'branch'), + cargs = {}; + for i=1:numel(c.values{1}.val), + cargs = {cargs{:},gettag(c.values{1}.val{i}),{}}; + end; + val = struct(cargs{:}); + + if isfield(c,'val') + for i=1:length(c.val), + [tag1,val1,typ1] = harvest(c.val{i}); + val(i) = val1; + end; + end; + else + val = {}; + if isfield(c,'val') + for i=1:length(c.val), + [tag1,val1,typ1] = harvest(c.val{i}); + if length(c.values)>1, + if iscell(val1) + val1 = struct(tag1,{val1}); + else + val1 = struct(tag1,val1); + end; + end; + val = {val{:}, val1}; + end; + end; + end; + +case {'choice'} + if isfield(c,'tag'), tag = gettag(c); end; + [tag1,val1] = harvest(c.val{1}); + if iscell(val1) + val = struct(tag1,{val1}); + else + val = struct(tag1,val1); + end; +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function tag = gettag(c) +% Get a tag field - possibly from one of the kids + +if (strcmp(c.type,'repeat') || strcmp(c.type,'choice')) && numel(c.values)>0 + tag = gettag(c.values{1}); + for i=2:length(c.values) + if ~strcmp(tag,gettag(c.values{i})) + tag = c.tag; + return; + end; + end; +else + tag = c.tag; +end; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function c0 = cntxtmnu(ob) +c0 = uicontextmenu('Parent',get(ob,'Parent')); +set(ob,'uicontextmenu',c0); +c1 = uimenu('Label','Font', 'Parent',c0); + uimenu('Label','Plain', 'Parent',c1,'Callback','set(gco,''FontWeight'',''normal'',''FontAngle'',''normal'');'); + uimenu('Label','Bold', 'Parent',c1,'Callback','set(gco,''FontWeight'',''bold'', ''FontAngle'',''normal'');'); + uimenu('Label','Italic', 'Parent',c1,'Callback','set(gco,''FontWeight'',''normal'',''FontAngle'',''italic'');'); + uimenu('Label','Bold-Italic','Parent',c1,'Callback','set(gco,''FontWeight'',''bold'', ''FontAngle'',''italic'');'); +c1 = uimenu('Label','Fontsize','Parent',c0); +fs = [8 9 10 12 14 16 18]; % [20 24 28 32 36 44 48 54 60 66 72 80 88 96]; +for i=fs, + uimenu('Label',sprintf('%-3d',i),'Parent',c1,'Callback',@fszoom,'UserData',i); +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function workaround(t) +set(t,'Value',[], 'Enable', 'on', 'Max',2, 'Min',0,'ListBoxTop',1); +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function addvfiles(id,c,c0) +if (nargin<2)||isempty(c), + c = get(batch_box,'UserData'); +end; +if nargin<3 + c0 = []; +end; +files_select_list('clearvfiles'); +vf =addvfiles1(c,id,{},c0); +spm_select('clearvfiles'); +spm_select('addvfiles',vf); +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function [vf,sts]=addvfiles1(c,id,vf,c0) +sts = 0; +if ~isstruct(c) || ~isfield(c,'type'), + return; +end; + +if isfield(c,'vfiles'), + if ~find_id(c,id), + [c,unused,ok] = get_strings1(c,0); + if ok, + [unused,job] = harvest(c); + vf1 = feval(c.vfiles,job); + if ~isempty(c0) + files = filter_files(c0, vf1); + else + files = vf1; + end; + if ~isempty(files) + files_select_list('addvfiles', sprintf('Output from "%s"', ... + c.name), ... + files, c.id); + end; + vf = {vf{:}, vf1{:}}; + end; + else + sts = 1; + end; + return; +end; + +switch c.type, +case {'repeat','choice','branch'}, + for i=1:length(c.val), + [vf,sts]=addvfiles1(c.val{i},id,vf,c0); + if sts, return; end; + end; +end; + +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function addinfiles(c0,c) +id = c0.id; +if nargin<2, + c = get(batch_box,'UserData'); +end; +files_select_list('clearinfiles'); +addinfiles1(c,id,'',c0); +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function sts=addinfiles1(c,id,progname,c0) +sts = 0; +if ~isstruct(c) || ~isfield(c,'type'), + return; +end; + +if find_id(c,id), + sts = 1; +end; + +switch c.type, +case 'files' + if ~isempty(c.val) && ~isempty(c.val{1}) + files = filter_files(c0,c.val{1}); + if ~isempty(files) + files_select_list('addinfiles', ... + sprintf('Input to "%s->%s"', ... + progname, c.name), ... + files, c.id); + end; + end; +case {'repeat','choice','branch'}, + if isfield(c,'prog') + progname = c.name; + oldin = files_select_list('getinnum'); + end; + for i=1:length(c.val), + sts=addinfiles1(c.val{i},id,progname,c0); + if sts, return; end; + end; + if isfield(c,'prog') + newin = files_select_list('getinnum'); + if (newin-oldin > 1) + files_select_list('allinfiles', ... + sprintf('All inputs to %s',progname),... + oldin+1, newin); + end; + end; +end; + +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function ffiles = filter_files(c,files) +if strcmp(c.filter, 'image')||strcmp(c.filter,'dir') + filter = ['ext' c.filter]; +else + filter = c.filter; +end; +if isfield(c,'ufilter') + uf = c.ufilter; + if uf(1) == '^' % This will not work with full pathnames + uf=uf(2:end); + end; +else + uf = '.*'; +end; +ffiles = spm_select('filter', files, ... + filter, uf); +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function varargout = files_select_list(c,varargin) +persistent vfstr; +persistent vfdat; +persistent vffiles; +persistent vfid; +persistent instr; +persistent indat; +persistent infiles; +persistent inid; +if isstruct(c) + switch lower(varargin{1}) + case 'getvf' + c.val{1} = vffiles{varargin{2}}; + case 'getin' + c.val{1} = infiles{varargin{2}}; + end; + varargout{1} = c; + return; +end; +if ~iscell(vfstr) && ~strcmp(lower(c),'init') + files_select_list('init'); +end; +switch lower(c) +case 'init' + vfstr = {}; + vfdat = {}; + vffiles = {}; + vfid = []; + instr = {}; + indat = {}; + infiles = {}; + inid = []; +case 'clearvfiles' + vfstr = {}; + vfdat = {}; + vffiles = {}; + vfid = []; +case 'clearinfiles' + instr = {}; + indat = {}; + infiles = {}; + inid = []; +case 'addvfiles' + nvfind = numel(vfstr)+1; + vfid(nvfind) = varargin{3}; + vfstr{nvfind} = varargin{1}; + vfdat{nvfind} = struct('fun',@files_select_list,'args',{{'getvf', nvfind}}, ... + 'redraw',1); + vffiles{nvfind} = varargin{2}(:); +case 'addinfiles' + ninind = numel(instr)+1; + inid(ninind) = varargin{3}; + instr{ninind} = varargin{1}; + indat{ninind} = struct('fun',@files_select_list,'args',{{'getin', ninind}}, ... + 'redraw',1); + infiles{ninind} = varargin{2}(:); +case 'allinfiles' + ninind = numel(instr)+1; + inid(ninind) = -1; + instr{ninind} = varargin{1}; + indat{ninind} = struct('fun',@files_select_list,'args',{{'getin', ninind}}, ... + 'redraw',1); + infiles{ninind} = cat(1,infiles{varargin{2}:varargin{3}}); +case 'getinnum' + varargout{1} = numel(instr); +case 'listall' + varargout{1} = {vfstr{:} instr{:}}; + varargout{2} = {vfdat{:} indat{:}}; +end +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function ok = find_id(c,id) +ok = 0; +if ~isstruct(c) || ~isfield(c,'type'), + return; +end; +if c.id==id, + ok = 1; + return; +end; +if isfield(c,'val'), + for i=1:length(c.val), + ok = find_id(c.val{i},id); + if ok, return; end; + end; +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function c = uniq_id(c) +if ~isstruct(c) || ~isfield(c,'type'), + return; +end; +c.id = rand(1); +if isfield(c,'val'), + for i=1:length(c.val), + c.val{i} = uniq_id(c.val{i}); + end; +end; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function show_msg(txt) +lb = findobj('tag','msg_box'); +if isempty(txt), + set(lb,'String',{}); +else + msg = get(lb,'String'); + if iscell(txt), + msg = {msg{:} txt{:}}; + else + msg = {msg{:} txt}; + end; + set(lb,'String',msg); +end; +drawnow; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +%function beep +%fprintf('%c',7); +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function pulldown +% Create a pulldown for individual jobs +c = initialise_struct; +fg = spm_figure('findwin','Graphics'); +if isempty(fg), return; end; +set(0,'ShowHiddenHandles','on'); +delete(findobj(fg,'tag','jobs')); +set(0,'ShowHiddenHandles','off'); +f0 = uimenu(fg,'Label','TASKS','HandleVisibility','off','tag','jobs'); +pulldown1(f0,c,c.tag); +uimenu(f0,'Label','Batch','CallBack',@interactive,'Separator','on'); +uimenu(f0,'Label','Defaults','CallBack',@defaults_edit,'Separator','off'); +f1 = uimenu(f0,'Label','Sequential'); +pulldown2(f1,c,c.tag); + +if 0, % Currently unused + f1 = uimenu(f0,'Label','Modality'); + modalities = {'FMRI','PET','EEG'}; + for i=1:length(modalities) + tmp = modalities{i}; + if strcmp(tmp,getdef('modality')), + tmp = ['*' tmp]; + else + tmp = [' ' tmp]; + end; + uimenu(f1,'Label',tmp,'CallBack',@chmod); + end; +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function pulldown1(f0,c0,tag0) +if ~isfield(c0,'values'), return; end; +for i=1:length(c0.values), + c1 = c0.values{i}; + if isstruct(c1) && ~isfield(c1,'hidden'), + tag1 = tag0; + if isfield(c1,'tag'), + tag1 = [tag1 '.' c1.tag]; + end; + if isfield(c1,'prog'), + uimenu(f0,'Label',c1.name,'CallBack',@interactive,'UserData',{'',tag1}); + else + f1 = uimenu(f0,'Label',c1.name); + pulldown1(f1,c1,tag1); + end; + end; +end; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function pulldown2(f0,c0,tag0) +if ~isfield(c0,'values'), return; end; +for i=1:length(c0.values), + c1 = c0.values{i}; + if isstruct(c1) && ~isfield(c1,'hidden'), + tag1 = tag0; + if isfield(c1,'tag'), + tag1 = [tag1 '.' c1.tag]; + end; + if isfield(c1,'prog'), + if findcheck(c1), + uimenu(f0,'Label',c1.name,'Enable','off'); + else + uimenu(f0,'Label',c1.name,'CallBack',@run_serial,'UserData',{'',tag1}); + end; + else + f1 = uimenu(f0,'Label',c1.name); + pulldown2(f1,c1,tag1); + end; + end; +end; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function hascheck = findcheck(c) +hascheck = false; +if ~isstruct(c) || ~isfield(c,'type'), return; end; +if isfield(c,'check'), + hascheck = true; + return; +end; +if isfield(c,'values'), + for i=1:numel(c.values), + hascheck = findcheck(c.values{i}); + if hascheck, return; end; + end; +end; +if isfield(c,'val'), + for i=1:numel(c.val), + hascheck = findcheck(c.val{i}); + if hascheck, return; end; + end; +end; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function chmod(mod,varargin) +global defaults +if isempty(defaults), spm_defaults; end; +if ischar(mod), + %if strcmpi(defaults.modality,mod), + % spm('ChMod',mod); + %end; + defaults.modality = mod; +else + tmp = get(mod,'Label'); + defaults.modality = tmp(2:end); +end; +pulldown; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function fszoom(varargin) +fs = sscanf(get(varargin{1},'Label'),'%d'); +set(gco,'FontSize',fs); +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function run_serial(varargin) +ud = get(varargin{1},'UserData'); +if iscell(ud) + serial(ud{:}); +else + serial; +end; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function serial(job,node) + +if nargin<2, node = 'jobs'; end; + +fg = spm_figure('FindWin','Interactive'); +if isempty(fg), fg = spm('CreateIntWin'); end; +delete(findobj(fg,'Parent',fg)); +t=uicontrol(fg,... + 'Style','listbox',... + 'Units','normalized',... + 'Position',[0.02 0.02 0.96 0.62],... + 'Tag','help_box2',... + 'FontName','fixedwidth',... + 'FontSize',12,... + 'BackgroundColor',[1 1 1]); +set(t,'Value',[], 'Enable', 'inactive', 'Max',2, 'Min',0); +workaround(t); +cntxtmnu(t); +spm('Pointer'); +drawnow; + +if nargin>0, + c = initialise_struct(job); +else + c = initialise_struct; +end; + +c = start_node(c,node); +c = start_node(c,@run_ui,{}); +spm_input('!DeleteInputObj'); +delete(findobj(fg,'Parent',fg)); +[unused,jobs] = harvest(c); +%savexml('job_last.xml','jobs'); +run_job(jobs); +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function c = run_ui(c,varargin) + +nnod=1; +while(1), + nod = nnod; + [ci,unused,hlp] = get_node(c,nod); + if isempty(ci), break; end; + + help_box = findobj(0,'tag','help_box2'); + if ~isempty(help_box), + set(help_box,'String',' '); + workaround(help_box); + ext = get(help_box,'Extent'); + pos = get(help_box,'position'); + pw = floor(pos(3)/ext(3)*21-4); + set(help_box,'String',spm_justify(pw,hlp)); + workaround(help_box); + + if isfield(c,'prog'), + try + set(help_box,'HandleVisibility','off'); + [Finter,unused,CmdLine] = spm('FnUIsetup',c.name); + spm('FigName',[c.name ': setup'],Finter,CmdLine); + catch + end; + set(help_box,'HandleVisibility','on'); + end; + end; + + pos = 1; + + switch ci.type, + case {'const','files','menu','entry'} + nnod = nod + 1; + + vl = {''}; + if isfield(ci,'def'), vl = getdef(ci.def); end; + if numel(vl)~=0 && (~ischar(vl{1}) || ~strcmp(vl{1},'')), + getit = 0; + if ~isfield(ci,'val') || ~iscell(ci.val) || isempty(ci.val), + ci.val = vl; + end; + else + getit = 1; + end; + + switch ci.type, + case {'const'} + case {'files'} + num = ci.num; + + if getit, + if ~isempty(ci.val), + sel = ci.val{1}; + else + sel = ''; + end; + addvfiles(ci.id,c); + if isfield(ci,'dir'), + dr = ci.dir; + else + dr = pwd; + end; + if isfield(ci,'ufilter') + uf = ci.ufilter; + else + uf = '.*'; + end; + [ci.val{1},ok] = spm_select(num,ci.filter,ci.name,sel,dr,uf); + if ~ok, + error('File Selector was deleted.'); + end; + spm_select('clearvfiles'); + ci.val{1} = cellstr(ci.val{1}); + end; + + case {'menu'} + dv = []; + if getit, + if isfield(ci,'val') && ~isempty(ci.val), + for i=1:length(ci.values) + if same(ci.values{i},ci.val{1}) + dv = i; + end; + end; + end; + lab = ci.labels{1}; + for i=2:length(ci.values), + lab = [lab '|' ci.labels{i}]; + end; + if isempty(dv), + ind = spm_input(ci.name,pos,'m',lab,1:length(ci.values)); + else + ind = spm_input(ci.name,pos,'m',lab,1:length(ci.values),dv); + end; + ci.val = {ci.values{ind}}; + end; + + case {'entry'} + n1 = Inf; + if isfield(ci,'num'), n1 = ci.num; end; + if getit, + val = ''; + if isfield(ci,'val') && ~isempty(ci.val) && ~strcmp(ci.val{1},''), + val = ci.val{1}; + end; + if isfield(ci,'extras') + val = spm_input(ci.name,pos,ci.strtype,val,n1,ci.extras); + else + val = spm_input(ci.name,pos,ci.strtype,val,n1); + end; + ci.val{1} = val; + end; + end; + + case {'repeat'}, + lab = 'Done'; + for i=1:length(ci.values) + lab = [lab '|New "' ci.values{i}.name '"']; + end; + tmp = spm_input(ci.name,pos,'m',lab,0:length(ci.values)); + if tmp, + ci.val = {ci.val{:}, uniq_id(ci.values{tmp})}; + nnod = nod; + else + nnod = nod + 1; + end; + + case {'choice'} + nnod = nod + 1; + lab = ci.values{1}.name; + for i=2:length(ci.values) + lab = [lab '|' ci.values{i}.name]; + end; + tmp = spm_input(ci.name,pos,'m',lab,1:length(ci.values)); + ci.val = {uniq_id(ci.values{tmp})}; + otherwise + error('This should not happen.'); + end; + c = set_node(c,nod,ci); +end; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function [ci,n,hlp] = get_node(c,n) +ci = []; +hlp = ''; +switch c.type, +case {'const','files','menu','entry'} + n = n-1; + if n==0, + ci = c; + hlp = {['* ' upper(c.name)],c.help{:},'',''}; + return; + end; + +case 'choice' + n = n-1; + if n==0, + ci = c; + hlp = {['* ' upper(c.name)],c.help{:},'',''}; + return; + end; + [ci,n,hlp] = get_node(c.val{1},n); + if ~isempty(ci), + hlp = {hlp{:},repmat('=',1,20),'',['* ' upper(c.name)],c.help{:},'',''}; + return; + end; + +case 'branch', + for i=1:numel(c.val), + [ci,n,hlp] = get_node(c.val{i},n); + if ~isempty(ci), + hlp = {hlp{:},repmat('=',1,20),'',['* ' upper(c.name)],c.help{:},'',''}; + return; + end; + end; + +case 'repeat', + for i=1:numel(c.val), + [ci,n,hlp] = get_node(c.val{i},n); + if ~isempty(ci), + hlp = {hlp{:},repmat('=',1,20),'',['* ' upper(c.name)],c.help{:},'',''}; + return; + end; + end; + n = n-1; + if n==0, + ci = c; + hlp = {['* ' upper(c.name)],c.help{:},'',''}; + return; + end; +otherwise + error('This should not happen'); + +end; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function [c,n] = set_node(c,n,ci) +switch c.type, +case {'const','files','menu','entry'} + n = n-1; + if n==0, + c = ci; + return; + end; + +case 'choice' + n = n-1; + if n==0, + c = ci; + return; + end; + [c.val{1},n] = set_node(c.val{1},n,ci); + if n<0,return; end; + +case 'branch', + for i=1:numel(c.val), + [c.val{i},n] = set_node(c.val{i},n,ci); + if n<0,return; end; + end; + +case 'repeat', + for i=1:numel(c.val), + [c.val{i},n] = set_node(c.val{i},n,ci); + if n<0,return; end; + end; + n = n-1; + if n==0, + c = ci; + return; + end; + +end; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function c = initialise_struct(job) +% load the config file, possibly adding a job +% to it, and generally tidy it up. The batch box +% is updated to contain the current structure. + +persistent c0 +if isempty(c0), + c0 = spm_config; + c0 = tidy_struct(c0); +end; +c = insert_defs(c0); +if nargin==1 && ischar(job) && strcmp(job,'defaults'), + c = defsub(c,{}); + c.name = 'SPM Defaults'; +else + c = hide_null_jobs(c); + if nargin>0 && ~isempty(job), + job = fromfile(job); + c = job_to_struct(c,job,'jobs'); + c = uniq_id(c); + end; +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function [c,defused] = defsub(c,defused) +if nargin<2, defused = {}; end; +if isfield(c,'prog'), c = rmfield(c,'prog'); end; +switch c.type, +case {'const'} + c = []; + +case {'menu','entry','files'} + if ~isfield(c,'def') || any(strcmp(c.def,defused)), + c = []; + else + defused = {defused{:},c.def}; + end; + +case {'branch'} + msk = true(1,length(c.val)); + for i=1:length(c.val), + [c.val{i},defused] = defsub(c.val{i},defused); + msk(i) = ~isempty(c.val{i}); + end; + c.val = c.val(msk); + if isempty(c.val), c = []; end; + +case {'choice','repeat'} + c.type = 'branch'; + c.val = c.values; + c = rmfield(c,'values'); + [c,defused] = defsub(c,defused); + +end; +if isfield(c,'vfiles'), c = rmfield(c,'vfiles'); end; +if isfield(c,'check'), c = rmfield(c,'check'); end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function c = insert_defs(c) +% Recursively descend through the tree structure, +% and assigning default values. +if ~isstruct(c) || ~isfield(c,'type'), + return; +end; +switch c.type +case {'menu','entry','files'} + if isfield(c,'def') + c.val = getdef(c.def); + if strcmp(c.type,'files') && ~isempty(c.val) + if ~isempty(c.val{1}) + c.val = {cellstr(c.val{1})}; + else + c.val = {{}}; + end; + end; + end; + +case {'repeat','choice'}, + if isfield(c,'values') + for i=1:numel(c.values) + c.values{i} = insert_defs(c.values{i}); + end; + end; +end; +if isfield(c,'val') + for i=1:numel(c.val) + c.val{i} = insert_defs(c.val{i}); + end; +end; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function c = tidy_struct(c) +% Recursively descend through the tree structure, cleaning up +% fields that may be missing and adding an 'expanded' field +% where necessary. + +if ~isstruct(c) || ~isfield(c,'name') || ~isfield(c,'type') + return; +end; +c.id = rand(1); + +if ~isfield(c,'help'), c.help = {}; end; +if ischar(c.help), c.help = {c.help}; end; + +switch c.type +case {'const'} + if ~isfield(c,'tag') + disp(c); warning(['No tag field for "' c.name '"']); + c.tag = 'unknown'; + end; + if ~isfield(c,'val') + disp(c); warning(['No val field for "' c.name '"']); + c.val = {''}; + end; + +case {'menu'} + if ~isfield(c,'tag') + disp(c); warning(['No tag field for "' c.name '"']); + c.tag = 'unknown'; + end; + + if ~isfield(c,'labels') || ~isfield(c,'values') + disp(c); warning(['No labels and values field for "' c.name '"']); + c.labels = {}; + c.values = {}; + end; + if length(c.labels) ~= length(c.values) + disp(c); warning(['Labels and values fields incompatible for "' c.name '"']); + c.labels = {}; + c.values = {}; + end; + if ~isfield(c,'help'), c.help = {'Option selection by menu'}; end; + +case {'entry'} + if ~isfield(c,'tag') + disp(c); warning(['No tag field for "' c.name '"']); + c.tag = 'unknown'; + end; + if ~isfield(c,'strtype') + disp(c); warning(['No strtype field for "' c.name '"']); + c.strtype = 'e'; + end; + if ~isfield(c,'num') + disp(c); warning(['No num field for "' c.name '"']); + c.num = [1 1]; + end; + if length(c.num)~=2 + disp(c); warning(['Num field for "' c.name '" is wrong length']); + c.num = [Inf 1]; + end; + if ~isfield(c,'help'), c.help = {'Option selection by text entry'}; end; + +case {'files'} + if ~isfield(c,'tag') + disp(c); warning(['No tag field for "' c.name '"']); + c.tag = 'unknown'; + end; + if ~isfield(c,'filter') + disp(c); warning(['No filter field for "' c.name '"']); + c.filter = '*'; + end; + if ~isfield(c,'num') + disp(c); warning(['No num field for "' c.name '"']); + c.num = Inf; + end; + if length(c.num)~=1 && length(c.num)~=2 + disp(c); warning(['Num field for "' c.name '" is wrong length']); + c.num = Inf; + end; + if isfield(c,'val') && iscell(c.val) && numel(c.val)>=1, + if ischar(c.val{1}) + c.val{1} = cellstr(c.val{1}); + end; + end; + if ~isfield(c,'help'), c.help = {'File selection'}; end; + +case {'branch'} + if ~isfield(c,'tag') + disp(c); warning(['No tag field for "' c.name '"']); + c.tag = 'unknown'; + end; + if ~isfield(c,'val') + disp(c); warning(['No val field for "' c.name '"']); + c.val = {}; + end; + + c.expanded = false; + if ~isfield(c,'help'), c.help = {'Branch structure'}; end; + +case {'choice'} + if ~isfield(c,'tag') + disp(c); warning(['No tag field for "' c.name '"']); + c.tag = 'unknown'; + end; + if ~isfield(c,'values') || ~iscell(c.values) + disp(c); error(['Bad values for "' c.name '"']); + end; + for i=1:length(c.values) + c.values{i} = tidy_struct(c.values{i}); + end; + if ~isfield(c,'val') || ~iscell(c.val) || length(c.val) ~= 1 + c.val = {c.values{1}}; + end; + c.expanded = true; + if ~isfield(c,'help'), c.help = {'Choice structure'}; end; + +case {'repeat'} + if ~isfield(c,'values') || ~iscell(c.values) + disp(c); error(['Bad values for "' c.name '"']); + end; + for i=1:length(c.values) + c.values{i} = tidy_struct(c.values{i}); + end; + if length(c.values)>1 && ~isfield(c,'tag') + disp(c); warning(['No tag field for "' c.name '"']); + c.tag = 'unknown'; + end; + if length(c.values)==1 && isfield(c,'tag') + % disp(c); warning(['"' c.name '" has unused tag']); + c = rmfield(c,'tag'); + end; + c.expanded = true; + if ~isfield(c,'help'), c.help = {'Repeated structure'}; end; + + if isfield(c,'num') && numel(c.num)==1, + if finite(c.num), + c.num = [c.num c.num]; + else + c.num = [0 c.num]; + end; + end; + +end; + +if ~isfield(c,'val'), c.val = {}; end; + +%switch c.type +%case {'menu','entry','files'} +% %if isempty(c.val) +% if isfield(c,'def') +% c.val = getdef(c.def); +% if strcmp(c.type,'files') && ~isempty(c.val) +% c.val = {cellstr(c.val{1})}; +% end; +% end; +% %end; +%end; + +if isfield(c,'val') + for i=1:length(c.val) + c.val{i} = tidy_struct(c.val{i}); + end; +end; +if isfield(c,'values') && strcmp(c.type,'repeat') + for i=1:length(c.values) + c.values{i} = tidy_struct(c.values{i}); + end; +end; + +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function job = fromfile(job) +if ischar(job) + [pth,nam,ext] = fileparts(job); + if strcmp(ext,'.xml') + spm('Pointer','Watch'); + try + loadxml(job,'jobs'); + catch + questdlg('LoadXML failed',job,'OK','OK'); + return; + end; + spm('Pointer'); + elseif strcmp(ext,'.mat') + try + load(job,'jobs'); + catch + questdlg('Load failed',job,'OK','OK'); + return; + end; + else + questdlg(['Unknown extension (' ext ')'],'Nothing loaded','OK','OK'); + return; + end; + job = jobs; +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function c = hide_null_jobs(c) +c = hide_null_jobs1(c); +c = hide_null_jobs2(c); +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function [c,flg] = hide_null_jobs1(c) +if ~isstruct(c) || ~isfield(c,'type'), + flg = true; + return; +end; + +if ~include(c), + flg = false; + c.hidden = true; + return; +end; + +switch c.type, +case {'repeat','branch','choice'}, + msk1 = true; + msk2 = true; + if isfield(c,'val') && ~isempty(c.val), + msk1 = true(1,numel(c.val)); + for i=1:length(c.val) + [c.val{i},msk1(i)] = hide_null_jobs1(c.val{i}); + end; + end; + if isfield(c,'values') && ~isempty(c.values), + msk2 = true(1,numel(c.values)); + for i=1:length(c.values) + [c.values{i},msk2(i)] = hide_null_jobs1(c.values{i}); + end; + end; + flg = any(msk1) || any(msk2); + if ~flg, c.hidden = true; end; +otherwise + flg = true; +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function ok = include(c) +% Check that the modality is OK +ok = true; +if isfield(c,'modality'), + mod = getdef('modality'); + if ~isempty(mod), + mod = mod{1}; + ok = false; + for i=1:length(c.modality), + if strcmpi(c.modality{i},mod), + ok = true; + return; + end; + end; + end; +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function [c,flg] = hide_null_jobs2(c) +if ~isstruct(c) || ~isfield(c,'type') + flg = 0; + return; +end; +if isfield(c,'prog'), + flg = 1; + return; +end; + +switch c.type, +case {'repeat','branch','choice'}, + flg = 0; + msk1 = []; + msk2 = []; + if isfield(c,'val'), + msk1 = ones(1,numel(c.val)); + for i=1:length(c.val) + [c.val{i},msk1(i)] = hide_null_jobs2(c.val{i}); + end; + end; + if isfield(c,'values'), + msk2 = ones(1,numel(c.values)); + for i=1:length(c.values) + [c.values{i},msk2(i)] = hide_null_jobs2(c.values{i}); + end; + end; + if (sum(msk1) + sum(msk2))>0, flg = 1; end; + if ~flg, c.hidden = 1; end; +otherwise + flg = 0; +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function c = job_to_struct(c,job,tag) +% Modify a structure based on a batch job +if isstruct(c) && isfield(c,'hidden'), + return; +end; +switch c.type +case {'const','menu','files','entry'} + if ~strcmp(gettag(c),tag), return; end; + if ischar(job) && strcmp(job,'') + c.val = {}; + else + c.val{1} = job; + end; + +case {'branch'} + if ~strcmp(gettag(c),tag), return; end; + if ~isstruct(job), return; end; + + tag = fieldnames(job); + for i=1:length(tag) + for j=1:length(c.val) + if strcmp(gettag(c.val{j}),tag{i}) + c.val{j} = job_to_struct(c.val{j},job.(tag{i}),tag{i}); + break; + end; + end; + end; + +case {'choice'} + if ~strcmp(gettag(c),tag), return; end; + if ~isstruct(job), return; end; + tag = fieldnames(job); + if length(tag)>1, return; end; + tag = tag{1}; + for j=1:length(c.values) + if strcmp(gettag(c.values{j}),tag) + c.val = {job_to_struct(c.values{j},job.(tag),tag)}; + end; + end; + +case {'repeat'} + if ~strcmp(gettag(c),tag), return; end; + if length(c.values)==1 && strcmp(c.values{1}.type,'branch') + if ~isstruct(job), return; end; + for i=1:length(job) + if strcmp(gettag(c.values{1}),tag) + c.val{i} = job_to_struct(c.values{1},job(i),tag); + c.val{i}.removable = true; + end; + end; + elseif length(c.values)>1 + if ~iscell(job), return; end; + for i=1:length(job) + tag = fieldnames(job{i}); + if length(tag)>1, return; end; + tag = tag{1}; + for j=1:length(c.values) + if strcmp(gettag(c.values{j}),tag) + c.val{i} = job_to_struct(c.values{j},job{i}.(tag),tag); + c.val{i}.removable = true; + break; + end; + end; + end; + else + if ~iscell(job), return; end; + for i=1:length(job) + c.val{i} = job_to_struct(c.values{1},job{i},tag); + c.val{i}.removable = true; + end; + end; + +end; +return; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function doc = showdoc(str,wid) +if nargin<1, str = ''; end; +if nargin<2, wid = 60; end; + +tmp = [0 find([str '.']=='.')]; +node = {}; +for i=1:length(tmp)-1, + tmp1 = str((tmp(i)+1):(tmp(i+1)-1)); + if ~isempty(tmp1), + node = {node{:},tmp1}; + end; +end; +if numel(node)>1 && strcmp(node{1},'jobs'), + node = node(2:end); +end; + +c = initialise_struct; +doc = showdoc1(node,c,wid); +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function doc = showdoc1(node,c,wid) +doc = {}; +if isempty(node), + doc = showdoc2(c,'',wid); + return; +end; + +if isfield(c,'values'), + for i=1:numel(c.values), + if isfield(c.values{i},'tag') && strcmp(node(1),c.values{i}.tag), + doc = showdoc1(node(2:end),c.values{i},wid); + return; + end; + end; +end; + +if isfield(c,'val'), + for i=1:numel(c.val), + if isfield(c.val{i},'tag') && strcmp(node(1),c.val{i}.tag), + doc = showdoc1(node(2:end),c.val{i},wid); + return; + end; + end; +end; +%------------------------------------------------------------------------ + +%------------------------------------------------------------------------ +function doc = showdoc2(c,lev,wid) +doc = {''}; +if ~isempty(lev) && sum(lev=='.')==1, + % doc = {doc{:},repmat('_',1,80),''}; +end; + +if isfield(c,'name'), + str = sprintf('%s %s', lev, c.name); + %under = repmat('-',1,length(str)); + doc = {doc{:},str}; + % if isfield(c,'modality'), + % txt = 'Only for '; + % for i=1:numel(c.modality), + % txt = [txt ' ' c.modality{i}]; + % end; + % doc = {doc{:},'',txt, ''}; + %end; + if isfield(c,'help'); + hlp = spm_justify(wid,c.help); + doc = {doc{:},hlp{:}}; + end; + + switch (c.type), + case {'repeat'}, + if length(c.values)==1, + doc = {doc{:}, '', sprintf('Repeat "%s", any number of times.',c.values{1}.name)}; + else + doc = {doc{:}, '', 'Any of the following options can be chosen, any number of times'}; + i = 0; + for ii=1:length(c.values), + if isstruct(c.values{ii}) && isfield(c.values{ii},'name'), + i = i+1; + doc = {doc{:}, sprintf(' %2d) %s', i,c.values{ii}.name)}; + end; + end; + end; + doc = {doc{:},''}; + + case {'choice'}, + doc = {doc{:}, '', 'Any one of these options can be selected:'}; + i = 0; + for ii=1:length(c.values), + if isstruct(c.values{ii}) && isfield(c.values{ii},'name'), + i = i+1; + doc = {doc{:}, sprintf(' %2d) %s', i,c.values{ii}.name)}; + end; + end; + doc = {doc{:},''}; + + case {'branch'}, + doc = {doc{:}, '', sprintf('This item contains %d fields:', length(c.val))}; + i = 0; + for ii=1:length(c.val), + if isstruct(c.val{ii}) && isfield(c.val{ii},'name'), + i = i+1; + doc = {doc{:}, sprintf(' %2d) %s', i,c.val{ii}.name)}; + end; + end; + doc = {doc{:},''}; + + case {'menu'}, + doc = {doc{:}, '', 'One of these values is chosen:'}; + for k=1:length(c.labels), + doc = {doc{:}, sprintf(' %2d) %s', k, c.labels{k})}; + end; + doc = {doc{:},''}; + + case {'files'}, + if length(c.num)==1 && isfinite(c.num(1)) && c.num(1)>=0, + tmp = spm_justify(wid,sprintf('A "%s" file is selected by the user.',c.filter)); + else + tmp = spm_justify(wid,sprintf('"%s" files are selected by the user.\n',c.filter)); + end; + doc = {doc{:}, '', tmp{:}, ''}; + + case {'entry'}, + switch c.strtype, + case {'e'}, + d = 'Evaluated statements'; + case {'n'}, + d = 'Natural numbers'; + case {'r'}, + d = 'Real numbers'; + case {'w'}, + d = 'Whole numbers'; + otherwise, + d = 'Values'; + end; + tmp = spm_justify(wid,sprintf('%s are entered.',d)); + doc = {doc{:}, '', tmp{:}, ''}; + end; + + i = 0; + doc = {doc{:},''}; + if isfield(c,'values'), + for ii=1:length(c.values), + if isstruct(c.values{ii}) && isfield(c.values{ii},'name'), + i = i+1; + lev1 = sprintf('%s%d.', lev, i); + doc1 = showdoc2(c.values{ii},lev1,wid); + doc = {doc{:}, doc1{:}}; + end; + end; + end; + if isfield(c,'val'), + for ii=1:length(c.val), + if isstruct(c.val{ii}) && isfield(c.val{ii},'name'), + i = i+1; + lev1 = sprintf('%s%d.', lev, i); + doc1 = showdoc2(c.val{ii},lev1,wid); + doc = {doc{:}, doc1{:}}; + end; + end; + end; + doc = {doc{:}, ''}; +end; + + diff --git a/spm/spm_log2html/spm_log2html.m b/spm/spm_log2html/spm_log2html.m new file mode 100644 index 0000000..09ea46f --- /dev/null +++ b/spm/spm_log2html/spm_log2html.m @@ -0,0 +1,154 @@ +function htmlfile = spm_log2html(prog, val, options) +%SPM_LOG2HTML - Log report on SPM processing to HTML file +% [] = spm_log2html(prog, val, options) print log to the html file named in output +% +% Example +% >> spm_log2html +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2008 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2008-10-17 Creation +% +% ----------------------------- Script History --------------------------------- + +% I guess there exists an xml -> html thingy I could use somewhere... + +prog_infos=functions(prog) +% jobname=sprintf('%s:', prog_infos.parentage{end:-1:1}); +% jobname(end)=[]; +jobname=sprintf('%s', prog_infos.parentage{1}); +logtxt={}; +logtxt{end+1}=['SPM logging for: ' jobname]; +htmlfile = fullfile(fileparts(getfield(spm_vol(val.data{1}{1}),'fname')),'log',[jobname '.html']); +logtxt{end+1}=['Into: ']; +logtxt{end+1}={'file' htmlfile}; +htmldir = fileparts(htmlfile); +% if exist(htmldir, 'dir') +% if length(dir(htmldir)) > 2 +% htmldir_old=fullfile(fileparts(htmldir), ['log_' datestr(now,30)]); +% movefile(htmldir,-) +% logtxt{end+1}=['Renaming older directory: ' ]; +% logtxt{end+1}={'folder', htmldir_old}; +% mkdir(htmldir) +% end +% end +if not(exist(htmldir, 'dir')) + logtxt{end+1}=['Creating new directory: ' ]; + logtxt{end+1}={'folder', htmldir}; + mkdir(htmldir) +end + +switch jobname + case 'estimate' + logtxt=log_realign(val,htmldir,logtxt); + case 'reslice'; + % to do + case 'estwrite_fun'; + % to do + otherwise + logtxt{end+1}=['Unknown function to log: ' jobname]; +end +log2html(htmlfile,logtxt) +return + +function [spname]=shortpathname(pname) +% shortens file names +spname=[pname(1:10) '...' pname(end-15:end)]; + +function [hpname]=htmlpathname(pname) +% Replace filesep in file path with slashes (/) for HTML compatibility +hpname = strrep(pname,filesep,'/'); + +function []=log2html(htmlfile, log) +% print out to html file +save(fullfile(fileparts(htmlfile),'spm_log2html.mat'),'log'); +fid=fopen(htmlfile,'wt'); +html_header(fid); +fprintf(fid,'

      '); +for i=1:length(log); + if ischar(log{i}) + if i>1 + fprintf(fid, '

      \n

      '); + end + fprintf(fid, '%s', log{i}); + else + switch log{i}{1} + case 'picture' + fprintf(fid, '',htmlpathname(log{i}{2}),htmlpathname(log{i}{2}),shortpathname(log{i}{2})); + case {'folder' 'file'} + fprintf(fid, '%s',htmlpathname(log{i}{2}),shortpathname(log{i}{2})); + end + end +end +fprintf(fid,'Done.

      \n\n'); +if fid > 2 + fclose(fid); + try + browser = '"C:\Documents and Settings\ndiayek\Local Settings\Application Data\Google\Chrome\Application\chrome.exe"' + system([browser ' "' htmlfile '"']); + catch + browser = '"C:\program Files\Mozilla Firefox\firefox.exe" '; + end; +end +return + +function [logtxt]=html_header(fid) +fprintf(fid,'\n'); +fprintf(fid,'\n'); +fprintf(fid,'

      Processing steps:

        '); +fprintf(fid,'
      1. realign'); +fprintf(fid,'
      2. segment'); +fprintf(fid,'
      3. normalize'); +fprintf(fid,'
      4. smooth'); +fprintf(fid,'
      5. design'); +fprintf(fid,'

      '); + + + +function [logtxt]=html_footer(htmlfile, logtxt) + + + +function [logtxt] = log_design(val,htmldir,logtxt) +% Log of DESIGN results + + +function [logtxt] = log_realign(val,htmldir,logtxt) +% Log of REALIGN results +mfile = which('spm_realign'); +if ~isequal(textread(mfile,'%s',4,'headerlines',82,'delimiter',' '),... + {'%', '$Id:', 'spm_realign.m', '433'}') % The native version at the time + logtxt{end+1}=sprintf('The spm_realign.m (%s) has changed! Default flags may also have changed.', mfile); + warning(logtxt(1)); +end +def_flags = struct('quality',1,'fwhm',5,'sep',4,'interp',2,'wrap',[0 0 0],'rtm',0,'PW','','graphics',1,'lkp',1:6); +flags =mergestructs(def_flags,val.eoptions); +if flags.graphics + opts = {fullfile(htmldir,'realign_estimate.png'),'-noui','-painters','-dpng'}; + fg = spm_figure('FindWin','Graphics'); + logtxt{end+1} = {'Plot of the Movement Parameters'}; + % export figure in image file + print(fg,opts{:}); + logtxt{end+1} = {'picture','realign_estimate.png'}; +else + logtxt{end+1}='Graphics must be generated for logging. Sorry...'; + % rp_file = [spm_str_manip(prepend(getfield(spm_vol(val.data{1}{1}),'fname'),'rp_'),'s') '.txt']; + % P=textread(rp_file); + % plot_parameters(P) +end +return + + +function [logtxt] = log_smooth(val,htmldir,logtxt) +% Log of SMOOTH results +mfile = which('spm_smooth'); +logtxt(end+1)='todo'; +return diff --git a/spm/spm_mm2vox.m b/spm/spm_mm2vox.m new file mode 100644 index 0000000..bc96eb8 --- /dev/null +++ b/spm/spm_mm2vox.m @@ -0,0 +1,52 @@ +function M = spm_mm2vox(V,XYZmm,dim) +%SPM_MM2VOX - Retrieve coordinates in voxels from millimeters +% [M] = spm_mm2vox(V,XYZmm,dim) +% +% Example +% >> spm_mm2vox +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2008 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2008-06-12 Creation +% +% ----------------------------- Script History --------------------------------- +if nargin<3 + dim=find(size(XYZmm) == 3); + if length(dim)~=1 + error('XYZmm must be a 3 by N matrix') + end +end +if ischar(V) + if exist(V,'file') + V=spm_vol(V); + end +end +if isstruct(V) + if isfield(V, 'mat') + V=V.mat; + end + if isfield(V, 'MAT') + V=V.MAT; + end +end +MAT=V; + +s = size(XYZmm); +if s(dim) ~= 3 + error('The %d-th dimension of XYZmm should be 3', dim) +end + +XYZmm = nd2array(XYZmm,dim); +XYZmm = [XYZmm ; ones(1, size(XYZmm,2))]; + +M = inv(MAT)*XYZmm; +M = M(1:3,:); +M = nd2array(M,-dim,s); diff --git a/spm/spm_mysections.m b/spm/spm_mysections.m new file mode 100644 index 0000000..f30274d --- /dev/null +++ b/spm/spm_mysections.m @@ -0,0 +1,58 @@ +function spm_mysections(input,varargin) +%SPM_MYSECTIONS - Display views +% [] = spm_mysections(input) +% +% Example +% >> spm_mysections +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2009 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2009-07-03 Creation +% +% ----------------------------- Script History --------------------------------- + +function spm_mysections(SPM,,hReg) +% rendering of regional effects [SPM{Z}] on orthogonal sections of +% colin27's brain +% FORMAT spm_sections(SPM,hReg) +% +% SPM - xSPM structure containing details of excursion set +% hReg - handle of MIP register +% +% see spm_getSPM for details +%_______________________________________________________________________ +% +% spm_sections is called by spm_results and uses variables in SPM and +% VOL to create three orthogonal sections though a background image. +% Regional foci from the selected SPM are rendered on this image. +% +%_______________________________________________________________________ +% @(#)spm_sections.m 2.14 John Ashburner 02/09/05 + +Fgraph = spm_figure('FindWin','Graphics'); +if isempty(Fgraph) + Fgraph = spm_figure('Create','Graphics'); +end +%spms = spm_get(1,'IMAGE','select image for rendering on'); +spms = fullfile(spm('dir'), 'canonical', 'single_subj_T1.nii'); +if ~exist(spms) + spms = fullfile(spm('dir'), 'canonical', 'single_subj_T1.mnc'); +end + +spm_results_ui('Clear',Fgraph); +spm_orthviews('Reset'); +global st +st.Space = spm_matrix([0 0 0 0 0 -pi/2])*st.Space; +spm_orthviews('Image',spms,[0.05 0.05 0.9 0.45]); +spm_orthviews MaxBB; +spm_orthviews('register',hReg); +spm_orthviews('addblobs',1,SPM.XYZ,SPM.Z,SPM.M); +spm_orthviews('Redraw'); diff --git a/spm/spm_render2.m b/spm/spm_render2.m new file mode 100644 index 0000000..0cd4650 --- /dev/null +++ b/spm/spm_render2.m @@ -0,0 +1,278 @@ +function [varargout]=spm_render2(dat,brt,rendfile) +% Render blobs on surface of a 'standard' brain +% FORMAT spm_render(dat,brt,rendfile) +% +% dat - a vertical cell array of length 1 to 3 +% - each element is a structure containing: +% - XYZ - the x, y & z coordinates of the transformed t values. +% in units of voxels. +% - t - the SPM{.} values +% - mat - affine matrix mapping from XYZ voxels to Talairach. +% - dim - dimensions of volume from which XYZ is drawn. +% brt - brightness control: +% If NaN, then displays using the old style with hot +% metal for the blobs, and grey for the brain. +% Otherwise, it is used as a ``gamma correction'' to +% optionally brighten the blobs up a little. +% rendfile - the file containing the images to render on to. See also +% spm_xbrain.m. +% +% Without arguments, spm_render acts as its own UI. +%_______________________________________________________________________ +% +% spm_render prompts for details of up to three SPM{Z}s or SPM{t}s that +% are then displayed superimposed on the surface of a standard brain. +% +% The first is shown in red, then green then blue. +% +% The blobs which are displayed are the integral of all transformed t +% values, exponentially decayed according to their depth. Voxels that +% are 10mm behind the surface have half the intensity of ones at the +% surface. +%_______________________________________________________________________ +% @(#)spm_render.m 2.19 John Ashburner FIL 02/10/29 + +%-Parse arguments, get data if not passed as parameters +%======================================================================= +if nargin < 1 + SPMid = spm('FnBanner',mfilename,'2.19'); + [Finter,Fgraph,CmdLine] = spm('FnUIsetup','Results: render',0); + + num = spm_input('Number of sets',1,'1 set|2 sets|3 sets',[1 2 3]); + + for i = 1:num, + [SPM,VOL] = spm_getSPM; + dat(i) = struct( 'XYZ', VOL.XYZ,... + 't', VOL.Z',... + 'mat', VOL.M,... + 'dim', VOL.DIM); + end; + showbar = 1; +else, + num = length(dat); + showbar = 0; +end; + +showbar=1 + +% get brightness +%----------------------------------------------------------------------- +if nargin < 2, + brt = 1; + if num==1, + brt = spm_input('Style',1,'new|old',[1 NaN], 1); + end; + if finite(brt), + brt = spm_input('Brighten blobs',1,'none|slightly|more|lots',[1 0.75 0.5 0.25], 1); + end; +end; + +if isstruct(dat) & isfield(dat, 'rend') + rend=dat.rend; + mxmx=dat.mxmx; + mnmn=dat.mnmn; + clear dat; +else + + % get surface + %----------------------------------------------------------------------- + if nargin < 3, + rendfile = fullfile(spm('Dir'),'rend', 'render_single_subj.mat') + % rendfile = spm_get(1,'render*.mat','Render file',fullfile(spm('Dir'),'rend')); + end; + + % Perform the rendering + %======================================================================= + spm('Pointer','Watch') + load(rendfile); + + if (exist('rend') ~= 1), % Assume old format... + rend = cell(size(Matrixes,1),1); + for i=1:size(Matrixes,1), + rend{i}=struct('M',eval(Matrixes(i,:)),... + 'ren',eval(Rens(i,:)),... + 'dep',eval(Depths(i,:))); + rend{i}.ren = rend{i}.ren/max(max(rend{i}.ren)); + end; + end; + + if showbar, spm_progress_bar('Init', size(dat,1)*length(rend),... + 'Formatting Renderings', 'Number completed'); end; + for i=1:length(rend), + rend{i}.max=0; + rend{i}.data = cell(size(dat,1),1); + if issparse(rend{i}.ren), + % Assume that images have been DCT compressed + % - the SPM99 distribution was originally too big. + d = size(rend{i}.ren); + B1 = spm_dctmtx(d(1),d(1)); + B2 = spm_dctmtx(d(2),d(2)); + rend{i}.ren = B1*rend{i}.ren*B2'; + % the depths did not compress so well with + % a straight DCT - therefore it was modified slightly + rend{i}.dep = exp(B1*rend{i}.dep*B2')-1; + end; + msk = find(rend{i}.ren>1);rend{i}.ren(msk)=1; + msk = find(rend{i}.ren<0);rend{i}.ren(msk)=0; + if showbar, spm_progress_bar('Set', i); end; + end; + if showbar, spm_progress_bar('Clear'); end; + + if showbar, spm_progress_bar('Init', length(dat)*length(rend),... + 'Making pictures', 'Number completed'); end; + + mx = zeros(length(rend),1)+eps; + mn = zeros(length(rend),1); + + for j=1:length(dat), + XYZ = dat(j).XYZ; + t = dat(j).t; + dim = dat(j).dim; + mat = dat(j).mat; + + for i=1:length(rend), + + % transform from Taliarach space to space of the rendered image + %------------------------------------------------------- + M1 = rend{i}.M*dat(j).mat; + zm = sum(M1(1:2,1:3).^2,2).^(-1/2); + M2 = diag([zm' 1 1]); + M = M2*M1; + cor = [1 1 1 ; dim(1) 1 1 ; 1 dim(2) 1; dim(1) dim(2) 1 ; + 1 1 dim(3) ; dim(1) 1 dim(3) ; 1 dim(2) dim(3); dim(1) dim(2) dim(3)]'; + tcor= M(1:3,1:3)*cor + M(1:3,4)*ones(1,8); + off = min(tcor(1:2,:)'); + M2 = spm_matrix(-off+1)*M2; + M = M2*M1; + xyz = (M(1:3,1:3)*XYZ + M(1:3,4)*ones(1,size(XYZ,2))); + d2 = ceil(max(xyz(1:2,:)')); + + % calculate 'depth' of values + %------------------------------------------------------- + dep = spm_slice_vol(rend{i}.dep,spm_matrix([0 0 1])*inv(M2),d2,1); + z1 = dep(round(xyz(1,:))+round(xyz(2,:)-1)*size(dep,1)); + + if ~finite(brt), msk = find(xyz(3,:) < (z1+20) & xyz(3,:) > (z1-5)); + else, msk = find(xyz(3,:) < (z1+60) & xyz(3,:) > (z1-5)); end; + + if ~isempty(msk), + + % generate an image of the integral of the blob values. + %----------------------------------------------- + xyz = xyz(:,msk); + if ~finite(brt), t0 = t(msk); + else, dst = xyz(3,:) - z1(msk); + dst = max(dst,0); + t0 = t(msk).*exp((log(0.5)/10)*dst)'; + end; + X0 = full(sparse(round(xyz(1,:)), round(xyz(2,:)), t0, d2(1), d2(2))); + hld = 1; if ~finite(brt), hld = 0; end; + X = spm_slice_vol(X0,spm_matrix([0 0 1])*M2,size(rend{i}.dep),hld); + msk = find(X<0); + X(msk) = 0; + else, + X = zeros(size(rend{i}.dep)); + end; + + % Brighten the blobs + if finite(brt), X = X.^brt; end; + + mx(j) = max([mx(j) max(max(X))]); + mn(j) = min([mn(j) min(min(X))]); + + rend{i}.data{j} = X; + + if showbar, spm_progress_bar('Set', i+(j-1)*length(rend)); end; + end; + end; + + mxmx = max(mx); + mnmn = min(mn); + +end + +if showbar, spm_progress_bar('Clear'); end; +Fgraph = spm_figure('GetWin','Graphics'); +spm_results_ui('Clear',Fgraph); + +nrow = ceil(length(rend)/2); +if showbar, hght = 0.95; else, hght = 0.5; end; +% subplot('Position',[0, 0, 1, hght]); +ax=axes('Parent',Fgraph,'units','normalized','Position',[0, 0, 1, hght],'Visible','off'); +image(0,'Parent',ax); +set(ax,'YTick',[],'XTick',[]); + +if ~finite(brt), + % Old style split colourmap display. + %--------------------------------------------------------------- + load Split; + colormap(split); + for i=1:length(rend), + ren = rend{i}.ren; + X = (rend{i}.data{1}-mnmn)/(mxmx-mnmn); + msk = find(X); + ren(msk) = X(msk)+(1+1.51/64); + ax=axes('Parent',Fgraph,'units','normalized',... + 'Position',[rem(i-1,2)*0.5, floor((i-1)/2)*hght/nrow, 0.5, hght/nrow],... + 'Visible','off'); + image(ren*64,'Parent',ax); + set(ax,'DataAspectRatio',[1 1 1], ... + 'PlotBoxAspectRatioMode','auto',... + 'YTick',[],'XTick',[],'XDir','normal','YDir','normal'); + end; +else, + % Colors=[1 0 0; 1 .7 0 ]; + Colors=[1 0 1; 1 .7 0 ]; + % Combine the brain surface renderings with the blobs, and display using + % 24 bit colour. + %--------------------------------------------------------------- + for i=1:length(rend), + ren = rend{i}.ren; + ren = repmat(ren, [ 1 1 3]); + % KND: + rgb=[]; + for j=1:length(rend{i}.data), + X = rend{i}.data{j}/(mxmx-mnmn)-mnmn; + col = Colors(j,:); + if isempty(rgb) + rgb=zeros([size(X) 3]); + end + rgb = rgb + repmat(reshape(col, [1,1,3]), size(X)).*repmat(X, [1 1 3]); + end + rgb= rgb*1.3+0.8*ren; + % rgb = rgb.*ren + ren.*repmat(all(rgb==0,3), [1 1 3]); + rgb=min(rgb,1); + rgb=max(rgb,0); + + % X = cell(3,1); + % for j=1:length(rend{i}.data), + % X{j} = rend{i}.data{j}/(mxmx-mnmn)-mnmn; + % end + % for j=(length(rend{i}.data)+1):3 + % X{j}=zeros(size(X{1})); + % end + % rgb = zeros([size(ren) 3]); + % tmp = ren.*max(1-X{1}-X{2}-X{3},0); + % rgb(:,:,1) = tmp + X{1}; + % rgb(:,:,2) = tmp + X{2}; + % rgb(:,:,3) = tmp + X{3}; + + ax=axes('Parent',Fgraph,'units','normalized',... + 'Position',[rem(i-1,2)*0.5, floor((i-1)/2)*hght/nrow, 0.5, hght/nrow],... + 'Visible','off'); + image(rgb,'Parent',ax); + set(ax,'DataAspectRatio',[1 1 1], ... + 'PlotBoxAspectRatioMode','auto',... + 'YTick',[],'XTick',[],... + 'XDir','normal','YDir','normal'); + drawnow + + end; +end; + +spm('Pointer') +if nargout>0 + varargout={rend,mxmx,mnmn}; +end +return; + diff --git a/spm/spm_sample_vol.m b/spm/spm_sample_vol.m new file mode 100644 index 0000000..e69de29 diff --git a/spm/spm_sections_colin27.m b/spm/spm_sections_colin27.m new file mode 100644 index 0000000..7349085 --- /dev/null +++ b/spm/spm_sections_colin27.m @@ -0,0 +1,30 @@ +function spm_sections(SPM,hReg) +% rendering of regional effects [SPM{Z}] on orthogonal sections of +% colin27's brain +% FORMAT spm_sections(SPM,hReg) +% +% SPM - xSPM structure containing details of excursion set +% hReg - handle of MIP register +% +% see spm_getSPM for details +%_______________________________________________________________________ +% +% spm_sections is called by spm_results and uses variables in SPM and +% VOL to create three orthogonal sections though a background image. +% Regional foci from the selected SPM are rendered on this image. +% +%_______________________________________________________________________ +% @(#)spm_sections.m 2.14 John Ashburner 02/09/05 + +Fgraph = spm_figure('FindWin','Graphics'); +%spms = spm_get(1,'IMAGE','select image for rendering on'); +spms= fullfile(spm('dir'), 'canonical', 'single_subj_T1.mnc'); +spm_results_ui('Clear',Fgraph); +spm_orthviews('Reset'); +global st +st.Space = spm_matrix([0 0 0 0 0 -pi/2])*st.Space; +spm_orthviews('Image',spms,[0.05 0.05 0.9 0.45]); +spm_orthviews MaxBB; +spm_orthviews('register',hReg); +spm_orthviews('addblobs',1,SPM.XYZ,SPM.Z,SPM.M); +spm_orthviews('Redraw'); diff --git a/spm/spm_update.m b/spm/spm_update.m new file mode 100644 index 0000000..d95869d --- /dev/null +++ b/spm/spm_update.m @@ -0,0 +1,69 @@ +function spm_update(update) +% Check (and install) SPM8 updates from the FIL FTP server +% FORMAT spm_update +% This function will connect itself to the FIL FTP server, compare the +% version number of the updates with the one of the SPM installation +% currently in the MATLAB path and will display the outcome. +% +% FORMAT spm_update(update) +% Invoking this function with any input parameter will do the same as +% above but will also attempt to download and install the updates. +%__________________________________________________________________________ +% Copyright (C) 2010-2013 Wellcome Trust Centre for Neuroimaging + +% Guillaume Flandin +% $Id: spm_update.m 5235 2013-02-04 15:30:21Z guillaume $ + +url = 'ftp://ftp.fil.ion.ucl.ac.uk/spm/spm8_updates/'; + +if ~nargin + update = false; +else + update = true; +end + +[s,sts] = urlread(url); +if ~sts, error('Cannot access the FIL FTP server.'); end +n = regexp(s,'spm8_updates_r(\d.*?)\.zip','tokens','once'); +if isempty(n) + fprintf(' There are no updates available yet.\n'); + return; +else + n = str2double(n{1}); +end + +try + [v,r] = spm('Ver','',1); r = str2double(r); +catch + error('SPM cannot be found in MATLAB path.'); +end +if ~strcmp(v,'SPM8'), error('Your SPM version is %s and not SPM8',v); end +rs = [3042 3164 3408 3684 4010 4290 4667 5236]; +if isnan(r), r = rs(1); end +if floor(r) == 8 + try + r = rs(round((r-floor(r))*10)+1); + catch + r = rs(end); + end +end + +if n > r + fprintf(' A new version of SPM is available on:\n'); + fprintf(' %s\n',url); + fprintf(' (Your version: %d - New version: %d)\n',r,n); + + if update + d = spm('Dir'); + delete(get(0,'Children')); spm('clean'); evalc('spm_rmpath'); + try + s = unzip([url sprintf('spm8_updates_r%d.zip',n)], d); + fprintf(' %d files have been updated.\n',numel(s)); + catch + fprintf(' Update failed: check file permissions.\n'); + end + addpath(d); + end +else + fprintf(' Your version of SPM is up to date.\n'); +end diff --git a/spm2.m b/spm2.m new file mode 100644 index 0000000..e652f86 --- /dev/null +++ b/spm2.m @@ -0,0 +1,3 @@ +function [spm2path]=spm2(varargin) +% spm2() - starts SPM2 on Karim's computers +myspm('ver','spm2','do','run', varargin{:}) \ No newline at end of file diff --git a/spm5.m b/spm5.m new file mode 100644 index 0000000..dc2c4d5 --- /dev/null +++ b/spm5.m @@ -0,0 +1,3 @@ +function [spmpath]=spm5(varargin) +% spm2() - starts SPM5 on Karim's computers +spmpath = myspm('ver','spm5','do','run', varargin{:}); diff --git a/spm_defaults.m b/spm_defaults.m new file mode 100644 index 0000000..0b3b75d --- /dev/null +++ b/spm_defaults.m @@ -0,0 +1,126 @@ + +% Sets the defaults which are used by SPM +% +% FORMAT spm_defaults +%_______________________________________________________________________ +% +% This file is intended to be customised for the site. +% Individual users can make copies which can be stored in their own +% matlab subdirectories. If ~/matlab is ahead of the SPM directory +% in the MATLABPATH, then the users own personal defaults are used. +% +% Care must be taken when modifying this file +%_______________________________________________________________________ +% @(#)spm_defaults.m 2.23 John Ashburner, Andrew Holmes 03/04/16 + +global defaults + +% Misc +%======================================================================= +defaults.grid = 0.4; +defaults.cmdline = 0; +defaults.logfile = ''; +defaults.printstr = [];%spm_figure('DefPrintCmd'),'spm2.ps']; + +% File format specific +%======================================================================= +defaults.analyze.multivol = 0; +defaults.analyze.flip = 1; % <<= Very important. Relates to L/R +% Changed by KND: +% defaults.analyze.flip = 0; % <<= Very important. Relates to L/R +% but now in the CMU it's 1... + +% Stats defaults +%======================================================================= +defaults.stats.maxmem = 2^20; +defaults.stats.maxres = 64; +defaults.stats.fmri.ufp = 0.001; +defaults.stats.fmri.t = 16; +defaults.stats.fmri.t0 = 8; +defaults.stats.pet.ufp = 0.05; + +defaults.stats.fmri.t0 = 1; + +defaults.modality = 'FMRI'; + + +% Mask defaults +%======================================================================= +defaults.mask.thresh = 0.8; + +% Realignment defaults +%======================================================================= +defaults.realign.estimate.quality = 0.75; +defaults.realign.estimate.weight = 0; +defaults.realign.estimate.interp = 2; +defaults.realign.estimate.wrap = [0 0 0]; +defaults.realign.write.mask = 1; +defaults.realign.write.interp = 4; +defaults.realign.write.wrap = [0 0 0]; + +% Unwarp defaults +%======================================================================= +defaults.unwarp.estimate.fwhm = 4; +defaults.unwarp.estimate.basfcn = [10 10]; +defaults.unwarp.estimate.regorder= 1; +defaults.unwarp.estimate.regwgt = 1e5; +defaults.unwarp.estimate.soe = 1; +defaults.unwarp.estimate.rem = 1; +defaults.unwarp.estimate.jm = 0; +defaults.unwarp.estimate.noi = 5; +defaults.unwarp.estimate.expround= 'Average'; +% +% Unwarp uses defaults.realign.write +% defaults for writing. +% + +% Coregistration defaults +%======================================================================= +defaults.coreg.estimate.cost_fun = 'nmi'; +defaults.coreg.estimate.sep = [4 2]; +defaults.coreg.estimate.tol = [0.02 0.02 0.02 0.001 0.001 0.001 0.01 0.01 0.01 0.001 0.001 0.001]; +defaults.coreg.estimate.fwhm = [7 7]; +defaults.coreg.write.interp = 1; +defaults.coreg.write.wrap = [0 0 0]; +defaults.coreg.write.mask = 0; + +% Spatial Normalisation defaults +%======================================================================= +defaults.normalise.estimate.smosrc = 8; +defaults.normalise.estimate.smoref = 0; +defaults.normalise.estimate.regtype = 'mni'; +defaults.normalise.estimate.weight = ''; +defaults.normalise.estimate.cutoff = 25; +defaults.normalise.estimate.nits = 16; +defaults.normalise.estimate.reg = 1; +defaults.normalise.estimate.wtsrc = 0; +defaults.normalise.write.preserve = 0; +defaults.normalise.write.bb = [[-78 -112 -50];[78 76 85]]; +%defaults.normalise.write.vox = [2 2 2]; +defaults.normalise.write.vox = [3 3 3]; +defaults.normalise.write.interp = 1; +defaults.normalise.write.wrap = [0 0 0]; + +% Segmentation defaults +%======================================================================= +defaults.segment.estimate.priors = str2mat(... + fullfile(spm('Dir'),'apriori','gray.mnc'),... + fullfile(spm('Dir'),'apriori','white.mnc'),... + fullfile(spm('Dir'),'apriori','csf.mnc')); +defaults.segment.estimate.reg = 0.01; +defaults.segment.estimate.cutoff = 30; +defaults.segment.estimate.samp = 3; +defaults.segment.estimate.bb = [[-88 88]' [-122 86]' [-60 95]']; +defaults.segment.estimate.affreg.smosrc = 8; +defaults.segment.estimate.affreg.regtype = 'mni'; +%defaults.segment.estimate.affreg.weight = fullfile(spm('Dir'),'apriori','brainmask.mnc'); +defaults.segment.estimate.affreg.weight = ''; +defaults.segment.write.cleanup = 1; +defaults.segment.write.wrt_cor = 1; + +% Bias field estimation defaults +%======================================================================= +defaults.bias.nbins = 256; % Number of histogram bins +defaults.bias.reg = 0.01; % Regularisation +defaults.bias.cutoff = 30; % DCT frequency cutoff (mm) + diff --git a/spm_mm2vox.m b/spm_mm2vox.m new file mode 100644 index 0000000..bc96eb8 --- /dev/null +++ b/spm_mm2vox.m @@ -0,0 +1,52 @@ +function M = spm_mm2vox(V,XYZmm,dim) +%SPM_MM2VOX - Retrieve coordinates in voxels from millimeters +% [M] = spm_mm2vox(V,XYZmm,dim) +% +% Example +% >> spm_mm2vox +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2008 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2008-06-12 Creation +% +% ----------------------------- Script History --------------------------------- +if nargin<3 + dim=find(size(XYZmm) == 3); + if length(dim)~=1 + error('XYZmm must be a 3 by N matrix') + end +end +if ischar(V) + if exist(V,'file') + V=spm_vol(V); + end +end +if isstruct(V) + if isfield(V, 'mat') + V=V.mat; + end + if isfield(V, 'MAT') + V=V.MAT; + end +end +MAT=V; + +s = size(XYZmm); +if s(dim) ~= 3 + error('The %d-th dimension of XYZmm should be 3', dim) +end + +XYZmm = nd2array(XYZmm,dim); +XYZmm = [XYZmm ; ones(1, size(XYZmm,2))]; + +M = inv(MAT)*XYZmm; +M = M(1:3,:); +M = nd2array(M,-dim,s); diff --git a/spm_render2.m b/spm_render2.m new file mode 100644 index 0000000..0cd4650 --- /dev/null +++ b/spm_render2.m @@ -0,0 +1,278 @@ +function [varargout]=spm_render2(dat,brt,rendfile) +% Render blobs on surface of a 'standard' brain +% FORMAT spm_render(dat,brt,rendfile) +% +% dat - a vertical cell array of length 1 to 3 +% - each element is a structure containing: +% - XYZ - the x, y & z coordinates of the transformed t values. +% in units of voxels. +% - t - the SPM{.} values +% - mat - affine matrix mapping from XYZ voxels to Talairach. +% - dim - dimensions of volume from which XYZ is drawn. +% brt - brightness control: +% If NaN, then displays using the old style with hot +% metal for the blobs, and grey for the brain. +% Otherwise, it is used as a ``gamma correction'' to +% optionally brighten the blobs up a little. +% rendfile - the file containing the images to render on to. See also +% spm_xbrain.m. +% +% Without arguments, spm_render acts as its own UI. +%_______________________________________________________________________ +% +% spm_render prompts for details of up to three SPM{Z}s or SPM{t}s that +% are then displayed superimposed on the surface of a standard brain. +% +% The first is shown in red, then green then blue. +% +% The blobs which are displayed are the integral of all transformed t +% values, exponentially decayed according to their depth. Voxels that +% are 10mm behind the surface have half the intensity of ones at the +% surface. +%_______________________________________________________________________ +% @(#)spm_render.m 2.19 John Ashburner FIL 02/10/29 + +%-Parse arguments, get data if not passed as parameters +%======================================================================= +if nargin < 1 + SPMid = spm('FnBanner',mfilename,'2.19'); + [Finter,Fgraph,CmdLine] = spm('FnUIsetup','Results: render',0); + + num = spm_input('Number of sets',1,'1 set|2 sets|3 sets',[1 2 3]); + + for i = 1:num, + [SPM,VOL] = spm_getSPM; + dat(i) = struct( 'XYZ', VOL.XYZ,... + 't', VOL.Z',... + 'mat', VOL.M,... + 'dim', VOL.DIM); + end; + showbar = 1; +else, + num = length(dat); + showbar = 0; +end; + +showbar=1 + +% get brightness +%----------------------------------------------------------------------- +if nargin < 2, + brt = 1; + if num==1, + brt = spm_input('Style',1,'new|old',[1 NaN], 1); + end; + if finite(brt), + brt = spm_input('Brighten blobs',1,'none|slightly|more|lots',[1 0.75 0.5 0.25], 1); + end; +end; + +if isstruct(dat) & isfield(dat, 'rend') + rend=dat.rend; + mxmx=dat.mxmx; + mnmn=dat.mnmn; + clear dat; +else + + % get surface + %----------------------------------------------------------------------- + if nargin < 3, + rendfile = fullfile(spm('Dir'),'rend', 'render_single_subj.mat') + % rendfile = spm_get(1,'render*.mat','Render file',fullfile(spm('Dir'),'rend')); + end; + + % Perform the rendering + %======================================================================= + spm('Pointer','Watch') + load(rendfile); + + if (exist('rend') ~= 1), % Assume old format... + rend = cell(size(Matrixes,1),1); + for i=1:size(Matrixes,1), + rend{i}=struct('M',eval(Matrixes(i,:)),... + 'ren',eval(Rens(i,:)),... + 'dep',eval(Depths(i,:))); + rend{i}.ren = rend{i}.ren/max(max(rend{i}.ren)); + end; + end; + + if showbar, spm_progress_bar('Init', size(dat,1)*length(rend),... + 'Formatting Renderings', 'Number completed'); end; + for i=1:length(rend), + rend{i}.max=0; + rend{i}.data = cell(size(dat,1),1); + if issparse(rend{i}.ren), + % Assume that images have been DCT compressed + % - the SPM99 distribution was originally too big. + d = size(rend{i}.ren); + B1 = spm_dctmtx(d(1),d(1)); + B2 = spm_dctmtx(d(2),d(2)); + rend{i}.ren = B1*rend{i}.ren*B2'; + % the depths did not compress so well with + % a straight DCT - therefore it was modified slightly + rend{i}.dep = exp(B1*rend{i}.dep*B2')-1; + end; + msk = find(rend{i}.ren>1);rend{i}.ren(msk)=1; + msk = find(rend{i}.ren<0);rend{i}.ren(msk)=0; + if showbar, spm_progress_bar('Set', i); end; + end; + if showbar, spm_progress_bar('Clear'); end; + + if showbar, spm_progress_bar('Init', length(dat)*length(rend),... + 'Making pictures', 'Number completed'); end; + + mx = zeros(length(rend),1)+eps; + mn = zeros(length(rend),1); + + for j=1:length(dat), + XYZ = dat(j).XYZ; + t = dat(j).t; + dim = dat(j).dim; + mat = dat(j).mat; + + for i=1:length(rend), + + % transform from Taliarach space to space of the rendered image + %------------------------------------------------------- + M1 = rend{i}.M*dat(j).mat; + zm = sum(M1(1:2,1:3).^2,2).^(-1/2); + M2 = diag([zm' 1 1]); + M = M2*M1; + cor = [1 1 1 ; dim(1) 1 1 ; 1 dim(2) 1; dim(1) dim(2) 1 ; + 1 1 dim(3) ; dim(1) 1 dim(3) ; 1 dim(2) dim(3); dim(1) dim(2) dim(3)]'; + tcor= M(1:3,1:3)*cor + M(1:3,4)*ones(1,8); + off = min(tcor(1:2,:)'); + M2 = spm_matrix(-off+1)*M2; + M = M2*M1; + xyz = (M(1:3,1:3)*XYZ + M(1:3,4)*ones(1,size(XYZ,2))); + d2 = ceil(max(xyz(1:2,:)')); + + % calculate 'depth' of values + %------------------------------------------------------- + dep = spm_slice_vol(rend{i}.dep,spm_matrix([0 0 1])*inv(M2),d2,1); + z1 = dep(round(xyz(1,:))+round(xyz(2,:)-1)*size(dep,1)); + + if ~finite(brt), msk = find(xyz(3,:) < (z1+20) & xyz(3,:) > (z1-5)); + else, msk = find(xyz(3,:) < (z1+60) & xyz(3,:) > (z1-5)); end; + + if ~isempty(msk), + + % generate an image of the integral of the blob values. + %----------------------------------------------- + xyz = xyz(:,msk); + if ~finite(brt), t0 = t(msk); + else, dst = xyz(3,:) - z1(msk); + dst = max(dst,0); + t0 = t(msk).*exp((log(0.5)/10)*dst)'; + end; + X0 = full(sparse(round(xyz(1,:)), round(xyz(2,:)), t0, d2(1), d2(2))); + hld = 1; if ~finite(brt), hld = 0; end; + X = spm_slice_vol(X0,spm_matrix([0 0 1])*M2,size(rend{i}.dep),hld); + msk = find(X<0); + X(msk) = 0; + else, + X = zeros(size(rend{i}.dep)); + end; + + % Brighten the blobs + if finite(brt), X = X.^brt; end; + + mx(j) = max([mx(j) max(max(X))]); + mn(j) = min([mn(j) min(min(X))]); + + rend{i}.data{j} = X; + + if showbar, spm_progress_bar('Set', i+(j-1)*length(rend)); end; + end; + end; + + mxmx = max(mx); + mnmn = min(mn); + +end + +if showbar, spm_progress_bar('Clear'); end; +Fgraph = spm_figure('GetWin','Graphics'); +spm_results_ui('Clear',Fgraph); + +nrow = ceil(length(rend)/2); +if showbar, hght = 0.95; else, hght = 0.5; end; +% subplot('Position',[0, 0, 1, hght]); +ax=axes('Parent',Fgraph,'units','normalized','Position',[0, 0, 1, hght],'Visible','off'); +image(0,'Parent',ax); +set(ax,'YTick',[],'XTick',[]); + +if ~finite(brt), + % Old style split colourmap display. + %--------------------------------------------------------------- + load Split; + colormap(split); + for i=1:length(rend), + ren = rend{i}.ren; + X = (rend{i}.data{1}-mnmn)/(mxmx-mnmn); + msk = find(X); + ren(msk) = X(msk)+(1+1.51/64); + ax=axes('Parent',Fgraph,'units','normalized',... + 'Position',[rem(i-1,2)*0.5, floor((i-1)/2)*hght/nrow, 0.5, hght/nrow],... + 'Visible','off'); + image(ren*64,'Parent',ax); + set(ax,'DataAspectRatio',[1 1 1], ... + 'PlotBoxAspectRatioMode','auto',... + 'YTick',[],'XTick',[],'XDir','normal','YDir','normal'); + end; +else, + % Colors=[1 0 0; 1 .7 0 ]; + Colors=[1 0 1; 1 .7 0 ]; + % Combine the brain surface renderings with the blobs, and display using + % 24 bit colour. + %--------------------------------------------------------------- + for i=1:length(rend), + ren = rend{i}.ren; + ren = repmat(ren, [ 1 1 3]); + % KND: + rgb=[]; + for j=1:length(rend{i}.data), + X = rend{i}.data{j}/(mxmx-mnmn)-mnmn; + col = Colors(j,:); + if isempty(rgb) + rgb=zeros([size(X) 3]); + end + rgb = rgb + repmat(reshape(col, [1,1,3]), size(X)).*repmat(X, [1 1 3]); + end + rgb= rgb*1.3+0.8*ren; + % rgb = rgb.*ren + ren.*repmat(all(rgb==0,3), [1 1 3]); + rgb=min(rgb,1); + rgb=max(rgb,0); + + % X = cell(3,1); + % for j=1:length(rend{i}.data), + % X{j} = rend{i}.data{j}/(mxmx-mnmn)-mnmn; + % end + % for j=(length(rend{i}.data)+1):3 + % X{j}=zeros(size(X{1})); + % end + % rgb = zeros([size(ren) 3]); + % tmp = ren.*max(1-X{1}-X{2}-X{3},0); + % rgb(:,:,1) = tmp + X{1}; + % rgb(:,:,2) = tmp + X{2}; + % rgb(:,:,3) = tmp + X{3}; + + ax=axes('Parent',Fgraph,'units','normalized',... + 'Position',[rem(i-1,2)*0.5, floor((i-1)/2)*hght/nrow, 0.5, hght/nrow],... + 'Visible','off'); + image(rgb,'Parent',ax); + set(ax,'DataAspectRatio',[1 1 1], ... + 'PlotBoxAspectRatioMode','auto',... + 'YTick',[],'XTick',[],... + 'XDir','normal','YDir','normal'); + drawnow + + end; +end; + +spm('Pointer') +if nargout>0 + varargout={rend,mxmx,mnmn}; +end +return; + diff --git a/spm_sections_colin27.m b/spm_sections_colin27.m new file mode 100644 index 0000000..7349085 --- /dev/null +++ b/spm_sections_colin27.m @@ -0,0 +1,30 @@ +function spm_sections(SPM,hReg) +% rendering of regional effects [SPM{Z}] on orthogonal sections of +% colin27's brain +% FORMAT spm_sections(SPM,hReg) +% +% SPM - xSPM structure containing details of excursion set +% hReg - handle of MIP register +% +% see spm_getSPM for details +%_______________________________________________________________________ +% +% spm_sections is called by spm_results and uses variables in SPM and +% VOL to create three orthogonal sections though a background image. +% Regional foci from the selected SPM are rendered on this image. +% +%_______________________________________________________________________ +% @(#)spm_sections.m 2.14 John Ashburner 02/09/05 + +Fgraph = spm_figure('FindWin','Graphics'); +%spms = spm_get(1,'IMAGE','select image for rendering on'); +spms= fullfile(spm('dir'), 'canonical', 'single_subj_T1.mnc'); +spm_results_ui('Clear',Fgraph); +spm_orthviews('Reset'); +global st +st.Space = spm_matrix([0 0 0 0 0 -pi/2])*st.Space; +spm_orthviews('Image',spms,[0.05 0.05 0.9 0.45]); +spm_orthviews MaxBB; +spm_orthviews('register',hReg); +spm_orthviews('addblobs',1,SPM.XYZ,SPM.Z,SPM.M); +spm_orthviews('Redraw'); diff --git a/sprintf2.m b/sprintf2.m new file mode 100644 index 0000000..9f4fb87 --- /dev/null +++ b/sprintf2.m @@ -0,0 +1,86 @@ +function t = sprintf2(varargin) +%SPRINTF2 - Intelligent sprintf +% S = sprintf2(A) tries to guess what would be the best format for A +% +% S = sprintf2(FORMAT, A, ...) behaves like the native SPRINTF +% +% Example +% >> sprintf2(pi) +% +% See also: sprintf, fprintf + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2010 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2010-04-01 Creation +% +% ----------------------------- Script History --------------------------------- + +if nargin>1 + s= sprintf(varargin{:}); + return +end +y=varargin{1}; +t=''; +if isempty(y) + return +end + brakets = '[]'; +if numel(y)>1 & ~ischar(y) + t = [brakets(1)]; + if isvector(y) && numel(y)<100 || (iscell(y) && size(y,1)<100) + if size(y,1)==1 + sep = ','; + elseif iscell(y) + sep = sprintf('\t'); + else + sep = ';'; + end + if iscell(y) + y=transpose(y); + end + for i=1:numel(y) + if i>1 + t = [t sep]; + end + if iscell(y) + yy=y{i}; + if mod(i+1,size(y,1))==0 + t=[t sprintf('\n')]; + end + else + yy= y(i); + end + t = [t sprintf2(yy)]; + end + else + t = [ t '...' ]; + end + t = [t brakets(end)]; + return +end + + +if isnumeric(y) + t = sprintf('%g',y); +elseif ischar(y) + t = sprintf('%s',y); +elseif islogical(y) + if y + t = sprintf('true'); + else + t = sprintf('false'); + end +elseif iscell(y) + brakets ='{}'; + t = [ brakets(1) sprintf2(y{1}) brakets(end) ]; +elseif isa(y,'function_handle') + t = char(y); +else + t = sprintf('%s','???'); +end \ No newline at end of file diff --git a/sprintfcell.m b/sprintfcell.m new file mode 100644 index 0000000..7c982b7 --- /dev/null +++ b/sprintfcell.m @@ -0,0 +1,42 @@ +function S = sprintf2cell(F,varargin) +%SPRINTF2CELL - sprintf to separate cells +% [] = sprintf2cell(input) +% +% Example +% >> sprintf2cell +% +% See also: + +% Author: K. N'Diaye (kndiaye01gmail.com) +% Copyright (C) 2010 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2010-10-05 Creation +% +% ----------------------------- Script History --------------------------------- + +S = {sprintf(F)}; +if nargin>1 + sz = cellfun('prodofsize',varargin); + u = unique(sz(sz~= 1)); + if numel(u) > 1 + error('arguments should be of the same size') + end + for i=1:u + A = {}; + for j = 1:(nargin-1) + if iscell(varargin{j}) + end + if sz(j)==1 + A{end+1} = varargin{j}; + else + A{end+1} = varargin{j}(i); + end + end + S{i,1} = sprintf(F,A{:}); + end +end \ No newline at end of file diff --git a/squeeze2.m b/squeeze2.m new file mode 100644 index 0000000..005f52c --- /dev/null +++ b/squeeze2.m @@ -0,0 +1,24 @@ +function b = squeeze(a,dims) +%SQUEEZE Remove singleton dimensions & squeeze some others +% B = SQUEEZE(A) behaves as the native SQUEEZE() +% B = SQUEEZE(A, [d1 d2 ... ]) reshapes so that dimensions d1, d2 etc. +% are merged into a single one +% +% For example, +% squeeze(rand(2,1,3)) +% is 2-by-3. +% +% See also SHIFTDIM, SQUEEZE. + +if nargin==1 + b=squeeze(a); +end + +if ndims(a)>2, + siz = size(a); + siz(siz==1) = []; % Remove singleton dimensions. + siz = [siz ones(1,2-length(siz))]; % Make sure siz is at least 2-D + b = reshape(a,siz); +else + b = a; +end diff --git a/ssetfield.m b/ssetfield.m new file mode 100644 index 0000000..9c3dc7a --- /dev/null +++ b/ssetfield.m @@ -0,0 +1,74 @@ +function [s]=ssetfield(s,varargin); +%SSETFIELD - Special SETFIELD for "field.subfield" structures +% S = SSETFIELD(S,'f.sf',V) sets the contents of the specified +% subfield "sf" of a given field "f" to the value V. +% This would be equivalent to the syntax: S.field.subfield = V. +% But it creates field "f" when missing. +% S must be a 1-by-1 structure. The changed structure is returned. + +% KND : 2005-07-06 : Adapted from SETFIELD. + +% NOT YET : +% +% S = SETFIELD(S,{i,j},'field.subfield.subsubfield',{k},{p},{q},V) +% is equivalent to the syntax: +% S(i,j).field(k).subfield(p).subsubfield(q) = V; +% Field references are passed as strings. +% +% See: SETFIELD + +% Check for sufficient inputs +if (isempty(varargin) | length(varargin) < 2) + error('KND:SSETFIELD:InsufficientInputs', 'Not enough input arguments.'); +end + +% The most common case +arglen = length(varargin); +strField = varargin{1}; +if isempty(strField) + error('KND:SSETFIELD:EmptyFieldname', 'Empty field name.'); +end +if (arglen==2) + if ~isempty(findstr('.', strField)) + [f,sf]=strtok(strField,'.'); + s1=ssetfield([], sf(2:end), varargin{end}); + s=mergestructs(s,setfield([], f, s1)); + else + s.(deblank(strField)) = varargin{end}; + %warning('MATLAB:SETFIELD:DeprecatedFunction:SingleInput', ... + % sprintf(['SETFIELD is deprecated. Please use: \n' ... + % 's.(fieldname) instead of setfield(s,fieldname)'])); + end + return +end +error('KND:SSETFIELD:WrongNumberOfInputs', 'Only 3 inputs are allowed.'); +return + + +% The following needs to bee adapted for recursion into fields & +% subfields. + +subs = varargin(1:end-1); +for i = 1:arglen-1 + index = varargin{i}; + if (isa(index, 'cell')) + types{i} = '()'; + elseif isstr(index) + types{i} = '.'; + subs{i} = deblank(index); % deblank field name + else + error('MATLAB:SETFIELD:DeprecatedFunction:InvalidType','Inputs must be either cell arrays or strings.'); + end +end + +% Perform assignment +try + s = builtin('subsasgn', s, struct('type',types,'subs',subs), varargin{end}); + %warning('MATLAB:SETFIELD:DeprecatedFunction:MultiInput', ... + % sprintf(['SETFIELD is deprecated. Please use: \n' ... + % 's(i,j).(fieldname)(k) instead of setfield(s,{i,j},fieldname,{k}) OR \n' ... + % 's(i,j).(field1).(field2) instead of setfield(s,{i,j},field1,field2)'])); +catch + error('MATLAB:SETFIELD:DeprecatedFunction', lasterr) +end + diff --git a/sstruct.m b/sstruct.m new file mode 100644 index 0000000..8291b73 --- /dev/null +++ b/sstruct.m @@ -0,0 +1,16 @@ +function [s]=sstruct(varargin) +%SSTRUCT - Special struct construction for dealing with fields & subfields +% [S]=SSTRUCT('field1',VALUES1,'field2.subfield1',VALUES2,...) creates +% a structure array with the specified fields, subfields and values. +% +% NB: Contrary to the native STRUCT, SSTRUCT doesn't create structure +% arrays from arrayed values. +% +% See also: sfield, issfield, sfieldnames +s=[]; +for i=1:2:length(varargin) + if ~ischar(varargin{i}) + error('KND:SSTRUCT:InvalidFieldname', 'Field names should be of type CHAR.'); + end + s=ssetfield(s, varargin{i}, varargin{i+1}); +end diff --git a/ssvep/flicker1.m b/ssvep/flicker1.m new file mode 100644 index 0000000..23cf7c0 --- /dev/null +++ b/ssvep/flicker1.m @@ -0,0 +1,50 @@ +% rotating hemifield flickering checkerboard +rcycles = 8; % number of white/black circle pairs +tcycles = 24; % number of white/black angular segment pairs (integer) +flicker_freq = 4; % full cycle flicker frequency (Hz) +flick_dur = 1/flicker_freq/2; +period = 30; % rotation period (sec) +[w, rect] = SCREEN('OpenWindow', 0, 128); +HideCursor +xc = rect(3)/2; +yc = rect(4)/2; +% make stimulus +hi_index=255; +lo_index=0; +bg_index =128; +xysize = rect(4); +s = xysize/sqrt(2); % size used for mask +xylim = 2*pi*rcycles; +[x,y] = meshgrid(-xylim:2*xylim/(xysize-1):xylim, - ... +xylim:2*xylim/(xysize-1):xylim); +at = atan2(y,x); +checks = ((1+sign(sin(at*tcycles)+eps) .* ... +sign(sin(sqrt(x.^2+y.^2))))/2) * (hi_index-lo_index) + lo_index; +circle = x.^2 + y.^2 <= xylim^2; +checks = circle .* checks + bg_index * ~circle; +t(1) = SCREEN('MakeTexture', w, checks); +t(2) = SCREEN('MakeTexture', w, hi_index - checks); % reversed contrast +flick = 1; +flick_time = 0; +start_time = GetSecs; +while (1) % animation loop +thetime = GetSecs - start_time; % time (sec) since loop started +if thetime > flick_time % time to reverse contrast? +flick_time = flick_time + flick_dur; % set next flicker time +flick = 3 - flick; +end +SCREEN('DrawTexture', w, t(flick)); +% draw mask +theta = 2*pi * mod(thetime, period)/period; +st = sin(theta); +ct = cos(theta); +xy = s * [-st,-ct; -st-ct,st-ct; st-ct,st+ct; st,ct] + ... +ones(4,1) * [xc yc]; +SCREEN('FillPoly', w, bg_index, xy); +SCREEN('Flip', w); +if KbCheck +break % exit loop upon key press +end +end +ShowCursor +SCREEN('Close',w); \ No newline at end of file diff --git a/staircase/CreateStaircase.m b/staircase/CreateStaircase.m new file mode 100755 index 0000000..4cceb0c --- /dev/null +++ b/staircase/CreateStaircase.m @@ -0,0 +1,37 @@ +function [staircase] = CreateStaircase(ptarget,xstart,sstart,dstart,chancefloor) + +if nargin < 5 + chancefloor = false; +end +if nargin < 4 + error('Missing input argument(s).'); +end + +staircase = struct; + +staircase.ptarget = ptarget; + +staircase.i = 1; +staircase.x = nan.*ones(1, 1000); +staircase.r = nan.*ones(1, 1000); + +staircase.scur = staircase.stepinfo(1,1); +staircase.dcur = staircase.stepinfo(2,1); +staircase.ncur = staircase.stepinfo(3,1); + +staircase.j = 0; + +staircase.x = nan(1,1000); staircase.x(1) = xstart; +staircase.r = nan(1,1000); +staircase.p = nan(1,1000); + +staircase.scur = sstart; +staircase.dcur = dstart; +staircase.wcur = 0; + +staircase.nstp = 0; +staircase.istp = nan(1,1000); + +staircase.chancefloor = chancefloor; + +end diff --git a/staircase/GetStaircaseResults.m b/staircase/GetStaircaseResults.m new file mode 100755 index 0000000..9bd806d --- /dev/null +++ b/staircase/GetStaircaseResults.m @@ -0,0 +1,45 @@ +function [results] = GetStaircaseResults(staircase) + +if nargin < 1 + error('Missing input argument.'); +end + +results = struct; + +results.ptarget = staircase.ptarget; + +results.n = staircase.i-1; + +results.x = staircase.x(1:results.n); +results.r = staircase.r(1:results.n); +results.p = staircase.p(1:results.n); + +results.nstp = length(find(staircase.istp <= results.n)); +results.istp = staircase.istp(1:results.nstp); + +results.nrev = staircase.nrev; +results.irev = staircase.irev(1:results.nrev); + +results.ndec = staircase.ndec; +results.idec = staircase.idec; +if isempty(icvg) + if isempty(results.idec) + icvg = []; + else + icvg = results.idec(end); + end +elseif icvg > results.n + icvg = nan; +end +results.icvg = icvg; +if ~isnan(icvg) + results.pc = mean(results.r(icvg:end)); + try + results.x0 = quantile(results.x(icvg:end), [0.25,0.5,0.75]); + catch + results.x0 =[NaN,median(results.x(icvg:end)),NaN]; + end +else + results.pc = nan; + results.x0 = [nan,nan,nan]; +end diff --git a/staircase/GetStaircaseThreshold.m b/staircase/GetStaircaseThreshold.m new file mode 100755 index 0000000..ffbbe06 --- /dev/null +++ b/staircase/GetStaircaseThreshold.m @@ -0,0 +1,23 @@ +function [results] = GetStaircaseThreshold(results,iout) + +if nargin < 2 + error('Missing input argument(s).'); +end + +iout = min(iout,length(results.istp)-1); +i1st = results.istp(iout)+1; + +xthres = 10^mean(log10(results.x(results.istp(iout+1:end)))); +pthres = []; + +rthres = results.r(i1st:end); +if all(ismember(rthres,[0 1])) + pthres = mean(rthres); +elseif all(ismember(rthres,[-2 -1 +1 +2])) + pthres = 0.5*(nnz(rthres == +1)/nnz(abs(rthres) == 1)+nnz(rthres == +2)/nnz(abs(rthres) == 2)); +end + +results.xthres = xthres; +results.pthres = pthres; + +end \ No newline at end of file diff --git a/staircase/GetStaircaseVariable.m b/staircase/GetStaircaseVariable.m new file mode 100755 index 0000000..2ba72e1 --- /dev/null +++ b/staircase/GetStaircaseVariable.m @@ -0,0 +1,7 @@ +function [x] = GetStaircaseVariable(staircase) + +if nargin < 1, error('Not enough input arguments.'); end + +x = staircase.x(staircase.i); + +end diff --git a/staircase/RefreshStaircase.m b/staircase/RefreshStaircase.m new file mode 100755 index 0000000..4f2a4fc --- /dev/null +++ b/staircase/RefreshStaircase.m @@ -0,0 +1,37 @@ +function [staircase] = RefreshStaircase(staircase) + +if nargin < 1 + error('missing input argument.'); +end + +if ~isnan(staircase.r(staircase.i)) + if ~mod(staircase.i-staircase.j,staircase.dcur) + staircase.nstp = staircase.nstp+1; + staircase.istp(staircase.nstp) = staircase.i; + rcur = staircase.r(staircase.i-staircase.dcur+1:staircase.i); + if all(ismember(rcur,[0,1])) + p = mean(rcur); + else + error('unable to refresh staircase.'); + end + if staircase.chancefloor + pcur = max(p,0.5); + else + pcur = p; + end + staircase.wcur = staircase.ptarget-pcur; + staircase.p(staircase.i) = p; + staircase.x(staircase.i+1) = staircase.x(staircase.i)*10^(staircase.wcur*staircase.scur); + else + staircase.p(staircase.i) = 0; + staircase.x(staircase.i+1) = staircase.x(staircase.i); + end + staircase.i = staircase.i+1; + if staircase.i == length(staircase.x) + staircase.x = [staircase.x,nan(1,staircase.i)]; + staircase.r = [staircase.r,nan(1,staircase.i)]; + staircase.p = [staircase.p,nan(1,staircase.i)]; + end +end + +end \ No newline at end of file diff --git a/staircase/SetStaircaseResponse.m b/staircase/SetStaircaseResponse.m new file mode 100755 index 0000000..882da21 --- /dev/null +++ b/staircase/SetStaircaseResponse.m @@ -0,0 +1,10 @@ +function [staircase] = SetStaircaseResponse(staircase,x,r) + +if nargin < 3 + error('Missing input argument(s).'); +end + +staircase.x(staircase.i) = x; +staircase.r(staircase.i) = r; + +end \ No newline at end of file diff --git a/staircase/ShowStaircaseResults.m b/staircase/ShowStaircaseResults.m new file mode 100644 index 0000000..352ede4 --- /dev/null +++ b/staircase/ShowStaircaseResults.m @@ -0,0 +1,20 @@ +function ShowStaircaseResults(results) + +if nargin < 1, error('Not enough input arguments.'); end + +figure; +hold on +ylim(10.^[floor(log10(min(results.x))),ceil(log10(max(results.x)))]); +if ~isnan(results.icvg) + area([results.icvg,results.n], results.x0(3)*[1,1], results.x0(1), 'FaceColor', [0.8,1.0,0.8], 'EdgeColor', 'none'); + plot([results.icvg,results.n], results.x0(2)*[1,1], 'g-'); + plot(results.icvg*[1,1], ylim, 'k-'); +end +plot(results.istp, results.x(results.istp), 'b-'); +plot(results.irev, results.x(results.irev), 'r.'); +plot(results.idec(1:results.ndec), results.x(results.idec(1:results.ndec)), 'ro'); +hold off +set(gca, 'Layer', 'top', 'YScale', 'lin', 'Box', 'on'); +xlabel('Trial index'); +ylabel('Psychophysical variable'); +title(sprintf('Psychophysical threshold = %.2g = 10^{%.2f}, probability correct = %.3f', results.x0(2), log10(results.x0(2)), results.pc)); diff --git a/staircase/UpdateStaircase.m b/staircase/UpdateStaircase.m new file mode 100755 index 0000000..6bf5a61 --- /dev/null +++ b/staircase/UpdateStaircase.m @@ -0,0 +1,20 @@ +function [staircase] = UpdateStaircase(staircase,xnew,snew,dnew) + +if nargin < 4 + error('Missing input argument(s).'); +end + +staircase = RefreshStaircase(staircase); +staircase.j = staircase.i-1; + +if ~isempty(xnew) + staircase.x(staircase.i) = xnew; +end +if ~isempty(snew) + staircase.scur = snew; +end +if ~isempty(dnew) + staircase.dcur = dnew; +end + +end \ No newline at end of file diff --git a/staircase/Weibull.m b/staircase/Weibull.m new file mode 100644 index 0000000..788e90c --- /dev/null +++ b/staircase/Weibull.m @@ -0,0 +1,13 @@ +function y = Weibull(p,x) +%y = Weibull(p,x) +% +%Parameters: p.b slope +% p.t threshold yeilding ~80% correct +% x intensity values. + +g = 0.5; %chance performance +e = (.5)^(1/3); %threshold performance ( ~80%) + +%here it is. +k = (-log( (1-e)/(1-g)))^(1/p.b); +y = 1- (1-g)*exp(- (k*x/p.t).^p.b); diff --git a/staircase/old/CreateStaircase.m b/staircase/old/CreateStaircase.m new file mode 100644 index 0000000..299bb3c --- /dev/null +++ b/staircase/old/CreateStaircase.m @@ -0,0 +1,39 @@ +function [staircase] = CreateStaircase(pctarget, stepinfo, xstart, pfdir) +% s = CreateStaircase(pctarget, stepinfo, xstart, pfdir) +% +% pctarget: +if nargin < 4, pfdir = +1; end +if nargin < 3, error('Not enough input arguments.'); end + +staircase = struct; + +staircase.pctarget = pctarget; +staircase.stepinfo = stepinfo; +staircase.pfdir = sign(pfdir); + +staircase.i = 1; +staircase.x = nan.*ones(1, 1000); +staircase.r = nan.*ones(1, 1000); + +staircase.scur = staircase.stepinfo(1,1); +staircase.dcur = staircase.stepinfo(2,1); +staircase.ncur = staircase.stepinfo(3,1); + +staircase.j = 0; +staircase.w = []; + +staircase.wcur = 0; +staircase.wold = 0; + +staircase.nstp = 0; +staircase.istp = nan.*ones(1, 1000); + +staircase.nrev = 0; +staircase.irev = nan.*ones(1, 1000); + +staircase.ndec = 0; +staircase.idec = nan.*ones(1, size(staircase.stepinfo, 2)-1); + +staircase.x(1) = xstart; + +end diff --git a/staircase/old/GetStaircaseResults.m b/staircase/old/GetStaircaseResults.m new file mode 100644 index 0000000..c1aca3c --- /dev/null +++ b/staircase/old/GetStaircaseResults.m @@ -0,0 +1,44 @@ +function [results] = GetStaircaseResults(staircase, icvg) + +if nargin < 2, icvg = []; end +if nargin < 1, error('Not enough input arguments.'); end + +results = struct; + +results.pctarget = staircase.pctarget; +results.stepinfo = staircase.stepinfo; +results.pfdir = staircase.pfdir; + +results.n = staircase.i-1; +results.x = staircase.x(1:results.n); +results.r = staircase.r(1:results.n); + +results.nstp = staircase.nstp; +results.istp = staircase.istp(1:results.nstp); + +results.nrev = staircase.nrev; +results.irev = staircase.irev(1:results.nrev); + +results.ndec = staircase.ndec; +results.idec = staircase.idec; +if isempty(icvg) + if isempty(results.idec) + icvg = []; + else + icvg = results.idec(end); + end +elseif icvg > results.n + icvg = nan; +end +results.icvg = icvg; +if ~isnan(icvg) + results.pc = mean(results.r(icvg:end)); + try + results.x0 = quantile(results.x(icvg:end), [0.25,0.5,0.75]); + catch + results.x0 =[NaN,median(results.x(icvg:end)),NaN]; + end +else + results.pc = nan; + results.x0 = [nan,nan,nan]; +end diff --git a/staircase/old/GetStaircaseVariable.m b/staircase/old/GetStaircaseVariable.m new file mode 100644 index 0000000..2ba72e1 --- /dev/null +++ b/staircase/old/GetStaircaseVariable.m @@ -0,0 +1,7 @@ +function [x] = GetStaircaseVariable(staircase) + +if nargin < 1, error('Not enough input arguments.'); end + +x = staircase.x(staircase.i); + +end diff --git a/staircase/old/ShowStaircaseResults.m b/staircase/old/ShowStaircaseResults.m new file mode 100644 index 0000000..352ede4 --- /dev/null +++ b/staircase/old/ShowStaircaseResults.m @@ -0,0 +1,20 @@ +function ShowStaircaseResults(results) + +if nargin < 1, error('Not enough input arguments.'); end + +figure; +hold on +ylim(10.^[floor(log10(min(results.x))),ceil(log10(max(results.x)))]); +if ~isnan(results.icvg) + area([results.icvg,results.n], results.x0(3)*[1,1], results.x0(1), 'FaceColor', [0.8,1.0,0.8], 'EdgeColor', 'none'); + plot([results.icvg,results.n], results.x0(2)*[1,1], 'g-'); + plot(results.icvg*[1,1], ylim, 'k-'); +end +plot(results.istp, results.x(results.istp), 'b-'); +plot(results.irev, results.x(results.irev), 'r.'); +plot(results.idec(1:results.ndec), results.x(results.idec(1:results.ndec)), 'ro'); +hold off +set(gca, 'Layer', 'top', 'YScale', 'lin', 'Box', 'on'); +xlabel('Trial index'); +ylabel('Psychophysical variable'); +title(sprintf('Psychophysical threshold = %.2g = 10^{%.2f}, probability correct = %.3f', results.x0(2), log10(results.x0(2)), results.pc)); diff --git a/staircase/old/UpdateStaircase.m b/staircase/old/UpdateStaircase.m new file mode 100644 index 0000000..ae12961 --- /dev/null +++ b/staircase/old/UpdateStaircase.m @@ -0,0 +1,63 @@ +function [staircase] = UpdateStaircase(staircase, r, x) +% sc2 = UpdateStaircase(sc1, accuracy) +% sc1: Staircase struct previously initialized +% accuracy = 0 (incorrect) or 1 (correct) +% Ouput: +% sc2: Updated staircase struct + +if nargin < 2, error('Not enough input arguments.'); end + +staircase.r(staircase.i) = r; +if nargin>2 + staircase.x(staircase.i) = x; +end + +if ~mod(staircase.i-staircase.j, staircase.dcur) + staircase.nstp = staircase.nstp+1; + staircase.istp(staircase.nstp) = staircase.i; + % Number of correct responses on the last 'dcur' trials (see stepinfo) + nc = sum(staircase.r(staircase.i-staircase.dcur+1:staircase.i)); + if isempty(staircase.w) + staircase.w = GetStaircaseWeights(staircase.pctarget, staircase.dcur); + end + % Get the weights for 'nc' correct responses + staircase.wcur = staircase.w(nc+1); + if abs(sign(staircase.wcur)-sign(staircase.wold)) > 1 + % We have a reversal at this trial... + staircase.nrev = staircase.nrev+1; + staircase.irev(staircase.nrev) = staircase.i; + % If # of reversals for this size of steps has been reached go to + % the next + if staircase.nrev == staircase.ncur + staircase.ndec = staircase.ndec+1; + staircase.idec(staircase.ndec) = staircase.i; + staircase.scur = staircase.stepinfo(1,staircase.ndec+1); + staircase.dcur = staircase.stepinfo(2,staircase.ndec+1); + staircase.ncur = staircase.ncur+staircase.stepinfo(3,staircase.ndec+1); + staircase.w = GetStaircaseWeights(staircase.pctarget, staircase.dcur); + staircase.j = staircase.i; + end + end + staircase.wold = staircase.wcur; + staircase.x(staircase.i+1) = staircase.x(staircase.i)*10^(staircase.pfdir*staircase.wcur*staircase.scur); +else + staircase.x(staircase.i+1) = staircase.x(staircase.i); +end + +staircase.i = staircase.i+1; + +if staircase.i == length(staircase.x) + staircase.x = [staircase.x,nan(1, staircase.i)]; + staircase.r = [staircase.r,nan(1, staircase.i)]; +end +if staircase.nrev == length(staircase.irev) + staircase.irev = [staircase.irev,nan(1, staircase.nrev)]; +end + +end + +function [w] = GetStaircaseWeights(pc, d) +b = factorial(d)./(factorial(0:+1:d).*factorial(d:-1:0)).*pc.^(0:+1:d).*(1-pc).^(d:-1:0); +w = pc-linspace(0, 1, d+1); +w = w./sum(b.*abs(w)); +end diff --git a/staircase/testStaircase.m b/staircase/testStaircase.m new file mode 100644 index 0000000..70d9a42 --- /dev/null +++ b/staircase/testStaircase.m @@ -0,0 +1,76 @@ +function sc=run +set(0,'DefaultFigureWindowStyle','docked') +%addpath('/Users/ndiaye/mtoolbox/matlab-washington') +p.t = .5; +p.b = 2; +example(p) +coh = @(x)10.^-x; +ptarget = .7; + +xstart=-log10(0.3); +sstart = .1; +dstart = 4; +chancefloor = 1; + +%sc= CreateStaircase(.7,[[0.1;10;4],[0.02;2;Inf]],x,-1); +sc= CreateStaircase(ptarget,xstart,sstart,dstart,chancefloor) + +Weibull(p,xstart) +n=300; +for i=1:n; + x=GetStaircaseVariable(sc); + r = randlength(DIRS) + error('No directory found for brainstorm3') +end +brainstorm \ No newline at end of file diff --git a/start_eeglab.m b/start_eeglab.m new file mode 100644 index 0000000..06980c8 --- /dev/null +++ b/start_eeglab.m @@ -0,0 +1,4 @@ +% starts eeglab +addpath(fullfile(fileparts(fileparts(mfilename('fullpath'))), 'mtoolbox', 'eeglab')); +addpath(fullfile(fileparts(fileparts(mfilename('fullpath'))), 'mtoolbox', 'eeglab2008October01_beta')); +eeglab diff --git a/start_spm.m b/start_spm.m new file mode 100644 index 0000000..06fcd75 --- /dev/null +++ b/start_spm.m @@ -0,0 +1,3 @@ +mypath +addpath(fullfile(HOMEDIR, 'mtoolbox', 'spm2'), '-BEGIN' ) +spm \ No newline at end of file diff --git a/startup.m b/startup.m new file mode 100644 index 0000000..21260ef --- /dev/null +++ b/startup.m @@ -0,0 +1,120 @@ +DoMatlabUpdate = false; +disp('Hi Karim,') +[hostname,hostname]=system('hostname'); +switch upper(deblank(hostname)) + case 'D5BDS81J' + disp('Welcome on FREDERIC''s PC!') + case 'IMAGERIE2-PV' + disp('Welcome on STEPHANIE''s PC!') + DoMatlabUpdate = 0; + case 'MONTBLANC' + disp('Welcome on the MONTBLANC Linux Blast @ LabNIC!') + case 'KARIMND' + + disp('Welcome on the LENA138/Windows station!') + homedir = 'E:/ndiaye/home'; + otherwise + if ~isempty(strfind(lower(hostname), 'chups.jussieu.fr')) + disp('Welcome at the COGIMAGE Lab!') + lcd=cd;cd('~');homedir=cd;cd(lcd); + switch strrep(hostname, '.chups.jussieu.fr', '') + case 'lena138.lena' + disp('Welcome on the COGIMAGE Lena138 station!') + %lcd=cd;cd('~');homedir=cd;cd(lcd); + end + end +end +disp(' ') + +[HOMEDIR]=mypath + +if not(exist(HOMEDIR, 'dir')) + warning(sprintf(['Directory %s does not exist!\n' ... + 'HOMEDIR variable unset'], HOMEDIR)) + clear HOMEDIR +else + disp(sprintf('HOMEDIR variable set to: %s', HOMEDIR)) + % addpath(fullfile(HOMEDIR, 'mtoolbox', 'lib')); + addpath(fullfile(HOMEDIR, 'matlab')); + %cd(fullfile(HOMEDIR, 'matlab')) +end + + +[USBDIR]=usbpath; + + +if isempty(USBDIR) || any(isnan(USBDIR)) + warning(sprintf(['No USB key found.\n' ... + 'USBDIR variable unset'])) + clear USBDIR +elseif not(exist(USBDIR, 'dir')) + warning(sprintf(['Directory %s does not exist!\n' ... + 'USBDIR variable unset'], USBDIR)) + clear USBDIR +else + disp(sprintf('USB key is: %s', USBDIR)) + % addpath(fullfile(USBDIR, 'mtoolbox', 'lib')); + addpath(fullfile(USBDIR, 'matlab')); + if DoMatlabUpdate + if exist('C:\Program Files\WinMerge') + if exist(HOMEDIR,'dir') && exist(USBDIR,'dir') + system(['"C:\Program Files\WinMerge\WinMerge.exe" /r /e /f "*.m" /x ' fullfile(HOMEDIR, 'matlab') ' ' fullfile(USBDIR, 'matlab')]) + end + end + end +end + +% try +% addpath 'C:\Program Files\MATLAB704\toolbox\matlab\datafun\' +% end + +% cd(fullfile(HOMEDIR, 'matlab')) + +if ~exist('DoMatlabUpdate','var') + DoMatlabUpdate = 1; +end + +% SVN update +switch upper(deblank(hostname)) + case 'D5BDS81J' + disp('SVU update...') + !svn update + !svn commit --message "Automatic update by startup.m" + case 'IMAGERIE2-PV' + disp('Welcome on STEPHANIE''s PC!') + case 'MONTBLANC' + disp('Welcome on the MONTBLANC Linux Blast!') + case 'LENA138.LENA.CHUPS.JUSSIEU.FR' + disp('SVN update...') + !svn update ~/matlab + !svn commit ~/matlab --message "Automatic update by startup.m" + case 'KARIMND' + disp('SVN update...') + !svn update . + !svn commit . --message "Automatic update by startup.m" +end + +if DoMatlabUpdate + try + matlabupdate + catch + warning('Couldn''t update your MATLAB scripts & functions...') + end +end +DoMatlabUpdate = 0; + +if exist('G:/mtoolbox/javaclasses') + javaaddpath G:/mtoolbox/javaclasses +end +if exist('g:\mtoolbox\Psychtoolbox\PsychJava') + javaaddpath g:\mtoolbox\Psychtoolbox\PsychJava +end +if exist('F:/mtoolbox/javaclasses') + javaaddpath F:/mtoolbox/javaclasses +end + +% warning('off','MATLAB:dispatcher:nameConflict') + +disp(' '); +disp(' '); + diff --git a/stat/alpha_significance.m b/stat/alpha_significance.m new file mode 100644 index 0000000..af49f18 --- /dev/null +++ b/stat/alpha_significance.m @@ -0,0 +1,18 @@ +function [sig,alpha]=alpha_significance(alpha,pv,alphas,pThresholds) +% [sig,alpha]=alpha_significance(alpha,pv,alphas, pThresholds) +% +% This function finds the significant value according to a given alpha with +% FWER specified by alphas and their corresponding thresholds +% +% Ex: +% >> [alphas,pThresholds,pv]=permttest_norm2(X1,X2); +% >> [sig,alpha]=alpha_significance(0.05,pv,alphas,pThresholds); +% +% In this example, you compare X1 and X2, sig will have 1 at any position +% where significativity is below 0.05 + +i=max(find(alphas?.50?for?factor?2? +% and?W?=?0.68,?p?>?.50?for?the?interaction;? +% this?test?cannot?be?performed?for?factor?1?since?it?has?only?two?levels +% but?we?generated?the?data?such?that?it?is?also?homogenous. +% The?Greenhouse?Geiser?and?the?Huynh?Feldt?epsilons?are?close?to?1?so?that +%?we?don?t?need?to?use?corrections?(Huynh,?1978,?Rouanet?and?Lepine,?1970). + +% Effect?name? SS?? dl?? MS?? F?? p<0.001 +% Factor?1?? 10621?? 1?? 10621?? 76.8? ***? +% Error?? 2073?? 15?? 135???? +% Factor?2?? 11784?? 4?? 8196?? 16.4? *** +% Error?? 4378?? 60? ?72.9???? +% Interaction??2250?? 4?? 562?? 6.52? ***? +% Error?? 5171?? 60?? 86.2???? +% Subject: F(1,?15)?=?710,?p?F) +% Residuals 4 349.13 87.28 +% +% Error: Subject:Task +% Df Sum Sq Mean Sq F value Pr(>F) +% Task 1 30.0000 30.0000 7.3469 0.05351 . +% Residuals 4 16.3333 4.0833 +% --- +% Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1 +% +% Error: Subject:Valence +% Df Sum Sq Mean Sq F value Pr(>F) +% Valence 2 9.8000 4.9000 1.4591 0.2883 +% Residuals 8 26.8667 3.3583 +% +% Error: Subject:Task:Valence +% Df Sum Sq Mean Sq F value Pr(>F) +% Task:Valence 2 1.4000 0.7000 0.2907 0.7553 +% Residuals 8 19.2667 2.4083 +% +% But to perform post-hoc we need to go through LME +% +% require(MASS) +% require(nlme) +% require(multcomp) +% lme.ex4 <- lme(Recall ~ Task + Valence, random = ~1 | Subject/(Task*Valence), data = data.example4) + + +a= [ 8 9 5 7 9 10 12 13 14 16 13 14 13 13 12 15 16 14 12 14 15 17 18 20 6 7 9 4 9 10 ]; +a = reshape(a,[3 2 5]) +myanova(a,2,1:2); + +%% http://blog.gribblelab.org/2009/03/09/repeated-measures-anova-using-r/ +% > dv <- c(1,3,4,2,2,3,2,5,6,3,4,4,3,5,6) +% > subject <- factor(c("s1","s1","s1","s2","s2","s2","s3","s3","s3", +% + "s4","s4","s4","s5","s5","s5")) +% > myfactor <- factor(c("f1","f2","f3","f1","f2","f3","f1","f2","f3", +% + "f1","f2","f3","f1","f2","f3")) +% > mydata <- data.frame(dv, subject, myfactor) +% +% Error: subject +% Df Sum Sq Mean Sq F value Pr(>F) +% Residuals 4 12.4 3.1 +% +% Error: subject:myfactor +% Df Sum Sq Mean Sq F value Pr(>F) +% myfactor 2 14.9333 7.4667 13.576 0.002683 ** +% Residuals 8 4.4000 0.5500 +% +% > require(nlme) +% > am2 <- lme(dv ~ myfactor, random = ~1|subject/myfactor, data=mydata) +% > anova(am2) +% numDF denDF F-value p-value +% (Intercept) 1 8 60.40869 0.0001 +% myfactor 2 8 13.57575 0.0027 +% +% > require(multcomp) +% > summary(glht(am2,linfct=mcp(myfactor="Tukey"))) +% Linear Hypotheses: +% Estimate Std. Error z value Pr(>|z|) +% f2 - f1 == 0 1.600 0.469 3.411 0.00185 ** +% f3 - f1 == 0 2.400 0.469 5.117 < 1e-04 *** +% f3 - f2 == 0 0.800 0.469 1.706 0.20308 +% +d = [ + 1 1 1 1 + 2 3 1 2 + 3 4 1 3 + 4 2 2 1 + 5 2 2 2 + 6 3 2 3 + 7 2 3 1 + 8 5 3 2 + 9 6 3 3 + 10 3 4 1 + 11 4 4 2 + 12 4 4 3 + 13 3 5 1 + 14 5 5 2 + 15 6 5 3 ] +X = reshape(d(:,2),[ 3 5 ]); +[p,F,fx,epsilon,df,dfe,SS,SSe,SSt]=myanova(X,1,1) + +%[Qc,HSDc] = tukeyhsd(dfe,ng,alpha,ns,MSe) + + +% [Qc,HSDc,Q,pairs,MX] = tukeyhsd(X,nf,alpha,fx,dfe,SSe) +[Qc,HSDc,Q,pairs,MX] = tukeyhsd(X,1,0.05) + + + +%% [R] Tukey HSD (or other post hoc tests) following repeated measures ANOVA +% https://stat.ethz.ch/pipermail/r-help/2008-May/163433.html +% +% ## Example +% require(MASS) ## for oats data set +% require(nlme) ## for lme() +% require(multcomp) ## for multiple comparison stuff +% +% Aov.mod <- aov(Y ~ N + V + Error(B/V), data = oats) +% Lme.mod <- lme(Y ~ N + V, random = ~1 | B/V, data = oats) +% +% summary(Aov.mod) +% anova(Lme.mod) +% +% summary(Lme.mod) +% summary(glht(Lme.mod, linfct=mcp(V="Tukey"))) +% +% E.g. with gribblelab data +% summary(glht(l, linfct=mcp(myfactor="Tukey"))) + + +%% http://yatani.jp/HCIstats/PostHoc +% Post-hoc Tests +% > aov <- aov(Value ~ factor(Group) + Error(factor(Participant)/factor(Group)), data) +% > summary(aov) +% Error: factor(Participant) +% Df Sum Sq Mean Sq F value Pr(>F) +% Residuals 7 5.1667 0.7381 +% Error: factor(Participant):factor(Group) +% Df Sum Sq Mean Sq F value Pr(>F) +% factor(Group) 2 22.7500 11.3750 10.92 0.001388 ** +% Residuals 14 14.5833 1.0417 +% + +X =[ 1 2 4 1 1 2 2 3 3 4 4 2 3 4 4 3 4 5 3 5 5 3 4 6] +X = reshape(X,[8 5])'; +myanova(X,1,1) + + + + diff --git a/stat/anovaeffects.m b/stat/anovaeffects.m new file mode 100644 index 0000000..9e59395 --- /dev/null +++ b/stat/anovaeffects.m @@ -0,0 +1,16 @@ +function [fx]=anovaeffects(nf,m) +% anovaeffects - List effects and their interaction in a factorial design +% [fx]=anovaeffects(nf) +% List main effects and interaction(s) in factorial design with [nf] factors +% [fx] is a N-by-1 cell array +% +% [fx]=anovaeffects(nf,m) +% m: maximum number of interacting factors (default: 3) + +fx={}; +if nargin<2 + m=min(nf,3); +end +for i=1:m + fx=[fx ; num2cell(nchoosek(1:nf,i),2)]; +end diff --git a/stat/anovalevels.m b/stat/anovalevels.m new file mode 100644 index 0000000..eef2791 --- /dev/null +++ b/stat/anovalevels.m @@ -0,0 +1,69 @@ +function [I L]= anovalevels(X,nf,labels) +%ANOVALEVELS - Put labels on levels of each factor in a design matrix X +% [I] = anovalevels(X) creates an array I with as many rows as there are +% elements in X and as many columns as its dimensions, where X contains +% the data of a full factorial ANOVA (see: myanova), so that each rows of +% I indicate for each X(:) the level in each factor it corresponds to. +% The concatenation [I X(:)] can then be used with by rmaov2, for +% example. +% +% [I] = anovalevels(X, nf) consider only the first nf dimensions of X as +% factors (the rest is supposed to be multivariate data) +% +% [L,I] = anovalevels(X, nf, labels) uses the specified labels instead +% of numerical indices. labels must be a cell array of length nf, with +% each labels{i} being itself an array of length = size(X,i) +% +% Example +% >> anovalevels(rand(3,2)) +% >> anovalevels(rand(3,2),2,{1:3 , {'a' 'b'}}) +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2006 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2006-02-14 Creation +% KND 2008-11-11 Processes text labels +% KND 2010-05-04 Renamed factorlabels -> anovalevels +% ----------------------------- Script History --------------------------------- + +sx=size(X); +if nargin<2 + nf=length(sx); +end +ns=prod(sx(1:nf)); +if nargin==3 + nl=cellfun('length', labels); + if ~all(nl(1:nf)==sx(1:nf) | ~(nl>1)) %| ~all(cellfun('isreal', [labels{nl==1}]) & (cellfun('length', [labels{nl==1}]) == sx(nl==1))) + error('Wrong size of labels') + end +end + +I=zeros(ns,nf); +for i=1:nf + z=ones(prod(sx(1:i-1)),1)*(1:sx(i)); + z=z(:)*ones(1,prod(sx(i+1:end))); + I(:,i)=z(:); +end + +if nargin>2 + L=cell(size(I)); + for i=1:nf + if iscell(labels{i}) + L(:,i)=labels{i}(I(:,i)); + else + L(:,i)=num2cell(labels{i}(I(:,i))); + end + end + %permute outputs + J=I; + I=L; + L=J; + clear J; +end \ No newline at end of file diff --git a/stat/anovand.m b/stat/anovand.m new file mode 100644 index 0000000..9d0661f --- /dev/null +++ b/stat/anovand.m @@ -0,0 +1,1533 @@ +function [p,t,stats,terms] = anovand(y,group,varargin) +%ANOVAND N-way analysis of variance (ANOVA) of multidimensional data +% P=ANOVAN(Y,GROUP) performs multi-way (n-way) anova on the vector Y +% grouped by entries in the cell array GROUP. Each cell of GROUP must +% contain a grouping variable that can be a categorical variable, numeric +% vector, character matrix, or single-column cell array of strings. +% Each grouping variable must have the same number of items as Y. The +% fitted anova model includes the main effects of each grouping variable. +% All grouping variables are treated as fixed effects by default. The +% result P is a vector of p-values, one per term. +% +% P=ANOVAN(Y,GROUP,'PARAM1',val1,'PARAM2',val2,...) specifies one or +% more of the following name/value pairs: +% +% Parameter Value +% 'alpha' A value between 0 and 1 requesting 100*(1-alpha)% +% confidence bounds (default 0.05 for 95% confidence) +% 'continuous' A vector of indices indicating which grouping variables +% should be treated as continuous predictors rather than +% as categorical predictors +% 'display' Either 'on' (the default) to display an anova table +% or 'off' to omit the display +% 'nested' A matrix M of 0's and 1's specifying the nesting +% relationships among the grouping variables. M(i,j) +% is 1 if variable i is nested in variable j. +% 'random' A vector of indices indicating which grouping variables +% are random effects (all are fixed by default) +% 'sstype' The type of sum of squares 1, 2, 3, or 'h' (default=3) +% 'varnames' Grouping variables names in a character matrix or +% a cell array of strings, one per grouping variable +% (default names are 'X1', 'X2', ...) +% +% 'model' The model to use, specified as one of the following: +% +% 'linear' to use only main effects of all factors (default) +% 'interaction' for main effects plus two-factor interactions +% 'full' to include interactions of all levels +% an integer representing the maximum interaction order, for example +% 3 means main effects plus two- and three-factor interactions +% a matrix of term definitions as accepted by the X2FX function, +% but all entries must be 0 or 1 (no higher powers) +% +% [P,T,STATS,TERMS]=ANOVAN(...) also returns a cell array T containing +% the anova table, a structure STATS containing a variety of statistics, +% and a matrix TERMS describing the terms used (suitable for use as the +% MODEL input argument if you run ANOVAN again). +% +% For models without random effects, the anova table T contains columns +% for terms, sum of squares, degrees of freedom, an indication of whether +% the term is singular, mean square, F statistic, and P value. For models +% with random effects there are additional columns showing the term type +% (fixed or random), expected mean square, denominator mean square for F, +% denominator degrees of freedom for F, denominator definition, variance +% component estimate, lower bound for variance, and upper bound for +% variance. +% +% For the 'sstype' parameter, values 1-3 produce the usual type 1, type 2, +% or type 3 sums of squares. The value 'h' produces sums of squares for +% a hierarchical model that is similar to type 2, but with continuous as +% well as categorical factors used to determine the hierarchy of terms. +% +% The STATS structure contains the fields listed below, in addition +% to a number of other fields required for doing multiple comparisons +% using the MULTCOMPARE function: +% coeffs estimated coefficients +% coeffnames name of term for each coefficient +% vars matrix of grouping variable values for each term +% resid residuals from the fitted model +% +% These fields exist if there are random effects: +% ems expected mean squares +% denom denominator definition +% rtnames names of random terms +% varest variance component estimates (one per random term) +% varci confidence intervals for variance components +% +% Example: +% load carbig +% [p, atab] = anovan(MPG, {Cylinders Origin Model_Year}, ... +% 'model',2, 'sstype',2, ... +% 'varnames',strvcat('Cyl', 'Org', 'Yr')) +% This example performs three-way anova on MPG using the factors +% Cylinders, Origin, and Year. The model will have all two-factor +% interactions but not the three-factor interaction. Sum of +% squares are Type 2. +% +% See also MULTCOMPARE, ANOVA1, ANOVA2, MANOVA1. + +% Also supports older calling sequence: +% P=ANOVAN(Y,GROUP,MODEL,SSTYPE,VARNAME,DISPLAYOPT) +% In addition to the matrix form of the model specification, the function +% allows MODEL to be a vector V of integers. In this compressed form +% each element describes a term, so the Ith term includes the Jth grouping +% variable if BITGET(V(I),J)==1 +% +% +% References: +% Dunn, O.J., and V.A. Clark (1974), "Applied Statistics: +% Analysis of Variance and Regression," Wiley, New York. +% Goodnight, J.H., and F.M. Speed (1978), "Computing Expected Mean +% Squares," SAS Institute, Cary, NC. +% Milliken, G.A., and D.E. Johnson, "Analysis of Messy Data," +% Chapman & Hall, New York. +% Seber, G.A.F. (1977), "Linear Regression Analysis," Wiley, +% New York. + +% Copyright 1993-2007 The MathWorks, Inc. +% $Revision: 1.9.2.11 $ $Date: 2007/05/23 19:14:56 $ + +% First: argument checking and selection of defaults +error(nargchk(2,Inf,nargin,'struct')); +% if (numel(y)~=length(y)) +% error('stats:anovan:VectorRequired','Y must be a vector.'); +% end + +% if (size(y,1)==1), y=y(:); end +dostats = (nargout>=3); % true to compute output stats structure + +if ~iscell(group) && ~isa(group,'categorical') + if isnumeric(group) && size(group,1)==size(y,1) + group = num2cell(group,1); + else + error('stats:anovan:BadGroup',... + 'GROUP must be a cell array or matrix of grouping variables.') + end +end +group = group(:); +ng = length(group); + +% Which style of calling sequence are we using? +newstyle = true; +if ~isempty(varargin) && nargin<=6 + va1 = varargin{1}; + if ~ischar(va1) || isempty(va1) + newstyle = false; + elseif ~isempty(strmatch(va1,{'l' 'i' 'f' 'linear' 'interaction' 'full'},... + 'exact')) + newstyle = false; + end +end + +if newstyle + % Calling sequence with named arguments + okargs = {'model' 'sstype' 'varnames' 'display' 'alpha' ... + 'random' 'continuous' 'nested'}; + defaults = {1 3 '' 'on' 0.05 ... + false(ng,1) [] []}; + + % Skip error detection... + % [eid emsg model sstype varnames displayopt alpha ... + % randomvar continuous vnested] = ... + % statgetargs(okargs,defaults,varargin{:}); + % if ~isempty(eid) + % error(sprintf('stats:anovan:%s',eid),emsg); + % end +else + % Old-style calling sequence with fixed argument positions + model = 1; + sstype = 3; + varnames = ''; + displayopt = 'on'; + continuous = []; + vnested = []; + if nargin>=3, model = varargin{1}; end + if nargin>=4, sstype = varargin{2}; end + if nargin>=5, varnames = varargin{3}; end + if nargin>=6, displayopt = varargin{4}; end + alpha = 0.05; + randomvar = false(ng,1); +end + +% Check optional arguments +[varnames,termlist,randomvar,sstype,continuous,vnested] = argcheck(ng,... + varnames,model,randomvar,sstype,alpha,continuous,vnested); +doems = any(randomvar); % true to compute expected mean squares +if isequal(lower(sstype),'h') + sstype = 2; + dohier = true; +else + dohier = false; +end +if any(vnested(:)) + varnames = makenestednames(varnames,vnested); +end + +% STEP 1: Remove NaN's and prepare grouping variables. +% Also, make sure all groups are rows, and create group index and name arrays. +[y,allgrps,nanrow,varinfo] = removenans(y,group,continuous); +n = length(y); + +% STEP 2: Create dummy variable arrays for each grouping variable. +% STEP 2a: For type 3 ss, create constraints for each grouping variable. +varinfo.varnames = varnames; +varinfo = makedummyvars(continuous,allgrps,vnested,varinfo); + +% STEP 3: Create dummy variable arrays for each term in the model. +nterms = size(termlist,1); +fulltermlist = showtermnesting(termlist,vnested); +tnested = gettermnesting(fulltermlist,vnested,continuous); + +[ignore,sindex] = sortrows(fulltermlist); +terminfo = makedummyterms(sindex, termlist, varinfo, ... + continuous, tnested); + +% STEP 4: Create the full design matrix +[dmat,cmat,termname] = makedesignmat(terminfo,n); + +% STEP 5: Fit the full model and compute the residual sum of squares +mu = mean(y); +y = y - mu; % two passes to improve accuracy +mu2 = mean(y); +mu = mu + mu2; +y = y - mu2; +sst = sum(y.^2); +if ~dostats + [ssx,dfx,dmat2,y] = dofit(dmat, y, cmat, sst, mu); +else + % Do initial fit requesting stats structure, then convert from full-rank + % to overdetermined form, then find a label for each coefficient + [ssx,dfx,dmat2,y,stats] = dofit(dmat,y,cmat,sst,mu); + [codes,names] = convertstats(stats,terminfo,varinfo,continuous); +end + +sse = sst - ssx; +dfe = n - dfx; + +% STEP 6: Determine which models to compare for testing each term +ssw = -ones(nterms, 1); % sum of squares with this term +sswo = ssw; % sum of squares without this term +dfw = ssw; % residual d.f. with this term +dfwo = ssw; % residual d.f. without this term + +switch sstype + case 1 + modw = tril(ones(nterms)); % get model with this term + k = nterms; % locations of model with all terms + case 2 + modw = termsnotcontained(fulltermlist,vnested,continuous,dohier); + k = (sum(modw,2) == nterms); + TnotC = modw; + case 3 + modw = ones(nterms); + k = 1:nterms; + TnotC = termsnotcontained(fulltermlist,vnested,continuous,dohier); +end +modw = logical(modw); +modwo = logical(modw - eye(nterms)); % get model without this term + +% STEP 7: Fit each model, get its residual SS and d.f. +ssw(k) = ssx; % for full model we already know the results +dfw(k) = dfx; + +% Fit each model separately +dfboth = [dfw; dfwo]; +if doems + Llist = cell(nterms,1); +end + +% Consider interactions before their components for type 3 ss, so +% examine the terms in decreasing order of the number factors in the term +if sstype==3 + termsize = sum(fulltermlist,2)'; + [stermsize,sindices] = sort(termsize); + sindices = [sindices(:); sindices(:)]; +else + sindices = [(1:size(termlist,1)), (1:size(termlist,1))]'; +end + +% Fit all required subsets of the full model +fitsubmodels; % nested function + +% STEP 7A: Compute expected means squares if requested +if doems + emsMat = makeemsmat(dmat, cmat, nterms, Llist, termname); +end + +% STEP 8: Compute the sum of squares attributed to each term +ssterm = ssw - sswo; +dfterm = dfw - dfwo; +ssterm(dfterm==0) = 0; % make this exact + +% STEP 9: Compute the mean square for each term +msterm = ssterm ./ max(1, dfterm); +mse = sse * (dfe>0) / max(1, dfe); + +% STEP 9a: Equate computed and expected mean squares, then solve for +% variance component estimates +randomterm = find(termlist*randomvar > 0); % indices of random terms +if isempty(randomterm) + msdenom = repmat(mse, size(msterm)); + dfdenom = repmat(dfe, size(msterm)); +else + [msdenom,dfdenom,varest,varci,txtdenom,txtems,denommat,rtnames] = ... + getRandomInfo(msterm,dfterm,mse,dfe,emsMat,randomterm,... + terminfo.tnames,alpha); +end + +% STEP 10: Compute an F statistic for each term +fstat = repmat(Inf, size(msterm)); +t1 = (msdenom>0); +t2 = (msterm>0); +fstat(t1) = msterm(t1) ./ msdenom(t1); +fstat(~(t1|t2)) = NaN; + +% STEP 11: Compute the p-value for each term +pval = repmat(NaN, size(fstat)); +t = (dfdenom>0 & dfterm>0); +pval(t) = 1 - fcdf(fstat(t), dfterm(t), dfdenom(t)); +p = pval; + +% Create anova table as a cell array +sing = (dfterm < terminfo.dfterm0); % is non-nested term singular? +if ~isempty(tnested) + sing(any(tnested,2)) = false; +end + +[tbl, t] = maketable; % nested function + +if (nargout > 3), terms = termlist; end + +if (isequal(displayopt,'on')) + switch(sstype) + case 1, cap = 'Sequential (Type I) sums of squares.'; + case 2, if dohier + cap = 'Hierarchical (modified Type II) sums of squares'; + else + cap = 'Hierarchical (Type II) sums of squares.'; + end + otherwise, cap = 'Constrained (Type III) sums of squares.'; + end + if (any(sing)), cap = [cap ' Terms marked with # are not full rank.']; end + digits = [-1 -1 -1 -1 2 4]; + figh = statdisptable(tbl, 'N-Way ANOVA', 'Analysis of Variance',cap,digits); + set(figh,'HandleVisibility','callback'); +end + +% Store additional information for multiple comparisons +if dostats + stats = makestats(stats); % nested function +end + +% ----- nested function +function fitsubmodels + for j=length(sindices):-1:1 + % Find the next model index to fit + k = sindices(j); + + % Look in unsorted arrays to see if we have already fit this model + if j>nterms + k0 = k+nterms; + else + k0 = k; + end + if dfboth(k0)~=-1 + continue + end + + % Find the model with this index + if (j > nterms) + thismod = modwo(k, :); + else + thismod = modw(k, :); + end + + % Get the design matrix for this model + keepterms = find(thismod); + clist = ismember(termname, [0 keepterms]); + X = dmat2(:,clist); + C = cmat(:,clist); + + % Fit this model + [ssx0,dfx0] = dofit(X, y, C); + + % If this model is the "without" model for a term, compute L + if doems && j>nterms + if sstype==1 + prevterms = (1:nterms)F'}; ... + terminfo.tnames tbl]; + + % Add random effects information if required + if ~isempty(randomterm) + rterms = [randomterm; nterms+1]; + C = size(tbl,2); + tbl(1,end+1:end+8) = {'Type' 'Expected MS' 'MS denom' 'd.f. denom' 'Denom. defn.' ... + 'Var. est.' 'Var. lower bnd' 'Var. upper bnd'}; + tbl(1+(1:nterms),C+1) = {'fixed'}; + tbl(1+rterms,C+1) = {'random'}; + tbl(1+(1:length(txtems)),C+2) = txtems; + tbl(1+(1:length(msdenom)),C+3) = num2cell(msdenom(:)); + tbl(1+(1:length(dfdenom)),C+4) = num2cell(dfdenom(:)); + tbl(1+(1:length(txtdenom)),C+5)= txtdenom; + tbl(1+rterms,C+6) = num2cell(varest); + tbl(1+rterms,C+7) = num2cell(varci(:,1)); + tbl(1+rterms,C+8) = num2cell(varci(:,2)); + end + + % Add total information + tbl{end+1,1} = 'Total'; + tbl{end,2} = sst; + tbl{end,3} = n-1; + tbl{end,4} = 0; + + % So far this is the table for the output argument + t = tbl; + + % For display, mark terms that are rank deficient + tbl{1,1} = [' ' tbl{1,1}]; + for r=2:size(tbl,1) + x = tbl{r,1}; + if (tbl{r,4}) + x = ['# ' x]; + else + x = [' ' x]; + end + tbl{r,1} = x; + end + tbl = tbl(:,[1 2 3 5 6 7]); + end % of nested function maketable + +end % of main function + + +% -------------------------- +function m = termsnotcontained(terms,vnested,continuous,dohier) +%TERMSNOTCONTAINED Creates a logical matrix indicating term containment +% m(i,j)==1 iff t(i) is not contained by t(j) + +% For starters, omit continuous variables from this test +contnum = find(continuous); +t = terms; +t(:,contnum) = 0; + +% The main test: no overlap between terms included in t(i) and not in t(j) +m = (t*~t') > 0; + +% Now consider continuous variables (X) +nterms = size(terms,1); +for j=1:length(contnum) + v = terms(:,contnum(j)); + vv = repmat(v,1,nterms); + if dohier + % A cannot be contained in B if A has a higher power of X + m = m | vv>vv'; + else + % A cannot be contained in B if they don't match on X + m = m | vv~=vv'; + end +end + +% Set diagonals to 1 because we want proper containment +m(1:(nterms+1):end) = 1; +end + +% -------------------------- +function v = makemodel(p,ng,vnested) +%MAKEMODEL Helper function to make model matrix +% P = max model term size, NG = number of grouping variables +% or +% P = vector of term codes +% VNESTED is a matrix specifying direct or indirect nesting. + +% We want a matrix with one row per term. Each row has a 1 for +% the variables participating in the term. There will be rows +% summing up to 1, 2, ..., p. + +if numel(p)==1 + % Create model matrix from a scalar max order value + vgen = 1:ng; + v = eye(ng); % linear terms + for j=2:min(p,ng) + c = nchoosek(vgen,j); % generate column #'s with 1's + nrows = size(c,1); % generate row #'s + r = repmat((1:nrows)',1,j); % and make it conform with c + m = zeros(nrows,ng); % create a matrix to get new rows + m(r(:)+nrows*(c(:)-1)) = 1; % fill in 1's + v = [v; m]; % append rows + end +else + % Create model matrix from terms encoded as bit patterns + nterms = length(p); + v = zeros(nterms,ng); + for j=1:nterms + tm = p(j); + while(tm) + % Get last-numbered effect remaining + lne = 1 + floor(log2(tm)); + tm = bitset(tm, lne, 0); + v(j,lne) = 1; + end + end +end + +% Remove terms forbidden by nesting +[nestee,nester] = find(vnested); +bad = any(v(:,nestee) & v(:,nester), 2); +v(bad,:) = []; + +end + +% -------------------------- +function [ssx,dfx,dmat2,y2,stats] = dofit(dmat,y,cmat,sst,mu) +%DOFIT Do constrained least squares fit and reduce data for subsequent fits + +% Find the null space of the constraints matrix +[Qc,Rc,Ec] = qr(cmat'); +pc = Rrank(Rc); +Qc0 = Qc(:,pc+1:end); + +% Do qr decomposition on design matrix projected to null space +Dproj = dmat*Qc0; +[Qd,Rd,Ed] = qr(Dproj,0); +dfx = Rrank(Rd); +Qd = Qd(:,1:dfx); +Rd = Rd(1:dfx,1:dfx); + +% Fit y to design matrix in null space +y2 = Qd' * y; % rotate y into that space +zq = Rd \ y2; % fit rotated y to projected design matrix + +z = zeros(length(Ed),1); % coefficient vector extend to full size ... +z(Ed(1:dfx)) = zq; % ... and in correct order for null space + +b = Qc0 * z; % coefficients back in full space +ssx = norm(y2)^2; % sum of squares explained by fit + +% Return reduced design matrix if requested +if nargout>2 + dmat2 = Qd' * dmat; +end + +% Prepare for multiple comparisons if requested +if (nargout >= 3) + stats.source = 'anovan'; + + % Get residuals + yhat = Dproj * z; + stats.resid = y - yhat; + + % Calculate coefficients, then adjust for previously removed mean + t = b; + if ~isempty(t) + t(1) = t(1) + mu; % undo mean adjustment before storing + end + stats.coeffs = t; + + RR = zeros(dfx,size(Dproj,2)); + RR(:,Ed(1:dfx)) = Rd; + stats.Rtr = RR'; + [qq,rr,ee] = qr(dmat,0); + pp = Rrank(rr); + rowbasis = zeros(pp,size(rr,2)); + rowbasis(:,ee) = rr(1:pp,:); + stats.rowbasis = rowbasis; + stats.dfe = length(y) - dfx; + stats.mse = (sst - ssx) / max(1, stats.dfe); + stats.nullproject = Qc0; +end +end + +% --------------------- +function p = Rrank(R,tol) +%RRANK Compute rank of R factor after qr decomposition +if (min(size(R))==1) + d = abs(R(1,1)); +else + d = abs(diag(R)); +end +if nargin<2 + tol = 100 * eps(class(d)) * max(size(R)); +end +p = sum(d > tol*max(d)); +end + +% ---------------------- +function ems = getems(L,R,termnums) +%GETEMS Final step in expected mean square computation +% The L array is an array of constraints on the coefficients, +% R is the R factor in a QR decomposition X, and termnums is a +% vector mapping R column numbers to term numbers. The output is +% a vector of expected mean square coefficients with one element +% per term. The first element is for the constant term, and +% the last is for the error term. See "Computing Expected Mean +% Squares" by Goodnight and Speed, 1978. + +% In G&S notation, form the matrix L*pinv(X'*X)*L'. +dfL = size(L,1); +temp = L/R; +LML = temp*temp'; + +% Create the Cholesky decomposition U and then compute U\L. +U = cholcov(LML); +C = U'\L; + +% Sum the squared values of the columns other than the column for +% the constant term, and assign the sums to terms. +sumsq = sum(C(:,2:end).^2,1); +cols = termnums(2:end); +rows = ones(size(cols)); +ems = accumarray([rows(:),cols(:)], sumsq) / dfL; +ems(:,end+1) = 1; % add entry for error term +end + +% ----------------------------- +function L = getL(X, curcols, incols, L2) +%GETL Get L hypothesis matrix for the current term after adjusting for +% terms that remain "in" for this test, making the whole thing +% orthogonal to the matrix L2 containing hypotheses for terms that +% contain the current term. +% +% Type incols L2 +% ---- -------------------- ---------------- +% 1 lower-numbered terms empty +% 2 non-containing terms empty +% 3 non-containing terms containing terms + +x1 = X(:,curcols); % the current term +x0 = X(:,incols); % terms that remain "in" when testing x1 + +% Find hypothesis for the current term. First remove effects of terms it does +% not contain (these terms remain in the model when testing the current term). +[Q0,R,E] = qr(x0,0); +tol = 100 * max(size(x0)) * eps(R(1,1)); +if min(size(R))>1 + p = sum(abs(diag(R)) > tol); +else + p = 1; +end + +Q0 = Q0(:,1:p); +adjx1 = x1 - Q0 * (Q0' * x1); + +% Now find a full-rank basis for the adjusted effect. +[Q2,R,E] = qr(adjx1,0); +tol = 100 * max(size(R)) * eps(R(1,1)); +if min(size(R))>1 + p = sum(abs(diag(R)) > tol); +else + p = 1; +end +Q2 = Q2(:,1:p); +L = Q2' * X; + +% Make L rows orthogonal to the L rows for containing terms. +if ~isempty(L2) + L2tr = L2'; + [Q,R,E] = qr(L2tr,0); + L = (L' - Q * (Q' * L'))'; + + % Make L full rank + [Q,R,E] = qr(L,0); + tol = 100 * max(size(L)) * eps(R(1,1)); + if min(size(R))>1 + p = sum(abs(diag(R)) > tol); + else + p = 1; + end + if p= tol); + indices = e(nonzeroterms); + tnums = randomterms(indices); + if length(nonzeroterms) == 1 + % If it's a single term, use it exactly + msDenom(j) = msTerm(tnums); + dfDenom(j) = dfTerm(tnums); + txtdenom{j} = sprintf('MS(%s)',tnames{tnums}); + else + % Otherwise use the linear combination and Satterthwaite's approximation + linprod = b(nonzeroterms) .* msTerm(tnums); + msDenom(j) = sum(linprod); + if any(dfTerm(tnums) == 0) + dfDenom(j) = NaN; + else + dfDenom(j) = msDenom(j)^2 / sum((linprod.^2) ./ dfTerm(tnums)); + end + txtdenom{j} = linprodtxt(b(nonzeroterms),tnames(tnums),'MS'); + end + + % Next get an expression for the expected mean square + prefix = repmat('Q',nterms+1,1); % quadratic form for fixed effects + prefix(randomterms,1) = 'V'; % variance for random effects + txtems{j} = linprodtxt(ems(j,:),tnames,prefix); +end + +% Get the variance component estimates +[varest,varci] = varcompest(ems,randomterms,msTerm,dfTerm,alpha); + +% Package up the results +denommat = B'; +rtnames = tnames(randomterms); +end + +% ----------------------------------------- +function [varest,varci] = varcompest(ems,Rterms,msTerm,dfTerm,alpha) +%VARCOMPEST Estimate variance components from obs. & exp. mean squares + +nterms = length(msTerm) - 1; % not including error term + +Fterms = find(~ismember(1:nterms, Rterms)); +Fterms = Fterms(:); + +% Get A and B so that expected means square is Av+Bq +% where v is the variances and q is quadratic forms in the fixed effects +B = ems(Rterms, Fterms); +A = ems(Rterms, Rterms); +const = msTerm(Rterms) - B*msTerm(Fterms); +Ainv = pinv(A); % should be well-conditioned and easily invertible +varest = Ainv * const; + +% Get confidence bounds for the variance components +varci = repmat(NaN,length(varest),2); +if all(dfTerm>0) + L = msTerm .* dfTerm ./ chi2inv(1-alpha/2,dfTerm); + U = msTerm .* dfTerm ./ chi2inv(alpha/2,dfTerm); + AinvB = Ainv * B; + for j=find(varest'>0) + Rcoeffs = Ainv(j,:); + Fcoeffs = AinvB(j,:); + t = (Rcoeffs>0); + s = (Fcoeffs>0); + cL = 0; + cU = 0; + if any(t) + cL = cL + dot(Rcoeffs(t), L(Rterms(t))); + cU = cU + dot(Rcoeffs(t), U(Rterms(t))); + end + if any(~t) + cL = cL + dot(Rcoeffs(~t), U(Rterms(~t))); + cU = cU + dot(Rcoeffs(~t), L(Rterms(~t))); + end + if any(s) + cL = cL + dot(Fcoeffs(s), L(Fterms(s))); + cU = cU + dot(Fcoeffs(s), U(Fterms(s))); + end + if any(~s) + cL = cL + dot(Fcoeffs(~s), U(Fterms(~s))); + cU = cU + dot(Fcoeffs(~s), L(Fterms(~s))); + end + varci(j,1) = max(0,cL); + varci(j,2) = max(0,cU); + end +end +end + +% ------------------------------------------------ +function txt=linprodtxt(coeffs,names,prefix) +%LINPRODTXT Create a text representing a linear combination of some things + +txt = ''; +plustxt = ''; + +% If things will display as 0 or 1, make them exact +tol = sqrt(eps(class(coeffs))) * max(abs(coeffs)); +coeffs(abs(coeffs)1 + preftxt = deblank(prefix(i,:)); + end + + txt = sprintf('%s%s%s%s(%s)',txt,signtxt,coefftxt,preftxt,names{i}); + plustxt = '+'; + end +end +end + +% ------------------- +function [varnames,termlist,randomvar,sstype,continuous,vnested] = ... + argcheck(ng,varnames,model,randomvar,sstype,alpha,continuous,vnested) +% variable names +if isempty(varnames) + varnames = cellstr([repmat('X',ng,1) strjust(num2str((1:ng)'),'left')]); +else + if (iscell(varnames)), varnames = varnames(:); end + if (size(varnames,1) ~= ng) + error('stats:anovan:InputSizeMismatch','VARNAMES must have %d rows', ng); + elseif (~(ischar(varnames) || iscellstr(varnames))) + error('stats:anovan:BadVarNames',... + 'VARNAMES must be a character matrix or cell array of strings.'); + elseif (ischar(varnames)) + varnames = cellstr(varnames); + end +end + +% sum-of-squares type +if isempty(sstype) + sstype = 3; +elseif ~isscalar(sstype) || (sstype ~= 1 && sstype ~= 2 && sstype ~= 3 && lower(sstype) ~= 'h') + error('stats:anovan:BadSumSquares','SSTYPE must be 1, 2, or 3.'); +end + +% continuous must be a logical vector indicating continuous variables +if islogical(continuous) && length(continuous)==ng && numel(continuous)==ng + continuous = continuous(:); +elseif isnumeric(continuous) && all(ismember(continuous(:),1:ng)) + continuous = ismember(1:ng,continuous); +else + error('stats:anovan:BadContinuous',... + 'CONTINUOUS must be a vector of indices between 1 and %d.', ng); +end + +% nested must be a binary matrix +if ~isempty(vnested) + if ~isequal(size(vnested),[ng ng]) || ~all(vnested(:)==0 | vnested(:)==1) + error('stats:anovan:BadNested',... + 'The NESTED parameter must be a %d-by-%d matrix of 0''s and 1''s.',... + ng,ng); + end + + % find all indirect nesting + vnested = nest_d2i(vnested); +end + +% alpha (confidence level is 100*(1-alpha)% +if ~isnumeric(alpha) || numel(alpha)~=1 || alpha<=0 || alpha>=1 + error('stats:anovan:BadAlpha','ALPHA must be a scalar between 0 and 1.'); +end + +% Randomvar may be a logical vector indicating the random variables +if islogical(randomvar) && length(randomvar)==ng && numel(randomvar)==ng + randomvar = randomvar(:); +elseif isnumeric(randomvar) && all(ismember(randomvar(:),1:ng)) + % or it may be an index vector -- convert to logical + randomvar = ismember((1:ng)', randomvar(:)); +else + error('stats:anovan:BadRandomVar',... + 'RANDOMVAR must be a vector of indices between 1 and %d.', ng); +end +if any(randomvar(continuous)) + error('stats:anovan:BadRandomVar',... + 'You cannot specify a continuous predictor as having a random effect'); +end + +% model +if isempty(model) + model = 1; +elseif ~isnumeric(model) + if strcmp(model, 'linear') || strcmp(model, 'l') + model = 1; + elseif strcmp(model, 'interaction') || strcmp(model, 'i') + model = 2; + elseif strcmp(model, 'full') || strcmp(model, 'f') + model = ng; + else + error('stats:anovan:BadModel','MODEL is invalid.'); + end +elseif min(size(model))>1 || ... + (isequal(size(model),[1 ng]) && all(ismember(model,0:1))) + % Matrix form of input + if (any(model~=floor(model)|model<0)) + error('stats:anovan:BadModel',... + 'MODEL matrix entries must be non-negative integers.'); + elseif size(model,2)~=ng + error('stats:anovan:InputSizeMismatch',... + 'MODEL matrix must have %d columns.',ng); + end +else + if (any(model~=floor(model)|model<=0)) + % Original vector form, no longer advertised + error('stats:anovan:BadModel','MODEL is invalid.'); + end + model = model(:); +end + +% convert model to numeric matrix list of terms +if (length(model)==1) || (any(size(model)==1) && size(model,2)~=ng) + % Convert from scalar or coded integer to matrix form + termlist = makemodel(model, ng, vnested); +else + termlist = model; +end + +% look for invalid model terms +t = all(termlist==0,2); +termlist(t,:) = []; % remove constant term -- it's implicit +if any(any(termlist(:,~continuous)>1)) + error('stats:anovan:BadModel',... + 'MODEL cannot have squared or higher terms for categorical factors.'); +end +if ~isempty(vnested) + % Must not have A(B) and B(A) + if any(any(vnested & vnested')) + error('stats:anovan:BadNesting',... + 'Circular nesting (such as A(B) and B(A)) is not allowed.'); + end + + % Must not have an interaction between B and A(B) + [nestee,nester] = find(vnested); + bad = any(termlist(:,nestee) & termlist(:,nester), 2); + if any(bad) + error('stats:anovan:BadModel',... + ['MODEL must not have an interaction between a nested factor'... + '\nand the factor that nests it.']); + end +end +end + +% ------------------ +function emsMat = makeemsmat(dmat, cmat, nterms, Llist, termname) + +% Factor the design matrix X appended with the constraints matrix C, +% getting a full-rank R factor. +xc = [dmat; cmat]; +[Qxc,Rxc,Exc] = qr(xc,0); +tol = sqrt(eps(class(Rxc))); +rankRxc = Rrank(Rxc,tol); +Rxc0 = zeros(rankRxc,size(Rxc,2)); +Rxc0(:,Exc) = Rxc(1:rankRxc,:); + +% Compute expected mean squares for each term. +emsMat = zeros(nterms,0); +for k=1:nterms + termems = getems(Llist{k},Rxc0,termname); + emsMat(k,1:length(termems)) = termems; +end +emsMat(nterms+1,end) = 1; % for error term +end + +% ---------------------- +function [codes,names] = convertstats(stats,terminfo,varinfo,continuous) + +allnames = varinfo.allnames; +varnames = varinfo.varnames; +ng = length(varnames); +fullcoef = stats.coeffs; +codes = zeros(length(fullcoef), ng); +names = cell(length(fullcoef),1); +base = 1; +names{1} = 'Constant'; + +% compute names +for j=1:length(terminfo.levelcodes) + M = terminfo.levelcodes{j}; + varlist = terminfo.termvars{j}; + v1 = varlist(1); + vals1 = allnames{v1}; + nrows = size(M,1); + codes(base+(1:nrows),varlist) = M; + for k=1:nrows + if continuous(v1) + nm = varnames{v1}; + else + nm = sprintf('%s=%s',varnames{v1},vals1{M(k,1)}); + end + for i=2:length(varlist) + v2 = varlist(i); + vals2 = allnames{v2}; + if continuous(v2) + nm2 = varnames{v2}; + else + nm2 = sprintf('%s=%s',varnames{v2},vals2{M(k,i)}); + end + nm = sprintf('%s * %s',nm,nm2); + end + names{base+k} = nm; + end + base = base+nrows; +end +end + +% -------------------------------- +function terminfo = makedummyterms(sindex,termlist,varinfo,continuous,tnested) + +ncols = 1; +[nterms,nfactors] = size(termlist); + +termdum = cell(nterms, 1); % dummy variable = design matrix cols +termconstr = cell(nterms,1); % constraints to make each term well defined +levelcodes = cell(nterms, 1); % codes for levels of each M row +tnames = cell(nterms, 1); % name for entire term, e.g. A*B +dfterm0 = zeros(nterms, 1); % nominal d.f. for each term +termvars = cell(nterms, 1); % list of vars in each term +termlength = zeros(size(sindex));% length of each term (number of columns) + +auxtermlist = zeros(0,nfactors); % may need to create temp terms for nesting +auxtermdum = cell(0,1); % may need their dummy variables + +isnested = false; + +% For each term, +for j=1:nterms + % Get dummy columns, var list, term name, etc + sj = sindex(j); + tm = termlist(sj,:); + if ~isempty(tnested) + isnested = any(tnested(sj,:)); + end + [tdum,vars,tn,df0,tconstr] = maketerm(tm,isnested,varinfo,j,sindex,... + termlist,termdum,termconstr,tnames,dfterm0); + + % Store this term's information + k = size(tdum, 2); + termlength(sindex(j),1) = k; + ncols = ncols + k; + termdum{sj} = tdum; + termvars{sj} = vars; + levelcodes{sj} = fliplr(fullfact(varinfo.vlevels(vars(end:-1:1)))); + tnames{sj,1} = tn; + dfterm0(sj) = df0; + + % For a nested term, figure out the constraint now + if isnested + % First get dummy variables for this term and its nesters + if any(tm(continuous)) + % Ignore any continuous variable contributions to this term + tmcat = tm; % this term, cat predictors only + tmcat(continuous) = 0; + ntmcat = termlist(tnested(sj,:),:); + ntmcat(:,continuous) = 0; % nesting terms, cat predictors only + [Ydum,auxtermlist,auxtermdum] =findtermdum(tmcat,termlist,termdum,... + auxtermlist,auxtermdum,varinfo); + Ydum = Ydum{1}; + [Xdum,auxtermlist,auxtermdum] =findtermdum(ntmcat,termlist,termdum,... + auxtermlist,auxtermdum,varinfo); + else + % No continuous variables, so work with the whole term + Ydum = tdum; + Xdum = termdum(tnested(sj,:)>0); + end + + % Constrain the part of this term in its nesters to be 0 + tconstr = gettermconstr(Ydum, Xdum); + end + termconstr{sj} = tconstr; +end +tnames{length(tnames)+1,1} = 'Error'; + +% Package up term info into a structure +terminfo.termdum = termdum; +terminfo.termconstr = termconstr; +terminfo.levelcodes = levelcodes; +terminfo.tnames = tnames; +terminfo.dfterm0 = dfterm0; +terminfo.termvars = termvars; +terminfo.termlength = termlength; +end + +% ----------------------------------- +function [y,allgrps,nanrow,varinfo] = removenans(y,group,continuous) + +% Find NaNs among response and group arrays +n = size(y,1); +nanrow = any(isnan(y(:,:)),2); +ng = length(group); +for j=1:ng + gj = group{j}; + if (size(gj,1) == 1), gj = gj(:); end + if (size(gj,1) ~= n) + error('stats:anovan:InputSizeMismatch',... + 'Group variable %d must have %d elements.',j,n); + end + if (ischar(gj)), gj = cellstr(gj); end + if ~isvector(gj) + error('stats:anovan:BadGroup',... + 'Grouping variable must be a vector or a character array.'); + end + + group{j} = gj; + if (isnumeric(gj)) + nanrow = (nanrow | isnan(gj)); + elseif isa(gj,'categorical') + nanrow = (nanrow | isundefined(gj)); + else + nanrow = (nanrow | strcmp(gj,'')); + end +end + +% Remove rows with NaN anywhere +y(nanrow,:) = []; +n = size(y,1); +ng = length(group); +dfvar = zeros(ng,1); +allgrps = zeros(n, ng); +allnames = cell(ng,1); + +% Get arrays describing the groups +for j=1:ng + gj = group{j}; + gj(nanrow,:) = []; + group{j} = gj; + if continuous(j) + dfvar(j) = 1; + allgrps(:,j) = gj; + else + [gij,gnj] = grp2idx(gj); + nlevels = size(gnj,1); + dfvar(j) = nlevels - 1; + allgrps(:,j) = gij; + allnames{j} = gnj; + end +end + +% The df and allnames information does not yet reflect nesting. We will +% fix up allnames for nested variables later, and not use df for them. + +varinfo.df = dfvar; +varinfo.allnames = allnames; +end + +% ----------------------------- +function varinfo = makedummyvars(continuous,allgrps,vnested,varinfo) +% Create main effect term values for each variable + +ng = size(allgrps,2); +vdum = cell(ng,1); % dummy terms for variables +vconstr = cell(ng,1); % constraints +vlevels = ones(ng,1); % levels corresponding to each dummy term +vmeans = zeros(ng,1); % variable means, used for continuous factors +varnames = varinfo.varnames; +allnames = varinfo.allnames; + +for j=1:ng + if continuous(j) + % For continuous variables, use the variable itself w/o constraints + vdum{j} = allgrps(:,j); + vconstr{j} = ones(0,1); + vmeans(j) = mean(allgrps(:,j)); + elseif isempty(vnested) || ~any(vnested(j,:)) + % Create dummy variable arrays for each grouping variable + % using a sum-to-zero constraint + vdum{j} = idummy(allgrps(:,j), 3); + nlevgrp = size(vdum{j},2); + vconstr{j} = ones(1,nlevgrp); % sum-to-zero constraint for categorical + vlevels(j) = nlevgrp; + else + % Create dummy variable arrays for a nested variable + nesternums = find(vnested(j,:)); + allvars = allgrps(:,[nesternums, j]); + [ugrps,ignore2,grpnum] = unique(allvars,'rows'); + vdum{j} = idummy(grpnum,3); + vconstr{j} = []; % to be computed later + vlevels(j) = size(vdum{j},2); + + % Fix up names for nested variables + levelnames = varinfo.allnames{j}; + namesj = cell(vlevels(j),1); + nester1names = allnames{nesternums(1)}; + for rnum = 1:vlevels(j) + nesterlist = nester1names{ugrps(rnum,1)}; + for k = 2:length(nesternums) + nesterknames = allnames{nesternums(k)}; + nesterlist = sprintf('%s,%s',nesterlist,... + nesterknames{ugrps(rnum,k)}); + end + namesj{rnum} = sprintf('%s(%s)',levelnames{ugrps(rnum,end)},... + nesterlist); + end + varinfo.allnames{j} = namesj; + end +end + +varinfo.vdum = vdum; +varinfo.vconstr = vconstr; +varinfo.vlevels = vlevels; +varinfo.vmeans = vmeans; +end + +% ----------------------------- +function [dmat,cmat,termname] = makedesignmat(terminfo,n) + +termlength = terminfo.termlength; +ncols = sum(termlength); + +nconstr = sum(cellfun('size',terminfo.termconstr,1)); +dmat = ones(n, ncols+1); % to hold design matrix +cmat = zeros(nconstr,ncols); % to hold constraints matrix +cbase = 0; % base from which to fill in cmat +termname = zeros(ncols,1); +termstart = cumsum([2; termlength(1:end-1)]); +termend = termstart + termlength - 1; +for j=1:length(termlength) + clist = termstart(j):termend(j); + dmat(:, clist) = terminfo.termdum{j}; + C = terminfo.termconstr{j}; + nC = size(C,1); + cmat(cbase+1:cbase+nC,clist) = C; + termname(clist) = j; + cbase = cbase + nC; +end +end + + +% ----------------------------- +function tnested = gettermnesting(fulltermlist,vnested,continuous) +% Create matrix with (i,j) indicating if term i is nested in term j + +% No nested variables implies no nested terms +if isempty(vnested) + tnested = []; + return +end + +% Work with categorical and continuous terms separately +ctermlist = fulltermlist(:,continuous); +fulltermlist(:,continuous) = 0; + +nterms = size(fulltermlist,1); +tnested = zeros(nterms); + +% If A(B), then any term containing A may be nested in one containing B +[nestee,nester] = find(vnested); +for j=1:length(nester) + hasnestee = (fulltermlist(:,nestee(j))>0); + hasnester = (fulltermlist(:,nester(j))>0); + tnested(hasnestee,hasnester) = 1; +end + +% But terms are not nested within themselves +tnested(1:nterms+1:end) = 0; + +% And there is no nesting relationship if the continuous vars don't match +for j=1:size(ctermlist,2) + c = repmat(ctermlist(:,j),1,nterms); + tnested = tnested & (c == c'); +end + +% And the full representation for a nesting term must be a subset of the +% full representation for a nested term +[nestee,nester] = find(tnested); +for j=1:length(nester) + if any(fulltermlist(nestee(j),:) < fulltermlist(nester(j),:)) + tnested(nestee(j),nester(j)) = 0; + end +end + +end + +% ---------------------------- +function constr = gettermconstr(tdum,nesterdum) + +% Create X matrix containing design matrix columns of nester terms +if iscell(nesterdum) + Xnester = cat(2,nesterdum{:}); +else + Xnester = nesterdum; +end +nnested = size(Xnester,2); + +% Find unique combinations of nested and nesting terms +Xboth = [Xnester tdum]; +Uboth = unique(Xboth,'rows'); + +% Find constraints to force the part of the nested terms in the column +% space of the nesting terms to be zero +[Q,R,E] = qr(Uboth(:,1:nnested)); +rnk = Rrank(R); +Q = Q(:,1:rnk); +constr = Q*(Q'*Uboth(:,nnested+1:end)); + +% Remove redundant constraints +[Q,R,E] = qr(constr',0); +rnk = Rrank(R); +constr = constr(E(1:rnk),:); +end + + +% ---------------------------- +function nestednames = makenestednames(varnames,vnested) + +nestednames = varnames; + +nestedvars = find(any(vnested,2)); +for j=1:length(nestedvars) + k = nestedvars(j); + thelist = sprintf('%s,',varnames{vnested(k,:)>0}); + nestednames{k} = sprintf('%s(%s)',varnames{k},thelist(1:end-1)); +end +end + +% ---------------------------- +function fulltermlist = showtermnesting(termlist,vnested) + +fulltermlist = termlist; + +[nestee,nester] = find(vnested); +for j=1:length(nester) + t = fulltermlist(:,nestee(j))>0; + fulltermlist(t,nester(j)) = 1; +end +end + + +% ---------------------------- +function [tdum,vars,tn,df0,tconstr] = maketerm(tm,isnested,varinfo,j,sindex,termlist,... + termdum,termconstr,tnames,dfterm0) +% Make term info such as dummy vars, name, constraints + +% Loop over elements of the term +df0 = 1; +tdum = []; % empty term so far +tconstr = 1; % empty constraints so far +tn = ''; % blank name so far +vars = find(tm); % list of variables making up terms +pwrs = tm(vars); % and their exponents +tm = (tm>0); % and a boolean mask for them +for varidx = 1:length(vars) + % Process each variable participating in this term + varnum = vars(varidx); % variable name + thispwr = pwrs(1); % power of this variable + tm(varnum) = 0; % term without this variable + pwrs(1) = []; % powers without this variable + df0 = df0 * varinfo.df(varnum); % d.f. so far + + % Combine its dummy variable with the part computed so far + G = varinfo.vdum{varnum}; % dummy vars for this grouping var + thisname = varinfo.varnames{varnum}; + if thispwr>1 + G = G .^ thispwr; + thisname = sprintf('%s^%d',thisname,thispwr); + end + tdum = termcross(G,tdum); % combine G into term dummy vars + + % Construct the term name and constraints matrix + if nargout>1 + if (isempty(tn)) + tn = thisname; + if ~isnested + tconstr = varinfo.vconstr{varnum}; + end + else + tn = [tn '*' thisname]; + if ~isnested + tconstr = [kron(varinfo.vconstr{varnum},eye(size(tconstr,2))); + kron(eye(length(varinfo.vconstr{varnum})),tconstr)]; + end + end + end + + if varidx1 + % If the rest of this term is computed, take advantage of that + prevterms = termlist(sindex(1:j-1),:); + oldtm = find(all(prevterms(:,tm) == repmat(pwrs,size(prevterms,1),1),2)); + oldtm = oldtm(~any(prevterms(oldtm,~tm),2)); + if (length(oldtm) > 0) + k = sindex(oldtm(1)); + tdum = termcross(termdum{k}, tdum); + if nargout>1 + if ~isnested + oconstr = termconstr{k}; + tconstr = [kron(tconstr, eye(size(oconstr,2))); + kron(eye(size(tconstr,2)), oconstr)]; + end + tn = [tn '*' tnames{k}]; + df0 = df0 * dfterm0(k); + end + break; + end + end +end +if (isempty(tn)), tn = 'Constant'; end +end + +% ---------------------------- +function [dum,auxtermlist,auxtermdum] =findtermdum(tms,termlist,termdum,... + auxtermlist,auxtermdum,varinfo) + + +nterms = size(tms,1); +dum = cell(nterms,1); +for j=1:nterms + tm = tms(j,:); + + % Try to find this term among the terms in the model + oldtm = find(all(termlist == repmat(tm,size(termlist,1),1),2)); + if ~isempty(oldtm) + dum{j} = termdum{oldtm(1)}; + continue + end + + % Try to find this term among the auxiliary terms + oldtm = find(all(auxtermlist == repmat(tm,size(auxtermlist,1),1),2)); + if ~isempty(oldtm) + dum{j} = auxtermdum{oldtm(1)}; + continue + end + + % Create an auxiliary term + dum{j} = maketerm(tm,[],varinfo,0); + + auxtermdum{end+1} = dum{j}; + auxtermlist(end+1,:) = tm; +end +end + +% --------------------------- +function m = nest_d2i(nested) +% Take matrix specifying at least direct nesting relationships, and +% return matrix that includes all indirect nesting relationships +m = nested; +n = size(m,1); +m0 = zeros(n); +while(~isequal(m,m0)) + m0 = m; + m = double(m | ((m*m) > 0)); +end +end \ No newline at end of file diff --git a/stat/binocdf.m b/stat/binocdf.m new file mode 100644 index 0000000..9ab3ce9 --- /dev/null +++ b/stat/binocdf.m @@ -0,0 +1,121 @@ +function y = binocdf(x,n,p) +%BINOCDF Binomial cumulative distribution function. +% Y=BINOCDF(X,N,P) returns the binomial cumulative distribution +% function with parameters N and P at the values in X. +% +% The size of Y is the common size of the input arguments. A scalar input +% functions as a constant matrix of the same size as the other inputs. +% +% The algorithm uses the cumulative sums of the binomial masses. + +% Reference: +% [1] M. Abramowitz and I. A. Stegun, "Handbook of Mathematical +% Functions", Government Printing Office, 1964, 26.1.20. + +% B.A. Jones 1-12-93 +% Copyright (c) 1993 by The MathWorks, Inc. +% $Revision: 1.1 $ $Date: 1993/05/24 18:53:29 $ + +if nargin < 3, + error('Requires three input arguments.'); +end + +scalarnp = (prod(size(n)) == 1 & prod(size(p)) == 1); + +%[errorcode x n p] = distchck(3,x,n,p); + +% if errorcode > 0 +% error('The arguments must be the same size or be scalars.'); +% end + +% Initialize Y to 0. +y=zeros(size(x)); + +% Y = 1 if X >= N +k = find(x >= n); +if any(k); + y(k) = ones(size(k)); +end + +% Compute Y when 0 < X < N. +xx = floor(x); +k = find(xx >= 0 & xx < n); + +% Accumulate the binomial masses up to the maximum value in X. +if any(k) + val = min(max(max(n)),max(max(xx))); + if scalarnp + tmp = cumsum(binopdf(0:val,n(1),p(1))); + y(k) = tmp(xx(k) + 1); + else + i = [0:val]'; + compare = i(:,ones(size(k))); + index(:) = xx(k); + index = index(:,ones(size(i)))'; + nbig(:) = n(k); + nbig = nbig(:,ones(size(i)))'; + pbig(:) = p(k); + pbig = pbig(:,ones(size(i)))'; + y0 = binopdf(compare,nbig,pbig); + indicator = find(compare > index); + y0(indicator) = zeros(size(indicator)); + y(k) = sum(y0); + end +end + +% Make sure that round-off errors never make P greater than 1. +k = find(y > 1); +if any(k) + y(k) = ones(size(k)); +end + +% Return NaN if any arguments are outside of their respective limits. +k1 = find(n < 0 | p < 0 | p > 1 | round(n) ~= n | x < 0); +if any(k1) + y(k1) = NaN * ones(size(k1)); +end + + + +function y = binopdf(x,n,p) +% BINOPDF Binomial probability density function. +% Y = BINOPDF(X,N,P) returns the binomial probability density +% function with parameters N and P at the values in X. +% Note that the density function is zero unless X is an integer. +% +% The size of Y is the common size of the input arguments. A scalar input +% functions as a constant matrix of the same size as the other inputs. + +% Reference: +% [1] M. Abramowitz and I. A. Stegun, "Handbook of Mathematical +% Functions", Government Printing Office, 1964, 26.1.20. + +% Copyright (c) 1993 by The MathWorks, Inc. +% $Revision: 1.1 $ $Date: 1993/05/24 18:53:34 $ + + +if nargin < 3, + error('Requires three input arguments'); +end + +% [errorcode x n p] = distchck(3,x,n,p); +% +% if errorcode > 0 +% error('The arguments must be the same size or be scalars.'); +% end +% +% Initialize Y to zero. +y = zeros(size(x)); + +% Binomial distribution is defined on positive integers less than N. +k = find(x >= 0 & x == round(x) & x <= n); +if any(k), + nk = round(exp(gammaln(n(k) + 1) - gammaln(x(k) + 1) - gammaln(n(k) - x(k) + 1))); + y(k) = nk .* p(k) .^x(k) .* (1 - p(k)) .^ (n(k) - x(k)); +end + +k1 = find(n < 0 | p < 0 | p > 1 | round(n) ~= n); +if any(k1) + y(k1) = NaN * ones(size(k1)); +end + diff --git a/stat/chi2_cdf.m b/stat/chi2_cdf.m new file mode 100644 index 0000000..c60515f --- /dev/null +++ b/stat/chi2_cdf.m @@ -0,0 +1,32 @@ +function [y]=chi2_cdf(x,dof,invcdf) +%chi2_cdf - Cumulative Distribution Function (CDF) of Chi² (Chi square) distribution +% [P]=chi2_cdf(chi2,dof) +% To get the Chi2 from the p-value (i.e. inverse cdf): +% Chi2 = chi2_cdf(P,-dof,1) +% +% KND, 2011 + +if nargin<3 + invcdf=0; +end +if dof<0 + dof=-dof; + invcdf=1; +end +if invcdf + y=ones(size(x)); + y(x==0)=0; + y(x==1)=Inf; + y(x<0)=NaN; + for i=find(y==1); + fun = @(y)(abs(chi2_cdf(y,dof)-x(i))); + % Guess starting point from normal approximation + y0 = dof+2*sqrt(dof)*erfcinv(2*x(i)); + y0 = max(0,y0); + [y(i),fval,flag,o]=fminsearch(fun,y0); + end +else + y = gammainc(x/2,dof/2); + y(x<0)=NaN; +end +return \ No newline at end of file diff --git a/stat/cohen_d.m b/stat/cohen_d.m new file mode 100644 index 0000000..04a3f2a --- /dev/null +++ b/stat/cohen_d.m @@ -0,0 +1,55 @@ +function [d,h,sigma_d,r2]=cohen_d(X1,X2,variant,parameters) +% cohen_d() - Computes Cohen's d' effect size and variants +% [d]=cohen_d(X1,X2) +% [d]=cohen_d(X1,X2,variant) with 'variant' being: +% 'none' (default) +% 'winsor' +% [d,h]=cohen_d(...) also compute Hedge's corrected d +% [d,h,sigma_d]=cohen_d(...) outputs sigma_d the S.D. of d so that +% Conf. Interval for d = d +/- 1.96*sigma_d +% +% [d,h,sigma_d,r2]=cohen_d(...) outputs r2 the percent variance explained + +if nargin<3 + options =[]; +end + +m2=0; +n2=0; +v2=0; +if nargin>1 + if isvector(X1) & isvector(X2) + X2=X2(:); + X1=X1(:); + end + m2=mean(X2); + n2=size(X2,1); + v2=var(X2); +elseif isvector(X1) + X1=X(:); +end + +if nargin>2 + switch lower(variant) + case 'none' + case {'winsor', 'winsorized', 'trimmed'} + +end +end + +m1=mean(X1); +n1=size(X1,1); +v1=var(X1); + +n = n1+n2; +pooled_v = ((n1-1)*v1 + (n2-1)*v2)./(n-2); + +d = (m1 - m2)./sqrt(pooled_v); + +% Hedges and Olkin's correction for small samples +% Hedges, L. and Olkin, I. (1985) +% Statistical Methods for Meta-Analysis. +% New York: Academic Press. +h = d*(1-3/(4*n-9)); +sigma_d = sqrt(n/n1/n2 + d.^2/2/n); +r2 = d.^2./(d.^2+4); diff --git a/stat/coincidence_matrix.m b/stat/coincidence_matrix.m new file mode 100644 index 0000000..b063e58 --- /dev/null +++ b/stat/coincidence_matrix.m @@ -0,0 +1,99 @@ +function [C] = coincidence_matrix(X) +levx = (unique(X(~isnan(X)))); +nval = numel(x); +cm = zeros(nval); +mu = no - sum(isnan(R)); + +P = nchoosek(r,2); +P = [ P ; fliplr(P) ]; +P = sortrows([ [r(:) r(:)] ; P ]); +np = size(P,1); +%Coincidence values +C = repmat(R, [ 1 1 np]) == permute(repmat(P',[1 1 nm ]), [1 3 2]); +C = squeeze(sum(all(C),2)) +% ... into matrix +C = reshape(C,[nr nr]); +C = C' + C; +n = 2*nm; +nc = sum(C); +dimx = size(X); + +% http://pbil.univ-lyon1.fr/library/concord/R/concord +% # calculates the coincidence matrix for the kripp.alpha() function +% +% coincidence.matrix<-function(x) { +% levx<-(levels(as.factor(x))) +% nval<-length(levx) +% # set up a coincidence matrix to hold the match/mismatch data +% cm<-matrix(rep(0,nval*nval),nrow=nval) +% dimx<-dim(x) +% # calculate correction factor (?) for data with missing values +% vn<-function(datavec) sum(!is.na(datavec)) +% if(any(is.na(x))) mc<-apply(x,2,vn)-1 +% else mc<-rep(1,dimx[2]) +% for(col in 1:dimx[2]) { +% for(i1 in 1:(dimx[1]-1)) { +% for(i2 in (i1+1):dimx[1]) { +% if(!is.na(x[i1,col]) && !is.na(x[i2,col])) { +% index1<-which(levx==x[i1,col]) +% index2<-which(levx==x[i2,col]) +% cm[index1,index2]<-cm[index1,index2]+(1+(index1==index2))/mc[col] +% if(index1 != index2) cm[index2,index1]<-cm[index1,index2] +% } +% } +% } +% } +% nmv<-sum(apply(cm,2,sum)) +% return(list(statistic=NA,coincidence.matrix=cm,data.values=levx,nmatchval=nmv)) +% } +% +% # calculates Krippendorff's alpha +% +% kripp.alpha<-function(x,method="nominal") { +% if(!missing(x)) { +% cm<-coincidence.matrix(x) +% dimcm<-dim(cm$coincidence.matrix) +% # upper triangle of the coincidence matrix as a vector +% utcm<-as.vector(cm$coincidence.matrix[upper.tri(cm$coincidence.matrix)]) +% # diagonal of the coincidence matrix +% diagcm<-diag(cm$coincidence.matrix) +% # sum of diagonal elements of coincidence matrix +% occ<-sum(diagcm) +% # the marginal sums for the coincidence matrix +% nc<-apply(cm$coincidence.matrix,1,sum) +% # calculate this term to simplify +% ncnc<-sum(nc*(nc-1)) +% # need the data values for interval and ratio methods +% dv<-as.numeric(cm$data.values) +% diff2<-rep(0,length(utcm)) +% ncnk<-rep(0,length(utcm)) +% ck<-1 +% for(k in 2:dimcm[2]) { +% for(c in 1:(k-1)) { +% ncnk[ck]<-nc[c]*nc[k] +% if(method == "nominal") diff2[ck]<-1 +% if(method == "ordinal") { +% diff2[ck]<-nc[c]/2 +% if(k > (c+1)) { +% for(g in (c+1):(k-1)) { +% diff2[ck]<-diff2[ck]+nc[g] +% } +% } +% diff2[ck]<-diff2[ck]+nc[k]/2 +% diff2[ck]<-diff2[ck]^2 +% } +% if(method == "interval") diff2[ck]<-(dv[c]-dv[k])^2 +% if(method == "ratio") { +% diff2[ck]<-(dv[c]-dv[k])^2/(dv[c]+dv[k])^2 +% } +% ck<-ck+1 +% } +% } +% cm$statistic<-1-(cm$nmatchval-1)*sum(utcm*diff2)/sum(ncnk*diff2) +% return(cm) +% } +% else { +% cat("Usage: kripp.alpha(x,method=c(\"nominal\",\"ordinal\",\"interval\",\"ratio\"))\n") +% cat("\twhere x is a classifier by object matrix of classifications\n\n") +% } +% } diff --git a/stat/compare_anovas.m b/stat/compare_anovas.m new file mode 100644 index 0000000..9b28d9b --- /dev/null +++ b/stat/compare_anovas.m @@ -0,0 +1,3 @@ + + +stats=Compute_anova_Optimized(dim_to_get,X,sujets,Logique,1,signedF, minusLogp); \ No newline at end of file diff --git a/stat/cronbach.m b/stat/cronbach.m new file mode 100644 index 0000000..daf658c --- /dev/null +++ b/stat/cronbach.m @@ -0,0 +1,46 @@ +function a=cronbach(X) +%cronbach - Calculates the Cronbach's alpha of a data set X. +% +% a=cronbach(X) +% a is the Cronbach's alpha. +% X is the data set (Observations by Items). +% + +% Reference: +% Cronbach L J (1951): Coefficient alpha and the internal structure of +% tests. Psychometrika 16:297-333 + +a=1/(1-1/size(X,2))*(1-sum(var(X))./var(sum(X,2))); + +% +% % Alexandros Leontitsis +% % Department of Education +% % University of Ioannina +% % Ioannina +% % Greece +% % +% % e-mail: leoaleq@yahoo.com +% % Homepage: http://www.geocities.com/CapeCanaveral/Lab/1421 +% % +% % June 10, 2005. +% +% if nargin<1 | isempty(X)==1 +% error('You shoud provide a data set.'); +% else +% % X must be a 2 dimensional matrix +% if ndims(X)~=2 +% error('Invalid data set.'); +% end +% end +% +% +% % Calculate the number of items +% k=size(X,2); +% +% % Calculate the variance of the items' sum +% VarTotal=; +% +% % Calculate the item variance +% SumVarX=; +% +% % Calculate the Cronbach's alpha diff --git a/stat/distributionPlot/countEntries.m b/stat/distributionPlot/countEntries.m new file mode 100644 index 0000000..8f954f0 --- /dev/null +++ b/stat/distributionPlot/countEntries.m @@ -0,0 +1,110 @@ +function [uniqueEntries,numberOfOccurences,whereIdx] = countEntries(m,isRow, keepNaN) +%COUNTENTRIES returns all unique entries (sorted) in the array m and how many times the respective entries occured +% +%SYNOPSIS [uniqueEntries,numberOfOccurences] = countEntries(m,isRow) +% +%INPUT m : any matrix (not cells or structs) +% isRow(opt) : should rows be counted or not [1/{0}] +% (if it's cols, transpose m before calling the function!) +% keepNaN (opt) : count NaN as entry? [{1}/0] If 0, NaNs (or +% NaN-containing rows) are removed after sorting, so +% that whereIdx still refers to the original position +% of the uniqueEntries in the input array. +% +%OUTPUT uniqueEntries : unique(m) +% numberOfOccurences : how many times the unique entries appear in m +% whereIdx : where in m do the entries appear? (m = uniqueEntries(whereIdx,:)) +% +%c: 11/03, jonas +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + +%---test input +if iscell(m) || isstruct(m) + error('cells and structs are not supportet as input'); +end + +if nargin < 2 || isempty(isRow) + doRow = 0; +else + if isRow == 1; + doRow = 1; + elseif isRow == 0 + doRow = 0; + else + error('input argument isRow has to be 1 or 0!') + end +end +if nargin < 3 || isempty(keepNaN) + keepNaN = true; +end +%---end test input + + + +if ~doRow %do the fast method + + %make m into a vector + m = m(:); + + %get unique Entries. sort them first, though + m = sort(m); + + [uniqueEntries, uniqueIdx, whereIdx] = unique(m); + + if ~keepNaN + % remove NaN, inf + badIdx = find(~isfinite(uniqueEntries)); + uniqueEntries(badIdx) = []; + uniqueIdx(badIdx) = []; + whereIdx(ismember(whereIdx,badIdx)) = []; + end + + %uniqueIdx returns the last occurence of the respective unique entry + %having sorted m before, we can now count the number of occurences + if size(uniqueEntries,1)>size(uniqueEntries,2) + uniqueIdx = [0;uniqueIdx]; + else + uniqueIdx = [0,uniqueIdx]; + end + + numberOfOccurences = diff(uniqueIdx); + +else %do it the complicated way + + %we do not care about the ordering of the matrix here: if the user + %specified rows, he/she wanted a columnVector as output (or should read the help) + [uniqueEntries, dummy, uniqueIdx] = unique(m,'rows'); + + %rember output + whereIdx = uniqueIdx; + + if ~keepNaN + % remove NaN, inf + badIdx = find(any(~isfinite(uniqueEntries),2)); + uniqueEntries(badIdx,:) = []; + whereIdx(ismember(whereIdx,badIdx)) = []; + uniqueIdx = whereIdx; + end + + %uniqueIdx returns the indexList where uniqueEntriy #x occurs. + %We will now sort this list and take a diff to find where this index + %changes. + %adding zero and length(uniqueIndex) to the vector, we can now via + %another diff see how many entries there are (see example) + + %example m: [11,11,22,33,33,22,22,22,44,11] + %corresponding uniqueEntries, uniqueIdx: [11,22,33,44] / [1 1 2 3 3 2 2 2 4 1] + + %sort: [1 1 1 2 2 2 2 3 3 4] + sortedIdx = sort(uniqueIdx); + + %diff: [0 0 1 0 0 0 1 0 1] + sortedIdxDiff = diff(sortedIdx); + + %find and add entries: [0 3 7 9 10] + changeValueIdx = [0;find(sortedIdxDiff);length(uniqueIdx)]; + + %diff again for the numberOfOccurences: [3 4 2 1] + numberOfOccurences = diff(changeValueIdx); +end \ No newline at end of file diff --git a/stat/distributionPlot/distributionPlot.m b/stat/distributionPlot/distributionPlot.m new file mode 100644 index 0000000..8bfcc21 --- /dev/null +++ b/stat/distributionPlot/distributionPlot.m @@ -0,0 +1,270 @@ +function handles = distributionPlot(varargin) +%DISTRIBUTIONPLOT plots distributions similar to boxplot +% +% SYNOPSIS: handles = distributionPlot(data,distWidth,showMM,xNames,histOpt,divFactor,invert) +% handles = distributionPlot(ah,...) +% +% INPUT data : cell array of length nData (with data shaped as vectors) or +% m-by-nData array of values +% distWidth : (opt) width of distributions. 1 means that the maxima +% of two adjacent distributions might touch. Negative numbers +% indicate that the distributions should have constant width, i.e +% the density is only expressed through greylevels. +% Values between 1 and 2 are like values between 0 and 1, except +% that densities are not expressed via graylevels. Default: 1.9 +% showMM : (opt) if 1, mean and median are shown as red circles and +% green squares, respectively. Default: 1 +% 2: only mean +% 3: only median +% xNames : (opt) cell array of length nData containing x-tick names +% (instead of the default '1,2,3') +% histOpt : (opt) histogram type to plot +% 0 : use hist command (no smoothing, fixed number of +% bins) +% 1 : smoothened histogram using ksdensity with +% Epanechnikov-kernel. Default. +% 2 : histogram command (no smoothing, automatic +% determination of bin width) +% divFactor : (opt) Parameter dependent on histOpt. +% histOpt == 0: divFactor = # of bins. Default: 25. +% Alternatively, pass a vector which will be +% interpreted as bin centers. +% histOpt == 1: divFactor decides by how much the default +% kernel-width is multiplied in order to avoid an +% overly smooth histogram. Default: 1/2 +% histOpt == 2: divFactor decides by how much the +% automatic bin width is multiplied in order to have +% more (<1) or less (>1) detail. Default: 1 +% histOpt == 3: divFacor specifies the bin locations +% invert : (opt) if 1, image will be white on black. Default: 0 +% ah (opt) axes handle to plot the distribution. Default: gca +% +% OUTPUT handles : 1-by-3 cell array with patch-handles for the +% distributions, plot handles for mean/median, and the +% axes handle +% +% REMARKS +% +% created with MATLAB ver.: 7.6.0.324 (R2008a) on Windows_NT +% +% created by: Jonas Dorn +% DATE: 08-Jul-2008 +% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%==================================== +%% TEST INPUT +%==================================== + +% set defaults +def_xNames = []; +def_showMM = 1; +def_distWidth = 1.9; +def_histOpt = 1; +def_divFactor = [25,2,1]; +def_invert = false; +useGray = true; + +if nargin == 0 + error('not enough input arguments') +end + +% check for axes handle +if ~iscell(varargin{1}) && length(varargin{1}) == 1 && ... + ishandle(varargin{1}) && strcmp(get(varargin{1},'Type'),'axes') + ah = varargin{1}; + data = varargin{2}; + varargin(1:2) = []; + newAx = false; +else + ah = gca; + data = varargin{1}; + varargin(1) = []; + newAx = true; +end + +% check data. If not cell, convert +if ~iscell(data) + [nPoints,nData] = size(data); + data = mat2cell(data,nPoints,ones(nData,1)); +else + % get nData + data = data(:); + nData = length(data); + % make sure all are vectors + badCol = ~cellfun(@isvector,data); + if any(badCol) + warning('DISTRIBUTIONPLOT:AUTORESHAPE',... + 'Elements %s of the cell array are not vectors. They will be reshaped automatically',... + num2str(find(badCol)')); + data(badCol) = cellfun(@(x)(x(:)),data(badCol),'UniformOutput',false); + end +end + + +% check for names, defaults +if ~isempty(varargin) && ~isempty(varargin{1}) + distWidth = varargin{1}; + if distWidth == 0 + error('distWidth==0 will not plot anything') + end +else + distWidth = def_distWidth; +end +if distWidth > 1 + distWidth = distWidth - 1; + useGray = false; +end +if length(varargin) > 1 && ~isempty(varargin{2}) + showMM = varargin{2}; +else + showMM = def_showMM; +end +if length(varargin) > 2 && ~isempty(varargin{3}) + xNames = varargin{3}; +else + xNames = def_xNames; +end +if length(varargin) > 3 && ~isempty(varargin{4}) + histOpt = varargin{4}; +else + histOpt = def_histOpt; +end +if length(varargin) > 4 && ~isempty(varargin{5}) + divFactor = varargin{5}; +else + divFactor = def_divFactor(histOpt+1); +end +if length(varargin) > 5 && ~isempty(varargin{6}) + invert = varargin{6}; +else + invert = def_invert; +end + + +% set hold on +holdState = get(ah,'NextPlot'); +set(ah,'NextPlot','add'); + +% if new axes: invert +if newAx && invert + set(gca,'Color','k') +end + +%=================================== + + + +%=================================== +%% PLOT DISTRIBUTIONS +%=================================== + +% assign output +hh = cell(nData,1); +[m,md] = deal(nan(nData,1)); + +% get base x-array +xBase = abs(distWidth) .* [-0.5;0.5;0.5;-0.5]; + +% loop through data. Prepare patch input, then draw patch into gca +for iData = 1:nData + currentData = data{iData}; + % only plot if there is some finite data + if ~isempty(currentData) && any(isfinite(currentData)) + + switch histOpt + case 0 + % use hist + [xHist,yHist] = hist(currentData,divFactor); + + case 1 + % use ksdensity + + % make histogram (use ksdensity for now) + % x,y are switched relative to normal histogram + [xHist,yHist,u] = ksdensity(currentData,'kernel','epanechnikov'); + % take smaller kernel to avoid over-smoothing + if divFactor ~= 1 + [xHist,yHist] = ksdensity(currentData,'kernel','epanechnikov','width',u/divFactor); + end + + case 2 + % use histogram + [xHist,yHist] = histogram(currentData,divFactor); + end + + % find y-step + dy = min(diff(yHist)); + + % create x,y arrays + nPoints = length(xHist); + xArray = repmat(xBase,1,nPoints); + yArray = repmat([-0.5;-0.5;0.5;0.5],1,nPoints); + + % x is iData +/- almost 0.5, multiplied with the height of the + % histogram + if distWidth > 0 + xArray = xArray.*repmat(xHist,4,1)./max(xHist) + iData; + else + xArray = xArray + iData; + end + + % yData is simply the bin locations + yArray = repmat(yHist,4,1) + dy*yArray; + + % add patch + axes(ah); + if invert + if useGray + hh{iData} = patch(xArray,yArray,repmat(xHist/max(xHist),[4,1,3])); + else + hh{iData} = patch(xArray,yArray,'w'); + end + else + if useGray + hh{iData} = patch(xArray,yArray,repmat(1-xHist/max(xHist),[4,1,3])); + else + hh{iData} = patch(xArray,yArray,'k'); + end + end + set(hh{iData},'EdgeColor','none') + + m(iData) = nanmean(currentData); + md(iData) = nanmedian(currentData); + end +end % loop + +if showMM + % plot mean, median. Mean is filled red circle, median is green square + if any(showMM==[1,2]) + mh = plot(1:nData,m,'or','MarkerFaceColor','r'); + end + if any(showMM==[1,3]) + mdh = plot(1:nData,md,'sg'); + end +end + +% if ~empty, use xNames +set(ah,'XTick',1:nData); +if ~isempty(xNames) + set(ah,'XTickLabel',xNames) +end +% have plot start/end properly +xlim([0,nData+1]) + +%========================== + + +%========================== +%% CLEANUP & ASSIGN OUTPUT +%========================== + +if nargout > 0 + handles{1} = hh; + if showMM + handles{2} = [mh;mdh]; + end + handles{3} = ah; +end + +set(ah,'NextPlot',holdState); \ No newline at end of file diff --git a/stat/distributionPlot/histogram.m b/stat/distributionPlot/histogram.m new file mode 100644 index 0000000..dc1bfef --- /dev/null +++ b/stat/distributionPlot/histogram.m @@ -0,0 +1,207 @@ +function [N,X,sp] = histogram(varargin) +% HISTOGRAM generates a histogram using the "optimal" number of bins +% +% If called with no output argument, histogram plots into the current axes +% +% SYNOPSIS [N,X,sp] = histogram(data,factor,normalize) +% [...] = histogram(data,'smooth') +% [...] = histogram(axesHandle,...) +% +% INPUT data: vector of input data +% factor: (opt) factor by which the bin-widths are multiplied +% if 'smooth' (or 's'), a smooth histogram will be formed. +% (requires the spline toolbox). For an alternative +% approach to a smooth histogram, see ksdensity.m +% if 'discrete' (or 'd'), the data is assumed to be a discrete +% collection of values. Note that if every data point is, +% on average, repeated at least 3 times, histogram will +% consider it a discrete distribution automatically. +% if 'continuous' (or 'c'), histogram is not automatically +% checking for discreteness. +% normalize : if 1 (default), integral of histogram equals number +% data points. If 0, height of bins equals counts. +% This option is exclusive to non-"smooth" histograms +% axesHandle: (opt) if given, histogram will be plotted into these +% axes, even if output arguments are requested +% +% OUTPUT N : number of points per bin (value of spline) +% X : center position of bins (sorted input data) +% sp : definition of the smooth spline +% +% REMARKS: The smooth histogram is formed by calculating the cumulative +% histogram, fitting it with a smoothening spline and then taking +% the analytical derivative. If the number of data points is +% markedly above 1000, the spline is fitting the curve too +% locally, so that the derivative can have huge peaks. Therefore, +% only 1000-1999 points are used for estimation. +% Note that the integral of the spline is almost exactly the +% total number of data points. For a standard histogram, the sum +% of the hights of the bins (but not their integral) equals the +% total number of data points. Therefore, the counts might seem +% off. +% +% WARNING: If there are multiples of the minimum value, the +% smooth histogram might get very steep at the beginning and +% produce an unwanted peak. In such a case, remove the +% multiple small values first (for example, using isApproxEqual) +% +% +% c: 2/05 jonas +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% test input +if nargin < 1 + error('not enough input arguments for histogram') +end + +% check for axes handle +if length(varargin{1}) == 1 && ishandle(varargin{1}); + axesHandle = varargin{1}; + varargin(1) = []; +else + % ensure compatibility to when axesHandle was given as last input + if nargin == 3 && ishandle(varargin{end}) && varargin{end} ~= 0 + axesHandle = varargin{end}; + varargin(end) = []; + else + axesHandle = 0; + end +end + +% assign data +numArgIn = length(varargin); +data = varargin{1}; +data = data(:); + +% check for non-finite data points +data(~isfinite(data)) = []; + +% check for "factor" +if numArgIn < 2 || isempty(varargin{2}) + factor = 1; +else + factor = varargin{2}; +end +if ischar(factor) + switch factor + case {'smooth','s'} + factor = -1; + case {'discrete','d'} + factor = -2; + case {'continuous','c'} + factor = -3; + otherwise + error('The only string inputs permitted for histogram.m are ''smooth'',''discrete'', or ''continuous''') + end +else + % check for normalize, but do so only if there is no "smooth". Note + % that numArgIn is not necessarily equal to nargin + if numArgIn < 3 || isempty(varargin{3}) + normalize = true; + else + normalize = varargin{3}; + end +end + +% doPlot is set to 1 for now. We change it to 0 below if necessary. +doPlot = 1; + +nData = length(data); +% check whether we do a standard or a smooth histogram +if factor ~= -1 + % check for discrete distribution + [xx,nn] = countEntries(data); + % consider the distribution discrete if there are, on average, 3 + % entries per bin + nBins = length(xx); + if factor == -2 || (factor ~= -3 && nBins*3 < nData) + % discrete distribution. + nn = nn'; + xx = xx'; + else + % not a discrete distribution + if nData < 20 + warning('HISTOGRAM:notEnoughDataPoints','Less than 20 data points!') + nBins = ceil(nData/4); + else + + % create bins with the optimal bin width + % W = 2*(IQD)*N^(-1/3) + interQuartileDist = diff(prctile(data,[25,75])); + binLength = 2*interQuartileDist*length(data)^(-1/3)*factor; + + % number of bins: divide data range by binLength + nBins = round((max(data)-min(data))/binLength); + + if ~isfinite(nBins) + nBins = length(unique(data)); + end + + end + + + + % histogram + [nn,xx] = hist(data,nBins); + % adjust the height of the histogram + if normalize + Z = trapz(xx,nn); + nn = nn * nData/Z; + end + + end + if nargout > 0 + N = nn; + X = xx; + doPlot = axesHandle; + end + if doPlot + if axesHandle + bar(axesHandle,xx,nn,1); + else + bar(xx,nn,1); + end + end + +else + % make cdf, smooth with spline, then take the derivative of the spline + + % cdf + xData = sort(data); + yData = 1:nData; + + % when using too many data points, the spline fits very locally, and + % the derivatives can still be huge. Good results can be obtained with + % 500-1000 points. Use 1000 for now + step = max(floor(nData/1000),1); + xData2 = xData(1:step:end); + yData2 = yData(1:step:end); + + % spline. Use strong smoothing + cdfSpline = csaps(xData2,yData2,1./(1+mean(diff(xData2))^3/0.0006)); + + % pdf is the derivative of the cdf + pdfSpline = fnder(cdfSpline); + + % histogram + if nargout > 0 + xDataU = unique(xData); + N = fnval(pdfSpline,xDataU); + X = xDataU; + % adjust the height of the histogram + Z = trapz(X,N); + N = N * nData/Z; + sp = pdfSpline; + % set doPlot. If there is an axesHandle, we will plot + doPlot = axesHandle; + end + % check if we have to plot. If we assigned an output, there will only + % be plotting if there is an axesHandle. + if doPlot + if axesHandle + plot(axesHandle,xData,fnval(pdfSpline,xData)); + else + plot(xData,fnval(pdfSpline,xData)); + end + end +end \ No newline at end of file diff --git a/stat/distributionfit.m b/stat/distributionfit.m new file mode 100644 index 0000000..5e3c86b --- /dev/null +++ b/stat/distributionfit.m @@ -0,0 +1,188 @@ +function [pdffit,offset,A,B,resnorm,h] = distributionfit(data,distribution,nbins) +%function [pdffit,offset,A,B,resnorm,h] = distributionfit(data,distribution,nbins) +%PURPOSE jdc rev. 06-jun-05 +% Fit one of three probability distributions (normal, lognormal, weibull) +% to input data vector. If the distribution is specified as 'best' the dis- +% tribution that best fits the data is selected automatically. +%INPUT +% If nargin==1, "distribution" is prompted for and entered interactively +% +% data - n x 1 or 1 x n input data vector +% distribution - probability distribution to fit to "data". Can +% be 'normal', 'lognormal', 'weibull', or 'best' ... default: 'best' +% nbins - number of bar-chart bins ......................... default: sqrt(length(data)) +%OUTPUT +% pdffit - fitted probability density function - n x 2 matrix with column 1 the +% x-values, column 2 the y values +% offset - amount by which the data was offset for lognormal or weibull fits +% (to satisfy the positive-definite requirements for these distributions). +% Note: this is roughly equivalent to fitting a 3- rather than a 2-parameter +% distribution. +% A,B - distribution parameters - mu and sigma for normal and lognormal distributions, +% scale and shape parameters for weibull distribution +% h - handles to the bar chart and probability density curve +% +%TYPICAL FUNCTION CALLS +% distributionfit(randn(10000,1)); +% distributionfit(wblrnd(2,3,10000,1)); +% distributionfit(wblrnd(2,3,10000,1),'weibull'); +% distributionfit(lognrnd(1.5,.5,10000,1),'lognormal'); +% distributionfit(lognrnd(1.5,.5,10000,1),'best'); +% distributionfit(lognrnd(1.5,.5,10000,1),'lognormal'); +% +%REFERENCE +% Statistics Toolbox Version 3.0.2, function HISTFIT.M + +data = data(~isnan(data)); +data = data(:); +ndata = length(data); +if nargin<3 | isempty(nbins), nbins = ceil(sqrt(ndata)); end +if nargin==1, + distID = menu('Choose a Distribution','Normal','Lognormal','Weibull','best'); +else + if strfind(lower(distribution),'lognormal'), distID = 2; + elseif strfind(lower(distribution),'normal' ), distID = 1; + elseif strfind(lower(distribution),'weibull' ), distID = 3; + elseif strfind(lower(distribution),'best' ), distID = 4; + elseif isempty(distribution), distID = 4; + end +end +switch distID + case 1, distribution = 'Normal'; + case 2, distribution = 'Lognormal'; + case 3, distribution = 'Weibull'; + case 4 + data = sort(data); + cdfe = (1:ndata)'/ndata; % experimental cdf + phat = mle(data,'distribution','Normal'); + A = phat(1); % for normal & lognormal, phat = [mu std], for weibull, = [A B] + B = phat(2); + cdft = cdf('Normal',data,A,B); % best-fit cdf + residuals = cdfe-cdft; + resnormNormal = residuals'*residuals; + %------------------------------------- + offset = -min(data)+10*eps; + offsetdata = data+offset; % zero-shift data so smallest value is 0+ + phat = mle(offsetdata,'distribution','Lognormal'); + A = phat(1); % for normal & lognormal, phat = [mu std], for weibull, = [A B] + B = phat(2); + cdft = cdf('Lognormal',offsetdata,A,B); % best-fit cdf + residuals = cdfe-cdft; + resnormLognormal = residuals'*residuals; + %------------------------------------- + phat = mle(offsetdata,'distribution','Weibull'); + A = phat(1); % for normal & lognormal, phat = [mu std], for weibull, = [A B] + B = phat(2); + cdft = cdf('Weibull',offsetdata,A,B); % best-fit cdf + residuals = cdfe-cdft; + resnormWeibull = residuals'*residuals; + %------------------------------------- + resnorms = [resnormNormal resnormLognormal resnormWeibull]; + distID = find(resnorms==min(resnorms)); + switch distID + case 1, distribution = 'Normal'; + case 2, distribution = 'Lognormal'; + case 3, distribution = 'Weibull'; + end +end +if distID==2 | distID==3, + offset = -min(data)+10*eps; + data = data+offset; % zero-shift data for lognormal and weibull fits so smallest value is 0+ +elseif distID==1, + offset = 0; +end +%---------------------------------------------------------------------- +figure +[n,xbin]=hist(data,nbins); +hh = bar(xbin,n,1); % get number of counts per bin and bin width +xd = get(hh,'Xdata'); % retrieve the x-coordinates of the bins. +rangex = max(xd(:)) - min(xd(:)); % find the bin range +binwidth = rangex/nbins; % find the width of each bin. +close(gcf); % close figure (will replot on probability scale) +%---------------------------------------------------------------------- +ch = get(0,'children'); +if isempty(ch), + figure(1); +else + figure(max(ch)+1); +end +set(0,'Units','inches'); +ss = get(0,'ScreenSize'); +set(0,'Units','pixels'); +width = 3; +height = 3; +edge = 0.25; +set(gcf,'Units','inches','Position',[ss(3)-width-edge ss(4)-height-edge width height],... + 'Color',[.8 .8 .8],'InvertHardCopy','off','PaperPosition',[1 1 width height]); +nscaled = n/(ndata*binwidth); % convert bin counts to probabilities +hh = bar(xbin,nscaled,1); % draw the probability-scaled bars +set(hh,'EdgeColor',[.6 .6 .6],'FaceColor',[.9 .9 .9]); +set(gca,'FontSize',8); +xlabel('Data'); +ylabel('Probability Density'); +%---------------------------------------------------------------------- +phat = mle(data,'distribution',distribution); % probability distribution parameter estimation +A = phat(1); % for normal & lognormal, phat = [mu std], for weibull, = [A B] +B = phat(2); +switch distID % get limits for plotting the best-fit pdf curve + case 1, + lolim = norminv(0.0001,A,B); + hilim = norminv(0.9999,A,B); + case 2, + lolim = logninv(0.0001,A,B); + hilim = logninv(0.9999,A,B); + case 3, + lolim = wblinv(0.0001,A,B); + hilim = wblinv(0.9999,A,B); +end +xpdf = (lolim:(hilim-lolim)/100:hilim); % construct the x-vector for the pdf curve +ypdf = pdf(distribution,xpdf,A,B); +hh1 = line(xpdf,ypdf,'Color','r','LineWidth',2); % overplot the histogram with the best-fit pdf curve +pdffit = [xpdf(:) ypdf(:)]; +%---------------------------------------------------------------------- +data = sort(data); % compute resnorm +cdfe = (1:ndata)'/ndata; % experimental cdf +cdft = cdf(distribution,data,A,B); % best-fit cdf +residuals = cdfe-cdft; +resnorm = residuals'*residuals; +%---------------------------------------------------------------------- +xlim = get(gca,'Xlim'); +ylim = get(gca,'Ylim'); +trd = text(xlim(2),ylim(2),distribution,'FontSize',8,'HorizontalAlignment','right','VerticalAlignment','bottom'); +ext = extent(trd); +tld = text(ext(1),ylim(2),'Distribution: ', 'FontSize',8,'HorizontalAlignment','right','VerticalAlignment','bottom'); + +trr = text(xlim(2),ylim(2)-ext(4),sprintf('%8.1e ',resnorm),'FontSize',8,'HorizontalAlignment','right','VerticalAlignment','middle'); +ext = extent(trr); +tll = text(ext(1),ylim(2)-ext(4),'Resnorm: ','FontSize',8,'HorizontalAlignment','right','VerticalAlignment','middle'); + +tt = text(xlim(2),ylim(2)-ext(4),sprintf('%4.2f ',A),'FontSize',8,'HorizontalAlignment','right','Visible','off'); +ext = extent(tt); + +i = 1; +switch distID + case {1,2} + if distID==2, + tl(i) = text(ext(1),ylim(2)-(i+1)*ext(4),'Zero Shift: '); + tr(i) = text(ext(1),ylim(2)-(i+1)*ext(4),sprintf('%+5.2f',offset)); + i = i+1; + end + tl(i) = text(ext(1),ylim(2)-(i+1)*ext(4),'Sigma: '); + tr(i) = text(ext(1),ylim(2)-(i+1)*ext(4),sprintf('%4.2f',B)); + i = i+1; + tl(i) = text(ext(1),ylim(2)-(i+1)*ext(4),'Mu: '); + tr(i) = text(ext(1),ylim(2)-(i+1)*ext(4),sprintf('%4.2f',A)); + case 3 + tl(i) = text(ext(1),ylim(2)-(i+1)*ext(4),'Zero Shift: '); + tr(i) = text(ext(1),ylim(2)-(i+1)*ext(4),sprintf('%+5.2f',offset)); + i = i+1; + tl(i) = text(ext(1),ylim(2)-(i+1)*ext(4),'scale: '); + tr(i) = text(ext(1),ylim(2)-(i+1)*ext(4),sprintf('%4.2f',A)); + i = i+1; + tl(i) = text(ext(1),ylim(2)-(i+1)*ext(4),'shape: '); + tr(i) = text(ext(1),ylim(2)-(i+1)*ext(4),sprintf('%4.2f',B)); +end +set(tl,'Color','k','FontSize',8,'HorizontalAlignment','right','VerticalAlignment','middle'); +set(tr,'Color','k','FontSize',8,'HorizontalAlignment','left', 'VerticalAlignment','middle'); +%---------------------------------------------------------------------- +h = [hh; hh1]; diff --git a/stat/dunnsidak.m b/stat/dunnsidak.m new file mode 100644 index 0000000..feeaa0a --- /dev/null +++ b/stat/dunnsidak.m @@ -0,0 +1,34 @@ +function [sig,alpha]=dunnsidak(alpha, n) +% DUNNSIDAK - Dunn-Sidak adjustment of alpha level for multiple comparisons +% alpha2=dunnsidak(alpha1, n) computes the new alpha2 to use when running +% n tests in order to achieve a familywise error rate of alpha +% alpha = 1 - (1-alpha1).^(1./n) +% +% [sig,alphas]=holm(alpha,p) assess the significance of the p-values +% coming from multiple tests according to the Dunn-Sidak correction +% yielding a familywise error rate of alpha. p being a vector of +% p-values, sig will be a logical vector of the same size. +% +% See also: holm, hochberg + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2007 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2007-11-05 Creation +% +% ----------------------------- Script History --------------------------------- + +if numel(n)==1 & n>=1 + alpha=1-(1-alpha).^(1./n); + sig=alpha; + return +end +p=n; +n=numel(p); +alpha=dunnsidak(alpha,n); +sig=p<=alpha; \ No newline at end of file diff --git a/stat/effectsizes_to_errorrates.m b/stat/effectsizes_to_errorrates.m new file mode 100644 index 0000000..92a0e4c --- /dev/null +++ b/stat/effectsizes_to_errorrates.m @@ -0,0 +1,47 @@ +% +% What is your error rate with a D prime of +% http://www.sportsci.org/resource/stats/effectmag.html +% +% NB: +% To take a familiar example of a physical sex difference, the effect size +% for U.S. male-female differences in height is very large at d = 1.93.) +% I found that it means approx. 1/6 men would be categorized as women in a +% cutoff, and 2.7% are below women's mean height) + +clear d r m m2 D v er +D= [0:.1:2 2:1:5] % 1.93 +for j=1:numel(D) + r=zeros(1,100); + for i=1:100 + x=randn(10000,1); + y=randn(10000,1)+D(j); + d(i,j)=cohen_d(x,y); + % percentage below cutoff + er(i,1)=mean([ymean([x;y])]); + er(i,2)=mean([ymean([y])]); + er(i,3)=mean(y=0) + varargout{1} = spm_Fcdf(x,v,w); +else + varargout{1} = spm_invFcdf(1+x,v,w); +end + +return + + +function F = spm_Fcdf(x,v,w) +% Cumulative Distribution Function (CDF) of F (Fisher-Snedecor) distribution +% FORMAT F = spm_Fpdf(x,df) +% FORMAT F = spm_Fpdf(x,v,w) +% +% x - F-variate (F has range [0,Inf) ) +% df - Degrees of freedom, concatenated along last dimension +% Eg. Scalar (or column vector) v & w. Then df=[v,w]; +% v - Shape parameter 1 / numerator degrees of freedom (v>0) +% w - Shape parameter 2 / denominator degrees of freedom (w>0) +% F - CDF of F-distribution with [v,w] degrees of freedom at points x +%_______________________________________________________________________ +% +% spm_Fcdf implements the Cumulative Distribution Function of the F-distribution. +% +% Definition: +%----------------------------------------------------------------------- +% The CDF F(x) of the F distribution with degrees of freedom v & w, +% defined for positive integer degrees of freedom v & w, is the +% probability that a realisation of an F random variable X has value +% less than x F(x)=Pr{X0 & w>0, and for x in [0,Inf) (See Evans et al., Ch16). +% +% Variate relationships: (Evans et al., Ch16 & 37) +%----------------------------------------------------------------------- +% The square of a Student's t variate with w degrees of freedom is +% distributed as an F-distribution with [1,w] degrees of freedom. +% +% For X an F-variate with v,w degrees of freedom, w/(w+v*X^2) has +% distributed related to a Beta random variable with shape parameters +% w/2 & v/2. +% +% Algorithm: +%----------------------------------------------------------------------- +% Using the relationship with the Beta distribution: The CDF of the +% F-distribution with v,w degrees of freedom is related to the +% incomplete beta function by: +% Pr(X1; +if sum(xa)>1 & any(any(diff(as(xa,:)),1)) + error('non-scalar args must match in size'), end + +%-Computation +%----------------------------------------------------------------------- +%-Initialise result to zeros +F = zeros(rs); + +%-Only defined for strictly positive v & w. Return NaN if undefined. +md = ( ones(size(x)) & v>0 & w>0 ); +if any(~md(:)), F(~md) = NaN; + warning('Returning NaN for out of range arguments'), end + +%-Non-zero where defined and x>0 +Q = find( md & x>0 ); +if isempty(Q), return, end +if xa(1), Qx=Q; else Qx=1; end +if xa(2), Qv=Q; else Qv=1; end +if xa(3), Qw=Q; else Qw=1; end + +%-Compute +F(Q) = 1 - betainc(w(Qw)./(w(Qw) + v(Qv).*x(Qx)),w(Qw)/2,v(Qv)/2); + + + +function x = spm_invFcdf(F,v,w) +% Inverse Cumulative Distribution (CDF) of F (Fisher-Snedecor) distribution +% FORMAT x = spm_invFpdf(F,df) +% FORMAT x = spm_invFpdf(F,v,w) +% +% F - CDF (lower tail p-value) +% df - Degrees of freedom, concatenated along last dimension +% Eg. Scalar (or column vector) v & w. Then df=[v,w]; +% v - Shape parameter 1 / numerator degrees of freedom (v>0) +% w - Shape parameter 2 / denominator degrees of freedom (w>0) +% x - F-variate (F has range [0,Inf) ) +%_______________________________________________________________________ +% +% spm_Fcdf implements the inverse Cumulative Distribution Function +% for the F-distribution. +% +% Definition: +%----------------------------------------------------------------------- +% The CDF F(x) of the F distribution with degrees of freedom v & w, +% defined for positive integer degrees of freedom v & w, is the +% probability that a realisation of an F random variable X has value +% less than x F(x)=Pr{X0 & w>0, and for x in [0,Inf) (See Evans et al., Ch16). +% +% Variate relationships: (Evans et al., Ch16 & 37) +%----------------------------------------------------------------------- +% The square of a Student's t variate with w degrees of freedom is +% distributed as an F-distribution with [1,w] degrees of freedom. +% +% For X an F-variate with v,w degrees of freedom, w/(w+v*X^2) has +% distribution related to a Beta random variable with shape parameters +% w/2 & v/2, as described below. +% +% Algorithm: +%----------------------------------------------------------------------- +% Using the routine spm_invBcdf for the Beta distribution, with +% appropriate parameters: The CDF of the F-distribution with v,w +% degrees of freedom is related to the incomplete beta function by: +% Pr(X1; +if sum(xa)>1 & any(any(diff(as(xa,:)),1)) + error('non-scalar args must match in size'), end + +%-Computation +%----------------------------------------------------------------------- +%-Initialise result to zeros +x = zeros(rs); + +%-Only defined for F in [0,1] & strictly positive v & w. +% Return NaN if undefined. +md = ( F>=0 & F<=1 & v>0 & w>0 ); +if any(~md(:)), x(~md) = NaN; + warning('Returning NaN for out of range arguments'), end + +%-Special cases: x=0 when F=0, x=Inf when F=1 +x(md & F==1) = Inf; + +%-Compute where defined & not special case +Q = find( md & F>0 & F<1 ); +if isempty(Q), return, end +if xa(1), QF=Q; else QF=1; end +if xa(2), Qv=Q; else Qv=1; end +if xa(3), Qw=Q; else Qw=1; end + +%-Compute +bQ = spm_invBcdf(1-F(QF),w(Qw)/2,v(Qv)/2); +x(Q) = (w(Qw)./bQ -w(Qw))./v(Qv); + +function x = spm_invBcdf(F,v,w,tol) +% Inverse Cumulative Distribution Function (CDF) of Beta distribution +% FORMAT x = spm_invBcdf(F,v,w,tol) +% +% F - CDF (lower tail p-value) +% v - Shape parameter (v>0) +% w - Shape parameter (w>0) +% x - Beta ordinates at which CDF F(x)=F +% tol - tolerance [default 10^-6] +%__________________________________________________________________________ +% +% spm_invBcdf implements the inverse Cumulative Distribution Function +% for Beta distributions. +% +% Returns the Beta-variate x such that Pr{X0 & w>0 and for x in [0,1] (See Evans et al., Ch5). +% The Cumulative Distribution Function (CDF) F(x) is the probability +% that a realisation of a Beta random variable X has value less than +% x. F(x)=Pr{X1; +if sum(xa)>1 & any(any(diff(as(xa,:)),1)) + error('non-scalar args must match in size'), end + +%-Computation - Undefined and special cases +%----------------------------------------------------------------------- +%-Initialise result to zeros +x = zeros(rs); + +%-Only defined for F in [0,1] & strictly positive v & w. +% Return NaN if undefined. +md = ( F>=0 & F<=1 & v>0 & w>0 ); +if any(~md(:)), x(~md) = NaN; + warning('Returning NaN for out of range arguments'), end + +%-Special cases: x=0 when F=0, x=1 when F=1 +x(md & F==1) = 1; + +%-Compute where defined & not special case +%----------------------------------------------------------------------- +Q = find( md & F>0 & F<1 ); +if isempty(Q), return, end +if xa(1), FQ=F(Q); FQ=FQ(:); else, FQ=F*ones(length(Q),1); end +if xa(2), vQ=v(Q); vQ=vQ(:); else, vQ=v*ones(length(Q),1); end +if xa(3), wQ=w(Q); wQ=wQ(:); else, wQ=w*ones(length(Q),1); end +%-?Q=?Q(:) stuff is to avoid discrepant orientations of vector arguments! + +%-Interval bisection +%----------------------------------------------------------------------- +a = zeros(length(Q),1); fa = a-FQ; +b = ones(length(Q),1); fb = b-FQ; +i = 0; +xQ = a+1/2; +QQ = 1:length(Q); + +while length(QQ) & i 0; + a(QQ(mQQ)) = xQ(QQ(mQQ)); fa(QQ(mQQ)) = fxQQ(mQQ); + b(QQ(~mQQ)) = xQ(QQ(~mQQ)); fb(QQ(~mQQ)) = fxQQ(~mQQ); + xQ(QQ) = a(QQ) + (b(QQ)-a(QQ))/2; + QQ = QQ( (b(QQ)-a(QQ))>tol ); + i = i+1; +end + +if i==maxIt, warning('convergence criteria not reached - maxIt reached'), end + +x(Q) = xQ; diff --git a/stat/friedman_posthoc.m b/stat/friedman_posthoc.m new file mode 100644 index 0000000..08fdc2b --- /dev/null +++ b/stat/friedman_posthoc.m @@ -0,0 +1,106 @@ +function [p]=friedman_posthoc + + + +% /* +% * Post-hoc analyses for a Friedman test of mean ranks in dependent samples +% * see Schaich & Hamerle (1984) and Conover (1971, 1980) as cited by Bortz, Lienert & Boehnke (2000, p. 275) +% * +% * Author: +% * Timo Gnambs +% * URL: http://timo.gnambs.at +% * Last modified: 2004-06-04 +% * +% * +% * Literature: +% * > Bortz, J., Lienert, G. & Boehnke, K. (2000). Verteilungsfreie Methoden in der Biostatistik. Berlin: Springer. +% * > Schaich, E. & Hamerle, A. (1984). Verteilungsfreie statistische Prüfverfahren. Berlin: Springer. +% * > Conover, W. J. (1971,1980). Practical nonparametric statistics. New York: Wiley. +% * +% * Instruction: +% * Modify the two passages titled "Configuration 1" and "Configuration +% */. +% +% +% temporary. +% select if($casenum = 1). +% +% +% /* START Configuration 1 */ +% * Load demo file. +% get file = spss_friedmanph.sav. +% * Significance level. +% compute #alpha = 0.05. +% * Number of variables to compare. +% compute #varcnt = 4. +% * Sample size. +% compute #sample = 5. +% +% * Maximum number of allowed loops. +% * This value must be greater than the sample size (#sample), else SPSS produces errors. +% set mxloop = 500. +% +% /* END Configuration 1 */ +% +% +% compute #chisq = idf.chisq((1 - #alpha), (#varcnt - 1)). +% compute #t = abs(idf.t(#alpha / 2, (#sample - 1) * (#varcnt - 1))). +% write outfile = friedmanposthoctmp / #chisq #t. +% exe. +% +% matrix. +% +% /* Load data to matrix */ +% read sig /file=friedmanposthoctmp /field = 1 to 16 /size = {1,2}. +% +% +% /* START Configuration 2 */ +% +% * Load variables to compare. +% get m /file=* /variables var1 var2 var3 var4. +% +% /* END Configuration 2 */ +% +% +% /* Ranked data */ +% compute #rnked = m. +% loop #i = 1 to nrow(m) by 1. +% compute #tmp = rnkorder(m(#i,:)). +% compute #rnked(#i,:) = #tmp. +% end loop. +% +% /* Sum of ranks */ +% compute #sums = csum(#rnked). +% compute msums = #sums / nrow(m). +% +% /* Rank differences */ +% compute mdiff = ident(ncol(m),ncol(m)). +% loop #j = 1 to (ncol(m)) by 1. +% loop #k = 1 to #j by 1. +% compute mdiff(#j,#k) = msums(#j) - msums(#k). +% compute mdiff(#k,#j) = -mdiff(#j,#k). +% end loop. +% end loop. +% +% /* Critical rank difference */ +% compute cdiff = sqrt(sig(1,1)) * sqrt((ncol(m) * (ncol(m) + 1)) / (nrow(m) * 6)). +% compute cdiff2 = sig(1,2) * sqrt((2 * (mssq(#rnked) - mssq( #sums) / nrow(m))) / (nrow(m) * (nrow(m) - 1) * (ncol(m) - 1))). +% +% /* Print results */ +% print /title 'Post-hoc analyses for Friedman test'. +% print /title 'Author: Timo Gnambs '. +% print /title '----------------------'. +% print ncol(m) /format = f8.0 /title 'Number of variables:'. +% print nrow(m) /format = f8.0 /title 'Sample size:'. +% print msums /format = f8.2 /title 'Mean rank of variables:' /cnames m. +% print mdiff /format f8.2 /title 'Mean rank difference of variables:'. +% print cdiff /format f8.2 /title 'Critical rank difference (Schaich & Hamerle, 1984):'. +% print cdiff2 /format f8.2 /title 'Critical rank difference (Conover, 1971, 1980):'. +% print /title '----------------------'. +% +% end matrix. +% exe. +% +% /* Delete temporary file */ +% erase file = friedmanposthoctmp. +% exe. \ No newline at end of file diff --git a/stat/friedman_test.m b/stat/friedman_test.m new file mode 100644 index 0000000..dcd22f7 --- /dev/null +++ b/stat/friedman_test.m @@ -0,0 +1,110 @@ +function [p,F,fx,epsilon,df,dfe,SS,SSe,SSt,Y]=friedman_test(X,nf,rp) +% friedman_test - Friedman's Test: N-way non-parametric ANOVA (using ranks) +% [p,F,fx]=friedman_test(X,nf) +% X should be [Factor1 x Factor2 x ... x Subjects x ...] +% nf is the number of factors +% rp is the list of within-subject factors +% +% [p,F,fx,epsilon,df,dfe,SS,SSe,SSt,Y]=friedman_test(...) +% ouputs data from myanova and Y, the rank matrix +% +% See also: myanova +% Uses: myanova, tiedrank +if nargin<3 + rp=[]; +end +if not(isempty(rp)) & ~all(ismember(1:nf, rp)) + error('Cannot deal with split-plot (ie. mixed) designs') +end +% This test is an ANOVA on the rank +sX=size(X); +ng=sX(1:nf); % nb of groups in each factor +png=prod(ng); % nb of cells +if isempty(rp) + %data are ranked across subjects + X=reshape(X,png*sX(nf+1),[]); +else + %data are ranked within subjects + X=reshape(X,png,[]); +end +Y=tiedrank(X); +Y=reshape(Y,sX); +if nargout>3 + [p,F,fx,epsilon,df,dfe,SS,SSe,SSt]=myanova(Y,nf,rp); +else + [p,F,fx]=myanova(Y,nf,rp); +end +return + +%% This example is in the SAS Sample library. +% TEST DATA: +% http://www.biostat.wustl.edu/archives/html/jmp-l/1995/msg00168.html +% columns: TREATMENT, 4-level within +% rows: SUBJECTS N=6 +X =[32.6 36.4 29.5 29.4 ; + 42.7 47.1 32.9 40.2 ; + 35.3 40.1 33.6 35 ; + 35.2 40.3 35.7 40 ; + 33.2 34.3 33.2 34 ; + 33.1 34.4 33.1 34.1 ]'; +% WARNING : the following does not used tied ranks! +% so tweak the data an epsilon bit... +X(1,5)=X(1,5)-1e-10; +X(1,6)=X(1,6)-1e-10; +friedman_test(X,1,1) +% Results : +% Response: Rank within Block +% Effect Test +% Source Nparm DF Sum of Squares F Ratio Prob>F +% BLOCK 5 5 0.000000 0.0000 1.0000 +% TRTMENT 3 3 19.333333 9.0625 0.0011 +% +% Step 4: +% TO CALCULATE THE CHI-SQUARE, +% MULTIPLY THE SUM-OF-SQUARES FOR TREATMENT BY +% 12/((T*(T+1)) WHERE T IS THE NUMBER OF TREATMENTS. + + + +%% Example from ZAR, p. 265 +X=[ 7.0 5.3 4.9 8.8 + 9.9 5.7 7.6 8.9 + 8.5 4.7 5.5 8.1 + 5.1 3.5 2.8 3.3 + 10.3 7.7 8.4 9.1]; + +% Cousineau 2005; TQPM +% Simulated data of a 2-by-5 exp with 16 subjects +X=str2num(urlread('http://www.tqmp.org/Content/vol01-1/p042/p042.dat')); +X=reshape(X(:,end),[16,5,2]); +X=permute(X,[3 2 1]); +p=myanova(X,2,1:2) +% all the variances are homogeneous and spherical, Tabachnik & Fidell, 1996 +% Mauchly s W = 0.74, p > .50 for factor 2 +% and W = 0.68, p > .50 for the interaction; + + +%% Avec R +%% http://www.r-statistics.com/2010/02/post-hoc-analysis-for-friedmans-test-r-code/ +X=[5.40, 5.50, 5.55, + 5.85, 5.70, 5.75, + 5.20, 5.60, 5.50, + 5.55, 5.50, 5.40, + 5.90, 5.85, 5.70, + 5.45, 5.55, 5.60, + 5.40, 5.40, 5.35, + 5.45, 5.50, 5.35, + 5.25, 5.15, 5.00, + 5.85, 5.80, 5.70, + 5.25, 5.20, 5.10, + 5.65, 5.55, 5.45, + 5.60, 5.35, 5.45, + 5.05, 5.00, 4.95, + 5.50, 5.50, 5.40, + 5.45, 5.55, 5.50, + 5.55, 5.55, 5.35, + 5.45, 5.50, 5.55, + 5.50, 5.45, 5.25, + 5.65, 5.60, 5.40, + 5.70, 5.65, 5.55, + 6.30, 6.30, 6.25] \ No newline at end of file diff --git a/stat/ftest.m b/stat/ftest.m new file mode 100644 index 0000000..09930e0 --- /dev/null +++ b/stat/ftest.m @@ -0,0 +1,33 @@ +function [p, f] = ftest(d1, d2) +%FTEST F-test for two samples. +% FTEST(X1, X2) gives the probability that the F value +% calculated as the rati of the variances of the two samples is +% greater than observed, i.e. the significance level. +% [P, F] = FTEST(X1, X2) gives the probability P and returns +% the value of F. +% +% A small value of P would lead to reject the hypothesis that +% both data sets are sampled from distributions with the same +% variances. +% +%See also : TTEST, TEST. +[l1 c1] = size(d1) ; +n1 = l1 * c1 ; +x1 = reshape(d1, l1 * c1, 1) ; +[l2 c2] = size(d2) ; +n2 = l2 * c2 ; +x2 = reshape(d2, l2 * c2, 1) ; +[a1 v1] = avevar(x1) ; +[a2 v2] = avevar(x2) ; +f = v1 / v2 ; +df1 = n1 - 1 ; +df2 = n2 - 1 ; +if (v1 > v2) + p = 2 * betainc( df2 / (df2 + df1 * f), df2 / 2, df1 / 2) ; +else + f = 1 / f ; + p = 2 * betainc( df1 / (df1 + df2 * f), df1 / 2, df2 / 2) ; +end +if (p > 1) + p = 2 - p ; +end \ No newline at end of file diff --git a/stat/goutte/avevar.m b/stat/goutte/avevar.m new file mode 100644 index 0000000..7072847 --- /dev/null +++ b/stat/goutte/avevar.m @@ -0,0 +1,17 @@ +function [xbar, varx] = avevar(v) +%AVEVAR average and variance of sample. +% AVEVAR(X) gives the average of the sample in X. +% X is a vector of values. +% [A, V] = AVEVAR(X) returns the average of X in A, +% and the variance in V. The variance is corrected +% using the two-pass formula. +% +% Ref: [1] Chan, Golub and LeVeque. 1983. American +% Statistician, vol. 37, pp. 242--247. +% [2] Press et al. 1992. Numerical recipes in C. +% Cambridge university press. +[n l] = size(v) ; +x = reshape(v, n*l, 1) ; +xbar = sum(x) / n / l ; +d = x - xbar ; +varx = ( sum(d .* d) - sum(d) * sum(d) / n / l) / (n * l - 1) ; diff --git a/stat/goutte/bootstrap.m b/stat/goutte/bootstrap.m new file mode 100644 index 0000000..f597a65 --- /dev/null +++ b/stat/goutte/bootstrap.m @@ -0,0 +1,47 @@ +function [biais, variance, fboot] = bootstrap(f, X, B, P1, P2, P3) +%BOOTSTRAP Bootstrap estimates of bias and variance. +% +% [BIAS VAR Fboot] = BOOTSTRAP('f', X, B) gives the following elements +% calculated by the bootstrap method using B replications: +% - bias of f(X), +% - variance of f(X), +% - corrected f(X). +% The data in X must be organised in rows (see function CORRCOEF) +% The bias formula is the standard one (not .632). +% +% [BIAS VAR Fboot] = BOOTSTRAP('f', X, B, opt1, opt2, opt3) allows to +% pass up to 3 additional arguments to 'f'. +% +% BOOTSTRAP needs the AVEVAR function. +% (c) 1997, C. Goutte. +% See also: JACKKNIFE. + +if (nargin < 3) | (nargin > 6) + error('BOOTSTRAP: wrong number of arguments.') ; +end + +[N P] = size(X) ; +if (N < 2) + error('BOOTSTRAP: not enough samples.') +end +evalstr = [f, '(X_i'] ; +for i = 1:(nargin - 3) + evalstr = [evalstr, ',P', int2str(i)] ; +end +evalstr = [evalstr, ')'] ; + +f_i = zeros(B,1) ; +X_i = X ; +fhat = eval(evalstr) ; + +for i = 1:B + idx_i = ceil(rand(1,N)*N) ; +% disp(idx_i) + X_i = X(idx_i, :) ; + f_i(i) = eval(evalstr) ; +end +[fboot, variance] = avevar(f_i) ; +biais = (fboot - fhat) ; +fboot = 2 * fhat - fboot ; + +% (c) 16/05/97, CG. diff --git a/stat/goutte/budget.txt b/stat/goutte/budget.txt new file mode 100644 index 0000000..940fe2a --- /dev/null +++ b/stat/goutte/budget.txt @@ -0,0 +1,24 @@ +18.0 .5 .1 6.7 .5 2.1 2.0 0 26.4 41.5 2.2 +14.1 .8 .1 15.3 1.9 3.7 .5 0 29.8 31.3 2.5 +13.6 .7 .7 6.8 .6 7.1 .7 0 33.8 34.4 1.6 +14.3 1.7 1.7 6.9 1.2 7.4 .8 0 37.7 26.2 2.1 +10.3 1.5 .4 9.3 .6 8.5 .9 0 38.4 27.2 2.9 +13.4 1.4 .5 8.1 .7 8.6 1.8 0 38.5 25.3 1.7 +13.5 1.1 .5 9.0 .6 9.0 3.4 0 36.8 23.5 2.6 +12.9 1.4 .3 9.4 .6 9.3 4.3 0 41.1 19.4 1.3 +12.3 .3 .1 11.9 2.4 3.7 1.7 1.9 42.4 23.1 .2 +7.6 1.2 3.2 5.1 .6 5.6 1.8 10.0 29.0 35.0 .9 +10.5 .3 .4 4.5 1.8 6.6 2.1 10.1 19.9 41.6 2.2 +10.0 0.6 0.6 9.0 1.0 8.1 3.2 11.8 28.0 25.8 1.9 +10.6 .8 .3 8.9 3.0 10.0 6.4 13.4 27.4 19.2 0 +8.8 2.6 1.4 7.8 1.4 12.4 6.2 11.3 29.3 18.5 .3 +10.1 1.1 1.2 5.9 1.4 9.5 6.0 5.9 40.7 18.2 0 +15.6 1.6 10.0 11.4 7.6 8.8 4.8 3.4 32.2 4.6 0 +11.2 1.3 16.5 12.4 15.8 8.1 4.9 3.4 20.7 4.2 1.5 +12.9 1.5 7.0 7.9 12.1 8.1 5.3 3.9 36.1 5.2 0 +10.9 5.3 9.7 7.6 9.6 9.4 8.5 4.6 28.2 6.2 0 +13.1 4.4 7.3 5.7 9.8 12.5 8.0 5.0 26.7 7.5 0 +12.8 4.7 7.5 6.6 6.8 15.7 9.7 5.3 24.5 6.4 .0 +12.4 4.3 8.4 9.1 6.0 19.5 10.6 4.7 19.8 3.5 1.7 +11.4 6.0 9.5 5.9 5.0 21.1 10.7 4.2 20.0 4.4 1.8 +12.8 2.8 7.1 8.5 4.0 23.8 11.3 3.7 18.8 7.2 0 diff --git a/stat/goutte/ftest.m b/stat/goutte/ftest.m new file mode 100644 index 0000000..09930e0 --- /dev/null +++ b/stat/goutte/ftest.m @@ -0,0 +1,33 @@ +function [p, f] = ftest(d1, d2) +%FTEST F-test for two samples. +% FTEST(X1, X2) gives the probability that the F value +% calculated as the rati of the variances of the two samples is +% greater than observed, i.e. the significance level. +% [P, F] = FTEST(X1, X2) gives the probability P and returns +% the value of F. +% +% A small value of P would lead to reject the hypothesis that +% both data sets are sampled from distributions with the same +% variances. +% +%See also : TTEST, TEST. +[l1 c1] = size(d1) ; +n1 = l1 * c1 ; +x1 = reshape(d1, l1 * c1, 1) ; +[l2 c2] = size(d2) ; +n2 = l2 * c2 ; +x2 = reshape(d2, l2 * c2, 1) ; +[a1 v1] = avevar(x1) ; +[a2 v2] = avevar(x2) ; +f = v1 / v2 ; +df1 = n1 - 1 ; +df2 = n2 - 1 ; +if (v1 > v2) + p = 2 * betainc( df2 / (df2 + df1 * f), df2 / 2, df1 / 2) ; +else + f = 1 / f ; + p = 2 * betainc( df1 / (df1 + df2 * f), df1 / 2, df2 / 2) ; +end +if (p > 1) + p = 2 - p ; +end \ No newline at end of file diff --git a/stat/goutte/jackknife.m b/stat/goutte/jackknife.m new file mode 100644 index 0000000..c3e3d61 --- /dev/null +++ b/stat/goutte/jackknife.m @@ -0,0 +1,46 @@ +function [biais, variance, fjack] = jackknife(f, X, P1, P2, P3) +%JACKKNIFE Jackknife estimates of bias and variance. +% +% [BIAS VAR Fjack] = JACKKNIFE('f', X) gives the following elements +% calculated by the jackknife method: +% - bias of f(X), +% - variance of f(X), +% - corrected f(X). +% The data in X must be organised in rows (see function CORRCOEF) +% +% [BIAS VAR Fjack] = JACKKNIFE('f', X, opt1, opt2, opt3) allows to +% pass up to 3 additional arguments to 'f'. +% +% JACKKNIFE needs the AVEVAR function. +% (c) 1997, C. Goutte. +% See also: BOOTSTRAP. + +if (nargin < 2) | (nargin > 5) + error('JACKKNIFE: wrong number of arguments.') ; +end + +[N P] = size(X) ; +if (N < 2) + error('JACKNIFE: not enough samples.') +end +evalstr = [f, '(X_i'] ; +for i = 1:(nargin - 2) + evalstr = [evalstr, ',P', int2str(i)] ; +end +evalstr = [evalstr, ')'] ; + +f_i = zeros(N,1) ; +X_i = X ; +fhat = eval(evalstr) ; + +for i = 1:N + idx_i = [1:(i-1), (i+1):N] ; + X_i = X(idx_i, :) ; + f_i(i) = eval(evalstr) ; +end +[fbar, variance] = avevar(f_i) ; +variance = (N - 1) * (N - 1) * variance / N ; +biais = (N - 1) * (fbar - fhat) ; +fjack = N * fhat - (N - 1) * fbar ; + +% (c) 16/05/97, CG. \ No newline at end of file diff --git a/stat/goutte/kstest.m b/stat/goutte/kstest.m new file mode 100644 index 0000000..fe6a550 --- /dev/null +++ b/stat/goutte/kstest.m @@ -0,0 +1,46 @@ +function [p, d] = kstest(v1, v2) +%KSTEST Kolmogorov-Smirnov test of two arrays. +% KSTEST(V1, V2) gives the significance level of V1 and V2 being +% sampled from the same distribution. +% [PROB, D] = KSTEST(V1, V2) returns : +% PROB : the probability of the KS statistics to be greater +% than observed on V1 and V2, +% D : the observed KS statistics. +% where V1 and V2 are the locations of the samples. +% +% When PROB is close to 0, the samples are probably from different +% distributions. +% +% To test a sample V against a distribution, produce a *big* sample VS +% from this distribution, and test KTEST(V, VS). +% +% See also: QKS. +if (nargin ~= 2) + error('KSTEST: requires 2 samples.') ; +end + +[nl1 nc1] = size(v1) ; +if ((nl1 *nc1 <= 0) | (min([nl1 nc1]) ~= 1)) + error('KSTEST: first argument is empty or has wrong dimensions.') ; +end +[nl2 nc2] = size(v2) ; +if ((nl2 *nc2 <= 0) | (min([nl2 nc2]) ~= 1)) + error('KSTEST: second argument is empty or has wrong dimensions.') ; +end + +n1 = nl1 * nc1 ; +d1 = reshape(v1, n1, 1) ; +n2 = nl2 * nc2 ; +d2 = reshape(v2, n2, 1) ; +%[d1 d2 ]' +h = zeros(1, n1 + n2) ; + +for i=1:n1 + h1(i) = length(find(d1 <= d1(i))) * 1/n1 - length(find(d2 <= d1(i))) * 1/n2 ; +end +for i=1:n2 + h2(i) = length(find(d1 <= d2(i))) * 1/n1 - length(find(d2 <= d2(i))) * 1/n2 ; +end +d = max([abs(h1) abs(h2)]) ; +n = sqrt(n1 * n2 / (n1 + n2)) ; +p = qks((n + 0.12 + 0.11/n) * d) ; diff --git a/stat/goutte/pca.m b/stat/goutte/pca.m new file mode 100644 index 0000000..bca4269 --- /dev/null +++ b/stat/goutte/pca.m @@ -0,0 +1,118 @@ +function [Y, lbd, cos2, CC] = pca(X, P, labels) +%PCA Principal Components Analysis +% +% Y = PCA(X, P) returns the first P principal components of X. +% X must be a NxL matrix of N vectors in L dimensions. +% +% Y = PCA(X, P, LABELS) displays several plots related to the PCA: +% . projection of data on the first two principal axes, +% . correlation circle, +% . % inertia of the principal components, +% . angles of the data w.r.t. the PC subspace. +% If LABELS is a matrix containing N rows of text, each row is used +% to label the corresponding data point, otherwise data go unlabelled. +% +% Additional outputs: [Y, LBD, C2, CC] = PCA(X, P [, LABELS]), where +% Y is the projection of X on the first P principal components, +% LBD contains the % inertia of the required P components, +% C2 is the squared cosine between projected data and original data, +% CC is the correlation between the input features and the first P P.C. +% +% See also: . +if (nargin < 2) or (nargin > 4) + error('PCANA: wrong number of arguments.') +end + +[N, L] = size(X) ; +if (P < 1) + error('PCANA: must return at least 1 principal component.') +end +if (P > L) + error('PCANA: can''t get more principal components than dimensions.') +end + +if (nargin == 2) + verbose = 0 ; +end + +%%%%%%%%%% + +R = corrcoef(X) ; +% XM= ones(N,1) * mean(X') ; +% Xc = X' - XM ; +% [U, S, V] = svd(Xc, 0) ; +[V, S] = eig(R) ; +[l, idx] = sort(diag(S)') ; +l = fliplr(l) ; +idx = fliplr(idx) ; + +% Coordonees centrees reduites. +XCR = (X - ones(N,1)*mean(X)) / diag(std(X)) ; +%PC = (Xc * V(:,idx)' + XM)' ; +PC = (XCR * V(:,idx) ) ; + +% P first principal components: +Y = PC(:, 1:P) ; + +% P first portions of inertia: +lbd = l(1:P) / sum(l) ; + +% Squared cosines of projection vs. original data: +cos2 = sum((Y.^2)') ./ sum((XCR.^2)') ; + +% Correlations of features with PCs: +CC = V(:,idx) * diag(sqrt(l)) ; + +if (nargin == 3) + figure + subplot(2,2,1) ; + plot(PC(:,1), PC(:,2), '+') ; + if (size(labels, 1) == N) + for i = 1:N + text(PC(i,1), PC(i,2), [' ', labels(i,:)]) ; + end + end + xlabel('First principal axis') ; + ylabel('Second principal axis') ; + title('Data projection on the first two principal axes') + subplot(2,2,2) ; + plot(cos(2*pi*(0:100)/100), sin(2*pi*(0:100)/100)) ; + hold on, plot([-1, 1], [0, 0]), plot([0, 0], [-1, 1]) ; + plot(CC(:,1), CC(:,2), 'x') ; + for k = 1:L + text(CC(k,1)+.03, CC(k,2)+.03, num2str(k)) ; + end + xlabel('First PC') ; + ylabel('Second PC') ; + title('Correlation of features with first 2 PCs') + subplot(2,2,3) ; + hh = plot(l/sum(l), '--') ; + set(hh, 'LineWidth', 3) ; + hold on, hh = plot(cumsum(l)/sum(l)) ; + set(hh, 'LineWidth', 3) ; + axis([1 L 0 1]) ; + plot([2 2], [0 1]) ; + xlabel('PC no.') ; + ylabel('Inertia') ; + legend('PC inertia', 'Cumulated inertia') ; + title('Repartition of inertia on the PCs') ; + subplot(2,2,4) ; + hold on + hh = plot(acos(sqrt(cos2))/pi*180, 1:N, '+') ; + set(hh, 'LineWidth', 3) ; + hold on + for i = 1:N + hh = plot([0, acos(sqrt(cos2(i)))/pi*180], [i, i], '-') ; + set(hh, 'LineWidth', 3) ; + end + set(gca, 'Box', 'on') ; + set(gca, 'YLim', [1 N]) ; + if (size(labels, 1) == N) + set(gca, 'YTick', 1:N) ; + set(gca, 'YTickLabels', labels) ; + end + xlabel('Angle (deg)') + title('Angle of data with the PC plan') +end + +CC = CC(:,1:P) ; diff --git a/stat/goutte/pttest.m b/stat/goutte/pttest.m new file mode 100644 index 0000000..21d5cdb --- /dev/null +++ b/stat/goutte/pttest.m @@ -0,0 +1,41 @@ +function [p, t, df] = pttest(d1, d2) +%PTTEST Student's paired t-test. +% PTTEST(X1, X2) gives the probability that Student's t +% calculated on paired data X1 and X2 is higher than +% observed, i.e. the "significance" level. This is used +% to test whether two paired samples have significantly +% different means. +% [P, T] = PTTEST(X1, X2) gives this probability P and the +% value of Student's t in T. The smaller P is, the more +% significant the difference between the means. +% E.g. if P = 0.05 or 0.01, it is very likely that the +% two sets are sampled from distributions with different +% means. +% +% This works for PAIRED SAMPLES, i.e. when elements of X1 +% and X2 correspond one-on-one somehow. +% E.g. residuals of two models on the same data. +% +%See also: TTEST, UTTEST. +[l1 c1] = size(d1) ; +n1 = l1 * c1 ; +x1 = reshape(d1, l1 * c1, 1) ; +[l2 c2] = size(d2) ; +n2 = l2 * c2 ; +if (n1 ~= n2) + error('PTTEST: paired samples must have the same number of elements !') +end +x2 = reshape(d2, l2 * c2, 1) ; +[a1 v1] = avevar(x1) ; +[a2 v2] = avevar(x2) ; +df = n1 - 1 ; +cab = (x1 - a1)' * (x2 - a2) / (n1 - 1) ; +if (a1 ~= a2) + % use abs to avoid numerical errors for very similar data + % for which v1+v2-2cab may be close to 0. + t = (a1 - a2) / sqrt(abs(v1 + v2 - 2 * cab) / n1) ; + p = betainc( df / (df + t*t), df/2, 0.5) ; +else + t = 0 ; + p = 1 ; +end diff --git a/stat/goutte/qks.m b/stat/goutte/qks.m new file mode 100644 index 0000000..3352400 --- /dev/null +++ b/stat/goutte/qks.m @@ -0,0 +1,58 @@ +function P = qks(M, tol) +%QKS Kolmogorov-Smirnov probability. +% QKS(x) computes the function that enters into the calculation +% of the significance level in a Kolmogorov-Smirnov test. +% +oo +% .-- [ k-1 2 2 ] +% Q (x) = 2 > [ (-1) exp(-2.x .k ) ] +% KS '-- [ ] +% k=1 +% Ref.: Numerical Recipes, 2nd ed. pp.623-628 +% QKS(M) calculates QKS(x) for each x in M. +% QKS(M, TOL) uses TOL as the precision required for the convergence. +% The default value is MATLAB's eps (1e-8). +% +% See also: KSTEST, QKS2. +if (nargin < 1 | nargin > 2) + error('QKS: requires one or two arguments.') ; +end + +if (nargin == 1) + tol = eps ; +else + if (tol <= 0) + error('QKS: precision must be strictly positive.') ; + end +end + +[nl, nc] = size(M) ; +if ((nl * nc) <= 0) + error('QKS: first argument is empty or has wrong dimensions.') ; +end + +n = nl * nc ; +X = reshape(M, n, 1) ; +P = ones(n, 1) ; + +for i = find(X ~= 0)' + coeff = 2 ; + alpha = -2 * X(i, 1) * X(i, 1) ; + sss = 0 ; + k = 1 ; + ui = coeff * exp(alpha) ; + while (( abs(ui) > tol * sss ) & ( k < 100 )) + sss = sss + ui ; + k = k + 1 ; + coeff = - coeff ; + ui = coeff * exp(alpha * k * k) ; + end + if (k == 100) +% Ca n'a pas converge + P(i,1) = 1 ; + else +% Ca a converge : on arrondit a epsilon pres. + P(i,1) = round((sss + ui) / tol) * tol ; + end +end + +P = reshape(P, nl, nc) ; diff --git a/stat/goutte/test.m b/stat/goutte/test.m new file mode 100644 index 0000000..c967377 --- /dev/null +++ b/stat/goutte/test.m @@ -0,0 +1,35 @@ +function [p, s] = test(x1, x2, type) +%TEST statistical test of two samples. +% TEST(X1, X2, 'test') gives the confidence level for +% different tests of the two samples X1 and X2. +% +% 'test' Null hypothesis Assumption on distributions Type +% ----------------------------------------------------------------- +% 't' Means are equal The variances are equal t-test +% 'u' Means are equal The variances are not equal t-test +% 'p' Means are equal The data are paired t-test +% 'f' Variances are equal F-test +% +% TEST(X1, X2) performs a t-test (for equal variances) by default. +% +% [P S] = TEST(X1, X2, ['test']) returns the confidence level in P +% and the value of the statistic (t or F) in S. +% +% Ref: Press et al. 1992. Numerical recipes in C. 14.2, Cambridge. +% +%See also : TTEST, UTEST, PTEST, FTEST. +if (nargin == 2) + type = 't' ; +end + + +if (type == 't') + [p s] = ttest(x1, x2) ; +elseif (type == 'u') + [p s] = uttest(x1, x2) ; +elseif (type == 'p') + [p s] = pttest(x1, x2) ; +elseif (type == 'f') + [p s] = ftest(x1, x2) ; +end + diff --git a/stat/goutte/ttest.m b/stat/goutte/ttest.m new file mode 100644 index 0000000..26c85cc --- /dev/null +++ b/stat/goutte/ttest.m @@ -0,0 +1,30 @@ +function [p, t] = ttest(d1, d2) +%TTEST Student's t-test for equal variances. +% TTEST(X1, X2) gives the probability that Student's t +% calculated on data X1 and X2, sampled from distributions +% with the same variance, is higher than observed, i.e. +% the "significance" level. This is used to test whether +% two sample have significantly different means. +% [P, T] = TTEST(X1, X2) gives this probability P and the +% value of Student's t in T. The smaller P is, the more +% significant the difference between the means. +% E.g. if P = 0.05 or 0.01, it is very likely that the +% two sets are sampled from distributions with different +% means. +% +% This works if the samples are drawn from distributions with +% the SAME VARIANCE. Otherwise, use UTTEST. +% +%See also: UTTEST, PTTEST. +[l1 c1] = size(d1) ; +n1 = l1 * c1 ; +x1 = reshape(d1, l1 * c1, 1) ; +[l2 c2] = size(d2) ; +n2 = l2 * c2 ; +x2 = reshape(d2, l2 * c2, 1) ; +[a1 v1] = avevar(x1) ; +[a2 v2] = avevar(x2) ; +df = n1 + n2 - 2 ; +pvar = ((n1 - 1) * v1 + (n2 - 1) * v2) / df ; +t = (a1 - a2) / sqrt( pvar * (1/n1 + 1/n2)) ; +p = betainc( df / (df + t*t), df/2, 0.5) ; diff --git a/stat/goutte/uttest.m b/stat/goutte/uttest.m new file mode 100644 index 0000000..f989423 --- /dev/null +++ b/stat/goutte/uttest.m @@ -0,0 +1,30 @@ +function [p, t, df] = uttest(d1, d2) +%UTTEST Student's t-test for unequal variances. +% UTTEST(X1, X2) gives the probability that Student's t +% calculated on data X1 and X2, sampled from distributions +% with different variances, is higher than observed, i.e. +% the "significance" level. This is used to test whether +% two sample have significantly different means. +% [P, T] = UTTEST(X1, X2) gives this probability P and the +% value of Student's t in T. The smaller P is, the more +% significant the difference between the means. +% E.g. if P = 0.05 or 0.01, it is very likely that the +% two sets are sampled from distributions with different +% means. +% +% This works if the samples are drawn from distributions with +% DIFFERENT VARIANCE. Otherwise, use TTEST. +% +%See also: TTEST, PTTEST. +[l1 c1] = size(d1) ; +n1 = l1 * c1 ; +x1 = reshape(d1, l1 * c1, 1) ; +[l2 c2] = size(d2) ; +n2 = l2 * c2 ; +x2 = reshape(d2, l2 * c2, 1) ; +[a1 v1] = avevar(x1) ; +[a2 v2] = avevar(x2) ; +df = (v1 / n1 + v2 / n2) * (v1 / n1 + v2 / n2) / ... + ( (v1 / n1) * (v1 / n1) / (n1 - 1) + (v2 / n2) * (v2 / n2) / (n2 -1) ) ; +t = (a1 - a2) / sqrt( v1 / n1 + v2 / n2 ) ; +p = betainc( df / (df + t*t), df/2, 0.5) ; diff --git a/stat/hedge_g.m b/stat/hedge_g.m new file mode 100644 index 0000000..11eb52e --- /dev/null +++ b/stat/hedge_g.m @@ -0,0 +1,32 @@ +function [g_star,g,v_star]=hedge_g(X1,X2) +% Computes effect size g, as suggested by Larry Hedges in 1981. +% [g_star,g]=functionhedge_g(X1,X2) +% + +X1=X1(:); +X2=X2(:); + +m1=mean(X1); +m2=mean(X2); + +n1=size(X1,1); +n2=size(X2,1); + +v1=var(X1); +v2=var(X2); + +%s Hedge's pooled variance +% note the -2 in the denominator +a=n1+n2-2; +v_star = ((n1-1)*v1 + (n2-1)*v2)/a; + +g = (m1 - m2)/sqrt(v_star); + +if 0 + J = gamma(a/2)/(sqrt(a/2)*gamma((a-1)/2)); +else + % approximate + J = 1 - 3/(a-1); +end + +g_star=J*g; diff --git a/stat/hochberg.m b/stat/hochberg.m new file mode 100644 index 0000000..368d389 --- /dev/null +++ b/stat/hochberg.m @@ -0,0 +1,46 @@ +function [sig,alpha]=hochberg(alpha,n) +% HOCHBERG - Hochberg's adjustment of alpha level for multiple comparisons +% +% alpha2=hochberg(alpha1, n) computes the new alpha2 to use when running +% n tests with a familywise error rate of alpha +% +% [sig,alphas]=hochberg(alpha,p) assess the significance of the p-values +% coming from multiple tests according to the Hochberg's procedure +% yielding a familywise error rate of alpha. p being a vector of +% p-values, sig will be a logical vector of the same size. +% +% See also: dunnsidak, hochberg + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2007 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2007-11-05 Creation +% +% ----------------------------- Script History --------------------------------- + +% Ref: +% http://www.unc.edu/courses/2007spring/biol/145/001/docs/lectures/Oct6.html +% + +if numel(n)==1 & n>=1 + alpha=alpha.*(1./(n-(0:n-1))); + sig=alpha; + return +end +p=n; +sz=size(p); +[p,ip]=sort(p(:)); +n=numel(p); +alpha=alpha.*(1./(n-(tiedrank(p)-1))); +sig=logical(p.*0); +i=find(p<=alpha(:)); +if ~isempty(i) + sig(1:i(end))=1; +end +sig(ip)=sig; +sig=reshape(sig,sz); \ No newline at end of file diff --git a/stat/holm.m b/stat/holm.m new file mode 100644 index 0000000..6dd59b7 --- /dev/null +++ b/stat/holm.m @@ -0,0 +1,47 @@ +function [sig,alpha]=holm(alpha,n) +% HOLM - Holm adjustment of alpha level for multiple comparisons +% +% alpha2=holm(alpha1, n) computes the new alpha2 to use when running +% n tests with a familywise error rate of alpha +% +% [sig,alphas]=holm(alpha,p) assess the significance of the p-values +% coming from multiple tests according to the Holm's procedure yielding a +% familywise error rate of alpha. p being a vector of p-values, sig will +% be a logical vector of the same size. +% +% See also: dunnsidak, hochberg + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2007 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2007-11-05 Creation +% +% ----------------------------- Script History --------------------------------- + +% Ref: +% http://www.unc.edu/courses/2007spring/biol/145/001/docs/lectures/Oct6.html +% + +if numel(n)==1 & n>=1 + alpha=alpha.*(1./(n-(0:n-1))); + sig=alpha; + return +end +p=n; +sz=size(p); +[p,ip]=sort(p(:)); +n=numel(p); +% alpha=holm(alpha,n); +alpha=alpha.*(1./(n-(tiedrank(p)-1))); +sig=logical(p.*0+1); +i=find(p>alpha(:)); +if ~isempty(i) + sig(i(1):end)=0; +end +sig(ip)=sig; +sig=reshape(sig,sz); \ No newline at end of file diff --git a/stat/krippendorff_alpha.m b/stat/krippendorff_alpha.m new file mode 100644 index 0000000..02779d3 --- /dev/null +++ b/stat/krippendorff_alpha.m @@ -0,0 +1,61 @@ +function a = krippendorff_alpha(R) +%KRIPPENDORFF_ALPHA - Krippendorff’s Alpha-Reliability between raters +% [] = krippendorff_alpha(input) +% +% Example +% >> krippendorff_alpha +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2010 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2010-09-23 Creation +% +% ----------------------------- Script History --------------------------------- + + +R = [ 'a a b b d c c c e d d a ' ... + ' b a b b b c c c e d d d '] +R = reshape(R(R~=' '),12,2)' - 'a' + + +R= [1 2 3 3 2 1 4 1 2 NaN NaN NaN + 1 2 3 3 2 2 4 1 2 5 NaN NaN + NaN 3 3 3 2 3 4 2 2 5 1 3 + 1 2 3 3 2 4 4 1 2 5 1 NaN ]; + + +[no , nm] = size(R); +if no==2 + if ~any(isnan(R)) + r = (unique(R(~isnan(R)))); + nr = numel(r); + P = nchoosek(r,2); + P = [ P ; fliplr(P) ]; + P = sortrows([ [r(:) r(:)] ; P ]); + np = size(P,1); + %Coincidence values + C = repmat(R, [ 1 1 np]) == permute(repmat(P',[1 1 nm ]), [1 3 2]); + C = squeeze(sum(all(C),2)) + % ... into matrix + C = reshape(C,[nr nr]); + C = C' + C; + n = 2*nm; + nc = sum(C); + sc = sum(nc.*(nc-1)) + a = (n-1)*sum(diag(C)) - sc; + a = a / (n*(n-1)-sc) + else + mu = no - sum(isnan(R)); + + end + +end +return +%% diff --git a/stat/kstest.m b/stat/kstest.m new file mode 100644 index 0000000..fe6a550 --- /dev/null +++ b/stat/kstest.m @@ -0,0 +1,46 @@ +function [p, d] = kstest(v1, v2) +%KSTEST Kolmogorov-Smirnov test of two arrays. +% KSTEST(V1, V2) gives the significance level of V1 and V2 being +% sampled from the same distribution. +% [PROB, D] = KSTEST(V1, V2) returns : +% PROB : the probability of the KS statistics to be greater +% than observed on V1 and V2, +% D : the observed KS statistics. +% where V1 and V2 are the locations of the samples. +% +% When PROB is close to 0, the samples are probably from different +% distributions. +% +% To test a sample V against a distribution, produce a *big* sample VS +% from this distribution, and test KTEST(V, VS). +% +% See also: QKS. +if (nargin ~= 2) + error('KSTEST: requires 2 samples.') ; +end + +[nl1 nc1] = size(v1) ; +if ((nl1 *nc1 <= 0) | (min([nl1 nc1]) ~= 1)) + error('KSTEST: first argument is empty or has wrong dimensions.') ; +end +[nl2 nc2] = size(v2) ; +if ((nl2 *nc2 <= 0) | (min([nl2 nc2]) ~= 1)) + error('KSTEST: second argument is empty or has wrong dimensions.') ; +end + +n1 = nl1 * nc1 ; +d1 = reshape(v1, n1, 1) ; +n2 = nl2 * nc2 ; +d2 = reshape(v2, n2, 1) ; +%[d1 d2 ]' +h = zeros(1, n1 + n2) ; + +for i=1:n1 + h1(i) = length(find(d1 <= d1(i))) * 1/n1 - length(find(d2 <= d1(i))) * 1/n2 ; +end +for i=1:n2 + h2(i) = length(find(d1 <= d2(i))) * 1/n1 - length(find(d2 <= d2(i))) * 1/n2 ; +end +d = max([abs(h1) abs(h2)]) ; +n = sqrt(n1 * n2 / (n1 + n2)) ; +p = qks((n + 0.12 + 0.11/n) * d) ; diff --git a/stat/kstest2.m b/stat/kstest2.m new file mode 100644 index 0000000..c3128d1 --- /dev/null +++ b/stat/kstest2.m @@ -0,0 +1,47 @@ +function [p, d] = kstest2(v1, v2) +%ktest2() - Kolmogorov-Smirnov test of two arrays. +% KSTEST2(V1, V2) gives the significance level of V1 and V2 being +% sampled from the same distribution. +% [PROB, D] = KSTEST2(V1, V2) returns : +% PROB : the probability of the KS statistics to be greater +% than observed on V1 and V2, +% D : the observed KS statistics. +% where V1 and V2 are the locations of the samples. +% +% When PROB is close to 0, the samples are probably from different +% distributions. +% +% To test a sample V against a distribution, produce a *big* sample VS +% from this distribution, and test KTEST(V, VS). +% +% See also: QKS. + +if (nargin ~= 2) + error('KSTEST: requires 2 samples.') ; +end + +[nl1 nc1] = size(v1) ; +if ((nl1 *nc1 <= 0) | (min([nl1 nc1]) ~= 1)) + error('KSTEST: first argument is empty or has wrong dimensions.') ; +end +[nl2 nc2] = size(v2) ; +if ((nl2 *nc2 <= 0) | (min([nl2 nc2]) ~= 1)) + error('KSTEST: second argument is empty or has wrong dimensions.') ; +end + +n1 = nl1 * nc1 ; +d1 = reshape(v1, n1, 1) ; +n2 = nl2 * nc2 ; +d2 = reshape(v2, n2, 1) ; +%[d1 d2 ]' +h = zeros(1, n1 + n2) ; + +for i=1:n1 + h1(i) = length(find(d1 <= d1(i))) * 1/n1 - length(find(d2 <= d1(i))) * 1/n2 ; +end +for i=1:n2 + h2(i) = length(find(d1 <= d2(i))) * 1/n1 - length(find(d2 <= d2(i))) * 1/n2 ; +end +d = max([abs(h1) abs(h2)]) ; +n = sqrt(n1 * n2 / (n1 + n2)) ; +p = qks((n + 0.12 + 0.11/n) * d) ; diff --git a/stat/levenetest.m b/stat/levenetest.m new file mode 100644 index 0000000..3d26b36 --- /dev/null +++ b/stat/levenetest.m @@ -0,0 +1,153 @@ +function [Levenetest] = Levenetest(X,alpha) +%Levene's Test for Homogeneity of Variances. +%[In the Levene's test the data are transforming to yij = abs[xij - mean(xj)] +%and uses the F distribution performing an one-way ANOVA using y as the +%dependent variable (Brownlee, 1965; Miller, 1986)]. +% +% Syntax: function [Levenetest] = Levenetest(X,alfa) +% +% Inputs: +% X - data matrix (Size of matrix must be n-by-2; data=column 1, sample=column 2). +% alpha - significance level (default = 0.05). +% Outputs: +% - Sample variances vector. +% - Whether or not the homoscedasticity was met. +% +% Example: From the example 10.1 of Zar (1999, p.180), to test the Levene's +% homoscedasticity of data with a significance level = 0.05. +% +% Diet +% --------------------------------- +% 1 2 3 4 +% --------------------------------- +% 60.8 68.7 102.6 87.9 +% 57.0 67.7 102.1 84.2 +% 65.0 74.0 100.2 83.1 +% 58.6 66.3 96.5 85.7 +% 61.7 69.8 90.3 +% --------------------------------- +% +% Data matrix must be: +% X=[60.8 1;57.0 1;65.0 1;58.6 1;61.7 1;68.7 2;67.7 2;74.0 2;66.3 2;69.8 2; +% 102.6 3;102.1 3;100.2 3;96.5 3;87.9 4;84.2 4;83.1 4;85.7 4;90.3 4]; +% +% Calling on Matlab the function: +% Levenetest(X) +% +% Answer is: +% +% The number of samples are: 4 +% +% ---------------------------- +% Sample Size Variance +% ---------------------------- +% 1 5 9.3920 +% 2 5 8.5650 +% 3 4 7.6567 +% 4 5 8.3880 +% ---------------------------- +% +% Levene's Test for Equality of Variances F=0.0335, df1= 3, df2=15 +% Probability associated to the F statistic = 0.9914 +% The associated probability for the F test is larger than 0.05 +% So, the assumption of homoscedasticity was met. +% + +% Created by A. Trujillo-Ortiz and R. Hernandez-Walls +% Facultad de Ciencias Marinas +% Universidad Autonoma de Baja California +% Apdo. Postal 453 +% Ensenada, Baja California +% Mexico. +% atrujo@uabc.mx +% +% April 11, 2003. +% +% To cite this file, this would be an appropriate format: +% Trujillo-Ortiz, A. and R. Hernandez-Walls. (2003). Levenetest: Levene's test for +% homogeneity of variances. A MATLAB file. [WWW document]. URL http://www.mathworks.com/ +% matlabcentral/fileexchange/loadFile.do?objectId=3375&objectType=FILE +% +% References: +% +% Brownlee, K. A. (1965) Statistical Theory and Methodology in Science +% and Engineering. New York: John Wiley & Sons. +% Miller, R. G. Jr. (1986) Beyond ANOVA, Basics of Applied Statistics. +% New York: John Wiley & Sons. +% Zar, J. H. (1999), Biostatistical Analysis (2nd ed.). +% NJ: Prentice-Hall, Englewood Cliffs. p. 180. +% + +if nargin < 2, + alpha = 0.05; +end + +Y=X; +k=max(Y(:,2)); +fprintf('The number of samples are:%2i\n\n', k); + +%Levene's Procedure. +n=[];s2=[];Z=[]; +indice=Y(:,2); +for i=1:k + Ye=find(indice==i); + eval(['Y' num2str(i) '=Y(Ye,1);']); + eval(['mY' num2str(i) '=mean(Y(Ye,1));']); + eval(['n' num2str(i) '=length(Y' num2str(i) ') ;']); + eval(['s2' num2str(i) '=(std(Y' num2str(i) ').^2) ;']); + eval(['Z' num2str(i) '= abs((Y' num2str(i) ') - mY' num2str(i) ');']); + eval(['xn= n' num2str(i) ';']); + eval(['xs2= s2' num2str(i) ';']); + eval(['x= Z' num2str(i) ';']); + n=[n;xn];s2=[s2;xs2];Z=[Z;x]; +end + +Y=[Z Y(:,2)]; + +fprintf('-----------------------------\n'); +disp(' Sample Size Variance') +fprintf('-----------------------------\n'); +for i=1:k + fprintf(' %d %2i %.4f\n',i,n(i),s2(i)) +end +fprintf('-----------------------------\n'); +disp(' ') + +C=(sum(Y(:,1)))^2/length(Y(:,1)); %correction term. +SST=sum(Y(:,1).^2)-C; %total sum of squares. +dfT=length(Y(:,1))-1; %total degrees of freedom. + +indice=Y(:,2); +for i=1:k + Ye=find(indice==i); + eval(['A' num2str(i) '=Y(Ye,1);']); +end + +A=[]; +for i=1:k + eval(['x =((sum(A' num2str(i) ').^2)/length(A' num2str(i) '));']); + A=[A,x]; +end + +SSA=sum(A)-C; %sample sum of squares. +dfA=k-1; %sample degrees of freedom. +SSE=SST-SSA; %error sum of squares. +dfE=dfT-dfA; %error degrees of freedom. +MSA=SSA/dfA; %sample mean squares. +MSE=SSE/dfE; %error mean squares. +F=MSA/MSE; %F-statistic. +v1=dfA;df1=v1; +v2=dfE;df2=v2; + +P = 1 - fcdf(F,v1,v2); %probability associated to the F-statistic. + +fprintf('Levene''s Test for Equality of Variances F=%3.4f, df1=%2i, df2=%2i\n', F,df1,df2); +fprintf('Probability associated to the F statistic = %3.4f\n', P); + +if P >= alpha; + fprintf('The associated probability for the F test is equal or larger than% 3.2f\n', alpha); + fprintf('So, the assumption of homoscedasticity was met.\n'); +else + fprintf('The associated probability for the F test is smaller than% 3.2f\n', alpha); + fprintf('So, the assumption of homoscedasticity was not met.\n'); +end \ No newline at end of file diff --git a/stat/mvrnorm.m b/stat/mvrnorm.m new file mode 100644 index 0000000..562b8fd --- /dev/null +++ b/stat/mvrnorm.m @@ -0,0 +1,96 @@ +function = mvrnorm(input,varargin) +%MVRNORM - Robust Correlation by Rich Herrington +% [] = mvrnorm(input) +% +% Example +% >> mvrnorm +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2009 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2009-10-13 Creation +% +% ----------------------------- Script History --------------------------------- + +% Dealing with Outliers in Bivariate Data: Robust Correlation +% By Dr. Rich Herrington, Research and Statistical Support Consultant +% +% R script: +% +% # n=sample size +% # p=number of columns +% # u=mean of columns +% # s=standard deviation of columns +% # S=correlation matrix +% +% mvrnorm <- function(n, p, u, s, S) { +% Z <- matrix(rnorm(n*p), p, n) +% t(u + s*t(chol(S))%*% Z) +% } +% +% # Sample from Bivariate Normal +% +% z<-mvrnorm(n=100, p=2, u=c(100,100), s=c(15,15), S=matrix(c(1, .4, .4, 1), ncol=2, nrow=2,byrow=T)) +% +% # Fit least squares regression +% +% z.fit<-lm(z[,1]~z[,2]) +% z.fit +% +% # Plot scatterplot +% plot(z[,1], z[,2]) +% +% # Plot best fit line +% abline(z.fit) +% +% # Calculate Pearson's Correlation +% cor(z[,1], z[,2]) + +if nargin<1 + error('No data!') +elseif nargin==1 || isnumeric(action) + varargin=[{action} varargin ]; + action='init'; +end + +try + varargout={eval(sprintf('action_%s(varargin{:});',action))}; +catch + eval(sprintf('action_%s(varargin{:});',action)); +end + + +function [ha]=action_init(data, varargin) +% ------------------------------------------------------------------------- +% Check OPTIONS and default values when optional arguments are not +% specified +Def_OPTIONS = struct(... + 'option1',[],... + ); +if nargin < 2 + OPTIONS = Def_OPTIONS; +else + if length(varargin)>1 + OPTIONS = cell2struct(varargin(2:2:end),varargin(1:2:end),2); + % struct(varargin{:}) bugs with something like {'string1' 'string2'} in the inputs! + else + OPTIONS = varargin{1}; + end + % Check field names of passed OPTIONS and fill missing ones with default values + DefFieldNames = fieldnames(Def_OPTIONS); + for k = 1:length(DefFieldNames) + if ~isfield(OPTIONS,DefFieldNames{k}) + OPTIONS = setfield(OPTIONS,DefFieldNames{k},getfield(Def_OPTIONS,DefFieldNames{k})); + end + end + clear DefFieldNames +end +clear Def_OPTIONS +% ------------------------------------------------------------------------- \ No newline at end of file diff --git a/stat/mwwtest.m b/stat/mwwtest.m new file mode 100644 index 0000000..8b43990 --- /dev/null +++ b/stat/mwwtest.m @@ -0,0 +1,129 @@ +function STATS=mwwtest(x1,x2) +%Mann-Whitney-Wilcoxon non parametric test for two unpaired groups. +%This file execute the non parametric Mann-Whitney-Wilcoxon test to evaluate the +%difference between unpaired samples. If the number of combinations is less than +%20000, the algorithm calculates the exact ranks distribution; else it +%uses a normal distribution approximation. The result is not different from +%RANKSUM MatLab function, but there are more output informations. +%There is an alternative formulation of this test that yields a statistic +%commonly denoted by U. Also the U statistic is computed. +% +% Syntax: STATS=MWWTEST(X1,X2) +% +% Inputs: +% X1 and X2 - data vectors. +% Outputs: +% - T and U values and p-value when exact ranks distribution is used. +% - T and U values, mean, standard deviation, Z value, and p-value when +% normal distribution is used. +% If STATS nargout was specified the results will be stored in the STATS +% struct. +% +% Example: +% +% X1=[181 183 170 173 174 179 172 175 178 176 158 179 180 172 177]; +% +% X2=[168 165 163 175 176 166 163 174 175 173 179 180 176 167 176]; +% +% Calling on Matlab the function: mwwtest(X1,X2) +% +% Answer is: +% +% MANN-WHITNEY-WILCOXON TEST +% -------------------------------------------------------------------------------- +% Sample size is good enough to use the normal distribution approximation +% +% T U mT sT zT p-value (1-tailed) +% -------------------------------------------------------------------------------- +% 270.0000 150.0000 232.5000 24.1071 1.5348 0.0624 +% -------------------------------------------------------------------------------- +% +% Created by Giuseppe Cardillo +% giuseppe.cardillo-edta@poste.it +% +% To cite this file, this would be an appropriate format: +% Cardillo G. (2009). MWWTEST: Mann-Whitney-Wilcoxon non parametric test for two unpaired samples. +% http://www.mathworks.com/matlabcentral/fileexchange/25830 + +%Input Error handling +if ~isvector(x1) || ~isvector(x2) + error('MWWTEST requires vector rather than matrix data.'); +end +if ~all(isfinite(x1)) || ~all(isnumeric(x1)) || ~all(isfinite(x2)) || ~all(isnumeric(x2)) + error('Warning: all X1 and X2 values must be numeric and finite') +end + +L=[length(x1) length(x2)]; k=min(L); N=sum(L); N1=N+1; %set the basic parameter + +[A,B]=tiedrank([x1(:); x2(:)]); %compute the ranks and the ties +%Compute the Mann-Whitney-Wilcon statistic summing the ranks of the sample with +%the less number of elements. +if L(1)<=L(2) + T=sum(A(1:k)); +else + T=sum(A(k+1:end)); +end + +%There is an alternative formulation of this test that yields a statistic +%commonly denoted by U. U is related to T by the formula U=T-k*(k+1)/2, +%where k is the size of the smaller sample (or either sample if both contain +%the same number of individuals). For a presentation of the U statistic, see: +% S. Siegel and N. J. Castellan, Jr., Nonparametric Statistics for the +% Behavioral Sciences, 2d ed. McGraw-Hill, New York, 1988, Section 6.4, “The +% Wilcoxon-Mann-Whitney U Test.â€? +%For a detailed derivation and discussion of the Mann-Whitney test as developed +%here, as well as its relationship to U, see: +% F. Mosteller and R. Rourke, Sturdy Statistics: Nonparametrics and Order +% Statistics, Addison-Wesley, Reading, MA, 1973, Chapter 3, “Ranking Methods for +% Two Independent Samples.â€? +U=T-k*(k+1)/2; + + +tr=repmat('-',1,80); %set the divisor +disp('MANN-WHITNEY-WILCOXON TEST') +disp(tr) +%if the number of combinations to obtain the exact MWW distribution is >20000 +%use the normal approximation +if round(exp(gammaln(N1)-gammaln(k+1)-gammaln(N1-k))) > 20000 + mT=k*N1/2; %mean + sT=realsqrt(prod(L)/12*(N1-2*B/N/(N^2-1))); %standard deviation + zT=(abs(T-mT)-0.5)/sT; %z-value with correction for continuity + %p=1-normcdf(zT); %p-value + p = 1-(0.5 * erfc(-zT ./ sqrt(2))); + %display results + disp('Sample size is good enough to use the normal distribution approximation') + disp(' ') + fprintf('T\t\tU\t\tmT\t\tsT\t\tzT\t\tp-value (1-tailed)\n') + disp(tr) + fprintf('%0.4f\t%0.4f\t%0.4f\t%0.4f\t\t%0.4f\t\t%0.4f\n',T,U,mT,sT,zT,p) + if nargout + STATS.method='Normal approximation'; + STATS.T=T; + STATS.U=U; + STATS.mean=mT; + STATS.std=sT; + STATS.z=zT; + STATS.p=p; + end +else + z=1:N; + M=combnk(z,k); %M is the matrix of all possible combinations of N elements taken k at time + pdf=sum(z(M),2); %probability density function of the MWW distribution + %to compute the p-value see how many values are more extreme of the observed + %T and then divide for the total number of combinations + p=length(pdf(pdf>=T))/length(pdf); + %display results + disp('The exact Mann-Whitney-Wilcoxon distribution was used') + disp(' ') + fprintf('T\t\tU\tp-value (1-tailed)\n') + disp(tr) + fprintf('%0.4f\t%0.4f\t%0.4f\n',T,U,p) + if nargout + STATS.method='Exact distribution'; + STATS.T=T; + STATS.U=U; + STATS.p=p; + end +end +disp(tr) +disp(' ') diff --git a/stat/myanova.m b/stat/myanova.m new file mode 100644 index 0000000..e8a0e30 --- /dev/null +++ b/stat/myanova.m @@ -0,0 +1,700 @@ +function [p,F,fx,varargout]=myanova(X,nf,rp,epsilon,verbose,posthoc) +% myanova - N-way ANOVA +% [p,F,fx]=myanova(X,nf) +% [p,F,fx,epsilon,df,dfe,SS,SSe,SSt]=... +% Performs a N-way beween subjects anova on X +% +%INPUTS: +% X = [ factor1 x factor2 x ... x factorN x replicates x ...] +% nf: number of factors +% This function is vectorized, ie. ANOVA is computed for each value in X +% beyond the 'replicates' (i.e. subject) dimension, which is (nf+1). +% +%OUTPUTS: +% p: the Null hypothesis probability +% F: the F-values +% fx: a cell array listing the tested effects. +% eta: percentage of explained variance by each factor +% epsilon: sphericity correction if applicable +% +%OPTIONAL INPUTS: +% [...]=myanova(X,nf,rp) +% To specifiy repeated/within-subject factors in a within-subject design +% rp: an array listing the dimensions of repeated factors +% +% [...]=myanova(X,nf,rp,epsilon) +% Correct for non-spherical data using epsilon (expanded to match the +% size of measures, if necessary). If epsilon=NaN, epsilon(s) will be +% computed using Greenhouse-Geisser correction or Huynh-Feldt (if eGG>.7) +% To apply no correction, set epsilon=1 (default) +% +% [...]=myanova(X,nf,rp,epsilon,verbose) +% verbose = 1 will display a table of results +% +% Examples: +% +% X(1:2,1:3,1:10) = data from 2 tasks by 3 condition for 10 subjects, +% both factors (dimensions 1 and 2) are within-subject factors: +% >> [p,F,fx]=myanova(X,2,1:2) +% p: 3x1 array +% F: 3x1 array +% fx: 3x1 cells: { 1 , 2 , [ 1 2 ]} +% +% X(1:3,1:4,1:10) = data from 3 groups of 10 subjects performing 4 tasks +% only the second factor (dimension 2 of X) is a within-subject variable: +% >> [p,F,fx]=myanova(X,2,[2]) +% p: 3x1 array +% F: 3x1 array +% fx: 3x1 cells: { 1 , 2 , [ 1 2 ]} +% +% Requires: f_cdf() + +% ----------------------------- Script History --------------------------------- +% KND 2007...2008 many changes... +% KND 2008-08-27 Corrected version (at last!) +% +% ----------------------------- Script History --------------------------------- + +% Version 2.0 - 27-Aug-2008 +sX=[size(X) 1]; +nX=ndims(X); +if nargin<2 + error('Number of factors (nf) is mandatory') +end +if ~isscalar(nf) + error('Number of factors should be a scalar'); +end +if nargin<3 + rp=[]; +end + +% Number of groups for each factor: +ng=sX(1:nf); +% Number of cells: +nc=prod(ng); +% Number of replicates, ie. samples in each group: +nr=sX(nf+1); +%Total number of observations: +no=nc*nr; +% Dimensions of (univariate) dependent variables: +svar=[sX(nf+2:end)]; +if ~isempty(rp) && nr<2 + error('You must have more than one observation per cell or more than one subject per condition') +end +% if (nr<=1) +% error('myanova doesn''t work with 1 subject / group!') +% end + +if nargin<3 + rp=[]; +end + if ~isempty(rp) & ~all(ismember(1:nf,rp)) + error('Mixed designs (split plot) ANOVA unavailable!') + end +% if ~isempty(rp) & nf>2 +% error('Repeated measure ANOVA is only available for 2 factor designs!') +% end +if nargin<4 + epsilon=1; +end +if nargin<5 || isempty(verbose) + % No display for massively univariate data... + verbose = (nargout==0) & prod([1 svar])<10; +end +if isempty(epsilon) + epsilon=NaN; +end +if nargin<6 + posthoc=[]; +end + +fx={}; +for i=1:nf + fx=[fx ; num2cell(nchoosek(1:nf,i),2)]; +end +nfx=length(fx); + +% Posthoc tests +if isempty(posthoc) + % No display for massively univariate data... + if verbose + posthoc = 1; + else + posthoc = 0; + end +end +if iscell(posthoc) + posthoc_fx = posthoc; + posthoc=1; +elseif isequal(posthoc,1) + posthoc_fx = cell(nfx,1 ); +end + +% Within Group/Error stats +% Mean in each cell +mXw=mean(X,nf+1); +% Residual Error of the model +SSe=X-repmat(mXw,[ones(1,nf) nr 1]); +SSe=reshape(SSe, nc*nr, []).^2; +SSe=sum(SSe); +dfe=nc*(nr-1); +MSe=squeeze(SSe/dfe); +% pre-allocate sum of squares of effects and errors: +SSbe=repmat(SSe,nfx,1); +SS=NaN.*SSbe; +%keeps the full model unexplained variance for later +MSw=MSe; +dfw=dfe; + +% Population stats +dft=(nc*nr)-1; +% Grand mean value +mX=reshape(mXw, nc, []); +mX=mean(mX,1); +% Correction factor: +% cX=mX.^2./(nc*nr); + +% These are not used afterwards +SSt=sum((reshape(X, nc*nr, [])-repmat(mX,[nc*nr 1])).^2); +% MSt=squeeze(SSt/dft); + +if ~isempty(rp) + % Correct Within Error term to account for repeated measurements + dfr=(nr-1); + mXr=permute(X, [nf+1 1:nf nf+2:nX]); + mXr=reshape(mXr,nr,nc,[]); + mXr=mean(mXr,2); + SSr=nc*sum((reshape(mXr, nr, []) - repmat(mX,[nr 1])).^2); +end + + + +% Sphericity checking +if isnan(epsilon) % | ischar(epsilon) + % check for sphericity first + if all(ng<=2) + epsilon=1; + else + [eGG,eHF]=sphericity(X,nf); + epsilon=eGG; + epsilon(eGG>.7)=eHF(eGG>.7); + epsilon(ng==2)=1; + epsilon=min(epsilon,1); + end +end +if numel(epsilon)==1; + epsilon=epsilon*ones(nfx,prod(sX(nf+2:end))); +elseif length(epsilon(:))==nfx + epsilon=epsilon(:)*ones(1,prod(sX(nf+2:end))); +end +varargout{1} = epsilon; + +% Number of groups in between subjects +if ~isempty(rp) + tmp=zeros(1,nf); + tmp(rp)=1; + rp=logical(tmp); + % number of cells for between-subject factors + ncb=prod(ng(~rp)); + nc=nc/ncb; + % Order factors so that within ones come first, followed by the between + % fxw = [find(rp) find(~rp)]; + % ngw = sX(pfxw); +else + rp=logical(zeros(1,nf)); +end + + +% % Within Group/Error stats +% % i.e unmodeled error +% mXe=mean(X,nf+1); +% SSe=X-repmat(mXe,[ones(1,nf) nr 1]); +% SSe=reshape(SSe, [nc nr svar]).^2; +% SSe=sum(sum(SSe)); +% MSe=squeeze(SSe/dfe); + + +% Population stats +dft=(no)-1; +% mX=reshape(mXe,nc,[svar]); +% varargout{1}=epsilon; +% end +if nargout > 8 || verbose==1 + varargout{6}=SSt; +end + +for i=1:length(fx) + j=fx{i}; + % Between groups stats for each factor + % Dimensions of the factors pooled through factor j + nj=setdiff(1:nf,j); + % Nb of cells for effect j + ncj = prod(ng(j)); + % Nb of cells pooled by effect j + nbj = nc./ncj; + % Degrees of freedom for this effect + dfb=prod(ng(j)-1); + mXb=permute(mXw, [j nj nf+1:nX]); + mXb=reshape(mXb,ncj,nbj,[]); + mXb=mean(mXb,2); + SSb=nr*nbj*sum((reshape(mXb,ncj, []) - repmat(mX,[ncj 1])).^2); + + if length(j)>1 + % Interaction: + % we need to remove the variance due to embedded effects from the + % Sum of Squares of the current interaction effect + for k=1:i-1 + if all(ismember(fx{k}, j)) + SSb=SSb-SS(k,:); + end + end + end + % Keep track of the SS of the current effect + SS(i,:)=SSb; + MSb=SSb./dfb; + + dfe=nc*(nr-1); + if any(rp(j)) + % Correct Error term to account for repeated measurements + dfe=(nr-1)*dfb; + mXe=permute(X, [nf+1 j nj nf+2:nX]); + mXe=reshape(mXe,nr*ncj,nbj,[]); + mXe=mean(mXe,2); + % Unexplained variance for this effect: + SSe=nbj*sum((reshape(mXe, nr*ncj, []) - repmat(mX,[nr*ncj 1])).^2); + % Subtract replication/subject variance and the effect itself + SSe=SSe-SSr-SSb; + for k=1:i-1 + %remove embedded factors effects and error terms + if all(ismember(fx{k}, j)) + SSe=SSe-SS(k,:)-SSbe(k,:); + end + end + SSbe(i,:)=SSe; + end + MSe=squeeze(SSbe(i,:)/dfe); + % % MSr=squeeze(SSr/dfr); + % else + % dfw=nc*(nr-1); + % MSw=squeeze(SSw/dfw); + % + % dfe=dfw; + % MSe=MSw; + % end + dfb=dfb.*epsilon(i,:); + dfe=dfe.*epsilon(i,:); + F(i,:)=squeeze(MSb)./MSe; + p(i,:)=1-f_cdf(F(i,:),dfb,dfe); + % dfb=dfb.*epsilon(i,:); + % dfw=dfw.*epsilon(i,:); + % F(i,:)=squeeze(MSb)./MSw; + % p(i,:)=1-f_cdf(F(i,:),dfb,dfw); + % end + % [p,F,fx,epsilon(1),df(2),dfe(3),SS(4),SSe(5),SSt(6)]=... + if nargout>4 || verbose==1 + varargout{2}(i,:)=dfb; + end + if nargout>5 || verbose==1 + %if any(rp) + varargout{3}(i,:)=dfe; + %else + % varargout{3}(i,:)=dfw; + %end + end + if nargout>6 || verbose==1 + varargout{4}(i,:)=SSb; + end + if nargout>7 || verbose==1 + %if any(rp) + varargout{5}(i,:)=SSe; + %else + % varargout{5}(i,:)=SSw; + %end + end +end + +if prod(sX)>nc*nr + p=reshape(p,[nfx,sX(nf+2:end)]); + F=reshape(F,[nfx,sX(nf+2:end)]); +end + +if verbose + [ignore,df,dfe,SSb,SSe,SSt]=deal(varargout{:}); + MS=SS./df; + + %Display results + fprintf([repmat('-',1,80) '\n'] ); + fprintf(['%' num2str(max(cellfun('length', fx))*2+15) 's \t| df \t| SS \t| MS \t| F \t| p\n'], 'ANOVA results'); + fprintf([repmat('-',1,80) '\n'] ); + for j=1:min(size(p,2),6) + if size(p,2)>1 + fprintf('MULTIDIMENSIONAL DATA: [%d] ...\n',j) + end + + for i=1:length(fx) + if i<=nf + label=sprintf('Factor #%d',i); + else + label=['Interaction ' sprintf('%d*',fx{i})]; + label=label(1:end-1); + end + label=sprintf([ '%' num2str(max(cellfun('length', fx))*2+15) 's'],label); + fprintf('%s: \t % 3.3g \t% 8.4g\t% 8.4g\t% 8.4g\t %g\n',... + label,df(i),SS(i,j),MS(i,j),F(i,j),p(i,j)); + if any(rp) || i==nfx + label=sprintf([ '%' num2str(length(label)) 's'],'Error'); + fprintf('%s: \t % 3.3g \t% 8.4g\t% 8.4g\n',... + label,dfe(i),SSe(i,j),SSe(i,j)./dfe(i)); + end + end + + if any(rp) + MS_r=SSr./dfr; + label=sprintf([ '%' num2str(length(label)) 's'],'Subj/Repl'); + fprintf('%s: \t % 3.3g \t% 8.4g\t% 8.4g',... + label,dfr,SSr(j),MS_r(j)); + % The following is wrong: + % MSw is not the correct error term for the subj effect + % F_r=squeeze(MS_r)./MSw; + % p_r=1-f_cdf(F_r,dfr,dfw); + % fprintf('\t% 8.4g\t %g\n',... + % F_r(j),p_r(j)); + fprintf('\n'); + end + + label=sprintf([ '%' num2str(max(cellfun('length', fx))*2+15) 's'],'Total'); + fprintf('%s: \t % 3.3g \t% 8.4g\n',... + label,dft,SSt(j)); + if size(p,2)>1 + fprintf([repmat('. ',1,40) '\n']) + end + + end + if j3 + SS(nfx+1,:)=SSt; + SS(nfx+2,:)=SSw; + SS(nfx+3,:)=SSr; + SS(nfx+4,:)=SSe; +end +if nargout>4 + df(nfx+1,:)=dft; + df(nfx+2,:)=dfw; + df(nfx+3,:)=dfr; + df(nfx+4,:)=dfe; +end +if nargout>5 + MS(nfx+1,:)=MSt; + MS(nfx+2,:)=MSw; + MS(nfx+3,:)=MSr; + MS(nfx+4,:)=MSe; +end + + +return + + +function [SS]= sumofsquares(X,j,nf,ncj,nbj,nX,mXw) + % Between groups stats for a given factor j + % ncj. Nb of cells for effect j + %ncj = prod(ng(j)); + % nbj: Nb of cells pooled by effect j + %nbj = nc./ncj; + mXb=permute(mX, [j setdiff(1:nf,j) nf+1:nX]); + mXb=reshape(mXb,ncj,nbj,[]); + mXb=mean(mXb,2); + SS=nr*nbj*sum((reshape(mXb,ncj, []) - repmat(mX,[ncj 1])).^2); + +return + + +%% RESSOURCES +% http://www.brown.edu/Research/LCE/Fall2004/Within%20subjs.pdf +% + +%% EXAMPLES +% +% from Zar, 1999, pp. 250 sqq. +% Ex. 12.5 (repeated measure one-way ANOVA) +X= [ + 164 152 178 + 202 181 222 + 143 136 132 + 210 194 216 + 228 219 245 + 173 159 182 + 161 157 165 + ]'; +% F = (1454/2)/(695.3/12) = 727/57.94 = 12.6 + + +% BETWEEN SUBJECT DESIGN WITH 2 REPLICATES +% ======================================== +% http://www.richland.edu/james/lecture/m170/ch13-2wy.html +% SS = 512.8667 449.4667 143.1333 136.0000 1241.4667 +% F observed = 28.283 12.393 1.973 +% F critique = 3.682 3.056 2.641 +X=[ + 106, 110 , 95, 100 , 94, 107 , 103, 104 , 100, 102; + 110, 112 , 98, 99 , 100, 101 , 108, 112 , 105, 107; + 94, 97, 86, 87 98, 99 99, 101 94, 98]; +X=cat(3,X(:,1:2:end),X(:,2:2:end)) +[p,F]=myanova(X,2,[]) + + + +% % http://www.linguistics.ucla.edu/faciliti/facilities/statistics/fromoac.htm +X=[ 8 9 8 8 9 7 10 9 10; 9 10 9 10 9 13 8 9 9; 8 7 7 12 7 9 10 9 7; 6 8 9 8 10 10 12 9 10; 7 6 7 11 12 8 8 11 9]; +X=reshape(X,5,3,3); +% +% % COND TRIAL SUBJECT +X=permute(X,[3 2 1]); +% +[p,F]=myanova(X,2,1:2) +% l=factorlabels(X) +% rm_anova2(X(:), l(:,3),l(:,1),l(:,2),{'COND', 'TRIAL'}) +% % 'Source' 'SS' 'df' 'MS' 'F' 'p' +% % 'COND' [24.8444] [ 2] [12.4222] [4.0216] [0.0618] +% % 'TRIAL' [ 0.3111] [ 2] [ 0.1556] [0.0625] [0.9399] +% % 'COND x TRIAL' [ 1.6889] [ 4] [ 0.4222] [0.1907] [0.9397] +% % 'COND x Subj' [24.7111] [ 8] [ 3.0889] [] [] +% % 'TRIAL x Subj' [19.9111] [ 8] [ 2.4889] [] [] +% % 'COND x TRIAL x Subj' [35.4222] [16] [ 2.2139] [] [] + + + +% http://lib.stat.cmu.edu/DASL/Datafiles/airpullutionfiltersdat.html +% 1. NOISE = Noise level reading (decibels) +% 2. SIZE = Vehicle size: 1 small 2 medium 3 large +% 3. TYPE = 1 standard silencer 2 Octel filter +% 4. SIDE = 1 right side 2 left side of car +% and 3 replicates + +x=[ + 810 1 1 1 + 820 1 1 1 + 820 1 1 1 + 840 2 1 1 + 840 2 1 1 + 845 2 1 1 + 785 3 1 1 + 790 3 1 1 + 785 3 1 1 + 835 1 1 2 + 835 1 1 2 + 835 1 1 2 + 845 2 1 2 + 855 2 1 2 + 850 2 1 2 + 760 3 1 2 + 760 3 1 2 + 770 3 1 2 + 820 1 2 1 + 820 1 2 1 + 820 1 2 1 + 820 2 2 1 + 820 2 2 1 + 825 2 2 1 + 775 3 2 1 + 775 3 2 1 + 775 3 2 1 + 825 1 2 2 + 825 1 2 2 + 825 1 2 2 + 815 2 2 2 + 825 2 2 2 + 825 2 2 2 + 770 3 2 2 + 760 3 2 2 + 765 3 2 2 + ]; +[i,nl]=dgrouping(x(:,2:end)); +X(i)=x(:,1); +X=reshape(X,nl); + +% One way ANOVA from Loftus & Masson, 1994 +% Exposure Duration Per Word (sec) +% 1 Sec 2 Sec 5 Sec +X= [ + 10 13 13 + 6 8 8 + 11 14 14 + 22 23 25 + 16 18 20 + 15 17 17 + 1 1 4 + 12 15 17 + 9 12 12 + 8 9 12 + ]'; +% M1 = 11.0 M2 = 13.0 M3 = 14.2 + +% Two-way ANOVA from Cousineau 2007 +X= [ + 550 580 610 + 605 635 655 + 660 690 710 + ]'; + + +% Cousineau ? +X= [ + 150. 44. 71. 59. 132. 74. 1. + 335. 270. 156. 160. 118. 230. 1. + 149. 52. 91. 115. 43. 154. 1. + 159. 31. 127. 212. 71. 224. 1. + 159. 0. 35. 75. 71. 34. 1. + 292. 125. 184. 246. 225. 170. 1. + 297. 187. 66. 96. 209. 74. 1. + 170. 37. 42. 66. 114. 81. 1. + 346. 175. 177. 192. 239. 140. 2. + 426. 329. 236. 76. 102. 232. 2. + 359. 238. 183. 123. 183. 30. 2. + 272. 60. 82. 85. 101. 98. 2. + 200. 271. 263. 216. 241. 227. 2. + 366. 291. 263. 144. 220. 180. 2. + 371. 364. 270. 308. 219. 267. 2. + 497. 402. 294. 216. 284. 255. 2. + 282. 186. 225. 134. 189. 169. 3. + 317. 31. 85. 120. 131. 205. 3. + 362. 104. 144. 114. 115. 127. 3. + 338. 132. 91. 77. 108. 169. 3. + 263. 94. 141. 142. 120. 195. 3. + 138. 38. 16. 95. 39. 55. 3. + 329. 62. 62. 6. 93. 67. 3. + 292. 139. 104. 184. 193. 122. 3. + ]; +X=reshape(X(:,1:6),[8,3,6]); +X=permute(X,[1 3 2]); + + +% Cousineau 2005; TQPM +% Simulated data of a 2-by-5 exp with 16 subjects +X=str2num(urlread('http://www.tqmp.org/Content/vol01-1/p042/p042.dat')); +X=reshape(X(:,end),[16,5,2]); +X=permute(X,[3 2 1]); +myanova(X,2,1:2) +% all the variances are homogeneous and spherical, Tabachnik & Fidell, 1996 +% Mauchly s W = 0.74, p > .50 for factor 2 +% and W = 0.68, p > .50 for the interaction; +% this test cannot be performed for factor 1 since it has only two levels +% but we generated the data such that it is also homogenous. +% The Greenhouse Geiser and the Huynh Feldt epsilons are close to 1 so that +% we don t need to use corrections (Huynh, 1978, Rouanet and Lepine, 1970). + +% Effect name SS dl MS F p<0.001 +% Factor 1 10621 1 10621 76.8 *** +% Error 2073 15 135 +% Factor 2 11784 4 8196 16.4 *** +% Error 4378 60 72.9 +% Interaction 2250 4 562 6.52 *** +% Error 5171 60 86.2 +% Subject: F(1, 15) = 710, p < .001) + +% Analyzed using SPSS15:; +% Tests of Between-Subjects Effects +% Dependent Variable: X +% Source Type III Sum of Squares df Mean Square F Sig. +% Intercept Hypothesis 66283627.628 1 66283627.628 710.851 .000 +% Error 1398682.399 15 93245.493(a) +% fx1 Hypothesis 10621.640 1 10621.640 76.829 .000 +% Error 2073.752 15 138.250(b) +% fx2 Hypothesis 4784.064 4 1196.016 16.389 .000 +% Error 4378.604 60 72.977(c) +% Subject Hypothesis 1398682.399 15 93245.493 745.749 .000 +% Error 1314.799 10.515 125.036(d) +% fx1 * fx2 Hypothesis 2250.918 4 562.729 6.529 .000 +% Error 5171.445 60 86.191(e) +% fx1 * Subject Hypothesis 2073.752 15 138.250 1.604 .100 +% Error 5171.445 60 86.191(e) +% fx2 * Subject Hypothesis 4378.604 60 72.977 .847 .739 +% Error 5171.445 60 86.191(e) +% fx1*fx2*Subj Hypothesis 5171.445 60 86.191 . . +% Error .000 0 .(f) +% (a) MS(Subject) +% (b) MS(fx1 * Subject) +% (c) MS(fx2 * Subject) +% (d) 1.000 MS(fx1 * Subject) + MS(fx2 * Subject) - MS(fx1 * fx2 * Subject) +% (e) MS(fx1 * fx2 * Subject) +% (f) MS(Error) + + + + +% Loftus & Masson 1994 +% Reported in Cousineau 2005 +% X(2*5*6) = R vs U | SOA 50/100/200/400 | SUBJ(6) +X= [ + 1 450 462 12 460 482 22 460 497 37 480 507 27 + 2 510 492 -18 515 530 15 520 534 14 504 550 46 + 3 492 508 16 512 522 10 503 553 50 520 539 19 + 4 524 532 8 530 543 13 517 546 29 503 553 50 + 5 420 409 -11 424 452 28 431 468 37 446 472 26 + 6 540 550 10 538 528 -10 552 575 23 562 598 36 + ]; +X=reshape(X(:,2:end),6,3,4); +X=permute(X(:,1:2,:),[2 3 1]); + + +% Loftus % Masson +% TABLE 2: Hypothetical Subject Means for a Within-Subject Design +% Raw data Normalized data +% Subject Incongruous Congruous Neutral Mi Incongruous Congruous Neutral Mi +X= [ + 1 784 632 651 689.0 848.0 696.0 715.0 753.0 + 2 853 702 689 748.0 858.0 707.0 694.0 753.0 + 3 622 598 606 608.7 766.3 742.3 750.3 753.0 + 4 954 873 855 894.0 813.0 732.0 714.0 753.0 + 5 634 600 595 609.7 777.3 743.3 738.3 753.0 + 6 751 729 740 740.0 764.0 742.0 753.0 753.0 + 7 918 877 893 896.0 775.0 734.0 750.0 753.0 + 8 894 801 822 839.0 808.0 715.0 736.0 753.0 + ]; + +X=reshape(X(:,2:4),8,3); +X=permute(X,[2 1]); +myanova(X,1,1) +% +% ANOVA +% Source df SS MS F +% +% Subjects 7 280,178 +% Conditions 2 27,984 13,992 13.08 +% S * C 14 14,972 1,069 +% Total 23 323,133 + + +clc; +for i=1:2; for j=1:3; for k=1:5; for l=1:10; + xx(i,j,k,l)=randn; + fprintf('%d\t%d\t%d\t%d\t%f\n', i,j,k,l,xx(i,j,k,l)); +end;end;end;end \ No newline at end of file diff --git a/stat/myanova3.m b/stat/myanova3.m new file mode 100644 index 0000000..36ed703 --- /dev/null +++ b/stat/myanova3.m @@ -0,0 +1,301 @@ +function [p,F,fx,varargout]=myanova(X,nf,rp,epsilon) +% myanova - N-way ANOVA +% [p,F,fx]=myanova(X,nf) +% [p,F,fx,epsilon,df,dfe,SS,SSe,SSt]=... +% Performs a N-way beween subjects anova on X +% +%INPUTS: +% X = [ factor1 x factor2 x ... x factorN x replicates x ...] +% This function is vectorized, ie. anova is computed for each value in X +% after the 'replicates' (i.e. subject) dimension. +% nf: number of factors +% +%OUTPUTS: +% p: the Null hypothesis probability +% F: the F-values +% fx: a cell array listing the tested effects. +% eta: percentage of explained variance by each factor +% epsilon: sphericity correction if applicable +% +%OPTIONAL INPUTS: +% [...]=myanova(X,nf,rp) +% To specifiy repeated factors in a within-subject design +% rp: an array listing the repeated factors +% +% [...]=myanova(X,nf,rp,epsilon) +% Correct for non-spherical data using epsilon (expanded to match the +% size of measures, if necessary). If epsilon=NaN, epsilon(s) will be +% computed using Greenhouse-Geisser or Huynh-Feldt (if eGG>.7) +% +% Ex: +% 2 tasks by 3 condition for 10 subjects, within subject design: +% >> [p,F,fx]=myanova(X(1:2,1:3,1:10),2,1:2) +% p: 3x1 array +% F: 3x1 array +% fx: 3x1 cells: { 1 , 2 , [ 1 2 ]} +% +% Requires: f_cdf() + +sX=[size(X) 1]; +nX=ndims(X); +if nargin<2 + error('Number of factors (nf) is mandatory') +end +% Number of groups for each factor +ng=sX(1:nf); +% Nb of cells +png=prod(ng); +% Number of replicates, ie. samples in each group +nr=sX(nf+1); +%Size of dependent variables (DV) +szd=[size(X) ones(1,nf)]; +szd=szd(nf+2:end); +% Number of DV +pszd=prod(szd); + +if nargin<3 + rp=[]; +end +if ~isempty(rp) & ~all(ismember(1:nf,rp)) + error('Mixed designs (split plot) ANOVA unavailable!') +end +if ~isempty(rp) & nf>3 + error('Repeated measure ANOVA is only available for 2 factor designs!') +end +if nargin<4 + epsilon=1; +end + +% Within Group/Error stats +mXw=sum(X,nf+1)./sX(nf+1); +SSw=X-repmat(mXw,[ones(1,nf) nr 1]); +SSw=reshape(SSw, [png*nr, szd]); +SSw=sum(SSw.^2); +if (nr>1) + dfw=png*(nr-1); + MSw=squeeze(SSw/dfw); +else + error('(my)anova doesn''t work with 1 subject / group!') +end + +% Population stats +%Total number of freedom +dft=(png*nr)-1; +mX=reshape(mXw, [png, szd]); +% Mean value of DV per subject +mX=sum(mX,1)./png; + +% Useful value: +sX2=sum(reshape(X, [png*nr, szd]),1).^2./(png*nr); + +% These are not used afterwards +SSt=sum(reshape(X, [png*nr, szd]).^2,1); +SSt=SSt-sX2; +MSt=squeeze(SSt/dft); + +error +unterminated program!!! + +if ~isempty(rp) + % Correct Within Error term to account for repeated measurements + dfr=(nr-1); + mXr=permute(X, [nf+1 1:nf nf+2:nX]); + mXr=reshape(mXr,nr,png,pszd); + mXr=mean(mXr,2); + % SSr=png*sum((reshape(mXr, nr, szd) - repmat(mX,[nr 1])).^2); + SSr=png*(sum(reshape(mXr, nr, szd).^2,1) - mX2); + SSw=SSt-SSr; +end + +fx={}; +for i=1:nf + fx=[fx ; num2cell(nchoosek(1:nf,i),2)]; +end +nfx=length(fx); + +% Sphericity checking +if isnan(epsilon) + % check for sphericity first + if all(ng<=2) + epsilon=1; + else + [eGG,eHF]=sphericity(X,nf); + epsilon=eGG; + epsilon(eGG>.7)=eHF(eGG>.7); + epsilon(ng==2)=1; + epsilon=min(epsilon,1); + end +end +if numel(epsilon)==1; + epsilon=epsilon*ones(nfx,prod(sX(nf+2:end))); +elseif length(epsilon(:))==nfx + epsilon=epsilon(:)*ones(1,prod(sX(nf+2:end))); +end + +% [p,F,fx,epsilon(1),df(2),dfe(3),SS(4),SSe(5),SSt(6)]=... +if nargout > 3 + varargout{1}=epsilon; +end +if nargout > 8 + varargout{6}=SSt; +end + +for i=1:length(fx) + j=fx{i}; + % Between groups stats for each factor + dfb=prod(ng(j)-1); + mXb=permute(mXw, [j setdiff(1:nf,j) nf+1:nX]); + mXb=reshape(mXb, prod(ng(j)),png/prod(ng(j)), []); + mXb=mean(mXb,2); + SSb=nr*png/prod(ng(j))*sum((reshape(mXb, prod(ng(j)), []) - repmat(mX,[prod(ng(j)) 1])).^2); + if length(j)>1 + % Interaction: + % we need to remove single factor variance from the Sum of Squares + for k=1:i-1 + if all(ismember(fx{k}, j)) + SSb=SSb-SS(k,:); + end + end + end + SS(i,:)=SSb; + MSb=SSb./dfb; + + if not(isempty(rp)) + % Correct Error term to account for repeated measurements + if length(j)==1 + dfe=(nr-1)*dfb; + mXe=permute(X, [nf+1 j setdiff(1:nf,j) nf+2:nX]); + mXe=reshape(mXe,nr*prod(ng(j)),png/prod(ng(j)),[]); + mXe=mean(mXe,2); + SSe=png/prod(ng(j))*sum((reshape(mXe, nr*prod(ng(j)), []) - repmat(mX,[nr*prod(ng(j)) 1])).^2); + SSe=SSe-SSr-SSb; + SSbe(i,:)=SSe; + else + % Interaction effects residuals + % + % WARNING !!!! + % + % the following does NOT work for more-than-2-factors design! + % + dfe=(png-sum(ng-1)-1)*(nr-1); + mXe=reshape(X,png*nr,[]); + SSe=sum((mXe - repmat(mX,[nr*png 1])).^2); + SSe=SSe-SSr-sum(SSbe(1:nf,:))-sum(SS(1:nfx,:)); + end + MSe=squeeze(SSe/dfe); + MSr=squeeze(SSr/dfr); + dfb=dfb.*epsilon(i,:); + dfe=dfe.*epsilon(i,:); + F(i,:)=squeeze(MSb)./MSe; + p(i,:)=1-f_cdf(F(i,:),dfb,dfe); + else + dfb=dfb.*epsilon(i,:); + dfw=dfw.*epsilon(i,:); + F(i,:)=squeeze(MSb)./MSw; + p(i,:)=1-f_cdf(F(i,:),dfb,dfw); + end + % [p,F,fx,epsilon(1),df(2),dfe(3),SS(4),SSe(5),SSt(6)]=... + if nargout>4 + varargout{2}(i,:)=dfb; + end + if nargout>5 + if not(isempty(rp)) + varargout{3}(i,:)=dfe; + else + varargout{3}(i,:)=dfw; + end + end + if nargout>6 + varargout{4}(i,:)=SSb; + end + if nargout>7 + if not(isempty(rp)) + varargout{5}(i,:)=SSe; + else + varargout{5}(i,:)=SSw; + end + end +end + +if prod(sX)>png*nr + p=reshape(p,[nfx,sX(nf+2:end)]); + F=reshape(F,[nfx,sX(nf+2:end)]); +end + + + +return + + +if nargout>3 + SS(nfx+1,:)=SSt; + SS(nfx+2,:)=SSw; + SS(nfx+3,:)=SSr; + SS(nfx+4,:)=SSe; +end +if nargout>4 + df(nfx+1,:)=dft; + df(nfx+2,:)=dfw; + df(nfx+3,:)=dfr; + df(nfx+4,:)=dfe; +end +if nargout>5 + MS(nfx+1,:)=MSt; + MS(nfx+2,:)=MSw; + MS(nfx+3,:)=MSr; + MS(nfx+4,:)=MSe; +end + + +return + +%% RESSOURCES +% http://www.brown.edu/Research/LCE/Fall2004/Within%20subjs.pdf +% + +%% EXAMPLES +% +% from Zar, 1999, pp. 250 sqq. +% Ex. 12.5 repeated measure ANOVA +X= [ + 164 152 178 + 202 181 222 + 143 136 132 + 210 194 216 + 228 219 245 + 173 159 182 + 161 157 165 + ]'; +% F= 727/57.94 = 12.6 + +% http://www.richland.edu/james/lecture/m170/ch13-2wy.html +% SS = 512.8667 449.4667 143.1333 136.0000 1241.4667 +% F = 3.682 3.056 2.641 +X=[ + 106, 110 , 95, 100 , 94, 107 , 103, 104 , 100, 102; + 110, 112 , 98, 99 , 100, 101 , 108, 112 , 105, 107; + 94, 97, 86, 87 98, 99 99, 101 94, 98]; +X=cat(3,X(:,1:2:end),X(:,2:2:end)) + + + + +% % http://www.linguistics.ucla.edu/faciliti/facilities/statistics/fromoac.htm +% X=[ 8 9 8 8 9 7 10 9 10; 9 10 9 10 9 13 8 9 9; 8 7 7 12 7 9 10 9 7; 6 8 9 8 10 10 12 9 10; 7 6 7 11 12 8 8 11 9]; +% X=reshape(X,5,3,3); +% +% % COND TRIAL SUBJECT +% X=permute(X,[3 2 1]); +% +% [p,F]=myanova(X,2,1:2) +% l=factorlabels(X) +% rm_anova2(X(:), l(:,3),l(:,1),l(:,2),{'COND', 'TRIAL'}) +% % 'Source' 'SS' 'df' 'MS' 'F' 'p' +% % 'COND' [24.8444] [ 2] [12.4222] [4.0216] [0.0618] +% % 'TRIAL' [ 0.3111] [ 2] [ 0.1556] [0.0625] [0.9399] +% % 'COND x TRIAL' [ 1.6889] [ 4] [ 0.4222] [0.1907] [0.9397] +% % 'COND x Subj' [24.7111] [ 8] [ 3.0889] [] [] +% % 'COND x Subj' [19.9111] [ 8] [ 2.4889] [] [] +% % 'COND x TRIAL x Subj' [35.4222] [16] [ 2.2139] [] [] + + \ No newline at end of file diff --git a/stat/myanovaeffects.m b/stat/myanovaeffects.m new file mode 100644 index 0000000..fd76895 --- /dev/null +++ b/stat/myanovaeffects.m @@ -0,0 +1,27 @@ +function [fx]=myanovaeffects(nf,factornames) +% myanovaeffects - List effects and their interaction in a factorial design +% [fx]=myanovaeffects(nf) +% List main effects and interaction(s) in factorial design with [nf] factors +% [fx] is a N-by-1 cell array +% +% [fx]=myanovaeffects(nf,factornames) +% Makes up a cell list of char listing instead of the factor index, +% their names according to factornames. +% Example: +% >> myanovaeffects(2, {'FACTOR1','FACTOR2'}) +% outputs: { 'FACTOR1' 'FACTOR2' 'FACTOR1*FACTOR2' } +% +% See also: myanova +fx={}; +for i=1:nf + fx=[fx ; num2cell(nchoosek(1:nf,i),2)]; +end +if nargin>1 + for i=1:length(fx) + fxn{i,1}=factornames{fx{i}(1)}; + for j=2:length(fx{i}) + fxn{i,1}=[ fxn{i} '*' factornames{fx{i}(j)}]; + end + end + fx=fxn; +end \ No newline at end of file diff --git a/stat/mypermtest.m b/stat/mypermtest.m new file mode 100644 index 0000000..247916d --- /dev/null +++ b/stat/mypermtest.m @@ -0,0 +1,266 @@ +function [pv,S0,S,P]=mypermtest(X,X2,dim_p,dim_m,NP,tails) +% mypermtest - permutation test +% [pv,S,T,P]=mypermtest(X1,X2,dimP,dimM,NP) +% [pv]=mypermtest(X,ns,dim,NP,ftest[abstmax],fparams) +% +%Inputs: +% X1,X2: Data matrices. +% If only X1 is given, the permutation dimension (dim_p) should be given. +% Alternatively: +% X can be a single data matrix. But the number of samples (ie subjects) +% in the first condition should be set using: ns +% I.e. X = [ X1 ; X2 ] & ns=size(X1,1) +% dimP: Permuted dimensions (ie. subjects). Default: first non singleton +% If dim_p > 0 : paired permutations (default) +% Use dim_p < 0 for unpaired samples. +% dimM: Dimensions of data over which the maximum T-value is to be found. +% Default: all dimensions +% NP: Number of permutations (may lead to exhaustive permutation, +% for example if NP <= 2^N-1 using paired data) +% +%Outputs: +% pv: thresholds at the given p-values, computed from the permutations +% T: observed T-values +% S: T-values from the permutations +% P: permutations used + +% NOT YET: +% tails ; one/two tailed tests + +% ftest: function applied on data. +% Default is 'maxabst', i.e. compute the maximum (two-tailed) T-value +% fparams: cell array indicating the parameters passed to the function +% when called + +NMAX_UNPAIRED=15; % maximum number of observations for exhaustive search for unpaired samples +NMAX_PAIRED=30; % maximum number of observations for exhaustive search in paired samples +NPERMS=10000; %Max. number of permutations + +%initialize parameters +if nargin<3 + dim=find(size(X)>1); + if isempty(dim) + error('Input X has only singleton or null dimensions!') + end + dim=dim(1); + dim_p=dim; +else + dim=abs(dim_p); +end + + +% ORIGINAL OBSERVATIONS +sX=size(X); +nX=length(sX); +X=permute(X, [dim setdiff(1:nX,dim)]); +% the shape of the measurement data +nsX=size(X); +nsX(1)=[]; +ndX=ndims(X); + +if nargin<4 + dim_m=1:ndX-1; +end + +if prod(size(X2))>1 + X2=permute(X2, [dim setdiff(1:nX,dim)]); + sX2=size(X2); + if ~isequal(sX(setdiff(1:nX,dim)), sX2(2:end)) + error('X1 and X2 should be of the same size in the non-permuted dimensions!') + end + X=cat(1,X,X2); + N=[sX(dim) sX2(1)]; + clear sX2; +elseif prod(size(X2))==1 + N=[X2 sX(dim)-X2]; +else + error('Check inputs!') +end +clear X2; + +% Now compute the number of exhaustive permutations +if dim_p>0 + % We compute only one half of the permutations for paired samples + Nexh=2^(N(1)-1)-1; +else + Nexh=factorial(N(1)+N(2))/(factorial(N(1))*factorial(N(2)))-1; +end + +exhaustive=0; +if nargin<5 + if (dim_p>0 & (N(1)+N(2))Nexh + warning(sprintf('Cannot do %d permutations. For this dataset, the maximum is: %d.\nThis latter value will be used.',NP, Nexh)); + NP=Nexh; + exhaustive=1; +end + +if NP>NPERMS + warning(sprintf('Too many permutations! Only %d will be performed.',NPERMS)); + NP = NPERMS; + exhaustive=0; +end + +% DEBUG: +NP = min(500,NP); + +% List of permutations/randomizations +if dim_p>0 + % paired permutations + if exhaustive + % compute exhaustive list of perms + P=sprintf(sprintf('%%0%dd',N(1)), str2num(dec2bin(1:NP))); + P=reshape(P==49, [N(1),NP])'; + else + % non exhaustive + P=round(rand(NP,N(1))); + end + P=[P 1-P].*N(1)+repmat(1:N(1), [NP,2]); +else + % non-paired perms + if exhaustive + % all possible perms + p=nchoosek(1:(N(1)+N(2)),N(1)); + p(1,:)=[]; % the 1st is not a permutation + for i=1:NP + P(i,:)=[p(i,:) setdiff(1:(N(1)+N(2)),p(i,:))]; + end + clear p + else + % non exhaustive + for i=1:NP + P(i,:)=randperm(N(1)+N(2)); + end + end +end + +% PERMUTATION LOOP +for i=0:NP + if i==0 + % At the 0-th permutation, evaluate original data + Y=X; + else + Y=X(P(i,:),:); + end + + Z=mean(Y(1:N(1),:))-mean(Y(N(1)+(1:N(2)),:)); + Z= Z ./ sqrt( std(Y(1:N(1),:)).^2/N(1) + std(Y(N(1)+(1:N(2)),:)).^2/N(2)); + Z= abs(Z); + Z=reshape(Z,nsX); + + if i==0 + S0=Z; + sz=size(S0); + % preallocate memory + S=zeros([NP,sz]); + else + if ~isequal(dim_m,1:(ndX-1)) + Z=submax(Z,dim_m); + else + Z=max(Z(:)); + end + S(i,:)=Z(:); + end + +end + + +% Compute pvalues based on permutation statistics +pv=(sum(repmat(shiftdim(S0, -1),[NP 1])<=S)+1)/(NP+1); +pv=shiftdim(pv, 1); + +return + + +% FORGET THE REST! + +% S=sort(S); + +for pp=1:length(pthres) + Thd(pp,:) = S(ceil((length(S)*(1-pthres(pp)))),:); +end +S0=S0(1,:); + +return + +function [Z]=submax(Z,dims) +sZ=size(Z); +odims=setdiff(1:length(sZ),dims); +pdims=prod(sZ(dims)); +Z=permute(Z, [dims odims]); +Z=reshape(Z,[pdims, sZ(odims) 1]); +Z=abs(Z); +Z=max(Z,[],1); +Z=repmat(Z,[pdims 1]); +Z=reshape(Z,[sZ(dims) sZ(odims) 1]); +Z=ipermute(Z, [dims odims]); + + + +return + +% +% OLDIES +% + + +function [Z]=pseudoT(Y1,Y2) +% default function: max of pseudo-t +% sqrt((std_PA_orig.*std_PA_orig/JA)+(std_PB_orig.*std_PB_orig/JB)); +Z=(mean(Y1)-mean(Y2)) ./ sqrt( std(Y1).*std(Y1)/size(Y1,1) + std(Y2).*std(Y2)/size(Y2,1) ); + +function [Z]=maxabst(Y1,Y2,dims) +[Z]=pseudoT(Y1,Y2); +% remove the 1st (subjects) dimension: +Z=shiftdim(Z,1); +sZ=size(Z); +odims=setdiff(1:length(sZ),dims); +pdims=prod(sZ(dims)); +Z=permute(Z, [dims odims]); +Z=reshape(Z,[pdims, sZ(odims) 1]); +Z=abs(Z); +Z=max(Z,[],1); +Z=repmat(Z,[pdims 1]); +Z=reshape(Z,[sZ(dims) sZ(odims) 1]); +Z=ipermute(Z, [dims odims]); + + +return + + +% EX: +% X=rand(3,5)+cumsum(ones(3,5)) +% [th,o,ss]=mypermtest(X,1,2,[.05],500,'myanova', {1}) + +if nargin<7 + fparams={}; +end +if nargin<8 + ftest='maxabst'; + % Matlab's inline calls are slow ! + % f=inline('mean(x)/std(x)','x'); + + % Maximum of T values will be search along all measurement dimensions + fparams={1:(ndims(X)-1)}; +end + + +if singleinput + Y=reshape(Y,[N(1)+N(2) nsX]); + Y=ipermute(Y, [dim setdiff(1:nX,[dim])]); + Z=feval(ftest,Y,fparams{:}); +else + Y1=reshape(Y(1:N(1),:), [N(1), nsX]); + Y2=reshape(Y(N(1)+(1:N(2)),:), [N(2), nsX]); + Z=feval(ftest,Y1,Y2,fparams{:}); +end + diff --git a/stat/myplannedcomp.m b/stat/myplannedcomp.m new file mode 100644 index 0000000..1a4aff1 --- /dev/null +++ b/stat/myplannedcomp.m @@ -0,0 +1,8 @@ +function [p,fx,t]=pc(X,nf) + +[p,F,fx,epsilon,df,dfe,SS,SSe,SSt]=myanova(X,nf); +% nr*png/prod(ng(j))*sum((reshape(mXb, prod(ng(j)), []) + +Y=cat(j, subarray(X,i,j), mean(subarray(X,i,-j),j)) + +p \ No newline at end of file diff --git a/stat/myposthoc.m b/stat/myposthoc.m new file mode 100644 index 0000000..4fc6c42 --- /dev/null +++ b/stat/myposthoc.m @@ -0,0 +1,8 @@ +function [p,fx,t]=ph(X,nf) + +[p,F,fx,epsilon,df,dfe,SS,SSe,SSt]=myanova(X,nf); +% nr*png/prod(ng(j))*sum((reshape(mXb, prod(ng(j)), []) + +Y=cat(j, subarray(X,i,j), mean(subarray(X,i,-j),j)) + +p \ No newline at end of file diff --git a/stat/myrmanova.m.bad b/stat/myrmanova.m.bad new file mode 100644 index 0000000000000000000000000000000000000000..a0a608f6afc1b2961d8e3b83a995b8aead3cba11 GIT binary patch literal 19990 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 X2 OR X1 < X2 (i.e. it is a two-tailed test). +% Observations (ie. subjects) are supposed to be along the given +% dimension. If not given, perform the test along the first non +% singleton dimension. +% +% MYTTEST(X, r,dim) with r single number specifying the dimension +% from which the two groups are formed, therefore, +% size(X,r) must be equal to 2 +% (and r must be different from dim) +% +% MYTTEST(X1, X2, dim, wtest) : wtest specify the test to be used: +% 'ttest' : t-test for equal variances [default] +% 'uttest' : t-test for unequal variances +% 'pttest' : t-test for paired samples +% +% MYTTEST(X1, X2, dim, wtest, varcorr) apply a matrix 'varcorr' on +% the denominator (ie. on empirical variance) before computing the +% T-value. +% +% MYTTEST(X1, [], [dim, ...]) performs a one-sample t-test. +% +% [P, T, CI, d, g, sigma_d] = TTEST(...) gives this probability P and +% the value of Student's t in T. The smaller P is, the more +% significant the difference between the means. +% d is the Cohen's d measure of the effect size. +% sigma_d is such that 1.96*sigma is the 95% Confidence Interval on d +% g is Hedges' measure of effect size (). +% E.g. if P = 0.05 or 0.01, it is very likely that the +% two sets are sampled from distributions with different +% means. An effect size of d=0.2 is considered as small; d=0.5 is +% medium and d>0.8 indicates a big effect. +% +% Example: X1 and X2 are [ N x [voxelsX x voxelsY x voxelsZ ] fMRI-volume ] +% corresponding to two conditions run with the same pool +% of (N) subjects +% To test whether some voxel value differ significantly +% between conditions: myttest(X1,X2,1,'pttest') +% + +% Adapted from C. Goutte's functions by K. N'Diaye, 2005/04/20 +if nargin<4 + wtest='ttest'; +end +if nargin==1 + X2=[]; +end +if isequal(X2,0) || isempty(X2) + X2=0.*X1; + wtest='onesample'; +elseif numel(X2)==1; + if ~isequal(size(X1,X2),2) + error('MyTTest:WrongDimension','Dimension along which the comparison is made must have only 2 levels') + end + r=X2; + X2=subarray(X1,2,r); + X1=subarray(X1,1,r); +end +if nargin<3, + dim=[]; +end +UseVarCorr=0; +if nargin>4 + UseVarCorr=1; +end +if not(isnumeric(dim)) & ischar(dim) + wtest=dim; + dim=[]; +end +if isempty(dim) + % Determine which dimension to use + dim = min(find(size(X1)~=1)); + if isempty(dim), dim = 1; end +end +if dim<1 + error('myttest: wrong dimension argument') +end +if dim>1 + p=[ dim, 1:dim-1 dim+1:ndims(X1) ]; + X1=permute(X1, p); + X2=permute(X2, p); +end + +s1=size(X1); +s2=size(X2); +if not(isequal(s1(2:end), s2(2:end))) + error('myttest: X1 and X2 must be the same size along the variable''s dimension(s)') +end +n1=size(X1,1); +n2=size(X2,1); +X1=X1(:,:);% reshape(X1,[n1 prod(s1)/n1 ]); +X2=X2(:,:);% reshape(X2,[n2 prod(s2)/n2 ]); + +tmap=repmat(NaN,1,prod(s1(2:end))); +pmap=tmap; +d=pmap; + +if isequal(s1, s2) + k=find(all(X1 == X2)); + pmap(k)=1; + tmap(k)=0; +else + k=[]; +end + +I=find(all(isfinite(X1))&all(isfinite(X2))); % make a test only on 'finite' numbers +I=setdiff(I,k); % remove columns which are identical in X1 and X2 +nI=length(I); +if nI2 + d(I)=(a1)./sqrt((v1)./2); + end + + case 'ttest' + df = n1 + n2 - 2 ; + n = 1./(1./n1 + 1./n2); + % Pooled variance + pvar = ((n1 - 1) * v1 + (n2 - 1) * v2) ./ df ; + + case 'uttest' + n = 1./(1./n1 + 1./n2) ; + pvar = (v1 ./ n1 + v2 ./ n2) ; + % Welch-Satterthwaite equation to approximate pooled d.f.: + df = pvar .* pvar ./ ( ... + (v1 ./ n1) .* (v1 ./ n1) ./ (n1 - 1) + ... + (v2 ./ n2) .* (v2 ./ n2) ./ (n2 - 1) ); + pvar = n.* pvar ; + + case {'paired' 'pttest'} + wtest='paired'; + if ~isequal(s1,s2) + error('Sizes of X1 and X2 should be equal for paired test.') + end + if any(a1(:) ~= a2(:)) + % use abs to avoid numerical errors for very similar data + % for which v1+v2-2cab may be close to 0. + df = n1 - 1 ; + cab = sum((X1(:,I) - repmat(a1,[n1 1])) .* (X2(:,I) - repmat(a2,[n1 1]))) / (n1 - 1) ; + n = n1; + pvar = abs(v1 + v2 - 2 .* cab) ; + + if nargout>2 + d(I) = (a1-a2)./sqrt(pvar./2); + warning('Paired variance used for computing d'); + end + else + warning('myttest:IdenticalSamples',['myttest: data are too similar to be compared, try using' ... + ' ''abs'' on your data']); + t = zeros(1,nI); + p = ones(1,nI); + end + + otherwise + error('Unknown test') +end +if UseVarCorr + pvar = varcorr*pvar; +end + +t = (a1 - a2) ./ sqrt( pvar ./ n ) ; +p = betainc( df ./ (df + t.*t), df/2, 0.5) ; +pmap(I)=p; +tmap(I)=t; + +if nargout>3 + d = pmap.*NaN; + d(I) = (a1-a2)./sqrt(pvar); + sigma_d = sqrt(n/n1/n2 + d.^2/2/n); + if isequal(wtest,'paired') + warning('Paired variance used for computing Cohen''s d'); + end +end +if nargout>4 + g = d.*(1-3./(4*(n1+n2)-9)); +end +% reshape the output so that it matches the input dimensions +if length(s1)>2 + rsize = @(x)reshape(x,s1(2:end)); + pmap=rsize(pmap); + tmap=reshape(tmap,s1(2:end)); + if nargout>3 + d=reshape(d,s1(2:end)); + sigma_d = rsize(sigma_d); + end + if nargout>5 + g=reshape(g,s1(2:end)); + end +end +if nargout == 0 + figure + if numel(2)==1 + a2=a1.*0+a2; + end + if numel(a1)>1 + x=1:numel(a1); + else x=1:2 + end + barerrorbar(x, [a1(:) a2(:)]', sqrt([v1(:)/n1 v2(:)/n2])',0) + legend({'Mean' 'S.E.'}) +end diff --git a/stat/octave_anovan.m b/stat/octave_anovan.m new file mode 100644 index 0000000..2541ad3 --- /dev/null +++ b/stat/octave_anovan.m @@ -0,0 +1,366 @@ +% Perform a multi-way analysis of variance (ANOVA). The goal is to test +% whether the population means of data taken from {k} different +% groups are all equal. +% +% Data is a single vector {data} with groups specified by +% a corresponding matrix of group labels {grps}, where {grps} +% has the same number of rows as {data}. For example, if +% {data} = [1.1;1.2]; {grps}= [1,2,1; 1,5,2]; +% then data point 1.1 was measured under conditions 1,2,1 and +% data point 1.2 was measured under conditions 1,5,2. +% Note that groups do not need to be sequentially numbered. +% +% By default, a 'linear' model is used, computing the N main effects +% with no interactions. this may be modified by param 'model' +% +% p= anovan(data,groups, 'model', modeltype) +% - modeltype = 'linear': compute N main effects +% - modeltype = 'interaction': compute N effects and +% N*(N-1) two-factor interactions +% - modeltype = 'full': compute interactions at all levels +% +% Under the null of constant means, the statistic {f} follows an F +% distribution with {df_b} and {df_e} degrees of freedom. +% +% The p-value (1 minus the CDF of this distribution at {f}) is +% returned in {pval}. +% +% If no output argument is given, the standard one-way ANOVA table is +% printed. +% +% BUG: DFE is incorrect for modeltypes != full +% @end deftypefn +% Copyright (C) 2003-2005 Andy Adler +% +% Octave is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by +% the Free Software Foundation; either version 2, or (at your option) +% any later version. +% +% Octave is distributed in the hope that it will be useful, but +% WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +% General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with Octave; see the file COPYING. If not, write to the Free +% Software Foundation, 59 Temple Place - Suite 330, Boston, MA +% 02111-1307, USA. +% Author: Andy Adler +% Based on code by: KH +% $Id: anovan.m,v 1.10 2005/05/30 13:04:41 adb014 Exp $ +% +% TESTING RESULTS: +% 1. ANOVA ACCURACY: www.itl.nist.gov/div898/strd/anova/anova.html +% Passes 'easy' test. Comes close on 'Average'. Fails 'Higher'. +% This could be fixed with higher precision arithmetic +% 2. Matlab anova2 test +% www.mathworks.com/access/helpdesk/help/toolbox/stats/anova2.html +% % From web site: +% popcorn= [ 5.5 4.5 3.5; 5.5 4.5 4.0; 6.0 4.0 3.0; +% 6.5 5.0 4.0; 7.0 5.5 5.0; 7.0 5.0 4.5]; +% % Define groups so reps = 3 +% groups = [ 1 1;1 2;1 3;1 1;1 2;1 3;1 1;1 2;1 3; +% 2 1;2 2;2 3;2 1;2 2;2 3;2 1;2 2;2 3 ]; +% anovan( vec(popcorn'), groups, 'model', 'full') +% % Results same as Matlab output +% 3. Matlab anovan test +% www.mathworks.com/access/helpdesk/help/toolbox/stats/anovan.html +% % From web site +% y = [52.7 57.5 45.9 44.5 53.0 57.0 45.9 44.0]'; +% g1 = [1 2 1 2 1 2 1 2]; +% g2 = {'hi';'hi';'lo';'lo';'hi';'hi';'lo';'lo'}; +% g3 = {'may'; 'may'; 'may'; 'may'; 'june'; 'june'; 'june'; 'june'}; +% anovan( y', [g1',g2',g3']) +% % Fails because we always do interactions + + +function [PVAL, FSTAT, DF_B, DFE] = anovan (data, grps, varargin) + +% if nargin <= 1 +% usage ('anovan (data, grps)'); +% end + +% test supplied parameters +modeltype= 'linear'; +for idx= 3:2:nargin + param= varargin{idx-2}; + value= varargin{idx-1}; + if strcmp(param, 'model') + modeltype= value; + % elseif strcmp(param % add other parameters here + else + error(sprintf('parameter %s is not supported', param)); + end +end + +if ~isvector (data) + error ('anova: for `anova (data, grps) data must be a vector'); +end + +nd = size (grps,1); % number of data points +nw = size (grps,2); % number of anova 'ways' +if (~ isvector (data) || (length(data) ~= nd)) + % error ('anova: grps must be a matrix of the same number of rows as data'); +end + +[g,grp_map] = relabel_groups (grps); +if strcmp(modeltype, 'linear') + max_interact = 1; +elseif strcmp(modeltype,'interaction') + max_interact = 2; +elseif strcmp(modeltype,'full') + max_interact = size(grps,1); +else + error(sprintf('modeltype %s is not supported', modeltype)); +end +ng = length(grp_map); +int_tbl = interact_tbl (nw, ng, max_interact ); +[gn, gs, gss] = raw_sums(data, g, ng, int_tbl); + +stats_tbl = int_tbl(2:size(int_tbl,1),:)>0; +nstats= size(stats_tbl,1); +stats= zeros( nstats+1, 5); % SS, DF, MS, F, p +for i= 1:nstats + [SS, DF, MS]= factor_sums( gn, gs, gss, stats_tbl(i,:), ng, nw); + stats(i,1:3)= [SS, DF, MS]; +end + +% The Mean squared error is the data - avg for each possible measurement +% This calculation doesn't work unless there is replication for all grps +% SSE= sum( gss(sel) ) - sum( gs(sel).^2 ./ gn(sel) ); +SST= gss(1) - gs(1)^2/gn(1); +SSE= SST - sum(stats(:,1)); + +% KND: Error with the following! +sel = select_pat( ones(1,nw), ng, nw); %incorrect for modeltypes != full +DFE= sum( (gn(sel)-1).*(gn(sel)>0) ); +MSE= SSE/DFE; +stats(nstats+1,1:3)= [SSE, DFE, MSE]; + +for i= 1:nstats + MS= stats(i,3); + DF= stats(i,2); + F= MS/MSE; + pval = 1 - f_cdf (F, DF, DFE); + stats(i,4:5)= [F, pval]; +end + +if nargout==0; + printout( stats, stats_tbl ); +else + PVAL= stats(1:nstats,5); + FSTAT=stats(1:nstats,4); + DF_B= stats(1:nstats,2); + DF_E= DFE; +end +return + + +% relabel groups to a mapping from 1 to ng +% Input +% grps input grouping +% Output +% g relabelled grouping +% grp_map map from output to input grouping +function [g,grp_map] = relabel_groups(grps) +grp_vec= vec(grps); +s= sort (grp_vec); +uniq = 1+[0;find(diff(s))]; +% mapping from new grps to old groups +grp_map = s(uniq); +% create new group g +ngroups= length(uniq); +g= zeros(size(grp_vec)); +for i = 1:ngroups + g( find( grp_vec== grp_map(i) ) ) = i; +end +g= reshape(g, size(grps)); +return + +% Create interaction table +% +% Input: +% nw number of 'ways' +% ng number of ANOVA groups +% max_interact maximum number of interactions to consider +% default is nw +function int_tbl =interact_tbl(nw, ng, max_interact) +combin= 2^nw; +inter_tbl= zeros( combin, nw); +idx= (0:combin-1)'; +for i=1:nw; + inter_tbl(:,i) = ( rem(idx,2^i) >= 2^(i-1) ); +end + +% find elements with more than max_interact 1's +idx = ( sum(inter_tbl',1) > max_interact ); +inter_tbl(idx,:) =[]; +combin= size(inter_tbl,1); % update value + +%scale inter_tbl +% use ng+1 to map combinations of groups to integers +% this would be lots easier with a hash data structure +int_tbl = inter_tbl .* (ones(combin,1) * (ng+1).^(0:nw-1) ); +return + +% Calculate sums for each combination +% +% Input: +% g relabelled grouping matrix +% ng number of ANOVA groups +% max_interact +% +% Output (virtual (ng+1)x(nw) matrices): +% gn number of data sums in each group +% gs sum of data in each group +% gss sumsqr of data in each group +function [gn, gs, gss] = raw_sums(data, g, ng, int_tbl); +nw= size(g,2); +ndata= size(g,1); +gn=zeros((ng+1)^nw, 1); +gs=zeros((ng+1)^nw, 1); +gss=zeros((ng+1)^nw, 1); +for i=1:ndata + % need offset by one for indexing + datapt= data(i); + idx = 1+ int_tbl*g(i,:)'; + gn(idx)=gn(idx)+1; + gs(idx)=gs(idx)+datapt; + gss(idx)=gss(idx)+datapt^2; +end +return + +% Calcualte the various factor sums +% Input: +% gn number of data sums in each group +% gs sum of data in each group +% gss sumsqr of data in each group +% select binary vector of factor for this 'way'? +% ng number of ANOVA groups +% nw number of ways + +function [SS,DF]= raw_factor_sums( gn, gs, gss, select, ng, nw); +sel= select_pat( select, ng, nw); +ss_raw= gs(sel).^2 ./ gn(sel); +SS= sum( ss_raw( ~isnan(ss_raw) )); +if length(find(select>0))==1 + DF= sum(gn(sel)>0)-1; +else + DF= 1; %this isn't the real DF, but needed to multiply +end +return + +function [SS, DF, MS]= factor_sums( gn, gs, gss, select, ng, nw); +SS=0; +DF=1; + +ff = find(select); +lff= length(ff); +% zero terms added, one term subtracted, two added, etc +for i= 0:2^lff-1 + remove= find( rem( floor( i * 2.^(-lff+1:0) ), 2) ); + sel1= select; + if ~isempty(remove) + sel1( ff( remove ) )=0; + end + [raw_sum,raw_df]= raw_factor_sums(gn,gs,gss,sel1,ng,nw); + + add_sub= (-1)^length(remove); + SS = SS + add_sub*raw_sum; + DF = DF*raw_df; +end + +MS= SS/DF; +return + +% Calcualte the various factor sums +% Input: +% select binary vector of factor for this 'way'? +% ng number of ANOVA groups +% nw number of ways +function sel= select_pat( select, ng, nw); +% if select(i) is zero, remove nonzeros +% if select(i) is zero, remove zero terms for i + +persistent field +%field=[]; + +if length(select) ~= nw; + error('length of select must be = nw'); +end +ng1= ng+1; + +if isempty(field) + % expand 0:(ng+1)^nw in base ng+1 + field= (0:(ng1)^nw-1)'* ng1.^(-nw+1:0); + field= rem( floor( field), ng1); + % select zero or non-zero elements + + %knd + field= (field>0); +end +%disp('selection pattern') %debug +sel= find( all( field == ones(ng1^nw,1)*select(:)', 2) ); + +return + + +function printout( stats, stats_tbl ); +nw= size( stats_tbl,2); +[jnk,order]= sort( sum(stats_tbl,2) ); + +fprintf('\n%d-way ANOVA Table (Factors A%s):\n\n', nw, ... + sprintf(',%c',('A')+(1:nw-1)) ); +fprintf('Source of Variation Sum Sqr df MeanSS Fval p-value\n'); +fprintf('*********************************************************************\n'); +fprintf('Error %10.2f %4d %10.2f\n', stats( size(stats,1),1:3)); + +for i= order(:)' + str= sprintf(' %c x',('A')+find(stats_tbl(i,:)>0)-1 ); + str= str(1:length(str)-2); % remove x + fprintf('Factor %15s %10.2f %4d %10.2f %7.3f %7.6f\n', ... + str, stats(i,:) ); +end +fprintf('\n'); +return + +% Test Data from http://maths.sci.shu.ac.uk/distance/stats/14.shtml +data=[7 9 9 8 12 10 ... + 9 8 10 11 13 13 ... + 9 10 10 12 10 12]'; +grp = [1,1; 1,1; 1,2; 1,2; 1,3; 1,3; + 2,1; 2,1; 2,2; 2,2; 2,3; 2,3; + 3,1; 3,1; 3,2; 3,2; 3,3; 3,3]; +data=[7 9 9 8 12 10 9 8 ... + 9 8 10 11 13 13 10 11 ... + 9 10 10 12 10 12 10 12]'; +grp = [1,4; 1,4; 1,5; 1,5; 1,6; 1,6; 1,7; 1,7; + 2,4; 2,4; 2,5; 2,5; 2,6; 2,6; 2,7; 2,7; + 3,4; 3,4; 3,5; 3,5; 3,6; 3,6; 3,7; 3,7]; +% Test Data from http://maths.sci.shu.ac.uk/distance/stats/9.shtml +data=[9.5 11.1 11.9 12.8 ... + 10.9 10.0 11.0 11.9 ... + 11.2 10.4 10.8 13.4]'; +grp= [1:4,1:4,1:4]'; +% Test Data from http://maths.sci.shu.ac.uk/distance/stats/13.shtml +data=[7.56 9.68 11.65 ... + 9.98 9.69 10.69 ... + 7.23 10.49 11.77 ... + 8.22 8.55 10.72 ... + 7.59 8.30 12.36]'; +grp = [1,1;1,2;1,3; + 2,1;2,2;2,3; + 3,1;3,2;3,3; + 4,1;4,2;4,3; + 5,1;5,2;5,3]; +% Test Data from www.mathworks.com/ +% access/helpdesk/help/toolbox/stats/linear10.shtml +data=[23 27 43 41 15 17 3 9 20 63 55 90]; +grp= [ 1 1 1 1 2 2 2 2 3 3 3 3; + 1 1 2 2 1 1 2 2 1 1 2 2]'; + + + +function [y]=vec(x) +y=x(:); diff --git a/stat/p_rep.m b/stat/p_rep.m new file mode 100644 index 0000000..dc11e97 --- /dev/null +++ b/stat/p_rep.m @@ -0,0 +1,54 @@ +function [prep prep2]= p_rep(p,tails) +%P_REP - Probability of re +% [prep prep2] = p_rep(p,tails) +% Computes prep, the probabilityy of replication according to Killeen +% prep2 is the approximation +% Example +% >> p_rep(ttest( +% +% References: Peter Killeen (2005) An alternative to null-hypothesis +% significance tests. Psychological Science, 16, 345-353. + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2007 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2007-10-28 Creation +% +% ----------------------------- Script History --------------------------------- +if nargin==0 + p = (-4.5:.05:-.5); + % p = 10.^(p-.5).*(mod(2*p,2))+10.^(p+log10(.5)).*(1-mod(2*p,2)); + p = 10.^(p-.5); + plot(p,p_rep(p), 'LineWidth', 2, 'marker', 'none') + set(gca, 'XScale', 'log') + set(gca, 'Ygrid', 'on') + set(gca, 'XTick', [.0001 .001 .01 .1]) + set(gca, 'XTickLabel', get(gca, 'XTick')) + set(gca, 'Xgrid', 'on') + set(gca, 'XMinorGrid', 'off') + xlabel('p value') + ylabel('p_{rep}') + title('$\displaystyle p_{rep} = \left(1+\left(\frac{p}{1-p}\right)^{\frac{2}{3}}\right)^{-1}$','interpreter','latex') + axis square + set([gca get(gca, 'Title') get(gca, 'Xlabel') get(gca, 'Ylabel')], 'FontSize', 16) +end + +if nargin<2 + tails=1; +end +if not(isequal(tails, 1) || isequal(tails, 2)) + error('Tails should be 1 or 2') +end +p=p./tails; +prep=normcdf(norminv(1-(p))./2.^.5); +prep2=1./(1+(p./(1-p)).^(2/3)); +return + + +function p = normcdf(z) +p = 0.5 * erfc(-z ./ sqrt(2)); diff --git a/stat/permftest.m b/stat/permftest.m new file mode 100644 index 0000000..b09ca8b --- /dev/null +++ b/stat/permftest.m @@ -0,0 +1,404 @@ +function [pv,fx,F0,P,PS]=permftest(X,dimF,varargin) +% permftest - N-way permutation univariate test (within design only) +% [pv,F,fx,P,PS] = permktest(X,dimF,dimP,dimM,NP,TimeBar) +% +% Performs a permutation test with the data analoguous to +% a one-way ANOVA testing the hypothesis H0: +% All groups X1, X2, ... have the same mean +% +%MANDATORY INPUTS: +% X is [ SUBJECT x FACTOR1 x FACTOR2 [...] x MEASURE1 x [...] ] +% dimF: Dimensions of the factors (in the previous example, with 2 factors: [2 3] +%OPTIONAL INPUTS (use [] for default values): +% dimP: Permuted dimensions (ie. subjects). Default: first non singleton +% dimM: Dimensions of data across which the maximum F-value is computed. +% E.g.Default is: all dimensions besides the "permuted" dimensions +% NP: Number of random permutations. (Default: 10000) +% TimeBar: if not 0, a progression time bar is displayed (default: 1) +%OUTPUTS: +% pv: (Approximate) p-values of the observed data, computed from the permutations +% fx: List of effects tested +% F: [ length(fx) x Ma x Mb...] Observed Fisher's discriminant values +% P: [NP x (N1+N2+...)] List of permutations used +% PS: [ P x Ma x Mb... ] Matrix of permutation statistics (may be quite big!) +% +% See also: permttest, permktest +% Author: Karim N'Diaye, on advice from Jacques Martinerie, CNRS UPR640 +% Created: Sep 2005 + + +% We have something like: +% X= [ X(1)11 X(2 + + +% X={X1 , ... Xi, ... Xn}: a cell array of [Ni x Ma x Mb...] data matrices. +% Alternatively: +% X can be a single data matrix. But the number of samples (ie subjects) +% in each condition should be set using 'grp' +% I.e. X = [ X1 ; X2 ; X3] +% grp = [ size(X1,1) size(X2,1) size(X3,1) ]; +% +%OPTIONAL INPUTS (use [] for default values): +% dimP: Permuted dimensions (ie. subjects). Default: first non singleton +% If dim_p > 0 : within-subject permutations (used as default) +% Use -dim_p for unpaired samples/subjects +% dimM: Dimensions of data across which the maximum T-value is computed. +% Default is: all dimensions besides the "permuted" dimensions +% (typically: [2 3 ... ndims(X)]) +% NP: Number of random permutations. (Default: 10000) +% TimeBar: if not 0, a progression time bar is displayed (default: 1) +% + + +% [pv ... ] = permktest(X,grp,dimP,dimM,NP,TimeBar) + +NPERMS=9999; % Max. number of permutations (instead of 10000 to have nice p values after division by (NP+1)) +NMAX_GROUPS=36; % Max. number of groups : cf 'symbols' in dec2base +NMAX_SUBJECTS=1000; % Max number of subjects + +% Metrics to be used +% metrics='fisher discriminant'; +metrics='euclidian distance'; + +% Variables used in this script: +% dim_p : subject (ie. samples, observation) diemsion in the data X +% dim = abs(dim_p) +% dimF = factor dimensions in X (not their size) +% NL : number of levels for each factor, NL=size(X,dimF) +% N : number of obs in each cell (ie group) + + +%initialize parameters +% if iscell(X) +% nparams=nargin-1; +% params=varargin(1:nparams); +% else +% N=varargin{1}; +% end +nparams=nargin-2; +params=varargin(1:nparams); +if nparams<1 + dim_p=[]; +else + dim_p=params{1}; +end +if nparams<2, + dim_m=[]; +else, + dim_m=params{2}; +end +if nparams<3 + NP=[]; +else + NP=params{3}; +end +if nparams<4 + TimeBar=1; +else + TimeBar=params{4}; +end +if TimeBar %& 0 + try + htimer = timebar('Permutation Statistics','Progress...'); + catch + warning('Function timebar.m missing. Use waitbar instead') + htimer = waitbar(0,'Permutation Statistics'); + end +else + htimer = NaN; +end + +if isempty(dim_p) + if iscell(X) + dim=find(size(X{1})>1); + else + dim=find(size(X)>1); + end + if isempty(dim) + error('Input X has only singleton or null dimensions!') + end + dim=dim(1); + dim_p=dim; +end +dim=abs(dim_p); + +% We will reshape the data X so that groups and subjects make the first +% dimension of X. +% That is for a LETTER x NUMBER : +% X = [ A1 ; B1 ; ... ; A2 ; B2 ... ] +% N(i,j,k...) will keep track of the number of samples (ie. subjects) in each +% group: N(1,1)=size(A1) ; N(2,1)=size(B2) etc. + +% if iscell(X) +% Y=X;X=[]; +% ndX=ndims(Y{1}); +% dX1=setdiff(1:ndX,dim); % non permuted dims +% for i=1:length(Y) +% X=cat(1, X, permute(Y{i}, [dim dX1])); +% N(i)=size(Y{i},dim); +% Y{i}=[]; +% end +% else +ndX=ndims(X); +dX1=setdiff(1:ndX,[dim dimF]); +% Put subjects in the first dimension and factors in the following ones +X=permute(X, [dim dimF dX1]); +sX=size(X); +% Number of factors: +nf=length(dimF); +% Population in each cell: +N=sX(1)*ones(sX(dimF)); +NL=sX(dimF); % Number of level in each factor +NG=prod(NL); % Number of cells +NS=sum(N(:)); % Total Number of samples +if NS>NMAX_SUBJECTS + error('Too many subjects!') +end +if dim_p>0 & length(unique(N))>1 + error('Repeated measure design must have the same number of samples in each group') +end +if NG>NMAX_GROUPS + error('Too many groups!') +end + +% Default is to compute the maximum T value across all dimensions of the +% measurement data +if isempty(dim_m) + dim_m=setdiff(1:ndX,[dim dimF]); +end +dim_m=unique(dim_m(:))'; +if max(dim_m)>ndX + error('Wrong dimM dimensions!') +end +if ismember(dim_p,dim_m) + error(sprintf('Wrong dimM dimensions: [%d] is already set as the permuted dimension!\n' , dim)) +end +% Reindex dimensions in the new X configuration: +if ~isempty(dim_m) + dim_m=dim_m+(dim_m0 + % Paired case: + Nexh=factorial(NG)^(N(1))-1; +else + % Unpaired case: + n=cumprod(1:max(N(:))); % hand made factorial + Nexh=factorial(NS)/prod(n(N(:)))-1; +end + +% Set the number of perms to be computed +exhaustive=0; +if isempty(NP) + if Nexh <= NPERMS + NP=Nexh; + exhaustive=1; + else + NP=NPERMS; + exhaustive=0; + end +end + +if NP>Nexh + warning(sprintf('Cannot do %d permutations. For this dataset, the maximum is: %d.',NP, Nexh)); +elseif NP==Nexh; + exhaustive=1; +end + +% if NP>NPERMS +% warning(sprintf('Too many permutations! Only %d will be performed.',NPERMS)); +% NP = NPERMS; +% exhaustive=0; +% end + +% WARNING: +% Creating the exhaustive list of permutations is rather painful and +% quite uninteresteing as we may pretty often be well above 10'000 perms +% (e.g. 3 groups of 6 subjects lead to 46656 perms in a within design!) +% Therefore: +exhaustive=0; + +% List of permutations/randomizations +if dim_p>0 + % paired permutations + if exhaustive + % compute exhaustive list of perms + % Within subject perms: + % Would be: P1=flipud(fliplr(perms(1:NG))); + % Between subject perms: + % Something like: P=dec2basen(1:NP,NG,N(1))+1; + error('no exhaustive search available!') + else + % within-subject & between-groups random permutations: + [ignore,P]=sort(rand(NP,N(1),NG),3); + % so that, with: group=a, b,...n & subject=1,2,...N + % P(i,:)= [ a1,a2,...,aN b1,b2, ... nN ] + P=(P-1)*N(1)+repmat(reshape(repmat([1:N(1)]',[1,NG]),[1, N(1), NG]), [NP,1]); + end +else + % non-paired perms + if exhaustive % do all possible perms + error('no exhaustive search available!') + else % non exhaustive + [ignore,P]=sort(rand(NP,NS),2); + end +end + +% List of effects and interactions between factors +fx={}; +for i=1:nf + fx=[fx ; num2cell(nchoosek(1:nf,i),2)]; +end +nfx=size(fx,1); + +% This works only for within design! +idx=reshape(1:NS,[N(1) NL]); + +% Now we make up the indices of each group in the first column of X +for i=1:nfx + % For each effect, we will test, we list all possible pairs of groups + % Examples: + % one factor with 3 levels(1,2,3): 1 vs 2, 1 vs 3, 2 vs 3 + % 2 factors, 3 by 2 interaction: A1/A2 A1/A3 A1/B1 A1/B2 A2/A3 ... B1/B2 + % + ngrps{i}=prod(NL(fx{i})); + %First, all pairs in a given effect (fx{i}) + pairs{i}=nchoosek(1:ngrps{i},2); + npairs=size(pairs{i},1); + for j=1:npairs + % In each pair, get the indices of the first group then those of + % the second + for k=1:2 + idx_pairs{i}{j,k}=subarray(idx, [{[] ; -1} [ num2cell(ind2sub2(NL(fx{i}),pairs{i}(j,k))); num2cell(fx{i}+1)]]); + end + end + for j=1:ngrps{i} + % Index of elements in each group + idx_grp{i}{j}=subarray(idx, [{[] ; -1} [ num2cell(ind2sub2(NL(fx{i}),j)); num2cell(fx{i}+1)]]); + end + +end +% +% for j=fx{i} % factor +% for k=1:NL(j) % level +% idx_group{i}{j,k}=[1:N(fx{i}(k))] +% end +% idx_pairs{j,k}=[1:N(pairs(j,k))]+sum([0 N([1:pairs(j,k)-1])]); +% end +% for j=1:NG +% % Index of elements in each group +% idx_grp{j}=[1:N(j)]+sum([0 N([1:j-1])]); % index of the group elements +% end + + +% +% PERMUTATION LOOP +% +for i=0:NP + + if i==0 + % At the 0-th permutation, evaluate original data + Y=X; + else + Y=X(P(i,:),:); + end + + % Generalized K-class Fisher's linear discriminant criterion : + % R. A. Fisher. The statistical utilization of multiple measurements. Ann. Eugen., 8:376-386, 1938. + % C. R. Rao. The utilization of multiple measurements in problems of biological classification. J. Roy. Stat. Soc. ser. B, 10:159-203, 1948. + + % Between group disparity + Z=zeros([nfx nsX]); + % Pooled variance: + K=zeros([nfx nsX]); + switch lower(metrics) + case 'fisher discriminant' + for ifx=1:nfx + npairs=size(pairs{ifx},1); + for j=1:npairs + Z(ifx,:)=Z(ifx,:)+abs(mean(Y(idx_pairs{ifx}{j,1},:)) - mean(Y(idx_pairs{ifx}{j,2},:))).^2; + end + Z(ifx,:)=Z(ifx,:)./npairs; + for j=1:ngrps{ifx} + % within-group variance (weighted with group size) + K(ifx,:)=K(ifx,:)+var(Y(idx_grp{ifx}{j},:),1)*N(j); + end + % Generalized Fisher's discriminant: + K(ifx,:)=Z(ifx,:)./K(ifx,:); + end + case 'euclidian distance' + [K,K]=myanova(permute(reshape(Y,sX),[dimF,1,(nf+2):ndX]),nf,1:nf); + end + + clear Z; % save some memory space + K=reshape(K,[nfx nsX]); + + if i==0 + F0=K; + sz=size(F0); + S=zeros(sz); + if nargout>3 + PS=zeros([NP sz(1) sz(3:end) 1]); + end + else + K=max(K,[],2); + if nargout>3 + PS(ifx, i,:)=K(:,:)'; + end + S=S+(repmat(K,[1 nsX(1)])>=abs(F0)); + if ishandle(htimer),try,if isequal(get(htimer, 'tag'), 'timebar'), timebar(htimer,i/NP),else,waitbar(i/NP,htimer);end;catch,end,end + end + clear K; +end +% Compute p-values +pv=(S+1)/(NP+1); +pv=reshape(pv, [nfx sX([dim_m setdiff(2:ndX,[dimF dim_m])]) 1]); +F0=reshape(F0, [nfx sX([dim_m setdiff(2:ndX,[dimF dim_m])]) 1]); +if ~isequal(dim_m, 0) + % permute back to put dimension across which the statistics maximum is + % computed at their original places + pv=ipermute(pv, [1 dim_m-nf setdiff(nf:ndX-nf, dim_m-nf)]); + F0=ipermute(F0, [1 dim_m-nf setdiff(nf:ndX-nf, dim_m-nf)]); +end +if nargout>3 + PS=reshape(PS, [NP nsX(2:end)]); +end +if ishandle(htimer),try,close(htimer),end,end +return + + +%% Validation using normally distributed random values: +% +% Simulate a design with 2 additive factor +% x=randn(100,4,2);x=x+cumsum(cumsum(ones(size(x)), 3),2)/10; +% +% Parametric 2 way ANOVA (indepentent samples) +% myanova(permute(x, [2 3 1 4 5]),2) +% +% [1]: 0.0013 +% [2]: 0.0009 +% [1x2]: 0.2255 +% +% anova2(permute(x, [2 3 1]), 2) give the same +% +% Repeated measures: +% myanova(permute(x, [2 3 1 4 5]),2,1:2) +% 0.0017 , 0.0017 , 0.2212 +% +% Permutation test yields: +% permftest(x, [2 3], -1, [],999) +% 0.0040 0.0020 0.0010 + + diff --git a/stat/permktest.m b/stat/permktest.m new file mode 100644 index 0000000..9b51a53 --- /dev/null +++ b/stat/permktest.m @@ -0,0 +1,308 @@ +function [pv,K0,P,PS]=permktest(X,varargin) +% permktest - One-way permutation univariate test +% [pv,K,P,PS] = permktest(X,dimP,dimM,NP,TimeBar) +% [pv ... ] = permktest(X,grp,dimP,dimM,NP,TimeBar) +% +% Performs a permutation test with the data analoguous to +% a one-way ANOVA testing the hypothesis H0: +% All groups X1, X2, ... have the same mean +% +%MANDATORY INPUTS: +% X={X1 , ... Xi, ... Xn}: a cell array of [Ni x Ma x Mb...] data matrices. +% Alternatively: +% X can be a single data matrix. But the number of samples (ie subjects) +% in each condition should be set using 'grp' +% I.e. X = [ X1 ; X2 ; X3] +% grp = [ size(X1,1) size(X2,1) size(X3,1) ]; +% +%OPTIONAL INPUTS (use [] for default values): +% dimP: Permuted dimensions (ie. subjects). Default: first non singleton +% If dim_p > 0 : within-subject permutations (used as default) +% Use -dim_p for unpaired samples/subjects +% dimM: Dimensions of data across which the maximum T-value is computed. +% Default is: all dimensions besides the "permuted" dimensions +% (typically: [2 3 ... ndims(X)]) +% NP: Number of random permutations. (Default: 10000) +% TimeBar: if not 0, a progression time bar is displayed (default: 1) +% +%OUTPUTS: +% pv: (Approximate) p-values of the observed data, computed from the permutations +% K: [Ma x Mb...] Observed Fisher's discriminant values +% P: [NP x (N1+N2+...)] List of permutations used +% PS: [ P x Ma x Mb... ] Matrix of permutation statistics (may be quite big!) +% +% See also: permttest +% Author: Karim N'Diaye, on advice from Jacques Martinerie, CNRS UPR640 +% Created: Sep 2005 + +NPERMS=9999; % Max. number of permutations (instead of 10000 to have nice p values after division by (NP+1)) +NMAX_GROUPS=36; % Max. number of groups : cf 'symbols' in dec2base +NMAX_SUBJECTS=1000; % Max number of subjects + +%initialize parameters +if iscell(X) + nparams=nargin-1; + params=varargin(1:nparams); +else + N=varargin{1}; +end +if nparams<1 + dim_p=[]; +else + dim_p=params{1}; +end +if nparams<2, + dim_m=[]; +else, + dim_m=params{2}; +end +if nparams<3 + NP=[]; +else + NP=params{3}; +end +if nparams<4 + TimeBar=1; +else + TimeBar=params{4}; +end +if TimeBar + try + htimer = timebar('Permutation Statistics','Progress...'); + catch + warning('Function timebar.m missing. Use waitbar instead') + htimer = waitbar(0,'Permutation Statistics'); + end +else + htimer = NaN; +end + +if isempty(dim_p) + if iscell(X) + dim=find(size(X{1})>1); + else + dim=find(size(X)>1); + end + if isempty(dim) + error('Input X has only singleton or null dimensions!') + end + dim=dim(1); + dim_p=dim; +end +dim=abs(dim_p); + +% We will reshape the data X so that groups and subjects make the first +% dimension of X. +% That is: +% X = [ Grp1 ; Grp2 ; ... ] +% N will keep track of the number of samples (ie. subjects) in each +% group: N(i)=size(Grp(i),1) etc. +if iscell(X) + Y=X;X=[]; + ndX=ndims(Y{1}); + dX1=setdiff(1:ndX,dim); % non permuted dims + for i=1:length(Y) + X=cat(1, X, permute(Y{i}, [dim dX1])); + N(i)=size(Y{i},dim); + Y{i}=[]; + end +else + ndX=ndims(X); + dX1=setdiff(1:ndX,dim); + % Put subjects in the first dimension + X=permute(X, [dim grp dX1]); + N=size(X,1)*ones(size(X,2)); +end +sX=size(X); +if sum(N)>NMAX_SUBJECTS + error('Too many subjects!') +end +if dim_p>0 & length(unique(N))>1 + error('Repeated measure design must have the same number of samples in each group') +end +NS=sum(N); % Total Number of samples +NG=length(N); % Number of groups +if NG>NMAX_GROUPS + error('Too many groups!') +end + +% Default is to compute the maximum T value across all dimensions of the +% measurement data +if isempty(dim_m) + dim_m=setdiff(1:ndX,dim); +end +dim_m=unique(dim_m(:))'; +if max(dim_m)>ndX + error('Wrong dimM dimensions!') +end +if ismember(dim_p,dim_m) + error(sprintf('Wrong dimM dimensions: [%d] is already set as the permuted dimension!\n' , dim)) +end +dim_m=dim_m+(dim_m0); + +% Define a practical shape for the measurement data +% (so that the maximum across dimension(s) is easy to compute) +if dim_m==0 + nsX=[sX(2:end) 1]; +else + X=permute(X,[1 dim_m setdiff(2:ndX, dim_m)]); + nsX=[prod(sX(dim_m)) sX(setdiff(2:ndX, dim_m)) 1]; +end + +% Now compute the number of exhaustive permutations +if dim_p>0 + % Paired case: + Nexh=factorial(NG)^(N(1))-1; +else + % Unpaired case: + n=cumprod(1:max(N(:))); % hand made factorial + Nexh=factorial(NS)/prod(n(N(:)))-1; +end + +% Set the number of perms to be computed +exhaustive=0; +if isempty(NP) + if Nexh <= NPERMS + NP=Nexh; + exhaustive=1; + else + NP=NPERMS; + exhaustive=0; + end +end + +if NP>Nexh + warning(sprintf('Cannot do %d permutations. For this dataset, the maximum is: %d.',NP, Nexh)); +elseif NP==Nexh; + exhaustive=1; +end + +% if NP>NPERMS +% warning(sprintf('Too many permutations! Only %d will be performed.',NPERMS)); +% NP = NPERMS; +% exhaustive=0; +% end + +% WARNING: +% Creating the exhaustive list of permutations is rather painful and +% quite uninteresteing as we may pretty often be well above 10'000 perms +% (e.g. 3 groups of 6 subjects lead to 46656 perms in a within design!) +% Therefore: +exhaustive=0; + +% List of permutations/randomizations +if dim_p>0 + % paired permutations + if exhaustive + % compute exhaustive list of perms + % Within subject perms: + % Would be: P1=flipud(fliplr(perms(1:NG))); + % Between subject perms: + % Something like: P=dec2basen(1:NP,NG,N(1))+1; + error('no exhaustive search available!') + else + % within-subject & between-groups random permutations: + [ignore,P]=sort(rand(NP,N(1),NG),3); + % so that, with: group=a, b,...n & subject=1,2,...N + % P(i,:)= [ a1,a2,...,aN b1,b2, ... nN ] + P=(P-1)*N(1)+repmat(reshape(repmat([1:N(1)]',[1,NG]),[1, N(1), NG]), [NP,1]); + end +else + % non-paired perms + if exhaustive % do all possible perms + error('no exhaustive search available!') + else % non exhaustive + [ignore,P]=sort(rand(NP,NS),2); + end +end + +% List of pairwise comparisons between groups +pairs=nchoosek(1:NG,2); +npairs=size(pairs,1); +for j=1:npairs + for k=1:2 + idx_pairs{j,k}=[1:N(pairs(j,k))]+sum([0 N([1:pairs(j,k)-1])]); + end +end +for j=1:NG + % Index of elements in each group + idx_grp{j}=[1:N(j)]+sum([0 N([1:j-1])]); % index of the group elements +end +% +% PERMUTATION LOOP +% +for i=0:NP + + if i==0 + % At the 0-th permutation, evaluate original data + Y=X; + else + Y=X(P(i,:),:); + end + + % Generalized K-class Fisher's linear discriminant criterion : + % R. A. Fisher. The statistical utilization of multiple measurements. Ann. Eugen., 8:376-386, 1938. + % C. R. Rao. The utilization of multiple measurements in problems of biological classification. J. Roy. Stat. Soc. ser. B, 10:159-203, 1948. + + % Between group disparity + Z=zeros([1 nsX]); + for j=1:npairs + Z=Z+abs(mean(Y(idx_pairs{j,1},:)) - mean(Y(idx_pairs{j,2},:))).^2; + end + Z=Z./npairs; + % Pooled variance: + K=zeros([1 nsX]); + for j=1:NG + % within-group variance (weighted with group size) + K=K+var(Y(idx_grp{j},:),1)*N(j); + end + % Generalized Fisher's discriminant: + K=Z./K; + clear Z; % save some memory space + K=reshape(K,nsX); + + if i==0 + K0=K; + sz=size(K0); + S=zeros(sz); + if nargout>3 + PS=zeros([NP sz(2:end) 1]); + end + else + K=max(K,[],1); + if nargout>3 + PS(i,:)=K(:)'; + end + S=S+(repmat(K,[nsX(1) 1])>=abs(K0)); + if ishandle(htimer),try,if isequal(get(htimer, 'tag'), 'timebar'), timebar(htimer,i/NP),else,waitbar(i/NP,htimer);end;catch,end,end + end +end +% Compute p-values +pv=(S+1)/(NP+1); +if ~isequal(dim_m, 0) + pv=reshape(pv, [sX([dim_m setdiff(2:ndX,dim_m)]) 1]); + K0=reshape(K0, [sX([dim_m setdiff(2:ndX,dim_m)]) 1]); + pv=ipermute(pv, [dim_m-1 setdiff(1:ndX-1, dim_m-1)]); + K0=ipermute(K0, [dim_m-1 setdiff(1:ndX-1, dim_m-1)]); +else + pv=reshape(pv, [1 sX(2:end) 1]); + K0=reshape(K0, [1 sX(2:end) 1]); +end +if nargout>3 + PS=reshape(PS, [NP nsX(2:end)]); +end +if ishandle(htimer),try,close(htimer),end,end +return + + +% % Validated using normally distributed random values: +% +% X1=randn(50,1); +% [p,K,P,PS]=permftest({X1,X1,X1+.3},-1); +% % Gives (almost) equal p-values as: +% [pa,Fa]=myanova(cat(1,X1',X1',X1'+.3),1) +% +% % Unequal number of samples: +% X3=randn(25,1)+.3; +% [p,K,P,PS]=permftest({X1,X3},-1); +% anova(1,[X1;X3], [ones(50,1);2*ones(25,1);]) \ No newline at end of file diff --git a/stat/permtest/permtest.m b/stat/permtest/permtest.m new file mode 100644 index 0000000..637cc8b --- /dev/null +++ b/stat/permtest/permtest.m @@ -0,0 +1,401 @@ +function [pv,S0,NP,PS,P]=permtest(X,X2,statistic,dim_p,dim_m,NP,TimeBar,tails,varargin) +% permtest - Generic permutation/randomization test +% [pv,S0,NP,PS,P] = permtest(X1,X2,statistic,dim_p,dim_m,NP,TimeBar,tails,[testoptions]) +% [pv ... ] = permtest(X, N1,statistic,...) +% +% Performs a permutation test with the data +% +%MANDATORY INPUTS: +% X1,X2: [N1 x Ma x Mb...] and [ N2 x Ma x Mb x...] Multidimensional matrices +% of Ni samples/subjects for each group, eg. [ Subjects x Channel x Time ] +% Alternatively: +% X can be a single data matrix. But the number of samples (ie subjects) +% in the first group should be set using 'N1' +% I.e. X = [ X1 ; X2 ] & N1=size(X1,1)=nb of samples in group 1 +% +% statistic: Name of the statistic to use: +% 'meandiff' (mean difference) +% 'tvalue' (Student's t-value) +% 'pseudotvalue' (tvalue with smoothed variance) +% 'signtest' +% 'wilcoxon' (aka. signed ranks) +% 'edist'(euclidian distance, on dimension dim_m) +% +%OPTIONAL INPUTS (use [] for default values): +% dimP: Permuted dimensions (ie. subjects). Default: first non singleton +% If dim_p > 0 : paired permutations (default) +% Use negative dim_p for unpaired samples/subjects +% dimM: Dimensions of data across which the maximum T-value is computed. +% For multidimensional data, eg. [ Subject x Channel x Time ], +% control of Family-wise Error Rate requires to compute T-value +% across all dimensions (starting after the permuted one. +% Default is: all dimensions besides the "permuted" dimensions +% (typically: [2 3 ... ndims(X)]) +% NP: Number of permutations (may be reduced if bigger than the number of permutations, +% for example if NP > 2^N-1 using paired data) +% If not specify, permttest uses 10000 (at most). +% Set NP=inf so to enforce exhaustive permutations, ie. all possible permutations +% (this may lead to HUGE numbers of permutations, avoid with unpaired tests) +% TimeBar: 1 (default) or 0. To display a progress bar. +% tails: 1- (one: X1>X2) or 2- (two: X1X2) tailed test (default: 2) +% testoptions: List of parameters required for the chosen test +% 'ttest'|'mann-whitney'|'wilcoxon': testoptions={}; +% 'pseudottest': testoptions{1}=smooting kernel +% 'edist': testoptions={} +% +%OUTPUTS: +% pv: p-values of the observed data, computed from the permutations +% (dimensions subjects and dim_m are squeezed). This is a fairly good +% approximation of the parametric test when data are normally +% distributed. But it is still valid when they are not! +% S0: [Ma x Mb...] Observed values of the statistic +% NP: Number of permutations actually performed +% PS: [P x Ma x Mb... ] Matrix of permutation statistics (may be quite big!) +% P: [NP x (N1+N2)] List of permutations used +% +% References: +% On euclidian distance: Greenblatt, Brain Topography, 2004 +% +% Authors: +% K² Team, aka. K. N'Diaye (kndiaye01yahoo.fr> & K. Jerbi (jerbichups.jussieu.fr) +% CNRS UPR640, Lab. d'imagerie cérébrale et neurosciences cognitives +% Hopital de la Salpetriere, Paris, France +% Copyright (C) 2006 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% K² 2006-02-02 Creation +% KND 2006-02-14 Added euclidian distance test ('edist') +% KND 2010-11-12 May now use function handle +% ----------------------------- Script History --------------------------------- + +NMAX_UNPAIRED=15; % maximum number of observations for exhaustive search for unpaired samples +NMAX_PAIRED=30; % maximum number of observations for exhaustive search in paired samples +NMAX_SUBJECTS=1000; % Max number of subjects +NPERMS=9999; % Default number of permutations +STATISTICS={'tvalue','meandiff','tvaluesmoothed','signtest','wilcoxon','edist'}; +PAIREDSTAT={'wilcoxon', 'mann-whitney', 'signtest', 'plusminus'}; +%Note for authors: +% 'mann-whitney' : check code below before making it available to user +% 'plusminus' : should be ok +% 'plusminus' (sum of X1>X2 along dim_m) + +%initialize parameters +if nargin<3 + error('Test Statistic need to be specified (e.g. ''meandiff'' or ''tvalue'')!') +end +testargs ={}; +if (iscell(statistic)) + testargs = statistic{2:end}; + statistic=statistic{1}; +end +if isa(statistic, 'function_handle') + testfun = statistic; + statistic = 'fun'; +elseif not(ismember(statistic, STATISTICS)) + error('Wrong type of test! Possible test statistics are:\n%s\n', sprintf('%s ', STATISTICS{:})) +end +if nargin<4 + dim_p=[]; +end +if nargin<5 + dim_m=[]; +end +if nargin<6 + NP=[]; +end +if nargin<7 + TimeBar=1; % KJ, default is off now... +end +if nargin<8 + tails=2; +end +if ~isequal(tails,1) && ~isequal(tails,2) && ~isequal(tails,-1) + error('Tails can be 1 or 2') +end + +%% Checking dimensions +% +if isempty(dim_p) + dim=find(size(X)>1,1); + if isempty(dim) + error('Input X has only singleton or null dimensions!') + end + %dim=dim(1); + if ndims(X2)>=dim + if size(X,dim) ~= size(X2,dim) + dim=-dim; + end + end + dim_p=dim; +end +dim=abs(dim_p); +options=varargin; +if dim_p<0 && ~isempty(strmatch(statistic, PAIREDSTAT)) + error('Unpaired data not supported for %s statistics, are you still a Student? LOL',statistic) +end + + +%% RESHAPING ORIGINAL DATA +% +% Data in X (and possibly in X2) will be concatenated (if needed) and +% reshaped so that following computations are made easier. +% Resulting size of X will be: [ (N1+N2) "dim_m"(1) "dim_m"(2) (...) ] +% i.e - subjects are put in the first dimension, group 1 above group 2 +% - data from the dimension(s) dim_m are put as the second, third... dimension of X +% - additional dimensions (tested separately, ie univariately) follow +% +ndX=ndims(X); +dX1=setdiff(1:ndX,dim); +sX1=[size(X) 1 1]; +% Put subjects in the first dimension +X=permute(X, [dim dX1]); +sX=[size(X) ones(1,ndX)]; +if numel(X2)>1 % X2 is a matrix + X2=permute(X2, [dim dX1]); + sX2=[size(X2) ones(1,ndX)]; + if ~isequal(sX(2:end), sX2(2:end)) + error('X1 and X2 should be of the same size in the non-permuted dimensions!') + end + X=cat(1,X,X2); + N=[sX(1) sX2(1)]; + clear sX2; +elseif numel(X2)==1 % X2 is the number of samples in the first group + N=[X2 sX(1)-X2]; +else + error('Check inputs!') +end +clear X2; +X=double(X); +if sum(N)>NMAX_SUBJECTS + error('Too many subjects!') +end +if dim_p>0 && N(1) ~= N(2) + error('Paired data should have the same number of samples!') +end + +%% SETTING dim_m VARIABLE & SHAPE OF X ALONG IT +% +% Default is to compute the maximum T value across all dimensions of the +% measurement data (dim_m). +if isempty(dim_m) + dim_m=setdiff(1:ndX,dim); +end +dim_m=unique(dim_m(:))'; +if any(dim_m>ndX) + error('Wrong dimM dimensions!') +end +if any(ismember(dim_p,dim_m)) + error(sprintf('Wrong dimM dimensions: [%d] is already set as the permuted dimension!\n' , dim)) +end +dim_m1=dim_m; +dim_m=dim_m+(dim_m0); + +%% PUT DATA INTO A SINGLE ARRAY FOR EFFICENCY +% Define a practical shape for the measurement data (all dim_m dimensions +% as a single vector) so that the maximum across dimension is easy to +% compute +if dim_m==0 + nsX=[1 sX(2:end)]; +elseif ndX==2 + nsX=[1 sX(2:end)]; +else + X=permute(X,[1 dim_m setdiff(2:ndX, dim_m)]); + nsX=[prod(sX(dim_m)) sX(setdiff(2:ndX, dim_m)) 1]; + X=reshape(X,[size(X,1) nsX]); +end + + +%% NUMBER OF PERMUTATIONS +% +% Now compute the number of exhaustive permutations +if dim_p>0 + if tails==2 + % We compute only one half of the permutations for paired samples + Nexh=2^(N(1)-1)-1; + else + Nexh=2^(N(1))-1; + end +else + Nexh=factorial(N(1)+N(2))/(factorial(N(1))*factorial(N(2)))-1; +end + +exhaustive=0; +if isempty(NP) + if (dim_p>0 && (N(1)+N(2))0 + msg=[ msg ' paired']; + end + warning(sprintf([ 'You are under-sampling (with %d permutations)!\nFor this dataset (with %s subjects),'... + 'the possible number of permutations is: %d.'],NP,msg,Nexh)); + end + elseif isequal(NP, inf) + if TimeBar + disp(sprintf('You have requested exhaustive permutations: %d.',Nexh)); + end + NP=Nexh; + exhaustive=1; + elseif NP>Nexh + if TimeBar + msg=sprintf('%d', N(1)); + if dim_p>0 + msg=[ msg ' paired']; + end + warning(sprintf([ 'You are over-sampling (with %d permutations)!\nFor this dataset (with %s subjects),'... + 'the maximum number of permutations is: %d.'],NP,msg,Nexh)); + end + elseif NP==Nexh; + exhaustive=1; + else + if NP>NPERMS + if TimeBar + warning(sprintf('So many permutations to be done (%d) may take a long time!',NP)); + end + end + end +end + + +%% LIST OF PERMUTAIONS +if dim_p>0 + % paired permutations + if exhaustive + % compute exhaustive list of perms + P=49==(dec2bin(1:NP,N(1))); + else + % non exhaustive + P=round(rand(NP,N(1))); + end + P=[P 1-P].*N(1)+repmat(1:N(1), [NP,2]); +else + % non-paired perms + if exhaustive % do all possible perms + p=nchoosek(1:(N(1)+N(2)),N(1)); + p(1,:)=[]; % the 1st is not a permutation + for i=1:NP + P(i,:)=[p(i,:) setdiff(1:(N(1)+N(2)),p(i,:))]; + end + clear p + else % non exhaustive + [ignore,P]=sort(rand(NP,N(1)+N(2)),2); + end +end + +%% DISPLAY TIMEBAR +if TimeBar + try + htimer = timebar('Permutation Statistics','Progress...'); + catch + warning('Function timebar.m missing. waitbar.m is used instead') + htimer = waitbar(0,'Permutation Statistics'); + end +else + htimer = NaN; +end + +%% PERMUTATION LOOP +for i=0:NP + if i==0 + % At the 0-th permutation, evaluate original data + Y=X; + else + Y=X(P(i,:),:,:); + end + + switch(statistic) + case 'tvalue' + Z=tvalue(Y(1:N(1),:),Y(N(1)+(1:N(2)),:)); + case 'meandiff' + Z=sum(Y(1:N(1),:),1)./N(1)-sum(Y(N(1)+(1:N(2)),:),1)./N(2); %this is to use difference of mean instead of T-statistic (KJ) + case 'pseudotvalue' + Z=pseudotvalue(Y(1:N(1),:),Y(N(1)+(1:N(2)),:),options{1}); + case 'signtest' + Z=sign(Y(1:N(1),:)-Y(N(1)+(1:N(2)),:)); + Z=sum(Z,1).^2./sum(abs(Z),1); + case 'wilcoxon' + Z=Y(1:N(1),:)-Y(N(1)+(1:N(2)),:); + Z=sign(Z).*tiedrank(abs(Z),1); + Z=sum(Z(:,:),1); + % case 'mann-whitney' + % Z=tiedrank(Y(:,:),1); + % Z=sum(Z(1:N(1),:),1); + case 'edist' + if i==0 + nsX(1)=1; + sX1(dim_m)=[]; + dim=dim-sum(dim_m1Y(N(1)+(1:N(2)),:),1); + case 'fun' + Z=feval(testfun,Y(1:N(1),:),Y(N(1)+(1:N(2)),:),testargs{:}); + end + Z=reshape(Z,nsX); + if tails==2 + Z=abs(Z); + end + if i==0 + S0=Z; + sz=size(S0); + S=zeros(sz); + if nargout>3 + %pack; + PS=zeros([NP sz(2:end) 1],'single'); + end + else + Z=max(Z,[],1); + if nargout>3 + PS(i,:)=Z(:)'; + end + S=S+(repmat(Z,[nsX(1) 1])>=S0); + if TimeBar,try if isequal(get(htimer,'tag'),'timebar'),timebar(htimer,i/NP),else waitbar(i/NP,htimer);end;catch,TimeBar=0;end,end + end +end + +%% COMPUTE RESULTING P-VALUES +pv=(S+1)./(NP+1); + +%% PROCESSING OUTPUTS +if ndX>2 + % Put back the dim_m dimension at their original place + if ~isequal(dim_m, 0) + pv=ipermute(pv, [dim_m-1 setdiff(1:ndX-1, dim_m-1)]); + S0=ipermute(S0, [dim_m-1 setdiff(1:ndX-1, dim_m-1)]); + end +end +if dim>1 || ndX>2 + sX1(dim)=[]; + pv=reshape(pv, sX1); + S0=reshape(S0, sX1); +end +if nargout>3 + PS=reshape(PS, [NP nsX(2:end)]); +end +try,if ishandle(htimer),close(htimer),end,drawnow;end +return + + +%% Validation: +X1=randn(100,1); +X=X1+0.2+randn(size(X1)); +% Paired case: +[p,K,P,PS]=permttest(X1,X,1,[],999,0);p +myttest(X1,X,1,'pttest') +% Unpaired (unequal variance) +[p,K,P,PS]=permttest(X1,X,-1,[],999,0);p +myttest(X1,X,1,'uttest') \ No newline at end of file diff --git a/stat/permtest/permtest_cluster.m b/stat/permtest/permtest_cluster.m new file mode 100644 index 0000000..8bf1e78 --- /dev/null +++ b/stat/permtest/permtest_cluster.m @@ -0,0 +1,119 @@ +function [clusters,pvclusters,pv,S,P,PS]=permtest_cluster(X1,X2,testname,vc,threshold,dims,NP,tails,st,TimeBar,varargin) +%permtest_cluster - Cluster-level permutation test +% [clusters,pvclusters,pv,S] = permtest_cluster(X1,X2,testname,vc,threshold,dims,st,NP,TimeBar,tails,testoptions) +% Performs two-pass permutation test for cluster-level inference +% On the firts pass, an uncorrected permutation test is performed on +% data. From this, the cluster-level significance level is assessed +% using connectivity information provided. +% +% MANDATORY INPUTS: +% X1,X2: 2D-data [Subjects x Measures] or [Measures x Subjects] (see permtest) +% testname: Name of the test used at the first level +% 'ttest'|'diff'|'pseudottest'|'mann-whitney'|'wilcoxon' +% OPTIONAL INPUTS: +% dimP: permuted dimension (default: 1. see permtest) +% vc: connectivity (if vc==1, assumed continuous, eg. time) +% threshold: primary threshold on uncorrected alpha significance +% default: 0.05 +% st: choice of the statistics: +% - 'probmass' (default): probability mass +% - { 'area' AREA} : area of the cluster. AREA should be a +% vector of areas associated to each vertices +% - 'size': number of vertices +% NP: number of permutations (default:see permtest) +% TimeBar: display TimeBar (default:see permtest) +% tails: 1 or 2-tail testing (default: 2) +% testoptions: parameters used by the chosen test +% (e.g. smoothing kernel for pseudottest) +% OUTPUTS: +% clusters: cell array of clusters +% pvcluster: p-value as approximated by permutations on clusters +% pv: uncorrected p-values at each measure point +% S: observed statistics in the data +% +% Example +% >> [c,pc]=permtest_cluster(x1,x2,'peusodttest',1,mni.vertconn,... +% 0.05,'pmass',9999,1,mni.smooth.W) +% +% See also: permtest, permtest_norm + +% Author: Th K&K Team. (kndiaye01yahoo.fr & jerbichups.jussieu.fr) +% Copyright (C) 2006 +% This program isfree software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% K&K 2006-01-05 Creation +% KND 2006-02-01 'area' option +% ----------------------------- Script History --------------------------------- + +if ndims(squeeze(X1))>2 + error('Cannot deal with multiple dimensions') +end + +if ~exist('dimP','var') + dimP=[]; +end +if ~exist('vc','var') + vc=[]; +end +if ~isequal(vc,1) && (~iscell(vc) || size(vc,1)~=size(vc,1)) && ~isempty(vc) + error('Connectivity Information provided through ''vc'' is of wrong type'); +end +if ~exist('dimM','var') + if iscell(vc) + dimM=find(size(X1)==length(vc)); + else + [ignore,dimM]=max(size(X1)); + end + fprintf('Clustering on dimension: %d\n', dimM); +end +if ~exist('threshold','var') + threshold=0.05; +end +if ~exist('st','var') + st='pmass'; +end +if ~exist('NP','var') + NP=[]; +end +if ~exist('TimeBar','var') + TimeBar=1; +end +if ~exist('tails','var') + tails=2; +end +if ~ismember(st{1}, { 'probmass' 'area' 'size' }) + error('Wrong cluster-level statistic!') +end + + +[pv,S,NP,P,PS] = permtest(X1,X2,testname,dimP,0,NP,TimeBar,tails,varargin{:}); +if nargout<5 + clear P; +end +PS=squeeze(PS); +pv=squeeze(pv); +T_thd=quantile(PS,1-threshold); +if tails==2 + TT=abs(S); +else + TT=S; +end + +if ischar(st) + st={st}; +end + +switch(st{1}) + case 'probmass' + [clusters,pvclusters]=prob_clusters(TT(:).*(TT(:)>=T_thd(:)),PS.*(PS>=repmat(T_thd(:)',NP,1)),vc,TimeBar); + case 'area' + [clusters,pvclusters]=prob_clusters(st{2}(:).*(TT(:)>=T_thd(:)),repmat(st{2}(:)',NP,1).*(PS>=repmat(T_thd(:)',NP,1)),vc,TimeBar); + case 'size' + [clusters,pvclusters]=prob_clusters(TT(:)>=T_thd(:),PS>=repmat(T_thd(:)',NP,1),vc,TimeBar); +end +return +%[FaceNormal, FaceArea, FaceCenter, VertexNormal, VertexArea, SuspectFace, NumFacesEachVertex, Duplicated_Faces, Not_Twice_Faces] = tessellation_stats( diff --git a/stat/permttest.m b/stat/permttest.m new file mode 100644 index 0000000..4859b51 --- /dev/null +++ b/stat/permttest.m @@ -0,0 +1,320 @@ +function [pv,S0,P,PS]=permttest(X,X2,dim_p,dim_m,NP,TimeBar,tails) +% permttest - Two-sided permutation pseudo T-test +% [pv,T,P,PS] = permtest2(X1,X2,dimP,dimM,NP,TimeBar,tails) +% [pv ... ] = permtest2(X ,N1,dimP,...) +% +% Performs a two-sided permutation pseudo T-test with the data, testing +% the following HO hypothesis: +% mean(X1) = mean(X2) +% +%MANDATORY INPUTS: +% X1,X2: [N1 x Ma x Mb...] and [ N2 x Ma x Mb x...] Multidimensional matrices +% of Ni samples/subjects for each group, eg. [ Subjects x Channel x Time ] +% Alternatively: +% X can be a single data matrix. But the number of samples (ie subjects) +% in the first group should be set using 'N1' +% I.e. X = [ X1 ; X2 ] & N1=size(X1,1) +% If N1<0 , define the dimension of the two samples +% +%OPTIONAL INPUTS (use [] for default values): +% dimP: Permuted dimensions (ie. subjects). Default: first non singleton +% If dim_p > 0 : paired permutations (default) +% Use negative dim_p for unpaired samples/subjects +% dimM: Dimensions of data across which the maximum T-value is computed. +% For multidimensional data, eg. [ Subject x Channel x Time ], +% control of Family-wise Error Rate requires to compute T-value +% across all dimensions (starting after the permuted one. +% Default is: all dimensions besides the "permuted" dimensions +% (typically: [2 3 ... ndims(X)]) +% NP: Number of permutations (may be reduced if bigger than the number of permutations, +% for example if NP > 2^N-1 using paired data) +% If not specify, permttest uses 10000 (at most). +% Set NP=inf so to enforce exhaustive permutations, ie. all possible permutations +% (this may lead to HUGE numbers of permutations, avoid with unpaired tests) +% TimeBar: 1 (default) or 0. To display a progress bar. +% tails: 1- (one: X1>X2) or 2- (two: X1X2) tailed test (default: 2) +% +%OUTPUTS: +% pv: (Approximate) p-values of the observed data, computed from the permutations +% T: [Ma x Mb...] Observed pseudo T-values +% P: [NP x (N1+N2)] List of permutations used +% PS: [P x Ma x Mb... ] Matrix of permutation statistics (may be quite big!) +% +% Author: Karim N'Diaye, CNRS UPR640 +% Created: Sep 2005 + +% NOT YET: + +% P-value normalization: +% To normalize distribution on a given dimension, use negative +% values in dimM. E.g. to normalize across space: dimM=[1 -2] or [ 1 2 -2] +% (order/repetition has no importance) +% See: Pantazis et al., NeuroImage, 2005 +% +% tails: 1 for one-sided [ X1>X2 ] +% 2 (default) two-sided test [ X1X2 ] +% ftest: function applied on data. +% Default is 'maxabst', i.e. compute the maximum (two-tailed) T-value +% fparams: cell array indicating the parameters passed to the function +% when called + +NMAX_UNPAIRED=15; % maximum number of observations for exhaustive search for unpaired samples +NMAX_PAIRED=30; % maximum number of observations for exhaustive search in paired samples +NMAX_SUBJECTS=1000; % Max number of subjects +NPERMS=9999; % Default number of permutations + +%initialize parameters +if nargin<3 + dim_p=[]; +end +if nargin<4 + dim_m=[]; +end +if nargin<5 + NP=[]; +end +if nargin<6 + TimeBar=1; % KJ, default is off now... +end +if nargin<7 + tails=[]; +end + +if isempty(tails) + tails=2; +end +if ~isequal(tails,1) && ~isequal(tails,2) + error('Tails can be 1 or 2') +end + +if isempty(dim_p) + dim=find(size(X)>1); + if isempty(dim) + error('Input X has only singleton or null dimensions!') + end + dim=dim(1); + dim_p=dim; +end +dim=abs(dim_p); + +% ORIGINAL OBSERVATIONS +% Data in X (and possibly in X2) will be concatenated (if needed) and +% reshaped so that folowing computations are made easier. +ndX=ndims(X); +dX1=setdiff(1:ndX,dim); +sX0=size(X); +% Put subjects in the first dimension +X=permute(X, [dim dX1]); +sX=size(X); +if numel(X2)>1 % X2 is a matrix + X2=permute(X2, [dim dX1]); + sX2=size(X2); + if ~isequal(sX(2:end), sX2(2:end)) + error('X1 and X2 should be of the same size in the non-permuted dimensions!') + end + if dim_p>0 & sX(1) ~= sX2(1) + error('X1 and X2 should have the same number of observations/subjects for paired data!') + end + X=cat(1,X,X2); + N=[sX(1) sX2(1)]; + clear sX2; +elseif numel(X2)==1 + if X2>0 + % X2 is the number of samples in the first group + N=[X2 sX0(1)-X2]; + elseif sX0(-X2)==2 + % X2 is the dimension defining the two samples + pX2=1:ndims(X); + pX2(-X2+(-X2NMAX_SUBJECTS + error('Too many subjects!') +end + +% Default is to compute the maximum T value across all dimensions of the +% measurement data +if isempty(dim_m) + dim_m=setdiff(1:ndX,dim); +end +dim_m=unique(dim_m(:))'; +if max(dim_m)>ndX + error('Wrong dimM dimensions!') +end +if ismember(dim_p,dim_m) + error(sprintf('Wrong dimM dimensions: [%d] is already set as the permuted dimension!\n' , dim)) +end +dim_m=dim_m+(dim_m0); + +% Define a practical shape for the measurement data +% (so that the maximum across dimension is easy to compute) +if dim_m==0 + nsX=[1 sX(2:end)]; +else + X=permute(X,[1 dim_m setdiff(2:ndX, dim_m)]); + nsX=[prod(sX(dim_m)) sX(setdiff(2:ndX, dim_m)) 1]; +end + +% Now compute the number of exhaustive permutations +if dim_p>0 + if tails==2 + % We compute only one half of the permutations for paired samples + Nexh=2^(N(1)-1)-1; + else + Nexh=2^(N(1))-1; + end +else + Nexh=factorial(N(1)+N(2))/(factorial(N(1))*factorial(N(2)))-1; +end + +exhaustive=0; +if isempty(NP) + if (dim_p>0 && (N(1)+N(2))Nexh + if TimeBar + warning(sprintf('You are over-sampling! For this dataset, the maximum number of permutations is: %d.',Nexh)); + end + elseif NP==Nexh; + exhaustive=1; + end +end +% if NP>NPERMS +% warning(sprintf('Too many permutations! Only %d will be performed.',NPERMS)); +% NP = NPERMS; +% exhaustive=0; +% end +% DEBUG: +% NP = min(500,NP); + + +% List of permutations +if dim_p>0 + % paired permutations + if exhaustive + % compute exhaustive list of perms + P=49==(dec2bin(1:NP,N(1))); + else + % non exhaustive + P=round(rand(NP,N(1))); + end + P=[P 1-P].*N(1)+repmat(1:N(1), [NP,2]); +else + % non-paired perms + if exhaustive % do all possible perms + p=nchoosek(1:(N(1)+N(2)),N(1)); + p(1,:)=[]; % the 1st is not a permutation + for i=1:NP + P(i,:)=[p(i,:) setdiff(1:(N(1)+N(2)),p(i,:))]; + end + clear p + else % non exhaustive + [ignore,P]=sort(rand(NP,N(1)+N(2)),2); + end +end + +if TimeBar + try + htimer = timebar('Permutation Statistics','Progress...'); + catch + warning('Function timebar.m missing. waitbar.m is used instead') + htimer = waitbar(0,'Permutation Statistics'); end +else + htimer = NaN; +end + +% PERMUTATION LOOP +for i=0:NP + if i==0 + % At the 0-th permutation, evaluate original data + Y=X; + else + Y=X(P(i,:),:); + end + +% Z=mean(Y(1:N(1),:),1)-mean(Y(N(1)+(1:N(2)),:),1); +% Z= Z ./ sqrt( std(Y(1:N(1),:),[],1).^2/N(1) + std(Y(N(1)+(1:N(2)),:),[],1).^2/N(2)); + Z=tvalue(Y(1:N(1),:),Y(N(1)+(1:N(2)),:)); + Z=reshape(Z,nsX); + + if i==0 + S0=Z; + sz=size(S0); + S=zeros(sz); + if nargout>3 + pack; + PS=zeros([NP sz(2:end) 1],'single'); + end + else + if tails==2 + Z=abs(Z); + end + Z=max(Z,[],1); + if nargout>3 + PS(i,:)=Z(:)'; + end + if tails==2 + S=S+(repmat(Z,[nsX(1) 1])>=abs(S0)); + else + S=S+(repmat(Z,[nsX(1) 1])>=S0); + end + if ishandle(htimer),try,if isequal(get(htimer, 'tag'), 'timebar'), timebar(htimer,i/NP),else,waitbar(i/NP,htimer);end;catch,end,end + end +end + +% Compute p-values +pv=(S+1)/(NP+1); +if ~isequal(dim_m, 0) + pv=reshape(pv, [sX([dim_m setdiff(2:ndX,dim_m)]) 1]); + S0=reshape(S0, [sX([dim_m setdiff(2:ndX,dim_m)]) 1]); + if ndX>2 + pv=ipermute(pv, [dim_m-1 setdiff(1:ndX-1, dim_m-1)]); + S0=ipermute(S0, [dim_m-1 setdiff(1:ndX-1, dim_m-1)]); + end +else + pv=reshape(pv, [sX(2:end) 1]); + S0=reshape(S0, [sX(2:end) 1]); +end +if nargout>3 + PS=reshape(PS, [NP nsX(2:end)]); +end +if ishandle(htimer),try,close(htimer),end,end +return + + +% Validation: +X1=randn(100,1); +X=X1+0.2+randn(size(X1)); +% Paired case: +[p,K,P,PS]=permttest(X1,X,1,[],999,0);p +myttest(X1,X,1,'pttest') +% Unpaired (unequal variance) +[p,K,P,PS]=permttest(X1,X,-1,[],999,0);p +myttest(X1,X,1,'uttest') \ No newline at end of file diff --git a/stat/permttest_cluster.m b/stat/permttest_cluster.m new file mode 100644 index 0000000..c0a13bf --- /dev/null +++ b/stat/permttest_cluster.m @@ -0,0 +1,246 @@ +function [clusters,pvclusters,pv,S,T,P,PS,S_sort]=permttest_cluster(X1,X2,dimP,dimM,vc,threshold,st,NP,TimeBar,tails) +%permttest_cluster - Cluster-level permutation test +% [clusters,pvclusters] = permttest_cluster(X1,X2,dimP,dimM,vcM,NP,threshold,TimeBar,tails,st) +% Performs two-pass permutation test for cluster-level inference +% On the firts pass, an uncorrected permutation test is performed on +% data. From this, the cluster-level assessment is performed using +% connectivity information provided. +% The choice of the cluster statistics 'mass' +% MANDATORY INPUTS: +% X1,X2: data (see permttest) +% OPTIONAL INPUTS: +% dimP: permuted dimension (default: see permttest) +% dimM: dimension used for clustering (typically: vertices or time) +% vc: connectivity (if vc==1, assumed continuous, eg. time) +% threshold: primary threshold on uncorrected alpha significance +% default: 0.05 +% st: choice of the statistics: 'mass' (default) or 'size'. +% NP: number of permutations (default:see permttest) +% TimeBar: display TimeBar (default:see permttest) +% tails: 1 or 2-tail testing (default: 2) +% OUTPUTS: +% clusters: cell array of clusters +% pvcluster: p-value as approximated by permutations +% +% Example +% >> [c,pc]=permttest_cluster(x1,x2,1 +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2006 +% This program isfree software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2006-01-05 Creation +% +% ----------------------------- Script History --------------------------------- + +if ndims(X1)>2 + error('Cannot deal with multiple dimensions') +end + +if ~exist('dimP','var') + dimP=[]; +end +if ~isempty(dimP) & ~isequal(abs(dimP),1) + error('Data matrices X1 and X2 should be: [Subjects]x[Vertices]x[Time Samples]') +end +if ~exist('vc','var') + vc=[]; +end +if ~isempty('vc') + vc=1; +end +if ~exist('dimM','var') + if iscell(vc) + dimM=find(size(X1)==length(vc)); + else + [ignore,dimM]=max(size(X1)); + end + fprintf('Clustering on dimension: %d\n', dimM); +end +if ~exist('threshold','var') + threshold=0.05; +end +if ~exist('st','var') + st='mass'; +end +if ~exist('NP','var') + NP=[]; +end +if ~exist('TimeBar','var') + TimeBar=0; +end +if ~exist('tails','var') + tails=2; +end +[pv,T,P,PS] = permttest(X1,X2,dimP,0,NP,TimeBar,tails); +T_thd=quantile(PS,1-threshold); +NP=size(PS,1); +if tails==2 + TT=abs(T); +end +switch(st) + case 'mass' + [clusters,pclusters]=prob_clusters(TT.*(TT>=shiftdim(T_thd)),PS.*(PS>=repmat(T_thd,NP,1)),vc); + case 'size' + [clusters,pclusters]=prob_clusters(TT>=shiftdim(T_thd),PS>=repmat(T_thd,NP,1),vc); +end +pvclusters=1-pclusters; % this is now a p-value... + +return + + +%==================================================================== +%% OLD STUFF (now use prob_clusters) +% +% + + + +clear P; +sX=size(pv); +NP=size(PS,1); +T_thd=quantile(PS,1-threshold); +T_thd=reshape(T_thd, sX); +PS=reshape(PS, [NP sX 1]); +PS=permute(PS, [1 dimM setdiff(2:length(sX), dimM)]); +sX=size(PS); +S=ones(NP,1); +for i=1:NP + if isempty(vcM) + s=piecemeal(shiftdim(PS(i,:,:),1)>=T_thd(:,:)); + if isempty(s) + s=0; + else + s=max(s); + end + S(i)=s; + else + clusters=tex_clusters(abs(T)>=T_thd,vcM) + for i=1:length(clusters{1}); + sz(i)=length(clusters{1}{i}); + end + end +end +if isempty(vcM) + [sz,samples]=piecemeal(abs(T)>=T_thd); + for i=1:length(sz); + clusters{i}=samples(i)+[1:sz(i)]-1; + end +else + clusters=tex_clusters(abs(T)>=T_thd,vcM) + for i=1:length(clusters{1}); + sz(i)=length(clusters{1}{i}); + end +end + +if isempty(sz) + clusters={}; + pclusters=[]; + return +end + +[sz,i]=sort(sz); +sz=flipud(sz); +i=flipud(i); +clusters=clusters(i); +for i=1:length(sz) + pclusters(i)=(sum(S>=sz(i))+1)/(NP+1); +end + +%==================================================================== +%% OLD STUFF +% +% + + +for k=1:blk:NV + kk=[k:min(k+blk-1,NV)]; + clear pv_tmp T_tmp P PS + tic + + toc + + if k==1 + % Those allocations cannot be done earlier as we don't know NP + NP=size(PS,1); + S=ones(NP,1); + T=zeros(szX(2:end)); + pv=zeros(szX(2:end)); + end + % Minimal p-value across channels, time etc for each perm(but not + % across perms) + S = min([S pvalue_fast(PS(:,:))]')'; + T(kk,:)=T_tmp; + pv(kk,:)=pv_tmp; +end + +S_sort = sort(S); +alphas = find(diff(S_sort)~=0)/length(S_sort); +pThresholds=S_sort(find(diff(S_sort)~=0)); +if nargout>3 + pv_alpha=alpha_threshold(alphas,pThresholds,pv); +end +return + +function [pA] = pvalue_fast( A ) +% calculate p-values of a matrix A +% p-values are calculated column-wise +A=A(:,:); +pA=zeros(size(A)); +[Y,I] = sort(-A); +n=size(A,1); +%fast calculation of p values ignoring common elements +for i=1:size(A,2) + pA(I(:,i),i) = 1:n; +end +pA=pA./n; +return + + +%========================================================================== +%%OLDER STUFF... + + + +S = ones(M,1); %initialize with worst case +T_orig_pvalue = zeros(size(T_orig)); +for k = 1:K %for all channels/sources + MF = []; %MF holds all data from same channel k (MxF matrix) + for m = 1:M %for all permutations + MF = [MF; T{m}(k,:)]; + end + %convert MF into p-values + MFpvalue = pvalue_fast(MF')'; + %Update S statistic, S = minmin_kf T_kf + %we find minimum component for each channel k each time, iterate for K channels + S = min([S MFpvalue]')'; +end + +S_sort = sort(S); + +bst_message_window('Convert Perm statistics into P-values -> Done') +delete(hwait) + +%This is not completely correct +%The empirical distribution of S_sort is discrete and the left +%tail is not clearly defined +%Instead of 5% threshold, you may achieve 7.6% or something like that + +% p_steps = find(diff(S_sort)~=0)/length(S_sort); +% true_pvalue = S_sort(length(S_sort)*closest(pthres,S_sort)); + +%Sth = S_sort(length(S_sort)*0.05) + +%Significant values: +%T_orig_pvalue > Sth +return + + + + diff --git a/stat/permttest_norm.m b/stat/permttest_norm.m new file mode 100644 index 0000000..95f7eff --- /dev/null +++ b/stat/permttest_norm.m @@ -0,0 +1,83 @@ +function [alphas,thresholds,pv,T,P,PS,S_sort]=permttest_norm(X1,X2,dimP,dimM,NP,TimeBar,tails) +% Normalized permutation Two-sided permutation pseudo T-test +% [alphas,thresholds,pv] = permttest_norm(X1,X2,dimP,dimM,NP,TimeBar,tails) +% +% dimP=1; +% NP=100; +% TimeBar=0; +% tails=2; +% X1=rand(15,10,12);X2=cumsum(repmat(randn(1,10), [15,1,12]),2)+rand(15,10,12); + +% actual code +[pv,T,P,PS] = permttest(X1,X2,dimP,0,+inf); % KJ customized..... +NP=size(PS,1); +S=ones(NP,1); + +for k=1:size(T,2) + MFpvalue=pvalue_fast(squeeze(PS(:,k,:))')'; + S = min([S MFpvalue]')'; +end + +S_sort = sort(S); +alphas = find(diff(S_sort)~=0)/length(S_sort); +thresholds=S_sort(find(diff(S_sort)~=0)); + +% Sth = S_sort(length(S_sort)*closest(pthres,S_sort)); + +return + + + + + +S = ones(M,1); %initialize with worst case +T_orig_pvalue = zeros(size(T_orig)); +for k = 1:K %for all channels/sources + MF = []; %MF holds all data from same channel k (MxF matrix) + for m = 1:M %for all permutations + MF = [MF; T{m}(k,:)]; + end + %convert MF into p-values + MFpvalue = pvalue_fast(MF')'; + %Update S statistic, S = minmin_kf T_kf + %we find minimum component for each channel k each time, iterate for K channels + S = min([S MFpvalue]')'; +end + +S_sort = sort(S); + +bst_message_window('Convert Perm statistics into P-values -> Done') +delete(hwait) + +%This is not completely correct +%The empirical distribution of S_sort is discrete and the left +%tail is not clearly defined +%Instead of 5% threshold, you may achieve 7.6% or something like that + +% p_steps = find(diff(S_sort)~=0)/length(S_sort); +% true_pvalue = S_sort(length(S_sort)*closest(pthres,S_sort)); + +%Sth = S_sort(length(S_sort)*0.05) + +%Significant values: +%T_orig_pvalue > Sth +return + + +function [pA] = pvalue_fast( A ) +%calculate p-values of a matrix A +%p-values are calculated row-wise + +[Y,I] = sort(-A,2); + +%fast calculation of p values ignoring common elements +for i=1:size(A,1) + if(rem(i,250)==0) + disp([num2str(i) ' out of ' num2str(size(A,1))]); + pack + end + pA(i,I(i,:)) = 1:size(A,2); +end +pA=pA/size(A,2); + + diff --git a/stat/permttest_norm2.m b/stat/permttest_norm2.m new file mode 100644 index 0000000..46a0089 --- /dev/null +++ b/stat/permttest_norm2.m @@ -0,0 +1,123 @@ +function [alphas,pThresholds,pv,pv_alpha,T,P,PS,S_sort]=permttest_norm2(X1,X2,dimP,NP,TimeBar,tails) +% Normalized permutation Two-sided permutation pseudo T-test (with memory optimization) +% [alphas,pThresholds,pv] = permttest_norm2(X1,X2,dimP,dimM,NP,TimeBar,tails) +% [alphas,pThresholds,pv,pv_alpha] = permttest_norm2(...) +% [alphas,pThresholds,pv,T,P,PS,S_sort] = permttest_norm2(...) +% +% dimP=1; +% NP=100; +% TimeBar=0; +% tails=2; +% X1=rand(15,10,12);X2=cumsum(repmat(randn(1,10), [15,1,12]),2)+rand(15,10,12); +if ~exist('dimP','var') + dimP=[]; +end +if ~isempty(dimP) & ~isequal(abs(dimP),1) + error('Data matrices X1 and X2 should be: [Subjects]x[Vertices]x[Time Samples]') +end +if ~exist('NP','var') + NP=+inf; +end +if ~exist('TimeBar','var') + TimeBar=0; +end +if ~exist('tails','var') + tails=2; +end +% try +% MEMSPACE=feature('dumpmem'); % largest contiguous block in memory +% MEMSPACE=MEMSPACE/8; % doubles are 8 bytes +% MEMSPACE=MEMSPACE/4; % we want to use only a 1/4 of it +% catch +% MEMSPACE=8e6; +% end +% +szX=[size(X1) 1]; +NV=size(X1,2); +blk=500; + +for k=1:blk:NV + kk=[k:min(k+blk-1,NV)]; + clear pv_tmp T_tmp P PS + tic + [pv_tmp,T_tmp,P,PS] = permttest(X1(:,kk,:),X2(:,kk,:),dimP,0,NP,TimeBar,tails); % KJ customized..... + toc + + if k==1 + % Those allocations cannot be done earlier as we don't know NP + NP=size(PS,1); + S=ones(NP,1); + T=zeros(szX(2:end)); + pv=zeros(szX(2:end)); + end + % Minimal p-value across channels, time etc for each perm(but not + % across perms) + S = min([S pvalue_fast(PS(:,:))]')'; + T(kk,:)=T_tmp; + pv(kk,:)=pv_tmp; +end + +S_sort = sort(S); +alphas = find(diff(S_sort)~=0)/length(S_sort); +pThresholds=S_sort(find(diff(S_sort)~=0)); +if nargout>3 + pv_alpha=alpha_threshold(alphas,pThresholds,pv); +end +return + +function [pA] = pvalue_fast( A ) +% calculate p-values of a matrix A +% p-values are calculated column-wise +A=A(:,:); +pA=zeros(size(A)); +[Y,I] = sort(-A); +n=size(A,1); +%fast calculation of p values ignoring common elements +for i=1:size(A,2) + pA(I(:,i),i) = 1:n; +end +pA=pA./n; +return + + + +%% OLDIES ========================================= + + + +S = ones(M,1); %initialize with worst case +T_orig_pvalue = zeros(size(T_orig)); +for k = 1:K %for all channels/sources + MF = []; %MF holds all data from same channel k (MxF matrix) + for m = 1:M %for all permutations + MF = [MF; T{m}(k,:)]; + end + %convert MF into p-values + MFpvalue = pvalue_fast(MF')'; + %Update S statistic, S = minmin_kf T_kf + %we find minimum component for each channel k each time, iterate for K channels + S = min([S MFpvalue]')'; +end + +S_sort = sort(S); + +bst_message_window('Convert Perm statistics into P-values -> Done') +delete(hwait) + +%This is not completely correct +%The empirical distribution of S_sort is discrete and the left +%tail is not clearly defined +%Instead of 5% threshold, you may achieve 7.6% or something like that + +% p_steps = find(diff(S_sort)~=0)/length(S_sort); +% true_pvalue = S_sort(length(S_sort)*closest(pthres,S_sort)); + +%Sth = S_sort(length(S_sort)*0.05) + +%Significant values: +%T_orig_pvalue > Sth +return + + + + diff --git a/stat/permttest_run.m b/stat/permttest_run.m new file mode 100644 index 0000000..e69de29 diff --git a/stat/phicoef.m b/stat/phicoef.m new file mode 100644 index 0000000..f5b4527 --- /dev/null +++ b/stat/phicoef.m @@ -0,0 +1,13 @@ +function [phi]=phicoef(C) +%phicoef - (Pearson's) Phi coefficient (correlation for binary data) +% +% [phi]=phicoef(C) computes phi for a contingeny matrix C +% +% KND, 2011 + +if ~isequal(size(C),[2 2]) + error +end +c1 = sum(C,1); +c2 = sum(C,2); +phi = det(C)/sqrt(prod([c1 c2'])); diff --git a/stat/posthoc.m b/stat/posthoc.m new file mode 100644 index 0000000..e25bc0f --- /dev/null +++ b/stat/posthoc.m @@ -0,0 +1,26 @@ +function p = posthoc(varargin) +%POSTHOC - Performs posthoc tests on data +% [] = posthoc(X,nf,rp) +% +% Example +% >> posthoc +% +% See also: + + +% Pb : +% Tukey pour les between-S +% Bonferroni / Dunn-Sidak pour les within +% + +% Author: K. N'Diaye (kndiaye01gmail.com) +% Copyright (C) 2010 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2010-11-13 Creation +% +% ----------------------------- Script History --------------------------------- diff --git a/stat/powerStudent.m b/stat/powerStudent.m new file mode 100755 index 0000000..8f5f23f --- /dev/null +++ b/stat/powerStudent.m @@ -0,0 +1,102 @@ +function [powerStudent] = powerStudent(t,df,c,a) +%Power estimation of a performed Student's t test about mean(s). +%(Estimates the statistical power of a performed Student's t test about mean(s). +%It recalls you the statistical result of the test you should have arrived.) +% +% Syntax: function [powerStudent] = powerStudent(t,df,c,a) +% +% Inputs: +% t - Student's t statistic. +% df - degrees of freedom. +% c - specified direction testing (1 = one-tailed; 2 = two-tailed). +% a - significance level (default = 0.05). +% Outputs: +% Specified direction test. +% (Statistical result of the test you should have arrived). +% Power. +% +% Example: From the example 7.9 of Zar (1999, p.108), the estimation of power +% of a one-sample t test for a two-tailed hypothesis (c = 2) with a +% significance level = 0.05 (t = 2.7662, df = 11, c = 2). +% +% Calling on Matlab the function: +% powerStudent(2.7662,11,2) +% +% Answer is: +% +% It is a two-tailed hypothesis test. +% (The null hypothesis was statistically significative.) +% Power is: 0.7086 +% + +% Created by A. Trujillo-Ortiz and R. Hernandez-Walls +% Facultad de Ciencias Marinas +% Universidad Autonoma de Baja California +% Apdo. Postal 453 +% Ensenada, Baja California +% Mexico. +% atrujo@uabc.mx +% +% January 2, 2003. +% +% To cite this file, this would be an appropriate format: +% Trujillo-Ortiz, A. and R. Hernandez-Walls. (2003). powerStudent: Power estimation of a performed +% Student's t test about mean(s). A MATLAB file. [WWW document]. URL http://www.mathworks.com/ +% matlabcentral/fileexchange/loadFile.do?objectId=2907&objectType=file +% +% References: +% +% Zar, J. H. (1999), Biostatistical Analysis. 4th. ed. +% New-Jersey:Upper Saddle River. p. 107-108,135-136,164. + +if nargin < 4, + a = 0.05; +end + +if nargin < 3, + error('Requires at least three input arguments.'); +end + +t=abs(t); + +if c==1; + disp('It is a one-tailed hypothesis test.'); + a=a; + P=1-tcdf(t,df); + if P >= a; + disp('(The null hypothesis was not statistically significative.)'); + tp=tinv(1-a,df) - t; %Power estimation. + Power=1-tcdf(tp,df); + disp(' '); + fprintf('Power is: %2.4f\n\n', Power) + else + disp('(The null hypothesis was statistically significative.)'); + tb=t - tinv(1-a,df); %Power estimation. + Power=tcdf(tb,df); + disp(' '); + fprintf('Power is: %2.4f\n\n', Power) + end +else c==2; + disp('It is a two-tailed hypothesis test.'); + a=a/2; + P=1-tcdf(t,df); + if P >= a; + disp('(The null hypothesis was not statistically significative.)'); + tp1=tinv(1-a,df) - t; %Power estimation. + Power1=1-tcdf(tp1,df); + tp2=t + tinv(1-a,df); + Power2=1-tcdf(tp2,df); + Power=Power1 + Power2; + disp(' '); + fprintf('Power is: %2.4f\n\n', Power) + else + disp('(The null hypothesis was statistically significative.)'); + tb1=t - tinv(1-a,df); %Power estimation. + b1=1-tcdf(tb1,df); + tb2=t + tinv(1-a,df); + b2=1-tcdf(tb2,df); + Power=1 - (b1 - b2); + disp(' '); + fprintf('Power is: %2.4f\n\n', Power) + end +end diff --git a/stat/power_ttest.m b/stat/power_ttest.m new file mode 100644 index 0000000..c04d48d --- /dev/null +++ b/stat/power_ttest.m @@ -0,0 +1,39 @@ +function [beta]=power_ttest(X1,X2,alpha) +% [beta]=power_ttest(X1,X2,alpha) + + +error + +if size(X1)==2 + mX = X1(:); + if numel(X2)==1 + X2=[X2;X2]; + end + if numel(X2)~=2 + error + end + sX=X2(:); +else + mX = [ mean(X1) ; mean(X2)]; + sX = [ std(X1) ; std(X2)]; + +end + + + +function power=powerfunT(mu0,mu1,sig,alpha,tail,n) +%POWERFUNT T power calculation + S = sig ./ sqrt(n); % std dev of mean + ncp = (mu1-mu0) ./ S; % noncentrality parameter + + if tail==0 + critL = tinv(alpha/2,n-1); % note tinv() is negative + critU = -critL; + power = nctcdf(critL,n-1,ncp) + nctcdf(-critU,n-1,-ncp); % P(t < critL) + P(t > critU) + elseif tail==1 + crit = tinv(1-alpha,n-1); + power = nctcdf(-crit,n-1,-ncp); % 1-nctcdf(crit,n-1,ncp), P(t > crit) + else % tail==-1 + crit = tinv(alpha,n-1); + power = nctcdf(crit,n-1,ncp); % P(t < crit) + end \ No newline at end of file diff --git a/stat/pseudotvalue.m b/stat/pseudotvalue.m new file mode 100644 index 0000000..63804bd --- /dev/null +++ b/stat/pseudotvalue.m @@ -0,0 +1,23 @@ +% pseudotvalue - Pseudo T-value(s) between two samples (regularization of sample variance) +% +% [T] = pseudotvalue(A,B,W) +% Computes a pseudo T-value between the two samples A and B where the +% variance in the denominator is smoothed by matrix W +% INPUTS: +% A - MxK matrix (M=number of observations in the 1st sample, K=number of measures) +% B - NxK matrix (N=number of observations in the 2nd sample, K=number of measures) +% W - KxK matrix (may be sparse) applied to the variance +% OUTPUTS: +% T - 1xK vector of T-values +% +% Author: Karim(s) Jerbi & N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2006 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2006-01-06 Creation of pseudotvalue.c & its helper pseudotvalue.m +% +% ----------------------------- Script History --------------------------------- diff --git a/stat/pttest.m b/stat/pttest.m new file mode 100644 index 0000000..21d5cdb --- /dev/null +++ b/stat/pttest.m @@ -0,0 +1,41 @@ +function [p, t, df] = pttest(d1, d2) +%PTTEST Student's paired t-test. +% PTTEST(X1, X2) gives the probability that Student's t +% calculated on paired data X1 and X2 is higher than +% observed, i.e. the "significance" level. This is used +% to test whether two paired samples have significantly +% different means. +% [P, T] = PTTEST(X1, X2) gives this probability P and the +% value of Student's t in T. The smaller P is, the more +% significant the difference between the means. +% E.g. if P = 0.05 or 0.01, it is very likely that the +% two sets are sampled from distributions with different +% means. +% +% This works for PAIRED SAMPLES, i.e. when elements of X1 +% and X2 correspond one-on-one somehow. +% E.g. residuals of two models on the same data. +% +%See also: TTEST, UTTEST. +[l1 c1] = size(d1) ; +n1 = l1 * c1 ; +x1 = reshape(d1, l1 * c1, 1) ; +[l2 c2] = size(d2) ; +n2 = l2 * c2 ; +if (n1 ~= n2) + error('PTTEST: paired samples must have the same number of elements !') +end +x2 = reshape(d2, l2 * c2, 1) ; +[a1 v1] = avevar(x1) ; +[a2 v2] = avevar(x2) ; +df = n1 - 1 ; +cab = (x1 - a1)' * (x2 - a2) / (n1 - 1) ; +if (a1 ~= a2) + % use abs to avoid numerical errors for very similar data + % for which v1+v2-2cab may be close to 0. + t = (a1 - a2) / sqrt(abs(v1 + v2 - 2 * cab) / n1) ; + p = betainc( df / (df + t*t), df/2, 0.5) ; +else + t = 0 ; + p = 1 ; +end diff --git a/stat/pvalue.m b/stat/pvalue.m new file mode 100644 index 0000000..6ea985a --- /dev/null +++ b/stat/pvalue.m @@ -0,0 +1,97 @@ +function = pvalue(input,varargin) +%PVALUE - One line description goes here. +% [] = pvalue(X,dist,params) +% +% Example +% >> pvalue(T,'T',dof) +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2009 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2009-10-16 Creation +% +% ----------------------------- Script History --------------------------------- + + +function p = tpvalue(x,v) +%TPVALUE Compute p-value for t statistic + +normcutoff = 1e7; +if length(x)~=1 && length(v)==1 + v = repmat(v,size(x)); +end + +% Initialize P. +p = NaN(size(x)); +nans = (isnan(x) | ~(0 (0 normcutoff); +p(normal) = 0.5 * erfc(-x(normal) ./ sqrt(2)); + +% See Abramowitz and Stegun, formulas 26.5.27 and 26.7.1 +gen = ~(cauchy | normal | nans); +p(gen) = betainc(v(gen) ./ (v(gen) + x(gen).^2), v(gen)/2, 0.5)/2; + +% Adjust for x>0. Right now p<0.5, so this is numerically safe. +reflect = gen & (x > 0); +p(reflect) = 1 - p(reflect); + +% Make the result exact for the median. +p(x == 0 & ~nans) = 0.5; + + +if nargin<1 + error('No data!') +elseif nargin==1 || isnumeric(action) + varargin=[{action} varargin ]; + action='init'; +end + +try + varargout={eval(sprintf('action_%s(varargin{:});',action))}; +catch + eval(sprintf('action_%s(varargin{:});',action)); +end + + +function [ha]=action_init(data, varargin) +% ------------------------------------------------------------------------- +% Check OPTIONS and default values when optional arguments are not +% specified +Def_OPTIONS = struct(... + 'option1',[],... + ); +if nargin < 2 + OPTIONS = Def_OPTIONS; +else + if length(varargin)>1 + OPTIONS = cell2struct(varargin(2:2:end),varargin(1:2:end),2); + % struct(varargin{:}) bugs with something like {'string1' 'string2'} in the inputs! + else + OPTIONS = varargin{1}; + end + % Check field names of passed OPTIONS and fill missing ones with default values + DefFieldNames = fieldnames(Def_OPTIONS); + for k = 1:length(DefFieldNames) + if ~isfield(OPTIONS,DefFieldNames{k}) + OPTIONS = setfield(OPTIONS,DefFieldNames{k},getfield(Def_OPTIONS,DefFieldNames{k})); + end + end + clear DefFieldNames +end +clear Def_OPTIONS +% ------------------------------------------------------------------------- \ No newline at end of file diff --git a/stat/qtukey.m b/stat/qtukey.m new file mode 100644 index 0000000..a2e48a5 --- /dev/null +++ b/stat/qtukey.m @@ -0,0 +1,139 @@ +function q = qtukey(rr,cc,df,p,lowt,logp) +% Computes the quantiles of the maximum of rr studentized +% ranges, each based on cc means and with df degrees of freedom +% for the standard error, is less than q. +% +% Uses the secant method to find critical values. +% +% p = confidence level (1 - alpha) +% rr = no. of rows or groups +% cc = no. of columns or treatments +% df = degrees of freedom of error term +% +% ir(1) = error flag = 1 if wprob probability > 1 +% ir(2) = error flag = 1 if ptukey probability > 1 +% ir(3) = error flag = 1 if convergence not reached in 50 iterations +% = 2 if df < 2 +% +% qtukey = returned critical value + % +% If the difference between successive iterates is less than eps, +% the search is terminated + + % +% The algorithm is based on that of the reference. +% +% REFERENCE +% Copenhaver, Margaret Diponzio & Holland, Burt S. +% Multiple comparisons of simple effects in +% the two-way analysis of variance with fixed effects. +% Journal of Statistical Computation and Simulation, +% Vol.30, pp.1-15, 1988. + +eps = 0.0001; +maxiter = 50; + +if (df < 2 || rr < 1 || cc < 2) + error; +end + +% Initial value + +x0 = qinv(p, cc, df); + +% Find prob(value < x0) + +valx0 = ptukey(x0, rr, cc, df, 1, 0) - p; + +% Find the second iterate and prob(value < x1). +% If the first iterate has probability value +% exceeding p then second iterate is 1 less than +% first iterate; otherwise it is 1 greater. + +if (valx0 > 0.0) + x1 = fmax2(0.0, x0 - 1.0); +else + x1 = x0 + 1.0; +end + +valx1 = ptukey(x1, rr, cc, df, 1 ,0 ) - p; + +% Find new iterate +for iter=1:maxiter + q = x1 - ((valx1 * (x1 - x0)) / (valx1 - valx0)); + valx0 = valx1; + % New iterate must be >= 0 + x0 = x1; + if (q < 0.0) + q = 0.0; + valx1 = -p; + end + % Find prob(value < new iterate) + + valx1 = ptukey(ans, rr, cc, df, 1, 0) - p; + x1 = q; + + % If the difference between two successive + % iterates is less than eps, stop + + xabs = fabs(x1 - x0); + if (xabs < eps) + return + end +end + +% If the difference between two successive +% iterates is less than eps, stop + +xabs = fabs(x1 - x0); +if (xabs < eps) + return +end +error('No convergence') + + + +% qinv() : +% this function finds percentage point of the studentized range +% which is used as initial estimate for the secant method. +% function is adapted from portion of algorithm as 70 +% from applied statistics (1974) ,vol. 23, no. 1 +% by odeh, r. e. and evans, j. o. +% +% p = percentage point +% c = no. of columns or treatments +% v = degrees of freedom +% qinv = returned initial estimate +% +% vmax is cutoff above which degrees of freedom +% is treated as infinity. + +function qi = qinv(p,c,v) +p0 = 0.322232421088; +q0 = 0.993484626060e-01; +p1 = -1.0; +q1 = 0.588581570495; +p2 = -0.342242088547; +q2 = 0.531103462366; +p3 = -0.204231210125; +q3 = 0.103537752850; +p4 = -0.453642210148e-04; +q4 = 0.38560700634e-02; +c1 = 0.8832; +c2 = 0.2368; +c3 = 1.214; +c4 = 1.208; +c5 = 1.4142; +vmax = 120.0; +ps = 0.5 - 0.5 * p; +yi = sqrt (log (1.0 / (ps * ps))); +t = yi + (((( yi * p4 + p3) * yi + p2) * yi + p1) * yi + p0) / (((( yi * q4 + q3) * yi + q2) * yi + q1) * yi + q0); +if (v < vmax) + t = t + (t * t * t + t) / v / 4.0; +end +q = c1 - c2 * t; +if (v < vmax) + q = q - c3 / v + c4 * t / v; +end +qi = t * (q * log (c - 1.0) + c5); + diff --git a/stat/rau.m b/stat/rau.m new file mode 100644 index 0000000..7d17868 --- /dev/null +++ b/stat/rau.m @@ -0,0 +1,48 @@ +function rau_score = rau(p, n) +% RAU Compute rationalized arcsine transform +% +% Doing linear tests (like ANOVA or t-tests) on proportional data +% (values between 0 and 1) is difficult since the distributions of +% these values are not strictly Gaussian, especially when the +% proportions are near 0 or 1. The Rationalized Arcsine Transform +% linearizes the proportions and converts them to "rational arcsine +% units". The linear tests can then be performed on the RAU units. +% (p=0.5 roughly corresponds to a rau of 50). +% +% RAU(p) computes the rationalized arcsine transform for a proportion +% value p (0 <= p <= 1). p can also be a vector of proportion values. +% +% RAU(p,n) computes the rau-value assuming that the proportion p +% was calculated using n values. A small n tends to flatten the +% RAU values. +% +% Based on: +% Studebaker, G. A. (1985). A 'rationalized' arcsine transform. +% J. Speech Hearing Res. 28, 455-462. +% +% Example 1: +% p = 0:0.1:1; plot(100*p, rau(p), 'o-'); +% +% Example 2 (which reproduces Fig 1 in Studebaker, 1985): +% p = 0:0.01:1; plot(100*p,rau(p)-(100*p)); +% +% Gautam Vallabha, Aug-27-2007, Gautam.Vallabha@mathworks.com + +if exist('n','var') + p = p.*n; % convert to frequency count + t = asin(sqrt(p/(n+1))) + asin(sqrt((p+1)/(n+1))); +else + t = 2 * asin(sqrt(p)); +end + +if any(p < 0 | p > 1) + error('p should be between 0 and 1'); +end + +% Eq. (7) in Studebaker, 1985 +% the advantage of this scaling is that RAU is roughly +% equal to the percentage score from about 20% to 80% +% and only gets scaled beyond that. + +rau_score = (46.47324337*t) - 23; + diff --git a/stat/rep_anovan.m b/stat/rep_anovan.m new file mode 100644 index 0000000..dfd060a --- /dev/null +++ b/stat/rep_anovan.m @@ -0,0 +1,52 @@ +function [p,t,stats,terms] = rep_anovan(tit,y,group,model,sstype,gnames) +% claculates repeated measureas anova and returns both the N- factor anova +% and n-factor repeated measures anova +% subjects should be highest level i.e. first variable in group +[RP,RT,RSTATS,RTERMS]=ANOVAN(y,group,model,sstype,gnames); +[r,c]=size(group); +Rep_T=RT; + +prevjk=0; +for k=1:c + jk=factorial(c) ./(factorial(c-k)*factorial(k)); % nCr r begins from 1 + for i=1:jk + ij=i+prevjk+1; + Rep_T{ij,6}=[]; Rep_T{ij,7}=[]; + end + prevjk=prevjk+jk; +end + + +prevjk=0; +for k=1:c-1 + jk=factorial(c) ./(factorial(c-k)*factorial(k)); % nCr r begins from 1 + rs=factorial(c-1) ./(factorial(c-1-(k-1))*factorial(k-1)); % (n-1)C(r-1) + st=factorial(c-1) ./(factorial(c-1-(k))*factorial(k)); % (n-1)C(r) + for i=1+rs:jk + ij=i+prevjk+1; + Rep_T{ij,6}=RT{ij,5}/RT{ij+st,5}; % col 6 is F, col 5 is Mean Sq + Rep_T{ij,7}=1-fcdf(Rep_T{ij,6},RT{ij,3},RT{ij+st,3}); % col 7 is p val, col 3 is df + end + prevjk=prevjk+jk; +end + + +fprintf('Source\tSum Sq\tdf\tMean Sq\tF val\tp\n'); +prevjk=0; +for k=1:c + jk=factorial(c) ./(factorial(c-k)*factorial(k)); % nCr r begins from 1 + for i=1:jk + ij=i+prevjk+1; + fprintf('%s\t%f\t%d\t%f\t%f\t%f\n',Rep_T{ij,1},Rep_T{ij,2},Rep_T{ij,3}, ... + Rep_T{ij,5},Rep_T{ij,6},Rep_T{ij,7}); + end + prevjk=prevjk+jk; +end + +switch(sstype) + case 1, cap = 'Sequential (Type I) sums of squares.'; + case 2, cap = 'Hierarchical (Type II) sums of squares.'; + otherwise, cap = 'Constrained (Type III) sums of squares.'; +end +digits = [-1 -1 -1 -1 -1 2 4]; +statdisptable(Rep_T, 'N-Way ANOVA', [tit,'rep-ANOVA'], cap, digits); \ No newline at end of file diff --git a/stat/rm_anova2.m b/stat/rm_anova2.m new file mode 100644 index 0000000..997bb7f --- /dev/null +++ b/stat/rm_anova2.m @@ -0,0 +1,136 @@ +function stats = rm_anova2(Y,S,F1,F2,FACTNAMES) +% +% function stats = rm_anova2(Y,S,F1,F2,FACTNAMES) +% +% Two-factor, within-subject repeated measures ANOVA. +% For designs with two within-subject factors. +% +% Parameters: +% Y dependent variable (numeric) in a column vector +% S grouping variable for SUBJECT +% F1 grouping variable for factor #1 +% F2 grouping variable for factor #2 +% F1name name (character array) of factor #1 +% F2name name (character array) of factor #2 +% +% Y should be a 1-d column vector with all of your data (numeric). +% The grouping variables should also be 1-d numeric, each with same +% length as Y. Each entry in each of the grouping vectors indicates the +% level # (or subject #) of the corresponding entry in Y. +% +% Returns: +% stats is a cell array with the usual ANOVA table: +% Source / ss / df / ms / F / p +% +% Notes: +% Program does not do any input validation, so it is up to you to make +% sure that you have passed in the parameters in the correct form: +% +% Y, S, F1, and F2 must be numeric vectors all of the same length. +% +% There must be at least one value in Y for each possible combination +% of S, F1, and F2 (i.e. there must be at least one measurement per +% subject per condition). +% +% If there is more than one measurement per subject X condition, then +% the program will take the mean of those measurements. +% +% Aaron Schurger (2005.02.04) +% Derived from Keppel & Wickens (2004) "Design and Analysis" ch. 18 +% + +stats = cell(4,5); + +F1_lvls = unique(F1); +F2_lvls = unique(F2); +Subjs = unique(S); + +a = length(F1_lvls); % # of levels in factor 1 +b = length(F2_lvls); % # of levels in factor 2 +n = length(Subjs); % # of subjects + +INDS = cell(a,b,n); % this will hold arrays of indices +CELLS = cell(a,b,n); % this will hold the data for each subject X condition +MEANS = zeros(a,b,n); % this will hold the means for each subj X condition + +% Calculate means for each subject X condition. +% Keep data in CELLS, because in future we may want to allow options for +% how to compute the means (e.g. leaving out outliers > 3stdev, etc...). +for i=1:a % F1 + for j=1:b % F2 + for k=1:n % Subjs + INDS{i,j,k} = find(F1==F1_lvls(i) & F2==F2_lvls(j) & S==Subjs(k)); + CELLS{i,j,k} = Y(INDS{i,j,k}); + MEANS(i,j,k) = mean(CELLS{i,j,k}); + end + end +end + +% make tables (see table 18.1, p. 402) +AB = reshape(sum(MEANS,3),a,b); % across subjects +AS = reshape(sum(MEANS,2),a,n); % across factor 2 +BS = reshape(sum(MEANS,1),b,n); % across factor 1 + +A = sum(AB,2); % sum across columns, so result is ax1 column vector +B = sum(AB,1); % sum across rows, so result is 1xb row vector +S = sum(AS,1); % sum across columns, so result is 1xs row vector +T = sum(sum(A)); % could sum either A or B or S, choice is arbitrary + +% degrees of freedom +dfA = a-1; +dfB = b-1; +dfAB = (a-1)*(b-1); +dfS = n-1; +dfAS = (a-1)*(n-1); +dfBS = (b-1)*(n-1); +dfABS = (a-1)*(b-1)*(n-1); + +% bracket terms (expected value) +expA = sum(A.^2)./(b*n); +expB = sum(B.^2)./(a*n); +expAB = sum(sum(AB.^2))./n; +expS = sum(S.^2)./(a*b); +expAS = sum(sum(AS.^2))./b; +expBS = sum(sum(BS.^2))./a; +expY = sum(Y.^2); +expT = T^2 / (a*b*n); + +% sums of squares +ssA = expA - expT; +ssB = expB - expT; +ssAB = expAB - expA - expB + expT; +ssS = expS - expT; +ssAS = expAS - expA - expS + expT; +ssBS = expBS - expB - expS + expT; +ssABS = expY - expAB - expAS - expBS + expA + expB + expS - expT; +ssTot = expY - expT; + +% mean squares +msA = ssA / dfA; +msB = ssB / dfB; +msAB = ssAB / dfAB; +msS = ssS / dfS; +msAS = ssAS / dfAS; +msBS = ssBS / dfBS; +msABS = ssABS / dfABS; + +% f statistic +fA = msA / msAS; +fB = msB / msBS; +fAB = msAB / msABS; + +% p values +pA = 1-f_cdf(fA,dfA,dfAS); +pB = 1-f_cdf(fB,dfB,dfBS); +pAB = 1-f_cdf(fAB,dfAB,dfABS); + +% return values +stats = {'Source','SS','df','MS','F','p';... + FACTNAMES{1}, ssA, dfA, msA, fA, pA;... + FACTNAMES{2}, ssB, dfB, msB, fB, pB;... + [FACTNAMES{1} ' x ' FACTNAMES{2}], ssAB, dfAB, msAB, fAB, pAB;... + [FACTNAMES{1} ' x Subj'], ssAS, dfAS, msAS, [], [];... + [FACTNAMES{1} ' x Subj'], ssBS, dfBS, msBS, [], [];... + [FACTNAMES{1} ' x ' FACTNAMES{2} ' x Subj'], ssABS, dfABS, msABS, [], []}; + + return \ No newline at end of file diff --git a/stat/shapirowilktest.m b/stat/shapirowilktest.m new file mode 100644 index 0000000..b05a37f --- /dev/null +++ b/stat/shapirowilktest.m @@ -0,0 +1,260 @@ +function [H, pValue, W] = swtest(x, alpha, tail) +%SWTEST Shapiro-Wilk parametric hypothesis test of composite normality. +% [H, pValue, SWstatistic] = SWTEST(X, ALPHA, TAIL) performs +% the Shapiro-Wilk test to determine if the null hypothesis of +% composite normality is a reasonable assumption regarding the +% population distribution of a random sample X. The desired significance +% level, ALPHA, is an optional scalar input (default = 0.05). +% TAIL indicates the type of test (default = 1). +% +% The Shapiro-Wilk hypotheses are: +% Null Hypothesis: X is normal with unspecified mean and variance. +% For TAIL = 0 (2-sided test), alternative: X is not normal. +% For TAIL = 1 (1-sided test), alternative: X is upper the normal. +% For TAIL = -1 (1-sided test), alternative: X is lower the normal. +% +% This is an omnibus test, and is generally considered relatively +% powerful against a variety of alternatives. +% Shapiro-Wilk test is better than the Shapiro-Francia test for +% Platykurtic sample. Conversely, Shapiro-Francia test is better than the +% Shapiro-Wilk test for Leptokurtic samples. +% +% When the series 'X' is Leptokurtic, SWTEST performs the Shapiro-Francia +% test, else (series 'X' is Platykurtic) SWTEST performs the +% Shapiro-Wilk test. +% +% [H, pValue, SWstatistic] = SWTEST(X, ALPHA, TAIL) +% +% Inputs: +% X - a vector of deviates from an unknown distribution. The observation +% number must exceed 3 and less than 5000. +% +% Optional inputs: +% ALPHA - The significance level for the test (default = 0.05). +% +% TAIL - The type of the test (default = 1). +% +% Outputs: +% SWstatistic - The test statistic (non normalized). +% +% pValue - is the p-value, or the probability of observing the given +% result by chance given that the null hypothesis is true. Small values +% of pValue cast doubt on the validity of the null hypothesis. +% +% H = 0 => Do not reject the null hypothesis at significance level ALPHA. +% H = 1 => Reject the null hypothesis at significance level ALPHA. +% + +% +% References: Royston P. "Algorithm AS R94", Applied Statistics (1995) Vol. 44, No. 4. +% AS R94 -- calculates Shapiro-Wilk normality test and P-value +% for sample sizes 3 <= n <= 5000. Handles censored or uncensored data. +% Corrects AS 181, which was found to be inaccurate for n > 50. +% + +% +% Ensure the sample data is a VECTOR. +% + +if numel(x) == length(x) + x = x(:); % Ensure a column vector. +else + error(' Input sample ''X'' must be a vector.'); +end + +% +% Remove missing observations indicated by NaN's and check sample size. +% + +x = x(~isnan(x)); + +if length(x) < 3 + error(' Sample vector ''X'' must have at least 3 valid observations.'); +end + +if length(x) > 5000 + warning('Shapiro-Wilk test might be inaccurate due to large sample size ( > 5000).'); +end + +% +% Ensure the significance level, ALPHA, is a +% scalar, and set default if necessary. +% + +if (nargin >= 2) && ~isempty(alpha) + if numel(alpha) > 1 + error(' Significance level ''Alpha'' must be a scalar.'); + end + if (alpha <= 0 || alpha >= 1) + error(' Significance level ''Alpha'' must be between 0 and 1.'); + end +else + alpha = 0.05; +end + +% +% Ensure the type-of-test indicator, TAIL, is a scalar integer from +% the allowable set {-1 , 0 , 1}, and set default if necessary. +% + +if (nargin >= 3) && ~isempty(tail) + if numel(tail) > 1 + error('Type-of-test indicator ''Tail'' must be a scalar.'); + end + if (tail ~= -1) && (tail ~= 0) && (tail ~= 1) + error('Type-of-test indicator ''Tail'' must be -1, 0, or 1.'); + end +else + tail = 1; +end + +% First, calculate the a's for weights as a function of the m's +% See Royston (1995) for details in the approximation. + +x = sort(x); % Sort the vector X in ascending order. +n = length(x); +mtilde = norminv(((1:n)' - 3/8) / (n + 0.25)); +weights = zeros(n,1); % Preallocate the weights. + +if kurtosis(x) > 3 + + % The Shapiro-Francia test is better for leptokurtic samples. + + weights = 1/sqrt(mtilde'*mtilde) * mtilde; + + % + % The Shapiro-Francia statistic W is calculated to avoid excessive rounding + % errors for W close to 1 (a potential problem in very large samples). + % + + W = (weights' * x) ^2 / ((x - mean(x))' * (x - mean(x))); + + nu = log(n); + u1 = log(nu) - nu; + u2 = log(nu) + 2/nu; + mu = -1.2725 + (1.0521 * u1); + sigma = 1.0308 - (0.026758 * u2); + + newSFstatistic = log(1 - W); + + % + % Compute the normalized Shapiro-Francia statistic and its p-value. + % + + NormalSFstatistic = (newSFstatistic - mu) / sigma; + + % the next p-value is for the tail = 1 test. + pValue = 1 - normcdf(NormalSFstatistic, 0, 1); + +else + + % The Shapiro-Wilk test is better for platykurtic samples. + + c = 1/sqrt(mtilde'*mtilde) * mtilde; + u = 1/sqrt(n); + + PolyCoef_1 = [-2.706056 , 4.434685 , -2.071190 , -0.147981 , 0.221157 , c(n)]; + PolyCoef_2 = [-3.582633 , 5.682633 , -1.752461 , -0.293762 , 0.042981 , c(n-1)]; + + PolyCoef_3 = [-0.0006714 , 0.0250540 , -0.39978 , 0.54400]; + PolyCoef_4 = [-0.0020322 , 0.0627670 , -0.77857 , 1.38220]; + PolyCoef_5 = [0.00389150 , -0.083751 , -0.31082 , -1.5861]; + PolyCoef_6 = [0.00303020 , -0.082676 , -0.48030]; + + PolyCoef_7 = [0.459 , -2.273]; + + weights(n) = polyval(PolyCoef_1 , u); + weights(1) = -weights(n); + + % Special attention when n=3 (this is a special case). + if n == 3 + weights(1) = 0.707106781; + weights(n) = -weights(1); + end + + if n >= 6 + weights(n-1) = polyval(PolyCoef_2 , u); + weights(2) = -weights(n-1); + + count = 3; + phi = (mtilde'*mtilde - 2 * mtilde(n)^2 - 2 * mtilde(n-1)^2) / ... + (1 - 2 * weights(n)^2 - 2 * weights(n-1)^2); + else + count = 2; + phi = (mtilde'*mtilde - 2 * mtilde(n)^2) / ... + (1 - 2 * weights(n)^2); + end + + % + % The vector 'WEIGHTS' obtained next corresponds to the same coefficients + % listed by Shapiro-Wilk in their original test for small samples. + % + + weights(count : n-count+1) = mtilde(count : n-count+1) / sqrt(phi); + + % + % The Shapiro-Wilk statistic W is calculated to avoid excessive rounding + % errors for W close to 1 (a potential problem in very large samples). + % + + W = (weights' * x) ^2 / ((x - mean(x))' * (x - mean(x))); + + % + % Calculate the significance level for W (exact for n=3). + % + + newn = log(n); + + if (n > 3) && (n <= 11) + + mu = polyval(PolyCoef_3 , n); + sigma = exp(polyval(PolyCoef_4 , n)); + gam = polyval(PolyCoef_7 , n); + + newSWstatistic = -log(gam-log(1-W)); + + elseif n >= 12 + + mu = polyval(PolyCoef_5 , newn); + sigma = exp(polyval(PolyCoef_6 , newn)); + + newSWstatistic = log(1 - W); + + elseif n == 3 + mu = 0; + sigma = 1; + newSWstatistic = 0; + end + + % + % Compute the normalized Shapiro-Wilk statistic and its p-value. + % + + NormalSWstatistic = (newSWstatistic - mu) / sigma; + + % The next p-value is for the tail = 1 test. + pValue = 1 - normcdf(NormalSWstatistic, 0, 1); + + % Special attention when n=3 (this is a special case). + if n == 3 + pValue = 1.909859 * (asin(sqrt(W)) - 1.047198); + NormalSWstatistic = norminv(pValue, 0, 1); + end + +end + +% The p-value just found is for the tail = 1 test. +if tail == 0 + pValue = 2 * min(pValue, 1-pValue); +elseif tail == -1 + pValue = 1 - pValue; +end + +% +% To maintain consistency with existing Statistics Toolbox hypothesis +% tests, returning 'H = 0' implies that we 'Do not reject the null +% hypothesis at the significance level of alpha' and 'H = 1' implies +% that we 'Reject the null hypothesis at significance level of alpha.' +% + +H = (alpha >= pValue); \ No newline at end of file diff --git a/stat/spearmann.m b/stat/spearmann.m new file mode 100644 index 0000000..5d7654e --- /dev/null +++ b/stat/spearmann.m @@ -0,0 +1,23 @@ +function rho = spearmann(X,Y) +%SPEARMANN - One line description goes here. +% [rho] = spearmann(X,Y) +% +% Example +% >> spearmann +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2009 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2009-10-16 Creation +% +% ----------------------------- Script History --------------------------------- + +d=diff(tiedranks(X)); +rho = 1 - 6*sum(d.^2)./(n*(n.^2 - 1)); diff --git a/stat/ssbar.m b/stat/ssbar.m new file mode 100644 index 0000000..b84ba48 --- /dev/null +++ b/stat/ssbar.m @@ -0,0 +1,78 @@ +function [CI ,fx] = ssbar(X,nf,rp,alpha) +%SSBAR - Statistical Significance Bars +% [CI] = ssbar(X,nf,rp,fx,alpha) +% +% Example +% >> ssbar +% +% See also: +% Reference: +% Statistical significance bars (SSB): A way to make graphs +% more interpretable. Christian D. Schunn (2007) + + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2007 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2007-10-25 Creation +% +% ----------------------------- Script History --------------------------------- + +if nargin<2 + nf=ndims(X)-1 +end +if nargin<3 + rp=1:nf +end +if nargin<4 + alpha=0.05; +end + +ng=size(X); +nr=ng(nf+1); +ng=ng(1:nf); +png=prod(ng); +[p,F,fx,epsilon,df,dfe,SS,SSe,SSt]=myanova(X,nf,rp); +CI=0.*p; +for i=1:length(fx) + %number of cells + k = prod(ng(fx{i})); + % number of + n = nr*png/k; + MSe = SSe(i,:)./dfe(i); + if n<120 + CI(i,:)=1/2*sqrt(MSe / n) * tukeyhsd(n,k,alpha); + else + warning('n=%d & k=%d too big for Tukey''s HSD computation',n,k) + CI(i,:)=NaN; + end +end + +return + + +% Validation data: +% One way ANOVA from Loftus & Masson, 1994 +% Exposure Duration Per Word (sec) +% 1 Sec 2 Sec 5 Sec +X= [ + 10 13 13 + 6 8 8 + 11 14 14 + 22 23 25 + 16 18 20 + 15 17 17 + 1 1 4 + 12 15 17 + 9 12 12 + 8 9 12 + ]'; +% From Schunn, 2000: +[c]=ssbar(X); % [c,fx]=ssbar(X,1,1,0.05) +k=3 ; n = 10 ; MSe = 0.615 +CI = 0.48 diff --git a/stat/stat_bar.m b/stat/stat_bar.m new file mode 100644 index 0000000..0630556 --- /dev/null +++ b/stat/stat_bar.m @@ -0,0 +1,52 @@ +function [s] = stat_bar(X,varargin) +%STAT_BAR - Bar plot with elementary statistics +% [s] = stat_bar(X,dimS,dimG) +% Display a bar plot with elementary statistics computed over +% population in dimS, grouped with dimG +% +% Example +% >> stat_bar +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2006 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2006-03-13 Creation +% +% ----------------------------- Script History --------------------------------- +sX=size(X); +if nargin<2 + dimS=find(sX>1); + sX(dimS)=-sX(dimS); +else + dimS=varargin{1}; +end +if nargin<3 + dimG=find(sX>1); +else + dimG=varargin{2}; +end + +paired=1; +if dimS<0 + paired=0; + dimS=-dimS; +end +X=permute2(X, [dimS dimG]); +s.mX=mean(X,1); +if paired + s.semX=stderrw(X,2,1); + [s.p,s.T]=myttest(X,2,1,'pttest'); +else + s.semX=stderr(X,1); + [s.p,s.T]=myttest(X,2,1,'ttest'); +end +s.mX=shiftdim(s.mX,1); +s.semX=shiftdim(s.semX,1); +s.h=barerrorbar(1:prod(sX)./sX(dimG)./sX(dimS),s.mX(:,:)', s.semX(:,:)') diff --git a/stat/stat_examples.m b/stat/stat_examples.m new file mode 100644 index 0000000..21877fc --- /dev/null +++ b/stat/stat_examples.m @@ -0,0 +1,67 @@ + + + +% http://www.abdn.ac.uk/~psy317/personal/files/teaching/spheric.htm +% http://www.psych.upenn.edu/~baron/rpsych.pdf +MD497= [420, 420, 480, 480, 600, 780, +420, 480, 480, 360, 480, 600, +480, 480, 540, 660, 780, 780, +420, 540, 540, 480, 780, 900, +540, 660, 540, 480, 660, 720, +360, 420, 360, 360, 480, 540, +480, 480, 600, 540, 720, 840, +480, 600, 660, 540, 720, 900, +540, 600, 540, 480, 720, 780, +480, 420, 540, 540, 660, 780]; +% SUBJECT x ANGLE x NOISE +MD497=reshape(MD497(:),10,3,2); + +H518=[ +49,47,46,47,48,47,41,46,43,47,46,45, +48,46,47,45,49,44,44,45,42,45,45,40, +49,46,47,45,49,45,41,43,44,46,45,40, +45,43,44,45,48,46,40,45,40,45,47,40 +]; +H518=H518'; +%SSUBJECT x SHAPE x COLOR +H518=reshape(H518,12,2,2); + +X=H518; + +X=MD497; + +v=cov(mean(X,3)); + +NL=size(X,2); %Nb of levels in factor ANGLE +N=10; % NB of subjects + +epsilonGG=NL^2*(mean(diag(v)) - mean(v(:))).^2/(NL-1)/(sum(v(:).^2) - 2*NL*sum(mean(v).^2) + NL^2*mean(v(:)).^2) +epsilonHF=(N*(NL-1)*epsilonGG-2)/(NL-1)/(N-1-(NL-1)*epsilonGG) + + +return + +% http://www.linguistics.ucla.edu/faciliti/facilities/statistics/fromoac.htm +% +X=[100 90 130 ; 90 100 100 ; 110 110 109 ; 100 90 109 ; 100 100 130]; +X=X'; +[p,F]=myanova(X,1,1) + + + + +return +% http://www.linguistics.ucla.edu/faciliti/facilities/statistics/fromoac.htm +% Example 2 +X=[ + 8 9 8 8 9 7 10 9 10; + 9 10 9 10 9 13 8 9 9; + 8 7 7 12 7 9 10 9 7; + 6 8 9 8 10 10 12 9 10; + 7 6 7 11 12 8 8 11 9]; +X=X; +X=reshape(X,5,3,3); +% COND TRIAL SUBJECT +X=permute(X,[3 2 1]); +[p,F]=myanova(X,2,1:2) + diff --git a/stat/t_cdf.m b/stat/t_cdf.m new file mode 100644 index 0000000..4540a56 --- /dev/null +++ b/stat/t_cdf.m @@ -0,0 +1,106 @@ +function p = spm_Tcdf(x,v) +% Cumulative Distribution Function (CDF) of Students t distribution +% FORMAT p = spm_Tcdf(x,v) +% +% x - T-variate (Student's t has range (-Inf,Inf) +% v - degrees of freedom (v>0, non-integer d.f. accepted) +% p - CDF of Student's t-distribution with v degrees of freedom at points x +%_______________________________________________________________________ +% +% spm_Tcdf implements the Cumulative Distribution of the Students t-distribution. +% +% Definition: +%----------------------------------------------------------------------- +% The CDF F(x) of the Student's t-distribution with v degrees of +% freedom is the probability that a realisation of a t random variable +% X has value less than x; F(x)=Pr{X0. +% +% Variate relationships: (Evans et al., Ch37 & 7) +%----------------------------------------------------------------------- +% The Student's t distribution with 1 degree of freedom is the Standard +% Cauchy distribution, which has a simple closed form CDF. +% +% Algorithm: +%----------------------------------------------------------------------- +% The CDF of the Student's t-distribution with v degrees of freedom +% is related to the incomplete beta function by: +% Pr(|X|0 +% +% See Abramowitz & Stegun, 26.5.27 & 26.7.1; Press et al., Sec6.4 for +% definitions of the incomplete beta function. The relationship is +% easily verified by substituting for v/(v+x^2) in the integral of the +% incomplete beta function. +% +% MatLab's implementation of the incomplete beta function is used. +% +% +% References: +%----------------------------------------------------------------------- +% Evans M, Hastings N, Peacock B (1993) +% "Statistical Distributions" +% 2nd Ed. Wiley, New York +% +% Abramowitz M, Stegun IA, (1964) +% "Handbook of Mathematical Functions" +% US Government Printing Office +% +% Press WH, Teukolsky SA, Vetterling AT, Flannery BP (1992) +% "Numerical Recipes in C" +% Cambridge +% +%_______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% Andrew Holmes +% $Id: spm_Tcdf.m 112 2005-05-04 18:20:52Z john $ + + +%-Format arguments, note & check sizes +%----------------------------------------------------------------------- +if nargin<2, error('Insufficient arguments'), end + +ad = [ndims(x);ndims(v)]; +rd = max(ad); +as = [ [size(x),ones(1,rd-ad(1))];... + [size(v),ones(1,rd-ad(2))] ]; +rs = max(as); +xa = prod(as,2)>1; +if all(xa) & any(diff(as(xa,:))) + error('non-scalar args must match in size'), end + + +%-Computation +%----------------------------------------------------------------------- +%-Initialise result to zeros +p = zeros(rs); + +%-Only defined for strictly positive v. Return NaN if undefined. +md = ( ones(size(x)) & v>0 ); +if any(~md(:)), p(~md) = NaN; + warning('Returning NaN for out of range arguments'), end + +%-Special case: f is 0.5 when x=0 (where betainc involves log of zero) +p( md & x==0 ) = 0.5; + +%-Special case: Standard Cauchy distribution when v=1 +ml = ( md & v==1 ); if xa(1), mlx=ml; else, mlx=1; end +p(ml) = 0.5 + atan(x(mlx))/pi; + +%-Compute where defined & not special cases +Q = find( md & x~=0 & v~=1 ); +if isempty(Q), return, end +if xa(1), Qx=Q; else Qx=1; end +if xa(2), Qv=Q; else Qv=1; end + +%-Compute +xQxPos = x(Qx)>0; +p(Q) = xQxPos -(xQxPos*2-1).*0.5.*betainc(v(Qv)./(v(Qv)+x(Qx).^2),v(Qv)/2,1/2); diff --git a/stat/t_test.m b/stat/t_test.m new file mode 100644 index 0000000..016e0f7 --- /dev/null +++ b/stat/t_test.m @@ -0,0 +1,78 @@ +## Copyright (C) 1995, 1996, 1997 Kurt Hornik +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2, or (at your option) +## any later version. +## +## This program is distributed in the hope that it will be useful, but +## WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +## General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this file. If not, write to the Free Software Foundation, +## 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +## usage: [pval, t, df] = t_test (x, m [, alt]) +## +## For a sample x from a normal distribution with unknown mean and +## variance, perform a t-test of the null hypothesis mean(x) == m. +## Under the null, the test statistic t follows a Student distribution +## with df = length (x) - 1 degrees of freedom. +## +## With the optional argument string alt, the alternative of interest +## can be selected. +## If alt is "!=" or "!=", the null is tested against the two-sided +## alternative mean(x) != m. +## If alt is ">", the one-sided alternative mean(x) > m is considered, +## similarly for "<". +## The default is the two-sided case. +## +## pval is the p-value of the test. +## +## If no output argument is given, the p-value of the test is displayed. + +## Author: KH +## Description: Student's one-sample t test + +function [pval, t, df] = t_test (x, m, alt) + + if ((nargin < 2) || (nargin > 3)) + usage ("[pval, t, df] = t_test (x, m [, alt])"); + endif + + if !(is_vector (x)) + error ("t_test: x must be a vector."); + endif + if !(is_scalar (m)) + error ("t_test: m must be a scalar."); + endif + + n = length (x); + df = n - 1; + t = sqrt (n) * (sum (x) / n - m) / std (x); + cdf = t_cdf (t, df); + + if (nargin == 2) + alt = "!="; + endif + + if !isstr (alt) + error ("t_test: alt must be a string"); + endif + if (strcmp (alt, "!=") || strcmp (alt, "!=")) + pval = 2 * min (cdf, 1 - cdf); + elseif strcmp (alt, ">") + pval = 1 - cdf; + elseif strcmp (alt, "<") + pval = cdf; + else + error (sprintf ("t_test: option %s not recognized", alt)); + endif + + if (nargout == 0) + printf (" pval: %g\n", pval); + endif + +endfunction diff --git a/stat/test.m b/stat/test.m new file mode 100644 index 0000000..c967377 --- /dev/null +++ b/stat/test.m @@ -0,0 +1,35 @@ +function [p, s] = test(x1, x2, type) +%TEST statistical test of two samples. +% TEST(X1, X2, 'test') gives the confidence level for +% different tests of the two samples X1 and X2. +% +% 'test' Null hypothesis Assumption on distributions Type +% ----------------------------------------------------------------- +% 't' Means are equal The variances are equal t-test +% 'u' Means are equal The variances are not equal t-test +% 'p' Means are equal The data are paired t-test +% 'f' Variances are equal F-test +% +% TEST(X1, X2) performs a t-test (for equal variances) by default. +% +% [P S] = TEST(X1, X2, ['test']) returns the confidence level in P +% and the value of the statistic (t or F) in S. +% +% Ref: Press et al. 1992. Numerical recipes in C. 14.2, Cambridge. +% +%See also : TTEST, UTEST, PTEST, FTEST. +if (nargin == 2) + type = 't' ; +end + + +if (type == 't') + [p s] = ttest(x1, x2) ; +elseif (type == 'u') + [p s] = uttest(x1, x2) ; +elseif (type == 'p') + [p s] = pttest(x1, x2) ; +elseif (type == 'f') + [p s] = ftest(x1, x2) ; +end + diff --git a/stat/test_anova.m b/stat/test_anova.m new file mode 100644 index 0000000..b5934c2 --- /dev/null +++ b/stat/test_anova.m @@ -0,0 +1,26 @@ +X=[ 25.4 23.4 20 ; + 26.31 21.8 22.2; + 24.1 23.5 19.75; + 23.74 22.75 20.6; + 25.1 21.6 20.4]'; + +X=[ + 106, 110 , 95, 100 , 94, 107 , 103, 104 , 100, 102; + 110, 112 , 98, 99 , 100, 101 , 108, 112 , 105, 107; + 94, 97, 86, 87 98, 99 99, 101 94, 98]; +X=cat(3,X(:,1:2:end),X(:,2:2:end)) + + + +% http://helios.bto.ed.ac.uk/bto/statistics/tress8.html +X=cat(3, [ +9 18 36; +13 23 27; +18 27 23; +22 20 7], [ +11 20 44; +17 27 33; +22 33 27; +28 24 13]); + +% diff --git a/stat/test_perm.m b/stat/test_perm.m new file mode 100644 index 0000000..3a1b25b --- /dev/null +++ b/stat/test_perm.m @@ -0,0 +1,7 @@ +x=randn(30,2); y=randn(size(x))+[.5+rand(size(x,1),1) zeros(size(x,1),1)]; +for i=1:100 + z(1,1:4,i)=[permttest(x,y,-1,0,100,0)' permttest(x,y,-1,2,100,0)]; +end +for i=1:100 + z(2,1:4,i)=[permttest(x,y,-1,0,1000,0)' permttest(x,y,-1,2,1000,0)]; +end diff --git a/stat/testdata.m b/stat/testdata.m new file mode 100644 index 0000000..278622f --- /dev/null +++ b/stat/testdata.m @@ -0,0 +1,17 @@ +% http://www.gardenersown.co.uk/Education/Lectures/R/nonparam.htm +a = [ + 1 2 1 2004 + 2 48 1 2005 + 3 40 1 2006 + 4 3 2 2004 + 5 120 2 2005 + 6 81 2 2006 + 7 2 3 2004 + 8 16 3 2005 + 9 36 3 2006 + 10 7 4 2004 + 11 21 4 2005 + 12 17 4 2006 + 13 2 5 2004 + 14 14 5 2005 + 15 17 5 2006 ]; diff --git a/stat/ttest.m b/stat/ttest.m new file mode 100644 index 0000000..26c85cc --- /dev/null +++ b/stat/ttest.m @@ -0,0 +1,30 @@ +function [p, t] = ttest(d1, d2) +%TTEST Student's t-test for equal variances. +% TTEST(X1, X2) gives the probability that Student's t +% calculated on data X1 and X2, sampled from distributions +% with the same variance, is higher than observed, i.e. +% the "significance" level. This is used to test whether +% two sample have significantly different means. +% [P, T] = TTEST(X1, X2) gives this probability P and the +% value of Student's t in T. The smaller P is, the more +% significant the difference between the means. +% E.g. if P = 0.05 or 0.01, it is very likely that the +% two sets are sampled from distributions with different +% means. +% +% This works if the samples are drawn from distributions with +% the SAME VARIANCE. Otherwise, use UTTEST. +% +%See also: UTTEST, PTTEST. +[l1 c1] = size(d1) ; +n1 = l1 * c1 ; +x1 = reshape(d1, l1 * c1, 1) ; +[l2 c2] = size(d2) ; +n2 = l2 * c2 ; +x2 = reshape(d2, l2 * c2, 1) ; +[a1 v1] = avevar(x1) ; +[a2 v2] = avevar(x2) ; +df = n1 + n2 - 2 ; +pvar = ((n1 - 1) * v1 + (n2 - 1) * v2) / df ; +t = (a1 - a2) / sqrt( pvar * (1/n1 + 1/n2)) ; +p = betainc( df / (df + t*t), df/2, 0.5) ; diff --git a/stat/ttest_bonf.m b/stat/ttest_bonf.m new file mode 100644 index 0000000..adfe35d --- /dev/null +++ b/stat/ttest_bonf.m @@ -0,0 +1,123 @@ +function [h,p,sigPairs] = ttest_bonf(x,pairs,alpha,tail) +%TTEST_BONF Hypothesis test: Isolate differences in +% Repeated-Measures Analysis of Variance. +% +% [H,P,SIGPAIRS] = TTEST_BONF(X,PAIRS,ALPHA,TAIL) performs a +% set of Bonferroni corrected t-tests to determine which pairs of +% treatments could have the same mean. +% +% X is a matrix of size NUMSUBJECTS rows and NUMTREATMENTS +% columns. +% +% PAIRS is a Nx2 matrix of pair-wise comparisons that should be +% tested. Example: PAIRS=[ 1 2 +% 2 3 +% 3 4 ], +% will perform 3 t-tests. The first will test the means +% of columns 1 and 2 of matrix X. The second tests the means of +% columns 2 and 3 of matrix X. The last tests the means of +% columns 3 and 4 of matrix X. +% PAIRS = nchoosek(1:NUMTREATMENTS, 2) by default. This tests all +% possible pairwise combinations of the columns of X. +% +% +% The null hypothesis is: "means of two treatments are equal". +% For TAIL = 0 the alternative hypothesis is: "means are not equal." +% For TAIL = 1, alternative: "mean of X is greater than mean of Y." +% For TAIL = -1, alternative: "mean of X is less than mean of Y." +% TAIL = 0 by default. +% +% ALPHA is desired experiment-wise significance level (ALPHA = +% 0.05 by default). The Bonferroni criteria effectively divides +% ALPHA by the number of t-tests being performed. +% +% P is the p-value, or the probability of observing the given result +% by chance given that the null hypothesis is true. Small values +% of P cast doubt on the validity of the null hypothesis. +% +% H=0 => "Do not reject null hypothesis at significance level of alpha." +% H=1 => "Reject null hypothesis at significance level of alpha." +% +% SIGPAIRS contains the rows of PAIRS which fail the t-test +% comparison. +% +% References: +% [1] S.A. Glantz, "Primer of BioStatistics", +% McGray-Hill, 1992, pp 309-310. + +% Oct 31, 2003 +% Adapted loosely from MATLAB's ttest2.m function by: +% +% Guy Shechter +% Lab of Cardiac Energetics, NHLBI, NIH, DHHS +% Bethesda, Maryland, USA +% guy at jhu dot edu + + + + if nargin < 1, + error('Requires at least one input arguments'); + end + + [m1 n1] = size(x); + + if nargin < 2, + pairs = nchoosek(1:n1,2); + elseif isempty(pairs) + pairs=nchoosek(1:n1,2); + elseif max(pairs(:))>n1 + error('PAIRS contains an illegal entry'); + end + + if (m1 == 1 & n1 == 1) + error('Requires matrix first inputs.'); + end + + if nargin < 4, + tail = 0; + end + + if nargin < 3, + alpha = 0.05; + end + + if (prod(size(alpha))>1), error('ALPHA must be a scalar.'); end + if (alpha<=0 | alpha>=1), error('ALPHA must be between 0 and 1.'); end + + df_btw_subjects = size(x,1)-1; + df_treatments = size(x,2)-1; + + df_residual = df_btw_subjects*df_treatments; + grandmean = mean(x(:)); + ss_subjects=0; + + for isub=1:m1 + ss_subjects = ss_subjects + sum( ( x(isub,:)-mean(x(isub,:))).^2); + end + ss_treatment = m1*sum( ( mean(x)-grandmean ).^2); + ssres = ss_subjects-ss_treatment; + msres = ssres/df_residual; + + + for q=1:size(pairs,1) + t(q) = ( mean(x(:,pairs(q,1))) - mean( x(:,pairs(q,2))))./ ... + sqrt(2*msres/m1); + end + + % Find the p-value for the tail = 1 test. + p = 1 - tcdf(t,df_residual); + % Adjust the p-value for other null hypotheses. + if (tail == 0) + for q=1:length(p) + p(q) = 2 * min(p(q), 1-p(q)); + end + end + + % Determine if the actual significance exceeds the desired significance + h=(p<= (alpha/size(pairs,1))); + + if (nargout>2), + sigPairs = pairs(h,:); + end + + return diff --git a/stat/tukeyhsd.m b/stat/tukeyhsd.m new file mode 100644 index 0000000..46103f8 --- /dev/null +++ b/stat/tukeyhsd.m @@ -0,0 +1,185 @@ +function [Qc,HSDc,Q,pairs,X] = tukeyhsd(varargin) +%TUKEYHSD - Performs Tukey Honnestly Significant Differences Test +% [Qc] = tukeyhsd(dfe,ng,alpha) +% Qc: critical Q of the Studentized range statistic for the number of +% freedom (df), the number of groups (ng) at the level: p> tukeyhsd +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2006 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2006-02-15 Creation +% KND 2007-10-25 Argument order changed +% +% ----------------------------- Script History --------------------------------- + +dfmax=120; +if nargin==2 + dfe=varargin{1}; + ng=varargin{2}; + alpha = 0.05; +elseif nargin==3 + dfe=varargin{1}; + ng=varargin{2}; + alpha=varargin{3}; +elseif nargin==5 + dfe=varargin{1}; + ng=varargin{2}; + alpha=varargin{3}; + ns=varargin{4}; + MSe=varargin{5}; +else + error + X=varargin{1}; + nf=varargin{2}; + fx=varargin{3}; + alpha=varargin{4}; + dfe=varargin{5}; + SSe=varargin{6}; + sx=size(X); + %number of cells in the effect + ng=prod(sx(fx)); + % number of subjects + ns=prod(sx(setdiff(1:nf+1, fx))); + MSe=SSe/dfe; +end +if dfe > dfmax; + error('Too many d.o.f.') +end +% A little math... +t = sqrt(2)*erfinv(1-alpha); +c=[0.89,0.237,1.214,1.21,1.414]; +t=t+(t^3+t)/dfe/4; +q=c(1)-c(2)*t; +q=q-c(3)/dfe+c(4)*t/dfe; +Qc=t*(q*log(ng-1)+c(5)); +if nargout<=1 + return +end +HSDc=Qc*sqrt(MSe*1/ns); +if nargout<=2 + return +end +X=permute(X,[fx setdiff(1:ndims(X), fx)]); +X=reshape(X,[ng ns nf+2:length(sx) ]); +pairs=nchoosek(1:ng,2)'; +X=reshape(mean(X(pairs(:),:,:),2),size(pairs)); +Q=diff(X)./sqrt(SSe./dfe./ns); +return + + + + +return +% Sample dataset: +% http://web.mst.edu/~psyworld/anovaexample.htm +%Problem: Susan Sound predicts that students will learn most effectively +%with a constant background sound, as opposed to an unpredictable sound or +%no sound at all. She randomly divides twenty-four students into three +%groups of eight. All students study a passage of text for 30 minutes. +%Those in group 1 study with background sound at a constant volume in the +%background. Those in group 2 study with noise that changes volume +%periodically. Those in group 3 study with no sound at all. After studying, +%all students take a 10 point multiple choice test over the material. Their +%scores follow: +% x1 x1^2 x2 x2^2 x3 x3^2 +x=[ + 7 49 5 25 2 4 + 4 16 5 25 4 16 + 6 36 3 9 7 49 + 8 64 4 16 1 1 + 6 36 4 16 2 4 + 6 36 7 49 1 1 + 2 4 2 4 5 25 + 9 81 2 4 5 25 + ]; + +% SStotal = 117.96 +% SSamong = 30.08 +% SSwithin = 117.96 - 30.08 = 87.88 +% according to the F sig/probability table with df = (2,21) F must be at +% least 3.4668 to reach p < .05, so F score is statistically significant) + +x=x(:,1:2:end); +x=transpose(x); +x=[ + 7 4 6 8 6 6 2 9 % 1) constant sound + 5 5 3 4 4 7 2 2 % 2) random sound + 2 4 7 1 2 1 5 5 % 3) no sound + ]; + +% One way between subject epsilon=1 +[p,F,fx,e,df,dfe,SS,SSe,SSt]=myanova(x,1,[],1) +% F=3.5946 -> p=0.0454 +% MSe=SSe/dfe = 4.18 +% Q= diff(mean(x))./sqrt(MSe*1/nr) +% mean(x(1,:))-mean(x(2,:))/.72=-2.7 +% mean(x(1,:))-mean(x(3,:))/.72=4.15 +% mean(x(3,:))-mean(x(2,:))/.72=1.38 +pairs=nchoosek(1:3,2)'; +diff(reshape(mean(x(pairs(:),:),2),[2 3]))./sqrt(SSe/dfe*1/size(x,2)) +qtukey(dfe,3,0.95) +% there is an error on this page! +% Mean of column 3 is 3.375 not 3! +% i wrote to the webmaster + + + +% http://facultyvassar.edu/lowry/ch14pt2.html +x =[ + 27.0000 26.2000 28.8000 33.5000 28.8000 + 22.8000 23.1000 27.7000 27.6000 24.0000 + 21.9000 23.4000 20.1000 27.8000 19.3000 + 23.5000 19.6000 23.7000 20.8000 23.9000 +]; +% Line("groups") means: +% Ma=28.86 Mb=25.04 Mc=22.50 Md=22.30 + +% Sources SS df MS F P +% between groups ("effect") 140.10 3 46.70 6.42 <.01 +% within groups ("error") 116.32 16 7.27 +% TOTAL 256.42 19 +% +% a versus b : +% Q = (28.86—25.04) / sqrt(7.27 / 5) = 3.16 +% For the present example, with k=4 and dfwg=16, you will end up with +% Q[.05] = 4.05 and Q[.01] = 5.2. + +% HSD[.05] = Q[.05] x sqrt(MSwg / Nps) +% = 4.05 * sqrt(7.275 / 5) +% = 4.88 +% HSD[.01] = 6.27 +% M[A-B] = 3.82 ; +% M[A-C] = 6.36 +% M[A-D] = 6.56 etc. +% As you can see, two of the comparisons (A·C and A·D) are significant +% beyond the .01 level, while all the others fail to achieve significance +% even at the basic .05 level + +% eta2 = SSbg / SST = 140.10 / 256.42 = 0.55 diff --git a/stat/tvalue.bak.dll b/stat/tvalue.bak.dll new file mode 100644 index 0000000000000000000000000000000000000000..20a38d2533c35295037d0ab37d087702e4a4a736 GIT binary patch literal 12288 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
      0f@DwxbP)6+=q%_WXbv`$<)AGf1@s{3 z3D9$()1aS%J_Z#)4je)J!(bgq2DO92pkdIXpyxp^gFXa(4q6O9TR{=fjfnkMAm(2Q zUsof>za6ptTAa0R#PVLm@P5SZcOZfeAXaYz$%xIjftnGEw}4s^i?)Hjj#zsWVr(9h zO6+@1eLij?tJvz%C34Q%P~ZzySnZIpe6Ai?Gsb16s~AIQ%^0tpB8K34DKKt_v3aGv z37R!PU_m+w?UR-?-n%-FZsS6)+WZ*e$0YZJR3@-@2LQMRKI-GnxI9dKRrbH)az z^%Ej7K6CW2n(I8NV~b{QdBWFi7*HP5g)_D&u6w^OL*1F%75uSU7h&YNF8VTS^I~~a zm&4x~JDKKhlP;gD2helLYQxXz1-5C-F;6bfgYjNgnKr?h`pcnUz zCAy7mBXRJBvytZD$*+)w@V^O24&DrnOUX(clb7I+N|yaq;3oucO*U{LuNOA6)&&$A zY?cP*-`9A7j|nBqU5uaC@DTIoG(5(J&N5Ecgs?iQ7dErxC9QBkNKWX5&GRAQ6^*aK zWIUSTA}`zur>$Qva!**A)}*|(*&s#HifHmWj8VVMdDuekg`w8n;l^zZ?crea*6niB z4Z6rmUXPPyo0~(yaGTs7Zr&cMHVGG%30rn--&&Ho*Og0y+uDnDEmXg_tZI;3U!q&C zAh*YF7yh(1>@h4>k;m=x`|7>xeEt?Yw-tsIYeMv54qWxG^V_)<#=mB^HBXpl_?n{g zbzVo)<`!VvL*b_8ttElS=h)K1HZ%cV0MS6Dsrf3r(2&AjIQ;uqGyF0(wxs%E-7zJe z`Mu_3Zz_!=D|TO2k#Jb8#T}T}BOOrEe042(Tt1gu!bM$lcPhOf|LYb}yQ7lZ<;A|L zEqs`(#0kdp$KT{UaSO2a_lgCI0ur`Yk$45nBzptQBq5HJ^AKdS{|JD7ed$DJk$K&~XPmee0 zRlR@V{l52Q@9W+Vy*A$x-!k7O-yWah%lMA^zUTXq?^WLg-?Xo~uD@|Be3@{~P{y{2%x~@lX3J>zCFqt9R9JsNYiGUVkrd8dz<71d3G` z!0*UlAND-rdEE1)=NZqlo|B%JJa2gZ(euxq4?O?j`N;E$=TpzmJ<}emcdob6%N4Jr suQ~$8@A#|sYWou2X|X@~P_74U1ew48_&zNFjFUOm(vU0pZ_^0;4-63 +#include "mex.h" + +//-------------------------------------------------------------- +// function: mexFunction - Entry point from Matlab environment +// INPUTS: +// nlhs - number of left hand side arguments (outputs) +// plhs[] - pointer to table where created matrix pointers are +// to be placed +// nrhs - number of right hand side arguments (inputs) +// prhs[] - pointer to table of input matrices +//-------------------------------------------------------------- + +// Compile trough: +// mex -v -g tvalue.c + +void mexFunction( int nlhs, mxArray *plhs[], int nrhs, const mxArray +*prhs[] ) +{ + int K; // Sizes + int k; + double *A, *B; + int nA, nB; + double *T; // Output: T-values + + if (nrhs < 1) + mexErrMsgTxt("Not enough input arguments."); + if (nrhs > 2) + mexErrMsgTxt("Too many input arguments."); + if (!mxIsDouble(prhs[0]) || ((nrhs > 1) && (!mxIsDouble(prhs[1])))) + mexErrMsgTxt("Arguments must be type double."); + K = mxGetN(prhs[0]); // Number of dimensions + if (nrhs > 1) + if (K != mxGetN(prhs[1])) + mexErrMsgTxt("Matrix dimensions (number of columns) must agree."); + if (mxIsComplex(prhs[0]) || ((nrhs > 1) && (mxIsComplex(prhs[1])))) + mexWarnMsgTxt("Complex parts ignored."); + + // edist(MxK,NxK) => MxN + A = mxGetPr(prhs[0]); + nA = mxGetM(prhs[0]); // Number of rows in A + + if (nrhs > 1) { + nB = mxGetM(prhs[1]); // Number of rows in B + B = mxGetPr(prhs[1]); + } + + K = mxGetN(prhs[0]); // Number of columns in output + + plhs[0] = mxCreateDoubleMatrix(1,K,mxREAL); + + // Outputs: + T = mxGetPr(plhs[0]); + + // Now the t-value computation + for (k=0; k 1) { + for (i=0; iRkA2tn=y7rvA;bYH773X^Pn%DF{`1EuhBxp0(Pr|( zhSzsZ*jiuT3SNIQjn$iYyg(&`4KldI8x8*$6Rhzu`>@5Tdh>3u0~(>Y5>uUiTRGYX<|8EI4jYPk&H(ft^IKal)suustiP1IS%Ym>Eqz|Q zTQK}V1<{sjx_y=Th!(%r|PeArR@{+Y)p0> z>H_3yA1Z{_V z-HggRZ~62Xm1vq@R(V&N&yVrT>88AzVARh$X#{;^%y8=1xP!=h4|R?`;VSya9o>(^ zdP7F89`Qh{L;GT}SWHbhFnXv%+o20in5m9&wTXd&6IxWR8(%o%$t>JU{R|a5v_BQ7 z9W%5^dmCB6gBhohM=HtKlyzDq%`eFiu-^e&RFD>f%ueBCx{5PM?ZPVz(KBPZkorwc zfHx!r{?FPYI-duh)7o@C7urD$m1WHX`Lq*4s6t2b+D?i%?jSj>3`lCK<}?FP-A{)$ z6y;4rq)ThjN1W<@It0)~LCyfRM-geyg~?O;SVq3PU7N#w^vEeX8!89+HC@&n>heoe zo+NrAQ@Pn>VL;^zqpvXoSd`gl{z`XlHd;{m+=Y_PVmX#^n$KY(qCEuT^klkwX<^rB z!$&BzU%soz=hDCRWZt>=!j13VnjLE_q^DcPD)yBWi1I5nnQIwq_%T&aCl`y#qSi!% zKY3|0-YUWd8PKHAN+_t)teevYdA4J99QruLN6jp zZ66L5_6{KXlVzck6sk5LHCb-XT)?jzl+u%P}4K3OovORbZ%wk+>cp?;Gm8==vn39zLFN7vjmjK zT|+bC^DDA>%Y>Io5iXj*qQ&@C6V!%qptK>Ic1lMWRbfi2rzRP*c;7O$-<;-`>GJ%c z2gvO8Z1*Mln4ypJ^pTn7=jr%DMV7y$Cg-siwtP>?+is~!Zq8AFeu|&Bsmc7Fqbdhbmxs zw>-!o|)UumuNhTdOS0RuccBvGnbVmT)^3iDy>{EsuSLDKvd;TRAWw=e@@<~xyw=t#@P4janqtv- zL>OV?0ZC4<;XyGb2|+h$3&>J>j3N>lWAODX=sD2y zpdWxTAhTTrf7K#i2cHLRLmXzSq_KlO2|55e2s#WJ0EwUx&?v}k@AaHBBs$V02?1GX z)_1&dfC#C#MMqi$xs8y$2KON1H@J3*^nv?yqI3qype`BqghWYH80iW~Vq0R+kM9e6 zsLV@JNhA@%kA&rRDb_C}666hnS$J_cEX7263_Eas#lZxsc+GZDj&zDg!eWrfq-P)) ziwh#zVe6rNPb@JI3W&joKz?Ey=#Rt_0)E?V(LhB1Vo*zZ`UT8_y=0HWp*%pyJ$5M& zhT3*}LXi6uAUo}e&q~nws68P|{qduOM8bX15qB^Wq2FX&??zNZD5;b~htd;*BnpvQ z&M>S7Vp7vVe{*ZA8-(2o{9hq+C;Wf)Y$;v6r{99;_nR6}18A-NZ&v{8eHa;fYM!nU zJ>T@a;Q68Fr=C|mS3JM--1K~atK>NDXI#8CRXbH%to7CPc}4H%yia+*?0wGrZSRZT zSG*U!m%Z1#H@pS!#`>>SACEZItLh8^x%bnm(a$n=V!9CAS za4&HexnFR<=B{ynHglzaT@JJkzK%!bdj+_uOa_pDH#ZJ7I@&jw5$o8|PL_bD) zN}Ph1%(#DEkx6!kPGMju6lRw0Wa%t~PIi|TFYzSM1_rum8^C7iG-X8Nft03Tc0E?V zbKiT?lg)Mtv$Nfu&Gnr7&i&50=broX?mh3yt@j+G3L>(B3I(E*cybDe{D035L%iVT zA1$C0^IpH@q^0HcTSC!TM(R(e`qGhv)E!ACQ>xUZNa<`+iY29{9YHCP>QP+t=U3L~ zrrTtq7Ry}v*<+bT=QTvVz~3k9EmanajVe)aXo}kw;pqg`in3GiRw9cg7XAyiA31jh ziTfogaXqdep88`iQ5(Yn^g&bP((DAKR~eOvLbIh`8F8t~fQs5tJ2Lnsp1R-CQZ~Da zo80S4_e9i)-ZlW?!vdCwP!IU`@H6L}Ya-nmwq9isI>-r57kiu9SXLM&KmBK&RXzfm} zE>z4GZVq1M!>*B|j6 z$T?nsQG0HiW9WewY?S?%Lg)qi#kYEn&f!`^qmJ;s-#E2%_}^iC=RVp;(MMpqkhSMq zt|ID$aCoBg)D#qkChV+}>vZH~`)2{+V>U4?Pv&Ijusn4{o*ZtR%4Hn6`|a#O*Ad5o zp$CrINZqh&=aK!+Lg=Wi^Ip`RH=?c@a6>DY|D!^oFf?j|bXPEclPK6xEoVELYjmK3 z9a>!W9ldzmJ#z6nwzIDg%>UOk*K_Qv%Kri*1q+6sT0e#c9hU5z{4(y}po74EA8a9u zZaL7ir|@Pv3vZ%puf5_R(KE+xbp6x(eXxcOf&cHkBKRrrm-BZE{&Mstu9%(7--+_m zb`+x30-4I+&WNpqT+UwyWN5VZX$QdEoh+JHP^l>dYPj>##aj+486)5{pxxvd@k8J!~Rv#XmC}bz{JJ<;f-P2C^IW#y)SsNzoKn{jO zvuq6yP8A!gEo7(ii@0_!J2`94K6YK zA2rK{PaXaRsW-6rpe_GbvqafzV0}UK&)DeGW+~u>CZl(mWyA6qJk@Qw&64%HSDlg1vEIcR6jqPRXLEz+a@k7@ z|2jKi`7D>cFlL)ZJysl&&slPV6YB@hJ#Y@?+o1bSc0v=bF+$z~f_`u{lV_(< z_CN!U4t;Fd{jRMBQ`|F^Q z8_bv6XT^Fn%tIodK$qV83C^GK>YDs5Vsh--+>duYo*TTB%bv&RjVYsJ*~iDLr%`qJ zkbDWFJGOrC(!z&YaasV|sBH^WSm9_O=doivO@$Rs6Tig7Uu)v4O}u2{*O>TP6Ti{K z2TXjEiElIUArrsH#CMqZy(YfL#79kh+{E{rc(urLpRc6f%n{&kFZPT>!DXv;AH_Ea ziSIH-_|xQCT~sY;j8PAJ8g1ooH6))T9#^v8k`V}9ZG07%JG>%8bdb{Jpk7iBMB-V+ z)y>6(GO#6^>{er`BsHcI{c&YL>W`$=j1=ojrqW7}i&`UUIyNBn#1cv}1HFv2Dw$1m zDQPLyD|M&h*+eq4T1sRysubxnxV zDfDMi$#HDM#baMIA2 z33~?^PL^L;dH}W_20a0K7W6!51Z1Q)z`tiIe*}ICv=n|AX&L(t+63ALY6o?IdO=Ch z0O&BtNWbnj-=xGqV_J!*N|RXG+KWQuwP4;Cl ziXT|o)%ecj!C10~R0{X@r23U4-DC;#Gn~rwMkC3dxI%wt>FtiEG791uOCk~%H%49x zcPnTGYsuOVgK96)B5OJlgW9#$jG}gBf!u7(+?R&Vt=5d1?(RQC6pwW!23$SyIN!-R zzlc~v(N&f}hiSi(PAYM)N2|CBh(V3*p{AA=7YM5r`2U5BmGHas#8i;e!ncj$iW3De z*Mn+78*!y~HPMaqZR9od81fbL1LU{TkCCsWw~()*g-eK5)2+yFBN_Sa6hXd*4kBMm zFCur*S>$fIh}=VS@X_q0<;d%33vxdx$m{7rlnTSW&6cNr$+W+1pTFd=J!)RZBZDq5u@_?Sl80a{arbO5O>)^c@W3*aQ- z8W)cWtb&=zxTWS*!Qu)_2*#x{wQwTB9Th8wb=Vf~L_~-6TDV|B`X=Z}9no}-0a=NA zeXgbHJPYpHGGqkE@-pN+kU$yo5fJ`;pw#*_koyH9>HhEWs`O$Q!Y>LIFxp7sk1>$$qvTSJFPVPn|w%*hqkba;d_z&!3BA~E=~ricb|gK_7?^=H(k(bSwR8Sg+zGt&A_sZ6Z2Nd*hKVCGPnp_%GA<&DQ6j8SP=tT4G(T zIPL;&Za;<&@j0(KkYNoDhRdO#qUr;}AvT507R(>dcp1OoTL5S&))xgm)DM>j`dlo< z`C(I(AMjE5i^KdtErp9=e!#tsW=8kC#xU6;>EHY!Jm0>#cKx5s({Kwl?qWSo92
      0f@DwxbP)6+=q%_WXbv`$<)AGf1@s{3 z3D9$()1aS%J_Z#)4je)J!(bgq2DO92pkdIXpyxp^gFXa(4q6O9TR{=fjfnkMAm(2Q zUsof>za6ptTAa0R#PVLm@P5SZcOZfeAXaYz$%xIjftnGEw}4s^i?)Hjj#zsWVr(9h zO6+@1eLij?tJvz%C34Q%P~ZzySnZIpe6Ai?Gsb16s~AIQ%^0tpB8K34DKKt_v3aGv z37R!PU_m+w?UR-?-n%-FZsS6)+WZ*e$0YZJR3@-@2LQMRKI-GnxI9dKRrbH)az z^%Ej7K6CW2n(I8NV~b{QdBWFi7*HP5g)_D&u6w^OL*1F%75uSU7h&YNF8VTS^I~~a zm&4x~JDKKhlP;gD2helLYQxXz1-5C-F;6bfgYjNgnKr?h`pcnUz zCAy7mBXRJBvytZD$*+)w@V^O24&DrnOUX(clb7I+N|yaq;3oucO*U{LuNOA6)&&$A zY?cP*-`9A7j|nBqU5uaC@DTIoG(5(J&N5Ecgs?iQ7dErxC9QBkNKWX5&GRAQ6^*aK zWIUSTA}`zur>$Qva!**A)}*|(*&s#HifHmWj8VVMdDuekg`w8n;l^zZ?crea*6niB z4Z6rmUXPPyo0~(yaGTs7Zr&cMHVGG%30rn--&&Ho*Og0y+uDnDEmXg_tZI;3U!q&C zAh*YF7yh(1>@h4>k;m=x`|7>xeEt?Yw-tsIYeMv54qWxG^V_)<#=mB^HBXpl_?n{g zbzVo)<`!VvL*b_8ttElS=h)K1HZ%cV0MS6Dsrf3r(2&AjIQ;uqGyF0(wxs%E-7zJe z`Mu_3Zz_!=D|TO2k#Jb8#T}T}BOOrEe042(Tt1gu!bM$lcPhOf|LYb}yQ7lZ<;A|L zEqs`(#0kdp$KT{UaSO2a_lgCI0ur`Yk$45nBzptQBq5HJ^AKdS{|JD7ed$DJk$K&~XPmee0 zRlR@V{l52Q@9W+Vy*A$x-!k7O-yWah%lMA^zUTXq?^WLg-?Xo~uD@|Be3@{~P{y{2%x~@lX3J>zCFqt9R9JsNYiGUVkrd8dz<71d3G` z!0*UlAND-rdEE1)=NZqlo|B%JJa2gZ(euxq4?O?j`N;E$=TpzmJ<}emcdob6%N4Jr suQ~$8@A#|sYWou2X|X@~P_74U1ew48_&zNFjFUOm(vU0pZ_^0;4-63=T))/length(pdf); +else + mu = + + mT=k*N1/2; %mean + sT=realsqrt(prod(L)/12*(N1-2*B/N/(N^2-1))); %standard deviation + zT=(abs(T-mT)-0.5)/sT; %z-value with correction for continuity + %p=1-normcdf(zT); %p-value + p = 1-(0.5 * erfc(-zT ./ sqrt(2))); +end + + +return + + + +L=[length(x1) length(x2)]; k=min(L); N=sum(L); N1=N+1; %set the basic parameter + +[A,B]=tiedrank([x1(:); x2(:)]); %compute the ranks and the ties +%Compute the Mann-Whitney-Wilcon statistic summing the ranks of the sample with +%the less number of elements. +if L(1)<=L(2) + T=sum(A(1:k)); +else + T=sum(A(k+1:end)); +end + +%There is an alternative formulation of this test that yields a statistic +%commonly denoted by U. U is related to T by the formula U=T-k*(k+1)/2, +%where k is the size of the smaller sample (or either sample if both contain +%the same number of individuals). For a presentation of the U statistic, see: +% S. Siegel and N. J. Castellan, Jr., Nonparametric Statistics for the +% Behavioral Sciences, 2d ed. McGraw-Hill, New York, 1988, Section 6.4, “The +% Wilcoxon-Mann-Whitney U Test.â€? +%For a detailed derivation and discussion of the Mann-Whitney test as developed +%here, as well as its relationship to U, see: +% F. Mosteller and R. Rourke, Sturdy Statistics: Nonparametrics and Order +% Statistics, Addison-Wesley, Reading, MA, 1973, Chapter 3, “Ranking Methods for +% Two Independent Samples.â€? +U=T-k*(k+1)/2; + + +function c = combnk(v,k) +%COMBNK All combinations of the N elements in V taken K at a time. +% C = COMBNK(V,K) produces a matrix, with K columns. Each row of C has +% K of the elements in the vector V. C has N!/K!(N-K)! rows. K must be +% a nonnegative integer. + +% Copyright 1993-2004 The MathWorks, Inc. +% $Revision: 2.12.2.2 $ $Date: 2004/12/24 20:46:48 $ + +[m, n] = size(v); + +if min(m,n) ~= 1 + error('stats:combnk:VectorRequired','First argument has to be a vector.'); +end + +if n == 1 + n = m; + flag = 1; +else + flag = 0; +end + +if n == k + c = v(:).'; +elseif n == k + 1 + tmp = v(:).'; + c = tmp(ones(n,1),:); + c(1:n+1:n*n) = []; + c = reshape(c,n,n-1); +elseif k == 1 + c = v.'; +elseif n < 17 && (k > 3 || n-k < 4) + rows = 2.^(n); + ncycles = rows; + + for count = 1:n + settings = (0:1); + ncycles = ncycles/2; + nreps = rows./(2*ncycles); + settings = settings(ones(1,nreps),:); + settings = settings(:); + settings = settings(:,ones(1,ncycles)); + x(:,n-count+1) = settings(:); + end + + idx = x(sum(x,2) == k,:); + nrows = size(idx,1); + [rows,ignore] = find(idx'); + c = reshape(v(rows),k,nrows).'; +else + P = []; + if flag == 1, + v = v.'; + end + if k < n && k > 1 + for idx = 1:n-k+1 + Q = combnk(v(idx+1:n),k-1); + P = [P; [v(ones(size(Q,1),1),idx) Q]]; + end + end + c = P; +end diff --git a/stat/uttest.m b/stat/uttest.m new file mode 100644 index 0000000..f989423 --- /dev/null +++ b/stat/uttest.m @@ -0,0 +1,30 @@ +function [p, t, df] = uttest(d1, d2) +%UTTEST Student's t-test for unequal variances. +% UTTEST(X1, X2) gives the probability that Student's t +% calculated on data X1 and X2, sampled from distributions +% with different variances, is higher than observed, i.e. +% the "significance" level. This is used to test whether +% two sample have significantly different means. +% [P, T] = UTTEST(X1, X2) gives this probability P and the +% value of Student's t in T. The smaller P is, the more +% significant the difference between the means. +% E.g. if P = 0.05 or 0.01, it is very likely that the +% two sets are sampled from distributions with different +% means. +% +% This works if the samples are drawn from distributions with +% DIFFERENT VARIANCE. Otherwise, use TTEST. +% +%See also: TTEST, PTTEST. +[l1 c1] = size(d1) ; +n1 = l1 * c1 ; +x1 = reshape(d1, l1 * c1, 1) ; +[l2 c2] = size(d2) ; +n2 = l2 * c2 ; +x2 = reshape(d2, l2 * c2, 1) ; +[a1 v1] = avevar(x1) ; +[a2 v2] = avevar(x2) ; +df = (v1 / n1 + v2 / n2) * (v1 / n1 + v2 / n2) / ... + ( (v1 / n1) * (v1 / n1) / (n1 - 1) + (v2 / n2) * (v2 / n2) / (n2 -1) ) ; +t = (a1 - a2) / sqrt( v1 / n1 + v2 / n2 ) ; +p = betainc( df / (df + t*t), df/2, 0.5) ; diff --git a/stat/winsor.m b/stat/winsor.m new file mode 100644 index 0000000..197ace7 --- /dev/null +++ b/stat/winsor.m @@ -0,0 +1,77 @@ +function [X,wtf] = winsor(X,p,dim) +% WINSOR - Winsorize data +% +% [W,wtf] = winsor(X,p) +% Replace p% (or p samples, if p>=1) in X at both ends by the p-th +% quantile values; wtf is a logical array of those elements which have +% been winsorized. +% +% [W] = winsor(X,[p_top p_bottom]) +% Remove possibly asymmetric %/samples at the top and at the bottom +% +% [W] = winsor(X,p,dim) +% Default is to work on dimension 1 +% +% See also: quantile, clipping + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2009 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2009-10-09 Creation +% +% ----------------------------- Script History --------------------------------- + +sX=size(X); +if nargin<2 + error('Missing p argument!'); +end +if nargin<3 + dim=1; + if isvector(X) + [dim,dim]=max(sX); + end +end +N = sX(dim); +if numel(p)<1 || numel(p)>2 + error('p must be a 1- or 2-element'); +end +if numel(p)==1 + p(2)=p; +end +if any(p<1) + do_warn=any(rem(N,1./p)); + p(p<1)=round(N*p(p<1)); + if do_warn + warning('Number of trimmed values rounded to: top=%d ; bottom=%d',p(1),p(2)); + end +end +if sum(p>=N) + error('Can''t trim all the data! Use a smaller p.'); +end +if dim>1 + X=permute(X, [dim setdiff(1:length(sX),dim)]); +end +X=X(:,:); +NC=size(X,2); +[j,j]=sort(X); +j = j+repmat(sX(1)*[0:NC-1],sX(1),1); +% Replace values in the upper tail +X(j(N-p(2)+1:N,:)) = repmat(X(j(N-p(2),:)),p(2),1); +% Replace values in the lower tail +X(j(1:p(1),:)) = repmat(X(j(p(1)+1,:)),p(1),1); +%X=reshape(X,N-p(1)-p(2),NC); +if dim>1 + X=ipermute(X, [dim setdiff(1:length(sX),dim)]); +end +if nargout>1 + wtf=logical(zeros(sX)); + wtf([j(1:p(1),:) j(N-p(2)+1:N,:)])=1; +end +%sX(dim)=N-p(1)-p(2); +X=reshape(X,sX); +return \ No newline at end of file diff --git a/stderr.m b/stderr.m new file mode 100644 index 0000000..2f4313f --- /dev/null +++ b/stderr.m @@ -0,0 +1,40 @@ +function y = stderr(x,dim,ci) +%stderr - Standard error (of the sample mean). +% +% y = stderr(x) +% For vectors, STDERR(X) returns the standard error around the mean. For +% ND arrays, it is computed for the first non-singleton dimension. +% The standard error is the (unbiased) standard deviation divided by the +% size of the sample: SE=SD/sqrt(N) +% +% STDERR(X,DIM) computes std err on the given dimension. +% +% See also: std + +% NOT YET: +% STDERR(X,DIM,P) computes confidence interval at P percent + +if nargin<2, + if isempty(x), y = 0/0; return; end % Empty case without dim argument + dim = min(find(size(x)~=1)); + if isempty(dim), dim = 1; end +end +if nargin<3, ci = []; end + +n=size(x,dim); + +% Avoid dividing by zero for scalar case +if n==1, y = zeros(size(x)); y(isnan(x))=NaN; return, end + +tile = ones(1,max(ndims(x),dim)); +tile(dim) = n; + +xc = x - repmat(sum(x,dim)/n,tile); % Remove mean +y = sum(conj(xc).*xc,dim)/(n-1)/n; +y=sqrt(y); +return + +% NOT YET +if not(isempty(ci)) + ci=tcdf(1); +end \ No newline at end of file diff --git a/stderr_within.m b/stderr_within.m new file mode 100644 index 0000000..ef8c4ad --- /dev/null +++ b/stderr_within.m @@ -0,0 +1,36 @@ +function y = stderr(x,dim,ci) +%stderr - Standard error (of the sample mean). +% For vectors, STDERR(X) returns the standard error around the mean. For +% ND arrays, it is computed for the first non-singleton dimension. +% The standard error is the (unbiased) standard deviation divided by the +% size of the sample: SE=SD/sqrt(N) +% +% STDERR(X,DIM) computes std err on the given dimension. + +% NOT YET: +% STDERR(X,DIM,P) computes confidence interval at P percent + +if nargin<2, + if isempty(x), y = 0/0; return; end % Empty case without dim argument + dim = min(find(size(x)~=1)); + if isempty(dim), dim = 1; end +end +if nargin<3, ci = []; end + +n=size(x,dim); + +% Avoid dividing by zero for scalar case +if n==1, y = zeros(size(x)); y(isnan(x))=NaN; return, end + +tile = ones(1,max(ndims(x),dim)); +tile(dim) = n; + +xc = x - repmat(sum(x,dim)/n,tile); % Remove mean +y = sum(conj(xc).*xc,dim)/(n-1)/n; +y=sqrt(y); +return + +% NOT YET +if not(isempty(ci)) + ci=tcdf(1); +end \ No newline at end of file diff --git a/stderrw.m b/stderrw.m new file mode 100644 index 0000000..91f0852 --- /dev/null +++ b/stderrw.m @@ -0,0 +1,45 @@ +function Y = stderrw(X,dimF,dimS) +%stderrw - Standard error for repated measure +% +% Y=stderrw(X,dimF) +% Y=stderrw(X,dimF,dimS) +% +% Computes standard error (of the sample mean) in fully repeated design. +% dimF indicates the dimension(s) of repeated factors/treatment +% dimS indicates the observations/subjects dimension +% (default: the first non-singleton dimension beside factors) +% +% Example: +% 5 different measures in 2 tasks by 3 condition for 10 subjects in a +% within subject design giving the data matrix X such as: +% size(X)==[ 2 3 5 10 ] +% >> Y=stderrw(X,1:2,4); +% Y is thus of size [2 3 5 1] + +sX=size(X); +ndX=ndims(X); +if nargin<3 + sY=sX; + sY(dimF)=0; + dimS = min(find(sY>1)); + if isempty(dimS), error('Not enough '); end +end +sY=sX; +sY(dimS)=1; +Y=zeros(sY); +sF=sX(dimF); +NL=prod(sX(dimF)); +NS=sX(dimS); +NX=[sX(setdiff(1:ndX,[dimS dimF])) 1]; + +% for each subject center X and compute stderr +pX=[dimF dimS setdiff(1:ndX,[dimS dimF])]; +X=permute(X,pX); +X=reshape(X,[NL,NS,NX]); +X=X-repmat(mean(X,1),[NL 1]); +Y=stderr(X,2); +Y=reshape(Y,[sX(dimF),1,NX]); +Y=ipermute(Y,pX); +return + + diff --git a/stdnan.m b/stdnan.m new file mode 100644 index 0000000..2090869 --- /dev/null +++ b/stdnan.m @@ -0,0 +1,82 @@ +function [y]=stdnan(x,flag,dim) +%stdnan - Standard deviation once NaN values have been removed +% +% see std() for details + +% For vectors, MEAN(X) is the mean value of the elements in X. For +% matrices, MEAN(X) is a row vector containing the mean value of +% each column. For N-D arrays, MEAN(X) is the mean value of the +% elements along the first non-singleton dimension of X. +% +% MEAN(X,DIM) takes the mean along the dimension DIM of X. +% +% Example: If X = [0 1 2 +% 3 4 5] +% +% then mean(X,1) is [1.5 2.5 3.5] and mean(X,2) is [1 +% 4] +% +% See also MEDIAN, STD, MIN, MAX, COV. + + +% Copyright 1984-2002 The MathWorks, Inc. +% $Revision: 5.17 $ $Date: 2002/06/05 17:06:39 $ + +if nargin < 2 + flag=0; +end +if nargin < 3 + dim=1; +end +x=permute(x, [dim 1:dim-1 dim+1:ndims(x)]); + +if size(x,dim)==1, + y = zeros(size(x)); + y(isnan(x))=NaN; + return +end + + +% tile = ones(1,max(ndims(x),dim)); + +t1 = [ size(x, 1) 1 ]; +t2 = [ 1 size(x, 2) ]; + +xn = x; +nn=sum(isnan(x)); +xn(isnan(xn))=0; +sx=size(x,1)-nn; + +xc = x - repmat(sum(xn),t1)./repmat((sx),t1); % Remove mean +xc(isnan(xc))=0; + +if flag, + y = sqrt(sum(conj(xn).*xc)./(sx)); +else + y = sqrt(sum(conj(xc).*xc)./(sx-1)); +end +if dim>1 + y=permute(y, [2:dim 1 dim+1:ndims(x)]); +end + +return +sx=size(x); + + + +if 0 + % Determine which dimension SUM will use + if ndims(x)>1 + error('stdnan: You have to specifiy the dimension on which stdnan operate!') + end + % dim = min(find(size(x)~=1)); + % if isempty(dim), dim = 1; end + x(find(isnan(x)))=[]; + y = sum(x)/size(x); +else + k=find(isnan(x)); + kk=zeros(size(x)); + kk(k)=1; + x(k)=0; + y = sum(x,dim)./(size(x,dim)-sum(kk,dim)); +end diff --git a/stdw.m b/stdw.m new file mode 100644 index 0000000..cda0a7c --- /dev/null +++ b/stdw.m @@ -0,0 +1,60 @@ +function Y = stdw(X,dimF,dimS) +%STDW - Standard deviation for repated measure +% +% Y=stdw(X,dimF) +% Y=stdw(X,dimF,dimS) +% Computes unbiased standard deviation in fully repeated design. +% dimF indicates the dimension(s) of repeated factors/treatment +% dimS indicates the observations/subjects dimension +% (default: the first non-singleton dimension beside factors) +% +% Example: +% 5 different measures in 2 tasks by 3 condition for 10 subjects in a +% within subject design giving the data matrix X such as: +% size(X)==[ 2 3 5 10 ] +% >> Y=stdw(X,1:2,4); +% Y is thus of size [2 3 5 1] +% +% See also: stderrw, std, stderr + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2006 +% This program isfree software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2006-01-05 Creation +% +% ----------------------------- Script History --------------------------------- + + +sX=size(X); +ndX=ndims(X); +if nargin<3 + sY=sX; + sY(dimF)=0; + dimS = min(find(sY>1)); + if isempty(dimS), error('Not enough '); end +end +sY=sX; +sY(dimS)=1; +Y=zeros(sY); +sF=sX(dimF); +NL=prod(sX(dimF)); +NS=sX(dimS); +NX=[sX(setdiff(1:ndX,[dimS dimF])) 1]; + +% for each subject center X and compute stderr +pX=[dimF dimS setdiff(1:ndX,[dimS dimF])]; +X=permute(X,pX); +X=reshape(X,[NL,NS,NX]); +X=X-repmat(mean(X,1),[NL 1]); +Y=std(X,[],2); +Y=reshape(Y,[sX(dimF),1,NX]); +Y=ipermute(Y,pX); +return + + + diff --git a/strcell.m b/strcell.m new file mode 100644 index 0000000..fc6246e --- /dev/null +++ b/strcell.m @@ -0,0 +1,22 @@ +function t = strcell(s) +%strcell() - Create cell array of strings from character array. +% [T] = STRCELL(S) converts S into a cell array T where every cell +% contains a character array: +% * char array are unmodified +% * empty elements or empty arrays are converted to '' +% * numbers are converted with num2str +% * non convertible elements (cell array, etc) are converted to '' +% +% See also STRINGS, CHAR, ISCELLSTR, CELLSTR. + +res = cellfun('isclass',s,'char'); +if all(res(:)); t=s; return; end +t = cell(size(s)); +t(res)=s(res); +for i=find(~res(:)') + try + t{i}=num2str(s{i}); + catch + t{i}=''; + end +end \ No newline at end of file diff --git a/strfindobj.m b/strfindobj.m new file mode 100644 index 0000000..a92db86 --- /dev/null +++ b/strfindobj.m @@ -0,0 +1,25 @@ +function [h]=strfindobj(varargin) +%strfindobj - Find obj whose property match with a given string +% h=strfindobj(S,H,P) +% String,Handles[gca],Property[Tag], + +S=varargin{1}; +switch nargin + case 1 + P='Tag'; + H=findobj(gca); + case 2 + H=varargin{2}; + P='Tag'; + case 3 + H=varargin{2}; + P=varargin{2}; +end + +r=get(H,P); +h=[]; +for i=1:length(r) + if ~isempty(strfind(lower(r{i}),lower(S))) + h=[h;H(i)]; + end +end diff --git a/strongconn.m b/strongconn.m new file mode 100644 index 0000000..b5a5ba2 --- /dev/null +++ b/strongconn.m @@ -0,0 +1,73 @@ +function [M,C] = strongconn(A,tol) +%STRONGCONN - Find strongly connected components from a graph +% [M,C] = strongconn(G) +% Finds the strongly connected sets of vertices +% in the (directed) graph matrix G +% A = 0-1 matrix displaying accessibility +% C = displays the equivalent classes +% +% See also: dmperm +% Web: http://www.nist.gov/dads/HTML/stronglyConnectedCompo.html + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2006 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2006-01-11 Creation +% +% ----------------------------- Script History + + + + + + +function [c,v] = dig(a,tol) +[m,n] = size(G); +if m~=n 'Not a Square Matrix', break, end +b=abs(a); o=ones(size(a)); x=zeros(1,n); +msg='The Matrix is Irreducible !'; v='Connected Directed Graph !'; +if (nargin==1) tol=n*eps*norm(a,'inf'); end + +% Create a companion matrix +b>tol*o; c=ans; if (c==o) msg, break, end + +% Compute accessibility in at most n-step paths +for k=1:n + for j=1:n + for i=1:n + % If index i accesses j, where can you go ? + if c(i,j) > 0 c(i,:) = c(i,:)+c(j,:); end + end + end +end +% Create a 0-1 matrix with the above information +c>zeros(size(a)); c=ans; if (c==o) msg, break, end + +% Identify equivalence classes +d=c.*c'+eye(size(a)); d>zeros(size(a)); d=ans; +v=zeros(size(a)); +for i=1:n find(d(i,:)); ans(n)=0; v(i,:)=ans; end + +% Eliminate displaying of identical rows +i=1; +while(i0 + h(j,:)=v(i,:); + j=j+1; + end +end +v=h; diff --git a/struct2cell2.m b/struct2cell2.m new file mode 100644 index 0000000..c9443e2 --- /dev/null +++ b/struct2cell2.m @@ -0,0 +1,12 @@ +%STRUCT2CELL2 - Recursive conversion of structure array to cell array. +% C = STRUCT2CELL2(S) converts the M-by-N structure S (with P fields) +% into a P-by-M-by-N cell array C. +% +% If S is N-D, C will have size [P SIZE(S)]. +% +% Example: +% clear s, s.category = 'tree'; s.height = 37.4; s.name = 'birch'; +% c = struct2cell(s); f = fieldnames(s); +% +% See also STRUCT2CELL, struct2list + diff --git a/struct2fv.m b/struct2fv.m new file mode 100644 index 0000000..f34a5ef --- /dev/null +++ b/struct2fv.m @@ -0,0 +1,18 @@ +function [fv]=struct2fv(s) +% struct2fv - Keep only the faces and vertices fields from a structure +% fv=struct2fv(s) +% will remove extra fields of s to keep only the "Vertices" and "Faces" ones +% Note: Upper case/lower case discrepancies are ignored. + +fv=[]; +for f={'faces', 'vertices'} + if isfield(s, f{1}) + fv=setfield(fv, f{1}, getfield(s, f{1})); + elseif isfield(s, lower(f{1})) + fv=setfield(fv, f{1}, getfield(s, lower(f{1}))); + elseif isfield(s, [upper(f{1}(1)) upper(f{1}(2:end))]) + fv=setfield(fv, f{1}, getfield(s, [upper(f{1}(1)) upper(f{1}(2:end))])); + else + warning(sprintf('No field %f in the structure', f{1})) + end +end \ No newline at end of file diff --git a/struct2list.m b/struct2list.m new file mode 100644 index 0000000..1865892 --- /dev/null +++ b/struct2list.m @@ -0,0 +1,81 @@ +function [C]=struct2list(S,depth,flat) +%struct2list - List each field and its content in an vertical cell array +% +% [C]=struct2list(S) +% Lists each fieldname and its content into a 2-column cell array. This +% is useful to pass structures as argument to function according to the +% scheme: +% function('name', value, 'name', value, ...) +% +% [C]=struct2list(S,depth) also expand the subfields which are +% themselves struct's. Down to a given depth. Use depth=Inf to explore +% the full arborescence. Default: depth=0 (i.e. no recursion) +% +% [C]=struct2list(S,depth,flat) if flat=1 (default) the structure is +% 'flattened' i.e. the field names in the first column are of the form: +% 'field.subfield' . +% +% If S is an array of struct's, C will be (N-by-2)-by-size(S) +% +% Example +% >> struct2list(ver,Inf,1) +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2009 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2009-04-03 Creation +% +% ----------------------------- Script History --------------------------------- +if nargin<1 + error('No input!'); +end +if ~isstruct(S) + error('S should be a struct or a struct array!'); +end + +if nargin<2 + depth=0; +end +if nargin<3 + flat=1; +end +C=[]; +nS = numel(S); +f=fieldnames(S); +nf=numel(f); +for i_S=1:nS + D=[]; + c=struct2cell(S(i_S)); + for i=1:nf + fi = f(i); + x = c(i); + if depth + if isstruct(x{1}) + if flat + x = struct2list(x{1}, depth-1); + nsub=size(x,1); + if isempty(x) + fi = { fi{1} }; + x={[]}; + else + fi = [repmat([ fi{1} '.' ],nsub,1) strvcat(x(:,1))]; + fi = cellstr(fi); + x=x(:,2); + end + else + x = {struct2list(x{1}, depth-1)}; + end + end + end + D = [ D ; [fi x]]; + end + C=cat(3,C,D); +end +C=reshape(C, [ size(C,1) size(C,2) size(S)]); diff --git a/struct2minf.m b/struct2minf.m new file mode 100644 index 0000000..8724018 --- /dev/null +++ b/struct2minf.m @@ -0,0 +1,63 @@ +function [varargout]=struct2minf(x,minfname) +% struct2minf - converts a matlab struct into Brainvisa minf info +% +% [s]=struct2minf(x,minfname) +% +% Input: +% x : matlab structure +% [optional] fname: filename for output (e.g. 'toto.tex.minf') +% +% Output: +% [optional] s : character array + + +s=['attributes = {\n' ]; +ff=fieldnames(x); +nff=length(ff); +for iff=1:nff + f=getfield(x,ff{iff}); + s=[ s ' ''' ff{iff} ''' : ']; + if iscell(f) + s= [ s '[' ]; + for i=1:length(f) + if not(ischar(f{i})) + error(sprintf('%s: cannot print non character cell array!', mfilename)); + end + s=[ s '"' f{i} '" ' ]; + end + s= [ s ']' ]; + elseif ischar(f) + s=[ s '''' f '''']; + elseif isnumeric(f) + if length(f)>1 + s= [ s '[ ' ]; + for i=1:length(f) + s=[ s '''' num2str(f(i)) '''' ]; + if i1 + fid=fopen(minfname, 'wt'); + fprintf(fid,s); + fclose(fid); +end +if nargout>0 + varargout(1)={s}; +end +if nargout == 0 & nargin==1 + fprintf('%s\n', s) +end +return +% attributes = {'MinT': ['-0.256000'], 'MaxT': ['0.752000'], 'Trial': ['1']} diff --git a/struct2tab.m b/struct2tab.m new file mode 100644 index 0000000..efec51e --- /dev/null +++ b/struct2tab.m @@ -0,0 +1,155 @@ +function [s] = struct2tab(x,varargin) +% struct2tab - Print fields of structure into a text table +% [s] = struct2tab(x) +% [s] = struct2tab(x, 'headers', ['no']|'yes'|'only') + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2009 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2009-03-12 Creation +% +% ----------------------------- Script History --------------------------------- + + +% Not implemented yet: +% [s] = struct2tab(x, 'format', ['%30.5']|'no'|'only') + +options = parser(varargin{:}); + +headers=options.headers; +superfield=options.superfield; + +if headers == 1 + s=[struct2tab(x,options,'headers','only');struct2tab(x,options,'headers','no')]; + return +end + +s=[]; +fn = fieldnames(x); +for f=fn(:)' + if ~headers + if numel(x)>1 + for i=1:numel(x) + s=[s;struct2tab(x(i), varargin{:})]; + end + return + end + end + y=getfield(x(1), f{1}); + if iscell(y) + y=y{1} + end + if isstruct(y) + if headers + if ~isempty(superfield) + superfield = [superfield '.' ]; + end + t=struct2tab(y,'headers',[superfield f{1}]); + else + t=struct2tab(y,'headers','no'); + end + s=[repmat(s,size(t,1)) t]; + else + if headers + y = [f{1}]; + if ~isempty(superfield) + y = [superfield '.' y]; + end + y=y(max(1,end-29):end); + end + if isempty(y) + t = sprintf('%30.5f',0); + t = strrep(t, '0', ' '); + t = strrep(t, '.', ' '); + elseif isnumeric(y) + if numel(y)>1 + %t=[]; + %for i=1:numel(y) + %t = [t;sprintf('%30.5f',y(i));]; + %end + t = sprintf('%30s','...'); + elseif fix(y)==y + t = sprintf('%30d',y); + else + t = sprintf('%30.5f',y); + end + elseif ischar(y) + t = sprintf('% 30s',y); + elseif islogical(y) + if y + t = sprintf('% 30s','0'); + else + t = sprintf('% 30s','1'); + end + else + t = sprintf('% 30s','N/A'); + end + + + % s_fill = repmat(' ', max(0,size(t,1)-size(s,1)), size(s,2)); + % % Keeps tabs in filling + % s_fill(:,all(s==9))=9; + % t_fill = repmat(' ', max(0,size(s,1)-size(t,1)), size(t,2)) + s_fill = ''; + t_fill = ''; + s=[[s; s_fill] [t; t_fill]]; + s = [s repmat(sprintf('\t'), size(s,1),1)] ; + end +end + + +function options = parser(varargin) +% Parse options + +% default values +options.headers = 1; +options.superfield = []; +options.fieldwidth = 30; +options.fieldformat.char = []; +options.fieldformat.logical = []; +options.fieldformat.numeric = []; +options.arrays = '...'; +if nargin==0 + return +end +if isstruct(varargin{1}) + options = mergestruct(options,varargin{1}); + varargin(1)=[]; +end +na=length(varargin); +if mod(na,2) + error('Options must be specified in pairs: ''option'', value'); +end +na=na/2; +for i=1:na + va=varargin{2*i}; + switch lower(varargin{2*i-1}) + case 'headers' + if isequal(va,'yes') + options.headers = 1; + elseif isequal(va,'only') + options.headers = 2; + elseif isequal(va,'no') + options.headers = 0; + options.superfield = []; + else + options.headers = 2; + options.superfield = va; + end + case 'superfield' + options.superfield=va; + end +end + + +function s3 = mergestruct(s1,s2) +s3=s1; +f=fieldnames(s2); +for i=1:length(f) + s3=setfield(s3, f{i}, getfield(s2,f{i})); +end diff --git a/structcomparator.m b/structcomparator.m new file mode 100644 index 0000000..b69029c --- /dev/null +++ b/structcomparator.m @@ -0,0 +1,56 @@ +function h = structcomparator(A,B,varargin) +%STRUCTCOMPARATOR - One line description goes here. +% [] = structcomparator(input) +% +% Example +% >> structcomparator +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2009 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2009-07-28 Creation +% +% ----------------------------- Script History --------------------------------- +F=structdiff(A,B,varargin{:}); +for i=1:size(F,1) + a=[]; + b=[]; + if ~isempty(F{i,1}) + a=getfield2(A,F{i,1}); + fprintf('%s',F{i,1}) + end + fprintf(' | '); + if ~isempty(F{i,2}) + b=getfield2(B,F{i,2}); + fprintf('%s',F{i,2}) + end + fprintf('\n') + if isstruct(a) | isstruct(b) + disp(a) + disp(b) + elseif isequal(size(a,1),size(b,1)) + if isnumeric(a) & isnumeric(b) + fmt='%g '; + elseif ischar(a) & ischar(b) + fmt='%s '; + else + fmt='...'; + end + for i_row=1:size(a,1) + ta=sprintf(fmt, a(i_row,:)); + tb=sprintf(fmt, b(i_row,:)); + fprintf('%s | %s\n', ta, tb); + end + else + disp(a) + disp(b) + end + fprintf('\n'); +end \ No newline at end of file diff --git a/structdiff.m b/structdiff.m new file mode 100644 index 0000000..53f365b --- /dev/null +++ b/structdiff.m @@ -0,0 +1,139 @@ +function [varargout] = structdiff(X,Y,recursive,output) +%STRUCTDIFF - Compare structures and output differences +% [C] = structdiff(X,Y) list the fields that differ in a N-by-2 cell +% array. Fileds that are in X but not in Y appear only in the first +% column; fields of Y absent in X appear in the 2nd column; and fields +% that differ between X and Y appear in both columns +% +% Example +% >> structdiff(get(figure('name','a')),get(figure('name','b'))) +% +% [C] = structdiff(X,Y,r) +% if r== 0 (default) not recursive +% if r==Inf -> recursive +% if r==N -> max depth of recursion +% +% See also: getfield2 + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2008 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2008-07-17 Creation +% KND 2008-10-28 Changed format of output +% +% ----------------------------- Script History --------------------------------- +if nargin<3 + recursive=0; +end +if ~iscell(recursive) + recursive={recursive ''}; + %Internally, the 'recursive' argument is embedded in a cell, so as to + %pass the prefix (i.e. the "super.field") as the second element of it. +end +if length(X)>1 | length(Y)>1 + error('Cannot deal with struct arrays') +end + +x=fieldnames(X); +y=fieldnames(Y); +C=setdiff(x,y); +C=C(:); +n=length(C); +for i=1:n + C{i,1}=[recursive{2} C{i,1}]; + C{i,2}=[]; +end +A=setdiff(y,x); +for i=1:length(A) + C{n+i,1}=[]; + C{n+i,2}=[recursive{2} A{i}]; +end +clear A +%common fields +f=intersect(x,y); +for i=1:length(f) + x=getfield(X,f{i}); + y=getfield(Y,f{i}); + if recursive{1} && isstruct(x) && isstruct(y) + recursive{1} = recursive{1} - 1; + if length(x)== 1 && length(y)==1 + C=[C; structdiff(x,y,{1 [recursive{2} f{i} '.']})]; + elseif length(x)~= length(y) + C=[C; [recursive{2} f{i}]]; + elseif ~isequalwithequalnans(x,y) + for j=1:length(x) + if ~isequal(x(j),y(j)) + C=[C; structdiff(x(j),y(j),{recursive{1} ,[recursive{2} f{i} sprintf('(%d)', j) '.']})]; + end + end + end + else + if ~isequalwithequalnans(x,y) + C=[C; {[recursive{2} f{i}] [recursive{2} f{i}]}]; + end + end +end +if nargin>3 + for i=1:size(C,1) + a=[]; + b=[]; + if ~isempty(C{i,1}) + a=getfield2(X,C{i,1}); + fprintf('%s',C{i,1}) + end + fprintf(' | '); + if ~isempty(C{i,2}) + b=getfield2(Y,C{i,2}); + fprintf('%s',C{i,2}) + end + fprintf('\n') + if isstruct(a) | isstruct(b) + disp(a) + disp(b) + elseif isequal(size(a,1),size(b,1)) + if isnumeric(a) & isnumeric(b) %& numel(a)<100 & numel(b)<100 + fmt='%g '; + elseif ischar(a) & ischar(b) + fmt='%s '; + elseif ischar(a) & ischar(b) + + else + a=' ';b=' '; + fmt='...'; + end + for i_row=1:size(a,1) + ta=sprintf(fmt, a(i_row,:)); + tb=sprintf(fmt, b(i_row,:)); + fprintf('%s | %s\n', ta, tb); + end + else + disp(a) + disp(b) + end + fprintf('\n'); + end +end + +if nargout>1 + varargout = {C}; +else + n=size(C,1); + D=cell(n,3); + disp(C) + for j=1:n + D(j,1) = unique(C(j,~cellfun('isempty', C(j,:)))'); + if ~isempty(C{j,1}) + getfield(X, D{j,1}) + D{j,2} = getfield(X, D{j,1}); + end + if ~isempty(C{j,2}) + D{j,3} = getfield(Y, D{j,1}); + end + end + varargout = {D}; +end diff --git a/structmatch.m b/structmatch.m new file mode 100644 index 0000000..8a7de3e --- /dev/null +++ b/structmatch.m @@ -0,0 +1,143 @@ +function [V,I,MK] = structmatch(D,H,K,W) +%STRUCTMATCH - Generic lookup function for structure arrays +% +% [VALUES] = structmatch(DATA,HASHKEY,KEY) will retrieve all elements +% from the structure DATA whose field HASHKEY match the value(s) in KEY +% (according to regexp). HASKEY can also be the index in the fieldname list. +% +% [VALUES] = structmatch(DATA,HASHKEY) will retrieve all elements from +% the structure DATA whose field HASHKEY is true (will be converted to +% logical if needed) +% +% [VALUES,INDICES] = structmatch(...) also outputs the logical indices of +% the matching elements from the structure +% +% [...] = structmatch(DATA,HASHKEY,KEY,WHAT) will only retrieve from +% the matching elements, the field indexed by WHAT +% If WHAT == NaN (default), retrieves all data +% If WHAT == 0, retrieves logical indices, see just below. +% If WHAT == N, retrieves indices of N>0 first matches. +% If WHAT == Inf, retrieves indices of all matches. +% +% [TF] = structmatch(DATA,HASHKEY,KEY,0) will only retrieve logicals +% of the same size as DATA with 1's where elements match the KEY +% +% [...] = structmatch(DATA,HASKEY,FUN) uses function handle FUN to make +% the match (the result FUN is converted into a logical). +% +% [VALUES,INDICES,MATCHINGKEYS] = structmatch(...) will also output the +% names of the fields used as hashkeys (e.g., if HASHKEY was input as the +% numerical index of the field in the structure) +% +% Examples +% >> structmatch(dir(pwd),'bytes',@(X)X>10000,'name') +% will list the name of the files from the current directory whose +% size is > 100 bytes (See help on dir and function_handle) +% +% >> structmatch(dir,'date',@(D)datenum(D)>now-7,'name')' +% will list the name of the files from the current directory which +% are earlier than 7 days +% +% >> findobj('type','fig') +% >> close(ans(structmatch(get(findobj('type','fig')),'Name',@isempty,0))) +% will close all open figures whose Names are empty. +% +% See also: strmatch, structcmp, function_handle + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2008 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2008-10-28 Creation +% KND 2008-11-05 Bug with nargin=3; +% KND 2009-07-24 Bug: structmatch(dir(pwd),4,1) +% KND 2009-11-05 Logical indices & Error checking +% +% ----------------------------- Script History --------------------------------- + +%warning('bug with: structmatch(dir(pwd),4,1)') +if ~ isstruct(D) + error('Input must be a structure array') +end +if nargin<4 + %retrieve all fields of the matching elements from the structure + W=NaN; +end +if nargin<3 + %retrieve all fields of the matching elements from the structure + K=[]; +end +if ischar(H) + if isfield(D,H) + HK={D.(deblank(H))}; + else + error('Unknown hashkey: %s is not a field of the structure', H); + end +elseif isnumeric(H) + FN=fieldnames(D); + if H<1 || H > numel(FN) + error('Requiring field #%d in structure that contains only %d fields!', H, numel(FN)); + end + H=FN{H}; + HK={D.(deblank(H))}; +end +% Find indices of the elements matching the key +if isempty(K) + I=cell2mat(HK); +elseif isa(K, 'function_handle') + [I]=cell2mat(cellfun2(K,HK)); + % elseif isnumeric(K) + % [I]=ismember(cell2mat(HK),K); + % this doesn't work... + % indeed if any empty value, result is shifted... + % eg: a=dir;b=a;b(3).bytes=[];structmatch(a,3,a(4).bytes) + % +elseif ischar(K) % reg exp + %[I]=ismember(HK,K); + [I]=not(cellfun('isempty',regexp(HK,K))); +else + I=logical(zeros(size(HK))); %#ok + for i=1:numel(HK) + if isequal(K,HK{i}) + I(i)=1; + end + end + %error('STRUCTMATCH:WrongKey','Wrong input: KEY'); +end +% Output matching keys, if user wants them +if nargout>2 + MK=HK(I); +end +if isequal(W,0) + V = I; + return +end +if ~isnan(W) & isnumeric(W) + V = find(I,W); + return +end + +% Filter matching elements +V=D(I); +if ~isnan(W) + V ={V.(deblank(W))}; +else + % output the whole struct as V +end + + +function [y] = cellfun2(fun, x) +% cellfun() - Cap funtion for cellfun which accepts any function call +% +% Y = CELLFUN(FUN, C) applies function FUN to the cells of C +% +%See also: cellfun +y = cell(size(x)); +for i=1:numel(x) + y{i} = feval(fun, x{i}); +end +return; diff --git a/subarray.m b/subarray.m new file mode 100644 index 0000000..7efd89d --- /dev/null +++ b/subarray.m @@ -0,0 +1,150 @@ +function [Y,S]=subarray(X,varargin) +% subarray - Retrieves subparts from a ND-array using subscripted reference +% +% [Y,S]=subarray(X,idx,dim) +% [Y]=subarray(X,idx1,dim1,idx2,dim2,...) +% [Y]=subarray(X,{idx1,dim1,idx2,dim2,...}) or {idx{:};dim{:}} +% Retrieves Y a subpart of a N-dimension array X based on subscripted +% references specified in inputs idx and dim (see below) +% +% INPUTS: +% X: a ND-array +% idx: indices of values to retrieve in the given dimension +% If negative indices start from the end +% E.g. 0 is the last element in the dimension, -1 the one before etc. +% Indices can also be specified as logical. +% dim: dimension on which the operation is done +% Default: dim = first non-singleton. So that, if X is a vector, +% subarray(X,n) outputs the n-th element of it. +% * if dim<0: remove the subpart and output the remainder +% * if dim=+Inf or -Inf, then output the given elements after +% vertical vector reshaping (i.e. using linear indexing). +% If the same dimension is specified in multiple places only the +% last one will be taken into account. +% If other dimensions are specified along with Inf, an error will +% ensue. +% Dimensions need not to be specified in any specific ordered. +% +% OUTPUTS: +% Y: a multiple dimension array depending on the given inputs. +% S: a structure that can be used in a call with SUBSREF to +% output Y from X. However, if Inf has been used as a dimension +% specifier, S cannot be output. +% +% Examples using matrix M = magic(3) +% >> subarray(M,1) retrieves the first row of matrix M +% >> subarray(M,[3 2],2) retrieves the 3rd and 2nd columns of M (in that +% order) +% >> subarray(M,1,5) retrieves the whole matrix M since M has less than 5 +% dimensions +% >> subarray(M,1,-2) retrieves all but the first column of M +% >> subarray(M,1,-2,2,1) is equivalent to M(2,[2 3]) (but this wouldn't +% be equivalent if M had more than 2 dimensions) +% >> subarray(M,0,1) retrieves the last line of M +% >> subarray(M,2,Inf) retrieves the 2nd element of M(:) +% >> subarray(M,[-1 -2],-Inf) retrieves all but the second-to-last and +% next-to-last elements of M(:) +% >> subarray(M,[false true true],2) retrieves the 2nd and 3rd columns +% +% +% Note: This function is useful when one doesn't know the number of +% dimensions of X. E.g. to retrieve the 3rd and 4th "line" of the 2nd +% dimension of a ND array (say, X is 4-by-5-by-3-by-...) +% >> subarray(X,[3 4] ,2) +% outputs a 4-by-[2]-by-3-by-... array +% +% See also: SUBSREF + +% Author: K. N'Diaye, kndiaye01yahoo.fr +% Copyright (C) 2007 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html + +% Version 1.0 +% ----------------------------- Script History ---------------------------- +% KND 2007-05-22 Published on Matlab Central +% KND 2007-10-28 Allows logical indexing +% KND 2009-02-26 Consistent behaviour with multiple "Inf"'s used as dim +% KND 2009-11-12 Corrected bug when any dim > ndims +% ----------------------------- Script History ---------------------------- + + +sx=size(X); +if nargin<2 + Y=X; + return; %previously error('No indices given!') +elseif nargin>2 + inputs=varargin; + if rem(nargin-1,2) + error('Indices & dimensions should be given in pairs') + end +else + if iscell(varargin{1}) + % when a single cell is given as an input, + inputs=varargin{1}(:)'; + else + % when no dimension is specified use the first non-singleton + inputs=varargin(1); + dim=[find(sx>1) 1]; + inputs{2}=dim(1); + end +end +% inputs should be split in indices and dimensions +idx=inputs(1:2:end); +dim=[inputs{2:2:end}]; +%indiced in dimensions that are negative will behave as "excluders" +exclude=logical(dim<0); +dim=abs(dim); +% if length(unique(dim))1 & isinf(dim) + error('Structure S cannot be output when using Inf as dimension'); +end +return diff --git a/subdir.m b/subdir.m new file mode 100644 index 0000000..6a4e6a4 --- /dev/null +++ b/subdir.m @@ -0,0 +1,26 @@ +function [D]=subdir(DN) +% subdir() - Process DIR command across subdirectories +% [output] = subdir(directory_name) +if nargin==0 + DN=pwd; +end +[p0,n,e]=fileparts(DN); +if isempty(p0) + p0=pwd; +end +p=strread(genpath(p0), '%s', 'delimiter', ';'); +D=[]; +for i=1:length(p) + f=dir(fullfile(p{i}, [n e])); + for j=1:length(f); + f(j).name=fullfile(p{i}, f(j).name); + end + D=[D; f]; +end +if nargout==0 + if ~isempty(p0) + cd(p0) + end + D=strvcat({D.name}); +end + \ No newline at end of file diff --git a/subjectimage2spmvol.m b/subjectimage2spmvol.m new file mode 100644 index 0000000..43daf7f --- /dev/null +++ b/subjectimage2spmvol.m @@ -0,0 +1,49 @@ +function [v,y]=subjectimage2spmvol(imagefile) +% subjectimage2spmvol - converts Brainstorm subjectimage to SPM volume +% [V,Y]=subjectimage2spmvol(imagefile) +% +% See also: spm_vol, spm_read_vol +if nargin<1 + imagefile=spm_get(1,'*subjectimage.mat',... + 'Choose a subjectimage'); +end + +m=load(imagefile); + +% Create volume +v.fname=imagefile; +TYPE=2; +v.dim=[size(m.Cube) TYPE]; +% Translation (origin) +% T=[0 ; 0 ; 0]; +% v.mat=[ diag(m.Voxsize) T; zeros(1,3) 1]; + +% v.mat=m.mat; + +% Orientation in SubjecTImage is in CTF referential, thats not what we +% want: we would prefere it in the MRI referential +% Anyhow, we drop SCS info... +% if isfield(m, 'SCS') +% iSCS=strmatch('ctf', lower({m.SCS.System})); +% if isfield(m.SCS(iSCS), 'R') & isfield(m.SCS(iSCS), 'T') +% v.mat=[ m.SCS(iSCS).R m.SCS(iSCS).T ; zeros(1,3) 1 ]; +% v.mat=v.mat([2 1 3 4],:); +% end +% else +% warning('No SCS in subjectimage.') + +v.mat=diag([-1 1 1 1]); +% i.e. origin is at: v.mat(:,4) = [ 0 0 0 ]' + +% end + +v.pinfo=[1 ; 0 ; 0 ]; % Slice info: intensity scale & offset and bytes offset +v.n=1; +v.descrip='Imported from BrainStrom Image'; + +if nargout>1 + y=m.Cube; +end + + + diff --git a/subpatch.m b/subpatch.m new file mode 100644 index 0000000..41475d1 --- /dev/null +++ b/subpatch.m @@ -0,0 +1,10 @@ +function [fv2]=subpatch(fv,idx) +% subpatch - Sub part of a patch struct +% [fv2]=subpatch(fv,idx) where idx are the indices of the subpatch +fv2.vertices=fv.vertices(idx,:); +fv2.faces=[]; +for i=1:size(fv.faces,1) + if all(ismember(fv.faces(i,:), idx)) + fv2.faces=[fv2.faces; find(fv.faces(i,1)==idx) find(fv.faces(i,2)==idx) find(fv.faces(i,3)==idx) ]; + end +end diff --git a/subsasgn2.m b/subsasgn2.m new file mode 100644 index 0000000..4331af5 --- /dev/null +++ b/subsasgn2.m @@ -0,0 +1,96 @@ +function X = subsasgn2(X,v,varargin) +%SUBSASGN2 - User-friendlier subscripted assignment. +% [Y] = subsasgn2(X,v,i,dim) outputs array Y where some values in X have +% been replaced by the value given in v +% +% Example +% >> subsasgn(magic(5),0,3,2) set the 3rd column to 0 +% NaN's +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2010 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2010-04-21 Creation +% +% ----------------------------- Script History --------------------------------- + +sx=size(X); +if nargin<2 + return; %previously error('No indices given!') +elseif nargin>2 + inputs=varargin; + if rem(nargin-1,3) + error('Indices & dimensions should be given in triplets') + end +else + if iscell(varargin{1}) + % when a single cell is given as an input, + inputs=varargin{1}(:)'; + else + % when no dimension is specified use the first non-singleton + inputs=varargin(1); + dim=[find(sx>1) 1]; + inputs{2}=dim(1); + end +end +% inputs should be split in indices and dimensions +idx=inputs(1:2:end); +dim=[inputs{2:2:end}]; +%indiced in dimensions that are negative will behave as "excluders" +exclude=logical(dim<0); +dim=abs(dim); +% if length(unique(dim))1 & isinf(dim) + error('Structure S cannot be output when using Inf as dimension'); +end +return \ No newline at end of file diff --git a/sumnan.m b/sumnan.m new file mode 100644 index 0000000..3ec3906 --- /dev/null +++ b/sumnan.m @@ -0,0 +1,45 @@ +function [y,nans]=sumnan(x,dim) +%SUMNAN Sum once NaN values have been removed +% [y,nans]=sumnan(x,dim) +% +% If dim is negative +% Do not include data if any NaN found along the dimension +% +% Example: +% a = cat(3,magic(5), cumsum(ones(5)));a([3 35])=NaN +% sumnan(a,1) +% sumnan(a,-2) +% see mean() for details + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2009 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2009-10-22 Creation +% +% ----------------------------- Script History --------------------------------- + + +if nargin==1, + dim = min(find(size(x)~=1)); + if isempty(dim), dim = 1; end +end +nans=isnan(x); +if dim < 0 + dim=-dim; + otherdims = setdiff(1:ndims(x),dim); + permdims = [dim otherdims]; + nans=permute(nans,permdims); + nans(any(nans(:,:),2),:)=1; + nans=ipermute(nans,permdims); +end +x(nans)=0; +nans=double(nans); +y = sum(x,dim); +if nargin>1 + nans=logical(nans); +end \ No newline at end of file diff --git a/system1020.m b/system1020.m new file mode 100644 index 0000000..8080415 --- /dev/null +++ b/system1020.m @@ -0,0 +1,5 @@ +divch2=setdiff(divch, [9 19]) +xyz=getChannelLocs(eegch(divch2)); +mass=median(xyz) +R0 = mean(norlig(xyz - ones(62,1)*mass)); % Average distance between the center of mass and the scalp points +[SphereParams,brp] = fminsearch('dist_sph',[median(xyz) R0],[],xyz) diff --git a/tabread.m b/tabread.m new file mode 100644 index 0000000..09beeb0 --- /dev/null +++ b/tabread.m @@ -0,0 +1,61 @@ +function [A,B,N]=tabread(file,varargin) +% tabread - reads tabulated data NOT IMPLEMENTED YET + + +% [X,N]=tabread(file) +% [X,N]=tabread(file,'headerlines', ... , 'delimiter', ...) +% +% Reads tabultaed data, where the field names are in the first +% column. N is a cell of field names, X is the rest of the +% (numerical) values + +% error('NOT IMPLEMENTED YET') + +if nargin>2 + Options=struct(varargin{:}); +else + Options=[]; +end + +if not(isfield(Options, 'skiplines')) + Options.skiplines=0; +end + +if not(isfield(Options, 'headerlines')) + Options.headerlines=false; +end + +if not(isfield(Options, 'delimiters')) + Options.delimiter='\t '; +end +% Options.delimiter=[Options.delimiters{:}]; + +if not(isfield(Options, 'deblank')) + Options.deblank=1; +end + +fid=fopen(file, 'rt'); +for i=1:Options.skiplines + fgetl(fid); +end +ok = 1; +i=1; +N={}; +X=[]; +sx=0; +while not(feof(fid)) && ok + t=fgetl(fid); + if not(isempty(t)) + t=strread(t, '%s', 'delimiter', Options.delimiter); + N{i,1}=t{1}; + sx=size(X,2); + X(i,:)=str2num(strvcat(t(2:end)))'; + if sx>0 & size(X,2)>sx + warning('Zero-padding due to non rectangular data!') + + end + i=i+1; + end + +end + \ No newline at end of file diff --git a/template.m b/template.m new file mode 100644 index 0000000..4b080ca --- /dev/null +++ b/template.m @@ -0,0 +1,62 @@ +function = $filename(input,varargin) +%$FILENAME - One line description goes here. +% [] = $filename(input) +% +% Example +% >> $filename +% +% See also: + +% Author: K. N'Diaye (kndiaye01gmail.com) +% Copyright (C) $date(yyyy) +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND $date(yyyy-mm-dd) Creation +% +% ----------------------------- Script History --------------------------------- + +if nargin<1 + error('No data!') +elseif nargin==1 || isnumeric(action) + varargin=[{action} varargin ]; + action='init'; +end + +try + varargout={eval(sprintf('action_%s(varargin{:});',action))}; +catch + eval(sprintf('action_%s(varargin{:});',action)); +end + + +function [ha]=action_init(data, varargin) +% ------------------------------------------------------------------------- +% Check OPTIONS and default values when optional arguments are not +% specified +Def_OPTIONS = struct(... + 'option1',[],... + ); +if nargin < 2 + OPTIONS = Def_OPTIONS; +else + if length(varargin)>1 + OPTIONS = cell2struct(varargin(2:2:end),varargin(1:2:end),2); + % struct(varargin{:}) bugs with something like {'string1' 'string2'} in the inputs! + else + OPTIONS = varargin{1}; + end + % Check field names of passed OPTIONS and fill missing ones with default values + DefFieldNames = fieldnames(Def_OPTIONS); + for k = 1:length(DefFieldNames) + if ~isfield(OPTIONS,DefFieldNames{k}) + OPTIONS = setfield(OPTIONS,DefFieldNames{k},getfield(Def_OPTIONS,DefFieldNames{k})); + end + end + clear DefFieldNames +end +clear Def_OPTIONS +% ------------------------------------------------------------------------- diff --git a/tess2patch.m b/tess2patch.m new file mode 100644 index 0000000..a8f9f92 --- /dev/null +++ b/tess2patch.m @@ -0,0 +1,23 @@ +function [fv]=tess2patch(tess) +% tess2patch - convert Brainstorm tessellation to FV structure +% [fv]=tess2patch(tess) +% [fv]=tess2patch('xxx_tess.mat') +% NB: On FV structures, see REDUCEPATCH +if nargin<1 + [f,p]=uigetfile; + if isempty(f) + return + end + tess=fullfile(p,f) +end + +if ischar(tess) + if exist(tess, 'file') | exist([tess '.mat'], 'file') + tess=load(tess, 'Faces', 'Vertices'); + end +end + +for i=1:length(tess.Faces) + fv(i).faces=tess.Faces{i}; + fv(i).vertices=tess.Vertices{i}'; +end diff --git a/tex_clusters.m b/tex_clusters.m new file mode 100644 index 0000000..7d81514 --- /dev/null +++ b/tex_clusters.m @@ -0,0 +1,110 @@ +function [clu,clu2,tex2]=tex_clusters(tex, vc, sx, tx,VERBOSE) +% tex_clusters - find clusters of a given extant on a texture +% +% [clu]=tex_clusters(tex, vc) +% [clu,clu2,tex2]=tex_clusters(tex, vc, sx, tx) +% +% Lists clusters of a texture, given a minimal size in space and time +% (i.e. at least [sx] connected vertices must be active during at +% least [tx] time sample for the cluster to be kept +% +%INPUTS: +% tex: [M-by-T] texture of logical values (0=not active) on a N-vertices mesh during T time samples +% vc: vertex connectivity of the mesh or adjacency matrix +%OPTIONAL INPUTS: +% sx: minimum spatial extant (default: 1) +% tx: minimum temporal extant (default: 1) +%OUTPUT: +% clu: cluster list, a cell array of vertex indices matchin the criteria +% clu2: time dependent new texture with 1's +% + + +% See also: dmperm + +% http://delivery.acm.org/10.1145/100000/98287/p303-pothen.pdf?key1=98287&key2=1613996311&coll=GUIDE&dl=GUIDE&CFID=65251534&CFTOKEN=21051873 + +DEBUG=0; +if nargin<5 + VERBOSE=1; +end +if nargin<4 + tx=1; +elseif tx>0 + warning('No temporal filtering yet!'); +end +if nargin<3 + sx=1; +end +if nargin<2 + error('Not enoug arguments'); +end + +nv=size(tex,1); +nt=size(tex,2); + +if length(vc) ~= nv + error('Connectivity and number of vertices in the texture don''t match') +end + +if iscell(vc) + A=vertconn2adjacency(vc); +else + A=vc; +end + +A=(A|speye(size(A))); +if VERBOSE + h = timebar('Spatial extent','Progress'); +end +C={}; +for it=1:nt + f=double(tex(1:nv,it)); + B=A; + % NaN values + f(isnan(f))=0; + + % Remove OFF vertices + B(~f,:)=0; + B(:,~f)=0; + + % Compute clusters + [p,q,r,s] = dmperm(B); + nc=0; % number of cluster at this time sample + sr=diff(r); % compute size of clusters + [ignore,rr]=sort(-sr); % sort clusters by decreasing size + for i=1:length(r)-1 + if r(rr(i))=sx % == it passes the spatial threshold + nc=nc+1; + C{it}{nc}=p(r(rr(i)):r(rr(i)+1)-1); + end + end + if VERBOSE + try;timebar(h,(i+(it-1)*length(r))/(nt*length(r)));end; + end + end + + +end +if VERBOSE + try;close(h);end +end +% Post process for time continuity +% + +clu=C; + +if nargout>1 + clu2=sparse(nv,nt); + for it=1:nt + for i=1:length(clu{it}) + clu2(C{it}{i},it)=i; + end + end +end + +return diff --git a/textalign.m b/textalign.m new file mode 100644 index 0000000..72e8c51 --- /dev/null +++ b/textalign.m @@ -0,0 +1,38 @@ +function A = textalign(A,alignment) +%TEXTALIGN - One line description goes here. +% [B] = textalign(A, 'left'|'right'|'center') +% +% Example +% >> textalign +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2009 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2009-09-15 Creation +% +% ----------------------------- Script History --------------------------------- + +CharOutput = false; +szA = size(A); +if ~iscell(A) + CharOutput = true; + A = cellstr(A); +end +for i=1:numel(A) + A{i} = fliplr(A{i}); +end +A = strvcat(A); +A = fliplr(A); +if ~CharOutput + A = cellstr(A); + A = reshape(A,szA); +end +return +B=fliplr(deblank(fliplr(A))); \ No newline at end of file diff --git a/threshold.m b/threshold.m new file mode 100644 index 0000000..a7a6aed --- /dev/null +++ b/threshold.m @@ -0,0 +1,19 @@ +function [ y ] = threshold( x , thd , mode , replace ) +% threshold - thresholds an array and replace values under threshold +% [ y ] = threshold( x , thd , mode , replace ) +% x: array +% thd: value of threshold +% mode: 'value' | '%max' | '%nb' +% threshold on value (keeps x>thd) +% relative to the max (x > thd*x_max) +% percentage of number thd% most intense +if nargin < 3 + mode='value'; +end +if nargin < 4 + replace=0; +end +y=x; +switch mode + case 'value' + y(x<=thd \ No newline at end of file diff --git a/tiedrank.m b/tiedrank.m new file mode 100644 index 0000000..0d89aaf --- /dev/null +++ b/tiedrank.m @@ -0,0 +1,26 @@ +function [r,ta]=tiedrank(X,dim) +%tiedrank - Tied rank of each element in a set +% [r]=tiedrank(X,dim) +% Computes tied rank of each element of X along given dimension +% (default is to use the first non-singleton dimension) +% "Tied ranking" is such that ex-aequo elements share the same (possibly +% half) rank: +% >> tiedrank(['DABBC']) %-> 6 1 2.5 2.5 4 +if nargin<2 + dim=min(find(size(X)>1)); + if isempty(dim) + error('X is empty!') + end +end +[ignore,Y]=sort(X,dim); +[ignore,r1]=sort(Y,dim); +[ignore,Y]=sort(-X,dim); +[ignore,r2]=sort(Y,dim); +r2=size(X,dim)-r2+1; +r=(r1+r2)/2; +if nargout > 1 + warning('Tier adjustments not computed yet!'); + + % Tie adjustment... + ta=0; +end diff --git a/tiedrank2.m b/tiedrank2.m new file mode 100644 index 0000000..0d89aaf --- /dev/null +++ b/tiedrank2.m @@ -0,0 +1,26 @@ +function [r,ta]=tiedrank(X,dim) +%tiedrank - Tied rank of each element in a set +% [r]=tiedrank(X,dim) +% Computes tied rank of each element of X along given dimension +% (default is to use the first non-singleton dimension) +% "Tied ranking" is such that ex-aequo elements share the same (possibly +% half) rank: +% >> tiedrank(['DABBC']) %-> 6 1 2.5 2.5 4 +if nargin<2 + dim=min(find(size(X)>1)); + if isempty(dim) + error('X is empty!') + end +end +[ignore,Y]=sort(X,dim); +[ignore,r1]=sort(Y,dim); +[ignore,Y]=sort(-X,dim); +[ignore,r2]=sort(Y,dim); +r2=size(X,dim)-r2+1; +r=(r1+r2)/2; +if nargout > 1 + warning('Tier adjustments not computed yet!'); + + % Tie adjustment... + ta=0; +end diff --git a/tilefigs.m b/tilefigs.m new file mode 100644 index 0000000..4905bc2 --- /dev/null +++ b/tilefigs.m @@ -0,0 +1,98 @@ +function tilefigs(figs) +%TILEFIGS Tile all open figure windows around on the screen. +% +% TILEFIGS places all open figure windows around on the screen with no +% overlap. +% +% TILEFIGS(FIGS) can be used to specify which figures that should be +% tiled. Figures are not sorted when specified. +% +% See also SORTFIGS, CYCLEFIGS. + +% Author: Peter J. Acklam +% Time-stamp: 2003-10-13 15:40:11 +0200 +% E-mail: pjacklam@online.no +% URL: http://home.online.no/~pjacklam + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Get the handles to the figures to process. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +if ~nargin % If no input arguments... + figs = findobj('Type', 'figure'); % ...find all figures. + figs = sort(figs); +end + +if isempty(figs) + disp('No open figures or no figures specified.'); + return +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% The elements in the vector specifying the position. +% 1 - Window left position +% 2 - Window bottom position +% 3 - Window horizontal size +% 4 - Window vertical size +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%hspc = 20; % Horisontal space. +%topspc = 20; % Space above top figure. +%medspc = 20; % Space between figures. +%botspc = 40; % Space below bottom figure. +% +%units = 'pixels'; + +hspc = 0.02; % Horisontal space. +topspc = 0.02; % Space above top figure. +medspc = 0.02; % Space between figures. +botspc = 0.05; % Space below bottom figure. + +units = 'normalized'; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Set miscellaneous parameter. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +nfigs = length(figs); % Number of figures. + +nh = ceil(sqrt(nfigs)); % Number of figures horisontally. +nv = ceil(nfigs / nh); % Number of figures vertically. + +nh = max(nh, 2); +nv = max(nv, 2); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Get the screen size. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +old_root_units = get(0, 'Units'); % Get current root units. +set(0, 'Units', units); % Set root units. +scrdim = get(0, 'ScreenSize'); % Get screen size. +set(0, 'Units', old_root_units); % Reset units. +scrwid = scrdim(3); % Screen width. +scrhgt = scrdim(4); % Screen height. + +figwid = (scrwid - (nh + 1) * hspc) / nh; +fighgt = (scrhgt - (topspc + botspc) - (nv - 1) * medspc) / nv; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% Put the figures where they belong. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +for row = 1 : nv + for col = 1 : nh + idx = (row - 1) * nh + col; + if idx <= nfigs + figlft = col * hspc + (col - 1) * figwid; + figbot = scrhgt - topspc - row * fighgt - (row - 1) * medspc; + figpos = [ figlft figbot figwid fighgt ]; % Figure position. + fighnd = figs(idx); % Figure handle. + old_fig_units = get(fighnd, 'Units'); % Get current units. + set(fighnd, 'Units', units); % Set new units. + set(fighnd, 'OuterPosition', figpos); % Set position. + set(fighnd, 'Units', old_fig_units); % Reset units. + figure(fighnd); % Raise figure. + end + end +end \ No newline at end of file diff --git a/tiles.m b/tiles.m new file mode 100644 index 0000000..1b12d1e --- /dev/null +++ b/tiles.m @@ -0,0 +1,44 @@ +function [T,idx]=tiles(A,C) +% tiles - extract subparts (a.k.a tiles or blocks) of a matrix +% [T,idx]=tiles(A,C) +% Cuts N-dimensional matrix A into pieces and extract them as a cell array of tiles +% C is a N-by-1 vector defining how each dimension of A is cut +% +%See also: tilesperm, subarray + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2008 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2008-09-04 Updated comments & help +% +% ----------------------------- Script History --------------------------------- + +S=size(A); +if ndims(A)>2 + error('Cannot process matrices with more than 2 dimensions'); +end +C=[C ones(1,max(0,length(S)-length(C)))]; +if any(C(length(S)+1:end)~=1) + error('Cuts cannot be made along those dimensions'); +end +if any(C>S) + error('Cuts cannot be made along those dimensions'); +end +%number of tiles +NT=prod(C); +T={}; +idx={}; +n1=ceil(S(1)/C(1)); +n2=ceil(S(2)/C(2)); + +for i1=1:C(1) + for i2=1:C(2) + T = [T { A( (1+(i1-1)*n1):min(S(1),i1*n1) , (1+(i2-1)*n2):min(S(2),i2*n2) )}]; + % idx = [idx { sub2ind(S, (1+(i1-1)*n1):min(S(1),i1*n1) , (1+(i2-1)*n2):min(S(2),i2*n2)) }]; + end +end \ No newline at end of file diff --git a/tilesperm.m b/tilesperm.m new file mode 100644 index 0000000..da299dd --- /dev/null +++ b/tilesperm.m @@ -0,0 +1,54 @@ +function [B,tp]=tilesperm(A,C,tp) +% Permute blocks (or tiles) of a rectangular matrix (e.g. image) +% +% B = tilesperm(A,C) will be a matrix of the same size as A but wherein +% rectangular block (tiles) have been randomly permuted +% +%INPUTS: +% A: original matrix (e.g. image) +% C = [v h]: number of tiles [vertically horizontally] +% +% [B,tp] = tilesperm(A,C[,tp]) +% tp: is the permutation table, it can be given as a 3rd argument. +% If not given or empty, a random permutation table is generated at +% runtime. +% +%See also: tiles, subarray, imgfilt + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2008 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2008-09-04 Updated comments & help +% +% ----------------------------- Script History --------------------------------- + +if nargin<3 + tp=[]; +end + +S=[size(A) 1]; +AA=A; +B=zeros(S,class(A)); + +for i3=1:S(3) + A=AA(:,:,i3); + [T]=tiles(A,C); + t=cellfun('prodofsize', T); + t=t==prod(size(T{1})); + if isempty(tp) + tp=randperm(sum(t)); + end + T(t)= subarray(T(t),tp,Inf); + n1=ceil(S(1)/C(1)); + n2=ceil(S(2)/C(2)); + for i1=1:C(1) + for i2=1:C(2) + B( (1+(i1-1)*n1):min(S(1),i1*n1) , (1+(i2-1)*n2):min(S(2),i2*n2) , i3 ) = T{ (i1-1)*C(2) + i2 }; + end + end +end \ No newline at end of file diff --git a/timebar.m b/timebar.m new file mode 100644 index 0000000..c07d4df --- /dev/null +++ b/timebar.m @@ -0,0 +1,215 @@ +function h = timebar(message,name,update_rate) +% TIMEBAR Progress bar with estimated time remaining +% H = TIMEBAR('message','name') creates and displays a progress +% bar with alphanumeric progress percentage and estimated time +% time remaining. A user input message ('message') is displayed, +% typically to distinguish what process is being monitored, and +% 'name' is an optional figure name. The figure handle H is +% returned. +% +% TIMEBAR(H,X) will update the length of the progress bar, the +% percentage complete, and the estimated time remaining, where +% X is a fractional progress between 0 (initial) and 1 (complete). +% (Note that the order of H,X is opposite to waitbar.) +% +% TIMEBAR(H,X,RATE) will update the progress bar and information +% at a rate given by RATE in seconds. Default is 0.1 seconds. +% +% The estimated time remaining is linear using the initial time +% (when TIMEBAR is first opened), the current time, and the percent +% complete. +% +% TIMEBAR is typically used inside a FOR loop or during numerical +% simulation. A sample for loop is shown below: +% +% h = timebar('Loop counter','Progress') +% for i = 1:100 +% % computation here % +% timebar(h,1/100) +% end +% close(h) +% +% A sample for numerical integration is shown below: +% +% % script file +% t0 = 0; +% tf = 60; +% h = timebar('Simulation integration','Progress') +% [tt,xx] = ode45('states.m',[t0 tf],initial_conditions); +% close(h) +% +% % states.m +% function xdot = states(t,x) +% xdot(1) = ...; +% xdot(2) = ...; +% ... +% timebar(h,(t-t0)/(tf-t0)) +% +% Version: 2.0 +% Version History: +% 1.0 2002-01-18 Initial release +% 2.0 2002-01-21 Added update rate option +% +% Copyright 2002, Chad English +% cenglish@myrealbox.com + +% KND: added '%' sign +% KND: no message needed + + +if nargin < 3 % If update rate is not input + update_rate = 0.1; % set it to 0.1 seconds +end +if nargin == 0 + message = [evalin('caller', 'mfilename') '@' datestr(now)]; +end +if ~ishandle(message) % If first input is not a timebar handle, + % treat as new timebar + + %%%%%%%%%% SET WINDOW SIZE AND POSITION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + winwidth = 300; % Width of timebar window + winheight = 75; % Height of timebar window + screensize = get(0,'screensize'); % User's screen size [1 1 width height] + screenwidth = screensize(3); % User's screen width + screenheight = screensize(4); % User's screen height + winpos = [0.5*(screenwidth-winwidth), ... + 0.5*(screenheight-winheight), winwidth, winheight]; % Position of timebar window origin + + %%%%%%%% END SET WINDOW SIZE AND POSITION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + %%%%%%%%%% OPEN FIGURE AND SET PROPERTIES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + if nargin < 2 + name = ''; % If timebar name not input, set blank + end + + wincolor = 0.75*[1 1 1]; % Define window color + est_text = 'Remaining time (est.): '; % Set static estimated time text + + h = figure('menubar','none',... % Turn figure menu display off + 'numbertitle','off',... % Turn figure numbering off + 'name',name,... % Set the figure name to input name + 'position',winpos,... % Set the position of the figure as above + 'color',wincolor,... % Set the figure color + 'resize','off',... % Turn of figure resizing + 'tag','timebar'); % Tag the figure for later checking + + userdata.text(1) = uicontrol(h,'style','text',... % Prepare message text (set the style to text) + 'pos',[10 winheight-30 winwidth-20 20],... % Set the textbox position and size + 'hor','center',... % Center the text in the textbox + 'backgroundcolor',wincolor,... % Set the textbox background color + 'foregroundcolor',0*[1 1 1],... % Set the text color + 'string',message); % Set the text to the input message + + userdata.text(2) = uicontrol(h,'style','text',... % Prepare static estimated time text + 'pos',[10 5 winwidth-20 20],... % Set the textbox position and size + 'hor','left',... % Left align the text in the textbox + 'backgroundcolor',wincolor,... % Set the textbox background color + 'foregroundcolor',0*[1 1 1],... % Set the text color + 'string',est_text); % Set the static text for estimated time + + userdata.text(3) = uicontrol(h,'style','text',... % Prepare estimated time + 'pos',[135 5 winwidth-145 20],... % Set the textbox position and size + 'hor','left',... % Left align the text in the textbox + 'backgroundcolor',wincolor,... % Set the textbox background color + 'foregroundcolor',0*[1 1 1],... % Set the text color + 'string',''); % Initialize the estimated time as blank + + userdata.text(4) = uicontrol(h,'style','text',... % Prepare the percentage progress + 'pos',[winwidth-35 winheight-50 35 20],... % Set the textbox position and size + 'hor','right',... % Left align the text in the textbox + 'backgroundcolor',wincolor,... % Set the textbox background color + 'foregroundcolor',0*[1 1 1],... % Set the textbox foreground color + 'string',''); % Initialize the progress text as blank + + userdata.axes = axes('parent',h,... % Set the progress bar parent to the figure + 'units','pixels',... % Provide axes units in pixels + 'pos',[10 winheight-45 winwidth-50 15],... % Set the progress bar position and size + 'xlim',[0 1],... % Set the range from 0 to 1 + 'box','on',... % Turn on axes box (to see where 100% is) + 'color',[1 1 1],... % Set plot background color to white + 'xtick',[],'ytick',[]); % Turn off axes tick marks and labels + + userdata.bar = patch([0 0 0 0 0],[0 1 1 0 0],'r'); % Initialize progress bar to zero area + userdata.time = []; % On opening don't set time + userdata.inc = []; % + set(h, 'userdata', userdata) % Allow access to the text and axes settings + % by including them with the timebar data + %%%%%%%% END OPEN FIGURE AND SET PROPERTIES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +else % If first input is a timebar handle, update + % the window + %%%%%%%%%% GET HANDLE AND PROGRESS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + drawnow % faster than pause ! (KND) + % pause(10e-100) % Message, bar, and static text won't display + % without arbitrary pause (don't know why) + h = message; % Set handle to first input + progress = name; % Set progress to second input + + if ~strcmp(get(h,'tag'), 'timebar') % Check object tag to see if it is a timebar + error('Handle is not to a timebar window') % If not a timebar, report error and stop + end + %%%%%%%% END GET HANGLE AND PROGRESS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + %%%%%%%%% CALCULATE ESTIMATED TIME REMAINING %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + userdata = get(h,'userdata'); % Get the userdata included with the timebar + if isempty(userdata.time) + userdata.time = clock; % Initialize the current time + userdata.inc = clock; % Set incremental clock to current time + set(h,'userdata',userdata) + end + inc = clock-userdata.inc; % Calculate time increment since last update + inc_secs = inc(3)*3600*24 + inc(4)*3600 + ... + inc(5)*60 + inc(6); % Convert the increment to seconds + + if [inc_secs > update_rate] | [progress == 1] % Only update at update rate or 100% complete + userdata.inc = clock; % If updating, reset the increment clock + set(h,'userdata',userdata) % Update userdata with the new clock setting + tpast = clock-userdata.time; % Calculate time since timebar initialized + seconds_past = tpast(3)*3600*24 + tpast(4)*3600 + ... + tpast(5)*60 + tpast(6); % Transform passed time into seconds + estimated_seconds = seconds_past*(1/progress-1); % Estimate the time remaining in seconds + hours = floor(estimated_seconds/3600); % Calculate integer hours of estimated time + minutes = floor((estimated_seconds-3600*hours)/60); % Calculate integer minutes of estimated time + seconds = floor(estimated_seconds-3600*hours- ... + 60*minutes); % Calculate integer seconds of estimated time + tenths = floor(10*(estimated_seconds - ... + floor(estimated_seconds))); % Calculate tenths of seconds (as integer) + %%%%%%%% END CALCULATE ESTIMATED TIME REMAINING %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + %%%%%%%%%% UPDATE ESTIMATED TIME AND PROGRESS TO TIMEBAR %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + if progress > 1 % Check if input progress is > 1 + time_message = ' Error! Progress > 1!'; % If >1, print error to estimated time + time_color = 'r'; % in red + else + +time_message = sprintf('%02d:%02d''%.1f', hours,minutes,seconds); +time_message = sprintf('%s (%02d:%02d''%.1f)', time_message, tpast(3)*24+tpast(4),tpast(5), tpast(6)); + +% +% if hours < 10; h0 = '0'; else h0 = '';end % Put leading zero on hours if < 10 +% if minutes < 10; m0 = '0'; else m0 = '';end % Put leading zero on minutes if < 10 +% if seconds < 10; s0 = '0'; else s0 = '';end % Put leading zero on seconds if < 10 +% time_message = strcat(h0,num2str(hours),'h',m0,... +% num2str(minutes),'''',s0,num2str(seconds),... +% '''''',num2str(tenths),... +% '(',num2str(tpast(3)*24+tpast(4)),'h',... +% num2str(tpast(5)),'''',... +% num2str(tpast(6)),''''''); % Format estimated time as hh:mm:ss.t + time_color = 'k'; % Format estimated time text as black + end + + set(userdata.bar,'xdata',[0 0 progress progress 0]) % Update progress bar + set(userdata.text(3),'string',time_message,... + 'foregroundcolor',time_color); % Update estimated time + set(userdata.text(4),'string',... + strcat(num2str(floor(100*progress)),'%')); % Update progress percentage + set(h, 'Name', ['Progress... ' get(userdata.text(4),'string')]) + end + %%%%%%%% END UPDATE ESTIMATED TIME AND PROGRESS TO TIMEBAR %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +end + +%%%%%%%%%% TIMEBAR HANDLE %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +if nargout == 0 % If handle not asked for + clear h % do not output it +end +%%%%%%%% END TIMEBAR HANDLE %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \ No newline at end of file diff --git a/timeit.m b/timeit.m new file mode 100644 index 0000000..7fd6897 --- /dev/null +++ b/timeit.m @@ -0,0 +1,1139 @@ +function [t, measurement_overhead, measurement_details] = timeit(f, num_outputs) +%TIMEIT Measure time required to run function. +% T = TIMEIT(F) measures the time (in seconds) required to run F, which is a +% function handle. TIMEIT calls F with either no output arguments or one +% output argument depending on nargout(F). +% +% T = TIMEIT(F,N) calls F with N output arguments. N can be 0, 1, 2, 3, or 4. +% +% TIMEIT handles automatically the usual benchmarking procedures of "warming +% up" F, figuring out how many times to repeat F in a timing loop, etc. +% TIMEIT also compensates for the estimated time-measurement overhead +% associated with tic/toc and with calling function handles. TIMEIT returns +% the median of several repeated measurements. +% +% Examples +% -------- +% How much time does it take to compute sum(A.' .* B, 1), where A is +% 12000-by-400 and B is 400-by-12000? +% +% A = rand(12000, 400); +% B = rand(400, 12000); +% f = @() sum(A.' .* B, 1); +% timeit(f) +% +% How much time does it take to call svd with three output arguments? +% +% X = [1 2; 3 4; 5 6; 7 8]; +% f = @() svd(X); +% timeit(f, 3) +% +% How much time does it take to dilate the text.png image with +% a 25-by-25 all-ones structuring element? (This example uses Image Processing +% Toolbox functions.) +% +% bw = imread('text.png'); +% se = strel(ones(25, 25)); +% g = @() imdilate(bw, se); +% timeit(g) + +% Steve Eddins +% Copyright 2008-2010 The MathWorks, Inc. + +if nargin < 2 + num_outputs = min(numOutputs(f), 1); +else + if num_outputs > 4 + warning('MATLAB:timeit:tooManyOutputs', ... + 'Too many function output arguments specified. timeit will call your function with 4 output arguments.'); + end +end + +t_rough = roughEstimate(f, num_outputs); + +% Calculate the number of inner-loop repetitions so that the inner for-loop +% takes at least about 1ms to execute. +desired_inner_loop_time = 0.001; +num_inner_iterations = max(ceil(desired_inner_loop_time / t_rough), 1); + +% Run the outer loop enough times to give a reasonable set of inputs to median. +num_outer_iterations = 11; + +% If the estimated running time for the timing loops is too long, +% reduce the number of outer loop iterations. +estimated_running_time = num_outer_iterations * num_inner_iterations * t_rough; +long_time = 15; +min_outer_iterations = 3; +if estimated_running_time > long_time + num_outer_iterations = ceil(long_time / (num_inner_iterations * t_rough)); + num_outer_iterations = max(num_outer_iterations, min_outer_iterations); +end + +times = zeros(num_outer_iterations, 1); + +for k = 1:num_outer_iterations + % Coding note: An earlier version of this code constructed an "outputs" cell + % array, which was used in comma-separated form for the left-hand side of + % the call to f(). It turned out, though, that the comma-separated output + % argument added significant measurement overhead. Therefore, the cases + % for different numbers of output arguments are hard-coded into the switch + % statement below. + switch num_outputs + case 0 + tic(); + for p = 1:num_inner_iterations + f(); + end + times(k) = toc(); + + case 1 + tic(); + for p = 1:num_inner_iterations + output = f(); + end + times(k) = toc(); + + case 2 + tic(); + for p = 1:num_inner_iterations + [output1, output2] = f(); + end + times(k) = toc(); + + case 3 + tic(); + for p = 1:num_inner_iterations + [output1, output2, output3] = f(); + end + times(k) = toc(); + + otherwise + tic(); + for p = 1:num_inner_iterations + [output1, output2, output3, output4] = f(); + end + times(k) = toc(); + end + +end + +t = median(times) / num_inner_iterations; + +measurement_details.EmptyFunctionCallTime = emptyFunctionCallTime(); +measurement_details.SimpleFunctionHandleCallTime = simpleFunctionHandleCallTime(); +measurement_details.AnonymousFunctionHandleCallTime = anonFunctionHandleCallTime(); +measurement_details.TicTocCallTime = tictocCallTime(); +measurement_overhead = (tictocCallTime() / num_inner_iterations) + ... + functionHandleCallOverhead(f); + +t = max(t - measurement_overhead, 0); + +if t < (5 * measurement_overhead) + warning('MATLAB:timeit:HighOverhead', 'The measured time for F may be inaccurate because it is close to the estimated time-measurement overhead (%.1e seconds). Try measuring something that takes longer.', measurement_overhead); +end + +function t = roughEstimate(f, num_f_outputs) +% Return rough estimate of time required for one execution of +% f(). Basic warmups are done, but no fancy looping, medians, +% etc. + +% Warm up tic/toc. +tic(); +elapsed = toc(); +tic(); +elapsed = toc(); + +% Call f() in a loop for at least a millisecond. +times = []; +time_threshold = 3; +iter_count = 0; +while sum(times) < 0.001 + iter_count = iter_count + 1; + + switch num_f_outputs + case 0 + tic(); + f(); + times(end+1) = toc(); + + case 1 + tic(); + output1 = f(); + times(end+1) = toc(); + + case 2 + tic(); + [output1, output2] = f(); + times(end+1) = toc(); + + case 3 + tic(); + [output1, output2, output3] = f(); + times(end+1) = toc(); + + otherwise + tic(); + [output1, output2, output3, output4] = f(); + times(end+1) = toc(); + end + + if iter_count == 1 + if times > time_threshold + % If the first call to f() takes more than time_threshold to run, + % then just use the result from that call. The assumption is that + % first-time effects are negligible compared to the running time for + % f(). + break; + else + % Discard first timing. + times = []; + end + end +end + +t = median(times); + +function n = numOutputs(f) +% Return the number of output arguments to be used when calling the function +% handle f. +% * If nargout(f) > 0, return 1. +% * If nargout(f) == 0, return 0. +% * If nargout(f) < 0, use try/catch to determine whether to call f with one +% or zero output arguments. +% Note: It is not documented (as of R2008b) that nargout can return -1. +% However, it appears to do so for functions that use varargout and for +% anonymous function handles. + +n = nargout(f); +if n < 0 + try + a = f(); + % If the line above doesn't throw an error, then it's OK to call f() with + % one output argument. + n = 1; + + catch %#ok + % If we get here, assume it's because f() has zero output arguments. In + % recent versions of MATLAB we could catch the specific exception ID + % MATLAB:maxlhs, but that would limit the use of timeit to MATLAB versions + % since the introduction of MExceptions. + n = 0; + end +end + +function t = tictocCallTime +% Return the estimated time required to call tic/toc. + +persistent ttct +if ~isempty(ttct) + t = ttct; + return +end + +% Warm up tic/toc. +temp = tic(); elapsed = toc(); +temp = tic(); elapsed = toc(); +temp = tic(); elapsed = toc(); + +num_repeats = 11; +times = zeros(1, num_repeats); + +for k = 1:num_repeats + times(k) = tictocTimeExperiment(); +end + +t = min(times); +ttct = t; + +function t = tictocTimeExperiment +% Call tic/toc 100 times and return the average time required. + +elapsed = 0; +% Call tic/toc 100 times. +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); +tic(); elapsed = elapsed + toc(); + +t = elapsed / 100; + +function emptyFunction() + +function t = functionHandleCallOverhead(f) +% Return the estimated overhead, in seconds, for calling a function handle +% compared to calling a normal function. + +fcns = functions(f); +if strcmp(fcns.type, 'anonymous') + t = anonFunctionHandleCallTime(); +else + t = simpleFunctionHandleCallTime(); +end + +t = max(t - emptyFunctionCallTime(), 0); + +function t = simpleFunctionHandleCallTime +% Return the estimated time required to call a simple function handle to a +% function with an empty body. +% +% A simple function handle fh has the form @foo. + +persistent sfhct +if ~isempty(sfhct) + t = sfhct; + return +end + +num_repeats = 101; +% num_repeats chosen to take about 100 ms, assuming that +% timeFunctionHandleCall() takes about 1 ms. +times = zeros(1, num_repeats); + +fh = @emptyFunction; + +% Warm up fh(). +fh(); +fh(); +fh(); + +for k = 1:num_repeats + times(k) = functionHandleTimeExperiment(fh); +end + +t = min(times); +sfhct = t; + +function t = anonFunctionHandleCallTime +% Return the estimated time required to call an anonymous function handle that +% calls a function with an empty body. +% +% An anonymous function handle fh has the form @(arg_list) expression. For +% example: +% +% fh = @(thetad) sin(thetad * pi / 180) + +persistent afhct +if ~isempty(afhct) + t = afhct; + return +end + +num_repeats = 101; +% num_repeats chosen to take about 100 ms, assuming that timeFunctionCall() +% takes about 1 ms. +times = zeros(1, num_repeats); + +fh = @() emptyFunction(); + +% Warm up fh(). +fh(); +fh(); +fh(); + +for k = 1:num_repeats + times(k) = functionHandleTimeExperiment(fh); +end + +t = min(times); +afhct = t; + +function t = functionHandleTimeExperiment(fh) +% Call the function handle fh 2000 times and return the average time required. + +% Record starting time. +tic(); + +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); + +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); + +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); + +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); + +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); + +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); + +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); + +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); + +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); + +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); + +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); + +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); + +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); + +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); + +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); + +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); + +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); + +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); + +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); + +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); +fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); fh(); + +t = toc() / 2000; + +function t = emptyFunctionCallTime() +% Return the estimated time required to call a function with an empty body. + +persistent efct +if ~isempty(efct) + t = efct; + return +end + +% Warm up emptyFunction. +emptyFunction(); +emptyFunction(); +emptyFunction(); + +num_repeats = 101; +% num_repeats chosen to take about 100 ms, assuming that timeFunctionCall() +% takes about 1 ms. +times = zeros(1, num_repeats); + +for k = 1:num_repeats + times(k) = emptyFunctionTimeExperiment(); +end + +t = min(times); +efct = t; + +function t = emptyFunctionTimeExperiment() +% Call emptyFunction() 2000 times and return the average time required. + +% Record starting time. +tic(); + +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); + +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); + +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); + +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); + +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); + +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); + +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); + +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); + +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); + +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); + +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); + +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); + +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); + +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); + +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); + +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); + +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); + +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); + +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); + +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); + +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); +emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); emptyFunction(); + +t = toc() / 2000; + + diff --git a/togglestate.m b/togglestate.m new file mode 100644 index 0000000..8a17b4a --- /dev/null +++ b/togglestate.m @@ -0,0 +1,51 @@ +function [varargout] = togglestate(handles,property,togglelist,exactflag) +%togglestate - toggle a handle between states +% [newstate] = togglestate(handles,Property,[ListOfStates], [exactflag]) +% Toggle the Property of each Handle individually +% ListOfState is optional. Default is to toggle between 'on' and 'off' +% If exactflag == 'exact', string matching will be exact +% ex: +% togglestate + +if nargin<4 + exactflag=0; +else + exactflag=isequal(exactflag, 'exact'); +end + +if nargin<3 + togglelist={'on' ; 'off'}; +end +if nargin<2 + error('Handles and Property to set are mandatory') +end + +cellofchar=0; +try + char(togglelist); + cellofchar=1; +end + +% togglelist2=unique(togglelist); +% if length(togglelist2) ~= length(togglelist) +% warning('List of States has redundant elements, they will be removed!') +% [i,j]=ismember(togglelist2,togglelist) +% togglelist=togglelist(sort(j)) +% end +% togglelist(end+1)=togglelist(1); +ns=length(togglelist); + +for i=1:length(handles) + % get(handles(i), property), + newstate{i}=togglelist{1}; + for j=1:ns-1 + if (cellofchar & strmatch(togglelist{j},get(handles(i), property))) | (~cellofchar & isequal(togglelist{j},get(handles(i), property))) + newstate{i}=togglelist{j+1}; + end + end + set(handles(i), property, newstate{i}); +end +if nargout>1 + varargout=newstate; +end +return diff --git a/trim.m b/trim.m new file mode 100644 index 0000000..f32213a --- /dev/null +++ b/trim.m @@ -0,0 +1,59 @@ +function [Y] = trim(X,method,varargin) +%trim() - Trim data from its outliers (values are discarded) +% [Y] = trim(X,p) trims p% at both tails of the data +% [Y] = trim(X,[p1 p2]) trims p1% of the lower and p2% at the upper tail +% [Y] = trim(X,method) trims data from their outliers using various +% methods: +% +%INPUTS: +% X: data matrix (considered column-wise) +% p or [p1 p2]: scalars specify the percentages of data to trim. +% If p/p1/p2 are >= 1, trims an exact number of elements +% method = {'name' params }: Specify method used and the parameters +% { 'quantile'/'q' [ p ] } standard trimming (default behavior) +% { 'threshold'/'t' [ t ] } remove values above a given threshold p +% if p=[p1 p2], trimming is done between p1 (min) and p2 (max) +% Example +% >> hist([trim(randn(10000,2),'quantile',.95) randn(10000,1)],50) +% % shows the extreme 5% of (two) normal distributions getting +% % trimmed to ~2 in comparison with the unchanged 3rd one +% +% See also: quantile, stat/winsor + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2006 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2006-02-09 Creation +% KND 2008-10-13 Example in help +% KND 2010-02-18 Renamed clipping() as trim() +% ----------------------------- Script History --------------------------------- + +sX=size(X); +if prod(sX)==max(sX) + X=X(:); +else + X=X(:,:); +end +if nargin==2 && isnumeric(method) + varargin{1} = method; + method = 'quantile'; +end +switch method + case {'quantile', 'q'} + Y=quantile(abs(X),varargin{1},1); + Y=Y(:)'; + Y=repmat(Y, size(X,1),1); + Y=X.*(abs(X)=Y); + case {'threshold', 't'} + Y=X; + Y(Y>varargin{1}(end))=varargin{1}(end); + if length(varargin{1})>1 + Y(Y0, hide what is BELOW xyz(i) +% if dxyz(i)<0, hide what is ABOVE xyz(i) +% if dxyz(i)=0, depends whether xyz(i)<0 or >O (BrainStorm behavior) +% +% KND : 2005-09-18 : Created based on code from tessellation_manager + +h=[]; +xyz=[]; +dxyz=[]; + +if nargin>1 + idxargin=1; + if length(varargin{1})==1 & ishandle(varargin{1}) + h=varargin{1}; + idxargin=idxargin+1; + end + if idxargin<=nargin + xyz=varargin{idxargin}; + idxargin=idxargin+1; + end + if idxargin<=nargin + dxyz=varargin{idxargin}; + idxargin=idxargin+1; + end +end + +if isempty(h) + try + h=findTessellationHandles; + catch; + end; +end + +if isempty(xyz) + xyz = [NaN NaN NaN]; % Normalized euclidian coordinate threshold along which we want to trim out the surface view +end +if isempty(dxyz) + dxyz = [0 0 0]; % Trimming direction +end + +if ishandle(h) + vertices = get(h,'vertices'); + FaceVertexAlphaData = get(h,'FaceVertexAlphaData'); + alpha=get(h,'FaceAlpha'); + if isreal(alpha) + alpha=max(alpha); + alpha=min(alpha,1); + alpha=max(alpha,0); + else + alpha=1; + end + if isempty(FaceVertexAlphaData) + FaceVertexAlphaData = ones(size(vertices,1),1); + end + iNoModif = []; + for iCoord = 1:3 + vertx = vertices(:,iCoord)'; +% vertx = vertx-mean(vertx); +% vertx = vertx/max(abs(vertx)); + if isnan(xyz(iCoord)) + % do nothing + elseif dxyz(iCoord)==0 % native Brainstorm behavior + if xyz(iCoord) > 0 + iNoModif = [iNoModif,find(vertx < xyz(iCoord) )]; + elseif xyz(iCoord) < 0 + iNoModif = [iNoModif,find(vertx > xyz(iCoord) )]; + else + %do nothing + end + else + if dxyz(iCoord)>0 + iNoModif = [iNoModif,find(vertx > xyz(iCoord) )]; + else + iNoModif = [iNoModif,find(vertx < xyz(iCoord) )]; + end + end + + end + + if isempty(iNoModif) + set(h,'alphadatamapping','scaled',... + 'FaceVertexAlphaData',alpha*ones(size(vertices,1),1),.... + 'FaceAlpha',alpha,... + 'backfacelighting','lit') + else + + FaceVertexAlphaData(unique(iNoModif)) = alpha; + FaceVertexAlphaData(setdiff(1:end,iNoModif)) = 0; + set(h,'alphadatamapping','none',... + 'FaceVertexAlphaData',FaceVertexAlphaData,... + 'FaceAlpha','interp',... + 'backfacelighting','unlit') + end + + +end diff --git a/trimsurface_gui.m b/trimsurface_gui.m new file mode 100644 index 0000000..1441a7d --- /dev/null +++ b/trimsurface_gui.m @@ -0,0 +1,229 @@ +function []=trimsurface_gui(varargin) +% trimsurface_gui - GUI interface for trimsurface +% trimsurface(h) pops up a window for trimming surface whose handle is h + +if nargin>=1 + if ischar(varargin{1}) + action=varargin{1}; + varargin(1)=[]; + else + action='init'; + end +else + action='init'; +end +feval(sprintf('action_%s', action), varargin{:}); +return + +function action_init(varargin) +%%%%%%%%%%%%%%%%%%%%% +%%% General Info. %%% +%%%%%%%%%%%%%%%%%%%%% +Black =[0 0 0 ]/255; +LightGray =[192 192 192 ]/255; +LightGray2 =[160 160 164 ]/255; +MediumGray =[128 128 128 ]/255; +White =[255 255 255 ]/255; + +Title='Surface Trimmer'; +WindowStyle='normal'; +Interpreter='none'; +if nargin<=4, + Resize = 'off'; +end + +%%%%%%%%%%%%%%%%%%%%%%% +%%% Create TrimFig %%% +%%%%%%%%%%%%%%%%%%%%%%% +FigWidth=100;FigHeight=300; +FigPos(3:4)=[FigWidth FigHeight]; +FigColor=get(0,'Defaultuicontrolbackgroundcolor'); +TextForeground = Black; +if sum(abs(TextForeground - FigColor)) < 1 + TextForeground = White; +end + +TrimFig=dialog( ... + 'Visible' ,'on' , ... + 'Name' ,Title , ... + 'Pointer' ,'arrow' , ... + 'Units' ,'points' , ... + 'UserData' ,'' , ... + 'Tag' ,Title , ... + 'HandleVisibility','on' , ... + 'Color' ,FigColor , ... + 'NextPlot' ,'add' , ... + 'WindowStyle' ,WindowStyle, ... + 'Resize' ,Resize ... +); +Temp=get(0,'Units'); +set(0,'Units','points'); +ScreenSize=get(0,'ScreenSize'); +set(0,'Units',Temp); +FigPos(1)=(ScreenSize(3)-FigWidth)/2; +FigPos(2)=(ScreenSize(4)-FigHeight)/2; +FigPos(3)=FigWidth; +FigPos(4)=FigHeight; +set(TrimFig,'Position',FigPos); +set(TrimFig,'WindowButtonDownFcn','trimsurface_gui(''btndwn'', gcbf)'); + +tags='XYZ'; +setappdata(TrimFig, 'Tags', tags); +for i=1:3 + hui(i)=uicontrol('style', 'slider', 'unit', 'normalized', ... + 'position', [.15+(i-1)*0.3 .1 .1 .75 ],'enable', 'on',... + 'callback', 'trimsurface_gui(''btndwnslider'', gcbf, gcbo)',... + 'tag', [tags(i)]); +end +set(hui, 'max', 1) +set(hui, 'min', -1) +set(hui, 'value', 0) +for i=4:6 + hui(i)=uicontrol('style', 'checkbox', 'unit', 'normalized', ... + 'position', [.15+(i-4)*0.3 .85 .1 .05 ]); + set(hui(i), 'callback', 'trimsurface_gui(''chkbox'',gcbf, gcbo)'); + set(hui(i), 'units', 'pixels'); + set(hui(i), 'position', get(hui(i), 'position').*[1 1 0 0]+[0 0 20 20]); + set(hui(i),'units', 'normalized') + set(hui(i),'tag', ['d' tags(i-3)]) + set(hui(i),'Tooltip', 'Hide the other side of the plane'); +end +for i=7:9 + hui(i)=uicontrol('style', 'checkbox', 'unit', 'normalized', ... + 'position', [.25+(i-7)*0.3 .1 .075 .75 ], 'value',1, ... + 'callback', 'trimsurface_gui(''toggleslider'', gcbf, get(gcbo, ''UserData''))',... + 'tag', ['c' tags(i-6)], 'UserData' , hui(i-6),... + 'Tooltip', 'Activate / Deactivate this cut'); +end + + +hui(end+1)=uicontrol('style', 'edit', 'unit', 'normalized', 'enable', 'off', ... + 'position', [.1 .9 .8 .05 ], 'min', 0, 'max', 0); +if nargin>0 + hp=varargin{1}; +else + hp=findTessellationHandles; +end +hui(end+1)=uicontrol('style', 'pushbutton', 'string', 'Unhide all', ... + 'unit', 'normalized', 'position', [.15 .05 .7 .05 ], ... + 'callback', 'trimsurface_gui(''unhide'',gcbf)'); + +hui(end+1)=uicontrol('style', 'pushbutton', 'string', 'Use OpenGL', ... + 'unit', 'normalized', 'position', [.15 .00 .7 .05 ], ... + 'UserData',get(get(hp, 'Parent'), 'Parent'), 'callback', 'set(get(gcbo, ''UserData''), ''Renderer'', ''opengl'');'); + +action_selectsurface(TrimFig,hp) +action_adjustsliders(TrimFig,hp); +return + +function action_adjustsliders(varargin) +hf=varargin{1}; +hp=varargin{2}; +tags=getappdata(hf,'Tags'); +dim=get(hp, 'Vertices'); +dim=[min(dim);max(dim)]; +for i=1:3 + hui=findobj(hf, 'style', 'slider', 'tag', tags(i)); + set(hui, 'min', dim(1,i), 'max', dim(2,i), 'value', mean(dim(:,i))) + setappdata(hf, tags(i), mean(dim(:,i))); + setappdata(hf, ['d' tags(i)], 1); +end + +function action_trim(varargin) +hf=varargin{1}; +tags=getappdata(hf, 'Tags'); +if nargin<2 + for i=1:3 + xyz(i)=getappdata(hf, tags(i)); + end +else + xyz=varargin{2}; +end +if nargin<3 + for i=1:3 + dxyz(i)=getappdata(hf, ['d' tags(i)]); + end +else + dxyz=varargin{3}; +end +dxyz=2*dxyz-1; +% hui(i)=findobj(hf, 'style', 'slider', 'tag', tags(i)); +hp=getappdata(hf, 'Surface'); +trimsurface(hp,xyz,dxyz) + + +function action_selectsurface(varargin) +hf=varargin{1}; +hp=varargin{2}; +setappdata(hf, 'Surface', hp) + + +function action_btndwn(varargin) +%Right click to toggle slider +hf=varargin{1}; +if ~isequal(get(hf, 'SelectionType'),'alt') +return +end +if nargin>=2 + ho=varargin{2}; +else + ho=overobj('uicontrol') + if ~isequal(get(ho, 'style'), 'slider') + return + end +end +if isequal(get(ho, 'enable')=='on') + set(ho, 'enable','off') + setappdata(hf,get(ho, 'tag'), 'NaN') +else + set(ho, 'enable','on') + setappdata(hf,get(ho, 'tag'), 0) +end +action_update(hf) + +function action_toggleslider(varargin) +%Right click to toggle slider +hf=varargin{1}; + ho=varargin{2}; +if isequal(get(ho, 'enable'),'on') + set(ho, 'enable','off') + setappdata(hf,get(ho, 'tag'), NaN) +else + set(ho, 'enable','on') + setappdata(hf,get(ho, 'tag'),get(ho, 'Value')) +end +action_update(hf) + + +function action_btndwnslider(varargin) +hf=varargin{1}; +ho=varargin{2}; +setappdata(hf,get(ho, 'tag'), get(ho, 'Value')) +action_update(hf) + +function action_chkbox(varargin) +hf=varargin{1}; +ho=varargin{2}; +setappdata(hf,get(ho, 'tag'), get(ho, 'Value')) +action_update(hf) + + +function action_update(varargin) +hf=varargin{1}; +tags=getappdata(hf, 'Tags'); +for i=1:3 + xyz(i)=getappdata(hf, tags(i)); + if ~isnan(xyz(i)) + set(findobj(hf, 'tag', [tags(i)]), 'value', xyz(i)); + end + dxyz(i)=getappdata(hf, ['d' tags(i)]); + set(findobj(hf, 'tag', ['d' tags(i)]), 'value', dxyz(i)); +end +sxyz=[sprintf('%2.2f ', xyz)]; +set(findobj(hf, 'style', 'edit'), 'string', sxyz) +action_trim(hf, xyz, dxyz) + +function action_unhide(varargin) +hf=varargin{1}; +hp=getappdata(hf, 'Surface'); +trimsurface(hp,[NaN NaN NaN]) diff --git a/twxticklabel.m b/twxticklabel.m new file mode 100644 index 0000000..654f337 --- /dev/null +++ b/twxticklabel.m @@ -0,0 +1,75 @@ +function hh = twxticklabel(ax) +% +% TWXTICKLABEL tweaks the xtick labels of the current axis, so that fonts +% are presereved when the figure is exported to eps (e.g. for inclusion +% into LaTeX). +% +% TWXTICKLABEL(AX) applies TWXTICKLABEL to the axis with handle AX. +% +% H = TWXTICKLABEL returns in H the handles of all XTick labels so the +% user can change every property which is possible with a text object created by text command. +% Following are a few examples: +% +% h = twxticklabel; +% set(h,'fontsize',12,'fontname','courier'); % to change font namae and size +% set(h,{'string'},char('10','-3','pi','he','90','ju')); % to change ticklabels +% set(h,'rotation',90); % to change angle +% +% The two m-files TWXTICKLABEL and TWYTICKLABEL motivated by the following problems in the way Matlab handles tick labels. +%1) +% Matlab provides no handles to xtick labels and ytick labels; meaning that you can't change their +% properties separately, but only by changing axis properties. For example, the only way to change +% the fontname of xtick labels is by setting the fontname property of the axis, thus changing the fontname +% of all it object, which may not be desirable. If a user wants to rotate the tick labels, one would +% like to be able to do that with commands like set(handle, 'rotation', 90); but that is only possible if you have +% the handle. +% +%2) +% Tick labels do not interpret TeX/LaTeX character sequences; What if you want LaTeX expression $W_i(t)$. +% +%3) +% Even if you are happy with changing the properties of the whole axis, another problem arises when the Matlab +% figure is exported to eps for inclusion into LaTeX. It sometimes changes the fonts (set by the user) of the +% xticklabels in an unpredictable way (fortunately, this does not happen with xlabel, ylabel, title and other text object). +% +% +% In these two files, I have tried to create ticklabels by using text command. Handles are returned for all tick labels, +% so the user can change every property which is possible with a text object created by text command. + +if nargin == 0 + ax = gca; +end + +xtick = get(ax,'XTick')'; + +if isempty(xtick) + error('''XTick'' found empty') +end + +a = 0.5; +hxlabel = get(ax,'xlabel'); +set(hxlabel,'Units','data'); +xlabpos = get(hxlabel,'position'); +temp = get(ax,'XTickLabel'); +if iscell(temp) + xticklab = cellstr(strjust(strvcat(temp), 'center')); +elseif ischar(temp) + xticklab = strjust(temp, 'center'); +end +set(ax,'XTickLabel',[]) + +xlabpos2 = get(hxlabel,'position'); +ylims = get(ax,'ylim'); +xtickloc = a*xlabpos2(2) + (1-a)*ylims(1); + +h = text(xtick, xtickloc(ones(size(xtick))), xticklab); + +set(h, 'fontsize', get(ax,'fontsize'), ... + 'HorizontalAlignment', 'center', 'VerticalAlignment', 'top') + +xlabpos(2) = xlabpos(2) + a*(xlabpos(2)-xlabpos2(2)); +set(hxlabel, 'position',xlabpos) + +if nargout == 1 + hh = h; +end \ No newline at end of file diff --git a/uigetpathfile.m b/uigetpathfile.m new file mode 100644 index 0000000..d6ec295 --- /dev/null +++ b/uigetpathfile.m @@ -0,0 +1,10 @@ +function [ff]=uigetpathfile(varargin) +[f,p]=uigetfile(varargin{:}); +if isequal(f,0) | isequal(p,0) + ff=0; + return +end +ff=fullfile(p,f); +if length(dbstack)==1 % (evalin('caller','mfilename')) + disp(ff) +end \ No newline at end of file diff --git a/usbpath.m b/usbpath.m new file mode 100644 index 0000000..e6511df --- /dev/null +++ b/usbpath.m @@ -0,0 +1,10 @@ +function [USBDIR]=usbpath() +% USBPATH - Retrieves directory of the USB drive on the current OS +USBDIR=''; +[hostname,hostname]=system('hostname'); +switch upper(deblank(hostname)) + case 'KARIMND' + USBDIR = 'I:/'; + case 'LENA138.LENA.CHUPS.JUSSIEU.FR' + USBDIR = '/mount/usb/'; +end diff --git a/vectvec.m b/vectvec.m new file mode 100644 index 0000000..02036d8 --- /dev/null +++ b/vectvec.m @@ -0,0 +1,4 @@ +function [X]=vectvec(X) +% vectvec - make array X into one column, same as X(:) +X=X(:); +% see also: vec() in BST diff --git a/vertvec.m b/vertvec.m new file mode 100644 index 0000000..c114c43 --- /dev/null +++ b/vertvec.m @@ -0,0 +1,7 @@ +function [X]=vertvec(X) +% vertvec - make array X into one column, same as X(:) +% [Y]=vertvec(X) is equivalent to Y=X(:) + +X=X(:); + +%see: vec() in BrainStorm \ No newline at end of file diff --git a/vidal.m b/vidal.m new file mode 100644 index 0000000..7b65bb4 --- /dev/null +++ b/vidal.m @@ -0,0 +1,77 @@ +if ~ exist('fic_ds') + fic_ds='/pclxserver/home/ndiaye/DAV/rawdata/S7/aud01.ds' +end + +if ~ exist('F') +[F,Channel,imegsens,ieegsens,iothersens,irefsens,grad_order_no,no_trials,filter,Time, RunTitle] = ds2brainstorm(fic_ds,0); +end + +t=textread('ClassFile.cls','%s'); +n=strmatch('BAD',t) +n=strmatch('NUMBER',t(n:end))+n-1 + +nbad=str2num(t{n(1)+3}); +badtrials=[]; +for i=1:nbad +j=i+n(2); +badtrials=[badtrials str2num(t{j})]; +end + +MARQUEURS={'Tr22' 'Tr23' 'Tr24' 'Tr25' 'Tr26'} +for i=1:length(MARQUEURS) +t=textread('MarkerFile.mrk','%s'); +mark(i).name=MARQUEURS{i} +n=strmatch(MARQUEURS{i},t); + + + n=strmatch('SAMPLES',t(n:end))+n-1; +mark(i).nb=str2num(t{n(1)+1}) + +for j=1:mark(i).nb +k=2*j+n(2)+7; +mark(i).tr(j)=str2num(t{k}); +end + +end + + +for i=mark(5).tr+1;, + if ~ find(badtrials==i), + keep=[keep i]; +end +end + 'Essais à tester :' +keep + +selection=ieegsens; +clear G +clear allG +clear pente +j=1; +allG(:,:,j)=F{1}(selection,:); +for i=keep(2:end) + j=j+1; +% G=G + F{i}(selection,:); +allG(:,:,j)= F{i}(selection,:); + +end +% G=G/length(keep); +G=mean(allG,3); +for i=1:size(G,1) + pente(i,1)=linearfit(G(i,64:500), Time(64:500)); + pente(i,2)=linearfit(G(i,501:625), Time(501:625)); +end + + [dpente,capteur]=sort(pente(:,1)-pente(:,2)); + +subplot(3,1,1) +bar(pente(capteur(2:end),1)) +subplot(3,1,2) +bar(pente(capteur(2:end),2)) +subplot(3,1,3) +bar(dpente(2:end)) +subplot(3,1,3) +hold on +bar(std(std(allG(capteur(2:end),:,:),[],3),[],2),'r') +hold off +capteur(1:10) diff --git a/view3d.m b/view3d.m new file mode 100644 index 0000000..e7e7bc4 --- /dev/null +++ b/view3d.m @@ -0,0 +1,48 @@ +% view3d() - a simple 3D viewer +% Use: +% >> view3d(FV) +% >> view(vertices, faces) +% FV might be a structure with fields .vertices and .faces +% +% Setting options: +% >> view3d(FV, options) +% >> view(vertices, faces, options) +% e.g.: +% >> view(vertices, faces, 'FaceColorCData', rand(length(vertices),1)) +function [ p ] = view3d(v,f, varargin) +if isfield(v, 'vertices') + for i=1:length(v) + + view3d(v(i).vertices, v(i).faces, f, varargin{:}) + hold on + zoom(1/1.1) + delete(findobj(gca, 'type', 'light')) + delete(findobj(gca, 'type', 'light')) + end + axis image + if length(findobj(gca, 'type', 'light')) == 0 + light('Position',[-1 -1 1],'Style','infinite'); + light('Position',[1 1 0],'Style','infinite'); + end +else + + if isempty(varargin) + options={'FaceColor' , [0.7 .65 .65]}; + else + options=varargin; + end + + p=patch('Vertices', v, 'Faces', f,'EdgeColor','none','FaceLighting','phong','FaceAlpha','interp', options{:}); + set(p,'DiffuseStrength',.6,'SpecularStrength',0,'AmbientStrength',.4,'SpecularExponent',5) + view(3); + grid on; + lighting phong; + alpha(1); + axis on; + axis image; + if length(findobj(gca, 'type', 'light')) == 0 + light('Position',[-1 -1 1],'Style','infinite'); + light('Position',[1 1 0],'Style','infinite'); + end + zoom(1.1); +end \ No newline at end of file diff --git a/viewer3d.m b/viewer3d.m new file mode 100644 index 0000000..cdd1b10 --- /dev/null +++ b/viewer3d.m @@ -0,0 +1,48 @@ +% viewer3d() - a simple 3D viewer +% Use: +% >> viewer3d(FV) +% >> view(vertices, faces) +% FV might be a structure with fields .vertices and .faces +% +% Setting options: +% >> viewer3d(FV, options) +% >> view(vertices, faces, options) +% e.g.: +% >> view(vertices, faces, 'FaceColorCData', rand(length(vertices),1)) +function [ p ] = viewer3d(v,f, varargin) +if isfield(v, 'vertices') + for i=1:length(v) + + view3d(v(i).vertices, v(i).faces, f, varargin{:}) + hold on + zoom(1/1.1) + delete(findobj(gca, 'type', 'light')) + delete(findobj(gca, 'type', 'light')) + end + axis image + if length(findobj(gca, 'type', 'light')) == 0 + light('Position',[-1 -1 1],'Style','infinite'); + light('Position',[1 1 0],'Style','infinite'); + end +else + + if isempty(varargin) + options={'FaceColor' , [0.7 .65 .65]}; + else + options=varargin; + end + + p=patch('Vertices', v, 'Faces', f,'EdgeColor','none','FaceLighting','phong','FaceAlpha','interp', options{:}); + set(p,'DiffuseStrength',.6,'SpecularStrength',0,'AmbientStrength',.4,'SpecularExponent',5) + view(3); + grid on; + lighting phong; + alpha(1); + axis on; + axis image; + if length(findobj(gca, 'type', 'light')) == 0 + light('Position',[-1 -1 1],'Style','infinite'); + light('Position',[1 1 0],'Style','infinite'); + end + zoom(1.1); +end \ No newline at end of file diff --git a/vline.m b/vline.m new file mode 100644 index 0000000..a7f7a2f --- /dev/null +++ b/vline.m @@ -0,0 +1,66 @@ +function varargout = vline(x,varargin) +%VLINE - Create a vertical line on a plot +% [h] = vline(x) +% Adds as many vertical lines on the current plot as there are values in x. +% +% See also: line, hline + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2006 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2006-02-02 Creation +% KND 2009-10-22 Corrected bug with single argument for linestyle +% ----------------------------- Script History --------------------------------- + +%y=max(abs(get(gca, 'Ylim')))*[-1 1]; +%y=*10; +y=get(gca, 'Ylim'); +if isequal(get(gca, 'Yscale'),'log') + % not a perfect trick to deal with log scales... + yt = get(gca,'Ytick'); + y = [yt(1).^2/yt(2) y(2)]; +end +x=x(:); +x=[x*ones(1,2)]'; +y=repmat(y,size(x,2),1)'; + +holdstate=ishold; +hold on; +if nargin>1 && ischar(varargin{1}) && mod(nargin-1,2)==1 + opt=[]; + c = ismember(varargin{1}, 'bgrcmykw'); + m = ismember(varargin{1}, '.ox+*sdv^<>ph'); + if any(c) + opt=[{'Color', varargin{1}(c)} opt]; + end + if any(m) + opt=[{'Marker', varargin{1}(m)} opt]; + end + if any(~c & ~m) + opt=[{'LineStyle', varargin{1}(~c & ~m)} opt]; + end + varargin(1)=[]; + varargin = [opt varargin]; +end +h=line(x,y,varargin{:}); +if ~holdstate + hold off; +end +LineStyles={'-', ':', '--', '-.'}; +if nargin>3 + if isempty(strmatch('linestyle', lower(varargin(cellfun('isclass', varargin, 'char'))))) && ... + isempty(intersect(LineStyles,lower(varargin(cellfun('isclass', varargin, 'char'))))) + for i=1:length(h); + set(h(i), 'LineStyle', LineStyles{mod(ceil(i/size(get(gca, 'ColorOrder'),1))-1,4)+1}); + end + end +end +if nargout==0 + return +end +varargout={h}; diff --git a/wheelzoom.m b/wheelzoom.m new file mode 100644 index 0000000..88df44d --- /dev/null +++ b/wheelzoom.m @@ -0,0 +1,110 @@ +function varargout = wheelzoom(action,varargin) +%wheelzoom - Control axes zoom using mouse wheel +% wheelzoom(ha) +% wheelzoom(ha,factor) +% +% Example +% >> wheelzoom(gca) +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2005 +% This program is free software; you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation; either version 2 of the License, or +% (at your option) any later version. +% +% ----------------------------- Script History --------------------------------- +% KND 2005-12-15 Creation +% +% ----------------------------- Script History --------------------------------- + +if nargin<1 + action='init'; +elseif isa(varargin{1}, 'java') && isequal(get(varargin{1}, 'Type'), 'java.awt.event.MouseWheelEvent') + action='wheel'; +else + +end +'weez' +out=eval(sprintf('action_%s(varargin{:});', action)); +if nargout>0 + varargout=out; +end +return + +function [varargout]=action_init(varargin) +if nargin<1 + ha=gca; +else + ha=varargin{1}; +end +if nargin<1 + ha=gca; +else + ha=varargin{1}; +end +if nargin<2 + wz.factor=1.5; +else + wz.factor=varargin{2}; +end + +% Adapted from Nanne van der Zijpp's setMouseWheel +jobj=javax.swing.JLabel; +jobj.setOpaque(0); +[jobj,h] = javacomponent(jobj,[],get(ha,'Parent')); +% drawnow; +for i=1:100; % Number of attempts to get the RootPane + RootPane=jobj.getRootPane; + pause(.01); + if ~isempty(RootPane); break;end +end +delete(h); +% setappdata(ha,'RootPane',RootPane); +wz.RootPane=RootPane; +set(RootPane,'MouseWheelMovedCallback',[ str2func(sprintf('%s', mfilename))]); +setappdata(ha,mfilename,wz); +varargout={{}}; + +function []=action_setfactor(ha,factor) +if nargin<2 + error('Wrong number of inputs. Needs axes handle & zoom factor') +end +wz=getappdata(ha,mfilename); +if isempty(wz) + error('No WheelZoom for this axes handle!') +end +wz.factor=factor; +setappdata(ha,mfilename,wz); + +function [varargout]=action_wheel(varargin) +varargout={{}}; +ha=overobj('axes'); +if isempty(ha) + return +end +wz=getappdata(ha,mfilename); +if isempty(wz) + return +end +updown=get(varargin{1}, 'WheelRotation'); +y=get(ha, 'Ylim'); +y(3)=(y(2)+y(1))/2; +y(4:5)=wz.factor^updown*(y(2)-y(1))/2*[-1 +1]; +if strmatch('on', get(varargin{1}, 'ControlDown')) + % Control Button for pointer centered zooming + c=get(gca, 'CurrentPoint'); + y(3)=c(3); +end +if strmatch('on', get(varargin{1}, 'ShiftDown')) + % Shift Button for zero-centered zooming + c=get(gca, 'CurrentPoint'); + y(3)=0; + +end + +% y=c(3)+wz.factor^updown*(y(2)-y(1))/2*[-1 +1]; +% +set(ha, 'Ylim', y(3)+y(4:5)) diff --git a/whichLoop.m b/whichLoop.m new file mode 100644 index 0000000..c52f529 --- /dev/null +++ b/whichLoop.m @@ -0,0 +1,60 @@ +function [v]=whichLoop(x,b,n,backward) +% whichLoop - get the n-th "pseudo-bit" of a number x in pseudo-base b (little-endian) +% +% [v]=whichLoop(x,b,[n], [backward]) +% +% Ex. you fill an array using "for" loops: +% >> for i=1:3, for j=1:7, for k=1:5, x=[x, ...], end, end, end +% if you want to know at which index of "j" (the 2nd loop index) +% the, say, 12-th value in x corresponds: +% >> whichLoop(12, [3 7 5], 2) +% returns: 3 +% +% Cool, isn't it? +% +% [n] is optional, if not given, the whole list is output +% >> whichLoop(1, [3 7 5]) -> [1 1 1] that is i=1, j=1, k=1 +% >> whichLoop(51,[3 7 5]) -> [2 4 1] that is i=2, j=4, k=1 +% +% [backward] is 0 (default) or 1. In the latter case, treat b as the +% last loops of some upper unspecified loops. +% i.e. "the heaviest (pseudo)bits are discarded" +% i.e. counting starts from the most inner loops +% >> for i=1:???, ..., for j=1:7, for k=1:5 ... end,end,...,end +% >> whichLoop(12, [ 6 4 7 5 ], 2, 1) -> [3] that is: j=3 +% (whatever the number of loops in '?') +% >> whichLoop(12, [ NaN 7 5 ],1:2,1) +% returns: [k=2 j=2] +% Note that in that case, the order of the outputs is also reversed. + +if nargin<4 + backward=0; +end +if nargin<3 + n=[]; +end +if isempty(n) + n=1:length(b); +end +n=n(:); +if not(backward) + if x < 1 | x > prod(b) + error([mfilename ': impossible x input according to the loops b' ]) + end +else + % little endian + n=length(b)-n+1; +end + +k=fliplr(1./cumprod([1 b(end:-1:2)])); +x=floor((x-1)*k); +v=rem(x,b)+1; + + +if any(n>length(b)) | any(n<1) + error(sprintf('%s: asked rank (n=%d) beyond the number of loops', ... + mfilename,n)) +end +v=v(n); + +return diff --git a/whichRank.m b/whichRank.m new file mode 100644 index 0000000..cad31e3 --- /dev/null +++ b/whichRank.m @@ -0,0 +1,63 @@ +function [v]=whichRank(x,b,n,backward) +% whichRank - get the n-th "pseudo-bit" of a number x in pseudo-base b (little-endian) +% +% [v]=whichRank(x,b,[n], [backward]) +% +% Ex. +% >> for i=1:3, for j=1:7, for k=1:5, x=[x, ...], end, end, end +% if you want to know at which rank in "j" (the 2nd loop index) +% the, say, 12-th value in x corresponds: +% >> whichrank(12, [3 7 5], 2) +% returns: 3 +% +% Cool, isn't it? +% +% [n] is optional, if not given, the whole list is output +% >> whichrank(1, [3 7 5]) -> [1 1 1] that is i=1, j=1, k=1 +% >> whichrank(51,[3 7 5]) -> [2 4 1] that is i=2, j=4, k=1 +% +% [backward] is 0 [default] or 1. In the latter case, treat b as the +% last loops of some upper unspecified loops. +% i.e. "the heaviest pseudo-bits are discarded": +% >> for i=1:???, ..., for j=1:7, for k=1:5 ... end +% >> whichrank(12, [ 6 4 7 5 ], 2, 1) +% returns : 3 (whatever the loops in '?') + + +if nargin<4 + backward=0; +end +if nargin<3 + n=[]; +end + +if not(backward) + if x < 1 | x > prod(b) + error([mfilename ': impossible x input according to the loops b' ]) + end +else + n=length(b)-n+1; +end + + +k=fliplr(1./cumprod([1 b(end:-1:2)])); +x=floor((x-1)*k); +v=rem(x,b)+1; + +if not(isempty(n)) + if n>length(b) | n<1 + error(sprintf('%s: asked rank (n=%d) beyond the number of loops', ... + mfilename,n)) + end + v=v(n); +end + +return + + +v=rem(x,b(n))+1; +return +k=floor(x/k)-1 +v=rem(k,b(n))+1; +return + diff --git a/windfind.m b/windfind.m new file mode 100644 index 0000000..4c6acb2 --- /dev/null +++ b/windfind.m @@ -0,0 +1,64 @@ +function h = windfind(name); +%WINDFIND - Find or create a particular window using it's NAME property +% function h = windfind(name); +% find the first window whos 'Name' is name. +% If no such window exists, create it. +% h is the handle of the window returned. + +% ---------------------- 27-Jun-2005 10:46:04 ----------------------- +% ------ Automatically Generated Comments Block Using AUTO_COMMENTS_PRE7 ------- +% +% CATEGORY: Utility - General +% +% At Check-in: $Author: Mosher $ $Revision: 16 $ $Date: 6/27/05 9:00a $ +% +% This software is part of BrainStorm Toolbox Version 27-June-2005 +% +% Principal Investigators and Developers: +% ** Richard M. Leahy, PhD, Signal & Image Processing Institute, +% University of Southern California, Los Angeles, CA +% ** John C. Mosher, PhD, Biophysics Group, +% Los Alamos National Laboratory, Los Alamos, NM +% ** Sylvain Baillet, PhD, Cognitive Neuroscience & Brain Imaging Laboratory, +% CNRS, Hopital de la Salpetriere, Paris, France +% +% See BrainStorm website at http://neuroimage.usc.edu for further information. +% +% Copyright (c) 2005 BrainStorm by the University of Southern California +% This software distributed under the terms of the GNU General Public License +% as published by the Free Software Foundation. Further details on the GPL +% license can be found at http://www.gnu.org/copyleft/gpl.html . +% +% FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE +% UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY +% WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF +% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY +% LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE. +% ------------------------ 27-Jun-2005 10:46:04 ----------------------- + +% ----------------------------- Script History --------------------------------- +% Author 1994 John C. Mosher +% 3/3/94 Author +% 19-May-2004 JCM Comments Cleaning +% ----------------------------- Script History --------------------------------- + +if(~isstr(name)), + error('WINDFIND: input argument must be string'); +end + +hw = get(0,'children'); % all open windows +hw = sort(hw); % in increasing window order + +for i = 1:length(hw), + s = get(hw(i),'Name'); + if(strcmp(deblank(s),deblank(name))), + h = hw(i); + return; + end +end + +% we exited out without a match, make the window +h = figure; +set(h,'Name',name); +return + diff --git a/wwhich.m b/wwhich.m new file mode 100644 index 0000000..681dd2f --- /dev/null +++ b/wwhich.m @@ -0,0 +1,78 @@ +function out=wwhich(s1,s2) + +% WWHICH Locate m-files using wildcards +% Works exactly as WHICH, but you can +% use wild cards. +% +% out is a cell whenever 2 or more +% matches are found or if the flag +% -all was used +% +% Note: a function may be shawdowed, +% use the flag -all to locate them +% +% See WHICH for more info +% +% by Lucio Andrade + +if length(strmatch(strvcat('PCWIN'),computer)) + pathseparator=';'; +elseif length(strmatch(strvcat('SUN4','SOL2','LNX86','MAC64'),computer)) + pathseparator=':'; +else + warning('I''m not sure of the path separator for this machine.') + pathseparator=':'; + disp('I''ll use '':'' as a path separator') +end + +if nargin<1 + error('Not enough input arguments.') +end + +p=path; +out=cell(0); + +h=[0 find(p==pathseparator) length(p)+1]; +for i=length(h):-1:2 + direc= p(h(i-1)+1:h(i)-1); + a=dir([direc '/' s1 '.m']); + for j=1:length(a) + if nargin>1 + outtmp=which(a(j).name,s2); + if iscell(outtmp) + out=[out;outtmp]; + else + out=[out;{outtmp}]; + end + else + outtmp=which(a(j).name); + out=[out;{outtmp}]; + end + end +end + +%take out repetitions +todel=[]; +for i=1:length(out) + for j=i+1:length(out) + if strcmp(out{i},out{j}) todel=[todel j]; end + end +end +out(unique(todel))=[]; + +%change to string when needed +if (nargin==1) & (length(out)==1) + out=out{1}; +end + +if nargout==0 + if iscell(out) + for i=1:length(out) + disp(out{i}) + end + else + disp(out) + end + clear out +end + diff --git a/x2cell.m b/x2cell.m new file mode 100644 index 0000000..f47c964 --- /dev/null +++ b/x2cell.m @@ -0,0 +1,4 @@ +function [x]=x2cell(x) +% x2cell - make sure x is cell. DEPRECATED by: forcecell() +warning('MATLAB:x2cell is DEPRECATED') +x=forcecell(x); diff --git a/x2char.m b/x2char.m new file mode 100644 index 0000000..1fe87b4 --- /dev/null +++ b/x2char.m @@ -0,0 +1,58 @@ +function S = x2char(X) +%X2CHAR - Convert data to formatted string output +% [S] = x2char(X) +% +% Example +% >> x2char(dir) +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2009 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2009-10-01 Creation +% +% ----------------------------- Script History --------------------------------- + +if isnumeric(X) + if isempty(X) + S = '[]'; + elseif numel(X)>10 + S = sprintf('[[%s ... %s]]',x2char(X(1)), x2char(X(end))); + elseif numel(X)>1 + if numel(X) == size(X,1) + S = sprintf('[%s\b\b\b]',sprintf('%g ; ',X)); + elseif numel(X) == size(X,2) + S = sprintf('[%s\b\b\b]',sprintf('%g , ',X)); + else + S = sprintf('[[%s\b]]',sprintf('%g ',X)); + end + else + S = sprintf('%g',X); + end +elseif ischar(X) + if isempty(X) + S = ''; + else + S = sprintf('%s',X); + end +elseif islogical(X) + if X + S = 'false'; + else + S = 'true'; + end +elseif iscell(X) + if isempty(X) + S = '{}'; + else + S = '{...}'; + end +else + S = '???'; +end \ No newline at end of file diff --git a/xjview.m b/xjview.m new file mode 100644 index 0000000..bce66ca --- /dev/null +++ b/xjview.m @@ -0,0 +1,8669 @@ +function xjview(varargin) +% xjview, version 4 +% +% usage 1: xjview (no argument) +% for displaying a result img file, or multiple image files, +% (which will be loaded later) and changing p-value or t/f-value +% usage 2: xjview(imagefilename) +% for displaying the result img file and changing p-value or +% t-value +% Example: xjView spmT_0002.img +% xjView('spmT_0002.img') +% xjView mymask.img +% usage 3: xjview(imagefilename1, imagefilename2, ...) +% for displaying the result img files and changing p-value or +% t/f-value +% Example: xjView spmT_0002.img spmT_0005.img spmT_0007.img +% xjView('spmT_0002.img', 'spmT_0003.img', 'spmT_0006.img') +% xjView myMask1.img myMask2.img myMask3.img +% usage 4: xjview(mnicoord, intensity) +% for displaying where are the mni coordinates +% mnicoord: Nx3 matrix of mni coordinates +% intensity: (optional) Nx1 matrix, usually t values of the +% corresponding voxels +% Example: xjView([20 10 1; -10 2 5],[1;2]) +% xjView([20 10 1; -10 2 5]) +% Note: to use xjview this way, you may need to modify the value +% of M and DIM in the begining of xjview.m +% +% http://people.hnl.bcm.tmc.edu/cuixu/xjView +% +% by Xu Cui and Jian Li 2/21/2005 +% last modified: 02/18/2007 (add colorbar max control) +% last modified: 11/16/2006 (keyboard shortcut for open image and open roi file) +% last modified: 06/16/2006 (spm5 compatible) +% last modified: 05/30/2006 (left/right flip, path of mask.img and templateFile.img) +% last modified: 05/08/2006 (debug CallBack_volumePush function, change handles.intensity{1} to intensity) +% last modified: 04/03/2006 (modify tr) +% last modified: 12/28/2005 (modify SPM process) +% +% Thank Sergey Pakhomov for sharing his database (MNI Space Utility). +% Thank Yuval Cohen for the maximize figure function (maximize.m) +% + +% TODO +% Send SPM to workspace + +warnstate = warning; +warning off; + +% pre-set values +% important! you need compare the display of xjview and spm. If you find +% xjview flipped the left/right, you need to set leftrightflip = 1; +% otherwise leave it to 0. +leftrightflip = 0; +% leftrightflip = 1 ; %must be on in the CMU... +% Now with spm-flip = 1; xjview-flip=1 + +% You only need to change M and DIM when you want to use xjview under +% 'usage 4'. +M = ... + [-4 0 0 84;... + 0 4 0 -116;... + 0 0 4 -56;... + 0 0 0 1]; +DIM = [41 48 35]'; +TR = 2; % you don't need to set TR is you only use the viewing part of xjview + +% system settings +try + spmdir = spm('dir'); + spm('defaults', 'fmri'); +catch + disp('Please add spm path.'); + warning(warnstate(1).state); + return +end + +if ispc + os = 'windows'; +elseif isunix + os = 'linux'; +else + warndlg('I don''t know what kind of computer you are using. I assumed it is unix.', 'What computer are you using?'); + os = 'linux'; +end +screenResolution = get(0,'ScreenSize'); + +xjviewpath = fileparts(which('xjview')); + +% pre-set values +pValue = 0.001; +intensityThreshold = 0; +clusterSizeThreshold = 5; + + +% Appearance Settings +figurePosition = [0.100, 0.050, 0.660, 0.880]; +sectionViewPosition = [0.5,0.61,0.45,0.45]; +glassViewAxesPosition = [0.000, 0.600, 0.464, 0.400]; +if screenResolution(3) <= 1024 + figurePosition = [0.100, 0.050, 0.700, 0.900]; + sectionViewPosition = [0.5, 0.61, 0.45, 0.46]; + glassViewAxesPosition = [0.000, 0.600, 0.464, 0.400]; +end + +left = 0.01; +editBoxHeight = 0.05; +editBoxWidth = 0.200; +editBoxLeft = 0.100; + +controlPanelPosition = [left, 0.080, 0.500, 0.500]; +stretchMatrix = diag([controlPanelPosition(3),controlPanelPosition(4),controlPanelPosition(3),controlPanelPosition(4)]); +controlPanelOffset = controlPanelPosition' .* [1,1,0,0]'; +heightUnit = 0.055; + +sliderPosition = stretchMatrix*[0.000, 0*heightUnit, 1.000, editBoxHeight]' + controlPanelOffset; +pValueTextPosition = stretchMatrix*[0.000, 1*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +pValueEditPosition = stretchMatrix*[0.110, 1*heightUnit, editBoxWidth*4/3, editBoxHeight]' + controlPanelOffset; +intensityThresholdTextPosition = stretchMatrix*[0.400, 1*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +intensityThresholdEditPosition = stretchMatrix*[0.520, 1*heightUnit, editBoxWidth*3/3, editBoxHeight]' + controlPanelOffset; +dfTextPosition = stretchMatrix*[0.740, 1*heightUnit, 0.8-0.74, editBoxHeight]' + controlPanelOffset; +dfEditPosition = stretchMatrix*[0.800, 1*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +clusterSizeThresholdTextPosition = stretchMatrix*[0.000, 2*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +clusterSizeThresholdEditPosition = stretchMatrix*[0.150, 2*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +pickThisClusterPushPosition = stretchMatrix*[0.400, 2*heightUnit, editBoxWidth*1, editBoxHeight]' + controlPanelOffset; +selectThisClusterPushPosition = stretchMatrix*[0.600, 2*heightUnit, editBoxWidth*1, editBoxHeight]' + controlPanelOffset; +clearSelectedClusterPushPosition = stretchMatrix*[0.800, 2*heightUnit, editBoxWidth*1, editBoxHeight]' + controlPanelOffset; +thisClusterSizeTextPosition = stretchMatrix*[0.600, 2*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +thisClusterSizeEditPosition = stretchMatrix*[0.800, 2*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +loadImagePushPosition = stretchMatrix*[0.000, 3*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +imageFileEditPosition = stretchMatrix*[0.200, 3*heightUnit, 1-editBoxWidth, editBoxHeight]' + controlPanelOffset; +saveImagePushPosition = stretchMatrix*[0.000, 4*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +saveImageFileEditPosition = stretchMatrix*[0.200, 4*heightUnit, 1-editBoxWidth, editBoxHeight]' + controlPanelOffset; +saveResultPSPushPosition = stretchMatrix*[0.000, 5*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +resultPSFileEditPosition = stretchMatrix*[0.200, 5*heightUnit, 1-editBoxWidth, editBoxHeight]' + controlPanelOffset; +displayIntensityTextPosition = stretchMatrix*[0.000, 3*heightUnit, editBoxWidth+0.1, editBoxHeight]' + controlPanelOffset; +allIntensityRadioPosition = stretchMatrix*[0.250, 3*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +positiveIntensityRadioPosition = stretchMatrix*[0.400, 3*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +negativeIntensityRadioPosition = stretchMatrix*[0.550, 3*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +renderViewCheckPosition = stretchMatrix*[0.780, 3*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +hideControlPushPosition = stretchMatrix*[0.200, 4*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +volumePushPosition = stretchMatrix*[0.000, 4*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +commonRegionPushPosition = stretchMatrix*[0.200, 4*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +%knd +paramestPushPosition = stretchMatrix*[0.800, 4*heightUnit, editBoxWidth*.5, editBoxHeight]' + controlPanelOffset; +%-- +displayPushPosition = stretchMatrix*[0.200, 4*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +allinonePushPosition = stretchMatrix*[0.400, 4*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +searchPushPosition = stretchMatrix*[0.000, 6*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +searchContentEditPosition = stretchMatrix*[0.200, 6*heightUnit, editBoxWidth*2, editBoxHeight]' + controlPanelOffset; +searchTextPosition = stretchMatrix*[0.600, 6*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +searchEnginePopPosition = stretchMatrix*[0.600, 6*heightUnit, 1-0.6, editBoxHeight]' + controlPanelOffset; +overlayPushPosition = stretchMatrix*[0.000, 5*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +overlayEditPosition = stretchMatrix*[0.200, 5*heightUnit, 0.6-editBoxWidth, editBoxHeight]' + controlPanelOffset; +overlayPopPosition = stretchMatrix*[0.600, 5*heightUnit, 1-0.6, editBoxHeight]' + controlPanelOffset; +helpPosition = stretchMatrix*[0.800, 5*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +infoTextBoxPosition = stretchMatrix*[0.000, 8*heightUnit, 1, editBoxHeight*9]' + controlPanelOffset; +%xjViewPosition = stretchMatrix*[0.400, 13*heightUnit, editBoxWidth*2.5, editBoxHeight*3]' + controlPanelOffset; +%connameTextBoxPosition = stretchMatrix*[0.000, 8*heightUnit, 1, editBoxHeight*9]' + controlPanelOffset; + +sectionViewListboxPosition = [sectionViewPosition(1)+0.4, sectionViewPosition(2)+0.02, 0.1, 0.14]; +sectionViewMoreTargetPushPosition = [sectionViewListboxPosition(1),sectionViewListboxPosition(2)-0.02,0.10,0.02]; +xHairCheckPosition = [sectionViewListboxPosition(1),sectionViewListboxPosition(2)+0.14,0.15,0.02]; +setTRangeEditPosition = [sectionViewListboxPosition(1),sectionViewListboxPosition(2)-0.06,0.10,0.02]; +setTRangeTextPosition = [sectionViewListboxPosition(1),sectionViewListboxPosition(2)-0.04,0.10,0.02]; + +getStructurePushPosition = [glassViewAxesPosition(1), glassViewAxesPosition(2)-0.06, editBoxWidth/2, editBoxHeight/2]; +structureEditPosition = [getStructurePushPosition(1), getStructurePushPosition(2), 1, getStructurePushPosition(4)]; +framePosition = (controlPanelPosition - controlPanelOffset')*1.05 + 0.95*controlPanelOffset'; + +%knd +setContrastNameTextPosition = [glassViewAxesPosition(1),glassViewAxesPosition(2)-0.026,0.6,0.03]; +setContrastListPosition = [setContrastNameTextPosition(1)+setContrastNameTextPosition(3)+.01,setContrastNameTextPosition(2),0.05,setContrastNameTextPosition(4)]; +setContrastListPosition = [glassViewAxesPosition(1)+.05*glassViewAxesPosition(3),glassViewAxesPosition(2)-0.026,glassViewAxesPosition(3)*.95,0.03]; +setIndivResultsListPosition = [setTRangeTextPosition(1)-0.25,setTRangeEditPosition(2),0.20,setTRangeEditPosition(4)]; +%-- knd + +% draw figure and control +figureBKcolor=[176/255 252/255 188/255]; +figureBKcolor=get(0,'Defaultuicontrolbackgroundcolor'); +f = figure('unit','normalized','position',figurePosition,'Color',figureBKcolor,'defaultuicontrolBackgroundColor', figureBKcolor,... + 'Name','xjView', 'Tag', 'xjView', 'NumberTitle','off','resize','off','CloseRequestFcn', {@CallBack_quit, warnstate(1).state}, 'visible','off'); +handles = guihandles(f); + +% databases +try + addpath g:\ndiayek\data\atlases\ + addpath ~/data/atlases + addpath /mount/usb/data/atlases +end +try + X = load('TDdatabase'); + handles.DB = X.DB; + handles.wholeMaskMNIAll = X.wholeMaskMNIAll; +catch + handles.DB = []; + handles.wholeMaskMNIAll = struct([]); + errordlg('I can''t find TDdatabase.mat','TDdatabase not found'); +end + +handles.figure = f; +handles.frame = uicontrol(handles.figure,'style','frame',... + 'unit','normalized',... + 'position',framePosition,... + 'Visible','off'); +handles.slider = uicontrol(handles.figure,'style','slider',... + 'unit','normalized',... + 'position',sliderPosition,... + 'max',1,'min',0,... + 'sliderstep',[0.01,0.10],... + 'callback',@CallBack_slider,... + 'value',0,'Visible','on'); +handles.pValueTextPosition = uicontrol(handles.figure,'style','text',... + 'unit','normalized','position',pValueTextPosition,... + 'string','pValue=','horizontal','left'); +handles.pValueEdit = uicontrol(handles.figure,'style','edit',... + 'unit','normalized','position',pValueEditPosition,... + 'horizontal','left',... + 'String', num2str(pValue),... + 'BackgroundColor', 'w',... + 'callback',@CallBack_pValueEdit); +handles.intensityThresholdText = uicontrol(handles.figure,'style','text',... + 'unit','normalized','position',intensityThresholdTextPosition,... + 'string',' intensity=','horizontal','left'); +handles.intensityThresholdEdit = uicontrol(handles.figure,'style','edit',... + 'unit','normalized','position',intensityThresholdEditPosition,... + 'horizontal','left',... + 'BackgroundColor', 'w',... + 'String', num2str(intensityThreshold),... + 'callback',@CallBack_intensityThresholdEdit); +handles.dfText = uicontrol(handles.figure,'style','text',... + 'unit','normalized','position',dfTextPosition,... + 'string','df= ','horizontal','right'); +handles.dfEdit = uicontrol(handles.figure,'style','edit',... + 'unit','normalized','position',dfEditPosition,... + 'horizontal','left',... + 'BackgroundColor', 'w',... + 'String', '',... + 'callback',@CallBack_dfEdit); +handles.clusterSizeThresholdText = uicontrol(handles.figure,'style','text',... + 'unit','normalized','position',clusterSizeThresholdTextPosition,... + 'string','cluster size >=','horizontal','left'); +handles.clusterSizeThresholdEdit = uicontrol(handles.figure,'style','edit',... + 'unit','normalized','position',clusterSizeThresholdEditPosition,... + 'horizontal','left',... + 'BackgroundColor', 'w',... + 'String', num2str(clusterSizeThreshold),... + 'callback',@CallBack_clusterSizeThresholdEdit); +handles.thisClusterSizeText = uicontrol(handles.figure,'style','text',... + 'unit','normalized','position',thisClusterSizeTextPosition,... + 'string','size= ','horizontal','right', 'visible', 'off'); +handles.thisClusterSizeEdit = uicontrol(handles.figure,'style','edit',... + 'unit','normalized','position',thisClusterSizeEditPosition,... + 'horizontal','left',... + 'Enable', 'inactive',... + 'String', '','visible','off'); + +handles.imageFileEdit = uicontrol(handles.figure,'style','edit',... + 'unit','normalized','position',imageFileEditPosition,... + 'horizontal','left',... + 'String', '',... + 'BackgroundColor', 'w',... + 'callback',@CallBack_imageFileEdit,... + 'visible','off'); +handles.saveImageFileEdit = uicontrol(handles.figure,'style','edit',... + 'unit','normalized','position',saveImageFileEditPosition,... + 'horizontal','left',... + 'BackgroundColor', 'w',... + 'String', 'myMask.img',... + 'callback',@CallBack_saveImageFileEdit,... + 'visible','off'); +handles.saveResultPSEdit = uicontrol(handles.figure,'style','edit',... + 'unit','normalized','position',resultPSFileEditPosition,... + 'horizontal','left',... + 'BackgroundColor', 'w',... + 'String', 'myResult.ps',... + 'callback',@CallBack_saveResultPSEdit,... + 'visible','off'); +handles.loadImagePush = uicontrol(handles.figure,'style','push',... + 'unit','normalized','position',loadImagePushPosition,... + 'string','Load Image','callback',@CallBack_loadImagePush,... + 'visible','off'); +handles.saveImagePush = uicontrol(handles.figure,'style','push',... + 'unit','normalized','position',saveImagePushPosition,... + 'string','Save Image','callback',@CallBack_saveImagePush,... + 'visible','off'); +handles.saveResultPSPush = uicontrol(handles.figure,'style','push',... + 'unit','normalized','position',saveResultPSPushPosition,... + 'string','Save Result','callback',@CallBack_saveResultPSPush,... + 'visible','off'); +handles.getStructurePush = uicontrol(handles.figure,'style','push',... + 'unit','normalized','position',getStructurePushPosition,... + 'string','Get Structure','callback',@CallBack_getStructurePush,'visible','off'); +handles.structureEdit = uicontrol(handles.figure,'style','edit',... + 'unit','normalized','position',structureEditPosition,... + 'horizontal','center',... + 'enable', 'on',... + 'UserData',struct(... + 'hReg', [],... + 'M', M,... + 'D', DIM,... + 'xyz', [0 0 0] )); +handles.pickThisClusterPush = uicontrol(handles.figure,'style','push',... + 'unit','normalized','position',pickThisClusterPushPosition,... + 'string','Pick Cluster/Info','callback',@CallBack_pickThisClusterPush); +handles.selectThisClusterPush = uicontrol(handles.figure,'style','push',... + 'unit','normalized','position',selectThisClusterPushPosition,... + 'string','Select Cluster','callback',@CallBack_selectThisClusterPush); +handles.clearSelectedClusterPush = uicontrol(handles.figure,'style','push',... + 'unit','normalized','position',clearSelectedClusterPushPosition,... + 'string','Clear Selection','callback',@CallBack_clearSelectedClusterPush); + +handles.displayIntensityText = uicontrol(handles.figure,'style','text',... + 'unit','normalized','position',displayIntensityTextPosition,... + 'string','display intensity','horizontal','left'); +handles.allIntensityRadio = uicontrol(handles.figure,'style','radio',... + 'unit','normalized',... + 'string','All',... + 'position',allIntensityRadioPosition,... + 'value', 1,... + 'callback',@CallBack_allIntensityRadio); +handles.positiveIntensityRadio = uicontrol(handles.figure,'style','radio',... + 'unit','normalized',... + 'string','Only +',... + 'position',positiveIntensityRadioPosition,... + 'callback',{@CallBack_allIntensityRadio,'+'}); +handles.negativeIntensityRadio = uicontrol(handles.figure,'style','radio',... + 'unit','normalized',... + 'string','Only -',... + 'position',negativeIntensityRadioPosition,... + 'callback',{@CallBack_allIntensityRadio,'-'}); +handles.renderViewCheck = uicontrol(handles.figure,'style','checkbox',... + 'unit','normalized',... + 'string','Render View' ,... + 'horizontal', 'right',... + 'position',renderViewCheckPosition,... + 'callback', @CallBack_renderViewCheck); + +handles.sectionViewListbox = uicontrol(handles.figure,'style','listbox',... + 'unit','normalized',... + 'String', {'single T1','avg152PD','avg152T1','avg152T2','avg305T1','ch2','ch2bet','aal','brodmann'}, ... + 'value',3,... + 'position',sectionViewListboxPosition,... + 'callback',@CallBack_sectionViewListbox); + +handles.xHairCheck = uicontrol(handles.figure,'style','checkbox',... + 'unit','normalized',... + 'string','XHairs Off' ,... + 'horizontal','left',... + 'position',xHairCheckPosition,... + 'callback',@CallBack_xHairCheck); +handles.sectionViewMoreTargetPush = uicontrol(handles.figure,'style','push',... + 'unit','normalized','position',sectionViewMoreTargetPushPosition,... + 'string','other ...','callback',@CallBack_sectionViewMoreTargetPush); +handles.setTRangeEdit = uicontrol(handles.figure,'style','edit',... + 'unit','normalized','position',setTRangeEditPosition,'BackgroundColor', 'w',... + 'string','auto','callback',@CallBack_setTRangeEdit); +handles.setTRangeText = uicontrol(handles.figure,'style','text',... + 'unit','normalized','position',setTRangeTextPosition,... + 'string','colorbar max'); +handles.hideControlPush = uicontrol(handles.figure, 'style', 'push',... + 'unit','normalized',... + 'String', '<', 'position', hideControlPushPosition,... + 'visible','off'); +handles.volumePush = uicontrol(handles.figure, 'style', 'push',... + 'unit','normalized',... + 'String', 'volume', ... + 'position', volumePushPosition,... + 'callback', @CallBack_volumePush); +handles.commonRegionPush = uicontrol(handles.figure, 'style', 'push',... + 'unit','normalized',... + 'String', 'common region', ... + 'position', commonRegionPushPosition,... + 'callback', @CallBack_commonRegionPush); +handles.displayPush = uicontrol(handles.figure, 'style', 'push',... + 'unit','normalized',... + 'String', 'display', ... + 'position', displayPushPosition,... + 'callback', @CallBack_displayPush,... + 'visible','off'); +handles.allinonePush = uicontrol(handles.figure, 'style', 'push',... + 'unit','normalized',... + 'String', 'all in one', ... + 'position', allinonePushPosition,... + 'callback', @CallBack_allinonePush,... + 'visible','off'); +%knd: +handles.setContrastNameText = uicontrol(handles.figure,'style','text',... + 'unit','normalized','position',setContrastNameTextPosition, 'FontSize', 13,... + 'string',' ... ','callback',@CallBack_setContrastNameTextPosition); +handles.contrastListPush = uicontrol(handles.figure, 'style', 'popup',... + 'unit','normalized',... + 'String', [ {''}'], ... % getfield(evalin('base','SS'),'sub2pr')),[],23)')], ... + 'position', setContrastListPosition,... + 'callback', @CallBack_contrastListPush,... + 'visible','on'); + +handles.indivResultsListPush(1) = uicontrol(handles.figure, 'style', 'popup',... + 'unit','normalized',... + 'String', [ {''}'], ... % getfield(evalin('base','SS'),'sub2pr')),[],23)')], ... + 'position', setIndivResultsListPosition,... + 'callback', @CallBack_indivResultsListPush,... + 'visible','on'); + +handles.paramestPush(1) = uicontrol(handles.figure, 'style', 'push',... + 'unit','normalized',... + 'String', 'Vox Betas', ... + 'position', paramestPushPosition'.*[1 1 2 1],... + 'callback', {@CallBack_paramestRegionPush, 'vox'},... + 'buttondownfcn', {@CallBack_paramestRegionPush, 'vox', 0},... + 'visible','on'); +handles.paramestPush(2) = uicontrol(handles.figure, 'style', 'push',... + 'unit','normalized',... + 'String', 'Cluster', ... + 'position', paramestPushPosition-[paramestPushPosition(3) 0 0 0]',... + 'callback', {@CallBack_paramestRegionPush, 'clu'},... + 'buttondownfcn', {@CallBack_paramestRegionPush, 'clu', 0},... + 'visible','on'); +handles.paramestPush(3) = uicontrol(handles.figure, 'style', 'push',... + 'unit','normalized',... + 'String', 'Sphere', ... + 'position', paramestPushPosition-2*[paramestPushPosition(3) 0 0 0]',... + 'callback', {@CallBack_paramestRegionPush, 'sph'},... + 'visible','on'); +%--knd + +handles.searchPush = uicontrol(handles.figure, 'style','push',... + 'unit','normalized','position',searchPushPosition,... + 'String', 'search','callback',@CallBack_searchPush, 'ForeGroundColor',[0 0 1]); +handles.searchContentEdit = uicontrol(handles.figure, 'style','edit',... + 'unit','normalized','position',searchContentEditPosition,... + 'ForeGroundColor',[0 0 1],... + 'BackgroundColor', 'w',... + 'horizontal','left','callback', @CallBack_searchContentEdit); +handles.searchText = uicontrol(handles.figure, 'style','text',... + 'unit','normalized','position',searchTextPosition,... + 'string', ' in',... + 'horizontal','left',... + 'visible','off'); +handles.searchEnginePop = uicontrol(... + 'Units','normalized', ... + 'ListboxTop',0, ... + 'Position',searchEnginePopPosition, ... + 'String',{'Brede';'Jede';'xBrain.org';'Google Scholar';'Pubmed';'Wikipedia'}, ... + 'Style','popupmenu', ... + 'value',1); +handles.overlayPush = uicontrol(handles.figure, 'style','push',... + 'unit','normalized','position',overlayPushPosition,... + 'String', 'overlay','callback',@CallBack_overlayPush); +handles.overlayEdit = uicontrol(handles.figure, 'style','edit',... + 'unit','normalized','position',overlayEditPosition,... + 'BackgroundColor', 'w',... + 'horizontal','left',... + 'callback', @CallBack_overlayEdit); +handles.overlayPop = uicontrol(handles.figure, 'style','popupmenu',... + 'unit','normalized','position',overlayPopPosition,... + 'string', sort(fieldnames(handles.wholeMaskMNIAll)),... + 'horizontal','left',... + 'callback', @CallBack_overlayPop); + +handles.helpPush = uicontrol(handles.figure, 'style','push',... + 'unit','normalized','position',helpPosition,... + 'String', 'help','callback','web http://people.hnl.bcm.tmc.edu/cuixu/xjView','ForeGroundColor',[0 0 1],... + 'horizontal','left', ... + 'visible','off'); +handles.infoTextBox = uicontrol(handles.figure, 'style','edit',... + 'unit','normalized','position',infoTextBoxPosition,... + 'String', 'Welcome to xjView 4','ForeGroundColor','k', 'BackgroundColor', 'w',... + 'horizontal','left', ...'fontname','times',... + 'max',2, 'min',0); +try + s = urlread('http://people.hnl.bcm.tmc.edu/cuixu/xjView/toUser.txt'); + set(handles.infoTextBox, 'String', s); +end + +handles.glassViewAxes = axes('unit','normalized','position',glassViewAxesPosition,'XTick',[],'YTick',[],'visible','off'); + + +handles.testEdit = uicontrol(handles.figure,'style','edit',... + 'unit','normalized','position',[0.1 0.4 0.2 0.05],... + 'horizontal','left',... + 'callback',@test,... + 'visible','off'); + +% menu +cSHH = get(0,'ShowHiddenHandles'); +set(0,'ShowHiddenHandles','on') +hMenuFile = findobj(get(handles.figure,'Children'),'flat','Label','&File'); +if ~isempty(hMenuFile) + hMenuFileOpen = findobj(get(handles.figure,'Children'),'Label','&Open...'); + set(hMenuFileOpen, 'label', 'Open Figure...'); + hMenuFileSave = findobj(get(handles.figure,'Children'),'Label','&Save'); + set(hMenuFileSave, 'label', 'Save Figure ...'); + hMenuFileSaveAs = findobj(get(handles.figure,'Children'),'Label','Save &As...'); + set(hMenuFileSaveAs, 'label', 'Save Figure As ...'); +else + hMenuFile = uimenu(handles.figure, 'label', '&File'); +end + +set(hMenuFile,'ForegroundColor',[0 0 1]); +set(findobj(hMenuFile,'Position',1),'Separator','on'); +%knd +uimenu('Parent',hMenuFile,'Position',1,'ForegroundColor',[0 0 1],... + 'Label','Open SPM file (SPM.mat) ...',... + 'CallBack',@CallBack_loadSPMmat, 'Accelerator', 'o'); +%--knd +uimenu('Parent',hMenuFile,'Position',1,'ForegroundColor',[0 0 1],... + 'Label','Open Images (*.img) ...',... + 'CallBack',@CallBack_loadImagePush, 'Accelerator', 'o'); +uimenu('Parent',hMenuFile,'Position',2,'ForegroundColor',[0 0 1],... + 'Label','Save Current Image (*.img) ...',... + 'CallBack',{@CallBack_saveImagePush, '', 0}); +uimenu('Parent',hMenuFile,'Position',3,'ForegroundColor',[0 0 1],... + 'Label','Save Current Image as Mask (*.img) ...',... + 'CallBack',{@CallBack_saveImagePush, '', 1}); +uimenu('Parent',hMenuFile,'Position',4,'ForegroundColor',[0 0 1],... + 'Label','Save Result (*.ps/pdf) ...',... + 'CallBack',@CallBack_saveResultPSPush); + +hMenuHelp = findobj(get(handles.figure,'Children'),'flat','Label','&Help'); +if isempty(hMenuHelp) + hMenuHelp = uimenu(handles.figure, 'label', 'xjView &Help'); +end +set(hMenuHelp,'ForegroundColor',[0 0 1]); +uimenu('Parent',hMenuHelp,'Position',1,'ForegroundColor',[0 0 1],... + 'Label','xBrain.org: brain mapping database',... + 'CallBack','web http://www.xbrain.org -browser'); +uimenu('Parent',hMenuHelp,'Position',2,... + 'Label','xjview help','ForegroundColor',[0 0 1],... + 'CallBack','web http://people.hnl.bcm.tmc.edu/cuixu/xjView'); +set(findobj(hMenuHelp,'Position',3),'Separator','on'); +set(0,'ShowHiddenHandles',cSHH) + +if exist('cuixuBOLDretrieve') + hMenuAnalyze = uimenu('label','&Analyze','ForegroundColor',[0 0 1],'visible','on'); + hMenuPreprocess = uimenu(hMenuAnalyze,'label','Preprocess','ForegroundColor',[0 0 1],'callback', @CallBack_preprocess); + hMenuProcess = uimenu(hMenuAnalyze,'label','Process (GLM estimation)','ForegroundColor',[0 0 1],'callback',@CallBack_process); + hMenuSPMProcess = uimenu(hMenuAnalyze,'label','SPMProcess (GLM using SPM)','ForegroundColor',[0 0 1],'callback',@CallBack_SPMProcess); + hMenuGLMPeak = uimenu(hMenuAnalyze,'label','GLM on peak BOLD','ForegroundColor',[0 0 1],'callback',@CallBack_GLMPeak); + hMenuContrast = uimenu(hMenuAnalyze,'label','Contrast','ForegroundColor',[0 0 1],'callback',@CallBack_contrast); + hMenuFDR = uimenu(hMenuAnalyze,'label','FDR','ForegroundColor',[0 0 1],'callback',@CallBack_fdr); + hMenuROI = uimenu(hMenuAnalyze,'label','ROI: retrieve signal','ForegroundColor',[0 0 1],'callback',@CallBack_timeSeries); + hMenuROIPlot = uimenu(hMenuAnalyze,'label','ROI: plot','ForegroundColor',[0 0 1],'callback',@CallBack_plotROI, 'Accelerator', 'm'); + hMenuROIIndividualPlot = uimenu(hMenuAnalyze,'label','ROI: individual plot','ForegroundColor',[0 0 1],'callback',@CallBack_plotIndividualROI); + hMenuROIIndividualPlotWithBehavior = uimenu(hMenuAnalyze,'label','ROI: individual plot with behavior','ForegroundColor',[0 0 1],'callback',@CallBack_plotIndividualROIWithBehavior); + hMenuROICorrelationPlot = uimenu(hMenuAnalyze,'label','ROI: correlation plot','ForegroundColor',[0 0 1],'callback',@CallBack_plotCorrelationROI); + %hMenuWholeBrainCorrelation = uimenu(hMenuAnalyze,'label','Whole brain correlation','ForegroundColor',[0 0 1],'callback',@CallBack_wholeBrainCorrelation); + hMenuBehaviorAnalysis = uimenu(hMenuAnalyze,'label','Behavior analsyis','ForegroundColor',[0 0 1],'callback',@CallBack_behaviorAnalysis); + hMenuHeadMovementAnalysis = uimenu(hMenuAnalyze,'label','Head movement analsyis','ForegroundColor',[0 0 1],'callback',@CallBack_headMovementAnalysis); + hMenuModelComparison = uimenu(hMenuAnalyze,'label','Linear model comparison','ForegroundColor',[0 0 1],'callback',@CallBack_modelComparison); + + hMenuHNLOnly = uimenu('label','For H&NL Only','ForegroundColor',[0 0 1],'visible','on'); + hMenuNew2Old = uimenu(hMenuHNLOnly,'label','Format convert','ForegroundColor',[0 0 1],'callback',@CallBack_new2old); + hMenuPreprocessCluster = uimenu(hMenuHNLOnly,'label','Preprocess (on cluster)','ForegroundColor',[0 0 1],'callback',{@CallBack_preprocess, 'cluster'}); + hMenuProcessCluster = uimenu(hMenuHNLOnly,'label','Process (GLM, on cluster)','ForegroundColor',[0 0 1],'callback',{@CallBack_process, 'cluster'}); + hMenuSPMProcessCluster = uimenu(hMenuHNLOnly,'label','SPM Process (GLM using SPM, on cluster)','ForegroundColor',[0 0 1],'callback',{@CallBack_SPMProcess, 'cluster'}); + hMenuGLMPeakCluster = uimenu(hMenuHNLOnly,'label','GLM on peak BOLD (on cluster)','ForegroundColor',[0 0 1],'callback',{@CallBack_GLMPeak, 'cluster'}); +end + +figurecm = uicontextmenu; +uimenu(figurecm,'label','Red','callback','set(gcf,''color'',''r'')'); +uimenu(figurecm,'label','White','callback','set(gcf,''color'',''w'')'); +uimenu(figurecm,'label','Gray','callback','set(gcf,''color'',[0.925, 0.914, 0.847])'); +%set(handles.figure,'uicontextmenu',figurecm); +set(handles.figure,'WindowButtonDownFcn',@figureMouseUpFcn); + +set(handles.figure,'visible','on'); + +% save pre-set values +handles.system = os; +handles.spmdir = spmdir; +handles.screenResolution = screenResolution; +handles.pValue = pValue; +handles.intensityThreshold = intensityThreshold; +handles.clusterSizeThreshold = clusterSizeThreshold; +handles.sectionViewPosition = sectionViewPosition; +handles.sectionViewTargetFile = getSectionViewTargetFile(spmdir, 'avg152T1'); +%knd +if exist('xjview.opt') + tmp=textread('xjview.opt','%s');eval(tmp{:}); + handles.sectionViewTargetFile = anatomy; +else + if ~isempty(findstr('gazemo', pwd)) + handles.sectionViewTargetFile = fullfile(fileparts(mfilename('fullpath')), 'group','anat', 'mean.img'); + end +end +guidata(f, handles); + +% global variables for rotation matrix M and dimension +global M_; +global DIM_; +global TR_; +global LEFTRIGHTFLIP_; +global TMAX_; % colorbar max to display in section view +M_ = M; +DIM_ = DIM; +TR_ = TR; +LEFTRIGHTFLIP_ = leftrightflip; +TMAX_ = 'auto'; + +% check input arguments +if length(varargin) == 0 + CallBack_loadSPMmat(handles.loadImagePush) + % if exist(fullfile(pwd,'SPM.mat'), 'file') + % xSPM=load(fullfile(pwd,'SPM.mat')); + % numc=listdlg('ListString' , {xSPM.SPM.xCon.name}, 'SelectionMode' , 'single', 'InitialValue', length(xSPM.SPM.xCon)); + % if ~isempty(numc) + % CallBack_loadImagePush(handles.loadImagePush, [], {fullfile(xSPM.SPM.swd,xSPM.SPM.xCon(numc).Vspm.fname)}); + % end + % end +elseif isstr(varargin{1}) + if isempty(findstr('.mat', varargin{1})) + CallBack_loadImagePush(handles.loadImagePush, [], varargin); + else + CallBack_loadSPMmat(handles.loadImagePush, [], varargin{1}); + end +else + mniCoord = varargin{1}; + if length(varargin) < 2 + intensity = ones(size(mniCoord,1),1); + else + intensity = varargin{2}; + end + thisStruct.mni = mniCoord; + thisStruct.intensity = intensity; + thisStruct.M = M; + thisStruct.DIM = DIM'; + CallBack_loadImagePush(handles.loadImagePush, [], thisStruct); +end + + + +function test(hObject, eventdata) +handles = guidata(gcbo); +set(hObject, 'String', num2str(handles.pValue)); +vars = evalin('base','who'); +vars +x = evalin('base',vars{1}); +x +handles.DIM + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% FDR +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_fdr(hObject, eventdata) +handles = guidata(hObject); +set(handles.infoTextBox, 'string', {'Right now FDR only works for T-test image.'}); +if length(handles.imageFileName) > 1 + set(handles.infoTextBox, 'string', {'I can only work for a single image file. You opened multiple files.'}); + return +end + +q = get(handles.pValueEdit,'String'); +q = str2num(q); + +if get(handles.allIntensityRadio, 'Value') + positive = 1; +elseif get(handles.positiveIntensityRadio, 'Value') + positive = 1; +elseif get(handles.negativeIntensityRadio, 'Value') + positive = -1; +end +xjviewpath = fileparts(which('xjview')); +maskImageFile = fullfile(xjviewpath, 'mask.img'); +[threshold, pvalue] = fdr(handles.imageFileName{1}, q, positive, maskImageFile); +set(handles.pValueEdit,'string',pvalue); +CallBack_pValueEdit(handles.pValueEdit, eventdata); + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% model comparison +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_modelComparison(hObject, eventdata) +answer = getVariable({'y','x (full model)', 'x (reduced model)'}); + +if isempty(answer) + return; +end + +if ~isempty(answer{1}) + y = evalin('base',answer{1}); +else + return; +end +if ~isempty(answer{2}) + xf = evalin('base',answer{2}); % full model +else + return; +end +if ~isempty(answer{3}) + xr = evalin('base',answer{3}); % reduced model +else + xr = []; +end + +% make vector column vector +[r, c] = size(y); +if r==1; y = y'; end +[r, c] = size(xf); +if r(0.5+ii/10); +end + +K = {zeros(1, size(C,1)),zeros(1, size(C,1)),zeros(1, size(C,1)),zeros(1, size(C,1))}; +for ii=1:size(C,1) + for jj=1:4 + K{jj}(ii) = sum(CC{jj}(ii,:)) - 1; + end +end + +x=[0:size(C,1)]; +for jj=1:4 + y{jj}=zeros(size(x)); +end + +for ii=1:length(x) + for jj=1:4 + y{jj}(ii) = sum(K{jj} == x(ii)); + end +end + +figure; +loglog(x,y{1}, x, y{2}, x, y{3}, x, y{4}); +xlabel('degree'); +ylabel('counts'); +legend('0.6', '0.7', '0.8', '0.9'); +axis equal + + +z = sum(abs(C))-1; +zx = 0:max(z); +for ii=1:length(zx)-1 + pos = find(z>zx(ii) & z<=zx(ii+1)); + zy(ii) = length(pos); +end +figure; +loglog(zx(1:length(zy)),zy); +xlabel('weighted degree'); +ylabel('counts'); +axis equal + +xjview(cor2mni(coord, handles.M{1}), z); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% get/select variable names in base workspace +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function vars = getVariable(titles) +vars = evalin('base','who'); +height = 0.07; +f = dialog('unit','normalized', 'menubar','none', 'position', [0.3 0.2 0.4 0.4], 'name', 'pick variables', 'NumberTitle','off'); +uicontrol(f,'style','text',... + 'unit','normalized',... + 'String', 'Available variables', ... + 'position',[0 0.9 0.5 0.1/2]); +variableListbox = uicontrol(f,'style','listbox','tag','variableListbox',... + 'unit','normalized',... + 'String', vars, ... + 'value',1,... + 'position',[0 0 0.5 0.9]); +okPush = uicontrol(f,'style','push',... + 'unit','normalized',... + 'String', 'OK', ... + 'position',[0.55 0.05 0.2 height],... + 'callback','uiresume'); +cancelPush = uicontrol(f,'style','push',... + 'unit','normalized',... + 'String', 'Cancel', ... + 'position',[0.75 0.05 0.2 height],... + 'callback','delete(gcf)'); + +uicontrol(f,'style','text',... + 'unit','normalized',... + 'String', titles{1}, ... + 'position',[0.5 0.9 0.5 0.1/2]); +imageDataPush = uicontrol(f,'style','push',... + 'unit','normalized',... + 'String', '->', ... + 'position',[0.5 0.8 0.1 height],... + 'callback', [... + 'xyouneverfindme=findobj(get(gcf,''Children''),''flat'',''tag'',''imageDataEdit'');'... + 'yyouneverfindme=findobj(get(gcf,''Children''),''flat'',''tag'',''variableListbox'');'... + 'allyouneverfindme=get(yyouneverfindme,''string'');'... + 'selectedyouneverfindme=allyouneverfindme{get(yyouneverfindme,''value'')};'... + 'set(xyouneverfindme,''string'',selectedyouneverfindme);'... + 'clear allyouneverfindme selectedyouneverfindme xyouneverfindme yyouneverfindme;']); +imageDataEdit = uicontrol(f,'style','edit','tag','imageDataEdit',... + 'unit','normalized',... + 'String', '', ... + 'BackgroundColor', 'w',... + 'position',[0.6 0.8 0.3 height]); + +if length(titles)>=2 + uicontrol(f,'style','text',... + 'unit','normalized',... + 'String', titles{2}, ... + 'position',[0.5 0.7 0.5 0.1/2]); + eventDataPush = uicontrol(f,'style','push',... + 'unit','normalized',... + 'String', '->', ... + 'position',[0.5 0.6 0.1 height],... + 'callback', [... + 'xyouneverfindme=findobj(get(gcf,''Children''),''flat'',''tag'',''eventDataEdit'');'... + 'yyouneverfindme=findobj(get(gcf,''Children''),''flat'',''tag'',''variableListbox'');'... + 'allyouneverfindme=get(yyouneverfindme,''string'');'... + 'selectedyouneverfindme=allyouneverfindme{get(yyouneverfindme,''value'')};'... + 'set(xyouneverfindme,''string'',selectedyouneverfindme);'... + 'clear allyouneverfindme selectedyouneverfindme xyouneverfindme yyouneverfindme;']); + + eventDataEdit = uicontrol(f,'style','edit','tag','eventDataEdit',... + 'unit','normalized',... + 'String', '', ... + 'BackgroundColor', 'w',... + 'position',[0.6 0.6 0.3 height]); +end +if length(titles)>=3 + uicontrol(f,'style','text',... + 'unit','normalized',... + 'String', titles{3}, ... + 'position',[0.5 0.5 0.5 0.1/2]); + correlatorDataPush = uicontrol(f,'style','push',... + 'unit','normalized',... + 'String', '->', ... + 'position',[0.5 0.4 0.1 height],... + 'callback', [... + 'xyouneverfindme=findobj(get(gcf,''Children''),''flat'',''tag'',''correlatorDataEdit'');'... + 'yyouneverfindme=findobj(get(gcf,''Children''),''flat'',''tag'',''variableListbox'');'... + 'allyouneverfindme=get(yyouneverfindme,''string'');'... + 'selectedyouneverfindme=allyouneverfindme{get(yyouneverfindme,''value'')};'... + 'set(xyouneverfindme,''string'',selectedyouneverfindme);'... + 'clear allyouneverfindme selectedyouneverfindme xyouneverfindme yyouneverfindme;']); + + correlatorDataEdit = uicontrol(f,'style','edit','tag','correlatorDataEdit',... + 'unit','normalized',... + 'String', '', ... + 'BackgroundColor', 'w',... + 'position',[0.6 0.4 0.3 height]); +end +if length(titles)>=4 + uicontrol(f,'style','text',... + 'unit','normalized',... + 'String', titles{4}, ... + 'position',[0.5 0.3 0.5 0.1/2]); + otherDataPush = uicontrol(f,'style','push',... + 'unit','normalized',... + 'String', '->', ... + 'position',[0.5 0.2 0.1 height],... + 'callback', [... + 'xyouneverfindme=findobj(get(gcf,''Children''),''flat'',''tag'',''otherDataEdit'');'... + 'yyouneverfindme=findobj(get(gcf,''Children''),''flat'',''tag'',''variableListbox'');'... + 'allyouneverfindme=get(yyouneverfindme,''string'');'... + 'selectedyouneverfindme=allyouneverfindme{get(yyouneverfindme,''value'')};'... + 'set(xyouneverfindme,''string'',selectedyouneverfindme);'... + 'clear allyouneverfindme selectedyouneverfindme xyouneverfindme yyouneverfindme;']); + + otherDataEdit = uicontrol(f,'style','edit','tag','otherDataEdit',... + 'unit','normalized',... + 'String', '', ... + 'BackgroundColor', 'w',... + 'position',[0.6 0.2 0.3 height]); +end + +uiwait(f); +try + var{1} = get(imageDataEdit,'string'); + if length(titles)>=2 + var{2} = get(eventDataEdit,'string'); + end + if length(titles)>=3 + var{3} = get(correlatorDataEdit,'string'); + end + if length(titles)>=4 + var{4} = get(otherDataEdit,'string'); + end + vars = var; + delete(f); +catch + vars = {}; + try + delete(f); + end +end + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% quit xjview +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_quit(hObject, eventdata, warnstate) +warning(warnstate) +% try +% rmdir('xjviewtmp'); +% end +delete(gcf); + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% mouse double click +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function figureMouseUpFcn(hObject, eventdata) +status = get(hObject, 'SelectionType'); + +% double click +if strcmp(status, 'extend') + handles = guidata(hObject); + CallBack_loadImagePush(handles.loadImagePush, eventdata); +elseif strcmp(status, 'open') + handles = guidata(hObject); + CallBack_loadSPMmat(handles.loadImagePush, eventdata); +else + return +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% change edit image file name +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_imageFileEdit(hObject, eventdata) +handles = guidata(hObject); +filename = get(hObject, 'String'); +filename = str2cell(filename); +CallBack_loadImagePush(handles.loadImagePush, eventdata, filename); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% click load image file button +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_contrastListPush(hObject, eventdata) +handles = guidata(hObject); +connum = get(handles.contrastListPush,'Value'); +[img.p, img.fname, img.ext]=fileparts(handles.imageFileName{1}); +spmfile=fullfile(img.p,'SPM.mat'); +if ~exist(spmfile) + set(handles.contrastListPush,'Value',0); +else + cons=getfield(getfield(load(spmfile, 'SPM'), 'SPM'), 'xCon'); + connames=get(handles.contrastListPush,'String'); + CallBack_loadImagePush(hObject,eventdata,fullfile(img.p, cons(connum).Vspm.fname), connum) +end + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% click load image file button +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_loadImagePush(hObject, eventdata, thisfilename, ContrastName) +handles = guidata(hObject); +handles.imageFileName=[]; handles.M=[]; handles.DIM=[]; handles.TF=[]; handles.df=[]; +handles.mni=[]; handles.intensity=[]; handles.currentmni=[]; handles.currentintensity=[]; handles.currentDisplayMNI=[]; handles.currentDisplayIntensity=[]; +if nargin>3 + handles.imageContrastName=ContrastName; +else + handles.imageContrastName=[]; +end +if ~exist('thisfilename') + thisfilename = ''; +end + +if isstruct(thisfilename) + handles.imageFileName = {''}; + handles.mni = {thisfilename.mni}; + handles.intensity = {thisfilename.intensity}; + handles.M = {thisfilename.M}; + handles.DIM = {thisfilename.DIM}; + handles.TF = {'S'}; + handles.df = {1e6}; + handles.pValue = 1; + set(handles.pValueEdit, 'string', '1'); + handles.clusterSizeThreshold = 0; + set(handles.clusterSizeThresholdEdit, 'String', '0'); + handles.imageContrastName={''}; +else + [handles.imageFileName, handles.M, handles.DIM, handles.TF, handles.df, handles.mni, handles.intensity] = getImageFile(thisfilename); +end + +if isempty(handles.imageFileName) + return +end + +% tries to find firts-level matching data +% knd +set(handles.indivResultsListPush(1),'String', ... + getfield2(dir(fileparts(fileparts(handles.imageFileName{1}))), 'name')); + +different = 0; % image files are same or different? +if length(handles.TF)>1 + for ii=1:length(handles.TF) + if strcmp(handles.TF{ii},handles.TF{1}) & isequal(handles.df{ii},handles.df{1}) & isequal(handles.M{ii},handles.M{1}) & isequal(handles.DIM{ii},handles.DIM{1}) + continue; + else + warndlg('Images are from different statistics or sources.', 'Warning'); + %set(handles.infoTextBox, 'string', 'Images are from different statistics or sources.'); + beep; + different = 1; + break; + end + end +end + +% reset files with empty df/TF to df=1 and TF='S'. 'S'=='T' but has a tag +% meaning it is changed. +resetTF = 0; +for ii=1:length(handles.TF) + if isempty(handles.TF{ii}) + handles.TF{ii} = 'S'; + resetTF = 1; + end + if isempty(handles.df{ii}) | isequal(handles.df{ii},0) + handles.df{ii} = 1e6; + resetTF = 1; + end +end + +handles.currentmni = handles.mni; +handles.currentintensity = handles.intensity; + +set(handles.dfEdit, 'String', cell2str(handles.df)); +set(handles.imageFileEdit, 'String', cell2str(handles.imageFileName)); % s=-log10(p) +maxs = maxcell(t2s(cellmax(handles.intensity,'abs'),handles.df, handles.TF),'abs'); +if isinf(maxs); maxs = 20; end +set(handles.slider, 'Max', maxs, 'Min', 0, 'sliderstep',[min(maxs/100,0.05),min(maxs/100,0.05)]); +if handles.TF{1}=='T' & different == 0 + str = [blanks(length(' intensit')) 'T=']; +elseif handles.TF{1}=='F' & different == 0 + str = [blanks(length(' intensit')) 'F=']; +else + str=' intensity='; +end +set(handles.intensityThresholdText, 'String', str); +set(handles.figure,'Name',['xjView: ' cell2str(handles.imageFileName)]); +try + for ii=1:length(handles.hLegend) + delete(handles.hLegend{ii}); + end +end +nimg=length(handles.TF); +if nimg>1 + colours = {'r';'g';'y';'c';'b';'m'}; + %colours = colours(mod(0:nimg-1,length(colours))+1); + %colours = repmat([1 0 0;1 1 0;0 1 0;0 1 1;0 0 1;1 0 1],100,1); + % read_conname = spm_input({'Retrieve contrasts names','Should I read SPM files to retrieve filenames?'},... + % 1,'bd',{'YES','NO'},[1,0]); + read_conname = 1; + for ii=1:nimg + [tmp,filename] = fileparts(handles.imageFileName{ii}); + c_names{ii} = filename ; + if read_conname & exist(fullfile(tmp, 'SPM.mat')) + tmp=load(fullfile(tmp, 'SPM.mat')); + tmp.Vspm=[tmp.SPM.xCon.Vspm]; + tmp.iCon=strmatch(filename, {tmp.Vspm.fname}); + if ~isempty(tmp.iCon) + c_names{ii} = [ [tmp.SPM.xCon(tmp.iCon).name] ' [' c_names{ii} ']']; + end + end + end + [tmp] = inputdlg({'colors' 'names'},'Loading',6,[{strvcat(c_names)};{strvcat(colours)}]); + colours = colorname2rgb(tmp{2}); + c_names = cellstr(tmp{1}); + + for ii=1:min(length(handles.TF),10) + filename = c_names{ii}; + if ii == 10; filename = '......'; end + pos0 = handles.sectionViewPosition; + pos(1) = pos0(1)+pos0(3)/2+0.03; + pos(2) = pos0(2)+ii/50-0.03; + pos(3) = 0.12; + pos(4) = 0.02; + handles.hLegend{ii}=uicontrol(handles.figure, 'style','text',... + 'unit','normalized','position',pos,... + 'string', filename,... + 'horizontal','left',... + 'fontweight','bold',... + 'ForeGroundColor',colours(ii,:)); + end + c_names = {'red';'yellow';'green';'cyan';'blue';'magenta'}; +end +guidata(hObject, handles); + +global M_; +global DIM_; +M_ = handles.M{1}; +DIM_ = handles.DIM{1}; + +if resetTF==1 + set(handles.pValueEdit,'string',1); +end +CallBack_pValueEdit(handles.pValueEdit, eventdata); + +% display info +try + s = urlread('http://people.hnl.bcm.tmc.edu/cuixu/xjView/toUser.txt'); + report{1} = s; +catch + report{1} = 'Welcome to xjView 4'; +end +for jj=1:length(handles.imageFileName) + report{2+6*(jj-1)} = cell2str(handles.imageFileName(jj)); + if handles.TF{jj} == 'T' | handles.TF{jj} == 'F' + report{3+6*(jj-1)} = ['This is a ' handles.TF{jj} ' test image.']; + else + report{3+6*(jj-1)} = '';%['I don''t know what test this image came from.']; + end + report{4+6*(jj-1)} = 'mat = '; + report{5+6*(jj-1)} = num2str(handles.M{jj}); + report{6+6*(jj-1)} = 'dimension = '; + report{7+6*(jj-1)} = num2str(handles.DIM{jj}); +end +if length(handles.imageFileName)==1 + [img.p, img.fname, img.ext]=fileparts(handles.imageFileName{1}); + spmfile=fullfile(img.p,'SPM.mat'); + if ~exist(spmfile) + if findstr('SPM2',spm('ver')) + spmfile = spm_get([0 1],'SPM.mat','locate the corresponding SPM.mat'); + elseif findstr('SPM5',spm('ver')) + spmfile = spm_select([0:1],'SPM.mat','locate the corresponding SPM.mat'); + end + end + if exist(spmfile) + cons=getfield(getfield(load(spmfile, 'SPM'), 'SPM'), 'xCon'); + for i=1:length(cons) + if not(isempty(cons(i).Vspm)) + consfilename{i}=cons(i).Vspm.fname; + else + consfilename{i}=''; + end + end + connum=strmatch([img.fname img.ext],consfilename); + if ~isempty(connum) + conlabel=cons(connum).name; + set(handles.figure,'Name',[get(handles.figure,'Name') ' :: ' conlabel]); + set(handles.contrastListPush,'String',{cons.name}) + set(handles.contrastListPush,'Value',connum) + set(handles.setContrastNameText,'String',[conlabel]); + report=[report(1:3) {['Contrast name in SPM.mat: (' num2str(connum) ') ' conlabel]} report(4:end)]; + else + set(handles.contrastListPush,'String',{'[none]'}) + set(handles.contrastListPush,'Value',0) + set(handles.contrastListPush,'Enable','on') + report=[report(1:3) {['Contrast has no matching name in SPM.mat']} report(4:end)]; + end + end +end +set(handles.infoTextBox, 'string', report); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% pValueEdit +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_pValueEdit(hObject, eventdata) +handles = guidata(hObject); +tmp = str2double(get(hObject, 'String')); +tmps = -log10(tmp); +if isnan(tmp) | tmp < 0 | tmp > 1 + errordlg('I don''t understand the input.','error'); + set(hObject, 'String', handles.pValue); + return +end + +if tmps>get(handles.slider,'max') | tmps maxcell(cellmax(handles.intensity,'abs')) + tmp = maxcell(cellmax(handles.intensity,'abs')); +end + +handles.pValue = t2p(tmp, handles.df{1}, handles.TF{1}); +handles.intensityThreshold = p2t(num2cell(handles.pValue*ones(1,length(handles.TF))), handles.df, handles.TF); +set(handles.slider,'Value', -log10(handles.pValue)); +set(handles.intensityThresholdEdit, 'String', cell2str(handles.intensityThreshold)); +guidata(hObject, handles); +CallBack_slider(hObject, eventdata, -log10(handles.pValue)); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% slider bar +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_slider(hObject, eventdata, value) + +handles = guidata(hObject); + +if exist('value') + s = value; + if s > get(handles.slider,'max') + s = get(handles.slider,'max')*0.99; + end + if s < get(handles.slider,'min') + s = get(handles.slider,'min'); + end +else + s = get(hObject,'Value'); +end + +set(handles.slider, 'value', s); +pvalue = 10^(-s); +set(handles.pValueEdit,'String',num2str(pvalue)); +t = p2t(num2cell(pvalue*ones(1,length(handles.TF))), handles.df, handles.TF); +handles.intensityThreshold = t; +set(handles.intensityThresholdEdit, 'String', cell2str(handles.intensityThreshold)); +handles.pValue = pvalue; +for ii=1:length(handles.TF) + pos{ii} = find(abs(handles.intensity{ii})>=t{ii}); + handles.currentintensity{ii} = handles.intensity{ii}(pos{ii}); + handles.currentmni{ii} = handles.mni{ii}(pos{ii},:); +end + +guidata(hObject,handles); + +if get(handles.allIntensityRadio, 'Value') + CallBack_allIntensityRadio(handles.allIntensityRadio, eventdata); +elseif get(handles.positiveIntensityRadio, 'Value') + CallBack_allIntensityRadio(handles.positiveIntensityRadio, eventdata, '+'); +elseif get(handles.negativeIntensityRadio, 'Value') + CallBack_allIntensityRadio(handles.negativeIntensityRadio, eventdata, '-'); +end + +set(handles.infoTextBox, 'string', {'Don''t drag the slider bar too fast. Release your mouse button at least 1 second later.', ... + 'This sounds stupid. But there is a bug (probably MatLab bug) which I can'' fix right now.', ... + 'I suggest you confirm the correctness of the current display by press Enter in the pValue edit box.'}); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% display intensity all+- radios +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_allIntensityRadio(hObject, eventdata, pnall) +% pnall = '+', '-', 'a' (all) or 'c' for current (simply update drawing) +% +handles = guidata(hObject); + +currentselect = []; +if get(handles.allIntensityRadio, 'Value'); currentselect = handles.allIntensityRadio; thispnall = 'a'; end +if get(handles.positiveIntensityRadio, 'Value'); currentselect = handles.positiveIntensityRadio; thispnall = '+'; end +if get(handles.negativeIntensityRadio, 'Value'); currentselect = handles.negativeIntensityRadio; thispnall = '-'; end + +set(handles.allIntensityRadio, 'Value', 0); +set(handles.positiveIntensityRadio, 'Value', 0); +set(handles.negativeIntensityRadio, 'Value', 0); + +if exist('pnall') + if pnall=='c' + hObject = currentselect; + pnall = thispnall; + end +end + +set(hObject, 'Value', 1); + +if ~isfield(handles,'currentintensity') + return +end +for ii=1:length(handles.TF) + if exist('pnall') + if pnall == '-' + pos{ii} = find(handles.currentintensity{ii} < 0); + elseif pnall == '+' + pos{ii} = find(handles.currentintensity{ii} > 0); + elseif pnall == 'a' + pos{ii} = 1:length(handles.currentintensity{ii}); + end + else + pos{ii} = 1:length(handles.currentintensity{ii}); + end + intensity{ii} = handles.currentintensity{ii}(pos{ii}); + mni{ii} = handles.currentmni{ii}(pos{ii},:); + cor{ii} = mni2cor(mni{ii}, handles.M{ii}); + + if ~isempty(cor{ii}) + A = spm_clusters(cor{ii}'); + pos0 = []; + for kk = 1:max(A) + jj = find(A == kk); + if length(jj) >= handles.clusterSizeThreshold; pos0 = [pos0 jj]; end + end + handles.currentDisplayMNI{ii} = mni{ii}(pos0,:); + handles.currentDisplayIntensity{ii} = intensity{ii}(pos0); + else + handles.currentDisplayMNI{ii} = mni{ii}([],:); + handles.currentDisplayIntensity{ii} = intensity{ii}([]); + end +end +[handles.hReg, handles.hSection, handles.hcolorbar] = Draw(handles.currentDisplayMNI, handles.currentDisplayIntensity, hObject, handles); + +% list of contrasts + +set(handles.contrastListPush,'String',regexprep(get(handles.contrastListPush,'String'),' \[Only [\+-a]\]$','')) +set(handles.setContrastNameText,'String',regexprep(get(handles.setContrastNameText,'String'),' \[Only [\+-a]\]$','')) + +if exist('pnall','var') + if ~isequal(pnall, 'a') + set(handles.setContrastNameText,'String',[get(handles.setContrastNameText,'String') ' [Only ' pnall ']']); + s=get(handles.contrastListPush,'String'); + s{get(handles.contrastListPush,'Value')}=[s{get(handles.contrastListPush,'Value')} ' [Only ' pnall ']']; + + set(handles.contrastListPush,'String',s); + end +end + +if get(handles.allIntensityRadio, 'Value') & max(handles.currentDisplayIntensity{1}) < 0 + warndlg('No supra-threshold positive intensity. Only negative intensity is displayed.'); +end + +try + set(handles.figure,'currentaxes', handles.glassViewAxes); + xrange = xlim; + yrange = ylim; + try + delete(handles.hGlassText) + end + if ~isempty(handles.selectedCluster,1) + %handles.hGlassText = text(xrange(1)+diff(xrange)*0.6, yrange(1)+diff(yrange)*0.9, [num2str(size(handles.selectedCluster,1)) ' clusters selected']); + set(handles.infoTextBox, 'string', [num2str(size(handles.selectedCluster,1)) ' clusters selected']); + end +end +guidata(hObject, handles); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% render view check? +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_renderViewCheck(hObject, eventdata) +check = get(hObject, 'Value'); +if check + CallBack_allIntensityRadio(hObject, eventdata, 'c'); +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% which section view target file? list box +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_sectionViewListbox(hObject, eventdata) +handles = guidata(hObject); +contents = get(handles.sectionViewListbox,'String'); +currentsel = contents{get(handles.sectionViewListbox,'Value')}; +handles.sectionViewTargetFile = getSectionViewTargetFile(handles.spmdir, currentsel); +guidata(hObject, handles); +CallBack_allIntensityRadio(hObject, eventdata, 'c'); + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% get sectionviewtargetfile +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function sectionViewTargetFile = getSectionViewTargetFile(spmdir, selectedcontent) +if findstr('SPM2',spm('ver')) + fileext = 'mnc'; +elseif findstr('SPM5',spm('ver')) + fileext = 'nii'; +end +currentsel = selectedcontent; +if ~isempty(strfind(currentsel, 'single')) + sectionViewTargetFile = fullfile(spmdir, 'canonical', ['single_subj_T1.' fileext]); +elseif ~isempty(strfind(currentsel, '152PD')) + sectionViewTargetFile = fullfile(spmdir, 'canonical', ['avg152PD.' fileext]); +elseif ~isempty(strfind(currentsel, '152T1')) + sectionViewTargetFile = fullfile(spmdir, 'canonical', ['avg152T1.' fileext]); +elseif ~isempty(strfind(currentsel, '152T2')) + sectionViewTargetFile = fullfile(spmdir, 'canonical', ['avg152T2.' fileext]); +elseif ~isempty(strfind(currentsel, '305T1')) + sectionViewTargetFile = fullfile(spmdir, 'canonical', ['avg305T1.' fileext]); +elseif strcmp(currentsel, 'ch2') + sectionViewTargetFile = fullfile(spmdir, 'canonical', 'ch2.img'); +elseif strcmp(currentsel, 'ch2bet') + sectionViewTargetFile = fullfile(spmdir, 'canonical', 'ch2bet.img'); +elseif strcmp(currentsel, 'aal') + sectionViewTargetFile = fullfile(spmdir, 'canonical', 'aal.img'); +elseif strcmp(currentsel, 'brodmann') + sectionViewTargetFile = fullfile(spmdir, 'canonical', 'brodmann.img'); + %knd: +elseif ~isempty(strfind(currentsel, 'gazemo')) + sectionViewTargetFile = fullfile(fileparts(mfilename('fullpath')), 'group','anat', 'mean-no120.img'); + %sectionViewTargetFile = fullfile('D:\ndiayek\data\gazemo\group\anat', 'mean-no120.img'); +end +return + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% xhairs in section view? +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_xHairCheck(hObject, eventdata) +check = get(hObject, 'Value'); +if check + spm_orthviews('Xhairs','off'); +else + spm_orthviews('Xhairs','on'); +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% other target file for section view, push button +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_sectionViewMoreTargetPush(hObject, eventdata) +handles = guidata(hObject); +[filename, pathname, filterindex] = uigetfile('*', 'Pick an target file'); +if isequal(filename,0) | isequal(pathname,0) + return; +end +handles.sectionViewTargetFile = fullfile(pathname, filename); +guidata(hObject, handles); +CallBack_allIntensityRadio(hObject, eventdata, 'c'); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% set colorbar range +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_setTRangeEdit(hObject, eventdata) +global TMAX_; +handles = guidata(hObject); +TMAX_ = get(hObject, 'String'); +if isempty(str2num(TMAX_)) && ~strcmp(TMAX_, 'auto') + return; +end +guidata(hObject, handles); +CallBack_allIntensityRadio(hObject, eventdata, 'c'); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% change degree of freedome +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_dfEdit(hObject, eventdata) +handles = guidata(hObject); +tmp = str2cell(get(hObject, 'String')); +if iscellstr(tmp) + errordlg('Please input a valid number.','error'); + try + set(hObject, 'String', handles.df); + catch + set(hObject, 'String', ''); + end + return +end +handles.df=tmp; + + +if isfield(handles,'TF') + t = p2t(mat2cell(handles.pValue*ones(1,length(handles.TF))), handles.df, handles.TF); + handles.intensityThreshold = t; + set(handles.intensityThresholdEdit, 'String', cell2str(t)); +end + +guidata(hObject, handles); +CallBack_pValueEdit(handles.pValueEdit, eventdata); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% get structure push +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_getStructurePush(hObject, eventdata) +handles = guidata(hObject); +xyz = spm_XYZreg('GetCoords',handles.hReg); +tmp_coor = cuixuFindTDstructure(xyz', handles.DB, 0); +set(handles.structureEdit,'String', tmp_coor{1}); +handles.currentxyz = xyz'; +guidata(hObject, handles); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% structure edit +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_structureEdit(action, xyz, thisfun, hReg) + +try + handles=guidata(hReg); +catch + return; +end + +handles.currentxyz = xyz'; +guidata(hReg, handles); + +try + [tmp_coor, cellstructure] = cuixuFindTDstructure(xyz', handles.DB, 0); +catch + return; +end + +set(handles.structureEdit,'String', tmp_coor{1}); + +for ii=[5 3 2 1 4] + if strfind('Unidentified', cellstructure{ii}) + continue; + else + set(handles.searchContentEdit,'string', trimStructureStr(cellstructure{ii})); + set(handles.searchContentEdit,'UserData', 'auto'); + return; + end +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% trim str +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function out = trimStructureStr(str) +pos = findstr('(', str); +if ~isempty(pos) + str(pos-1:end)=[]; +end +out = str; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% cluster size threshold edit +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_clusterSizeThresholdEdit(hObject, eventdata) +handles = guidata(hObject); +tmp = str2double(get(hObject, 'String')); +if isnan(tmp) | tmp<0 + errordlg('Please input a valid number.','error'); + try + set(hObject, 'String', handles.clusterSizeThreshold); + catch + set(hObject, 'String', '5'); + end + return +end +handles.clusterSizeThreshold=tmp; + +guidata(hObject, handles); +CallBack_allIntensityRadio(hObject, eventdata, 'c'); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% save image push button +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_saveImagePush(hObject, eventdata, thisfilename, isMask) +handles = guidata(hObject); + +if exist('thisfilename') + if ~strcmp(deblank(thisfilename), '') + if isfield(handles,'imageFileName') + if ~isempty(handles.imageFileName) + mni2mask(cell2mat(handles.currentDisplayMNI'), thisfilename, cell2mat(handles.currentDisplayIntensity), handles.M{1}, handles.DIM{1}, handles.imageFileName{1}); + return; + end + end + mni2mask(cell2mat(handles.currentDisplayMNI'), thisfilename, cell2mat(handles.currentDisplayIntensity), handles.M{1}, handles.DIM{1}); + return; + end +end + +[filename, pathname] = uiputfile('*.img', 'Save image file as', get(handles.saveImageFileEdit, 'string')); +if isequal(filename,0) | isequal(pathname,0) + return +else + thisfilename = fullfile(pathname, filename); +end + +if isfield(handles,'imageFileName') + if ~isempty(handles.imageFileName{1}) + mni2mask(cell2mat(handles.currentDisplayMNI'), thisfilename, cell2mat(handles.currentDisplayIntensity'), handles.M{1}, handles.DIM{1}, handles.imageFileName{1}, isMask); + return; + end +end + +mni2mask(cell2mat(handles.currentDisplayMNI'), thisfilename, cell2mat(handles.currentDisplayIntensity), handles.M{1}, handles.DIM{1}, '', isMask); + +return; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% save image edit +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_saveImageFileEdit(hObject, eventdata) +handles = guidata(hObject); +CallBack_saveImagePush(handles.saveImagePush, eventdata, get(hObject,'string')); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% save result push +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_saveResultPSPush(hObject, eventdata, thisfilename) + +handles = guidata(hObject); + +if exist('thisfilename') + if ~strcmp(deblank(thisfilename), '') + spm_print(handles.figure); + if strcmp(handles.system, 'linux') + system(['ps2pdf spm2.ps']); + system(['mv spm2.ps ' thisfilename]); + [p,f,ext]=fileparts(thisfilename); + system(['mv spm2.pdf ' fullfile(p,f) '.pdf']); + elseif strcmp(handles.system, 'windows') + system(['move spm2.ps ' '"' thisfilename '"']); + end + return; + end +end + +[filename, pathname] = uiputfile('*.ps', 'Save result as', get(handles.saveResultPSEdit, 'string')); +if isequal(filename,0) | isequal(pathname,0) + return +else + thisfilename = fullfile(pathname, filename); +end + + +% print +H = findobj(get(handles.figure,'Children'),'flat','Type','axes'); +un = cellstr(get(H,'Units')); +pos = get(H,'position'); +index = []; + +for ii=1:length(H) + if findstr('pixels', un{ii}) + continue; + end + if pos{ii}(1)>0.4 & pos{ii}(2) > 0.5 + set(H(ii),'position',[pos{ii}(1), pos{ii}(2), pos{ii}(3)*3/4, pos{ii}(4)]); + index = [index ii]; + end +end + + +spm_print(handles.figure); +if strcmp(handles.system, 'linux') + system(['ps2pdf spm2.ps']); + system(['mv spm2.ps ' thisfilename]); + [p,f,ext]=fileparts(thisfilename); + system(['mv spm2.pdf ' fullfile(p,f) '.pdf']); +elseif strcmp(handles.system, 'windows') + system(['move spm2.ps ' '"' thisfilename '"']); +end + +% set the position back +set(H(index), {'position'}, pos(index)); + + +% printstr = ['print -dpsc2 -painters -noui ' '''' thisfilename '''']; +% try +% orient portrait +% eval(printstr); +% printsuccess = 1; +% catch +% errordlg('Print to ps file failed', 'print error'); +% printsuccess = 0; +% end +%set(H,{'Units'},un); +% if strcmp(handles.system, 'linux') & printsuccess == 1 +% system(['ps2pdf ' '''' thisfilename '''']); +% end + +return; + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% save result edit +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_saveResultPSEdit(hObject, eventdata) +handles = guidata(hObject); +CallBack_saveResultPSPush(hObject, eventdata, get(handles.saveResultPSEdit,'string')); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% select a cluster +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_selectThisClusterPush(hObject, eventdata) +handles = guidata(hObject); + +try + xyz = handles.currentxyz'; +catch + xyz = spm_XYZreg('GetCoords',handles.hReg); +end + +try + handles.selectedCluster = [handles.selectedCluster; xyz']; +catch + handles.selectedCluster = xyz'; +end + +disp('handles defines!') +assignin('base','handles',handles) + +set(handles.figure,'currentaxes', handles.glassViewAxes); +xrange = xlim; +yrange = ylim; +try + delete(handles.hGlassText) +end +%handles.hGlassText = text(xrange(1)+diff(xrange)*0.6, yrange(1)+diff(yrange)*0.9, [num2str(size(handles.selectedCluster,1)) ' clusters selected']); +set(handles.infoTextBox, 'string', [num2str(size(handles.selectedCluster,1)) ' clusters selected']); +guidata(hObject, handles); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% unselect a cluster +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_clearSelectedClusterPush(hObject, eventdata) +handles = guidata(hObject); +try + handles = rmfield(handles,'selectedCluster'); +end +set(handles.figure,'currentaxes', handles.glassViewAxes); +xrange = xlim; +yrange = ylim; +try + %delete(handles.hGlassText) + set(handles.infoTextBox, 'string', ['No clusters selected']); +end + +guidata(hObject, handles); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% pick a cluster +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_pickThisClusterPush(hObject, eventdata) +handles = guidata(hObject); +mni = cell2mat(handles.currentDisplayMNI'); +if isempty(mni) + %errordlg('No cluster is picked up.','oops'); + set(handles.infoTextBox, 'string', 'No cluster is picked up.'); + beep + return; +end + +if ~isfield(handles, 'selectedCluster') | isempty(handles.selectedCluster) + try + xyz = handles.currentxyz'; + catch + xyz = spm_XYZreg('GetCoords',handles.hReg); + end + handles.selectedCluster = xyz'; +end + +intensity = cell2mat(handles.currentDisplayIntensity'); +cor = mni2cor(mni, handles.M{1}); +A = spm_clusters(cor'); +xyzcor = mni2cor(handles.selectedCluster, handles.M{1}); + +pos = []; +for ii = 1:size(xyzcor,1) + pos0 = find(cor(:,1)==xyzcor(ii,1) & cor(:,2)==xyzcor(ii,2) & cor(:,3)==xyzcor(ii,3)); + if isempty(pos0) + continue; + end + pos = [pos find(A==A(pos0(1)))]; +end +if isempty(pos) + %errordlg('No cluster is picked up.','oops'); + set(handles.infoTextBox, 'string', 'No cluster is picked up.'); + beep + return +end + +pos = unique(pos); + +tmpmni = mni(pos,:); +tmpintensity = intensity(pos); + +[B,I,J] = unique(tmpmni, 'rows'); +handles.currentDisplayMNI = {B}; +fprintf('Cluster defined from: %s \n', handles.imageFileName{1}); +assignin('base', 'currentDisplayMNI', handles.currentDisplayMNI ); +handles.currentDisplayIntensity = {tmpintensity(I,:)}; + +handles.currentxyz = handles.selectedCluster(end,:); + +[handles.hReg, handles.hSection, handles.hcolorbar] = Draw(handles.currentDisplayMNI, handles.currentDisplayIntensity, hObject, handles); +set(handles.figure,'currentaxes', handles.glassViewAxes); +xrange = xlim; +yrange = ylim; + +handles.selectedCluster = []; +try + delete(handles.hGlassText) +end + +guidata(hObject, handles); + +set(handles.thisClusterSizeEdit,'string', num2str(size(handles.currentDisplayMNI{1},1))); +str = get(handles.searchContentEdit, 'string'); +pos = findstr(' ', str); +str(pos) = []; +files = []; +for ii=1:length(handles.imageFileName) + [a,b,c] = fileparts(handles.imageFileName{ii}); + files = [files b]; +end +if ~isempty(files) + files = ['_from_' files]; +end + +set(handles.saveImageFileEdit, 'string', [str files '.img']); + +% list structure of voxels in this cluster +[a, b] = cuixuFindTDstructure(cell2mat(handles.currentDisplayMNI'), handles.DB, 0); +names = unique(b(:)); +index = NaN*zeros(length(b(:)),1); +for ii=1:length(names) + pos = find(strcmp(b(:),names{ii})); + index(pos) = ii; +end + +for ii=1:max(index) + report{ii,1} = names{ii}; + report{ii,2} = length(find(index==ii)); +end +for ii=1:size(report,1) + for jj=ii+1:size(report,1) + if report{ii,2} < report{jj,2} + tmp = report(ii,:); + report(ii,:) = report(jj,:); + report(jj,:) = tmp; + end + end +end +report = [{'structure','# voxels'}; {'--TOTAL # VOXELS--', length(a)}; report]; +% format long +% disp(b) +% disp(report) +% format +report2 = {sprintf('%s\t%s',report{1,2}, report{1,1}),''}; +for ii=2:size(report,1) + if strcmp('Unidentified', report{ii,1}); continue; end + report2 = [report2, {sprintf('%5d\t%s',report{ii,2}, report{ii,1})}]; +end + +report2 = [report2, {'','select and Ctrl-C to copy'}]; +% f = figure('unit','normalized', 'menubar','none', 'position', [0.3 0.2 0.2 min(0.7,0.016*length(report2))], 'name', 'cluster information', 'NumberTitle','off'); +% hEdit = uicontrol(f,'style','edit',... +% 'unit','normalized','position',[0 0 1 1],... +% 'horizontal','left',... +% 'BackgroundColor', 'w',... +% 'String', report2,... +% 'max',2,'min',0); + +set(handles.infoTextBox, 'string', report2); +assignin('base', 'ans', handles.currentDisplayMNI) +disp('Cluster coordinates in "ans"') +return + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% commonRegion push +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_commonRegionPush(hObject, eventdata) +handles = guidata(hObject); +if ~isfield(handles,'TF') | length(handles.TF)<=1 + %msgbox('Two or more images need to be loaded to find the common region.', 'info'); + set(handles.infoTextBox, 'string', 'Two or more images need to be loaded to find the common region.'); + beep + return; +end + +common = handles.currentDisplayMNI{1}; +for ii=2:length(handles.TF) + common = intersect(common, handles.currentDisplayMNI{ii}, 'rows'); +end + +if isempty(common) + %msgbox('No common region found.', 'info'); + set(handles.infoTextBox, 'string', 'No common region found.'); + beep + return; +end + +tmpMNI = cell2mat(handles.currentDisplayMNI'); +tmpIntensity = cell2mat(handles.currentDisplayIntensity'); +intensity = zeros(size(common,1),1); + +for ii=1:size(common,1) + pos = find(abs(tmpMNI(:,1)-common(ii,1))<0.1 & abs(tmpMNI(:,2)-common(ii,2))<0.1 & abs(tmpMNI(:,3)-common(ii,3))<0.1); + intensity(ii) = prod(tmpIntensity(pos)); +end + +handles.currentDisplayMNI = {common}; +handles.currentDisplayIntensity = {intensity}; +[handles.hReg, handles.hSection, handles.hcolorbar] = Draw(handles.currentDisplayMNI, handles.currentDisplayIntensity, hObject, handles); + +% if isfield(handles,'hLegend') +% try +% set(cell2mat(handles.hLegend'),'visible',{'off'}); +% end +% end +guidata(hObject, handles); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% volume push +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_volumePush(hObject, eventdata) +handles = guidata(hObject); +mni = cell2mat(handles.currentDisplayMNI'); +intensity = cell2mat(handles.currentDisplayIntensity'); + +xSPM.XYZ = mni2cor(mni, handles.M{1}); +xSPM.XYZ = xSPM.XYZ'; +xSPM.XYZmm = mni'; +xSPM.Z = (intensity'); +xSPM.M = handles.M{1}; +xSPM.DIM = handles.DIM{1}; +xSPM.STAT = handles.TF{1}; +if xSPM.STAT == 'T' + xSPM.STATstr = [xSPM.STAT '_{' num2str(handles.df{1}) '}']; + xSPM.df = [1 handles.df{1}]; +elseif xSPM.STAT == 'F' + xSPM.STATstr = [xSPM.STAT '_{' num2str(handles.df{1}(1)) ',' num2str(handles.df{1}(2)) '}']; + xSPM.df = [handles.df{1}]; +else + xSPM.STAT = 'T'; + xSPM.STATstr = [xSPM.STAT '_{' num2str(handles.df{1}) '}']; + xSPM.df = [1 handles.df{1}]; +end +xSPM.k = str2num(get(handles.clusterSizeThresholdEdit, 'string')); +xSPM.u = str2num(get(handles.intensityThresholdEdit, 'string')); +xSPM.u = xSPM.u(1); +xSPM.VOX = abs([xSPM.M(1,1) xSPM.M(2,2) xSPM.M(3,3)]); +xSPM.n = 1; + + +% SPM - structure containing SPM, distribution & filtering details +% - required fields are: +% .swd - SPM working directory - directory containing current SPM.mat +% .Z - minimum of n Statistics {filtered on u and k} +% .n - number of conjoint tests +% .STAT - distribution {Z, T, X or F} +% .df - degrees of freedom [df{interest}, df{residual}] +% .u - height threshold +% .k - extent threshold {voxels} +% .XYZ - location of voxels {voxel coords} +% .XYZmm - location of voxels {mm} +% .S - search Volume {voxels} +% .R - search Volume {resels} +% .FWHM - smoothness {voxels} +% .M - voxels - > mm matrix +% .VOX - voxel dimensions {mm} +% .Vspm - mapped statistic image(s) +% .Ps - uncorrected P values in searched volume (for FDR) + +xSPM.S = length(handles.intensity{1}); +xSPM.R = [3 27.0931 276.3307 498.1985]; +xSPM.FWHM = [2.9746 3.1923 2.8600]; + +if get(handles.positiveIntensityRadio, 'Value') + xSPM.Z = abs(xSPM.Z); + if xSPM.STAT == 'T'; xSPM.Ps = (1-spm_Tcdf(intensity, xSPM.df(2))); end + if xSPM.STAT == 'F'; xSPM.Ps = (1-spm_Fcdf(intensity, xSPM.df)); end +end + +if get(handles.negativeIntensityRadio, 'Value'); + xSPM.Z = abs(xSPM.Z); + if xSPM.STAT == 'T'; xSPM.Ps = (1-spm_Tcdf(-intensity, xSPM.df(2))); end + if xSPM.STAT == 'F'; xSPM.Ps = (1-spm_Fcdf(-intensity, xSPM.df)); end +end + +if get(handles.allIntensityRadio, 'Value'); + if xSPM.STAT == 'T'; xSPM.Ps = (1-spm_Tcdf((intensity), xSPM.df(2))); end + if xSPM.STAT == 'F'; xSPM.Ps = (1-spm_Fcdf((intensity), xSPM.df)); end +end + +if findstr('SPM2',spm('ver')) + P = spm_get([0 1],'SPM.mat','locate the corresponding SPM.mat'); +elseif findstr('SPM5',spm('ver')) + P = spm_select([0:1],'SPM.mat','locate the corresponding SPM.mat'); +end + +if ~isempty(P) + load(P); + xSPM.FWHM = SPM.xVol.FWHM; + xSPM.R = SPM.xVol.R; + xSPM.S = SPM.xVol.S; +else + warndlg(['You did not input SPM.mat. The listed result may not be correct.'], 'SPM.mat missing'); +end + +spm_list('List',xSPM,handles.hReg); + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% display +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_displayPush(hObject, eventdata) +handles = guidata(hObject); +spm_image('init', handles.imageFileName); + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% all in one +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_allinonePush(hObject, eventdata) +try + if findstr('SPM2',spm('ver')) + P = spm_get([0:100],'*IMAGE','Select image files'); + elseif findstr('SPM5',spm('ver')) + P = spm_select(Inf,'image','Select image files'); + end + +catch + return; +end +if isempty(P) + return +end + +handles = guidata(hObject); +%if ~isfield(handles,'hReg') | ~isfield(handles,'hSection') | ~isfield(handles,'hcolorbar') +tmp = [0 0 0]; +[handles.hReg, handles.hSection, handles.hcolorbar] = Draw(tmp([],:), [], hObject, handles); +%end + +colors=mycolourset; +for ii=1:size(P,1) + thisfilename = deblank(P(ii,:)); + [handles.imageFileName, handles.M, handles.DIM, handles.TF, handles.df, handles.mni, handles.intensity] = getImageFile(thisfilename); + cor = mni2cor(handles.mni, handles.M); + spm_orthviews('addcolouredblobs',1,cor',handles.intensity',handles.M,colors(mod(ii,6)+1,:)); +end +spm_orthviews('Redraw'); + +guidata(hObject, handles); + +% contents = get(handles.sectionViewListbox,'String'); +% currentsel = contents{get(handles.sectionViewListbox,'Value')}; +% sectionViewTargetFile = getSectionViewTargetFile(handles.spmdir, currentsel); +% +% % spm_image('init', sectionViewTargetFile); +% % spm_image('addblobs'); +% % return + +% guidata(hObject, handles); +% load xSPM; +% VOL.XYZ = xSPM.XYZ; +% VOL.Z = xSPM.Z; +% VOL.M = handles.M; + +%addcolouredimage(handles.hSection, '333.img',[1 0 1]); +% uigetfiles +% nblobs = 4; +% for i=1:nblobs, +% %[SPM,VOL] = spm_getSPM; +% %c = spm_input('Colour','+1','m','Red blobs|Yellow blobs|Green blobs|Cyan blobs|Blue blobs|Magenta blobs',[1 2 3 4 5 6],1); +% c = i; +% VOL.XYZ = ceil(rand(3,20)*20); +% VOL.Z = randn(1,20); +% VOL.M = handles.M; +% colours = [1 0 0;1 1 0;0 1 0;0 1 1;0 0 1;1 0 1]; +% spm_orthviews('addcolouredblobs',1,VOL.XYZ,VOL.Z,VOL.M,colours(c,:)); +% end; + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% overlay a brain region Push +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_overlayPush(hObject, eventdata) +handles = guidata(hObject); + +tobeoverlay = deblank(get(handles.overlayEdit, 'string')); + +if isempty(tobeoverlay) + return; +end + +tobeoverlay = str2cell(tobeoverlay, ' '); +if isnumeric(tobeoverlay{1}) + if length(tobeoverlay{1})>1 + errordlg(['Your input, ' deblank(get(handles.overlayEdit, 'string')) ', seems coincide with a matlab constant.'], 'error'); + return + end + for ii=1:length(tobeoverlay) + tobeoverlay{ii} = num2str(tobeoverlay{ii}); + end +end +fn = fieldnames(handles.wholeMaskMNIAll); +for ii=1:length(tobeoverlay) + pos{ii} = []; + for jj=1:length(fn) + x = []; + if ~isempty(str2num(tobeoverlay{ii})) + y = str2cell(fn{jj},'_'); + for kk=1:length(y) + x = isequal(tobeoverlay{ii}, y{kk}); + if x; break;end; + end + if x==0; x = []; end; + else + x = findstr(lower(tobeoverlay{ii}), lower(fn{jj})); + end + if ~isempty(x) + pos{ii} = [pos{ii} jj]; + end + end +end +common = pos{1}; +for ii=2:length(pos) + common = intersect(common, pos{ii}); +end + +if isempty(common) + %warndlg(['I don'' find ' deblank(get(handles.overlayEdit, 'string')) '.'], 'oops'); + set(handles.infoTextBox, 'string', ['I don'' find ' deblank(get(handles.overlayEdit, 'string')) '.']); + return +end + +mask = []; +for ii=1:length(common) + eval(['mask = [mask; handles.wholeMaskMNIAll.' fn{common(ii)} '];']); +end + +if isempty(mask) + %warndlg(['I don'' find ' deblank(get(handles.overlayEdit, 'string')) '.'], 'oops'); + set(handles.infoTextBox, 'string', ['I don'' find ' deblank(get(handles.overlayEdit, 'string')) '.']); + return; +end + +try + handles.mni; +catch + delete(gcf); + xjview(mask); + return; +end + +handles.imageFileName = [handles.imageFileName, {deblank(get(handles.overlayEdit, 'string'))}]; +handles.M = [handles.M, {handles.M{1}}]; +handles.DIM = [handles.DIM, {handles.DIM{1}}]; +handles.currentDisplayMNI = [handles.currentDisplayMNI, {mask}]; +m = max(abs(cell2mat(handles.currentDisplayIntensity'))); +if ~isempty(m) + handles.currentDisplayIntensity = [handles.currentDisplayIntensity, {-2*m*ones(size(mask,1),1)}]; +else + handles.currentDisplayIntensity = [handles.currentDisplayIntensity, {ones(size(mask,1),1)}]; +end +[handles.hReg, handles.hSection, handles.hcolorbar] = Draw(handles.currentDisplayMNI, handles.currentDisplayIntensity, hObject, handles); +return + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% overlay a brain region Edit +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_overlayEdit(hObject, eventdata) +handles = guidata(hObject); +CallBack_overlayPush(handles.overlayPush, eventdata); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% overlay a brain region popup +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_overlayPop(hObject, eventdata) +handles = guidata(hObject); +names = get(hObject, 'string'); +value = get(hObject, 'value'); +set(handles.overlayEdit, 'string', names{value}); +CallBack_overlayEdit(handles.overlayEdit, eventdata); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% search xBrain.org and other databases +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_searchPush(hObject, eventdata) + +try + handles = guidata(hObject); + searchContent = get(handles.searchContentEdit,'string'); + searchEngine = get(handles.searchEnginePop,'string'); + searchEngine = searchEngine{get(handles.searchEnginePop,'value')}; + + + xyz = str2num(searchContent); + searchMode=get(handles.searchContentEdit,'UserData'); + + switch searchMode + case 'auto'; + xyz = spm_XYZreg('GetCoords',handles.hReg); + handles.currentxyz = xyz'; + guidata(hObject, handles); + [a,b] = cuixuFindTDstructure(xyz', handles.DB, 0); + try + brodmann = strmatch('Brodmann',b) + brodmann = b{brodmann}; + brodmann = brodmann(1:end-4); + end; + try + region = b{3}; + region = region(1:end-4); + end + + case 'manual' + if isempty(xyz) + searchMode='text'; + elseif length(xyz)==3 + searchMode='xyz'; + end + end + + + switch searchEngine + case 'Brede' + switch searchMode + case 'auto' + searchString = sprintf(' %0.0f',xyz') + set(handles.searchContentEdit,'string',searchString); + case 'xyz' + searchString = num2str(xyz');%searchContent=sprintf('+%0.0f',searchContent) + case 'text' + end + urlstr = ['http://hendrix.imm.dtu.dk/cgi-bin/brede_loc_query.pl?q=' searchString ]; + case 'xBrain.org' + switch searchMode + case { 'auto' 'xyz'} + xbrainSearchField = 'mni or tal&mnidistance=20'; + searchString = sprintf(' %0.1f',xyz')% searchString = num2str(xyz'); + case 'text' + xbrainSearchField = 'region'; + searchString = searchContent; + end + set(handles.searchContentEdit,'string',searchString); + urlstr = ['http://people.hnl.bcm.tmc.edu/cuixu/cgi-bin/bmd/paper.pl?search_content=' searchString '&search_field=' xbrainSearchField]; + case 'Google Scholar' + if isequal(searchMode,'auto' ) + searchString = { brodmann region }; + searchString(cellfun('isempty',searchString))=[]; + searchString = sprintf('%%22%s%%22|',searchString{:}); + try + searchString = searchString(1:end-1); + end + %set(handles.searchContentEdit,'string',searchString); + end + urlstr = ['http://scholar.google.com/scholar?q=' searchString ]; + case 'Pubmed' + switch searchMode + case 'auto' + searchString = { brodmann region }; + searchString(cellfun('isempty',searchString))=[]; + searchString = sprintf('%%22%s%%22 OR ',searchString{:}); + try + searchString = searchString(1:end-4); + end + %searchString = brodmann; + case 'text' + searchString = searchContent; + end + % urlstr = ['http://www.ncbi.nlm.nih.gov/entrez/query.fcgi?cmd=PureSearch&db=pubmed&details_term=(' searchString ')']; + urlstr = ['http://www.ncbi.nlm.nih.gov/sites/entrez?cmd=search&db=pubmed&pubmedfilters=true&term=(' searchString ')']; + case 'Wikipedia' + if isequal(searchMode,'auto') + searchString = region; + end + urlstr = ['http://en.wikipedia.org/w/index.php?search=%22' searchString '%22']; + end + set(handles.searchContentEdit,'UserData',searchMode); +catch + urlstr = 'http://www.google.com'; +end + +try + web(urlstr,'-browser'); +catch + if exist('c:\program Files\mozilla Firefox\firefox.exe','file') + system(['"c:\program Files\mozilla Firefox\firefox.exe" "' urlstr '"']) + else + web(urlstr); + end +end + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% control panel on or off +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function controlHide(handles, status) + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% get multiple values from a string deliminated delim +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function out = str2cell(str, delim) +if ~exist('delim') + delim = '; '; +end + +[out{1},b] = strtok(str, delim); +ii = 2; +while ~isempty(b) + [out{ii},b] = strtok(b, delim); + ii = ii+1; +end + +for ii=1:length(out) + out2{ii} = str2num(out{ii}); + if isempty(out2{ii}) + return; + end +end + +for ii=1:length(out) + out{ii} = str2num(out{ii}); +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% cell2str +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function str = cell2str(acell, delim) +if ~exist('delim') + delim = ';'; +end + +str = []; + +if length(acell)==1 + if isstr(acell{1}) + str = acell{1}; + else + str = num2str(acell{1}); + end +else + for ii=1:length(acell) + if isstr(acell{ii}) + str = [str acell{ii}, delim ' ']; + else + str = [str num2str(acell{ii}), delim ' ']; + end + end + str(end-1:end)=[]; +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% cellmax, find max in each element, return a cell +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function MAXX = cellmax(acell, absolute) + +if ~exist('absolute') + absolute = ''; +end + +MAXX = []; +for ii=1:length(acell) + if strfind('abs',absolute) + MAXX = [MAXX max(abs(acell{ii}))]; + else + MAXX = [MAXX max(acell{ii})]; + end +end +MAXX = num2cell(MAXX); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% maxcell +%%% find max of all numbers in the whole cell, return a single value +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function MAXX = maxcell(acell, absolute) + +if ~exist('absolute') + absolute = ''; +end +MAXX = cellmax(acell, absolute); +MAXX = max(cell2mat(MAXX)); + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% p2t +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function t = p2t(p, df, TF) +if ~iscell(p) + if upper(TF)=='T' | upper(TF)=='S' + t = spm_invTcdf(1-p,df); + elseif upper(TF) == 'F' + t = spm_invFcdf(1-p,df); + end +else + for ii=1:length(p) + t{ii} = p2t(p{ii},df{ii},TF{ii}); + end +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% s2t, s is defined as -log10(p) +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function t = s2t(s, df, TF) + +if ~iscell(s) + p = 10^(-s); + if upper(TF)=='T' | upper(TF)=='S' + t = spm_invTcdf(1-p,df); + elseif upper(TF) == 'F' + t = spm_invFcdf(1-p,df); + end +else + for ii=1:length(p) + t{ii} = s2t(s{ii},df{ii},TF{ii}); + end +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% t2p +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function p = t2p(t, df, TF) +if ~iscell(t) + if upper(TF)=='T' | upper(TF)=='S' + p = 1-spm_Tcdf(t,df); + elseif upper(TF) == 'F' + p = 1-spm_Fcdf(t,df); + end +else + for ii=1:length(t) + p{ii} = t2p(t{ii},df{ii},TF{ii}); + end +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% t2s, s is defined as -log10(p) +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function s = t2s(t, df, TF) +if ~iscell(t) + if upper(TF)=='T' | upper(TF)=='S' + p = 1-spm_Tcdf(t,df); + elseif upper(TF) == 'F' + p = 1-spm_Fcdf(t,df); + else + p = 0.1; + end + s = -log10(p); +else + for ii=1:length(t) + s{ii} = t2s(t{ii},df{ii},TF{ii}); + end +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% mni2mask +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function mask = mni2mask(coords, targetfilename, intensity, M, DIM, templateFile, isMask) +% function mask = mni2mask(coords, targetfilename, intensity, templateFile) +% make mask from coordinates +% +% coords: a Nx3 column of 3-d coordinates in MNI +% space +% targetfilename: (optional) the image files to be written +% intensity: (optional) Nx1, the values of each coordinate. +% M: rotation matrix +% DIM: dimension +% templateFile: (optional) the templateFile from which we can get the right +% dimensions. +% isMask: if this variable exist and equal to 1, then all non-zero +% intensities will be set to 1 +% +% Xu Cui +% 2004/11/18 + +if ~exist('intensity') + intensity = ones(size(coords,1),1); +end +thistemplateFile = ''; +if exist('templateFile') + if ~isempty(templateFile) + thistemplateFile = templateFile; + end +end + +if isempty(thistemplateFile) + V.mat = [... + -4 0 0 84; ... + 0 4 0 -116; ... + 0 0 4 -56; ... + 0 0 0 1]; + V.dim = [41 48 35 16]; + if exist('M') + V.mat = M; + end + if exist('DIM') + V.dim = DIM; + V.dim(4) = 16; + end + V.fname = targetfilename; + V.descrip = 'our own image'; +else + V = spm_vol(templateFile); + V.fname = targetfilename; + if isfield(V, 'descrip') + V.descrip = ['my image from ' V.descrip]; + else + V.descrip = 'my own image'; + end +end + +thisismask = 0; +if exist('isMask') + if isMask == 1 + thisismask = 1; + end +end +if thisismask + V.descrip = 'my mask'; + intensity = ones(size(intensity)); +end + +O = zeros(V.dim(1),V.dim(2),V.dim(3)); + +coords = mni2cor(coords,V.mat); + + +for ii=1:size(coords,1) + O(coords(ii,1),coords(ii,2),coords(ii,3)) = intensity(ii); +end + +if exist('targetfilename') + V = spm_write_vol(V,O); +end + +mask = O; + +return; + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% get image file information +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function [imageFile,M,DIM,TF,df,mni,intensity] = getImageFile(thisfilename) +% function imageFile = getImageFile(filename) +% get the information of this/these image file(s) +% +% thisfilename: (optional) if doesn't give file name, then show a open file +% dialog +% imageFile: the full name of the selected file (if user pressed cancel, +% imageFile == 0 +% M: M matrix (rotation matrix) +% DIM: dimension +% TF: t test or f test? 'T' or 'F' +% df: degree of freedome +% mni: mni coord +% intensity: intensity of each mni coord +% +% Note: The returned variables are cellarrays. +% +% Xu Cui +% last revised: 2005-05-03 + +if nargin < 1 | isempty(thisfilename) + if findstr('SPM2',spm('ver')) + P0 = spm_get([0:100],'*IMAGE','Select image files'); + elseif findstr('SPM5',spm('ver')) + P0 = spm_select(Inf,'image','Select image files'); + end + + % try + % P0 = spm_get([0:100],'*IMAGE','Select image files'); + % catch + % P0 = []; + % [FileName,PathName] = uigetfile({'*.img';'*.IMG';'*.*'},'Select image files','MultiSelect','on'); + % if isstr(FileName) + % P0 = {fullfile(PathName, FileName)}; + % elseif iscellstr(FileName) + % for ii=1:length(FileName) + % P0{ii} = fullfile(PathName, FileName{ii}); + % end + % else + % P0 = []; + % end + % end + try + if isempty(P0) + imageFile = '';M=[];DIM=[];TF=[];df=[];mni=[];intensity=[]; + return + end + end + for ii=1:size(P0,1) + P{ii} = deblank(P0(ii,:)); + end +else + if isstr(thisfilename) + P = {thisfilename}; + elseif iscellstr(thisfilename) + P = thisfilename; + else + disp('Error: In getImageFile: I don''t understand the input.'); + imageFile = '';M=[];DIM=[];TF=[];df=[];mni=[];intensity=[]; + return + end +end + +global LEFTRIGHTFLIP_; + +for ii=1:length(P) + imageFile{ii} = P{ii}; + [cor{ii}, intensity{ii}, tmp{ii}, M{ii}, DIM{ii}, TF{ii}, df{ii}] = mask2coord(imageFile{ii}, 0); + if LEFTRIGHTFLIP_ == 1 + M{ii}(1,:) = -M{ii}(1,:); + end + mni{ii} = cor2mni(cor{ii}, M{ii}); +end + + + + + + + + + + + + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% cuixuFindTDstructure +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function [onelinestructure, cellarraystructure] = cuixuFindTDstructure(pos, DB, TALMNI) +% function [onelinestructure, cellarraystructure] = cuixuFindTDstructure(pos, DB, TALMNI) +% +% this function converts Talairach Daemon coordinate or MNI coordinate to a description of +% brain structure. +% +% pos: the coordinate (MNI or Talairach coordinate, defined by TALMNI) of the position in the brain, in mm +% DB: the database structure (see below for detailed explanation). If you don't input this argument, there +% should be a file called 'TDdatabase.mat' in the current directory +% TALMNI: 1 if pos is talairach coordinate, 0 if pos is MNI coordinate. 1 +% by default. +% +% onelinestructure: a one-line description of the returned brain +% structure +% cellarraystructure: a cell array of size 5, each cell contains a string +% describing the structure of the brain in a certain level. +% +% Example: +% [onelinestructure, cellarraystructure] = cuixuFindTDstructure([-24 24 48], DB, 1) +% [onelinestructure, cellarraystructure] = cuixuFindTDstructure([-24 24 48]) +% then +% onelinestructure = // Left Cerebrum // Frontal Lobe (L) // Middle Frontal Gyrus (L) // Gray Matter (L) // Brodmann area 8 (L) +% cellarraystructure = 'Left Cerebrum' 'Frontal Lobe (L)' [1x24 char] 'Gray Matter (L)' 'Brodmann area 8 (L)' +% +% Xu Cui +% 2004-6-28 +% + +%---------------------------------------------------------------------------------- +% DB strcture +%---------------------------------------------------------------------------------- +%------------------------------- +% Grid specification parameters: +%------------------------------- +% minX - min X (mm) +% maxX - max X (mm) +% voxX - voxel size (mm) in X direction +% minY - min Y (mm) +% maxY - max Y (mm) +% voxY - voxel size (mm) in Y direction +% minZ - min Z (mm) +% maxZ - max Z (mm) +% voxZ - voxel size (mm) in Z direction +% nVoxX - number of voxels in X direction +% nVoxY - number of voxels in Y direction +% nVoxZ - number of voxels in Z direction +%------------------------------- +% Classification parameters: +%------------------------------- +% numClass - number of classification types +% cNames - cNames{i} - cell array of class names for i-th CT +% numClassSize - numClassSize(i) - number of classes for i-th CT +% indUnidentified - indUnidentified(i) - index of "indUnidentified" class for i-th CT +% volClass - volClass{i}(j) - number of voxels in class j for i-th CT +% +% data - N x numClass matrix of referencies; let +% x y z coordinates in mm (on the grid) and +% nx = (x-minX)/voxX +% ny = (y-minY)/voxY +% nz = (z-minZ)/voxZ +% ind = nz*nVoxX*nVoxY + ny*nVoxX + nx + 1 +% data(ind, i) - index of the class for i-th CT in cNames{i} to +% which (x y z) belongs, i.e. +% cNames{i}{data(ind, i)} name of class for i-th CT +%---------------------------------------------------------------------------------- + +if nargin==1 + load('TDdatabase.mat'); + TALMNI = 1; +elseif nargin == 2 + TALMNI = 1; +end + +if(TALMNI == 1) + pos = tal2mni(pos); +elseif(TALMNI == 0) + []; +else + disp('TALMNI should be 1 or 0.'); +end + +pos(:,1) = DB.voxX*round(pos(:,1)/DB.voxX); +pos(:,2) = DB.voxY*round(pos(:,2)/DB.voxY); +pos(:,3) = DB.voxZ*round(pos(:,3)/DB.voxZ); + +min = []; +vox = []; +for(i=1:size(pos,1)) + min = [min; DB.minX DB.minY DB.minZ]; + vox = [vox; DB.voxX DB.voxY DB.voxZ]; +end +n_pos = (pos - min)./vox; + +nx = n_pos(:,1); +ny = n_pos(:,2); +nz = n_pos(:,3); +index = nz*DB.nVoxX*DB.nVoxY + ny*DB.nVoxX + nx + 1; +indMax = size(DB.data, 1); + +onelinestructuretmp = []; +onelinestructure = []; +for(j=1:size(pos,1)) + onelinestructuretmp = []; + for(i=1:DB.numClass) + + if (index(j) <= 0 | index(j) > indMax) + ind(j) = DB.indUnidentified(i); + else + ind(j) = DB.data(index(j), i); + end + + cellarraystructure{j,i} = DB.cNames{i}{ind(j)}; + onelinestructuretmp = [onelinestructuretmp ' // ' cellarraystructure{j,i}]; + end + onelinestructure{j} = onelinestructuretmp; +end + +return + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% mask2coord +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function [cor, intensity, cor_singlecolumn,M,DIM,TF,df] = mask2coord(mask, checkdimension) +% [cor, intensity, cor_singlecolumn] = mask2coord(mask, checkdimension) +% +% This is to retrieve the coordinate of a mask file, or a matrix of 3-D +% +% mask: an image file or a matrix (3-D), with some of the elements are +% non-zeros +% checkdimension: check if the dimension is checkdimension, if not, return empty +% matrix +% cor: a N*3 matrix, which each row a coordinate in matrix +% intensity: a N*1 matrix, which encodes the intensity of each voxel. +% cor_singlecolumn: a N*1 matrix, each row is the index in the matrix +% M: rotation matrix +% DIM: dimension +% TF: t test or f test? 'T','F' or [] +% df: degree of freedome for T/F test +% +% Example: +% mask = zeros(4,3,2); +% mask(1,2,1) = 1; +% mask(3,2,2) = 1; +% mask2coord(mask) +% +% mask2coord('spmT_0002.img') +% mask2coord('spmT_0002.img',[41 48 35]) +% +% Xu Cui +% 2004-9-20 +% last revised: 2005-04-30 + +if nargin < 2 + checkdimension = 0; +end + +if ischar(mask) + V = spm_vol(mask); + mask = spm_read_vols(V); + M = V.mat; + DIM = V.dim; + TF = 'T'; + T_start = strfind(V.descrip,'SPM{T_[')+length('SPM{T_['); + if isempty(T_start); T_start = strfind(V.descrip,'SPM{F_[')+length('SPM{F_['); TF='F'; end + if isempty(T_start) + TF=[]; df=[]; + else + T_end = strfind(V.descrip,']}')-1; + df = str2num(V.descrip(T_start:T_end)); + end +else + M = []; + TF = []; + df = []; +end + +dim = size(mask); +if length(checkdimension)==3 + if dim(1)~= checkdimension(1) | dim(2)~= checkdimension(2) | dim(3)~= checkdimension(3) + y = []; + disp('dimension doesn''t match') + return + end +end + +pos = find(mask~=0); +intensity = mask(pos); + +y = zeros(length(pos),3); + +y(:,3) = ceil(pos/(dim(1)*dim(2))); +pos = pos - (y(:,3)-1)*(dim(1)*dim(2)); +y(:,2) = ceil(pos/dim(1)); +pos = pos - (y(:,2)-1)*(dim(1)); +y(:,1) = pos; + +cor = y; +cor_singlecolumn = pos; +DIM = dim; +return + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% mni2cor +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function coordinate = mni2cor(mni, T) +% function coordinate = mni2cor(mni, T) +% convert mni coordinate to matrix coordinate +% +% mni: a Nx3 matrix of mni coordinate +% T: (optional) transform matrix +% coordinate is the returned coordinate in matrix +% +% caution: if T is not specified, we use: +% T = ... +% [-4 0 0 84;... +% 0 4 0 -116;... +% 0 0 4 -56;... +% 0 0 0 1]; +% +% xu cui +% 2004-8-18 +% + +if isempty(mni) + coordinate = []; + return; +end + +if ~exist('T') + T = ... + [-4 0 0 84;... + 0 4 0 -116;... + 0 0 4 -56;... + 0 0 0 1]; +end + +coordinate = [mni(:,1) mni(:,2) mni(:,3) ones(size(mni,1),1)]*(inv(T))'; +coordinate(:,4) = []; +coordinate = round(coordinate); +return; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% cor2mni +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function mni = cor2mni(cor, T) +% function mni = cor2mni(cor, T) +% convert matrix coordinate to mni coordinate +% +% cor: an Nx3 matrix +% T: (optional) rotation matrix +% mni is the returned coordinate in mni space +% +% caution: if T is not given, the default T is +% T = ... +% [-4 0 0 84;... +% 0 4 0 -116;... +% 0 0 4 -56;... +% 0 0 0 1]; +% +% xu cui +% 2004-8-18 +% last revised: 2005-04-30 + +if nargin == 1 + T = ... + [-4 0 0 84;... + 0 4 0 -116;... + 0 0 4 -56;... + 0 0 0 1]; +end + +cor = round(cor); +mni = T*[cor(:,1) cor(:,2) cor(:,3) ones(size(cor,1),1)]'; +mni = mni'; +mni(:,4) = []; +return; + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% draw +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function [hReg, hSection, hcolorbar] = Draw(mniCoord, intensity, hObject, handles) + +try + delete(handles.hcolorbar); +end +try + delete(handles.hReg); +end +try + delete(handles.hSection); +end +try + cla(handles.glassViewAxes); +end + +try + H = findobj(get(handles.figure,'Children'),'flat','Type','axes'); + un = cellstr(get(H,'Units')); + pos = get(H,'position'); + + for ii=1:length(H) + if findstr('pixels', un{ii}) + continue; + end + if pos{ii}(1)>0.4 + delete(H(ii)); + end + end +end + +sectionViewTargetFile = handles.sectionViewTargetFile; + +if ~iscell(mniCoord) | (iscell(mniCoord) & length(mniCoord)==1)% multiple input? no + if (iscell(mniCoord) & length(mniCoord)==1) + mniCoord = mniCoord{1}; + intensity = intensity{1}; + handles.M = handles.M{1}; + handles.DIM = handles.DIM{1}; + end + if max(intensity)*min(intensity) < 0 + [hReg, hSection, hcolorbar] = cuixuSectionView(mniCoord,intensity,sectionViewTargetFile,hObject,handles); + else + [hReg, hSection, hcolorbar] = cuixuSectionView(mniCoord,abs(intensity),sectionViewTargetFile,hObject,handles); + end + + if size(mniCoord,1)>1 + pos1 = find(intensity>=0); + pos2 = find(intensity<0); + if ~isempty(pos1) & ~isempty(pos2) + if get(handles.renderViewCheck, 'Value'); cuixuRenderView(mniCoord(pos1,:),intensity(pos1,:),mniCoord(pos2,:),-intensity(pos2,:)); end + else + if get(handles.renderViewCheck, 'Value'); cuixuRenderView(mniCoord, abs(intensity)); end + end + end +else % multiple input? yes + [hReg, hSection, hcolorbar] = cuixuSectionView(mniCoord,intensity,sectionViewTargetFile,hObject,handles); + + mniCoordtmp=[]; + intensitytmp=[]; + for ii=1:length(mniCoord) + mniCoordtmp = [mniCoordtmp; mniCoord{ii}]; + intensitytmp = [intensitytmp; intensity{ii}]; + end + mniCoord = mniCoordtmp; + intensity = intensitytmp; + if size(mniCoord,1)>1 + pos1 = find(intensity>=0); + pos2 = find(intensity<0); + if ~isempty(pos1) & ~isempty(pos2) + if get(handles.renderViewCheck, 'Value'); cuixuRenderView(mniCoord(pos1,:),intensity(pos1,:),mniCoord(pos2,:),-intensity(pos2,:)); end + else + if get(handles.renderViewCheck, 'Value'); cuixuRenderView(mniCoord, abs(intensity)); end + end + end + +end + +try + spm_XYZreg('SetCoords',handles.currentxyz',hReg); +end + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% cuixuSectionView +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function [hReg, hSection, hcolorbar] = cuixuSectionView(mniCoord, intensity, targetFile, hObject,handles) +% function h = cuixuSectionView(mniCoord, intensity) +% This is to project your coordinate to section view +% +% mniCoord: the mni coordinate, N*3 matrix +% intensity: the plot intensity of the spots, 1*N matrix. The intensity +% could be t value, for example. +% +% a special feature of this function is: it automatically seperate the +% negative and positive intensity and use hot and cold color to represent +% them. +% +% h: the returned handle for the axes +% +% SEE ALSO: cuixuView cuixuGlassView cuixuRenderView +% +% Xu Cui +% 2004/11/11 + +if ~iscell(mniCoord) | (iscell(mniCoord) & length(mniCoord)==1)% multiple input? no + multiple = 0; + if (iscell(mniCoord) & length(mniCoord)==1) + mniCoord = mniCoord{1}; + intensity = intensity{1}; + handles.M = handles.M{1}; + handles.DIM = handles.DIM{1}; + end +else + multiple = 1; + mniCoordtmp = []; + intensitytmp = []; + for ii=1:length(mniCoord) + mniCoordtmp = [mniCoordtmp; mniCoord{ii}]; + intensitytmp = [intensitytmp; intensity{ii}]; + % for multiple input + cSPM{ii}.XYZ = mni2cor(mniCoord{ii}, handles.M{ii}); + cSPM{ii}.XYZ = cSPM{ii}.XYZ'; + cSPM{ii}.Z = abs(intensity{ii}); + cSPM{ii}.M = handles.M{ii}; + cSPM{ii}.DIM = handles.DIM{ii}'; + end + mniCoord = mniCoordtmp; + intensity = intensitytmp; + handles.M = handles.M{1}; + handles.DIM = handles.DIM{1}; +end + +SPM.XYZ = mni2cor(mniCoord, handles.M); +SPM.XYZ = SPM.XYZ'; +SPM.Z = intensity; +SPM.M = handles.M; +SPM.DIM = handles.DIM'; + +%%%%%%%%%%%%%%% Reg %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +handles.colormap = colormap; + +coor = mniCoord; +xSPM = SPM; +xSPM.XYZmm = mniCoord'; +axes(handles.glassViewAxes) +WS = spm('WinScale'); +FS = spm('FontSizes'); + +Finter = gcf; +hReg = uicontrol(Finter,'Style','Frame','Position',[60 100 300 300].*WS,... + 'Visible','off'); +[hReg,xyz] = spm_XYZreg('InitReg',hReg,xSPM.M,xSPM.DIM,[0;0;0]); + +hFxyz = spm_results_ui('DrawXYZgui',xSPM.M,xSPM.DIM,xSPM,xyz,Finter); +spm_XYZreg('XReg',hReg,hFxyz,'spm_results_ui'); + +hMIPax =gca ; +setcolormap('gray'); + +hMIPax = spm_mip_ui(xSPM.Z,coor',xSPM.M,xSPM.DIM,hMIPax); +spm_XYZreg('XReg',hReg,hMIPax,'spm_mip_ui'); + +colormap(handles.colormap); +setcolormap('gray-hot'); + +if isempty(handles.M) + spm_XYZreg('SetReg',handles.structureEdit,hReg); +else + set(handles.structureEdit, 'UserData', struct('hReg',hReg,'M',handles.M,'D', handles.DIM,'xyx',[0 0 0])); +end + +spm_XYZreg('Add2Reg',hReg,handles.structureEdit,@CallBack_structureEdit); + +%%%%%%%%%%%%%%% Reg end %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +if multiple == 0 + [hgraph, hSection, hcolorbar] = spm_sections(SPM,hReg,targetFile,handles.sectionViewPosition); +else + [hgraph, hSection, hcolorbar] = spm_sections(cSPM,hReg,targetFile,handles.sectionViewPosition); +end + +return + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% spm_sections +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function [Fgraph, hMe, hcolorbar] = spm_sections(SPM,hReg,targetFile,sectionViewPosition) +% rendering of regional effects [SPM{Z}] on orthogonal sections +% FORMAT spm_sections(SPM,hReg) +% +% SPM - xSPM structure containing details of excursion set +% hReg - handle of MIP register +% +% see spm_getSPM for details +%_______________________________________________________________________ +% +% spm_sections is called by spm_results and uses variables in SPM and +% VOL to create three orthogonal sections though a background image. +% Regional foci from the selected SPM are rendered on this image. +% +%_______________________________________________________________________ +% @(#)spm_sections.m 2.14 John Ashburner 02/09/05 + +Fgraph = gcf; + +spms = fullfile(spm('dir'),'canonical', 'single_subj_T1.mnc'); +if exist('targetFile') + spms = targetFile; +end + +spm_orthviews('Reset'); +global st +st.fig = Fgraph; +st.Space = spm_matrix([0 0 0 0 0 -pi/2])*st.Space; + +spm_orthviews('Image',spms,sectionViewPosition); % position +spm_orthviews('MaxBB'); +spm_orthviews('register',hReg); +if ~iscell(SPM) + spm_orthviews('addblobs',1,SPM.XYZ,SPM.Z,SPM.M); +elseif length(SPM) == 1 + SPM = SPM{1}; + spm_orthviews('addblobs',1,SPM.XYZ,SPM.Z,SPM.M); +else + colors = mycolourset; + for ii=1:length(SPM) + spm_orthviews('addcolouredblobs',1,SPM{ii}.XYZ, SPM{ii}.Z, SPM{ii}.M, colors(ii,:)); + end +end +spm_orthviews('Redraw'); + +hMe = st.registry.hMe; +try + hcolorbar = st.vols{1}.blobs{1}.cbar; +catch + hcolorbar = 0; +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% cuixuRenderView +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function h = cuixuRenderView(mniCoord, intensity, varargin) +% function h = cuixuRenderView(mniCoord1, intensity1, mniCoord2, intensity2, mniCoord3, intensity3) +% This is to project your coordinate to render view +% +% mniCoord: the mni coordinate, N*3 matrix +% intensity: the plot intensity of the spots, 1*N matrix. The intensity +% could be t value, for example. +% +% You can input 1, 2, or 3 pairs of coordinates and intensity. +% +% h: the returned handle for the figure +% +% Xu Cui +% 2004/11/11 + +global M_; +global DIM_; + +if nargin < 3 + dat.XYZ = mni2cor(mniCoord, M_); + dat.XYZ = dat.XYZ'; + if nargin < 2 + dat.t = ones(size(mniCoord,1),1); + else + dat.t = intensity; + end + dat.mat = M_; + dat.dim = DIM_; +else + if mod(nargin,2) ~=0 + disp('You should put even number of parameters.') + return; + end + for ii=1:(2+length(varargin))/2 + if ii==1 + dat(ii).XYZ = mni2cor(mniCoord, M_); + dat(ii).t = intensity; + else + dat(ii).XYZ = mni2cor(varargin{2*(ii-1)-1}, M_); + dat(ii).t = varargin{2*(ii-1)}; + end + dat(ii).XYZ = dat(ii).XYZ'; + dat(ii).mat = M_; + dat(ii).dim = DIM_; + end +end + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% spm_render +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +h = spm_render(dat,1,fullfile(spm('dir'), 'rend', 'render_single_subj.mat')); +return + +function Fgraph = spm_render(dat,brt,rendfile) +% Render blobs on surface of a 'standard' brain +% FORMAT spm_render(dat,brt,rendfile) +% +% dat - a vertical cell array of length 1 to 3 +% - each element is a structure containing: +% - XYZ - the x, y & z coordinates of the transformed t values. +% in units of voxels. +% - t - the SPM{.} values +% - mat - affine matrix mapping from XYZ voxels to Talairach. +% - dim - dimensions of volume from which XYZ is drawn. +% brt - brightness control: +% If NaN, then displays using the old style with hot +% metal for the blobs, and grey for the brain. +% Otherwise, it is used as a ``gamma correction'' to +% optionally brighten the blobs up a little. +% rendfile - the file containing the images to render on to. See also +% spm_xbrain.m. +% +% Without arguments, spm_render acts as its own UI. +%_______________________________________________________________________ +% +% spm_render prompts for details of up to three SPM{Z}s or SPM{t}s that +% are then displayed superimposed on the surface of a standard brain. +% +% The first is shown in red, then green then blue. +% +% The blobs which are displayed are the integral of all transformed t +% values, exponentially decayed according to their depth. Voxels that +% are 10mm behind the surface have half the intensity of ones at the +% surface. +%_______________________________________________________________________ +% @(#)spm_render.m 2.19 John Ashburner FIL 02/10/29 + +%-Parse arguments, get data if not passed as parameters +%======================================================================= +if nargin < 1 + SPMid = spm('FnBanner',mfilename,'2.19'); + [Finter,Fgraph,CmdLine] = spm('FnUIsetup','Results: render',0); + + num = spm_input('Number of sets',1,'1 set|2 sets|3 sets',[1 2 3]); + + for i = 1:num, + [SPM,VOL] = spm_getSPM; + dat(i) = struct( 'XYZ', VOL.XYZ,... + 't', VOL.Z',... + 'mat', VOL.M,... + 'dim', VOL.DIM); + end; + showbar = 1; +else, + num = length(dat); + showbar = 0; +end; + +% get surface +%----------------------------------------------------------------------- +if nargin < 3, + rendfile = spm_get(1,'render*.mat','Render file',fullfile(spm('Dir'),'rend')); +end; + +% get brightness +%----------------------------------------------------------------------- +if nargin < 2, + brt = 1; + if num==1, + brt = spm_input('Style',1,'new|old',[1 NaN], 1); + end; + if finite(brt), + brt = spm_input('Brighten blobs',1,'none|slightly|more|lots',[1 0.75 0.5 0.25], 1); + end; +end; + + + +% Perform the rendering +%======================================================================= +spm('Pointer','Watch') + +load(rendfile); + +if (exist('rend') ~= 1), % Assume old format... + rend = cell(size(Matrixes,1),1); + for i=1:size(Matrixes,1), + rend{i}=struct('M',eval(Matrixes(i,:)),... + 'ren',eval(Rens(i,:)),... + 'dep',eval(Depths(i,:))); + rend{i}.ren = rend{i}.ren/max(max(rend{i}.ren)); + end; +end; + +if showbar, spm_progress_bar('Init', size(dat,1)*length(rend),... + 'Formatting Renderings', 'Number completed'); end; +for i=1:length(rend), + rend{i}.max=0; + rend{i}.data = cell(size(dat,1),1); + if issparse(rend{i}.ren), + % Assume that images have been DCT compressed + % - the SPM99 distribution was originally too big. + d = size(rend{i}.ren); + B1 = spm_dctmtx(d(1),d(1)); + B2 = spm_dctmtx(d(2),d(2)); + rend{i}.ren = B1*rend{i}.ren*B2'; + % the depths did not compress so well with + % a straight DCT - therefore it was modified slightly + rend{i}.dep = exp(B1*rend{i}.dep*B2')-1; + end; + msk = find(rend{i}.ren>1);rend{i}.ren(msk)=1; + msk = find(rend{i}.ren<0);rend{i}.ren(msk)=0; + if showbar, spm_progress_bar('Set', i); end; +end; +if showbar, spm_progress_bar('Clear'); end; + +if showbar, spm_progress_bar('Init', length(dat)*length(rend),... + 'Making pictures', 'Number completed'); end; + +mx = zeros(length(rend),1)+eps; +mn = zeros(length(rend),1); + +for j=1:length(dat), + XYZ = dat(j).XYZ; + t = dat(j).t; + dim = dat(j).dim; + mat = dat(j).mat; + + for i=1:length(rend), + + % transform from Taliarach space to space of the rendered image + %------------------------------------------------------- + M1 = rend{i}.M*dat(j).mat; + zm = sum(M1(1:2,1:3).^2,2).^(-1/2); + M2 = diag([zm' 1 1]); + M = M2*M1; + cor = [1 1 1 ; dim(1) 1 1 ; 1 dim(2) 1; dim(1) dim(2) 1 ; + 1 1 dim(3) ; dim(1) 1 dim(3) ; 1 dim(2) dim(3); dim(1) dim(2) dim(3)]'; + tcor= M(1:3,1:3)*cor + M(1:3,4)*ones(1,8); + off = min(tcor(1:2,:)'); + M2 = spm_matrix(-off+1)*M2; + M = M2*M1; + xyz = (M(1:3,1:3)*XYZ + M(1:3,4)*ones(1,size(XYZ,2))); + d2 = ceil(max(xyz(1:2,:)')); + + % calculate 'depth' of values + %------------------------------------------------------- + dep = spm_slice_vol(rend{i}.dep,spm_matrix([0 0 1])*inv(M2),d2,1); + z1 = dep(round(xyz(1,:))+round(xyz(2,:)-1)*size(dep,1)); + + if ~finite(brt), msk = find(xyz(3,:) < (z1+20) & xyz(3,:) > (z1-5)); + else, msk = find(xyz(3,:) < (z1+60) & xyz(3,:) > (z1-5)); end; + + if ~isempty(msk), + + % generate an image of the integral of the blob values. + %----------------------------------------------- + xyz = xyz(:,msk); + if ~finite(brt), t0 = t(msk); + else, dst = xyz(3,:) - z1(msk); + dst = max(dst,0); + t0 = t(msk).*exp((log(0.5)/10)*dst)'; + end; + X0 = full(sparse(round(xyz(1,:)), round(xyz(2,:)), t0, d2(1), d2(2))); + hld = 1; if ~finite(brt), hld = 0; end; + X = spm_slice_vol(X0,spm_matrix([0 0 1])*M2,size(rend{i}.dep),hld); + msk = find(X<0); + X(msk) = 0; + else, + X = zeros(size(rend{i}.dep)); + end; + + % Brighten the blobs + if finite(brt), X = X.^brt; end; + + mx(j) = max([mx(j) max(max(X))]); + mn(j) = min([mn(j) min(min(X))]); + + rend{i}.data{j} = X; + + if showbar, spm_progress_bar('Set', i+(j-1)*length(rend)); end; + end; +end; + +mxmx = max(mx); +mnmn = min(mn); + +if showbar, spm_progress_bar('Clear'); end; +Fgraph = gcf;%spm_figure('GetWin','Graphics'); +%spm_results_ui('Clear',Fgraph); + +nrow = ceil(length(rend)/2); +hght = 0.25; +width = 0.25; +x0 = 0.5; +y0 = 0.01; +% subplot('Position',[0, 0, 1, hght]); +ax=axes('Parent',Fgraph,'units','normalized','Position',[0, 0, 0.5, hght],'Visible','off'); +%ax=axes; +%image(0,'Parent',ax); +set(ax,'YTick',[],'XTick',[]); + +if ~finite(brt), + % Old style split colourmap display. + %--------------------------------------------------------------- + load Split; + colormap(split); + for i=1:length(rend), + ren = rend{i}.ren; + X = (rend{i}.data{1}-mnmn)/(mxmx-mnmn); + msk = find(X); + ren(msk) = X(msk)+(1+1.51/64); + ax=axes('Parent',Fgraph,'units','normalized',... + 'Position',[x0+rem(i-1,2)*width, y0+floor((i-1)/2)*hght/nrow, width, hght/nrow],... + 'Visible','off'); + image(ren*64,'Parent',ax); + set(ax,'DataAspectRatio',[1 1 1], ... + 'PlotBoxAspectRatioMode','auto',... + 'YTick',[],'XTick',[],'XDir','normal','YDir','normal'); + end; +else, + % Combine the brain surface renderings with the blobs, and display using + % 24 bit colour. + %--------------------------------------------------------------- + for i=1:length(rend), + ren = rend{i}.ren; + X = cell(3,1); + for j=1:length(rend{i}.data), + X{j} = rend{i}.data{j}/(mxmx-mnmn)-mnmn; + end + for j=(length(rend{i}.data)+1):3 + X{j}=zeros(size(X{1})); + end + + rgb = zeros([size(ren) 3]); + tmp = ren.*max(1-X{1}-X{2}-X{3},0); + rgb(:,:,1) = tmp + X{1}; + rgb(:,:,2) = tmp + X{2}; + rgb(:,:,3) = tmp + X{3}; + + ax=axes('Parent',Fgraph,'units','normalized',... + 'Position',[x0+rem(i-1,2)*width, y0+floor((i-1)/2)*hght/nrow*2, width, hght/nrow*2],... + 'Visible','off'); + image(rgb,'Parent',ax); + set(ax,'DataAspectRatio',[1 1 1], ... + 'PlotBoxAspectRatioMode','auto',... + 'YTick',[],'XTick',[],... + 'XDir','normal','YDir','normal'); + end; +end; + +spm('Pointer') +return; + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% spm_list +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function varargout = spm_list(varargin) +% Display and analysis of SPM{.} +% FORMAT TabDat = spm_list('List',SPM,hReg,[Num,Dis,Str]) +% Summary list of local maxima for entire volume of interest +% FORMAT TabDat = spm_list('ListCluster',SPM,hReg,[Num,Dis,Str]) +% List of local maxima for a single suprathreshold cluster +% +% SPM - structure containing SPM, distribution & filtering details +% - required fields are: +% .swd - SPM working directory - directory containing current SPM.mat +% .Z - minimum of n Statistics {filtered on u and k} +% .n - number of conjoint tests +% .STAT - distribution {Z, T, X or F} +% .df - degrees of freedom [df{interest}, df{residual}] +% .u - height threshold +% .k - extent threshold {voxels} +% .XYZ - location of voxels {voxel coords} +% .XYZmm - location of voxels {mm} +% .S - search Volume {voxels} +% .R - search Volume {resels} +% .FWHM - smoothness {voxels} +% .M - voxels - > mm matrix +% .VOX - voxel dimensions {mm} +% .Vspm - mapped statistic image(s) +% .Ps - uncorrected P values in searched volume (for FDR) +% +% (see spm_getSPM for further details of xSPM structures) +% +% hReg - Handle of results section XYZ registry (see spm_results_ui.m) +% +% Num - number of maxima per cluster +% Dis - distance among clusters (mm) +% Str - header string +% +% TabDat - Structure containing table data +% - fields are +% .tit - table Title (string) +% .hdr - table header (2x11 cell array) +% .fmt - fprintf format strings for table data (1x11 cell array) +% .str - table filtering note (string) +% .ftr - table footnote information (4x2 cell array) +% .dat - table data (Nx11 cell array) +% +% ---------------- +% +% FORMAT spm_list('TxtList',TabDat,c) +% Prints a tab-delimited text version of the table +% TabDat - Structure containing table data (format as above) +% c - Column of table data to start text table at +% (E.g. c=3 doesn't print set-level results contained in columns 1 & 2) +% ---------------- +% +% FORMAT spm_list('SetCoords',xyz,hAx,hC) +% Highlighting of table co-ordinates (used by results section registry) +% xyz - 3-vector of new co-ordinate +% hAx - table axis (the registry object for tables) +% hReg - Handle of caller (not used) +%_______________________________________________________________________ +% +% spm_list characterizes SPMs (thresholded at u and k) in terms of +% excursion sets (a collection of face, edge and vertex connected +% subsets or clusters). The currected significance of the results are +% based on set, cluster and voxel-level inferences using distributional +% approximations from the Theory of Gaussian Fields. These +% distributions assume that the SPM is a reasonable lattice +% approximation of a continuous random field with known component field +% smoothness. +% +% The p values are based on the probability of obtaining c, or more, +% clusters of k, or more, resels above u, in the volume S analysed = +% P(u,k,c). For specified thresholds u, k, the set-level inference is +% based on the observed number of clusters C, = P(u,k,C). For each +% cluster of size K the cluster-level inference is based on P(u,K,1) +% and for each voxel (or selected maxima) of height U, in that cluster, +% the voxel-level inference is based on P(U,0,1). All three levels of +% inference are supported with a tabular presentation of the p values +% and the underlying statistic: +% +% Set-level - c = number of suprathreshold clusters +% - P = prob(c or more clusters in the search volume) +% +% Cluster-level - k = number of voxels in this cluster +% - Pc = prob(k or more voxels in the search volume) +% - Pu = prob(k or more voxels in a cluster) +% +% Voxel-level - T/F = Statistic upon which the SPM is based +% - Ze = The eqivalent Z score - prob(Z > Ze) = prob(t > T) +% - Pc = prob(Ze or higher in the search volume) +% - Qu = Expd(Prop of false positives among voxels >= Ze) +% - Pu = prob(Ze or higher at that voxel) +% +% x,y,z (mm) - Coordinates of the voxel +% +% The table is grouped by regions and sorted on the Ze-variate of the +% primary maxima. Ze-variates (based on the uncorrected p value) are the +% Z score equivalent of the statistic. Volumes are expressed in voxels. +% +% Clicking on values in the table returns the value to the Matlab +% workspace. In addition, clicking on the co-ordinates jumps the +% results section cursor to that location. The table has a context menu +% (obtained by right-clicking in the background of the table), +% providing options to print the current table as a text table, or to +% extract the table data to the Matlab workspace. +% +%_______________________________________________________________________ +% @(#)spm_list.m 2.43 Karl Friston, Andrew Holmes 02/10/31 + + +% satellite figure global variable +%----------------------------------------------------------------------- +global SatWindow + +%======================================================================= +switch lower(varargin{1}), case 'list' %-List + %======================================================================= + % FORMAT TabDat = spm_list('list',SPM,hReg) + + %-Tolerance for p-value underflow, when computing equivalent Z's + %----------------------------------------------------------------------- + tol = eps*10; + + %-Parse arguments and set maxima number and separation + %----------------------------------------------------------------------- + if nargin < 2, error('insufficient arguments'), end + if nargin < 3, hReg = []; else, hReg = varargin{3}; end + + + %-Get current location (to highlight selected voxel in table) + %----------------------------------------------------------------------- + %xyzmm = spm_results_ui('GetCoords'); + xyzmm = [0 0 0]'; + + %-Extract data from xSPM + %----------------------------------------------------------------------- + S = varargin{2}.S; + R = varargin{2}.R; + FWHM = varargin{2}.FWHM; + VOX = varargin{2}.VOX; + n = varargin{2}.n; + STAT = varargin{2}.STAT; + df = varargin{2}.df; + u = varargin{2}.u; + M = varargin{2}.M; + v2r = 1/prod(FWHM(~isinf(FWHM))); %-voxels to resels + k = varargin{2}.k*v2r; + QPs = varargin{2}.Ps; % Needed for FDR + QPs = sort(QPs(:)); + + %-get number and separation for maxima to be reported + %----------------------------------------------------------------------- + if length(varargin) > 3 + + Num = varargin{4}; % number of maxima per cluster + Dis = varargin{5}; % distance among clusters (mm) + else + Num = 3; + Dis = 8; + end + if length(varargin) > 5 + + Title = varargin{6}; + else + Title = 'p-values adjusted for search volume'; + end + + + %-Setup graphics panel + %----------------------------------------------------------------------- + spm('Pointer','Watch') + if SatWindow + Fgraph = SatWindow; + figure(Fgraph); + else + Fgraph = figure('unit','normalized','position',[0.5,0.1,0.55,0.5],'Color','w',... + 'Name','volume', 'NumberTitle','off','resize','off','MenuBar','none'); + Fgraph = gcf; + end + %spm_results_ui('Clear',Fgraph) + FS = spm('FontSizes'); %-Scaled font sizes + PF = spm_platform('fonts'); %-Font names (for this platform) + + + %-Table header & footer + %======================================================================= + + %-Table axes & Title + %---------------------------------------------------------------------- + if SatWindow, ht = 0.85; bot = .14; else, ht = 0.8; bot = 0.15; end; + + if STAT == 'P' + Title = 'Posterior Probabilities'; + end + + hAx = axes('Position',[0.025 bot 0.9 ht],... + 'DefaultTextFontSize',FS(8),... + 'DefaultTextInterpreter','Tex',... + 'DefaultTextVerticalAlignment','Baseline',... + 'Units','points',... + 'Visible','off'); + + AxPos = get(hAx,'Position'); set(hAx,'YLim',[0,AxPos(4)]) + dy = FS(9); + y = floor(AxPos(4)) - dy; + + text(0,y,['Statistics: \it\fontsize{',num2str(FS(9)),'}',Title],... + 'FontSize',FS(11),'FontWeight','Bold'); y = y - dy/2; + line([0 1],[y y],'LineWidth',3,'Color','r'), y = y - 9*dy/8; + + + %-Construct table header + %----------------------------------------------------------------------- + set(gca,'DefaultTextFontName',PF.helvetica,'DefaultTextFontSize',FS(8)) + + Hc = []; + Hp = []; + h = text(0.01,y, 'set-level','FontSize',FS(9)); Hc = [Hc,h]; + h = line([0,0.11],[1,1]*(y-dy/4),'LineWidth',0.5,'Color','r'); Hc = [Hc,h]; + h = text(0.08,y-9*dy/8, '\itc '); Hc = [Hc,h]; + h = text(0.02,y-9*dy/8, '\itp '); Hc = [Hc,h]; + Hp = [Hp,h]; + text(0.22,y, 'cluster-level','FontSize',FS(9)); + line([0.15,0.41],[1,1]*(y-dy/4),'LineWidth',0.5,'Color','r'); + h = text(0.16,y-9*dy/8, '\itp \rm_{corrected}'); Hp = [Hp,h]; + h = text(0.33,y-9*dy/8, '\itp \rm_{uncorrected}'); Hp = [Hp,h]; + h = text(0.26,y-9*dy/8, '\itk \rm_E'); + + text(0.60,y, 'voxel-level','FontSize',FS(9)); + line([0.46,0.86],[1,1]*(y-dy/4),'LineWidth',0.5,'Color','r'); + h = text(0.46,y-9*dy/8, '\itp \rm_{FWE-corr}'); Hp = [Hp,h]; + h = text(0.55,y-9*dy/8, '\itp \rm_{FDR-corr}'); Hp = [Hp,h]; + h = text(0.79,y-9*dy/8, '\itp \rm_{uncorrected}'); Hp = [Hp,h]; + h = text(0.64,y-9*dy/8, sprintf('\\it%c',STAT)); + h = text(0.72,y-9*dy/8, '(\itZ\rm_\equiv)'); + + text(0.93,y - dy/2,['x,y,z \fontsize{',num2str(FS(8)),'}\{mm\}']); + + + %-Headers for text table... + %----------------------------------------------------------------------- + TabDat.tit = Title; + TabDat.hdr = { 'set', 'c';... + 'set', 'p';... + 'cluster', 'p(cor)';... + 'cluster', 'equivk';... + 'cluster', 'p(unc)';... + 'voxel', 'p(FWE-cor)';... + 'voxel', 'p(FDR-cor)';... + 'voxel', STAT;... + 'voxel', 'equivZ';... + 'voxel', 'p(unc)';... + '', 'x,y,z {mm}'}';... + + TabDat.fmt = { '%-0.3f','%g',... %-Set + '%0.3f', '%0.0f', '%0.3f',... %-Cluster + '%0.3f', '%0.3f', '%6.2f', '%5.2f', '%0.3f',... %-Voxel + '%3.0f %3.0f %3.0f'}; %-XYZ + + %-Column Locations + %----------------------------------------------------------------------- + tCol = [ 0.00 0.07 ... %-Set + 0.16 0.26 0.34 ... %-Cluster + 0.46 0.55 0.62 0.71 0.80 ...%-Voxel + 0.92]; %-XYZ + + % move to next vertial postion marker + %----------------------------------------------------------------------- + y = y - 7*dy/4; + line([0 1],[y y],'LineWidth',1,'Color','r') + y = y - 5*dy/4; + y0 = y; + + + %-Table filtering note + %----------------------------------------------------------------------- + if isinf(Num) + TabDat.str = sprintf('table shows all local maxima > %.1fmm apart',Dis); + else + TabDat.str = sprintf(['table shows %d local maxima ',... + 'more than %.1fmm apart'],Num,Dis); + end + text(0.5,4,TabDat.str,'HorizontalAlignment','Center','FontName',PF.helvetica,... + 'FontSize',FS(8),'FontAngle','Italic') + + + %-Volume, resels and smoothness (if classical inference) + %----------------------------------------------------------------------- + line([0 1],[0 0],'LineWidth',1,'Color','r') + if STAT ~= 'P' + %----------------------------------------------------------------------- + FWHMmm = FWHM.*VOX; % FWHM {mm} + Pz = spm_P(1,0,u,df,STAT,1,n,S); + Pu = spm_P(1,0,u,df,STAT,R,n,S); + Qu = spm_P_FDR(u,df,STAT,n,QPs); + [P Pn Em En EN] = spm_P(1,k,u,df,STAT,R,n,S); + + + %-Footnote with SPM parameters + %----------------------------------------------------------------------- + set(gca,'DefaultTextFontName',PF.helvetica,... + 'DefaultTextInterpreter','None','DefaultTextFontSize',FS(8)) + TabDat.ftr = cell(5,2); + TabDat.ftr{1} = ... + sprintf('Height threshold: %c = %0.2f, p = %0.3f (%0.3f)',... + STAT,u,Pz,Pu); + TabDat.ftr{2} = ... + sprintf('Extent threshold: k = %0.0f voxels, p = %0.3f (%0.3f)',... + k/v2r,Pn,P); + TabDat.ftr{3} = ... + sprintf('Expected voxels per cluster, = %0.3f',En/v2r); + TabDat.ftr{4} = ... + sprintf('Expected number of clusters, = %0.2f',Em*Pn); + TabDat.ftr{5} = ... + sprintf('Expected false discovery rate, <= %0.2f',Qu); + TabDat.ftr{6} = ... + sprintf('Degrees of freedom = [%0.1f, %0.1f]',df); + TabDat.ftr{7} = ... + sprintf(['Smoothness FWHM = %0.1f %0.1f %0.1f {mm} ',... + ' = %0.1f %0.1f %0.1f {voxels}'],FWHMmm,FWHM); + TabDat.ftr{8} = ... + sprintf('Search vol: %0.0f cmm; %0.0f voxels; %0.1f resels',S*prod(VOX),S,R(end)); + TabDat.ftr{9} = ... + sprintf(['Voxel size: [%0.1f, %0.1f, %0.1f] mm ',... + ' (1 resel = %0.2f voxels)'],VOX,prod(FWHM)); + + text(0.0,-1*dy,TabDat.ftr{1},... + 'UserData',[u,Pz,Pu,Qu],'ButtonDownFcn','get(gcbo,''UserData'')') + text(0.0,-2*dy,TabDat.ftr{2},... + 'UserData',[k/v2r,Pn,P],'ButtonDownFcn','get(gcbo,''UserData'')') + text(0.0,-3*dy,TabDat.ftr{3},... + 'UserData',En/v2r,'ButtonDownFcn','get(gcbo,''UserData'')') + text(0.0,-4*dy,TabDat.ftr{4},... + 'UserData',Em*Pn,'ButtonDownFcn','get(gcbo,''UserData'')') + text(0.0,-5*dy,TabDat.ftr{5},... + 'UserData',Qu,'ButtonDownFcn','get(gcbo,''UserData'')') + text(0.5,-1*dy,TabDat.ftr{6},... + 'UserData',df,'ButtonDownFcn','get(gcbo,''UserData'')') + text(0.5,-2*dy,TabDat.ftr{7},... + 'UserData',FWHMmm,'ButtonDownFcn','get(gcbo,''UserData'')') + text(0.5,-3*dy,TabDat.ftr{8},... + 'UserData',[S*prod(VOX),S,R(end)],... + 'ButtonDownFcn','get(gcbo,''UserData'')') + text(0.5,-4*dy,TabDat.ftr{9},... + 'UserData',[VOX,prod(FWHM)],... + 'ButtonDownFcn','get(gcbo,''UserData'')') + + end % Classical + + + %-Characterize excursion set in terms of maxima + % (sorted on Z values and grouped by regions) + %======================================================================= + if ~length(varargin{2}.Z) + text(0.5,y-6*dy,'no suprathreshold clusters',... + 'HorizontalAlignment','Center',... + 'FontAngle','Italic','FontWeight','Bold',... + 'FontSize',FS(16),'Color',[1,1,1]*.5); + TabDat.dat = cell(0,11); + varargout = {TabDat}; + spm('Pointer','Arrow') + return + end + + % Includes Darren Gitelman's code for working around + % spm_max for conjunctions with negative thresholds + %----------------------------------------------------------------------- + minz = abs(min(min(varargin{2}.Z))); + zscores = 1 + minz + varargin{2}.Z; + [N Z XYZ A] = spm_max(zscores,varargin{2}.XYZ); + Z = Z - minz - 1; + + %-Convert cluster sizes from voxels to resels + %----------------------------------------------------------------------- + if isfield(varargin{2},'VRvp') + V2R = spm_get_data(varargin{2}.VRvp,XYZ); + else + V2R = v2r; + end + N = N.*V2R; + + %-Convert maxima locations from voxels to mm + %----------------------------------------------------------------------- + XYZmm = M(1:3,:)*[XYZ; ones(1,size(XYZ,2))]; + + + + %-Table proper (& note all data in cell array) + %======================================================================= + + %-Pagination variables + %----------------------------------------------------------------------- + hPage = []; + set(gca,'DefaultTextFontName',PF.courier,'DefaultTextFontSize',FS(7)) + + + %-Set-level p values {c} - do not display if reporting a single cluster + %----------------------------------------------------------------------- + c = max(A); %-Number of clusters + if STAT ~= 'P' + Pc = spm_P(c,k,u,df,STAT,R,n,S); %-Set-level p-value + else + Pc = []; + set(Hp,'Visible','off') + end + + if c > 1; + h = text(tCol(1),y,sprintf(TabDat.fmt{1},Pc),'FontWeight','Bold',... + 'UserData',Pc,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + h = text(tCol(2),y,sprintf(TabDat.fmt{2},c),'FontWeight','Bold',... + 'UserData',c,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + else + set(Hc,'Visible','off') + end + + TabDat.dat = {Pc,c}; %-Table data + TabLin = 1; %-Table data line + + + %-Local maxima p-values & statistics + %----------------------------------------------------------------------- + HlistXYZ = []; + while prod(size(find(finite(Z)))) + + % Paginate if necessary + %--------------------------------------------------------------- + if y < min(Num + 1,3)*dy + + % added Fgraph term to paginate on Satellite window + %------------------------------------------------------- + h = text(0.5,-5*dy,... + sprintf('Page %d',spm_figure('#page',Fgraph)),... + 'FontName',PF.helvetica,'FontAngle','Italic',... + 'FontSize',FS(8)); + + spm_figure('NewPage',[hPage,h]) + hPage = []; + y = y0; + end + + %-Find largest remaining local maximum + %--------------------------------------------------------------- + [U,i] = max(Z); % largest maxima + j = find(A == A(i)); % maxima in cluster + + + %-Compute cluster {k} and voxel-level {u} p values for this cluster + %--------------------------------------------------------------- + Nv = N(i)/v2r; % extent {voxels} + + + if STAT ~= 'P' + Pz = spm_P(1,0, U,df,STAT,1,n,S);% uncorrected p value + Pu = spm_P(1,0, U,df,STAT,R,n,S);% FWE-corrected {based on Z) + Qu = spm_P_FDR( U,df,STAT,n,QPs);% FDR-corrected {based on Z) + [Pk Pn] = spm_P(1,N(i),u,df,STAT,R,n,S);% [un]corrected {based on k) + + if Pz < tol % Equivalent Z-variate + Ze = Inf; % (underflow => can't compute) + else + Ze = spm_invNcdf(1 - Pz); + end + else + Pz = []; + Pu = []; + Qu = []; + Pk = []; + Pn = []; + Ze = spm_invNcdf(U); + end + + + %-Print cluster and maximum voxel-level p values {Z} + %--------------------------------------------------------------- + h = text(tCol(3),y,sprintf(TabDat.fmt{3},Pk),'FontWeight','Bold',... + 'UserData',Pk,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + + h = text(tCol(4),y,sprintf(TabDat.fmt{4},Nv),'FontWeight','Bold',... + 'UserData',Nv,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + h = text(tCol(5),y,sprintf(TabDat.fmt{5},Pn),'FontWeight','Bold',... + 'UserData',Pn,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + + h = text(tCol(6),y,sprintf(TabDat.fmt{6},Pu),'FontWeight','Bold',... + 'UserData',Pu,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + h = text(tCol(7),y,sprintf(TabDat.fmt{7},Qu),'FontWeight','Bold',... + 'UserData',Qu,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + h = text(tCol(8),y,sprintf(TabDat.fmt{8},U),'FontWeight','Bold',... + 'UserData',U,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + h = text(tCol(9),y,sprintf(TabDat.fmt{9},Ze),'FontWeight','Bold',... + 'UserData',Ze,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + h = ... + text(tCol(10),y,sprintf(TabDat.fmt{10},Pz),'FontWeight','Bold',... + 'UserData',Pz,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + + % Specifically changed so it properly finds hMIPax + %--------------------------------------------------------------------- + h = text(tCol(11),y,sprintf(TabDat.fmt{11},XYZmm(:,i)),... + 'FontWeight','Bold',... + 'Tag','ListXYZ',... + 'ButtonDownFcn',[... + 'hMIPax = findobj(''tag'',''hMIPax'');',... + 'spm_mip_ui(''SetCoords'',get(gcbo,''UserData''),hMIPax);'],... + 'Interruptible','off','BusyAction','Cancel',... + 'UserData',XYZmm(:,i)); + + HlistXYZ = [HlistXYZ, h]; + if spm_XYZreg('Edist',xyzmm,XYZmm(:,i)) Dis mm apart) + %--------------------------------------------------------------- + [l q] = sort(-Z(j)); % sort on Z value + D = i; + for i = 1:length(q) + d = j(q(i)); + if min(sqrt(sum((XYZmm(:,D)-XYZmm(:,d)*ones(1,size(D,2))).^2)))>Dis; + + if length(D) < Num + + % Paginate if necessary + %----------------------------------------------- + if y < dy + h = text(0.5,-5*dy,sprintf('Page %d',... + spm_figure('#page',Fgraph)),... + 'FontName',PF.helvetica,... + 'FontAngle','Italic',... + 'FontSize',FS(8)); + + spm_figure('NewPage',[hPage,h]) + hPage = []; + y = y0; + end + + % voxel-level p values {Z} + %----------------------------------------------- + if STAT ~= 'P' + Pz = spm_P(1,0,Z(d),df,STAT,1,n,S); + Pu = spm_P(1,0,Z(d),df,STAT,R,n,S); + Qu = spm_P_FDR(Z(d),df,STAT,n,QPs); + if Pz < tol + Ze = Inf; + else, Ze = spm_invNcdf(1 - Pz); end + else + Pz = []; + Pu = []; + Qu = []; + Ze = spm_invNcdf(Z(d)); + end + + h = text(tCol(6),y,sprintf(TabDat.fmt{6},Pu),... + 'UserData',Pu,... + 'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + + h = text(tCol(7),y,sprintf(TabDat.fmt{7},Qu),... + 'UserData',Qu,... + 'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + h = text(tCol(8),y,sprintf(TabDat.fmt{8},Z(d)),... + 'UserData',Z(d),... + 'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + h = text(tCol(9),y,sprintf(TabDat.fmt{9},Ze),... + 'UserData',Ze,... + 'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + h = text(tCol(10),y,sprintf(TabDat.fmt{10},Pz),... + 'UserData',Pz,... + 'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + + % specifically modified line to use hMIPax + %----------------------------------------------- + h = text(tCol(11),y,... + sprintf(TabDat.fmt{11},XYZmm(:,d)),... + 'Tag','ListXYZ',... + 'ButtonDownFcn',[... + 'hMIPax = findobj(''tag'',''hMIPax'');',... + 'spm_mip_ui(''SetCoords'',',... + 'get(gcbo,''UserData''),hMIPax);'],... + 'Interruptible','off','BusyAction','Cancel',... + 'UserData',XYZmm(:,d)); + + HlistXYZ = [HlistXYZ, h]; + if spm_XYZreg('Edist',xyzmm,XYZmm(:,d))1 + h = text(0.5,-5*dy,sprintf('Page %d/%d',spm_figure('#page',Fgraph)*[1,1]),... + 'FontName',PF.helvetica,'FontSize',FS(8),'FontAngle','Italic'); + spm_figure('NewPage',[hPage,h]) + end + + %-End: Store TabDat in UserData of axes & reset pointer + %======================================================================= + h = uicontextmenu('Tag','TabDat',... + 'UserData',TabDat); + set(gca,'UIContextMenu',h,... + 'Visible','on',... + 'XColor','w','YColor','w') + uimenu(h,'Label','Table') + uimenu(h,'Separator','on','Label','Print text table',... + 'Tag','TD_TxtTab',... + 'CallBack',... + 'spm_list(''txtlist'',get(get(gcbo,''Parent''),''UserData''),3)',... + 'Interruptible','off','BusyAction','Cancel'); + uimenu(h,'Separator','off','Label','Extract table data structure',... + 'Tag','TD_Xdat',... + 'CallBack','get(get(gcbo,''Parent''),''UserData'')',... + 'Interruptible','off','BusyAction','Cancel'); + uimenu(h,'Separator','on','Label','help',... + 'Tag','TD_Xdat',... + 'CallBack','spm_help(''spm_list'')',... + 'Interruptible','off','BusyAction','Cancel'); + + %-Setup registry + %----------------------------------------------------------------------- + set(hAx,'UserData',struct('hReg',hReg,'HlistXYZ',HlistXYZ)) + spm_XYZreg('Add2Reg',hReg,hAx,'spm_list'); + + %-Return TabDat structure & reset pointer + %----------------------------------------------------------------------- + varargout = {TabDat}; + spm('Pointer','Arrow') + + + + + + %======================================================================= + case 'listcluster' %-List for current cluster only + %======================================================================= + % FORMAT TabDat = spm_list('listcluster',SPM,hReg) + + spm('Pointer','Watch') + + %-Parse arguments + %----------------------------------------------------------------------- + if nargin < 2, error('insufficient arguments'), end + if nargin < 3, hReg = []; else, hReg = varargin{3}; end + SPM = varargin{2}; + + %-get number and separation for maxima to be reported + %----------------------------------------------------------------------- + if length(varargin) > 3 + + Num = varargin{4}; % number of maxima per cluster + Dis = varargin{5}; % distance among clusters (mm) + else + Num = 32; + Dis = 4; + end + + + %-if there are suprathreshold voxels, filter out all but current cluster + %----------------------------------------------------------------------- + if length(SPM.Z) + + %-Jump to voxel nearest current location + %-------------------------------------------------------------- + [xyzmm,i] = spm_XYZreg('NearestXYZ',... + spm_results_ui('GetCoords'),SPM.XYZmm); + spm_results_ui('SetCoords',SPM.XYZmm(:,i)); + + %-Find selected cluster + %-------------------------------------------------------------- + A = spm_clusters(SPM.XYZ); + j = find(A == A(i)); + SPM.Z = SPM.Z(j); + SPM.XYZ = SPM.XYZ(:,j); + SPM.XYZmm = SPM.XYZmm(:,j); + if isfield(SPM,'Rd'), SPM.Rd = SPM.Rd(:,j); end + end + + %-Call 'list' functionality to produce table + %----------------------------------------------------------------------- + varargout = {spm_list('list',SPM,hReg,Num,Dis)}; + + + + + + %======================================================================= + case 'txtlist' %-Print ASCII text table + %======================================================================= + % FORMAT spm_list('TxtList',TabDat,c) + + if nargin<2, error('Insufficient arguments'), end + if nargin<3, c=1; else, c=varargin{3}; end + TabDat = varargin{2}; + + %-Table Title + %----------------------------------------------------------------------- + fprintf('\n\nSTATISTICS: %s\n',TabDat.tit) + fprintf('%c','='*ones(1,80)), fprintf('\n') + + %-Table header + %----------------------------------------------------------------------- + fprintf('%s\t',TabDat.hdr{1,c:end-1}), fprintf('%s\n',TabDat.hdr{1,end}) + fprintf('%s\t',TabDat.hdr{2,c:end-1}), fprintf('%s\n',TabDat.hdr{2,end}) + fprintf('%c','-'*ones(1,80)), fprintf('\n') + + %-Table data + %----------------------------------------------------------------------- + for i = 1:size(TabDat.dat,1) + for j=c:size(TabDat.dat,2) + fprintf(TabDat.fmt{j},TabDat.dat{i,j}) + fprintf('\t') + end + fprintf('\n') + end + for i=1:max(1,11-size(TabDat.dat,1)), fprintf('\n'), end + fprintf('%s\n',TabDat.str) + fprintf('%c','-'*ones(1,80)), fprintf('\n') + + %-Table footer + %----------------------------------------------------------------------- + fprintf('%s\n',TabDat.ftr{:}) + fprintf('%c','='*ones(1,80)), fprintf('\n\n') + + + + %======================================================================= + case 'setcoords' %-Co-ordinate change + %======================================================================= + % FORMAT spm_list('SetCoords',xyz,hAx,hReg) + if nargin<3, error('Insufficient arguments'), end + hAx = varargin{3}; + xyz = varargin{2}; + UD = get(hAx,'UserData'); + HlistXYZ = UD.HlistXYZ(ishandle(UD.HlistXYZ)); + + %-Set all co-ord strings to black + %----------------------------------------------------------------------- + set(HlistXYZ,'Color','k') + + %-If co-ord matches a string, highlight it in red + %----------------------------------------------------------------------- + XYZ = get(HlistXYZ,'UserData'); + if iscell(XYZ), XYZ = cat(2,XYZ{:}); end + [null,i,d] = spm_XYZreg('NearestXYZ',xyz,XYZ); + if d=2, st.vols{H}.area = varargin{2}; end; + if isempty(st.bb), st.bb = maxbb; end; + bbox; + cm_pos; + end; + varargout{1} = H; + st.centre = mean(maxbb); + redraw_all + + case 'bb', + if length(varargin)> 0 & all(size(varargin{1})==[2 3]), st.bb = varargin{1}; end; + bbox; + redraw_all; + + case 'redraw', + redraw_all; + eval(st.callback); + if isfield(st,'registry'), + spm_XYZreg('SetCoords',st.centre,st.registry.hReg,st.registry.hMe); + end; + + case 'reposition', + if length(varargin)<1, tmp = findcent; + else, tmp = varargin{1}; end; + if length(tmp)==3 + h = valid_handles(st.snap); + if ~isempty(h) + tmp=st.vols{h(1)}.mat*... + round(inv(st.vols{h(1)}.mat)*[tmp; ... + 1]); + end; + st.centre = tmp(1:3); + end; + redraw_all; + eval(st.callback); + if isfield(st,'registry'), + spm_XYZreg('SetCoords',st.centre,st.registry.hReg,st.registry.hMe); + end; + cm_pos; + + case 'setcoords', + st.centre = varargin{1}; + st.centre = st.centre(:); + redraw_all; + eval(st.callback); + cm_pos; + + case 'space', + if length(varargin)<1, + st.Space = eye(4); + st.bb = maxbb; + bbox; + redraw_all; + else, + space(varargin{1}); + bbox; + redraw_all; + end; + + case 'maxbb', + st.bb = maxbb; + bbox; + redraw_all; + + case 'resolution', + resolution(varargin{1}); + bbox; + redraw_all; + + case 'window', + if length(varargin)<2, + win = 'auto'; + elseif length(varargin{2})==2, + win = varargin{2}; + end; + for i=valid_handles(varargin{1}), + st.vols{i}.window = win; + end; + redraw(varargin{1}); + + case 'delete', + my_delete(varargin{1}); + + case 'move', + move(varargin{1},varargin{2}); + % redraw_all; + + case 'reset', + my_reset; + + case 'pos', + if isempty(varargin), + H = st.centre(:); + else, + H = pos(varargin{1}); + end; + varargout{1} = H; + + case 'interp', + st.hld = varargin{1}; + redraw_all; + + case 'xhairs', + xhairs(varargin{1}); + + case 'register', + register(varargin{1}); + + case 'addblobs', + addblobs(varargin{1}, varargin{2},varargin{3},varargin{4}); + % redraw(varargin{1}); + + case 'addcolouredblobs', + addcolouredblobs(varargin{1}, varargin{2},varargin{3},varargin{4},varargin{5}); + % redraw(varargin{1}); + + case 'addimage', + addimage(varargin{1}, varargin{2}); + % redraw(varargin{1}); + + case 'addcolouredimage', + addcolouredimage(varargin{1}, varargin{2},varargin{3}); + % redraw(varargin{1}); + + case 'addtruecolourimage', + % spm_orthviews('Addtruecolourimage',handle,filename,colourmap,prop,mx,mn) + % Adds blobs from an image in true colour + % handle - image number to add blobs to [default 1] + % filename of image containing blob data [default - request via GUI] + % colourmap - colormap to display blobs in [GUI input] + % prop - intensity proportion of activation cf grayscale [0.4] + % mx - maximum intensity to scale to [maximum value in activation image] + % mn - minimum intensity to scale to [minimum value in activation image] + % + if nargin < 2 + varargin(1) = {1}; + end + if nargin < 3 + varargin(2) = {spm_select(1, 'image', 'Image with activation signal')}; + end + if nargin < 4 + actc = []; + while isempty(actc) + actc = getcmap(spm_input('Colourmap for activation image', '+1','s')); + end + varargin(3) = {actc}; + end + if nargin < 5 + varargin(4) = {0.4}; + end + if nargin < 6 + actv = spm_vol(varargin{2}); + varargin(5) = {max([eps maxval(actv)])}; + end + if nargin < 7 + varargin(6) = {min([0 minval(actv)])}; + end + + addtruecolourimage(varargin{1}, varargin{2},varargin{3}, varargin{4}, ... + varargin{5}, varargin{6}); + % redraw(varargin{1}); + + case 'addcolourbar', + addcolourbar(varargin{1}, varargin{2}); + + case 'rmblobs', + rmblobs(varargin{1}); + % redraw(varargin{1}); + + case 'addcontext', + if nargin == 1, + handles = 1:24; + else, + handles = varargin{1}; + end; + addcontexts(handles); + + case 'rmcontext', + if nargin == 1, + handles = 1:24; + else, + handles = varargin{1}; + end; + rmcontexts(handles); + + case 'context_menu', + c_menu(varargin{:}); + + case 'valid_handles', + if nargin == 1 + handles = 1:24; + else, + handles = varargin{1}; + end; + varargout{1} = valid_handles(handles); + + otherwise, + addonaction = strcmp(st.plugins,action); + if any(addonaction) + feval(['spm_ov_' st.plugins{addonaction}],varargin{:}); + else + warning('Unknown action string') + end; +end; + +spm('Pointer'); +return; + + +%_______________________________________________________________________ +%_______________________________________________________________________ +function addblobs(handle, xyz, t, mat) +global st +global TMAX_ +for i=valid_handles(handle), + if ~isempty(xyz), + rcp = round(xyz); + dim = max(rcp,[],2)'; + off = rcp(1,:) + dim(1)*(rcp(2,:)-1 + dim(2)*(rcp(3,:)-1)); + vol = zeros(dim)+NaN; + vol(off) = t; + vol = reshape(vol,dim); + st.vols{i}.blobs=cell(1,1); + if st.mode == 0, + axpos = get(st.vols{i}.ax{2}.ax,'Position'); + else, + axpos = get(st.vols{i}.ax{1}.ax,'Position'); + end; + mx = max([eps max(t)]); + mn = min([0 min(t)]); + if ~strcmp(TMAX_, 'auto') + mx = str2num(TMAX_); + end + %KND: + if numel(mx)==2 + mn=mx(1); + mx=mx(2); + end + st.vols{i}.blobs{1} = struct('vol',vol,'mat',mat,'max',mx, 'min',mn); + addcolourbar(handle,1); + end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function addimage(handle, fname) +global st +for i=valid_handles(handle), + if isstruct(fname), + vol = fname(1); + else, + vol = spm_vol(fname); + end; + mat = vol.mat; + st.vols{i}.blobs=cell(1,1); + mx = max([eps maxval(vol)]); + mn = min([0 minval(vol)]); + st.vols{i}.blobs{1} = struct('vol',vol,'mat',mat,'max',mx,'min',mn); + addcolourbar(handle,1); +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function addcolouredblobs(handle, xyz, t, mat,colour) +global st +for i=valid_handles(handle), + if ~isempty(xyz), + rcp = round(xyz); + dim = max(rcp,[],2)'; + off = rcp(1,:) + dim(1)*(rcp(2,:)-1 + dim(2)*(rcp(3,:)-1)); + vol = zeros(dim)+NaN; + vol(off) = t; + vol = reshape(vol,dim); + if ~isfield(st.vols{i},'blobs'), + st.vols{i}.blobs=cell(1,1); + bset = 1; + else, + bset = length(st.vols{i}.blobs)+1; + end; + mx = max([eps maxval(vol)]); + mn = min([0 minval(vol)]); + st.vols{i}.blobs{bset} = struct('vol',vol,'mat',mat,'max',mx,'min',mn,'colour',colour); + end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function addcolouredimage(handle, fname,colour) +global st +for i=valid_handles(handle), + if isstruct(fname), + vol = fname(1); + else, + vol = spm_vol(fname); + end; + mat = vol.mat; + if ~isfield(st.vols{i},'blobs'), + st.vols{i}.blobs=cell(1,1); + bset = 1; + else, + bset = length(st.vols{i}.blobs)+1; + end; + mx = max([eps maxval(vol)]); + mn = min([0 minval(vol)]); + st.vols{i}.blobs{bset} = struct('vol',vol,'mat',mat,'max',mx,'min',mn,'colour',colour); +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function addtruecolourimage(handle,fname,colourmap,prop,mx,mn) +% adds true colour image to current displayed image +global st +for i=valid_handles(handle), + if isstruct(fname), + vol = fname(1); + else, + vol = spm_vol(fname); + end; + mat = vol.mat; + if ~isfield(st.vols{i},'blobs'), + st.vols{i}.blobs=cell(1,1); + bset = 1; + else, + bset = length(st.vols{i}.blobs)+1; + end; + c = struct('cmap', colourmap,'prop',prop); + st.vols{i}.blobs{bset} = struct('vol',vol,'mat',mat,'max',mx, ... + 'min',mn,'colour',c); + addcolourbar(handle,bset); +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function addcolourbar(vh,bh) +global st +if st.mode == 0, + axpos = get(st.vols{vh}.ax{2}.ax,'Position'); +else, + axpos = get(st.vols{vh}.ax{1}.ax,'Position'); +end; +st.vols{vh}.blobs{bh}.cbar = axes('Parent',st.fig,... + 'Position',[(axpos(1)+axpos(3)+0.05+(bh-1)*.1) (axpos(2)+0.005) 0.05 (axpos(4)-0.01)],... + 'Box','on', 'YDir','normal', 'XTickLabel',[], 'XTick',[]); +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function rmblobs(handle) +global st +for i=valid_handles(handle), + if isfield(st.vols{i},'blobs'), + for j=1:length(st.vols{i}.blobs), + if isfield(st.vols{i}.blobs{j},'cbar') & ishandle(st.vols{i}.blobs{j}.cbar), + delete(st.vols{i}.blobs{j}.cbar); + end; + end; + st.vols{i} = rmfield(st.vols{i},'blobs'); + end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function register(hreg) +global st +tmp = uicontrol('Position',[0 0 1 1],'Visible','off','Parent',st.fig); +h = valid_handles(1:24); +if ~isempty(h), + tmp = st.vols{h(1)}.ax{1}.ax; + st.registry = struct('hReg',hreg,'hMe', tmp); + spm_XYZreg('Add2Reg',st.registry.hReg,st.registry.hMe, 'spm_orthviews'); +else, + warning('Nothing to register with'); +end; +st.centre = spm_XYZreg('GetCoords',st.registry.hReg); +st.centre = st.centre(:); +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function xhairs(arg1), +global st +st.xhairs = 0; +opt = 'on'; +if ~strcmp(arg1,'on'), + opt = 'off'; +else, + st.xhairs = 1; +end; +for i=valid_handles(1:24), + for j=1:3, + set(st.vols{i}.ax{j}.lx,'Visible',opt); + set(st.vols{i}.ax{j}.ly,'Visible',opt); + end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function H = pos(arg1) +global st +H = []; +for arg1=valid_handles(arg1), + is = inv(st.vols{arg1}.premul*st.vols{arg1}.mat); + H = is(1:3,1:3)*st.centre(:) + is(1:3,4); +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function my_reset +global st +if ~isempty(st) & isfield(st,'registry') & ishandle(st.registry.hMe), + delete(st.registry.hMe); st = rmfield(st,'registry'); +end; +my_delete(1:24); +reset_st; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function my_delete(arg1) +global st +for i=valid_handles(arg1), + kids = get(st.fig,'Children'); + for j=1:3, + if any(kids == st.vols{i}.ax{j}.ax), + set(get(st.vols{i}.ax{j}.ax,'Children'),'DeleteFcn',''); + delete(st.vols{i}.ax{j}.ax); + end; + end; + st.vols{i} = []; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function resolution(arg1) +global st +res = arg1/mean(svd(st.Space(1:3,1:3))); +Mat = diag([res res res 1]); +st.Space = st.Space*Mat; +st.bb = st.bb/res; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function move(handle,pos) +global st +for handle = valid_handles(handle), + st.vols{handle}.area = pos; +end; +bbox; +% redraw(valid_handles(handle)); +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function bb = maxbb +global st +mn = [Inf Inf Inf]; +mx = -mn; +for i=valid_handles(1:24), + bb = [[1 1 1];st.vols{i}.dim(1:3)]; + c = [ bb(1,1) bb(1,2) bb(1,3) 1 + bb(1,1) bb(1,2) bb(2,3) 1 + bb(1,1) bb(2,2) bb(1,3) 1 + bb(1,1) bb(2,2) bb(2,3) 1 + bb(2,1) bb(1,2) bb(1,3) 1 + bb(2,1) bb(1,2) bb(2,3) 1 + bb(2,1) bb(2,2) bb(1,3) 1 + bb(2,1) bb(2,2) bb(2,3) 1]'; + tc = st.Space\(st.vols{i}.premul*st.vols{i}.mat)*c; + tc = tc(1:3,:)'; + mx = max([tc ; mx]); + mn = min([tc ; mn]); +end; +bb = [mn ; mx]; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function space(arg1) +global st +if ~isempty(st.vols{arg1}) + num = arg1; + Mat = st.vols{num}.premul(1:3,1:3)*st.vols{num}.mat(1:3,1:3); + vox = sqrt(sum(Mat.^2)); + if det(Mat(1:3,1:3))<0, vox(1) = -vox(1); end; + Mat = diag([vox 1]); + Space = (st.vols{num}.mat)/Mat; + bb = [1 1 1;st.vols{num}.dim(1:3)]; + bb = [bb [1;1]]; + bb=bb*Mat'; + bb=bb(:,1:3); + bb=sort(bb); + st.Space = Space; + st.bb = bb; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function H = specify_image(arg1, arg2) +global st +H=[]; +ok = 1; +if isstruct(arg1), + V = arg1(1); +else, + try, + V = spm_vol(arg1); + catch, + fprintf('Can not use image "%s"\n', arg1); + return; + end; +end; + +ii = 1; +while ~isempty(st.vols{ii}), ii = ii + 1; end; + +DeleteFcn = ['spm_orthviews(''Delete'',' num2str(ii) ');']; +V.ax = cell(3,1); +for i=1:3, + ax = axes('Visible','off','DrawMode','fast','Parent',st.fig,'DeleteFcn',DeleteFcn,... + 'YDir','normal','ButtonDownFcn',... + ['if strcmp(get(gcf,''SelectionType''),''normal''),spm_orthviews(''Reposition'');',... + 'elseif strcmp(get(gcf,''SelectionType''),''extend''),spm_orthviews(''Reposition'');',... + 'spm_orthviews(''context_menu'',''ts'',1);end;']); + d = image(0,'Tag','Transverse','Parent',ax,... + 'DeleteFcn',DeleteFcn); + set(ax,'Ydir','normal','ButtonDownFcn',... + ['if strcmp(get(gcf,''SelectionType''),''normal''),spm_orthviews(''Reposition'');',... + 'elseif strcmp(get(gcf,''SelectionType''),''extend''),spm_orthviews(''reposition'');',... + 'spm_orthviews(''context_menu'',''ts'',1);end;']); + + lx = line(0,0,'Parent',ax,'DeleteFcn',DeleteFcn); + ly = line(0,0,'Parent',ax,'DeleteFcn',DeleteFcn); + if ~st.xhairs, + set(lx,'Visible','off'); + set(ly,'Visible','off'); + end; + V.ax{i} = struct('ax',ax,'d',d,'lx',lx,'ly',ly); +end; +V.premul = eye(4); +V.window = 'auto'; +V.mapping = 'linear'; +st.vols{ii} = V; + +H = ii; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function addcontexts(handles) +global st +for ii = valid_handles(handles), + cm_handle = addcontext(ii); + for i=1:3, + set(st.vols{ii}.ax{i}.ax,'UIcontextmenu',cm_handle); + st.vols{ii}.ax{i}.cm = cm_handle; + end; +end; +spm_orthviews('reposition',spm_orthviews('pos')); +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function rmcontexts(handles) +global st +for ii = valid_handles(handles), + for i=1:3, + set(st.vols{ii}.ax{i}.ax,'UIcontextmenu',[]); + st.vols{ii}.ax{i} = rmfield(st.vols{ii}.ax{i},'cm'); + end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function bbox +global st +Dims = diff(st.bb)'+1; + +TD = Dims([1 2])'; +CD = Dims([1 3])'; +if st.mode == 0, SD = Dims([3 2])'; else, SD = Dims([2 3])'; end; + +un = get(st.fig,'Units');set(st.fig,'Units','Pixels'); +sz = get(st.fig,'Position');set(st.fig,'Units',un); +sz = sz(3:4); +sz(2) = sz(2)-40; + +for i=valid_handles(1:24), + area = st.vols{i}.area(:); + area = [area(1)*sz(1) area(2)*sz(2) area(3)*sz(1) area(4)*sz(2)]; + if st.mode == 0, + sx = area(3)/(Dims(1)+Dims(3))/1.02; + else, + sx = area(3)/(Dims(1)+Dims(2))/1.02; + end; + sy = area(4)/(Dims(2)+Dims(3))/1.02; + s = min([sx sy]); + + offy = (area(4)-(Dims(2)+Dims(3))*1.02*s)/2 + area(2); + sky = s*(Dims(2)+Dims(3))*0.02; + if st.mode == 0, + offx = (area(3)-(Dims(1)+Dims(3))*1.02*s)/2 + area(1); + skx = s*(Dims(1)+Dims(3))*0.02; + else, + offx = (area(3)-(Dims(1)+Dims(2))*1.02*s)/2 + area(1); + skx = s*(Dims(1)+Dims(2))*0.02; + end; + + DeleteFcn = ['spm_orthviews(''Delete'',' num2str(i) ');']; + + % Transverse + set(st.vols{i}.ax{1}.ax,'Units','pixels', ... + 'Position',[offx offy s*Dims(1) s*Dims(2)],... + 'Units','normalized','Xlim',[0 TD(1)]+0.5,'Ylim',[0 TD(2)]+0.5,... + 'Visible','on','XTick',[],'YTick',[]); + + % Coronal + set(st.vols{i}.ax{2}.ax,'Units','Pixels',... + 'Position',[offx offy+s*Dims(2)+sky s*Dims(1) s*Dims(3)],... + 'Units','normalized','Xlim',[0 CD(1)]+0.5,'Ylim',[0 CD(2)]+0.5,... + 'Visible','on','XTick',[],'YTick',[]); + + % Sagittal + if st.mode == 0, + set(st.vols{i}.ax{3}.ax,'Units','Pixels', 'Box','on',... + 'Position',[offx+s*Dims(1)+skx offy s*Dims(3) s*Dims(2)],... + 'Units','normalized','Xlim',[0 SD(1)]+0.5,'Ylim',[0 SD(2)]+0.5,... + 'Visible','on','XTick',[],'YTick',[]); + else, + set(st.vols{i}.ax{3}.ax,'Units','Pixels', 'Box','on',... + 'Position',[offx+s*Dims(1)+skx offy+s*Dims(2)+sky s*Dims(2) s*Dims(3)],... + 'Units','normalized','Xlim',[0 SD(1)]+0.5,'Ylim',[0 SD(2)]+0.5,... + 'Visible','on','XTick',[],'YTick',[]); + end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function redraw_all +global st +redraw(1:24); +return; +%_______________________________________________________________________ +function mx = maxval(vol) +if isstruct(vol), + mx = -Inf; + for i=1:vol.dim(3), + tmp = spm_slice_vol(vol,spm_matrix([0 0 i]),vol.dim(1:2),0); + imx = max(tmp(find(finite(tmp)))); + if ~isempty(imx),mx = max(mx,imx);end + end; +else, + mx = max(vol(find(finite(vol)))); +end; +%_______________________________________________________________________ +function mn = minval(vol) +if isstruct(vol), + mn = Inf; + for i=1:vol.dim(3), + tmp = spm_slice_vol(vol,spm_matrix([0 0 i]),vol.dim(1:2),0); + imn = min(tmp(find(finite(tmp)))); + if ~isempty(imn),mn = min(mn,imn);end + end; +else, + mn = min(vol(find(finite(vol)))); +end; + +%_______________________________________________________________________ +%_______________________________________________________________________ +function redraw(arg1) +global st +bb = st.bb; +Dims = round(diff(bb)'+1); +is = inv(st.Space); +cent = is(1:3,1:3)*st.centre(:) + is(1:3,4); + +for i = valid_handles(arg1), + M = st.vols{i}.premul*st.vols{i}.mat; + TM0 = [ 1 0 0 -bb(1,1)+1 + 0 1 0 -bb(1,2)+1 + 0 0 1 -cent(3) + 0 0 0 1]; + TM = inv(TM0*(st.Space\M)); + TD = Dims([1 2]); + + CM0 = [ 1 0 0 -bb(1,1)+1 + 0 0 1 -bb(1,3)+1 + 0 1 0 -cent(2) + 0 0 0 1]; + CM = inv(CM0*(st.Space\M)); + CD = Dims([1 3]); + + if st.mode ==0, + SM0 = [ 0 0 1 -bb(1,3)+1 + 0 1 0 -bb(1,2)+1 + 1 0 0 -cent(1) + 0 0 0 1]; + SM = inv(SM0*(st.Space\M)); SD = Dims([3 2]); + else, + SM0 = [ 0 1 0 -bb(1,2)+1 + 0 0 1 -bb(1,3)+1 + 1 0 0 -cent(1) + 0 0 0 1]; + SM0 = [ 0 -1 0 +bb(2,2)+1 + 0 0 1 -bb(1,3)+1 + 1 0 0 -cent(1) + 0 0 0 1]; + SM = inv(SM0*(st.Space\M)); + SD = Dims([2 3]); + end; + + ok=1; + eval('imgt = (spm_slice_vol(st.vols{i},TM,TD,st.hld))'';','ok=0;'); + eval('imgc = (spm_slice_vol(st.vols{i},CM,CD,st.hld))'';','ok=0;'); + eval('imgs = (spm_slice_vol(st.vols{i},SM,SD,st.hld))'';','ok=0;'); + if (ok==0), fprintf('Image "%s" can not be resampled\n', st.vols{i}.fname); + else, + % get min/max threshold + if strcmp(st.vols{i}.window,'auto') + mn = -Inf; + mx = Inf; + else + mn = min(st.vols{i}.window); + mx = max(st.vols{i}.window); + end; + % threshold images + imgt = max(imgt,mn); imgt = min(imgt,mx); + imgc = max(imgc,mn); imgc = min(imgc,mx); + imgs = max(imgs,mn); imgs = min(imgs,mx); + % compute intensity mapping, if histeq is available + if license('test','image_toolbox') == 0 + st.vols{i}.mapping = 'linear'; + end; + switch st.vols{i}.mapping, + case 'linear', + case 'histeq', + % scale images to a range between 0 and 1 + imgt1=(imgt-min(imgt(:)))/(max(imgt(:)-min(imgt(:)))+eps); + imgc1=(imgc-min(imgc(:)))/(max(imgc(:)-min(imgc(:)))+eps); + imgs1=(imgs-min(imgs(:)))/(max(imgs(:)-min(imgs(:)))+eps); + img = histeq([imgt1(:); imgc1(:); imgs1(:)],1024); + imgt = reshape(img(1:numel(imgt1)),size(imgt1)); + imgc = reshape(img(numel(imgt1)+[1:numel(imgc1)]),size(imgc1)); + imgs = reshape(img(numel(imgt1)+numel(imgc1)+[1:numel(imgs1)]),size(imgs1)); + mn = 0; + mx = 1; + case 'quadhisteq', + % scale images to a range between 0 and 1 + imgt1=(imgt-min(imgt(:)))/(max(imgt(:)-min(imgt(:)))+eps); + imgc1=(imgc-min(imgc(:)))/(max(imgc(:)-min(imgc(:)))+eps); + imgs1=(imgs-min(imgs(:)))/(max(imgs(:)-min(imgs(:)))+eps); + img = histeq([imgt1(:).^2; imgc1(:).^2; imgs1(:).^2],1024); + imgt = reshape(img(1:numel(imgt1)),size(imgt1)); + imgc = reshape(img(numel(imgt1)+[1:numel(imgc1)]),size(imgc1)); + imgs = reshape(img(numel(imgt1)+numel(imgc1)+[1:numel(imgs1)]),size(imgs1)); + mn = 0; + mx = 1; + case 'loghisteq', + warning off % messy - but it may avoid extra queries + imgt = log(imgt-min(imgt(:))); + imgc = log(imgc-min(imgc(:))); + imgs = log(imgs-min(imgs(:))); + warning on + imgt(~isfinite(imgt)) = 0; + imgc(~isfinite(imgc)) = 0; + imgs(~isfinite(imgs)) = 0; + % scale log images to a range between 0 and 1 + imgt1=(imgt-min(imgt(:)))/(max(imgt(:)-min(imgt(:)))+eps); + imgc1=(imgc-min(imgc(:)))/(max(imgc(:)-min(imgc(:)))+eps); + imgs1=(imgs-min(imgs(:)))/(max(imgs(:)-min(imgs(:)))+eps); + img = histeq([imgt1(:); imgc1(:); imgs1(:)],1024); + imgt = reshape(img(1:numel(imgt1)),size(imgt1)); + imgc = reshape(img(numel(imgt1)+[1:numel(imgc1)]),size(imgc1)); + imgs = reshape(img(numel(imgt1)+numel(imgc1)+[1:numel(imgs1)]),size(imgs1)); + mn = 0; + mx = 1; + end; + % recompute min/max for display + if strcmp(st.vols{i}.window,'auto') + mx = -inf; mn = inf; + end; + if ~isempty(imgt), + tmp = imgt(finite(imgt)); + mx = max([mx max(max(tmp))]); + mn = min([mn min(min(tmp))]); + end; + if ~isempty(imgc), + tmp = imgc(finite(imgc)); + mx = max([mx max(max(tmp))]); + mn = min([mn min(min(tmp))]); + end; + if ~isempty(imgs), + tmp = imgs(finite(imgs)); + mx = max([mx max(max(tmp))]); + mn = min([mn min(min(tmp))]); + end; + if mx==mn, mx=mn+eps; end; + + if isfield(st.vols{i},'blobs'), + if ~isfield(st.vols{i}.blobs{1},'colour'), + % Add blobs for display using the split colourmap + scal = 64/(mx-mn); + dcoff = -mn*scal; + imgt = imgt*scal+dcoff; + imgc = imgc*scal+dcoff; + imgs = imgs*scal+dcoff; + + if isfield(st.vols{i}.blobs{1},'max'), + mx = st.vols{i}.blobs{1}.max; + else, + mx = max([eps maxval(st.vols{i}.blobs{1}.vol)]); + st.vols{i}.blobs{1}.max = mx; + end; + if isfield(st.vols{i}.blobs{1},'min'), + mn = st.vols{i}.blobs{1}.min; + else, + mn = min([0 minval(st.vols{i}.blobs{1}.vol)]); + st.vols{i}.blobs{1}.min = mn; + end; + + vol = st.vols{i}.blobs{1}.vol; + M = st.vols{i}.premul*st.vols{i}.blobs{1}.mat; + tmpt = spm_slice_vol(vol,inv(TM0*(st.Space\M)),TD,[0 NaN])'; + tmpc = spm_slice_vol(vol,inv(CM0*(st.Space\M)),CD,[0 NaN])'; + tmps = spm_slice_vol(vol,inv(SM0*(st.Space\M)),SD,[0 NaN])'; + + %tmpt_z = find(tmpt==0);tmpt(tmpt_z) = NaN; + %tmpc_z = find(tmpc==0);tmpc(tmpc_z) = NaN; + %tmps_z = find(tmps==0);tmps(tmps_z) = NaN; + + sc = 64/(mx-mn); + off = 65.51-mn*sc; + msk = find(finite(tmpt)); imgt(msk) = off+tmpt(msk)*sc; + msk = find(finite(tmpc)); imgc(msk) = off+tmpc(msk)*sc; + msk = find(finite(tmps)); imgs(msk) = off+tmps(msk)*sc; + + cmap = get(st.fig,'Colormap'); + + figure(st.fig) + if mn*mx < 0 + setcolormap('gray-hot-cold') + elseif mx > 0 + setcolormap('gray-hot'); + else + setcolormap('gray-cold') + end + redraw_colourbar(i,1,[mn mx],[1:64]'+64); + elseif isstruct(st.vols{i}.blobs{1}.colour), + % Add blobs for display using a defined + % colourmap + + % colourmaps + gryc = [0:63]'*ones(1,3)/63; + actc = ... + st.vols{1}.blobs{1}.colour.cmap; + actp = ... + st.vols{1}.blobs{1}.colour.prop; + + % scale grayscale image, not finite -> black + imgt = scaletocmap(imgt,mn,mx,gryc,65); + imgc = scaletocmap(imgc,mn,mx,gryc,65); + imgs = scaletocmap(imgs,mn,mx,gryc,65); + gryc = [gryc; 0 0 0]; + + % get max for blob image + vol = st.vols{i}.blobs{1}.vol; + mat = st.vols{i}.premul*st.vols{i}.blobs{1}.mat; + if isfield(st.vols{i}.blobs{1},'max'), + cmx = st.vols{i}.blobs{1}.max; + else, + cmx = max([eps maxval(st.vols{i}.blobs{1}.vol)]); + end; + if isfield(st.vols{i}.blobs{1},'min'), + cmn = st.vols{i}.blobs{1}.min; + else, + cmn = -cmx; + end; + + % get blob data + vol = st.vols{i}.blobs{1}.vol; + M = st.vols{i}.premul*st.vols{i}.blobs{1}.mat; + tmpt = spm_slice_vol(vol,inv(TM0*(st.Space\M)),TD,[0 NaN])'; + tmpc = spm_slice_vol(vol,inv(CM0*(st.Space\M)),CD,[0 NaN])'; + tmps = spm_slice_vol(vol,inv(SM0*(st.Space\M)),SD,[0 NaN])'; + + % actimg scaled round 0, black NaNs + topc = size(actc,1)+1; + tmpt = scaletocmap(tmpt,cmn,cmx,actc,topc); + tmpc = scaletocmap(tmpc,cmn,cmx,actc,topc); + tmps = scaletocmap(tmps,cmn,cmx,actc,topc); + actc = [actc; 0 0 0]; + + % combine gray and blob data to + % truecolour + imgt = reshape(actc(tmpt(:),:)*actp+ ... + gryc(imgt(:),:)*(1-actp), ... + [size(imgt) 3]); + imgc = reshape(actc(tmpc(:),:)*actp+ ... + gryc(imgc(:),:)*(1-actp), ... + [size(imgc) 3]); + imgs = reshape(actc(tmps(:),:)*actp+ ... + gryc(imgs(:),:)*(1-actp), ... + [size(imgs) 3]); + + redraw_colourbar(i,1,[cmn cmx],[1:64]'+64); + + else, + % Add full colour blobs - several sets at once + scal = 1/(mx-mn); + dcoff = -mn*scal; + + wt = zeros(size(imgt)); + wc = zeros(size(imgc)); + ws = zeros(size(imgs)); + + imgt = repmat(imgt*scal+dcoff,[1,1,3]); + imgc = repmat(imgc*scal+dcoff,[1,1,3]); + imgs = repmat(imgs*scal+dcoff,[1,1,3]); + + cimgt = zeros(size(imgt)); + cimgc = zeros(size(imgc)); + cimgs = zeros(size(imgs)); + + for j=1:length(st.vols{i}.blobs), % get colours of all images first + if isfield(st.vols{i}.blobs{j},'colour'), + colour(j,:) = reshape(st.vols{i}.blobs{j}.colour, [1 3]); + else, + colour(j,:) = [1 0 0]; + end; + end; + %colour = colour/max(sum(colour)); + + for j=1:length(st.vols{i}.blobs), + if isfield(st.vols{i}.blobs{j},'max'), + mx = st.vols{i}.blobs{j}.max; + else, + mx = max([eps max(st.vols{i}.blobs{j}.vol(:))]); + st.vols{i}.blobs{j}.max = mx; + end; + if isfield(st.vols{i}.blobs{j},'min'), + mn = st.vols{i}.blobs{j}.min; + else, + mn = min([0 min(st.vols{i}.blobs{j}.vol(:))]); + st.vols{i}.blobs{j}.min = mn; + end; + + vol = st.vols{i}.blobs{j}.vol; + M = st.Space\st.vols{i}.premul*st.vols{i}.blobs{j}.mat; + tmpt = spm_slice_vol(vol,inv(TM0*M),TD,[0 NaN])'; + tmpc = spm_slice_vol(vol,inv(CM0*M),CD,[0 NaN])'; + tmps = spm_slice_vol(vol,inv(SM0*M),SD,[0 NaN])'; + % check min/max of sampled image + % against mn/mx as given in st + tmpt(tmpt(:)mx) = mx; + tmpc(tmpc(:)>mx) = mx; + tmps(tmps(:)>mx) = mx; + tmpt = (tmpt-mn)/(mx-mn); + tmpc = (tmpc-mn)/(mx-mn); + tmps = (tmps-mn)/(mx-mn); + tmpt(~finite(tmpt)) = 0; + tmpc(~finite(tmpc)) = 0; + tmps(~finite(tmps)) = 0; + + cimgt = cimgt + cat(3,tmpt*colour(j,1),tmpt*colour(j,2),tmpt*colour(j,3)); + cimgc = cimgc + cat(3,tmpc*colour(j,1),tmpc*colour(j,2),tmpc*colour(j,3)); + cimgs = cimgs + cat(3,tmps*colour(j,1),tmps*colour(j,2),tmps*colour(j,3)); + + wt = wt + tmpt; + wc = wc + tmpc; + ws = ws + tmps; + cdata=permute(shiftdim([1/64:1/64:1]'* ... + colour(j,:),-1),[2 1 3]); + redraw_colourbar(i,j,[mn mx],cdata); + end; + + imgt = repmat(1-wt,[1 1 3]).*imgt+cimgt; + imgc = repmat(1-wc,[1 1 3]).*imgc+cimgc; + imgs = repmat(1-ws,[1 1 3]).*imgs+cimgs; + + imgt(imgt<0)=0; imgt(imgt>1)=1; + imgc(imgc<0)=0; imgc(imgc>1)=1; + imgs(imgs<0)=0; imgs(imgs>1)=1; + end; + else, + scal = 64/(mx-mn); + dcoff = -mn*scal; + imgt = imgt*scal+dcoff; + imgc = imgc*scal+dcoff; + imgs = imgs*scal+dcoff; + end; + + set(st.vols{i}.ax{1}.d,'HitTest','off', 'Cdata',imgt); + set(st.vols{i}.ax{1}.lx,'HitTest','off',... + 'Xdata',[0 TD(1)]+0.5,'Ydata',[1 1]*(cent(2)-bb(1,2)+1)); + set(st.vols{i}.ax{1}.ly,'HitTest','off',... + 'Ydata',[0 TD(2)]+0.5,'Xdata',[1 1]*(cent(1)-bb(1,1)+1)); + + set(st.vols{i}.ax{2}.d,'HitTest','off', 'Cdata',imgc); + set(st.vols{i}.ax{2}.lx,'HitTest','off',... + 'Xdata',[0 CD(1)]+0.5,'Ydata',[1 1]*(cent(3)-bb(1,3)+1)); + set(st.vols{i}.ax{2}.ly,'HitTest','off',... + 'Ydata',[0 CD(2)]+0.5,'Xdata',[1 1]*(cent(1)-bb(1,1)+1)); + + set(st.vols{i}.ax{3}.d,'HitTest','off','Cdata',imgs); + if st.mode ==0, + set(st.vols{i}.ax{3}.lx,'HitTest','off',... + 'Xdata',[0 SD(1)]+0.5,'Ydata',[1 1]*(cent(2)-bb(1,2)+1)); + set(st.vols{i}.ax{3}.ly,'HitTest','off',... + 'Ydata',[0 SD(2)]+0.5,'Xdata',[1 1]*(cent(3)-bb(1,3)+1)); + else, + set(st.vols{i}.ax{3}.lx,'HitTest','off',... + 'Xdata',[0 SD(1)]+0.5,'Ydata',[1 1]*(cent(3)-bb(1,3)+1)); + set(st.vols{i}.ax{3}.ly,'HitTest','off',... + 'Ydata',[0 SD(2)]+0.5,'Xdata',[1 1]*(bb(2,2)+1-cent(2))); + end; + + if ~isempty(st.plugins) % process any addons + for k = 1:prod(size(st.plugins)) + if isfield(st.vols{i},st.plugins{k}) + feval(['spm_ov_', st.plugins{k}], ... + 'redraw', i, TM0, TD, CM0, CD, SM0, SD); + end; + end; + end; + end; +end; +drawnow; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function redraw_colourbar(vh,bh,interval,cdata) +global st +if isfield(st.vols{vh}.blobs{bh},'cbar') + if st.mode == 0, + axpos = get(st.vols{vh}.ax{2}.ax,'Position'); + else, + axpos = get(st.vols{vh}.ax{1}.ax,'Position'); + end; + % only scale cdata if we have out-of-range truecolour values + if ndims(cdata)==3 && max(cdata(:))>1 + cdata=cdata./max(cdata(:)); + end; + image([0 1],interval,cdata,'Parent',st.vols{vh}.blobs{bh}.cbar); + set(st.vols{vh}.blobs{bh}.cbar, ... + 'Position',[(axpos(1)+axpos(3)+0.05+(bh-1)*.1)... + (axpos(2)+0.005) 0.05 (axpos(4)-0.01)],... + 'YDir','normal','XTickLabel',[],'XTick',[]); +end; +%_______________________________________________________________________ +%_______________________________________________________________________ +function centre = findcent +global st +obj = get(st.fig,'CurrentObject'); +centre = []; +cent = []; +cp = []; +for i=valid_handles(1:24), + for j=1:3, + if ~isempty(obj), + if (st.vols{i}.ax{j}.ax == obj), + cp = get(obj,'CurrentPoint'); + end; + end; + if ~isempty(cp), + cp = cp(1,1:2); + is = inv(st.Space); + cent = is(1:3,1:3)*st.centre(:) + is(1:3,4); + switch j, + case 1, + cent([1 2])=[cp(1)+st.bb(1,1)-1 cp(2)+st.bb(1,2)-1]; + case 2, + cent([1 3])=[cp(1)+st.bb(1,1)-1 cp(2)+st.bb(1,3)-1]; + case 3, + if st.mode ==0, + cent([3 2])=[cp(1)+st.bb(1,3)-1 cp(2)+st.bb(1,2)-1]; + else, + cent([2 3])=[st.bb(2,2)+1-cp(1) cp(2)+st.bb(1,3)-1]; + end; + end; + break; + end; + end; + if ~isempty(cent), break; end; +end; +if ~isempty(cent), centre = st.Space(1:3,1:3)*cent(:) + st.Space(1:3,4); end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function handles = valid_handles(handles) +global st; +handles = handles(:)'; +handles = handles(find(handles<=24 & handles>=1 & ~rem(handles,1))); +for h=handles, + if isempty(st.vols{h}), handles(find(handles==h))=[]; end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function reset_st +global st +fig = spm_figure('FindWin','Graphics'); +bb = []; %[ [-78 78]' [-112 76]' [-50 85]' ]; +st = struct('n', 0, 'vols',[], 'bb',bb,'Space',eye(4),'centre',[0 0 0],'callback',';','xhairs',1,'hld',1,'fig',fig,'mode',1,'plugins',[],'snap',[]); +st.vols = cell(24,1); + +pluginpath = fullfile(spm('Dir'),'spm_orthviews'); +if isdir(pluginpath) + pluginfiles = dir(fullfile(pluginpath,'spm_ov_*.m')); + if ~isempty(pluginfiles) + addpath(pluginpath); + % fprintf('spm_orthviews: Using Plugins in %s\n', pluginpath); + for k = 1:length(pluginfiles) + [p pluginname e v] = fileparts(pluginfiles(k).name); + st.plugins{k} = strrep(pluginname, 'spm_ov_',''); + % fprintf('%s\n',st.plugins{k}); + end; + end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function img = scaletocmap(inpimg,mn,mx,cmap,miscol) +if nargin < 5, miscol=1;end +cml = size(cmap,1); +scf = (cml-1)/(mx-mn); +img = round((inpimg-mn)*scf)+1; +img(find(img<1)) = 1; +img(find(img>cml)) = cml; +img(~finite(img)) = miscol; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function cmap = getcmap(acmapname) +% get colormap of name acmapname +if ~isempty(acmapname), + cmap = evalin('base',acmapname,'[]'); + if isempty(cmap), % not a matrix, is .mat file? + [p f e] = fileparts(acmapname); + acmat = fullfile(p, [f '.mat']); + if exist(acmat, 'file'), + s = struct2cell(load(acmat)); + cmap = s{1}; + end; + end; +end; +if size(cmap, 2)~=3, + warning('Colormap was not an N by 3 matrix') + cmap = []; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function item_parent = addcontext(volhandle) +global st; +%create context menu +fg = spm_figure('Findwin','Graphics');set(0,'CurrentFigure',fg); +%contextmenu +item_parent = uicontextmenu; + +%contextsubmenu 0 +item00 = uimenu(item_parent, 'Label','unknown image', 'Separator','on'); +spm_orthviews('context_menu','image_info',item00,volhandle); +item0a = uimenu(item_parent, 'UserData','pos_mm', 'Callback','spm_orthviews(''context_menu'',''repos_mm'');','Separator','on'); +item0b = uimenu(item_parent, 'UserData','pos_vx', 'Callback','spm_orthviews(''context_menu'',''repos_vx'');'); +item0c = uimenu(item_parent, 'UserData','v_value'); + +%contextsubmenu 1 +item1 = uimenu(item_parent,'Label','Zoom'); +item1_1 = uimenu(item1, 'Label','Full Volume', 'Callback','spm_orthviews(''context_menu'',''zoom'',6);', 'Checked','on'); +item1_2 = uimenu(item1, 'Label','160x160x160mm', 'Callback','spm_orthviews(''context_menu'',''zoom'',5);'); +item1_3 = uimenu(item1, 'Label','80x80x80mm', 'Callback','spm_orthviews(''context_menu'',''zoom'',4);'); +item1_4 = uimenu(item1, 'Label','40x40x40mm', 'Callback','spm_orthviews(''context_menu'',''zoom'',3);'); +item1_5 = uimenu(item1, 'Label','20x20x20mm', 'Callback','spm_orthviews(''context_menu'',''zoom'',2);'); +item1_6 = uimenu(item1, 'Label','10x10x10mm', 'Callback','spm_orthviews(''context_menu'',''zoom'',1);'); + +%contextsubmenu 2 +checked={'off','off'}; +checked{st.xhairs+1} = 'on'; +item2 = uimenu(item_parent,'Label','Crosshairs'); +item2_1 = uimenu(item2, 'Label','on', 'Callback','spm_orthviews(''context_menu'',''Xhair'',''on'');','Checked',checked{2}); +item2_2 = uimenu(item2, 'Label','off', 'Callback','spm_orthviews(''context_menu'',''Xhair'',''off'');','Checked',checked{1}); + +%contextsubmenu 3 +if st.Space == eye(4) + checked = {'off', 'on'}; +else + checked = {'on', 'off'}; +end; +item3 = uimenu(item_parent,'Label','Orientation'); +item3_1 = uimenu(item3, 'Label','World space', 'Callback','spm_orthviews(''context_menu'',''orientation'',3);','Checked',checked{2}); +item3_2 = uimenu(item3, 'Label','Voxel space (1st image)', 'Callback','spm_orthviews(''context_menu'',''orientation'',2);','Checked',checked{1}); +item3_3 = uimenu(item3, 'Label','Voxel space (this image)', 'Callback','spm_orthviews(''context_menu'',''orientation'',1);','Checked','off'); + +%contextsubmenu 3 +if isempty(st.snap) + checked = {'off', 'on'}; +else + checked = {'on', 'off'}; +end; +item3 = uimenu(item_parent,'Label','Snap to Grid'); +item3_1 = uimenu(item3, 'Label','Don''t snap', 'Callback','spm_orthviews(''context_menu'',''snap'',3);','Checked',checked{2}); +item3_2 = uimenu(item3, 'Label','Snap to 1st image', 'Callback','spm_orthviews(''context_menu'',''snap'',2);','Checked',checked{1}); +item3_3 = uimenu(item3, 'Label','Snap to this image', 'Callback','spm_orthviews(''context_menu'',''snap'',1);','Checked','off'); + +%contextsubmenu 4 +if st.hld == 0, + checked = {'off', 'off', 'on'}; +elseif st.hld > 0, + checked = {'off', 'on', 'off'}; +else, + checked = {'on', 'off', 'off'}; +end; +item4 = uimenu(item_parent,'Label','Interpolation'); +item4_1 = uimenu(item4, 'Label','NN', 'Callback','spm_orthviews(''context_menu'',''interpolation'',3);', 'Checked',checked{3}); +item4_2 = uimenu(item4, 'Label','Bilin', 'Callback','spm_orthviews(''context_menu'',''interpolation'',2);','Checked',checked{2}); +item4_3 = uimenu(item4, 'Label','Sinc', 'Callback','spm_orthviews(''context_menu'',''interpolation'',1);','Checked',checked{1}); + +%contextsubmenu 5 +% item5 = uimenu(item_parent,'Label','Position', 'Callback','spm_orthviews(''context_menu'',''position'');'); + +%contextsubmenu 6 +item6 = uimenu(item_parent,'Label','Image','Separator','on'); +item6_1 = uimenu(item6, 'Label','Window'); +item6_1_1 = uimenu(item6_1, 'Label','local'); +item6_1_1_1 = uimenu(item6_1_1, 'Label','auto', 'Callback','spm_orthviews(''context_menu'',''window'',2);'); +item6_1_1_2 = uimenu(item6_1_1, 'Label','manual', 'Callback','spm_orthviews(''context_menu'',''window'',1);'); +item6_1_2 = uimenu(item6_1, 'Label','global'); +item6_1_2_1 = uimenu(item6_1_2, 'Label','auto', 'Callback','spm_orthviews(''context_menu'',''window_gl'',2);'); +item6_1_2_2 = uimenu(item6_1_2, 'Label','manual', 'Callback','spm_orthviews(''context_menu'',''window_gl'',1);'); +if license('test','image_toolbox') == 1 + offon = {'off', 'on'}; + checked = offon(strcmp(st.vols{volhandle}.mapping, ... + {'linear', 'histeq', 'loghisteq', 'quadhisteq'})+1); + item6_2 = uimenu(item6, 'Label','Intensity mapping'); + item6_2_1 = uimenu(item6_2, 'Label','local'); + item6_2_1_1 = uimenu(item6_2_1, 'Label','Linear', 'Checked',checked{1}, ... + 'Callback','spm_orthviews(''context_menu'',''mapping'',''linear'');'); + item6_2_1_2 = uimenu(item6_2_1, 'Label','Equalised histogram', 'Checked',checked{2}, ... + 'Callback','spm_orthviews(''context_menu'',''mapping'',''histeq'');'); + item6_2_1_3 = uimenu(item6_2_1, 'Label','Equalised log-histogram', 'Checked',checked{3}, ... + 'Callback','spm_orthviews(''context_menu'',''mapping'',''loghisteq'');'); + item6_2_1_4 = uimenu(item6_2_1, 'Label','Equalised squared-histogram', 'Checked',checked{4}, ... + 'Callback','spm_orthviews(''context_menu'',''mapping'',''quadhisteq'');'); + item6_2_2 = uimenu(item6_2, 'Label','global'); + item6_2_2_1 = uimenu(item6_2_2, 'Label','Linear', 'Checked',checked{1}, ... + 'Callback','spm_orthviews(''context_menu'',''mapping_gl'',''linear'');'); + item6_2_2_2 = uimenu(item6_2_2, 'Label','Equalised histogram', 'Checked',checked{2}, ... + 'Callback','spm_orthviews(''context_menu'',''mapping_gl'',''histeq'');'); + item6_2_2_3 = uimenu(item6_2_2, 'Label','Equalised log-histogram', 'Checked',checked{3}, ... + 'Callback','spm_orthviews(''context_menu'',''mapping_gl'',''loghisteq'');'); + item6_2_2_4 = uimenu(item6_2_2, 'Label','Equalised squared-histogram', 'Checked',checked{4}, ... + 'Callback','spm_orthviews(''context_menu'',''mapping_gl'',''quadhisteq'');'); +end; +%contextsubmenu 7 +item7 = uimenu(item_parent,'Label','Blobs'); +item7_1 = uimenu(item7, 'Label','Add blobs'); +item7_1_1 = uimenu(item7_1, 'Label','local', 'Callback','spm_orthviews(''context_menu'',''add_blobs'',2);'); +item7_1_2 = uimenu(item7_1, 'Label','global', 'Callback','spm_orthviews(''context_menu'',''add_blobs'',1);'); +item7_2 = uimenu(item7, 'Label','Add image'); +item7_2_1 = uimenu(item7_2, 'Label','local', 'Callback','spm_orthviews(''context_menu'',''add_image'',2);'); +item7_2_2 = uimenu(item7_2, 'Label','global', 'Callback','spm_orthviews(''context_menu'',''add_image'',1);'); +item7_3 = uimenu(item7, 'Label','Add colored blobs','Separator','on'); +item7_3_1 = uimenu(item7_3, 'Label','local', 'Callback','spm_orthviews(''context_menu'',''add_c_blobs'',2);'); +item7_3_2 = uimenu(item7_3, 'Label','global', 'Callback','spm_orthviews(''context_menu'',''add_c_blobs'',1);'); +item7_4 = uimenu(item7, 'Label','Add colored image'); +item7_4_1 = uimenu(item7_4, 'Label','local', 'Callback','spm_orthviews(''context_menu'',''add_c_image'',2);'); +item7_4_2 = uimenu(item7_4, 'Label','global', 'Callback','spm_orthviews(''context_menu'',''add_c_image'',1);'); +item7_5 = uimenu(item7, 'Label','Remove blobs', 'Visible','off','Separator','on'); +item7_6 = uimenu(item7, 'Label','Remove colored blobs','Visible','off'); +item7_6_1 = uimenu(item7_6, 'Label','local', 'Visible','on'); +item7_6_2 = uimenu(item7_6, 'Label','global','Visible','on'); + +if ~isempty(st.plugins) % process any plugins + for k = 1:prod(size(st.plugins)), + feval(['spm_ov_', st.plugins{k}], ... + 'context_menu', volhandle, item_parent); + end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function c_menu(varargin) +global st + +switch lower(varargin{1}), + case 'image_info', + if nargin <3, + current_handle = get_current_handle; + else + current_handle = varargin{3}; + end; + if isfield(st.vols{current_handle},'fname'), + [p,n,e,v] = spm_fileparts(st.vols{current_handle}.fname); + if isfield(st.vols{current_handle},'n') + v = sprintf(',%d',st.vols{current_handle}.n); + end; + set(varargin{2}, 'Label',[n e v]); + end; + delete(get(varargin{2},'children')); + if exist('p','var') + item1 = uimenu(varargin{2}, 'Label', p); + end; + if isfield(st.vols{current_handle},'descrip'), + item2 = uimenu(varargin{2}, 'Label',... + st.vols{current_handle}.descrip); + end; + dt = st.vols{current_handle}.dt(1); + item3 = uimenu(varargin{2}, 'Label', sprintf('Data type: %s', spm_type(dt))); + str = 'Intensity: varied'; + if size(st.vols{current_handle}.pinfo,2) == 1, + if st.vols{current_handle}.pinfo(2), + str = sprintf('Intensity: Y = %g X + %g',... + st.vols{current_handle}.pinfo(1:2)'); + else, + str = sprintf('Intensity: Y = %g X', st.vols{current_handle}.pinfo(1)'); + end; + end; + item4 = uimenu(varargin{2}, 'Label',str); + item5 = uimenu(varargin{2}, 'Label', 'Image dims', 'Separator','on'); + item51 = uimenu(varargin{2}, 'Label',... + sprintf('%dx%dx%d', st.vols{current_handle}.dim(1:3))); + prms = spm_imatrix(st.vols{current_handle}.mat); + item6 = uimenu(varargin{2}, 'Label','Voxel size', 'Separator','on'); + item61 = uimenu(varargin{2}, 'Label', sprintf('%.2f %.2f %.2f', prms(7:9))); + item7 = uimenu(varargin{2}, 'Label','Origin', 'Separator','on'); + item71 = uimenu(varargin{2}, 'Label',... + sprintf('%.2f %.2f %.2f', prms(1:3))); + R = spm_matrix([0 0 0 prms(4:6)]); + item8 = uimenu(varargin{2}, 'Label','Rotations', 'Separator','on'); + item81 = uimenu(varargin{2}, 'Label', sprintf('%.2f %.2f %.2f', R(1,1:3))); + item82 = uimenu(varargin{2}, 'Label', sprintf('%.2f %.2f %.2f', R(2,1:3))); + item83 = uimenu(varargin{2}, 'Label', sprintf('%.2f %.2f %.2f', R(3,1:3))); + item9 = uimenu(varargin{2},... + 'Label','Specify other image...',... + 'Callback','spm_orthviews(''context_menu'',''swap_img'');',... + 'Separator','on'); + + case 'repos_mm', + oldpos_mm = spm_orthviews('pos'); + newpos_mm = spm_input('New Position (mm)','+1','r',sprintf('%.2f %.2f %.2f',oldpos_mm),3); + spm_orthviews('reposition',newpos_mm); + + case 'repos_vx' + current_handle = get_current_handle; + oldpos_vx = spm_orthviews('pos', current_handle); + newpos_vx = spm_input('New Position (voxels)','+1','r',sprintf('%.2f %.2f %.2f',oldpos_vx),3); + newpos_mm = st.vols{current_handle}.mat*[newpos_vx;1]; + spm_orthviews('reposition',newpos_mm(1:3)); + + case 'zoom' + zoom_all(varargin{2}); + bbox; + redraw_all; + + case 'xhair', + spm_orthviews('Xhairs',varargin{2}); + cm_handles = get_cm_handles; + for i = 1:length(cm_handles), + z_handle = get(findobj(cm_handles(i),'label','Crosshairs'),'Children'); + set(z_handle,'Checked','off'); %reset check + if strcmp(varargin{2},'off'), op = 1; else op = 2; end + set(z_handle(op),'Checked','on'); + end; + + case 'orientation', + cm_handles = get_cm_handles; + for i = 1:length(cm_handles), + z_handle = get(findobj(cm_handles(i),'label','Orientation'),'Children'); + set(z_handle,'Checked','off'); + end; + if varargin{2} == 3, + spm_orthviews('Space'); + elseif varargin{2} == 2, + spm_orthviews('Space',1); + else, + spm_orthviews('Space',get_current_handle); + z_handle = get(findobj(st.vols{get_current_handle}.ax{1}.cm,'label','Orientation'),'Children'); + set(z_handle(1),'Checked','on'); + return; + end; + for i = 1:length(cm_handles), + z_handle = get(findobj(cm_handles(i),'label','Orientation'),'Children'); + set(z_handle(varargin{2}),'Checked','on'); + end; + + case 'snap', + cm_handles = get_cm_handles; + for i = 1:length(cm_handles), + z_handle = get(findobj(cm_handles(i),'label','Snap to Grid'),'Children'); + set(z_handle,'Checked','off'); + end; + if varargin{2} == 3, + st.snap = []; + elseif varargin{2} == 2, + st.snap = 1; + else, + st.snap = get_current_handle; + z_handle = get(findobj(st.vols{get_current_handle}.ax{1}.cm,'label','Snap to Grid'),'Children'); + set(z_handle(1),'Checked','on'); + return; + end; + for i = 1:length(cm_handles), + z_handle = get(findobj(cm_handles(i),'label','Snap to Grid'),'Children'); + set(z_handle(varargin{2}),'Checked','on'); + end; + + case 'interpolation', + tmp = [-4 1 0]; + st.hld = tmp(varargin{2}); + cm_handles = get_cm_handles; + for i = 1:length(cm_handles), + z_handle = get(findobj(cm_handles(i),'label','Interpolation'),'Children'); + set(z_handle,'Checked','off'); + set(z_handle(varargin{2}),'Checked','on'); + end; + redraw_all; + + case 'window', + current_handle = get_current_handle; + if varargin{2} == 2, + spm_orthviews('window',current_handle); + else + if isnumeric(st.vols{current_handle}.window) + defstr = sprintf('%.2f %.2f', st.vols{current_handle}.window); + else + defstr = ''; + end; + spm_orthviews('window',current_handle,spm_input('Range','+1','e',defstr,2)); + end; + + case 'window_gl', + if varargin{2} == 2, + for i = 1:length(get_cm_handles), + st.vols{i}.window = 'auto'; + end; + else, + current_handle = get_current_handle; + if isnumeric(st.vols{current_handle}.window) + defstr = sprintf('%d %d', st.vols{current_handle}.window); + else + defstr = ''; + end; + data = spm_input('Range','+1','e',defstr,2); + + for i = 1:length(get_cm_handles), + st.vols{i}.window = data; + end; + end; + redraw_all; + + case 'mapping', + checked = strcmp(varargin{2}, ... + {'linear', 'histeq', 'loghisteq', ... + 'quadhisteq'}); + checked = checked(end:-1:1); % Handles are stored in inverse order + current_handle = get_current_handle; + cm_handles = get_cm_handles; + st.vols{current_handle}.mapping = varargin{2}; + z_handle = get(findobj(cm_handles(current_handle), ... + 'label','Intensity mapping'),'Children'); + for k = 1:numel(z_handle) + c_handle = get(z_handle(k), 'Children'); + set(c_handle, 'checked', 'off'); + set(c_handle(checked), 'checked', 'on'); + end; + redraw_all; + + case 'mapping_gl', + checked = strcmp(varargin{2}, ... + {'linear', 'histeq', 'loghisteq', 'quadhisteq'}); + checked = checked(end:-1:1); % Handles are stored in inverse order + cm_handles = get_cm_handles; + for k = valid_handles(1:24), + st.vols{k}.mapping = varargin{2}; + z_handle = get(findobj(cm_handles(k), ... + 'label','Intensity mapping'),'Children'); + for l = 1:numel(z_handle) + c_handle = get(z_handle(l), 'Children'); + set(c_handle, 'checked', 'off'); + set(c_handle(checked), 'checked', 'on'); + end; + end; + redraw_all; + + case 'swap_img', + current_handle = get_current_handle; + new_info = spm_vol(spm_select(1,'image','select new image')); + fn = fieldnames(new_info); + for k=1:numel(fn) + st.vols{current_handle}.(fn{k}) = new_info.(fn{k}); + end; + spm_orthviews('context_menu','image_info',get(gcbo, 'parent')); + redraw_all; + + case 'add_blobs', + % Add blobs to the image - in split colortable + cm_handles = valid_handles(1:24); + if varargin{2} == 2, cm_handles = get_current_handle; end; + spm_figure('Clear','Interactive'); + [SPM,VOL] = spm_getSPM; + for i = 1:length(cm_handles), + addblobs(cm_handles(i),VOL.XYZ,VOL.Z,VOL.M); + c_handle = findobj(findobj(st.vols{cm_handles(i)}.ax{1}.cm,'label','Blobs'),'Label','Remove blobs'); + set(c_handle,'Visible','on'); + delete(get(c_handle,'Children')); + item7_3_1 = uimenu(c_handle,'Label','local','Callback','spm_orthviews(''context_menu'',''remove_blobs'',2);'); + if varargin{2} == 1, + item7_3_2 = uimenu(c_handle,'Label','global','Callback','spm_orthviews(''context_menu'',''remove_blobs'',1);'); + end; + end; + redraw_all; + + case 'remove_blobs', + cm_handles = valid_handles(1:24); + if varargin{2} == 2, cm_handles = get_current_handle; end; + for i = 1:length(cm_handles), + rmblobs(cm_handles(i)); + c_handle = findobj(findobj(st.vols{cm_handles(i)}.ax{1}.cm,'label','Blobs'),'Label','Remove blobs'); + delete(get(c_handle,'Children')); + set(c_handle,'Visible','off'); + end; + redraw_all; + + case 'add_image', + % Add blobs to the image - in split colortable + cm_handles = valid_handles(1:24); + if varargin{2} == 2, cm_handles = get_current_handle; end; + spm_figure('Clear','Interactive'); + fname = spm_select(1,'image','select image'); + for i = 1:length(cm_handles), + addimage(cm_handles(i),fname); + c_handle = findobj(findobj(st.vols{cm_handles(i)}.ax{1}.cm,'label','Blobs'),'Label','Remove blobs'); + set(c_handle,'Visible','on'); + delete(get(c_handle,'Children')); + item7_3_1 = uimenu(c_handle,'Label','local','Callback','spm_orthviews(''context_menu'',''remove_blobs'',2);'); + if varargin{2} == 1, + item7_3_2 = uimenu(c_handle,'Label','global','Callback','spm_orthviews(''context_menu'',''remove_blobs'',1);'); + end; + end; + redraw_all; + + case 'add_c_blobs', + % Add blobs to the image - in full colour + cm_handles = valid_handles(1:24); + if varargin{2} == 2, cm_handles = get_current_handle; end; + spm_figure('Clear','Interactive'); + [SPM,VOL] = spm_getSPM; + c = spm_input('Colour','+1','m',... + 'Red blobs|Green blobs|Yellow blobs|Cyan blobs|Blue blobs|Magenta blobs',[1 2 3 4 5 6],1); + colours = [1 0 0;0 1 0;1 1 0;0 0 1;0 1 1;1 0 1]; + c_names = {'red';'green';'yellow';'blue';'cyan';'magenta'}; + hlabel = sprintf('%s (%s)',VOL.title,c_names{c}); + for i = 1:length(cm_handles), + addcolouredblobs(cm_handles(i),VOL.XYZ,VOL.Z,VOL.M,colours(c,:)); + addcolourbar(cm_handles(i),numel(st.vols{cm_handles(i)}.blobs)); + c_handle = findobj(findobj(st.vols{cm_handles(i)}.ax{1}.cm,'label','Blobs'),'Label','Remove colored blobs'); + ch_c_handle = get(c_handle,'Children'); + set(c_handle,'Visible','on'); + %set(ch_c_handle,'Visible',on'); + item7_4_1 = uimenu(ch_c_handle(2),'Label',hlabel,'ForegroundColor',colours(c,:),... + 'Callback','c = get(gcbo,''UserData'');spm_orthviews(''context_menu'',''remove_c_blobs'',2,c);',... + 'UserData',c); + if varargin{2} == 1, + item7_4_2 = uimenu(ch_c_handle(1),'Label',hlabel,'ForegroundColor',colours(c,:),... + 'Callback','c = get(gcbo,''UserData'');spm_orthviews(''context_menu'',''remove_c_blobs'',1,c);',... + 'UserData',c); + end; + end; + redraw_all; + + case 'remove_c_blobs', + cm_handles = valid_handles(1:24); + if varargin{2} == 2, cm_handles = get_current_handle; end; + colours = [1 0 0;1 1 0;0 1 0;0 1 1;0 0 1;1 0 1]; + c_names = {'red';'yellow';'green';'cyan';'blue';'magenta'}; + for i = 1:length(cm_handles), + if isfield(st.vols{cm_handles(i)},'blobs'), + for j = 1:length(st.vols{cm_handles(i)}.blobs), + if st.vols{cm_handles(i)}.blobs{j}.colour == colours(varargin{3},:); + if isfield(st.vols{cm_handles(i)}.blobs{j},'cbar') + delete(st.vols{cm_handles(i)}.blobs{j}.cbar); + end + st.vols{cm_handles(i)}.blobs(j) = []; + break; + end; + end; + rm_c_menu = findobj(st.vols{cm_handles(i)}.ax{1}.cm,'Label','Remove colored blobs'); + delete(findobj(rm_c_menu,'Label',c_names{varargin{3}})); + if isempty(st.vols{cm_handles(i)}.blobs), + st.vols{cm_handles(i)} = rmfield(st.vols{cm_handles(i)},'blobs'); + set(rm_c_menu, 'Visible', 'off'); + end; + end; + end; + redraw_all; + + case 'add_c_image', + % Add truecolored image + cm_handles = valid_handles(1:24); + if varargin{2} == 2, cm_handles = get_current_handle;end; + spm_figure('Clear','Interactive'); + fname = spm_select(1,'image','select image'); + c = spm_input('Colour','+1','m','Red blobs|Yellow blobs|Green blobs|Cyan blobs|Blue blobs|Magenta blobs',[1 2 3 4 5 6],1); + colours = [1 0 0;1 1 0;0 1 0;0 1 1;0 0 1;1 0 1]; + c_names = {'red';'yellow';'green';'cyan';'blue';'magenta'}; + hlabel = sprintf('%s (%s)',fname,c_names{c}); + for i = 1:length(cm_handles), + addcolouredimage(cm_handles(i),fname,colours(c,:)); + addcolourbar(cm_handles(i),numel(st.vols{cm_handles(i)}.blobs)); + c_handle = findobj(findobj(st.vols{cm_handles(i)}.ax{1}.cm,'label','Blobs'),'Label','Remove colored blobs'); + ch_c_handle = get(c_handle,'Children'); + set(c_handle,'Visible','on'); + %set(ch_c_handle,'Visible',on'); + item7_4_1 = uimenu(ch_c_handle(2),'Label',hlabel,'ForegroundColor',colours(c,:),... + 'Callback','c = get(gcbo,''UserData'');spm_orthviews(''context_menu'',''remove_c_blobs'',2,c);','UserData',c); + if varargin{2} == 1 + item7_4_2 = uimenu(ch_c_handle(1),'Label',hlabel,'ForegroundColor',colours(c,:),... + 'Callback','c = get(gcbo,''UserData'');spm_orthviews(''context_menu'',''remove_c_blobs'',1,c);',... + 'UserData',c); + end + end + redraw_all; +end; +%_______________________________________________________________________ +%_______________________________________________________________________ +function current_handle = get_current_handle +global st +cm_handle = get(gca,'UIContextMenu'); +cm_handles = get_cm_handles; +current_handle = find(cm_handles==cm_handle); +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function cm_pos +global st +for i = 1:length(valid_handles(1:24)), + if isfield(st.vols{i}.ax{1},'cm') + set(findobj(st.vols{i}.ax{1}.cm,'UserData','pos_mm'),... + 'Label',sprintf('mm: %.1f %.1f %.1f',spm_orthviews('pos'))); + pos = spm_orthviews('pos',i); + set(findobj(st.vols{i}.ax{1}.cm,'UserData','pos_vx'),... + 'Label',sprintf('vx: %.1f %.1f %.1f',pos)); + set(findobj(st.vols{i}.ax{1}.cm,'UserData','v_value'),... + 'Label',sprintf('Y = %g',spm_sample_vol(st.vols{i},pos(1),pos(2),pos(3),st.hld))); + end +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function cm_handles = get_cm_handles +global st +cm_handles = []; +for i=valid_handles(1:24), + cm_handles = [cm_handles st.vols{i}.ax{1}.cm]; +end +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function zoom_all(op) +global st +cm_handles = get_cm_handles; +res = [.125 .125 .25 .5 1 1]; +if op==6, + st.bb = maxbb; +else, + vx = sqrt(sum(st.Space(1:3,1:3).^2)); + vx = vx.^(-1); + pos = spm_orthviews('pos'); + pos = st.Space\[pos ; 1]; + pos = pos(1:3)'; + if op == 5, st.bb = [pos-80*vx ; pos+80*vx] ; + elseif op == 4, st.bb = [pos-40*vx ; pos+40*vx] ; + elseif op == 3, st.bb = [pos-20*vx ; pos+20*vx] ; + elseif op == 2, st.bb = [pos-10*vx ; pos+10*vx] ; + elseif op == 1; st.bb = [pos- 5*vx ; pos+ 5*vx] ; + else disp('no Zoom possible'); + end; +end +resolution(res(op)); +redraw_all; +for i = 1:length(cm_handles) + z_handle = get(findobj(cm_handles(i),'label','Zoom'),'Children'); + set(z_handle,'Checked','off'); + set(z_handle(op),'Checked','on'); +end +return; + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% ROI: TimeSeries +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_searchContentEdit(hObject, eventdata) +handles = guidata(hObject); +set(handles.searchContentEdit,'UserData', 'manual'); + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% click load SPM file button +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_loadSPMmat(hObject, eventdata, spmfile) +handles = guidata(hObject); +if nargin < 3 | isempty(spmfile) + %if exist(fullfile(pwd,'SPM.mat'), 'file') + if ~isempty(findstr('SPM2',spm('ver'))) & exist('spm_get')==2 + spmfile = spm_get([1],'SPM.mat','Select a SPM file'); + elseif ~isempty(findstr('SPM5',spm('ver'))) & exist('spm_select')==2 + spmfile = spm_select(1,'SPM.mat','Select a SPM file'); + else + if exist('spm')==2 + error('Check for inconsistencies in your SPM install.\nspm(''ver'') = %s\n', spm('ver')) + else + error('No SPM found'); + end + end +end +xSPM=load(spmfile); +if isfield(xSPM.SPM, 'xCon') + + if ~exist(xSPM.SPM.swd, 'dir') + nswd = fileparts(spmfile); + warning('Directory specified in SPM.swd is missing (%s).\nWill use: %s',xSPM.SPM.swd,nswd); + xSPM.SPM.swd=nswd; + end + numc=listdlg('ListString' , {xSPM.SPM.xCon.name}, 'SelectionMode' , 'single', 'InitialValue', 2);%length(xSPM.SPM.xCon)); + if ~exist(fullfile(xSPM.SPM.swd,xSPM.SPM.xCon(numc).Vspm.fname)) & ... + exist(subarray(fullfile(xSPM.SPM.swd,xSPM.SPM.xCon(numc).Vspm.fname),1:2,-2)) + warning('Changing path') + xSPM.SPM.swd=xSPM.SPM.swd(3:end); + end + if ~isempty(numc) + CallBack_loadImagePush(handles.loadImagePush, [],... + {fullfile(xSPM.SPM.swd,xSPM.SPM.xCon(numc).Vspm.fname)},... + {xSPM.SPM.xCon(numc).name}); + end +else + msgbox('No contrast in this file') +end +return + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% paramest push (Parameter estimates) +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_indivResultsListPush(hObject, eventdata) +handles = guidata(hObject); +if ischar(handles.imageFileName) + handles.imageFileName={handles.imageFileName}; +end +[datadir,condir,condext]=fileparts(handles.imageFileName{1}); +if datadir(2)==':' + datadir=datadir(3:end); +end +[datadir,subdir]=fileparts(datadir); +if isempty(findstr('indiv/', datadir)) +elseif isempty(findstr('rfx/', datadir)) + xSPM=load(fullfile(datadir,'SPM.mat')); + indivdir=xSPM.xY.P; +else + return +end +req = get(handles.indivResultsListPush,'String'); +req = req{get(handles.indivResultsListPush, 'value')}; + +if isequal(req,'Group') + datadir=fileparts(datadir); + condir=handles.imageContrastName{1}; + condir=strrep(condir, '>', 'vs'); + condir=strrep(condir, '<', 'vs'); + condir=strrep(condir, '+', '_'); + condir=strrep(condir, ':', ' at'); + condir=deblank(condir); + condir=fullfile(datadir,'rfx',condir); + if ~exist(condir,'dir') + return + end + xSPM=load(fullfile(condir,'SPM.mat')); + numc=2; + CallBack_loadImagePush(handles.loadImagePush, [],... + {fullfile(xSPM.SPM.swd,xSPM.SPM.xCon(numc).Vspm.fname)},... + {xSPM.SPM.xCon(numc).name}); + return +elseif exist('swds','var') + CallBack_loadImagePush(handles.loadImagePush, [],... + swds(get(gcbo, 'value'),:),... + handles.imageContrastName); +else + if exist(fullfile(datadir,req,'SPM.mat')) + xSPM=load(fullfile(datadir,req,'SPM.mat')); + if ~isfield(handles,'imageContrastName') + return + end + % numc=strmatch(handles.imageContrastName{1},{xSPM.SPM.xCon.name},'exact'); + numc=get(handles.contrastListPush, 'Value'); + filename=fullfile(xSPM.SPM.swd,xSPM.SPM.xCon(numc).Vspm.fname); + else + % SPM adds a comma for 4D volumes + condext = strtok(condext,','); + filename = fullfile(datadir,req,[condir condext]); + warning('Using same filename in %s: ', req, filename) + end + if filename(2)==':' + filename=filename(3:end); + end + if ~exist(filename,'file') + error('File doesn''t exist: %s',filename) + return + end + set(handles.sectionViewListbox, 'Value', [1]); + % handles.sectionViewTargetFile = + % fullfile('\ndiayek\data\gazemo\rawdata\',req,'anat','wf_0001.img'); + guidata(hObject, handles); + if exist('xSPM','var') + CallBack_loadImagePush(handles.loadImagePush, [],... + {filename},... + {xSPM.SPM.xCon(numc).name}); + else + CallBack_loadImagePush(handles.loadImagePush, [],... + {filename},... + '???'); + end + % CallBack_allIntensityRadio(hObject, eventdata, 'c'); +end +uicontrol(hObject) + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% paramest push (Parameter estimates) +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_paramestRegionPush(hObject, eventdata, roi,readdata) +if nargin<4 + readdata=1; +end +roi=struct('type', roi); +handles = guidata(hObject); +roi.name=get(handles.structureEdit, 'String'); + +[img.p, img.fname, img.ext]=fileparts(handles.imageFileName{1}); +spmfile=fullfile(img.p,'SPM.mat'); + +if exist(spmfile(3:end), 'file') + warning('Possible disk swapping in xjview.m'); + spmfile=spmfile(3:end); +end +if ~exist(spmfile) + if findstr('SPM2',spm('ver')) + spmfile = spm_get([0 1],'SPM.mat','locate the RFX SPM.mat'); + elseif findstr('SPM5',spm('ver')) + spmfile = spm_select([0:1],'SPM.mat','locate the RFX SPM.mat'); + end +end +if ~exist(spmfile) + return +end +xSPM=load(spmfile, 'SPM'); +[glmpath, glmpath]=fileparts(fileparts(fileparts(xSPM.SPM.swd))); + +RetrieveRawData = 0; +status = get(gcbf, 'SelectionType') +% right click +if strcmp(status, 'alt') + RetrieveRawData = 1; +end + + + + +%[xyzmm,i] = spm_XYZreg('NearestXYZ',... +% spm_results_ui('GetCoords'),handles.currentxyz); +%spm_results_ui('SetCoords',xSPM.XYZmm(:,i)); +switch roi.type + case 'vox' + if not(isfield(handles,'currentxyz')) %%% + error('Wrong selection') + return + end + xyz = handles.currentxyz; + case 'clu' + if not(isfield(handles,'selectedCluster')) | not(isempty(handles.selectedCluster)) + switch questdlg('Load from workspace','cluster?', 'currentDisplayMNI{1}','...','Cancel','currentDisplayMNI{1}') + case 'currentDisplayMNI{1}' + xyz=evalin('base', 'currentDisplayMNI{1}'); + case '...' + case 'Cancel' + return + end + delete(findobj('Tag', 'paramest')) + + else + delete(findobj('Tag', 'paramest')) + xyz =handles.currentDisplayMNI{1}; + end + case 'sph' + if not(isfield(handles,'currentxyz')) %%% + error('Wrong selection') + return + end + delete(findobj('Tag', 'paramest')) + xyz = handles.currentxyz; + roi.radius=inputdlg('Radius of the sphere?'); + if isempty(roi.radius) + return + end + roi.radius=str2num(roi.radius{1}) + % XYZmm = xSPM.SPM.xVol.M(1:3,:)*[xSPM.SPM.xVol.XYZ; ones(1,size(xSPM.SPM.xVol.XYZ,2))]; + % xyz = sqrt( XYZmm-repmat(xyz,[1 size(XYZmm,2)]) ) + % xyz = XYZmm(:, xyz < roi.radius) + +end +% handles.currentDisplayMNI{1} +% ha=axes('Tag', 'paramest', 'Parent',gcf,'units','normalized','Position',[0.55, 0.05, 0.4, 0.4]); +if isequal(roi.type, 'sph') + if roi.radius > 20 + if not(spm_input({'Many many voxels may be retrieve','Radius of the sphere (in mm):',... + num2str(roi.radius),... + 'Continue anyway?'},... + 1,'bd',{'OK','NO'},[1,0])) + return + end + end + +end +if size(xyz,1)>40 + if not(spm_input({'Many many voxels to retrieve','Number of voxel:',... + num2str(size(xyz,1)),... + 'Continue anyway?'},... + 1,'bd',{'OK','NO'},[1,0])) + return + end +end + +allbetas= []; +allcons= []; +allxyz = []; +allvxyz = []; +roi.nvox = []; +hwait = waitbar(0,'Reading 1st level data'); +lcd=cd; +handles.currentxyz +if isfield(handles, 'paramest') + handles.paramest=[]; +end + +roi.stat.TF=handles.TF; +roi.stat.pValue=handles.pValue; +roi.stat.df=handles.df; +% roi.stat.Intensity = +% handles.currentDisplayIntensity{1}(find(all(handles.currentDisplayMNI{1}==repmat(handles.currentxyz, 3,1),2))); +[i,j]=ismember(handles.mni{1}, flipud(xyz), 'rows'); +roi.stat.intensity(j(i)) = handles.intensity{1}(i); + +if isstruct(xSPM.SPM.xX.K) | ~readdata + n=1; + % Ic=strmatch('F Task',{xSPM.SPM.xCon.name}) + % + % allbetas=[] + % vcon= + % XYZ = SPM.xVol.XYZ; + % XYZmm = SPM.xVol.M(1:3,:)*[XYZ; ones(1,size(XYZ,2))]; + % tmpallbetas= []; + % [nxyz,i] = spm_XYZreg('NearestXYZ',xyz(cluvox,:),XYZmm); + % allbetas = [allbetas;(mean(tmpallbetas,1))]; + % allxyz= [allxyz;mean(tmpallxyz,1)]; + +else + n= length(xSPM.SPM.xY.P) +end + +% Is that a correlation? +PlotCorrelation = (n>1) && not(isequal(all(xSPM.SPM.xX.X==1), [1])); + +try + roi.sub2pr = str2num(subarray(strvcat(xSPM.SPM.xY.P),55:56,2)); +end + + +for sub = 1:n + + waitbar(sub/n,hwait); + if n>1 + subdir = fileparts(xSPM.SPM.xY.P{sub}); + else + subdir = xSPM.SPM.swd; + end + subdir0 =subdir; + if ~exist(subdir, 'dir') + subdir = regexprep(subdir, '[A-Z]?\:',''); + end + if ~exist(subdir, 'dir') + subdir = regexprep(subdir, {'^\\ndiayek', '^\\ndiaye'},'') + end + + + if ~exist(fullfile(subdir, 'SPM.mat'), 'file') & ... + exist(subarray(fullfile(subdir, 'SPM.mat'),1:2,-2),'file') + subdir = subdir(3:end); + end + if exist(fullfile(subdir, 'SPM.mat'), 'file') + load(fullfile(subdir, 'SPM.mat')); + fprintf('Retrieving betas from: %s\n', fullfile(subdir, 'SPM.mat')) + + if sub==1 + % [cn.path cn.fname cn.ext]=fileparts(char(xSPM.SPM.xY.P(sub,:))); + % consfiles=[SPM.xCon.Vcon]; + + % retrieves the f map (where all betas should be) + Ic = [... + strmatch('f anim', lower({SPM.xCon.name})) ... + strmatch('f task', lower({SPM.xCon.name})) ... + strmatch('f map', lower({SPM.xCon.name}))]; + if isempty(Ic) + Ic = listdlg('ListString',{SPM.xCon.name},'InitialValue',Ic); + end + Ic=Ic(end); + conname=SPM.xCon(Ic).name; + end + + %------- + Ic = strmatch(conname,{SPM.xCon.name}, 'exact') ; %contrast number + if isempty(Ic) + Ic = listdlg('ListString',{SPM.xCon.name}); + end + %------- + else + SPM = xSPM.SPM + %using raw data + Ic=NaN; + end + try + XYZ = SPM.xVol.XYZ; + XYZmm = SPM.xVol.M(1:3,:)*[XYZ; ones(1,size(XYZ,2))]; + tmpallbetas= []; + tmpallcons= []; + tmpallxyz = []; + cd(subdir) + for cluvox = 1:size(xyz,1) + switch (roi.type) + case 'sph' + [d] = spm_XYZreg('Edist',xyz(cluvox,:),XYZmm); + i=find(d<=roi.radius); + govox = ~isempty(i); + nxyz=XYZmm(:,i); + case {'vox', 'clu'} + govox = 1; + [nxyz,i] = spm_XYZreg('NearestXYZ',xyz(cluvox,:),XYZmm); + if sqrt(sum((nxyz'-xyz(cluvox,:)).^2))>=sqrt(3) %one voxel in each dimZ + govox= spm_input({'No data stored for this voxel','Closest voxels with data are:',... + num2str(xyz(cluvox,:)),... + 'Continue anyway?'},... + 1,'bd',{'OK','NO'},[1,0]); + end + end + if govox==1 + vXYZ = XYZ(:,i) ; % coordinates in voxels + + %-Get parameter and hyperparameter estimates + %======================================================================= + % ResMS = spm_get_data(SPM.VResMS,vXYZ); + % Bcov = ResMS*SPM.xX.Bcov; + CI = 1.6449; % = spm_invNcdf(1 - 0.05); + % compute contrast of parameter estimates and 90% C.I. + %---------------------------------------------------------- + %---- + xSPM.SPM.xY.VY(sub).fname = strrep(xSPM.SPM.xY.VY(sub).fname, subdir0, subdir); + tmpallcons = [tmpallcons ; spm_get_data(xSPM.SPM.xY.VY(sub), vXYZ)]; + if ~isnan(Ic) + beta = spm_get_data(SPM.Vbeta, vXYZ); + tmpallbetas = [tmpallbetas; (SPM.xCon(Ic).c'*beta)']; + else + tmpallbetas = tmpallcons; + end + + if RetrieveRawData + if sub==1 + fprintf('\tRetrieving rawdata from: %s\n', fullfile(subdir, 'SPM.mat')) + tmpallcons = [ spm_get_data(xSPM.SPM.xY.VY, vXYZ)']; + end + end + tmpallxyz = [tmpallxyz; nxyz']; + end + end + allbetas = [allbetas;(mean(tmpallbetas,1))]; + allcons = [allcons;(mean(tmpallcons,1))]; + allxyz= [allxyz;mean(tmpallxyz,1)]; + allvxyz= [allvxyz mean(vXYZ,2)]; + roi.nvox = [ roi.nvox; size(tmpallbetas,1)]; + tmp = mean(tmpallxyz); + % disp(['meancluster = ' num2str(mean(tmpallxyz,1))]); + catch + warning('Error with data from: %s', subdir) + end +end +close(hwait) +cd(lcd); +% +if ~isnan(Ic) + regnames=cellstr(subarray(strvcat(strrep(SPM.xX.name(any(SPM.xCon(Ic).c )),'*bf(1)', '')'), 1:6, -2)); + regnames=cellstr(subarray(strvcat(strrep(SPM.xX.name(any(SPM.xCon(Ic).c')),'*bf(1)', '')'), 1:6, -2)); + [regnames, ireg,ireg2]=unique(regnames); + a=[ repmat(' ',length(regnames),1) strvcat(strrep(regnames, '_', ' '))]; + a=dataread('string',a', '%s'); + leg=a(isort(ireg)); +else + leg={'??'}; +end +%a=reshape(a, [],108])'; +% +% n=factor(size(allbetas,2)); +% n=sort([n(end) prod(n(1:end-1))]); +% +% prompt={'Enter the matrix size for x^2:','Enter the colormap name:'}; +% def={'20','hsv'}; +% dlgTitle='Input for Peaks function'; +% lineNo=1; +% answer=inputdlg(prompt,dlgTitle,lineNo,def); +% +% AddOpts.Resize='on'; +% AddOpts.WindowStyle='normal'; +% AddOpts.Interpreter='tex'; +% answer=inputdlg(prompt,dlgTitle,lineNo,def,AddOpts); +% +assignin('base', 'allbetas', allbetas) +% ReshapeData = []; +% xbars=[]; +% fxorder=[2 3 1]; +% switch size(allbetas,2) +% case 8 +% ReshapeData = [4 2]; +% case 4 +% if all(ismember(a,{'Neutral' 'Fearful' 'Angry' 'Happy'})) +% ReshapeData = [4 1]; +% end +% case 18 +% ReshapeData = [18 1]; +% case 16 +% % ReshapeData = [2 8]; +% % xtick=[1 3 4 5 7 8 9 11]; +% % fxorder=[3 2 1]; +% ReshapeData = [16 1]; +% end +% if isempty(ReshapeData) +% ReshapeData = [fliplr(factor(size(allbetas,2))) 1]; +% ReshapeData = [ ReshapeData(1) prod(ReshapeData(2:end))] +% end +% allbetas=reshape(allbetas, [],ReshapeData(1),ReshapeData(2)); +% allbetas = permute(allbetas,fxorder); + +% allxyz=reshape(allxyz, [], 1, 3); +% allxyz=permute(allxyz, [3 2 1]); + +%assignin('base', 'allbetas', allbetas) +%assignin('base', 'allxyz', xyz) + + +roi.allbetas=allbetas; +roi.allxyz=allxyz'; +roi.allvxyz=allvxyz; +roi.allcons=allcons'; +roi.XYZmm=xyz'; +roi.xyz=mean(allxyz,1)'; +assignin('base', 'roi', roi) +%Icc = find(xSPM.SPM.xX.X*xSPM.SPM.xCon(strmatch(handles.imageContrastName{1}, {xSPM.SPM.xCon.name})).c); +% if ~isempty(handles.imageContrastName) +% fn=handles.imageContrastName{1}; +% else +[fp,fn,fe] = fileparts(handles.imageFileName{1}); +fn=[fn fe]; +cn=[xSPM.SPM.xCon.Vspm]; + +% end +Icc = strmatch(fn, {cn.fname}) ; +roi.fname = fullfile(fp,fn); +roi.cn = cn; +roi.Icc=Icc; +roi.leg=leg; +assignin('base', 'roi', roi) + + +try + plot_betas(roi,glmpath,handles) +catch + if n>1 + try + figure;h=barerrorbar(1:(prod(size(roi.allbetas))/size(roi.allbetas,1)),mean(roi.allbetas,1)',stderrw(roi.allbetas,2:ndims(roi.allbetas),1)',NaN) + catch + end + else + figure;bar(roi.allbetas) + end + xlabel('Task') + set(gca, 'XTickLabel', leg) + set(gca, 'XTickLabel', {'Self Pos' 'Self Neg' 'Other Pos' 'Other Neg' 'Word Pos' 'Word Neg'}) + xlabel('Task') + %legend({'Positive Words' 'Negative Words'},'Location', 'Best') + delete(legend) +end +title(sprintf('XYZ = %+0.1f %+0.1f %+0.1f', roi.XYZmm(1:3))); +setappdata(gca, 'ROI', roi) + +if ~isempty(Icc) + if ~isempty(xSPM.SPM.xCon(Icc).c) + roi.allregressors = xSPM.SPM.xX.X*xSPM.SPM.xCon(Icc).c; + else + roi.allregressors = []; + end + assignin('base', 'roi', roi) +end +if PlotCorrelation + %correlation plot + figure; + cla; + plot(roi.allregressors, roi.allcons, 'x') + %hl=legend({'test'}, 0); + [s,o,r2,sce,p]=linearfit(roi.allcons,roi.allregressors); + text(median(roi.allregressors), quantile(roi.allcons,.90), {'Correlation: ' sprintf('r2 = %g\np = %g', r2,p)}) + xlabel(xSPM.SPM.xX.name(find(xSPM.SPM.xCon(Icc).c))) + ylabel(xSPM.SPM.xY.VY(1).descrip) + [xx]=axis; + hold on + plot(xx(1:2), s*xx(1:2)+o, '--') + hold off + title(sprintf('XYZ = %+0.1f %+0.1f %+0.1f', roi.XYZmm(1:3))); +end + +if RetrieveRawData + figure; + if n==1 + subplot(2,1,1); + plot(tmpallcons); + legend('BOLD',0) + subplot(2,1,2); + % iReg=any(SPM.xCon(handles.imageContrastName).c,2); + % plot(normalize(SPM.xX.X(:,iReg), 'unity')*diff(subarray(axis, [3 4]))/5+subarray(axis, 3)) + % legend([{'BOLD'} SPM.xX.name(iReg)],1) + Ic = handles.imageContrastName; + if isempty(Ic) || ~isnumeric(Ic) + Ic = get(handles.contrastListPush, 'Value'); + end + plot( [ + spm_FcUtil('Yc',SPM.xCon(Ic),SPM.xX.xKXs,beta) ... + SPM.xX.X*SPM.xCon(Ic).c*pinv(SPM.xCon(Ic).c)*beta ... + (1-2*get(handles.negativeIntensityRadio, 'Value'))*SPM.xX.X*SPM.xCon(Ic).c ]) + legend({ 'adjusted', 'predicted' SPM.xCon(Ic).name},0) + end +end + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% paramest push (Parameter estimates) +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function [c_num, c_let, c_word]=mycolourset +c_num = [1 0 0;0 1 0;1 1 0;0 1 1;0 0 1;1 0 1]; +c_let = {'r';'g';'y';'c';'b';'m'}; +c_word= {'red';'green';'yellow';'cyan';'blue';'magenta'}; \ No newline at end of file diff --git a/xjview8p4.m b/xjview8p4.m new file mode 100644 index 0000000..6a6fed1 --- /dev/null +++ b/xjview8p4.m @@ -0,0 +1,12218 @@ +function xjview(varargin) +% xjview, version 8.4 +% +% usage 1: xjview (no argument) +% for displaying a result img file, or multiple image files, +% (which will be loaded later) and changing p-value or t/f-value +% usage 2: xjview(imagefilename) +% for displaying the result img file and changing p-value or +% t-value +% Example: xjView spmT_0002.img +% xjView('spmT_0002.img') +% xjView mymask.img +% usage 3: xjview(imagefilename1, imagefilename2, ...) +% for displaying the result img files and changing p-value or +% t/f-value +% Example: xjView spmT_0002.img spmT_0005.img spmT_0007.img +% xjView('spmT_0002.img', 'spmT_0003.img', 'spmT_0006.img') +% xjView myMask1.img myMask2.img myMask3.img +% usage 4: xjview(mnicoord, intensity) +% for displaying where are the mni coordinates +% mnicoord: Nx3 matrix of mni coordinates +% intensity: (optional) Nx1 matrix, usually t values of the +% corresponding voxels +% Example: xjView([20 10 1; -10 2 5],[1;2]) +% xjView([20 10 1; -10 2 5]) +% Note: to use xjview this way, you may need to modify the value +% of M and DIM in the begining of xjview.m +% +% http://www.alivelearn.net/xjview +% +% by Xu Cui, Jian Li and Xiaowei Song +% last modified: 10/21/2011 (compatible with MatLab 2011) +% last modified: 2/28/2011 (add small volume correction) +% last modified: 2/25/2011 (fix volume bug under SPM 8 r4010) +% last modified: 12/14/2009 (FDR and Feed) +% last modified: 06/24/2009 (fix bug in activation report) +% last modified: 05/24/2009 (slice view) +% last modified: 04/17/2009 (SPM8 compatible) +% last modified: 11/20/2007 (new database, all database from wfu_pickatlas, including aal +% last modified: 06/01/2007 (correct FDR corrected p-value list, change intensity to handles.intensity{1}) +% last modified: 02/18/2007 (add colorbar max control) +% last modified: 11/16/2006 (keyboard shortcut for open image and open roi file) +% last modified: 06/16/2006 (spm5 compatible) +% last modified: 05/30/2006 (left/right flip, path of mask.img and templateFile.img) +% last modified: 05/08/2006 (debug CallBack_volumePush function, change handles.intensity{1} to intensity) +% last modified: 04/03/2006 (modify tr) +% last modified: 12/28/2005 (modify SPM process) +% +% Thank Sergey Pakhomov for sharing his database (MNI Space Utility). +% Thank Joseph Maldjian for WFU_PickAtlas +% Thank Yuval Cohen for the maximize figure function (maximize.m) +% + +warnstate = warning; +warning off; + +% pre-set values +% important! you need compare the display of xjview and spm. If you find +% xjview flipped the left/right, you need to set leftrightflip = 1; +% otherwise leave it to 0. +leftrightflip = 0; + +% You only need to change M and DIM when you want to use xjview under +% 'usage 4'. +M = ... + [-4 0 0 84;... + 0 4 0 -116;... + 0 0 4 -56;... + 0 0 0 1]; +DIM = [41 48 35]'; + M = [ + -2 0 0 92 + 0 2 0 -128 + 0 0 2 -74 + 0 0 0 1]; + DIM = [91 109 91]'; +M = ... + [-4 0 0 82;... + 0 4 0 -116;... + 0 0 4 -54;... + 0 0 0 1]; +DIM = [40 48 34]'; + +TR = 2; % you don't need to set TR is you only use the viewing part of xjview +XJVIEWURL = 'http://www.alivelearn.net/xjview'; + +% system settings +try + spmdir = spm('dir'); + %spm('defaults', 'fmri'); +catch + disp('Please add spm path.'); + warning(warnstate(1).state); + return +end + +try + spm('defaults', 'fmri'); +catch + []; +end + + +if ispc + os = 'windows'; +elseif isunix + os = 'linux'; +else + warndlg('I don''t know what kind of computer you are using. I assumed it is unix.', 'What computer are you using?'); + os = 'linux'; +end +screenResolution = get(0,'ScreenSize'); + +xjviewpath = fileparts(which('xjview')); + +% pre-set values +pValue = 0.001; +intensityThreshold = 0; +clusterSizeThreshold = 5; + + +% Appearance Settings +figurePosition = [0.100, 0.050, 0.550, 0.880]; +sectionViewPosition = [0.5,0.61,0.45,0.45]; +glassViewAxesPosition = [0.000, 0.600, 0.464, 0.400]; +if screenResolution(3) <= 1024 + figurePosition = [0.100, 0.050, 0.700, 0.900]; + sectionViewPosition = [0.5, 0.61, 0.45, 0.46]; + glassViewAxesPosition = [0.000, 0.600, 0.464, 0.400]; +end + +left = 0.01; +editBoxHeight = 0.05; +editBoxWidth = 0.200; +editBoxLeft = 0.100; + +controlPanelPosition = [left, 0.080, 0.500, 0.500]; +stretchMatrix = diag([controlPanelPosition(3),controlPanelPosition(4),controlPanelPosition(3),controlPanelPosition(4)]); +controlPanelOffset = controlPanelPosition' .* [1,1,0,0]'; +heightUnit = 0.055; + +sliderPosition = stretchMatrix*[0.000, 0*heightUnit-0.01, 1.000, editBoxHeight]' + controlPanelOffset; +pValueTextPosition = stretchMatrix*[0.000, 0.8*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +pValueEditPosition = stretchMatrix*[0.100, 1*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +fdrTextPosition = stretchMatrix*[0.350, 0.8*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +fdrEditPosition = stretchMatrix*[0.450, 1*heightUnit, editBoxWidth/2, editBoxHeight]' + controlPanelOffset; +fdrPushPosition = stretchMatrix*[0.300, 1*heightUnit, editBoxWidth/2, editBoxHeight]' + controlPanelOffset; +intensityThresholdTextPosition = stretchMatrix*[0.55, 0.8*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +intensityThresholdEditPosition = stretchMatrix*[0.65, 1*heightUnit, editBoxWidth*3/4, editBoxHeight]' + controlPanelOffset; +dfTextPosition = stretchMatrix*[0.840, 0.8*heightUnit, 0.8-0.74, editBoxHeight]' + controlPanelOffset; +dfEditPosition = stretchMatrix*[0.900, 1*heightUnit, editBoxWidth/2, editBoxHeight]' + controlPanelOffset; +clusterSizeThresholdTextPosition = stretchMatrix*[0.000, 1.8*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +clusterSizeThresholdEditPosition = stretchMatrix*[0.150, 2*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +pickThisClusterPushPosition = stretchMatrix*[0.400, 2*heightUnit, editBoxWidth*1, editBoxHeight]' + controlPanelOffset; +selectThisClusterPushPosition = stretchMatrix*[0.600, 2*heightUnit, editBoxWidth*1, editBoxHeight]' + controlPanelOffset; +clearSelectedClusterPushPosition = stretchMatrix*[0.800, 2*heightUnit, editBoxWidth*1, editBoxHeight]' + controlPanelOffset; +thisClusterSizeTextPosition = stretchMatrix*[0.600, 2*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +thisClusterSizeEditPosition = stretchMatrix*[0.800, 2*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +loadImagePushPosition = stretchMatrix*[0.000, 3*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +imageFileEditPosition = stretchMatrix*[0.200, 3*heightUnit, 1-editBoxWidth, editBoxHeight]' + controlPanelOffset; +saveImagePushPosition = stretchMatrix*[0.000, 4*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +saveImageFileEditPosition = stretchMatrix*[0.200, 4*heightUnit, 1-editBoxWidth, editBoxHeight]' + controlPanelOffset; +saveResultPSPushPosition = stretchMatrix*[0.000, 5*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +resultPSFileEditPosition = stretchMatrix*[0.200, 5*heightUnit, 1-editBoxWidth, editBoxHeight]' + controlPanelOffset; +displayIntensityTextPosition = stretchMatrix*[0.000, 3*heightUnit, editBoxWidth+0.1, editBoxHeight]' + controlPanelOffset; +allIntensityRadioPosition = stretchMatrix*[0.250, 3*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +positiveIntensityRadioPosition = stretchMatrix*[0.400, 3*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +negativeIntensityRadioPosition = stretchMatrix*[0.550, 3*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +renderViewCheckPosition = stretchMatrix*[0.730, 3*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +renderStylePopPosition = stretchMatrix*[0.90, 3*heightUnit, editBoxWidth/2, editBoxHeight]' + controlPanelOffset; +hideControlPushPosition = stretchMatrix*[0.200, 4*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +reportPushPosition = stretchMatrix*[0.000, 4*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +volumePushPosition = stretchMatrix*[0.200, 4*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +smallVolumePosition = stretchMatrix*[0.400, 4*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +commonRegionPushPosition = stretchMatrix*[0.600, 4*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; + +reslicePushPosition = stretchMatrix*[0.600, 4*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +sliceViewPushPosition = stretchMatrix*[0.780, 4*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +displayPushPosition = stretchMatrix*[0.200, 4*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +allinonePushPosition = stretchMatrix*[0.400, 4*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +searchPushPosition = stretchMatrix*[0.000, 6*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +searchContentEditPosition = stretchMatrix*[0.200, 6*heightUnit, editBoxWidth*2, editBoxHeight]' + controlPanelOffset; +searchTextPosition = stretchMatrix*[0.600, 6*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +searchEnginePopPosition = stretchMatrix*[0.600, 6*heightUnit, 1-0.6, editBoxHeight]' + controlPanelOffset; +overlayPushPosition = stretchMatrix*[0.000, 5*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +overlayEditPosition = stretchMatrix*[0.200, 5*heightUnit, 0.6-editBoxWidth, editBoxHeight]' + controlPanelOffset; +overlayPopPosition = stretchMatrix*[0.600, 5*heightUnit, 1-0.6, editBoxHeight]' + controlPanelOffset; +helpPosition = stretchMatrix*[0.800, 5*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +infoTextBoxPosition = stretchMatrix*[0.000, 8*heightUnit, 1, editBoxHeight*9]' + controlPanelOffset; +%xjViewPosition = stretchMatrix*[0.400, 13*heightUnit, editBoxWidth*2.5, editBoxHeight*3]' + controlPanelOffset; + +sectionViewListboxPosition = [sectionViewPosition(1)+0.4, sectionViewPosition(2)+0.02, 0.1, 0.14]; +sectionViewMoreTargetPushPosition = [sectionViewListboxPosition(1),sectionViewListboxPosition(2)-0.02,0.10,0.02]; +xHairCheckPosition = [sectionViewListboxPosition(1),sectionViewListboxPosition(2)+0.14,0.15,0.02]; +setTRangeEditPosition = [sectionViewListboxPosition(1),sectionViewListboxPosition(2)-0.06,0.10,0.02]; +setTRangeTextPosition = [sectionViewListboxPosition(1),sectionViewListboxPosition(2)-0.04,0.10,0.02]; + +getStructurePushPosition = [glassViewAxesPosition(1), glassViewAxesPosition(2)-0.06, editBoxWidth/2, editBoxHeight/2]; +structureEditPosition = [getStructurePushPosition(1), getStructurePushPosition(2), 1, getStructurePushPosition(4)]; +framePosition = (controlPanelPosition - controlPanelOffset')*1.05 + 0.95*controlPanelOffset'; + +% draw figure and control +figureBKcolor=[176/255 252/255 188/255]; +figureBKcolor=get(0,'Defaultuicontrolbackgroundcolor'); +f = figure('unit','normalized','position',figurePosition,'Color',figureBKcolor,'defaultuicontrolBackgroundColor', figureBKcolor,... + 'Name','xjView', 'NumberTitle','off','resize','off','CloseRequestFcn', {@CallBack_quit, warnstate(1).state}, 'visible','off', 'DoubleBuffer','on'); +handles = guihandles(f); + +% databases +try + X = load('TDdatabase'); + handles.DB = X.DB; + handles.wholeMaskMNIAll = X.wholeMaskMNIAll; +catch + errordlg('I can''t find TDdatabase.mat','TDdatabase not found'); +end + +handles.figure = f; +handles.frame = uicontrol(handles.figure,'style','frame',... + 'unit','normalized',... + 'position',framePosition,... + 'Visible','off'); +handles.slider = uicontrol(handles.figure,'style','slider',... + 'unit','normalized',... + 'position',sliderPosition,... + 'max',1,'min',0,... + 'sliderstep',[0.01,0.10],... + 'callback',@CallBack_slider,... + 'value',0,'Visible','on'); +handles.pValueTextPosition = uicontrol(handles.figure,'style','text',... + 'unit','normalized','position',pValueTextPosition,... + 'string','pValue=','horizontal','left'); +handles.pValueEdit = uicontrol(handles.figure,'style','edit',... + 'unit','normalized','position',pValueEditPosition,... + 'horizontal','left',... + 'String', num2str(pValue),... + 'BackgroundColor', 'w',... + 'callback',@CallBack_pValueEdit); +handles.fdrTextPosition = uicontrol(handles.figure,'style','text',... + 'unit','normalized','position',fdrTextPosition,... + 'string','FDR p=','horizontal','left'); +handles.fdrEdit = uicontrol(handles.figure,'style','edit',... + 'unit','normalized','position',fdrEditPosition,... + 'horizontal','left',... + 'String', '',... + 'BackgroundColor', 'w',... + 'callback',@CallBack_fdrEdit); +handles.intensityThresholdText = uicontrol(handles.figure,'style','text',... + 'unit','normalized','position',intensityThresholdTextPosition,... + 'string',' intensity=','horizontal','left'); +handles.intensityThresholdEdit = uicontrol(handles.figure,'style','edit',... + 'unit','normalized','position',intensityThresholdEditPosition,... + 'horizontal','left',... + 'BackgroundColor', 'w',... + 'String', num2str(intensityThreshold),... + 'callback',@CallBack_intensityThresholdEdit); +handles.dfText = uicontrol(handles.figure,'style','text',... + 'unit','normalized','position',dfTextPosition,... + 'string','df= ','horizontal','right'); +handles.dfEdit = uicontrol(handles.figure,'style','edit',... + 'unit','normalized','position',dfEditPosition,... + 'horizontal','left',... + 'BackgroundColor', 'w',... + 'String', '',... + 'callback',@CallBack_dfEdit); +handles.clusterSizeThresholdText = uicontrol(handles.figure,'style','text',... + 'unit','normalized','position',clusterSizeThresholdTextPosition,... + 'string','cluster size >=','horizontal','left'); +handles.clusterSizeThresholdEdit = uicontrol(handles.figure,'style','edit',... + 'unit','normalized','position',clusterSizeThresholdEditPosition,... + 'horizontal','left',... + 'BackgroundColor', 'w',... + 'String', num2str(clusterSizeThreshold),... + 'callback',@CallBack_clusterSizeThresholdEdit); +handles.thisClusterSizeText = uicontrol(handles.figure,'style','text',... + 'unit','normalized','position',thisClusterSizeTextPosition,... + 'string','size= ','horizontal','right', 'visible', 'off'); +handles.thisClusterSizeEdit = uicontrol(handles.figure,'style','edit',... + 'unit','normalized','position',thisClusterSizeEditPosition,... + 'horizontal','left',... + 'Enable', 'inactive',... + 'String', '','visible','off'); + +handles.imageFileEdit = uicontrol(handles.figure,'style','edit',... + 'unit','normalized','position',imageFileEditPosition,... + 'horizontal','left',... + 'String', '',... + 'BackgroundColor', 'w',... + 'callback',@CallBack_imageFileEdit,... + 'visible','off'); +handles.saveImageFileEdit = uicontrol(handles.figure,'style','edit',... + 'unit','normalized','position',saveImageFileEditPosition,... + 'horizontal','left',... + 'BackgroundColor', 'w',... + 'String', 'myMask.img',... + 'callback',@CallBack_saveImageFileEdit,... + 'visible','off'); +handles.saveResultPSEdit = uicontrol(handles.figure,'style','edit',... + 'unit','normalized','position',resultPSFileEditPosition,... + 'horizontal','left',... + 'BackgroundColor', 'w',... + 'String', 'myResult.ps',... + 'callback',@CallBack_saveResultPSEdit,... + 'visible','off'); +handles.loadImagePush = uicontrol(handles.figure,'style','push',... + 'unit','normalized','position',loadImagePushPosition,... + 'string','Load Image','callback',@CallBack_loadImagePush,... + 'visible','off'); +handles.saveImagePush = uicontrol(handles.figure,'style','push',... + 'unit','normalized','position',saveImagePushPosition,... + 'string','Save Image','callback',@CallBack_saveImagePush,... + 'visible','off'); +handles.saveResultPSPush = uicontrol(handles.figure,'style','push',... + 'unit','normalized','position',saveResultPSPushPosition,... + 'string','Save Result','callback',@CallBack_saveResultPSPush,... + 'visible','off'); +handles.getStructurePush = uicontrol(handles.figure,'style','push',... + 'unit','normalized','position',getStructurePushPosition,... + 'string','Get Structure','callback',@CallBack_getStructurePush,'visible','off'); +handles.structureEdit = uicontrol(handles.figure,'style','edit',... + 'unit','normalized','position',structureEditPosition,... + 'horizontal','center',... + 'enable', 'on',... + 'UserData',struct(... + 'hReg', [],... + 'M', M,... + 'D', DIM,... + 'xyz', [0 0 0] )); +handles.pickThisClusterPush = uicontrol(handles.figure,'style','push',... + 'unit','normalized','position',pickThisClusterPushPosition,... + 'string','Pick Cluster/Info','callback',@CallBack_pickThisClusterPush); +handles.selectThisClusterPush = uicontrol(handles.figure,'style','push',... + 'unit','normalized','position',selectThisClusterPushPosition,... + 'string','Select Cluster','callback',@CallBack_selectThisClusterPush); +handles.clearSelectedClusterPush = uicontrol(handles.figure,'style','push',... + 'unit','normalized','position',clearSelectedClusterPushPosition,... + 'string','Clear Selection','callback',@CallBack_clearSelectedClusterPush); + +handles.displayIntensityText = uicontrol(handles.figure,'style','text',... + 'unit','normalized','position',displayIntensityTextPosition,... + 'string','display intensity','horizontal','left'); +handles.allIntensityRadio = uicontrol(handles.figure,'style','radio',... + 'unit','normalized',... + 'string','All',... + 'position',allIntensityRadioPosition,... + 'value', 1,... + 'callback',@CallBack_allIntensityRadio); +handles.positiveIntensityRadio = uicontrol(handles.figure,'style','radio',... + 'unit','normalized',... + 'string','Only +',... + 'position',positiveIntensityRadioPosition,... + 'callback',{@CallBack_allIntensityRadio,'+'}); +handles.negativeIntensityRadio = uicontrol(handles.figure,'style','radio',... + 'unit','normalized',... + 'string','Only -',... + 'position',negativeIntensityRadioPosition,... + 'callback',{@CallBack_allIntensityRadio,'-'}); +handles.renderViewCheck = uicontrol(handles.figure,'style','checkbox',... + 'unit','normalized',... + 'string','Render View' ,... + 'horizontal', 'right',... + 'position',renderViewCheckPosition,... + 'callback', @CallBack_renderViewCheck); +handles.renderStylePop = uicontrol(... + 'Units','normalized', ... + 'ListboxTop',0, ... + 'Position',renderStylePopPosition, ... + 'String',{'new';'old'}, ... + 'Style','popupmenu', ... + 'value',1,... + 'callback', @CallBack_renderStylePop); +handles.sectionViewListbox = uicontrol(handles.figure,'style','listbox',... + 'unit','normalized',... + 'String', {'single T1','avg152PD','avg152T1','avg152T2','avg305T1','ch2','ch2bet','aal','brodmann'}, ... + 'value',3,... + 'position',sectionViewListboxPosition,... + 'callback',@CallBack_sectionViewListbox); + +handles.xHairCheck = uicontrol(handles.figure,'style','checkbox',... + 'unit','normalized',... + 'string','XHairs Off' ,... + 'horizontal','left',... + 'position',xHairCheckPosition,... + 'callback',@CallBack_xHairCheck); +handles.sectionViewMoreTargetPush = uicontrol(handles.figure,'style','push',... + 'unit','normalized','position',sectionViewMoreTargetPushPosition,... + 'string','other ...','callback',@CallBack_sectionViewMoreTargetPush); +handles.setTRangeEdit = uicontrol(handles.figure,'style','edit',... + 'unit','normalized','position',setTRangeEditPosition,'BackgroundColor', 'w',... + 'string','auto','callback',@CallBack_setTRangeEdit); +handles.setTRangeText = uicontrol(handles.figure,'style','text',... + 'unit','normalized','position',setTRangeTextPosition,... + 'string','colorbar max'); +handles.hideControlPush = uicontrol(handles.figure, 'style', 'push',... + 'unit','normalized',... + 'String', '<', 'position', hideControlPushPosition,... + 'visible','off'); +handles.reportPush = uicontrol(handles.figure, 'style', 'push',... + 'unit','normalized',... + 'String', 'report', ... + 'position', reportPushPosition,... + 'callback', @CallBack_reportPush); +handles.volumePush = uicontrol(handles.figure, 'style', 'push',... + 'unit','normalized',... + 'String', 'volume', ... + 'position', volumePushPosition,... + 'callback', @CallBack_volumePush); +handles.commonRegionPush = uicontrol(handles.figure, 'style', 'push',... + 'unit','normalized',... + 'String', 'common region', ... + 'position', commonRegionPushPosition,... + 'callback', @CallBack_commonRegionPush); +% handles.fdrPush = uicontrol(handles.figure, 'style', 'push',... +% 'unit','normalized',... +% 'String', 'FDR', ... +% 'position', fdrPushPosition,... +% 'callback', @CallBack_fdrPush); +handles.reslicePush = uicontrol(handles.figure, 'style', 'push',... + 'unit','normalized',... + 'String', 'reslice', ... + 'visible', 'off', ... + 'position', reslicePushPosition,... + 'callback', @CallBack_reslicePush); + +handles.sliceViewPush = uicontrol(handles.figure, 'style', 'push',... + 'unit','normalized',... + 'String', 'slice view', ... + 'position', sliceViewPushPosition,... + 'callback', @CallBack_sliceViewPush); + +handles.displayPush = uicontrol(handles.figure, 'style', 'push',... + 'unit','normalized',... + 'String', 'display', ... + 'position', displayPushPosition,... + 'callback', @CallBack_displayPush,... + 'visible','off'); +handles.allinonePush = uicontrol(handles.figure, 'style', 'push',... + 'unit','normalized',... + 'String', 'all in one', ... + 'position', allinonePushPosition,... + 'callback', @CallBack_allinonePush,... + 'visible','off'); +handles.searchPush = uicontrol(handles.figure, 'style','push',... + 'unit','normalized','position',searchPushPosition,... + 'String', 'search','callback',@CallBack_searchPush, 'ForeGroundColor',[0 0 1]); +handles.searchContentEdit = uicontrol(handles.figure, 'style','edit',... + 'unit','normalized','position',searchContentEditPosition,... + 'ForeGroundColor',[0 0 1],... + 'BackgroundColor', 'w',... + 'horizontal','left'); +handles.searchText = uicontrol(handles.figure, 'style','text',... + 'unit','normalized','position',searchTextPosition,... + 'string', ' in',... + 'horizontal','left',... + 'visible','off'); +handles.searchEnginePop = uicontrol(... + 'Units','normalized', ... + 'ListboxTop',0, ... + 'Position',searchEnginePopPosition, ... + 'String',{'in xBrain';'in google scholar';'in pubmed';'in wiki'}, ... + 'Style','popupmenu', ... + 'value',1); +handles.overlayPush = uicontrol(handles.figure, 'style','push',... + 'unit','normalized','position',overlayPushPosition,... + 'String', 'overlay','callback',@CallBack_overlayPush); +handles.overlayEdit = uicontrol(handles.figure, 'style','edit',... + 'unit','normalized','position',overlayEditPosition,... + 'BackgroundColor', 'w',... + 'horizontal','left',... + 'callback', @CallBack_overlayEdit); +handles.overlayPop = uicontrol(handles.figure, 'style','popupmenu',... + 'unit','normalized','position',overlayPopPosition,... + 'string', sort(fieldnames(handles.wholeMaskMNIAll)),... + 'horizontal','left',... + 'callback', @CallBack_overlayPop); + +handles.helpPush = uicontrol(handles.figure, 'style','push',... + 'unit','normalized','position',helpPosition,... + 'String', 'help','callback',['web ' XJVIEWURL],'ForeGroundColor',[0 0 1],... + 'horizontal','left', ... + 'visible','off'); +handles.infoTextBox = uicontrol(handles.figure, 'style','edit',... + 'unit','normalized','position',infoTextBoxPosition,... + 'String', 'Welcome to xjView 8','ForeGroundColor','k', 'BackgroundColor', 'w',... + 'horizontal','left', ...'fontname','times',... + 'max',2, 'min',0); +handles.getCurrentPosition = uicontrol(handles.figure, 'style','push',... + 'unit','normalized','position',[0.6 0.4 0.2 0.05],... + 'String', 'get xyz','callback',@CallBack_getCurrentPosition,'ForeGroundColor',[0 0 1],... + 'horizontal','left', ... + 'visible','off'); +handles.getCurrentPosition = uicontrol(handles.figure, 'style','push',... + 'unit','normalized','position',smallVolumePosition,... + 'String', 'Small volume','callback',@CallBack_smallVolume,... + 'horizontal','left'); +try + %urlread([XJVIEWURL '/guestbook/stat.php']); + s = urlread([XJVIEWURL '/toUser.txt']); + set(handles.infoTextBox, 'String', s); +end + +% feed +try + s = urlread(['http://www.alivelearn.net/xjview8/getFeed.php']); + [feeds, links]=strread(s,'%s%s', 'delimiter','\t'); + + handles.feed = uicontrol(handles.figure,'style','listbox',... + 'unit','normalized',... + 'String', feeds, ... + 'userdata',links, ... + 'value',1,... + 'fontsize',12,... + 'position',[0.55 0.05 0.44 0.25]); + handles.refreshclick = uicontrol(handles.figure,'style','push',... + 'unit','normalized',... + 'String', 'Refresh', ... + 'position',[0.55 0.02 0.1 0.03],... + 'fontsize',12, ... + 'callback',@refreshFeeds); + handles.feedclick = uicontrol(handles.figure,'style','push',... + 'unit','normalized',... + 'String', 'Read', ... + 'position',[0.88 0.02 0.1 0.03],... + 'fontsize',12, ... + 'callback',@openURL); + handles.postfeedclick = uicontrol(handles.figure,'style','push',... + 'unit','normalized',... + 'String', 'Post', ... + 'position',[0.65 0.02 0.05 0.03],... + 'fontsize',12, ... + 'callback','web(''http://www.alivelearn.net/xjview8/feed.php'')'); +end + +handles.glassViewAxes = axes('unit','normalized','position',glassViewAxesPosition,'XTick',[],'YTick',[],'visible','off'); + + +handles.testEdit = uicontrol(handles.figure,'style','edit',... + 'unit','normalized','position',[0.1 0.4 0.2 0.05],... + 'horizontal','left',... + 'callback',@test,... + 'visible','off'); + +% menu +cSHH = get(0,'ShowHiddenHandles'); +set(0,'ShowHiddenHandles','on') +hMenuFile = findobj(get(handles.figure,'Children'),'flat','Label','&File'); +if ~isempty(hMenuFile) + hMenuFileOpen = findobj(get(handles.figure,'Children'),'Label','&Open...'); + set(hMenuFileOpen, 'label', 'Open Figure...'); + hMenuFileSave = findobj(get(handles.figure,'Children'),'Label','&Save'); + set(hMenuFileSave, 'label', 'Save Figure ...'); + hMenuFileSaveAs = findobj(get(handles.figure,'Children'),'Label','Save &As...'); + set(hMenuFileSaveAs, 'label', 'Save Figure As ...'); +else + hMenuFile = uimenu(handles.figure, 'label', '&File'); +end + +set(hMenuFile,'ForegroundColor',[0 0 1]); +set(findobj(hMenuFile,'Position',1),'Separator','on'); +uimenu('Parent',hMenuFile,'Position',1,'ForegroundColor',[0 0 1],... + 'Label','Open Images (*.img) ...',... + 'CallBack',@CallBack_loadImagePush, 'Accelerator', 'o'); +uimenu('Parent',hMenuFile,'Position',2,'ForegroundColor',[0 0 1],... + 'Label','Save Current Image (*.img) ...',... + 'CallBack',{@CallBack_saveImagePush, '', 0}); +uimenu('Parent',hMenuFile,'Position',3,'ForegroundColor',[0 0 1],... + 'Label','Save Current Image as Mask (*.img) ...',... + 'CallBack',{@CallBack_saveImagePush, '', 1}); +% uimenu('Parent',hMenuFile,'Position',4,'ForegroundColor',[0 0 1],... +% 'Label','Save Result (*.ps/pdf) ...',... +% 'CallBack',@CallBack_saveResultPSPush); + +hMenuHelp = findobj(get(handles.figure,'Children'),'flat','Label','&Help'); +if isempty(hMenuHelp) + hMenuHelp = uimenu(handles.figure, 'label', 'xjView &Help'); +end +set(hMenuHelp,'ForegroundColor',[0 0 1]); +uimenu('Parent',hMenuHelp,'Position',1,'ForegroundColor',[0 0 1],... + 'Label','xBrain.org: brain mapping database',... + 'CallBack','web http://www.xbrain.org'); +uimenu('Parent',hMenuHelp,'Position',2,... + 'Label','xjview help','ForegroundColor',[0 0 1],... + 'CallBack',['web ' XJVIEWURL]); +set(findobj(hMenuHelp,'Position',3),'Separator','on'); +set(0,'ShowHiddenHandles',cSHH) + +if exist('cuixuBOLDretrieve') + hMenuAnalyze = uimenu('label','&Analyze','ForegroundColor',[0 0 1],'visible','on'); + hMenuPreprocess = uimenu(hMenuAnalyze,'label','Preprocess CIBSR','ForegroundColor',[0 0 1],'callback', @CallBack_preprocess_cibsr); + hMenuPreprocess = uimenu(hMenuAnalyze,'label','Preprocess','ForegroundColor',[0 0 1],'callback', @CallBack_preprocess); + hMenuProcess = uimenu(hMenuAnalyze,'label','Process (GLM estimation)','ForegroundColor',[0 0 1],'callback',@CallBack_process); + hMenuSPMProcess = uimenu(hMenuAnalyze,'label','SPMProcess (GLM using SPM)','ForegroundColor',[0 0 1],'callback',@CallBack_SPMProcess); + hMenuGLMPeak = uimenu(hMenuAnalyze,'label','GLM on peak BOLD','ForegroundColor',[0 0 1],'callback',@CallBack_GLMPeak); + hMenuContrast = uimenu(hMenuAnalyze,'label','Contrast','ForegroundColor',[0 0 1],'callback',@CallBack_contrast); + hMenuFDR = uimenu(hMenuAnalyze,'label','FDR','ForegroundColor',[0 0 1],'callback',@CallBack_fdr); + hMenuROI = uimenu(hMenuAnalyze,'label','ROI: retrieve signal','ForegroundColor',[0 0 1],'callback',@CallBack_timeSeries); + hMenuROIPlot = uimenu(hMenuAnalyze,'label','ROI: plot','ForegroundColor',[0 0 1],'callback',@CallBack_plotROI, 'Accelerator', 'm'); + hMenuROIIndividualPlot = uimenu(hMenuAnalyze,'label','ROI: individual plot','ForegroundColor',[0 0 1],'callback',@CallBack_plotIndividualROI); + hMenuROIIndividualPlotWithBehavior = uimenu(hMenuAnalyze,'label','ROI: individual plot with behavior','ForegroundColor',[0 0 1],'callback',@CallBack_plotIndividualROIWithBehavior); + hMenuROICorrelationPlot = uimenu(hMenuAnalyze,'label','ROI: correlation plot','ForegroundColor',[0 0 1],'callback',@CallBack_plotCorrelationROI); + %hMenuWholeBrainCorrelation = uimenu(hMenuAnalyze,'label','Whole brain correlation','ForegroundColor',[0 0 1],'callback',@CallBack_wholeBrainCorrelation); + hMenuBehaviorAnalysis = uimenu(hMenuAnalyze,'label','Behavior analsyis','ForegroundColor',[0 0 1],'callback',@CallBack_behaviorAnalysis); + hMenuHeadMovementAnalysis = uimenu(hMenuAnalyze,'label','Other analysis (head motion, gsr, physio etc)','ForegroundColor',[0 0 1],'callback',@CallBack_headMovementAnalysis); + hMenuModelComparison = uimenu(hMenuAnalyze,'label','Linear model comparison','ForegroundColor',[0 0 1],'callback',@CallBack_modelComparison); + + hMenuHNLOnly = uimenu('label','For H&NL Only','ForegroundColor',[0 0 1],'visible','on'); + hMenuNew2Old = uimenu(hMenuHNLOnly,'label','Format convert','ForegroundColor',[0 0 1],'callback',@CallBack_new2old); + hMenuPreprocessCluster = uimenu(hMenuHNLOnly,'label','Preprocess (on cluster)','ForegroundColor',[0 0 1],'callback',{@CallBack_preprocess, 'cluster'}); + hMenuProcessCluster = uimenu(hMenuHNLOnly,'label','Process (GLM, on cluster)','ForegroundColor',[0 0 1],'callback',{@CallBack_process, 'cluster'}); + hMenuSPMProcessCluster = uimenu(hMenuHNLOnly,'label','SPM Process (GLM using SPM, on cluster)','ForegroundColor',[0 0 1],'callback',{@CallBack_SPMProcess, 'cluster'}); + hMenuGLMPeakCluster = uimenu(hMenuHNLOnly,'label','GLM on peak BOLD (on cluster)','ForegroundColor',[0 0 1],'callback',{@CallBack_GLMPeak, 'cluster'}); + + hMenuNIRS = uimenu('label','&NIRS','ForegroundColor',[0 0 1],'visible','on'); + hMenuNIRS_SPM = uimenu(hMenuNIRS,'label','NIRS-SPM','ForegroundColor',[0 0 1],'callback', @CallBack_nirs_spm); + hMenuNIRS_TOPO = uimenu(hMenuNIRS,'label','xTOPO','ForegroundColor',[0 0 1],'callback', @CallBack_nirs_topo); + hMenuNIRS_nirs = uimenu(hMenuNIRS,'label','nirs','ForegroundColor',[0 0 1],'callback', @CallBack_nirs); + + hMenuOther = uimenu('label','&Other','ForegroundColor',[0 0 1],'visible','on'); + hMenuOther_signal = uimenu(hMenuOther,'label','Signal processing toolbox','ForegroundColor',[0 0 1],'callback', @CallBack_signal_processing); + hMenuOther_ge2sn = uimenu(hMenuOther,'label','ge2sn','ForegroundColor',[0 0 1],'callback', @CallBack_ge2sn); +end + +figurecm = uicontextmenu; +uimenu(figurecm,'label','Red','callback','set(gcf,''color'',''r'')'); +uimenu(figurecm,'label','White','callback','set(gcf,''color'',''w'')'); +uimenu(figurecm,'label','Gray','callback','set(gcf,''color'',[0.925, 0.914, 0.847])'); +%set(handles.figure,'uicontextmenu',figurecm); +set(handles.figure,'WindowButtonDownFcn',@figureMouseUpFcn); +set(handles.figure,'KeyPressFcn',@KeyPressFcn); + +set(handles.figure,'visible','on'); + +% save pre-set values +handles.system = os; +handles.spmdir = spmdir; +handles.screenResolution = screenResolution; +handles.pValue = pValue; +handles.intensityThreshold = intensityThreshold; +handles.clusterSizeThreshold = clusterSizeThreshold; +handles.sectionViewPosition = sectionViewPosition; +handles.sectionViewTargetFile = getSectionViewTargetFile(spmdir, 'avg152T1'); + +guidata(f, handles); + +% global variables for rotation matrix M and dimension +global M_; +global DIM_; +global TR_; +global LEFTRIGHTFLIP_; +global TMAX_; % colorbar max to display in section view +global XJVIEWURL_; + +M_ = M; +DIM_ = DIM; +TR_ = TR; +LEFTRIGHTFLIP_ = leftrightflip; +TMAX_ = 'auto'; +XJVIEWURL_ = XJVIEWURL; + +% check input arguments +if length(varargin) == 0 + []; +elseif isstr(varargin{1}) + CallBack_loadImagePush(handles.loadImagePush, [], varargin); +else + mniCoord = varargin{1}; + if length(varargin) < 2 + intensity = ones(size(mniCoord,1),1); + else + intensity = varargin{2}; + end + thisStruct.mni = mniCoord; + thisStruct.intensity = intensity; + thisStruct.M = M; + thisStruct.DIM = DIM'; + CallBack_loadImagePush(handles.loadImagePush, [], thisStruct); +end + + + +function test(hObject, eventdata) +handles = guidata(gcbo); +set(hObject, 'String', num2str(handles.pValue)); +vars = evalin('base','who'); +vars +x = evalin('base',vars{1}); +x +handles.DIM + +function CallBack_getCurrentPosition(hObject, eventdata) +handles = guidata(hObject); +disp(handles.currentxyz) + + +function KeyPressFcn(src,evt) +handles = guidata(src); +m = handles.M{1}; +m = abs(diag(m)); +if(strcmp(evt.Key, 'leftarrow')) + xyz = spm_XYZreg('GetCoords',handles.hReg); + xyz(1) = xyz(1)-m(1); + spm_XYZreg('SetCoords', xyz, handles.hReg); +elseif(strcmp(evt.Key, 'rightarrow')) + xyz = spm_XYZreg('GetCoords',handles.hReg); + xyz(1) = xyz(1)+m(1); + spm_XYZreg('SetCoords', xyz, handles.hReg); +elseif(strcmp(evt.Key, 'pageup')) + xyz = spm_XYZreg('GetCoords',handles.hReg); + xyz(2) = xyz(2)+m(2); + spm_XYZreg('SetCoords', xyz, handles.hReg); +elseif(strcmp(evt.Key, 'pagedown')) + xyz = spm_XYZreg('GetCoords',handles.hReg); + xyz(2) = xyz(2)-m(2); + spm_XYZreg('SetCoords', xyz, handles.hReg); +elseif(strcmp(evt.Key, 'uparrow')) + xyz = spm_XYZreg('GetCoords',handles.hReg); + xyz(3) = xyz(3)+m(3); + spm_XYZreg('SetCoords', xyz, handles.hReg); +elseif(strcmp(evt.Key, 'downarrow')) + xyz = spm_XYZreg('GetCoords',handles.hReg); + xyz(3) = xyz(3)-m(3); + spm_XYZreg('SetCoords', xyz, handles.hReg); +end + + +function refreshFeeds(hObject, eventdata) +handles = guidata(hObject); +s = urlread(['http://www.alivelearn.net/xjview8/getFeed.php']); +[feeds, links]=strread(s,'%s%s', 'delimiter','\t'); +set(handles.feed, 'String', feeds); +set(handles.feed, 'userdata', links); + + +function openURL(hObject, eventdata) +handles = guidata(hObject); +value = get(handles.feed,'value'); +urls = get(handles.feed,'userdata'); +if(~isempty(urls{value})) + web(urls{value}); +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% NIRS-SPM +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_nirs_spm(hObject, eventdata) +try + nirs_spm; +catch + disp('Please add NIRS-SPM path.'); + return +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% NIRS-TOPO +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_nirs_topo(hObject, eventdata) +try + warndlg('You need to run xTopo in command line. Type topo and press enter.'); +catch + disp('Please add NIRS-SPM path.'); + return +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% NIRS +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_nirs(hObject, eventdata) +try + nirs; +catch + disp('Please add NIRS-SPM path.'); + return +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% NIRS +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_signal_processing(hObject, eventdata) +try + signalPreprocess; +catch + disp('Please add NIRS-SPM path.'); + return +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% NIRS +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_ge2sn(hObject, eventdata) +try + xge2sn; +catch + disp('Please add NIRS-SPM path.'); + return +end + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% FDR edit +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_fdrEdit(hObject, eventdata) +handles = guidata(hObject); +set(handles.infoTextBox, 'string', {'Right now FDR only works for T-test image.'}); +if length(handles.imageFileName) > 1 + set(handles.infoTextBox, 'string', {'FDR can only work for a single image file. You opened multiple files.'}); + return +end + +q = get(handles.fdrEdit,'String'); +q = str2num(q); +if get(handles.allIntensityRadio, 'Value') + set(handles.infoTextBox, 'string', {'FDR only works on positive or negative direction, not both.'}); + return; +elseif get(handles.positiveIntensityRadio, 'Value') + positive = 1; +elseif get(handles.negativeIntensityRadio, 'Value') + positive = -1; +end + +VspmSv = spm_vol(handles.imageFileName{1}); +Ts = spm_read_vols(VspmSv); +Ts(Ts==0) = []; +Ts(isnan(Ts)) = []; +Ts = positive*Ts; +Ts = sort(Ts(:)); +if handles.TF{1} ~= 'P', Ts = flipud(Ts); end + +ps = t2p(Ts, handles.df{1}, handles.TF{1}); + +intensity = spm_uc_FDR(q, [1 handles.df{1}],handles.TF{1},1,ps,0); + +pvalue = t2p(intensity, handles.df{1}, handles.TF{1}); +set(handles.pValueEdit,'string',pvalue); +CallBack_pValueEdit(handles.pValueEdit, eventdata); +%pause(1) +%CallBack_pValueEdit(handles.pValueEdit, eventdata); + +% set(handles.intensityThresholdEdit,'string',intensity); +% pause(2) +% CallBack_intensityThresholdEdit(handles.intensityThresholdEdit, +% eventdata); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% FDR push button +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_fdrPush(hObject, eventdata) +handles = guidata(hObject); +set(handles.infoTextBox, 'string', {'Right now FDR only works for T-test image.'}); +if length(handles.imageFileName) > 1 + set(handles.infoTextBox, 'string', {'I can only work for a single image file. You opened multiple files.'}); + return +end + +q = get(handles.pValueEdit,'String'); +q = str2num(q); +if get(handles.allIntensityRadio, 'Value') + set(handles.infoTextBox, 'string', {'FDR only works on positive or negative direction, not both.'}); + return; +elseif get(handles.positiveIntensityRadio, 'Value') + positive = 1; +elseif get(handles.negativeIntensityRadio, 'Value') + positive = -1; +end + +VspmSv = spm_vol(handles.imageFileName{1}); +Ts = spm_read_vols(VspmSv); +Ts(Ts==0) = []; +Ts(isnan(Ts)) = []; +Ts = positive*Ts; +Ts = sort(Ts(:)); +if handles.TF{1} ~= 'P', Ts = flipud(Ts); end + +ps = t2p(Ts, handles.df{1}, handles.TF{1}); + +intensity = spm_uc_FDR(q, [1 handles.df{1}],handles.TF{1},1,ps,0); + +pvalue = t2p(intensity, handles.df{1}, handles.TF{1}); +set(handles.pValueEdit,'string',pvalue); +%CallBack_pValueEdit(handles.pValueEdit, eventdata); +pause(1) +%CallBack_pValueEdit(handles.pValueEdit, eventdata); + +% set(handles.intensityThresholdEdit,'string',intensity); +% pause(2) +% CallBack_intensityThresholdEdit(handles.intensityThresholdEdit, eventdata); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% FDR +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_fdr(hObject, eventdata) +handles = guidata(hObject); +set(handles.infoTextBox, 'string', {'Right now FDR only works for T-test image.'}); +if length(handles.imageFileName) > 1 + set(handles.infoTextBox, 'string', {'I can only work for a single image file. You opened multiple files.'}); + return +end + +q = get(handles.pValueEdit,'String'); +q = str2num(q); +if get(handles.allIntensityRadio, 'Value') + positive = 1; +elseif get(handles.positiveIntensityRadio, 'Value') + positive = 1; +elseif get(handles.negativeIntensityRadio, 'Value') + positive = -1; +end +xjviewpath = fileparts(which('xjview')); +maskImageFile = fullfile(xjviewpath, 'mask.img'); +[threshold, pvalue] = fdr(handles.imageFileName{1}, q, positive, maskImageFile); +set(handles.pValueEdit,'string',pvalue); +CallBack_pValueEdit(handles.pValueEdit, eventdata); + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% model comparison +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_modelComparison(hObject, eventdata) +answer = getVariable({'y','x (full model)', 'x (reduced model)'}); + +if isempty(answer) + return; +end + +if ~isempty(answer{1}) + y = evalin('base',answer{1}); +else + return; +end +if ~isempty(answer{2}) + xf = evalin('base',answer{2}); % full model +else + return; +end +if ~isempty(answer{3}) + xr = evalin('base',answer{3}); % reduced model +else + xr = []; +end + +% make vector column vector +[r, c] = size(y); +if r==1; y = y'; end +[r, c] = size(xf); +if r mn + hmmin = mn; + end + if hmmax < mx + hmmax = mx; + end + end + end + + tmp = load(deblank(Pe(ii,:))); + tmp2 = fieldnames(tmp); + hm = eval(['tmp.' tmp2{1}]); + hmcell = struct2cell(hm); + hmname = fieldnames(hm); + for jj=1:length(hmname) + toplot1 = []; + toplot2 = []; + for mm = 1:length(hmcell{jj}) + toplot1 = [toplot1 hmcell{jj}(mm) hmcell{jj}(mm) nan]; + toplot2 = [toplot2 hmmin hmmax nan]; + end + plot(toplot1, toplot2, 'color', colors(jj,:)); + %ylim([0 4]) + hold on + end + legend(hmname) + title(['subject ' num2str(ii)]) + xlabel('time in s'); + + if ~isempty(P) % if there is head movement data, gsr data, eye tracking data, physio data etc + for jj=1:length(Ohmname) + plot(TRs(jj)*[0:length(Ohmcell{jj})-1],Ohmcell{jj},'color', colors(jj,:)); + hold on + end + legend([hmname; Ohmname]) + end + + %% prepare interval data for later plot + alltimes{ii} = []; + for jj=1:length(hmname) + alltimes{ii} = union(alltimes{ii}, hmcell{jj}); + end + + subplot(4,2,2*kk) + for jj=1:length(hmname) + roundhmcell{jj} = round(hmcell{jj}*1); + signal = zeros(1, max(roundhmcell{jj})+1); + signal(roundhmcell{jj}+1) = 1; + signal = signal - mean(signal); + y = fft(signal); + plotlength = round(length(y))/1; + plot([1:plotlength]/plotlength, abs(y(1:plotlength)).^2, 'color', colors(jj,:)); + hold on + end + legend(hmname) + xlabel('frequency'); + ylabel('fft power'); + title(['subject ' num2str(ii)]) + + if mod(ii,4) == 0 && ii~=size(Pe, 1) + figure; + Maximize(gcf); + end + end + + %% now I plot correlogram of intervals + figure; + kk = 1; + for ii=whichtoplot + alltimes{ii} = sort(alltimes{ii}); + intervals = diff(alltimes{ii}); + corrlag(intervals,intervals,[max(-10, -length(intervals)+2) : min(10, length(intervals)-2)], colors(ii,:)); + hold on; + legendname{kk} = ['sub ' num2str(ii)]; + kk = kk+1; + end + legend(legendname) + xlabel('lag'); + ylabel('correlation coefficient'); + title('correlogram of intervals') +end + +%% correlator + +if ~isempty(Pc) + + tmp = load(deblank(Pc(1,:))); + tmp2 = fieldnames(tmp); + hm = eval(['tmp.' tmp2{1}]); + + C = hm; + cenames = fields(C); + for jj=1:length(cenames) + eval(['correlatornames{jj} = fields(C.' cenames{jj} ');']); + for kk=1:length(correlatornames{jj}) + eval(['C.' cenames{jj} '.' correlatornames{jj}{kk} '= [];']); + end + end + + %list all plot names in command window for selection + promt{1} = 'Please select which two correlators do you want to plot. The first one will be x and the second will be y. The two have to be of the same length.'; + promt{2} = 'index event correlator'; + count = 1; + for jj=1:length(cenames) + eval(['correlatornames{jj} = fields(C.' cenames{jj} ');']); + for kk=1:length(correlatornames{jj}) + promt{count+2} = sprintf('%d %s %s', count, cenames{jj}, correlatornames{jj}{kk}); + count = count + 1; + end + end + promt{count+2} = ''; + + if count == 1 + return + end + promt = char(promt); + name='Please select which two correlator do you want to plot. The first one will be x and the second will be y.'; + numlines=1; + if count == 1 + defaultanswer={['You don''t have more than one correlators. Click Cancel']}; + else + defaultanswer={['[1 ' num2str(count-1) ']']}; + end + + whichplot=inputdlg(promt,name,numlines,defaultanswer); + if isempty(whichplot) + return; + end + whichplot = eval(whichplot{1}); + + figure; + Maximize(gcf); + for ii=whichtoplot + tmp = load(deblank(Pc(ii,:))); + tmp2 = fieldnames(tmp); + hm = eval(['tmp.' tmp2{1}]); + count = 1; + for jj=1:length(cenames) + eval(['correlatornames{jj} = fields(C.' cenames{jj} ');']); + for kk=1:length(correlatornames{jj}) + tmp2 = eval(['hm.' cenames{jj} '.' correlatornames{jj}{kk} ';']); + if size(tmp2,1) == 1 + tmp2 = tmp2'; + end + eval(['C.' cenames{jj} '.' correlatornames{jj}{kk} '= [C.' cenames{jj} '.' correlatornames{jj}{kk} '; tmp2];']); + + if whichplot(1) == count + x = tmp2; + xlabeltext = [correlatornames{jj}{kk} ' at ' cenames{jj}]; + end + if whichplot(2) == count + y = tmp2; + ylabeltext = [correlatornames{jj}{kk} ' at ' cenames{jj}]; + end + count = count + 1; + end + end + + kk = mod(ii,16); + if kk == 0 + kk = 16; + end + subplot(4,4,kk) + + plot2(x,y, 'b', 1); + xlabel(xlabeltext) + ylabel(ylabeltext) + title(['subject ' num2str(ii)]) + try + % convert to colum vector + if size(x,1)==1 + x = x'; + end + if size(y,1)==1 + y = y'; + end + + % remove NaN + tmpposx = find(isnan(x) | isinf(x)); + tmpposy = find(isnan(y) | isinf(y)); + x([tmpposx; tmpposy]) = []; + y([tmpposx; tmpposy]) = []; + + b = linearregression(y,x); + + totalvar = var(y); + residvar = var(y - x*b(1) - b(2)); + modelvar = totalvar - residvar; + dfm = 1; + dft = length(x) - 1; + dfr = dft - dfm; + mmodelvar = modelvar/dfm; + mresidvar = residvar/dfr; + F = mmodelvar/mresidvar; + pvalue = 1-spm_Fcdf(F, [dfm dfr]); + + title(sprintf('sub %d, pValue=%g', ii, pvalue)) + xx=[min(x):(max(x)-min(x))/10:max(x)]; + yy=b(1) * xx + b(2); + hold on; + plot(xx,yy,'g'); + end + + if mod(ii,16) == 0 && ii~=size(Pc, 1) + figure; + Maximize(gcf); + end + end + + % plot the average + count = 1; + for jj=1:length(cenames) + eval(['correlatornames{jj} = fields(C.' cenames{jj} ');']); + for kk=1:length(correlatornames{jj}) + if whichplot(1) == count + eval(['x = C.' cenames{jj} '.' correlatornames{jj}{kk} ';']); + xlabeltext = [correlatornames{jj}{kk} ' at ' cenames{jj}]; + end + if whichplot(2) == count + eval(['y = C.' cenames{jj} '.' correlatornames{jj}{kk} ';']); + ylabeltext = [correlatornames{jj}{kk} ' at ' cenames{jj}]; + end + count = count + 1; + end + end + + figure + plot2(x,y, 'b', 1); + xlabel(xlabeltext) + ylabel(ylabeltext) + try + % convert to colum vector + if size(x,1)==1 + x = x'; + end + if size(y,1)==1 + y = y'; + end + + % remove NaN + tmpposx = find(isnan(x) | isinf(x)); + tmpposy = find(isnan(y) | isinf(y)); + x([tmpposx; tmpposy]) = []; + y([tmpposx; tmpposy]) = []; + + b = linearregression(y,x); + + totalvar = var(y); + residvar = var(y - x*b(1) - b(2)); + modelvar = totalvar - residvar; + dfm = 1; + dft = length(x) - 1; + dfr = dft - dfm; + mmodelvar = modelvar/dfm; + mresidvar = residvar/dfr; + F = mmodelvar/mresidvar; + pvalue = 1-spm_Fcdf(F, [dfm dfr]); + + title(sprintf('pValue=%g; slope=%g; constant=%g', pvalue, b(1), b(2))) + xx=[min(x):(max(x)-min(x))/10:max(x)]; + yy=b(1) * xx + b(2); + hold on; + plot(xx,yy,'g'); + end +end + +return + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% head motion, physio, gsr, eyetracking etc analysis +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_headMovementAnalysis(hObject, eventdata) + +global TR_; +handles = guidata(hObject); + +answer = getVariable({'head motion, gsr, physio, eye tracking data', 'Event time data', 'Correlator (optional)'}); + +if isempty(answer) + return; +end +if isempty(answer{1}) + return; +end + +[filename, pathname] = uiputfile('*.mat', 'Save result to', ''); +if isequal(filename,0) | isequal(pathname,0) + return +else + thisfilename = fullfile(pathname, filename); +end + + +P = evalin('base',answer{1}); % data +try + E = evalin('base',answer{2}); % event +catch + E = []; +end +try + C = evalin('base',answer{3}); % correlator +catch + C = []; +end + +% read what data in there from the 1st dataset +tmp = load(deblank(P(1,:))); +tmp1 = fields(tmp); +eval(['M = tmp.' tmp1{1} ';']); +dataNames = fields(M); + +% prompt for TR (s) +promt = dataNames; +defaultanswer = repmat({num2str(TR_)}, 1, length(dataNames)); +name='Sampling interval (TR) in seconds'; +options.Resize = 'on'; +TRs=inputdlg(promt,name,1,defaultanswer, options); +if isempty(TRs); + return; +end +TRs = str2double(TRs); + +% % prompt for Moving Average Detrend window size (s) +% promt = dataNames; +% defaultanswer = repmat({num2str(40)}, 1, length(dataNames)); +% name='Moving average detrending window size in seconds'; +% options.Resize = 'on'; +% MWs=inputdlg(promt,name,1,defaultanswer, options); +% if isempty(MWs); +% return; +% end +% MWs = str2double(MWs); +% MWs = MWs./TRs; + +h = waitbar(0,'Please wait...'); + +for nn=1:length(dataNames) + + for ii=1:size(P,1) + tmp = load(deblank(P(ii,:))); + tmp1 = fields(tmp); + eval(['Ms = tmp.' tmp1{1} ';']); + eval(['M = Ms.' dataNames{nn} ';']); + if isnumeric(M) + M = double(M); + end + if ~isempty(E) + tmp = load(deblank(E(ii,:))); + tmp1 = fields(tmp); + eval(['event{ii} = tmp.' tmp1{1} ';']); + if ~isstruct(event{ii}) + error('I need you specify the event in a structure, not a cell array!'); + return + end + else + event = {}; + end + + [eventResponse{ii}, time{ii}, wholeOriginal{ii}] = cuixuSignalRetrieve(M, event{ii}, 50, -20, 0, TRs(nn)); + + if ~isempty(C) + tmp = load(deblank(C(ii,:))); + tmp1 = fields(tmp); + eval(['correlator{ii} = tmp.' tmp1{1} ';']); + if ~isstruct(correlator{ii}) + error('I need you specify the correlator in a structure, not a cell array!'); + return + end + + names = fields(correlator{ii}); + for kk=1:length(names) + eval(['names2 = fields(correlator{ii}.' names{kk} ');']); + for mm=1:length(names2) + eval(['correlator{ii}.' names{kk} '.' names2{mm} ' = correlator{ii}.' names{kk} '.' names2{mm} '(remainedEvent{ii}.' names{kk} ');']);; + end + end + end + waitbar(ii/size(P,1),h,[dataNames{nn} ': finished ' num2str(ii) ' of ' num2str(size(P,1))]); + disp(['finished ' num2str(ii) ' of ' num2str(size(P,1))]); + end + + + if ~exist('correlator') + correlator = {}; + end + + TR = TRs(nn); + [a,b,c] = fileparts(thisfilename); + save([fullfile(a,b) '_' dataNames{nn}], 'eventResponse', 'time', 'wholeOriginal', 'correlator', 'event', 'P', 'TR'); +end +close(h) + +%CallBack_plotROI(hObject, eventdata, thisfilename); + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% contrast +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_contrast(hObject, eventdata) + +answer = getVariable({'beta files', 'contrast vector'}); + +if isempty(answer) + return; +end +if isempty(answer{1}) | isempty(answer{2}) + return; +end + +[filename, pathname] = uiputfile('*.img', 'Save t-test image as', ''); +if isequal(filename,0) | isequal(pathname,0) + return +else + thisfilename = fullfile(pathname, filename); +end + + +P1 = evalin('base',answer{1}); % beta image files +c = evalin('base',answer{2}); % contrast vector + +if iscell(P1) + []; +else + disp('format wrong'); + return; +end + + +h = waitbar(0,'Please wait...'); +V=spm_vol(deblank(P1{1}(1,:))); +M1={spm_read_vols(V)}; +contr = zeros(size(M1{1},1), size(M1{1},2), size(M1{1},3), size(P1{1},1)); + +for ii=1:size(P1{1},1) + for jj=1:length(P1) + if c(jj)~=0 + V=spm_vol(deblank(P1{jj}(ii,:))); + M1{jj}=spm_read_vols(V); + contr(:,:,:,ii) = contr(:,:,:,ii)+c(jj)*double(M1{jj}); + end + end + + waitbar(ii/size(P1{1},1),h,['finished ' num2str(ii) ' of ' num2str(size(P1{1},1))]); +end +close(h); + + +df = size(P1{1},1)-1; + +T = squeeze(t(permute(contr,[4 1 2 3]))); % return a 41*48*35 matrix +xjviewpath = fileparts(which('xjview')); +V=spm_vol(fullfile(xjviewpath, 'mask.img')); +m=spm_read_vols(V); +T = T.*m; + +targetfilename = thisfilename; +global M_; +global DIM_; +V.mat = M_; +V.dim = [DIM_(1) DIM_(2) DIM_(3) 16]; +V.fname = targetfilename; +V.descrip = ['SPM{T_[' num2str(df) '.0]} ' num2str(c)]; +V = spm_write_vol(V,T); + +handles = guidata(hObject); +[tmp,tmp,ext] = fileparts(thisfilename); +if isempty(ext) + ext = '.img'; +elseif isequal(lower(ext), '.img') + ext = ''; +end +CallBack_loadImagePush(handles.loadImagePush, [], {[thisfilename ext]}); + +return; + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% SPM process (use SPM processing) +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_SPMProcess(hObject, eventdata, singleOrCluster) + +answer = getVariable({'Image files', 'Event time data', 'Modulator (optional)','Other (head motion, gsr, physio, eye track, etc, optional)'}); + +if isempty(answer) + return; +end +if isempty(answer{1}) | isempty(answer{2}) + return; +end + +P = evalin('base',answer{1}); % subject directories +E = evalin('base',answer{2}); % event time +try + Modulator = evalin('base',answer{3}); % modulator +catch + Modulator = []; +end +try + headmovement = evalin('base',answer{4}); % other regressor such as headmovement +catch + headmovement = []; +end + +if nargin < 3 + singleOrCluster = 'single'; +end + + +prompt='I will create a folder under each subject''s directory and put the SPM output files (including beta images, SPM.mat etc) for that subject into that folder. Please give a name to the folder (example: FixedEffect). No space or strange characters allowed.'; +name='Give a name to SPM output folder'; +numlines=1; +defaultanswer={'FixedEffect'}; + +wheretosave=inputdlg(prompt,name,numlines,defaultanswer); + +if isempty(wheretosave) + return +else + wheretosave = wheretosave{1}; +end + +currentdir = pwd; + +if strcmp(singleOrCluster, 'cluster') + handles = guidata(hObject); + set(handles.infoTextBox, 'string', {'This will do SPM GLM estimation using cluster.', 'Check out xjviewtmp directory'}); + + mkdir('xjviewtmp'); + cd('xjviewtmp'); + system('rm *'); + + fid = fopen('processALL.pbs','wt'); + fprintf(fid,'#!/bin/bash\n\n'); + fprintf(fid,'for ((s=1;s<=%d;s++))\n', size(P,1)); + fprintf(fid,'do\n\tsleep 3.5\n\tqsub %s/process.pbs -v "s=$s" -N SPM_$s\ndone\n', pwd); + fclose(fid); + + save P P; + save E E; + save Modulator Modulator; + save headmovement headmovement; + fid = fopen('process.pbs','wt'); + fprintf(fid,'#!/bin/bash\n\n'); + fprintf(fid,'#PBS -l nodes=1:ppn=1,walltime=04:00:00\n'); + fprintf(fid,'#PBS -N process\n'); + fprintf(fid,'#PBS -e %s\n', pwd); + fprintf(fid,'#PBS -o %s\n\n', pwd); + fprintf(fid,'sleep 3.5\n\n'); + fprintf(fid,'PATH=/usr/local/bin:$PATH\n\n'); + fprintf(fid,'matlab -nojvm -nodisplay \\\n'); + fprintf(fid,'-logfile "%s/process.$s.$PBS_JOBID.log" \\\n', pwd); + + + fprintf(fid,['-r "addpath ~/gang/xjview;'... + 'wheretosave = ''%s'';'... + 'cd %s; '... + 'load P; '... + 'load E; '... + 'load Modulator;'... + 'load headmovement;'... + 'tmp=load(deblank(P($s,:)));'... + 'fd=fields(tmp);'... + 'eval([''Pi=tmp.'' fd{1} '';'']);'... + 'N = size(Pi,1);'... + 'tmp=load(deblank(E($s,:))); '... + 'fd=fields(tmp); '... + 'eval([''EE=tmp.'' fd{1} '';'']); '... + 'thisModulator = [];'... + 'if ~isempty(Modulator);'... + 'tmp = load(deblank(Modulator($s,:))); '... + 'fd=fields(tmp); '... + 'eval([''thisModulator=tmp.'' fd{1} '';'']);'... + 'end;'... + '[otherregressor{1}.C, otherregressor{1}.name]=struct2matrix(N, EE, thisModulator);'... + 'if ~isempty(headmovement);'... + 'tmp = load(deblank(headmovement($s,:))); '... + 'fd=fields(tmp); '... + 'eval([''thisheadmovement=tmp.'' fd{1} '';'']);'... + 'tmphd = struct2cell(thisheadmovement);'... + 'tmpname = fieldnames(thisheadmovement);'... + 'for kk=1:length(tmphd);'... + '[r,c] = size(tmphd{kk});'... + 'if r==1; tmphd{kk} = tmphd{kk}''; end;'... + 'otherregressor{1}.C = [otherregressor{1}.C tmphd{kk}];'... + 'otherregressor{1}.name = [otherregressor{1}.name {tmpname{kk}}];'... + 'end;'... + 'end;'... + 'subDir = fileparts(deblank(Pi(1,:))); '... + 'for kk=1:length(subDir); '... + 'if subDir(end-kk+1)==filesep;'... + 'pos=kk;'... + 'break;'... + 'end;'... + 'end; '... + 'subDir(end-pos+1:end)=[]; '... + 'cd(subDir); '... + 'try;' ... + 'rmdir(wheretosave, ''s'');'... + 'end;'... + 'mkdir(wheretosave);'... + 'cd(wheretosave);'... + 'spm_defaults;'... + 'disp(subDir);'... + 'cuixuprocess(N, [0], {}, {}, {}, [], otherregressor, Pi, 16);'... + 'disp(''done!'');'... + 'exit;"'], wheretosave, pwd); + + fclose(fid); + + system(['ssh cluster.hnl.bcm.tmc.edu PATH=$PATH:/usr/local/pbs/i686/bin bash ' pwd '/processALL.pbs']); + cd .. +elseif strcmp(singleOrCluster, 'single') + handles = guidata(hObject); + set(handles.infoTextBox, 'string', {'This will do SPM GLM estimation.'}); + for ii=1:size(P,1) + tmp=load(deblank(P(ii,:))); + fd=fields(tmp); + eval(['Pi=tmp.' fd{1} ';']); + N = size(Pi,1); + tmp=load(deblank(E(ii,:))); + fd=fields(tmp); + eval(['EE=tmp.' fd{1} ';']); + thisModulator = []; + if ~isempty(Modulator) + tmp = load(deblank(Modulator(ii,:))); + fd=fields(tmp); + eval(['thisModulator=tmp.' fd{1} ';']); + end + [otherregressor{1}.C, otherregressor{1}.name]=struct2matrix(N, EE, thisModulator); + if ~isempty(headmovement) + tmp = load(deblank(headmovement(ii,:))); + fd=fields(tmp); + eval(['thisheadmovement=tmp.' fd{1} ';']); + + tmphd = struct2cell(thisheadmovement); + tmpname = fieldnames(thisheadmovement); + for kk=1:length(tmphd) + [r,c] = size(tmphd{kk}); + if r==1; tmphd{kk} = tmphd{kk}'; end + otherregressor{1}.C = [otherregressor{1}.C tmphd{kk}]; + otherregressor{1}.name = [otherregressor{1}.name {tmpname{kk}}]; + end + end + subDir = fileparts(deblank(Pi(1,:))); + for kk=1:length(subDir); + if subDir(end-kk+1)==filesep; + pos=kk; + break; + end; + end; + subDir(end-pos+1:end)=[]; + cd(subDir); + try + rmdir(wheretosave, 's'); + end + mkdir(wheretosave); + cd(wheretosave); + + spm_defaults; + disp(subDir); + cuixuprocess(N, [0], {}, {}, {}, [], otherregressor, Pi, 16); + + tmp = get(handles.infoTextBox, 'string'); + set(handles.infoTextBox, 'string', [tmp; {['SPM Processing subject ' num2str(ii) ' / ' num2str(size(P,1)) ' ...']}]); + disp(['----finished ' num2str(ii) ' out of ' num2str(size(P,1)) ' subjects----']); + end + tmp = get(handles.infoTextBox, 'string'); + set(handles.infoTextBox, 'string', [tmp; {'Done!'}]); +end + +cd(currentdir) + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% GLMPeak (only do regression on peak BOLD +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_GLMPeak(hObject, eventdata, singleOrCluster) + +answer = getVariable({'Image data', 'Event time data', 'Modulator'}); + +if isempty(answer) + return; +end +if isempty(answer{1}) | isempty(answer{2}) | isempty(answer{3}) + return; +end + +directoryname = uigetdir(pwd, 'Pick a Directory to Save Your Beta Files...'); +if isequal(directoryname, 0) + return; +end + +peakpoint = inputdlg({'What points in time do you considered as peak BOLD?'}, 'Peak point',1,{'[4 6]'}); +if isempty(peakpoint); return; end +peakpointstr = peakpoint{1}; +peakpoint = str2num(peakpoint{1}); + + +P = evalin('base',answer{1}); % subject directories +E = evalin('base',answer{2}); % event time +Modulator = evalin('base',answer{3}); % modulator + + +if nargin < 3 + singleOrCluster = 'single'; +end + +if strcmp(singleOrCluster, 'cluster') + mkdir('xjviewtmp'); + cd('xjviewtmp'); + system('rm *'); + + fid = fopen('processALL.pbs','wt'); + fprintf(fid,'#!/bin/bash\n\n'); + fprintf(fid,'for ((s=1;s<=%d;s++))\n', size(P,1)); + fprintf(fid,'do\n\tsleep 3.5\n\tqsub %s/process.pbs -v "s=$s" -N pro_$s\ndone\n', pwd); + fclose(fid); + + save P P; + save E E; + save Modulator Modulator; + fid = fopen('process.pbs','wt'); + fprintf(fid,'#!/bin/bash\n\n'); + fprintf(fid,'#PBS -l nodes=1:ppn=1,walltime=08:00:00\n'); + fprintf(fid,'#PBS -N process\n'); + fprintf(fid,'#PBS -e %s\n', pwd); + fprintf(fid,'#PBS -o %s\n\n', pwd); + fprintf(fid,'sleep 3.5\n\n'); + fprintf(fid,'PATH=/usr/local/bin:$PATH\n\n'); + fprintf(fid,'matlab -nojvm -nodisplay \\\n'); + fprintf(fid,'-logfile "%s/process.$s.$PBS_JOBID.log" \\\n', pwd); + + fprintf(fid,['-r "addpath ~/gang/xjview; '... + 'cd %s; '... + 'load P; '... + 'load E; '... + 'load Modulator; '... + 'tmp=load(deblank(P($s,:))); '... + 'fd=fields(tmp); '... + 'eval([''SS=tmp.'' fd{1} '';'']); '... + 'tmp=load(deblank(E($s,:))); '... + 'fd=fields(tmp); '... + 'eval([''EE=tmp.'' fd{1} '';'']); '... + 'tmp = load(deblank(Modulator($s,:))); fd=fields(tmp); eval([''MM=tmp.'' fd{1} '';'']);'... + 'names = fieldnames(EE);'... + 'for jj=1:length(names);'... + 'eval([''MM.'' names{jj} ''.time = EE.'' names{jj} '';'']);'... + 'end;'... + 'xjviewpath = fileparts(which(''xjview''));'... + 'cuixuGLMpeak(MM, SS, ''%s'', [''subject'' num2str($s)], fullfile(xjviewpath, ''mask.img''), %s, 100, 2, fullfile(xjviewpath, ''templateFile.img''));'... + 'exit;"'], pwd, directoryname, peakpointstr); + + fclose(fid); + + system(['ssh cluster.hnl.bcm.tmc.edu PATH=$PATH:/usr/local/pbs/i686/bin bash ' pwd '/processALL.pbs']); + cd .. +elseif strcmp(singleOrCluster, 'single') + xjviewpath = fileparts(which('xjview')); + handles = guidata(hObject); + set(handles.infoTextBox, 'string', {'This will do simple GLM estimation on peak BOLD.'}); + for ii = 1:size(P,1) + tmp = get(handles.infoTextBox, 'string'); + set(handles.infoTextBox, 'string', [tmp; {['Processing subject ' num2str(ii) ' / ' num2str(size(P,1)) ' ...']}]); + tmp=load(deblank(P(ii,:))); + fd=fields(tmp); + eval(['SS=tmp.' fd{1} ';']); + tmp=load(deblank(E(ii,:))); + fd=fields(tmp); + eval(['EE=tmp.' fd{1} ';']); + tmp=load(deblank(Modulator(ii,:))); + fd=fields(tmp); + eval(['MM=tmp.' fd{1} ';']); + % convert Modulator and E to be recognized by cuixuGLMpeak + names = fieldnames(EE); + for jj=1:length(names) + eval(['MM.' names{jj} '.time = EE.' names{jj} ';']); + end + cuixuGLMpeak(MM, SS, directoryname, ['subject' num2str(ii)], fullfile(xjviewpath, 'mask.img'), peakpoint, 100, 2, fullfile(xjviewpath, 'templateFile.img')); + disp(['----finished ' num2str(ii) ' out of ' num2str(size(P,1)) ' subjects----']); + end + tmp = get(handles.infoTextBox, 'string'); + set(handles.infoTextBox, 'string', [tmp; {'Done!'}]); +end + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% process (use my own process code, cuixuAGLM, estimation on detrended relative BOLD) +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_process(hObject, eventdata, singleOrCluster) + +answer = getVariable({'Image data', 'Event time data', 'Modulator'}); + +if isempty(answer) + return; +end +if isempty(answer{1}) | isempty(answer{2}) + return; +end + +directoryname = uigetdir(pwd, 'Pick a Directory to Save Your Beta Files...'); +if isequal(directoryname, 0) + return; +end + +P = evalin('base',answer{1}); % subject directories +E = evalin('base',answer{2}); % event time +try + Modulator = evalin('base',answer{3}); % modulator +catch + Modulator = []; +end + + +if nargin < 3 + singleOrCluster = 'single'; +end + +if strcmp(singleOrCluster, 'cluster') + mkdir('xjviewtmp'); + cd('xjviewtmp'); + system('rm *'); + + fid = fopen('processALL.pbs','wt'); + fprintf(fid,'#!/bin/bash\n\n'); + fprintf(fid,'for ((s=1;s<=%d;s++))\n', size(P,1)); + fprintf(fid,'do\n\tsleep 3.5\n\tqsub %s/process.pbs -v "s=$s" -N pro_$s\ndone\n', pwd); + fclose(fid); + + save P P; + save E E; + save Modulator Modulator; + fid = fopen('process.pbs','wt'); + fprintf(fid,'#!/bin/bash\n\n'); + fprintf(fid,'#PBS -l nodes=1:ppn=1,walltime=08:00:00\n'); + fprintf(fid,'#PBS -N process\n'); + fprintf(fid,'#PBS -e %s\n', pwd); + fprintf(fid,'#PBS -o %s\n\n', pwd); + fprintf(fid,'sleep 3.5\n\n'); + fprintf(fid,'PATH=/usr/local/bin:$PATH\n\n'); + fprintf(fid,'matlab -nojvm -nodisplay \\\n'); + fprintf(fid,'-logfile "%s/process.$s.$PBS_JOBID.log" \\\n', pwd); + + fprintf(fid,['-r "addpath ~/gang/xjview; '... + 'cd %s; '... + 'load P; '... + 'load E; '... + 'load Modulator; '... + 'tmp=load(deblank(P($s,:))); '... + 'fd=fields(tmp); '... + 'eval([''M=tmp.'' fd{1} '';'']); '... + 'if isnumeric(M); M=double(M); elseif isstr(M); V=spm_vol(M); M =spm_read_vols(V); else; error(''I don not understand the fileformat''); end;' ... + 'tmp=load(deblank(E($s,:))); '... + 'fd=fields(tmp); '... + 'eval([''EE=tmp.'' fd{1} '';'']); '... + 'if ~isempty(Modulator); tmp = load(deblank(Modulator($s,:))); fd=fields(tmp); eval([''thisModulator=tmp.'' fd{1} '';'']); else; thisModulator=[]; end;'... + 'M = permute(M,[4 1 2 3]); '... + '[tmp,tmp,M]=mAveDetrend(M,100); '... + 'M = permute(M,[2 3 4 1]); '... + '[a,b,c]=fileparts(deblank(P($s,:)));'... + 'cuixuAGLM(M,EE,{''%s'', b}, thisModulator); '... + 'exit;"'], pwd, directoryname); + + fclose(fid); + + system(['ssh cluster.hnl.bcm.tmc.edu PATH=$PATH:/usr/local/pbs/i686/bin bash ' pwd '/processALL.pbs']); + cd .. +elseif strcmp(singleOrCluster, 'single') + handles = guidata(hObject); + set(handles.infoTextBox, 'string', {'This will do simple GLM estimation.'}); + for ii=1:size(P,1) + tmp = get(handles.infoTextBox, 'string'); + set(handles.infoTextBox, 'string', [tmp; {['Processing subject ' num2str(ii) ' / ' num2str(size(P,1)) ' ...']}]); + tmp=load(deblank(P(ii,:))); + fd=fields(tmp); + eval(['M=tmp.' fd{1} ';']); + if isnumeric(M); M=double(M); elseif isstr(M); V=spm_vol(M); M =spm_read_vols(V); else; error('I don''t understand the fileformat'); end; + tmp=load(deblank(E(ii,:))); + fd=fields(tmp); + eval(['EE=tmp.' fd{1} ';']); + if ~isempty(Modulator); tmp = load(deblank(Modulator(ii,:))); fd=fields(tmp); eval(['thisModulator=tmp.' fd{1} ';']); else; thisModulator=[]; end; + M = permute(M,[4 1 2 3]); + [tmp,tmp,M]=mAveDetrend(M,100); + M = permute(M,[2 3 4 1]); + [a,b,c]=fileparts(deblank(P(ii,:))); + cuixuAGLM(M,EE,{directoryname, b}, thisModulator); + disp(['----finished ' num2str(ii) ' out of ' num2str(size(P,1)) ' subjects----']); + end + tmp = get(handles.infoTextBox, 'string'); + set(handles.infoTextBox, 'string', [tmp; {'Done!'}]); +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% convert new data format to old +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_new2old(hObject, eventdata) +answer = getVariable({'subjects directories'}); +if isempty(answer) + return; +end +if isempty(answer{1}) + return; +end +P = evalin('base',answer{1}); % subject directories +new2old(P); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% pre-process_cibsr +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_preprocess_cibsr(hObject, eventdata) +answer = getVariable({'subjects directories'}); +if isempty(answer) + return; +end +if isempty(answer{1}) + return; +end +P = evalin('base',answer{1}); % subject directories + +warndlg('You need to addpath ml7spm2devel2') + +cuixuPreprocessCIBSR(P); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% pre-process +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_preprocess(hObject, eventdata, singleOrCluster) +answer = getVariable({'subjects directories'}); +if isempty(answer) + return; +end +if isempty(answer{1}) + return; +end +P = evalin('base',answer{1}); % subject directories + +if nargin < 3 + singleOrCluster = 'single'; +end + +if strcmp(singleOrCluster, 'cluster') + mkdir('xjviewtmp'); + cd('xjviewtmp'); + system('rm *'); + + fid = fopen('preprocessALL.pbs','wt'); + fprintf(fid,'#!/bin/bash\n\n'); + fprintf(fid,'for ((s=1;s<=%d;s++))\n', size(P,1)); + fprintf(fid,'do\n\tsleep 3.5\n\tqsub %s/preprocess.pbs -v "s=$s" -N pre_$s\ndone\n', pwd); + fclose(fid); + + save P P; + fid = fopen('preprocess.pbs','wt'); + fprintf(fid,'#!/bin/bash\n\n'); + fprintf(fid,'#PBS -l nodes=1:ppn=1,walltime=08:00:00\n'); + fprintf(fid,'#PBS -N preprocess\n'); + fprintf(fid,'#PBS -e %s\n', pwd); + fprintf(fid,'#PBS -o %s\n\n', pwd); + fprintf(fid,'sleep 3.5\n\n'); + fprintf(fid,'PATH=/usr/local/bin:$PATH\n\n'); + fprintf(fid,'matlab -nojvm -nodisplay \\\n'); + fprintf(fid,'-logfile "%s/preprocess.$s.$PBS_JOBID.log" \\\n', pwd); + fprintf(fid,['-r "addpath ~/gang/xjview; cd ' pwd '; load P; cuixuSmartPreprocess(P, $s);exit;"']); + fclose(fid); + + system(['ssh cluster.hnl.bcm.tmc.edu PATH=$PATH:/usr/local/pbs/i686/bin bash ' pwd '/preprocessALL.pbs']); + cd .. +elseif strcmp(singleOrCluster, 'single') + handles = guidata(hObject); + set(handles.infoTextBox, 'string', {'What will be done in preprocessing is:',... + '1. Converting DICOM files',... + '2. Re-align',... + '3. Coregister',... + '4. Slice timing',... + '5. Normalize',... + '6. Smooth','',... + 'This function creates a directory ''preprocessing'' under your homeDir and put all preprocessing files there. It also saves the head movement matrix in ''headmovement.mat''. The intermediate files are removed',''}); + for ii=1:size(P,1) + tmp = get(handles.infoTextBox, 'string'); + set(handles.infoTextBox, 'string', [tmp; {['Preprocessing subject ' num2str(ii) ' / ' num2str(size(P,1)) ' ...']}]); + cuixuSmartPreprocess(P, ii); + end + tmp = get(handles.infoTextBox, 'string'); + set(handles.infoTextBox, 'string', [tmp; {'Done!'}]); +end + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% ROI: TimeSeries +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_timeSeries(hObject, eventdata) + +global TR_; +handles = guidata(hObject); +if ~isfield(handles,'currentDisplayMNI') | isempty(cell2mat(handles.currentDisplayMNI')) + %errordlg('No voxels found.','error'); + set(handles.infoTextBox, 'string', 'No voxels found.'); + beep + return; +end + +dimensionmatch = questdlg('The dimension of your mask image should be identical to the dimensions of the images where the time series are extracted. Are they identical?', ... + 'Dimension Match Question', ... + 'Yes', 'No', 'I Do Not Know', 'Yes'); + +switch dimensionmatch + case 'No', + msgbox('You should use spm reslice to change your mask image dimensions','warning'); + return; + case 'I Do Not Know', + msgbox('You should check the dimensions and use spm reslice to change your mask image dimensions','warning'); + return; +end % switch + + + +answer = getVariable({'Image data', 'Event time data', 'Correlator', 'Effects to be removed (e.g. headmovement)'}); +%answer=inputdlg('Input the name of the variable (in the base workspace) which contains the image file name list, or the 4-D data matrix. If you wish to select the image files directly, click cancel.', 'Variable Name?', 1, {'P'}); +if isempty(answer) + return; +end +if isempty(answer{1}) + return; +end + +searchContent = get(handles.searchContentEdit,'string'); +if isempty(searchContent) + searchContent = num2str(coord(1,3)); +end +searchContent = ['ROI_' searchContent]; +[filename, pathname] = uiputfile('*.mat', 'Save result to', searchContent); +if isequal(filename,0) | isequal(pathname,0) + return +else + thisfilename = fullfile(pathname, filename); +end + + +P = evalin('base',answer{1}); % data +try + E = evalin('base',answer{2}); % event +catch + E = []; +end +try + C = evalin('base',answer{3}); % correlator +catch + C = []; +end +try + headmovement = evalin('base',answer{4}); % headmovement +catch + headmovement = []; +end + +mni = cell2mat(handles.currentDisplayMNI'); +coord = mni2cor(mni, handles.M{1}); + + +if ischar(P) + h = waitbar(0,'Please wait...'); + for ii=1:size(P,1) + tmp = load(deblank(P(ii,:))); + tmp1 = fields(tmp); + eval(['M = tmp.' tmp1{1} ';']); + if isnumeric(M) + M = double(M); + end + if ~isempty(E) + tmp = load(deblank(E(ii,:))); + tmp1 = fields(tmp); + eval(['event{ii} = tmp.' tmp1{1} ';']); + if ~isstruct(event{ii}) + error('I need you specify the event in a structure, not a cell array!'); + return + end + else + event = {}; + end + + if ~isempty(headmovement) % if headmovement is present (or any other effects to be removed), then get rid of it first + H = []; + tmp = load(deblank(headmovement(ii,:))); + fd=fields(tmp); + eval(['thisheadmovement=tmp.' fd{1} ';']); + + tmphd = struct2cell(thisheadmovement); + for kk=1:length(tmphd) + [r,c] = size(tmphd{kk}); + if r==1; tmphd{kk} = tmphd{kk}'; end + H = [H tmphd{kk}]; + end + for kk=1:size(coord,1) + b = linearregression(squeeze(M(coord(kk,1), coord(kk,2), coord(kk,3), :)),H); + M(coord(kk,1), coord(kk,2), coord(kk,3), :) = squeeze(M(coord(kk,1), coord(kk,2), coord(kk,3), :)) - H*b(1:end-1); + end + end + + [eventResponse{ii}, time{ii}, wholeOriginal{ii}, wholeAbsolute{ii}, wholeBaseline{ii}, removedEvent{ii}, remainedEvent{ii}] = cuixuBOLDretrieve(M, coord, event{ii}, 50, -20, 100, 0, TR_); + + if ~isempty(C) + tmp = load(deblank(C(ii,:))); + tmp1 = fields(tmp); + eval(['correlator{ii} = tmp.' tmp1{1} ';']); + if ~isstruct(correlator{ii}) + error('I need you specify the correlator in a structure, not a cell array!'); + return + end + + names = fields(correlator{ii}); + for kk=1:length(names) + eval(['names2 = fields(correlator{ii}.' names{kk} ');']); + for mm=1:length(names2) + eval(['correlator{ii}.' names{kk} '.' names2{mm} ' = correlator{ii}.' names{kk} '.' names2{mm} '(remainedEvent{ii}.' names{kk} ');']);; + end + end + end + waitbar(ii/size(P,1),h,['finished ' num2str(ii) ' of ' num2str(size(P,1))]); + disp(['finished ' num2str(ii) ' of ' num2str(size(P,1))]); + end + close(h) +elseif isnumeric(P) % never used! + P = double(P); + if ~isempty(E) + tmp = load(deblank(E(ii,:))); + tmp1 = fields(tmp); + eval(['event{ii} = tmp.' tmp1{1} ';']); + else + event = {}; + end + [eventResponse{ii}, time{ii}, wholeOriginal{ii}, wholeAbsolute{ii}, wholeBaseline{ii}, removedEvent{ii}, remainedEvent{ii}] = cuixuBOLDretrieve(P, coord, event{ii}, 50, -20, 100, 0, TR_); +end + +if ~exist('correlator') + correlator = {}; +end + +mat = handles.M{1}; +save(thisfilename, 'eventResponse', 'time', 'wholeOriginal', 'wholeAbsolute', 'wholeBaseline', 'correlator', 'coord', 'mni', 'removedEvent','remainedEvent', 'event', 'mat', 'P', 'headmovement'); +CallBack_plotROI(hObject, eventdata, thisfilename); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% myerrorbar plot (change errorbar width) +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function hE = myerrorbar(varargin) +hE = errorbar(varargin{:}); +xdata = get(hE, 'xdata'); +errorbarwidth = (xdata(2) - xdata(1))/4; +% adjust error bar width +hE_c = ... + get(hE , 'Children' ); +errorbarXData = ... + get(hE_c(2), 'XData' ); +errorbarXData(4:9:end) = ... + errorbarXData(1:9:end) - errorbarwidth; +errorbarXData(7:9:end) = .... + errorbarXData(1:9:end) - errorbarwidth; +errorbarXData(5:9:end) = ... + errorbarXData(1:9:end) + errorbarwidth; +errorbarXData(8:9:end) = ... + errorbarXData(1:9:end) + errorbarwidth; +set(hE_c(2), 'XData', errorbarXData); + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% plot ROI +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_plotROI(hObject, eventdata, filename) +handles = guidata(hObject); + +if ~exist('filename') + if findstr('SPM2',spm('ver')) + P = spm_get([0:1],'*.mat','Select ROI result file'); + else %if findstr('SPM5',spm('ver')) + P = spm_select([0:1],'mat','Select ROI result file'); + end +else + P = filename; +end + +if isempty(P) + return; +end +load(deblank(P)); + +if exist('mni') + delete(gcf) + xjview(mni); +end + + +% allow user to select which subject to plot +prompt=['I find you have ' num2str(length(eventResponse)) ' subjects. Please let me know which subjects you want to include to generate the plots.']; +name='Which subject(s) to plot?'; +numlines=1; +defaultanswer={['[1:' num2str(length(eventResponse)) ']']}; + +whichtoplot=inputdlg(prompt,name,numlines,defaultanswer); +if isempty(whichtoplot) + return; +end +whichtoplot = eval(whichtoplot{1}); + + +response = eventResponse{1}; + +eventisstruct = 0; +if isstruct(response) + eventisstruct = 1; + f = fieldnames(response); + response = struct2cell(response); +else + f = num2cell([1:length(response)]); + for ii=1:length(response) + f{ii} = num2str(f{ii}); + end +end +for jj=1:length(response) + response{jj} = []; +end + +if exist('correlator') & ~isempty(correlator) & eventisstruct==1 + C = correlator{1}; + cenames = fields(C); + for jj=1:length(cenames) + eval(['correlatornames{jj} = fields(C.' cenames{jj} ');']); + for kk=1:length(correlatornames{jj}) + eval(['C.' cenames{jj} '.' correlatornames{jj}{kk} '= [];']); + end + end + for ii=whichtoplot%1:length(correlator) + for jj=1:length(cenames) + eval(['correlatornames{jj} = fields(C.' cenames{jj} ');']); + for kk=1:length(correlatornames{jj}) + tmp2 = eval(['correlator{ii}.' cenames{jj} '.' correlatornames{jj}{kk} ';']); + if size(tmp2,1) == 1 + tmp2 = tmp2'; + end + eval(['C.' cenames{jj} '.' correlatornames{jj}{kk} '= [C.' cenames{jj} '.' correlatornames{jj}{kk} '; tmp2];']); + end + end + end +end + +if eventisstruct == 1 + for ii=1:length(eventResponse) + for kk=1:length(f) + eval(['tmptmp{kk} = eventResponse{ii}.' f{kk} ';']); + end + eventResponse{ii} = tmptmp; + end +end + +for ii=whichtoplot%1:length(eventResponse) + for jj=1:length(eventResponse{1}) + response{jj} = [response{jj}; eventResponse{ii}{jj}]; + end +end + +if isempty(response) + msgbox('Nothing to plot'); + return +end + + + + +v = version; +figure; +%colors='rbkmcgyrbkmcgyrbkmcgyrbkmcgyrbkmcgyrbkmcgyrbkmcgy'; +colors=[1 0 0; + 0 0 1; + 0 0 0; + 1 0 1; + 0 1 1; + 0 1 0; + 1 1 0; + 1 0.5 0.5; + .5 .5 1; + .5 .5 .5; + 1 .5 0; + 1 0 .5; + 0 1 .5; + .5 1 0; + 0 .5 1; + .5 0 1]; +colors = repmat(colors, 10, 1); +thislabel = {}; +for ii=1:length(response) + if v(1)=='6' + ff = myerrorbar(time{1}, meannan(response{ii}), stdnan(response{ii})/sqrt(size(response{ii},1))); + set(ff,'color',colors(ii, :)); + thislabel = [thislabel {''} f(ii)]; + elseif v(1)=='7' + hE = myerrorbar(time{1}, meannan(response{ii}), stdnan(response{ii})/sqrt(size(response{ii},1)), 'color', colors(ii, :)); +% errorbarwidth = (time{1}(2) - time{1}(1))/4; +% % adjust error bar width +% hE_c = ... +% get(hE , 'Children' ); +% errorbarXData = ... +% get(hE_c(2), 'XData' ); +% errorbarXData(4:9:end) = ... +% errorbarXData(1:9:end) - errorbarwidth; +% errorbarXData(7:9:end) = .... +% errorbarXData(1:9:end) - errorbarwidth; +% errorbarXData(5:9:end) = ... +% errorbarXData(1:9:end) + errorbarwidth; +% errorbarXData(8:9:end) = ... +% errorbarXData(1:9:end) + errorbarwidth; +% set(hE_c(2), 'XData', errorbarXData); + thislabel = [thislabel f(ii)]; + end + hold on; +end +xlabel('time (s)'); +ylabel('relative signal'); +legend(thislabel) +hold off; + +[row,col] = size(response); +if row == 1; response = response'; end; +ResponseForPlot = cell2struct(response, f, 1); + +if exist('correlator') & ~isempty(correlator) + %list all plot names in command window for selection + promt{1} = 'I will plot signal amplitude vs correlator. Here are all the correlators:'; + promt{2} = 'index event correlator'; + count = 1; + for jj=1:length(cenames) + eval(['correlatornames{jj} = fields(C.' cenames{jj} ');']); + for kk=1:length(correlatornames{jj}) + promt{count+2} = sprintf('%d %s %s', count, cenames{jj}, correlatornames{jj}{kk}); + count = count + 1; + end + end + promt{count+2} = 'Which plots do you want me to show?'; + + promt = char(promt); + name='Please select which plots to show'; + numlines=1; + defaultanswer={['[1:' num2str(count-1) ']']}; + + whichplot=inputdlg(promt,name,numlines,defaultanswer); + if isempty(whichplot) + return; + end + whichplot = eval(whichplot{1}); + + count = 1; + for jj=1:length(cenames) + eval(['correlatornames{jj} = fields(C.' cenames{jj} ');']); + for kk=1:length(correlatornames{jj}) + if ~ismember(count, whichplot) + count = count + 1; + continue; + end + + eval(['x = C.' cenames{jj} '.' correlatornames{jj}{kk} ';']); + + uniquex = unique(x); + pos = find(isnan(uniquex)); + uniquex(pos) = []; + + if(length(uniquex)<10) % only if the correlator is discrete, then plot BOLD vs time for each correlator + figure; + thislabel = {}; + for ii=1:length(uniquex) + pos = find(x==uniquex(ii)); + eval(['z = meannan(ResponseForPlot.' cenames{jj} '(pos,:));']); + eval(['stdz = stdnan(ResponseForPlot.' cenames{jj} '(pos,:))/sqrt(length(pos));']); + + if v(1)=='6' + ff = myerrorbar(time{1}, z, stdz); + set(ff,'color',colors(ii, :)); + thislabel = [thislabel {''} {(num2str(uniquex(ii)))}]; + elseif v(1)=='7' + myerrorbar(time{1}, z, stdz, 'color', colors(ii, :)); + thislabel = [thislabel {(num2str(uniquex(ii)))}]; + end + hold on; + end + legend(thislabel); + xlabel('time in second'); + ylabel('BOLD'); + title([cenames{jj} ' ' correlatornames{jj}{kk}]) + end + + count = count + 1; + end + end + + peakpoint = inputdlg({'I will plot peak signal. What points in time do you considered as peak signal?'}, 'Peak point',1,{'4 6'}); + if isempty(peakpoint); return; end + peakpoint = str2num(peakpoint{1}); + + + %list all plot names in command window for selection, effect(s) to be + %removed + clear promt; + promt{1} = 'Which effect(s) do you want me to remove first? (If none to remove, leave it blank and click OK)'; + promt{2} = 'index event correlator'; + count = 1; + for jj=1:length(cenames) + eval(['correlatornames{jj} = fields(C.' cenames{jj} ');']); + for kk=1:length(correlatornames{jj}) + promt{count+2} = sprintf('%d %s %s', count, cenames{jj}, correlatornames{jj}{kk}); + count = count + 1; + end + end + %promt{count+2} = 'Which effect(s) do you want me to remove first?'; + + promt = char(promt); + name='Please select which effect to remove'; + numlines=1; + defaultanswer={''}; + + whichremovefirst=inputdlg(promt,name,numlines,defaultanswer); + if isempty(whichremovefirst) + whichremovefirst = []; + elseif isempty(deblank(whichremovefirst{1})) + whichremovefirst = []; + else + whichremovefirst = eval(whichremovefirst{1}); + end + + +%% remove effect first + tmpResponseForPlot = ResponseForPlot; + for jj=1:length(cenames) + eval(['tmpResponseForPlot.' cenames{jj} ' = mean(tmpResponseForPlot.' cenames{jj} '(:,findind(time{1},peakpoint)), 2);']); + end + + if ~isempty(whichremovefirst) + count = 1; + for jj=1:length(cenames) + eval(['correlatornames{jj} = fields(C.' cenames{jj} ');']); + eval(['y = tmpResponseForPlot.' cenames{jj} ';']); + x = []; + if size(y,1)==1 + y = y'; + end + for kk=1:length(correlatornames{jj}) + if ~ismember(count, whichremovefirst) + count = count + 1; + continue; + end + + eval(['tmpx = C.' cenames{jj} '.' correlatornames{jj}{kk} ';']); + + % convert to colum vector + if size(tmpx,1)==1 + tmpx = tmpx'; + end + x = [x tmpx]; + + + + count = count + 1; + end + + % remove NaN + tmpposx = find(isnan(mean(x,2)) | isinf(mean(x,2))); + tmpposy = find(isnan(mean(y,2)) | isinf(mean(y,2))); + x([tmpposx; tmpposy],:) = []; + y([tmpposx; tmpposy],:) = []; + + b = linearregression(y,x); + y = y - x*b(1:end-1) - b(end); + eval(['tmpResponseForPlot.' cenames{jj} ' = y;']); + end + end + +%% now plot the residual amplitude against correlator + count = 1; + for jj=1:length(cenames) + eval(['correlatornames{jj} = fields(C.' cenames{jj} ');']); + for kk=1:length(correlatornames{jj}) + if ~ismember(count, whichplot) + count = count + 1; + continue; + end + + eval(['y = tmpResponseForPlot.' cenames{jj} ';']); + eval(['x = C.' cenames{jj} '.' correlatornames{jj}{kk} ';']); + + + figure + plot(x,y, 'sb'); + hold on; + plot2(x,y,'r',1); + + xlabel(correlatornames{jj}{kk}) + ylabel('peak relative BOLD'); + legend(cenames{jj}) + try + % convert to colum vector + if size(x,1)==1 + x = x'; + end + if size(y,1)==1 + y = y'; + end + + % remove NaN + tmpposx = find(isnan(x) | isinf(x)); + tmpposy = find(isnan(y) | isinf(y)); + x([tmpposx; tmpposy]) = []; + y([tmpposx; tmpposy]) = []; + + b = linearregression(y,x); + + totalvar = var(y); + residvar = var(y - x*b(1) - b(2)); + modelvar = totalvar - residvar; + dfm = 1; + dft = length(x) - 1; + dfr = dft - dfm; + mmodelvar = modelvar/dfm; + mresidvar = residvar/dfr; + F = mmodelvar/mresidvar; + pvalue = 1-spm_Fcdf(F, [dfm dfr]); + + title(sprintf('pValue=%s; slope=%g; constant=%g', pvalue, b(1), b(2))) + xx=[min(x):(max(x)-min(x))/10:max(x)]; + yy=b(1) * xx + b(2); + hold on; + plot(xx,yy,'g'); + end + count = count + 1; + end + end +end + +function index = findind(vector1, vector2) +kk = 1; +for ii=1:length(vector2) + tmp = find(vector1 == vector2(ii)); + if ~isempty(tmp) + index(kk) = tmp; + kk = kk+1; + end +end +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% plot ROI, individual by individual, with behavior timing on it +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_plotIndividualROIWithBehavior(hObject, eventdata) +global TR_; +handles = guidata(hObject); + +if findstr('SPM2',spm('ver')) + P = spm_get([0:1],'*.mat','Select ROI result file'); +else%if findstr('SPM5',spm('ver')) + P = spm_select([0:1],'mat','Select ROI result file'); +end + +if isempty(P) + return; +end +load(P); +if ~exist('TR') + TR = TR_; +end + +colors=[1 0 0; + 0 0 1; + 0 0 0; + 1 0 1; + 0 1 1; + 0 1 0; + 1 1 0; + 1 0.5 0.5; + .5 .5 1; + .5 .5 .5; + 1 .5 0; + 1 0 .5; + 0 1 .5; + .5 1 0; + 0 .5 1; + .5 0 1]; +colors = repmat(colors, 10, 1); + + +figure; +Maximize(gcf); +for ii=1:length(event) + kk = mod(ii,8); + if kk == 0 + kk = 8; + end + subplot(4,2,kk) + + m = mean(wholeOriginal{ii}); + s = std(wholeOriginal{ii}); + mx = max(wholeOriginal{ii}); + mn = min(wholeOriginal{ii}); + ylowerlimit = mn; + + hm = event{ii}; + hmcell = struct2cell(hm); + hmname = fieldnames(hm); + for jj=1:length(hmname) + toplot1 = []; + toplot2 = []; + for mm = 1:length(hmcell{jj}) + toplot1 = [toplot1 hmcell{jj}(mm) hmcell{jj}(mm) nan]; + toplot2 = [toplot2 m-s m+s nan]; + end + plot(toplot1, toplot2, 'color', colors(jj,:)); + hold on + end + legend(hmname) + title(['subject ' num2str(ii)]) + xlabel('time in s'); + + % plot imaging data + plot(TR*[0:length(wholeOriginal{ii})-1], wholeOriginal{ii}, 'k'); + hold on; + ylim([ylowerlimit mx]) + + if mod(ii,8) == 0 && ii~=length(event) + figure; + Maximize(gcf); + end +end +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% plot ROI, individual by individual +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_plotIndividualROI(hObject, eventdata, filename) +handles = guidata(hObject); + +if ~exist('filename') + if findstr('SPM2',spm('ver')) + P = spm_get([0:1],'*.mat','Select ROI result file'); + else%if findstr('SPM5',spm('ver')) + P = spm_select([0:1],'mat','Select ROI result file'); + end +else + P = filename; +end + +if isempty(P) + return; +end +load(deblank(P)); + +if exist('mni') + delete(gcf) + xjview(mni); +end + +response = eventResponse{1}; + +eventisstruct = 0; +if isstruct(response) + eventisstruct = 1; + f = fieldnames(response); + response = struct2cell(response); +else + f = num2cell([1:length(response)]); + for ii=1:length(response) + f{ii} = num2str(f{ii}); + end +end +for jj=1:length(response) + response{jj} = []; +end + +if exist('correlator') & ~isempty(correlator) & eventisstruct==1 + C = correlator{1}; + cenames = fields(C); + for jj=1:length(cenames) + eval(['correlatornames{jj} = fields(C.' cenames{jj} ');']); + for kk=1:length(correlatornames{jj}) + eval(['C.' cenames{jj} '.' correlatornames{jj}{kk} '= [];']); + end + end + for ii=1:length(correlator) + for jj=1:length(cenames) + eval(['correlatornames{jj} = fields(C.' cenames{jj} ');']); + for kk=1:length(correlatornames{jj}) + tmp2 = eval(['correlator{ii}.' cenames{jj} '.' correlatornames{jj}{kk} ';']); + if size(tmp2,1) == 1 + tmp2 = tmp2'; + end + eval(['C.' cenames{jj} '.' correlatornames{jj}{kk} '= [C.' cenames{jj} '.' correlatornames{jj}{kk} '; tmp2];']); + end + end + end +end + +if eventisstruct == 1 + for ii=1:length(eventResponse) + for kk=1:length(f) + eval(['tmptmp{kk} = eventResponse{ii}.' f{kk} ';']); + end + eventResponse{ii} = tmptmp; + end +end + +for ii=1:length(eventResponse) + for jj=1:length(eventResponse{1}) + response{jj} = [response{jj}; eventResponse{ii}{jj}]; + end +end + +if isempty(response) + msgbox('Nothing to plot'); + return +end + + + + +v = version; +figure; +%colors='rbkmcgyrbkmcgyrbkmcgyrbkmcgyrbkmcgyrbkmcgyrbkmcgy'; +colors=[1 0 0; + 0 0 1; + 0 0 0; + 1 0 1; + 0 1 1; + 0 1 0; + 1 1 0; + 1 0.5 0.5; + .5 .5 1; + .5 .5 .5; + 1 .5 0; + 1 0 .5; + 0 1 .5; + .5 1 0; + 0 .5 1; + .5 0 1]; +colors = repmat(colors, 10, 1); +thislabel = {}; +for ii=1:length(response) + if v(1)=='6' + ff = myerrorbar(time{1}, meannan(response{ii}), stdnan(response{ii})/sqrt(size(response{ii},1))); + set(ff,'color',colors(ii, :)); + thislabel = [thislabel {''} f(ii)]; + elseif v(1)=='7' + myerrorbar(time{1}, meannan(response{ii}), stdnan(response{ii})/sqrt(size(response{ii},1)), 'color', colors(ii, :)); + thislabel = [thislabel f(ii)]; + end + hold on; +end +xlabel('time (s)'); +ylabel('relative signal'); +legend(thislabel) +hold off; + +figure; +Maximize(gcf); +for jj=1:length(eventResponse) + wheretoplot = mod(jj, 16); + if wheretoplot == 0; + wheretoplot = 16; + subplot(4,4,wheretoplot) + else + subplot(4,4,wheretoplot) + end + for ii=1:length(response) + if v(1)=='6' + ff = myerrorbar(time{1}, meannan(eventResponse{jj}{ii}), stdnan(eventResponse{jj}{ii})/sqrt(size(eventResponse{jj}{ii},1))); + set(ff,'color',colors(ii, :)); + %thislabel = [thislabel {''} f(ii)]; + elseif v(1)=='7' + myerrorbar(time{1}, meannan(eventResponse{jj}{ii}), stdnan(eventResponse{jj}{ii})/sqrt(size(eventResponse{jj}{ii},1)), 'color', colors(ii, :)); + %myerrorbar(time{1}, meannan(response{ii}), stdnan(response{ii})/sqrt(size(response{ii},1)), 'color', colors(ii, :)); + %thislabel = [thislabel f(ii)]; + end + hold on; + end + title(['subject ' num2str(jj)]) + + if mod(jj, 16)==0 && jj~=length(eventResponse) + figure; + Maximize(gcf); + end +end + + + +[row,col] = size(response); +if row == 1; response = response'; end; +for jj=1:length(eventResponse) + ResponseForPlot{jj} = cell2struct(eventResponse{jj}, f, 2); +end + +if exist('correlator') & ~isempty(correlator) + %list all plot names in command window for selection + promt{1} = 'I will plot signal amplitude vs correlator. Here are all the correlators:'; + promt{2} = 'index event correlator'; + count = 1; + for jj=1:length(cenames) + eval(['correlatornames{jj} = fields(C.' cenames{jj} ');']); + for kk=1:length(correlatornames{jj}) + promt{count+2} = sprintf('%d %s %s', count, cenames{jj}, correlatornames{jj}{kk}); + count = count + 1; + end + end + promt{count+2} = 'Which plots do you want me to show?'; + + promt = char(promt); + name='Please select which plots to show'; + numlines=1; + defaultanswer={['[1:' num2str(count-1) ']']}; + + whichplot=inputdlg(promt,name,numlines,defaultanswer); + if isempty(whichplot) + return; + end + whichplot = eval(whichplot{1}); + + peakpoint = inputdlg({'What points in time do you considered as peak signal?'}, 'Peak point',1,{'4 6'}); + if isempty(peakpoint); return; end + peakpoint = str2num(peakpoint{1}); + + count = 1; + for jj=1:length(cenames) + eval(['correlatornames{jj} = fields(C.' cenames{jj} ');']); + for kk=1:length(correlatornames{jj}) + if ~ismember(count, whichplot) + count = count + 1; + continue; + end + figure; + Maximize(gcf); + for ii=1:length(eventResponse) + wheretoplot = mod(ii, 16); + if wheretoplot == 0; + wheretoplot = 16; + subplot(4,4,wheretoplot) + else + subplot(4,4,wheretoplot) + end + + + eval(['y = mean(ResponseForPlot{ii}.' cenames{jj} '(:,findind(time{1},peakpoint)), 2);']); + eval(['x = correlator{ii}.' cenames{jj} '.' correlatornames{jj}{kk} ';']); + + %plot(x,y, 'sb'); + %hold on; + pos = find(isnan(x)); + x(pos) = []; + y(pos) = []; + pos = find(isnan(y)); + x(pos) = []; + y(pos) = []; + if length(x) < 1 + continue; + end + plot2(x,y,'r',1); + + xlabel(correlatornames{jj}{kk}) + ylabel(['peak signal at ' cenames{jj}]); + %legend(cenames{jj}) + try + % convert to colum vector + if size(x,1)==1 + x = x'; + end + if size(y,1)==1 + y = y'; + end + + % remove NaN + tmpposx = find(isnan(x) | isinf(x)); + tmpposy = find(isnan(y) | isinf(y)); + x([tmpposx; tmpposy]) = []; + y([tmpposx; tmpposy]) = []; + + b = linearregression(y,x); + + totalvar = var(y); + residvar = var(y - x*b(1) - b(2)); + modelvar = totalvar - residvar; + dfm = 1; + dft = length(x) - 1; + dfr = dft - dfm; + mmodelvar = modelvar/dfm; + mresidvar = residvar/dfr; + F = mmodelvar/mresidvar; + pvalue = 1-spm_Fcdf(F, [dfm dfr]); + + title(sprintf('subject %d, pValue=%s', ii, pvalue)) + xx=[min(x):(max(x)-min(x))/10:max(x)]; + yy=b(1) * xx + b(2); + hold on; + plot(xx,yy,'g'); + end + if mod(ii, 16)==0 && ii~=length(eventResponse) + figure; + Maximize(gcf); + end + end % end of loop subject + count = count + 1; + end + end +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% plot ROI: correlation +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_plotCorrelationROI(hObject, eventdata) + +if findstr('SPM2',spm('ver')) + P1 = spm_get([0:100],'*.mat','Select ROI result file(s)'); +else%if findstr('SPM5',spm('ver')) + P1 = spm_select([0:100],'mat','Select ROI result file(s)'); +end + +if isempty(P1) + return; +end + +N = size(P1,1); +t = [-20:20]; + +v = version; + +if N == 1 + load(deblank(P1)); + tmp = []; + for jj=1:length(wholeAbsolute) + tmp = [tmp; corrlag(wholeAbsolute{jj},wholeAbsolute{jj},t)]; + end + figure; + myerrorbar(t, mean(tmp,1), std(tmp)/sqrt(length(wholeAbsolute))); + xlabel('lag in scan number'); + ylabel('auto correlation'); + title(deblank(P1)) + return +else + figure; + colors='rbkmcgyrbkmcgyrbkmcgyrbkmcgyrbkmcgyrbkmcgyrbkmcgy'; + legendlabel = {}; + totalPermute = nchoosek([1:N],2); + for ii=1:size(totalPermute,1) + tmp = []; + s1 = load(deblank(P1(totalPermute(ii,1),:))); + s2 = load(deblank(P1(totalPermute(ii,2),:))); + for jj=1:length(s1.wholeAbsolute) + tmp = [tmp; corrlag(s1.wholeAbsolute{jj},s2.wholeAbsolute{jj},t)]; + end + myerrorbar(t, mean(tmp,1), std(tmp)/sqrt(length(s1.wholeAbsolute)), colors(ii)); + xlabel('lag in scan number'); + ylabel('cross correlation'); + hold on; + if v(1) == '6' + legendlabel = [legendlabel, {'', P(totalPermute(ii,:),:)}]; + elseif v(1) == '7' + legendlabel = [legendlabel, {P1(totalPermute(ii,:),:)}]; + end + end + legend(legendlabel); + hold off; + return +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% whole brain correlation analysis +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_wholeBrainCorrelation(hObject, eventdata) +% answer = getVariable({'Which data file?'}); +% if isempty(answer) +% return; +% end +% if isempty(answer{1}) +% return; +% end +% P = evalin('base',answer{1}); % subject directories +if findstr('SPM2',spm('ver')) + P = spm_get([0:1],'*.mat','Whole Brain Signal file'); +else%if findstr('SPM5',spm('ver')) + P = spm_select([0:1],'mat','Whole Brain Signal file'); +end + +if isempty(P) + return +end + +load(deblank(P)); +M = double(M); + +N=zeros(size(M,4), size(M,1) * size(M,2) * size(M,3)); + +mm = 1; +for ii=1:2:size(M,1) + for jj=1:3:size(M,2) + for kk=1:2:size(M,3) + N(:, mm) = squeeze(M(ii,jj,kk,:)); + mm = mm + 1; + cor(mm,:) = [ii jj kk]; + end + end +end + +N(:, mm:end) = []; + +C=corrcoef(N); +C(find(isnan(C))) = 0; + +for ii=1:4 + CC{ii} = abs(C)>(0.5+ii/10); +end + +K = {zeros(1, size(C,1)),zeros(1, size(C,1)),zeros(1, size(C,1)),zeros(1, size(C,1))}; +for ii=1:size(C,1) + for jj=1:4 + K{jj}(ii) = sum(CC{jj}(ii,:)) - 1; + end +end + +x=[0:size(C,1)]; +for jj=1:4 + y{jj}=zeros(size(x)); +end + +for ii=1:length(x) + for jj=1:4 + y{jj}(ii) = sum(K{jj} == x(ii)); + end +end + +figure; +loglog(x,y{1}, x, y{2}, x, y{3}, x, y{4}); +xlabel('degree'); +ylabel('counts'); +legend('0.6', '0.7', '0.8', '0.9'); +axis equal + + +z = sum(abs(C))-1; +zx = 0:max(z); +for ii=1:length(zx)-1 + pos = find(z>zx(ii) & z<=zx(ii+1)); + zy(ii) = length(pos); +end +figure; +loglog(zx(1:length(zy)),zy); +xlabel('weighted degree'); +ylabel('counts'); +axis equal + +xjview(cor2mni(coord, handles.M{1}), z); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% get/select variable names in base workspace +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function vars = getVariable(titles) +vars = evalin('base','who'); +height = 0.07; +f = dialog('unit','normalized', 'menubar','none', 'position', [0.3 0.2 0.4 0.4], 'name', 'pick variables', 'NumberTitle','off'); +uicontrol(f,'style','text',... + 'unit','normalized',... + 'String', 'Available variables', ... + 'position',[0 0.9 0.5 0.1/2]); +variableListbox = uicontrol(f,'style','listbox','tag','variableListbox',... + 'unit','normalized',... + 'String', vars, ... + 'value',1,... + 'position',[0 0 0.5 0.9]); +okPush = uicontrol(f,'style','push',... + 'unit','normalized',... + 'String', 'OK', ... + 'position',[0.55 0.05 0.2 height],... + 'callback','uiresume'); +cancelPush = uicontrol(f,'style','push',... + 'unit','normalized',... + 'String', 'Cancel', ... + 'position',[0.75 0.05 0.2 height],... + 'callback','delete(gcf)'); + +uicontrol(f,'style','text',... + 'unit','normalized',... + 'String', titles{1}, ... + 'position',[0.5 0.9 0.5 0.1/2]); +imageDataPush = uicontrol(f,'style','push',... + 'unit','normalized',... + 'String', '->', ... + 'position',[0.5 0.8 0.1 height],... + 'callback', [... + 'xyouneverfindme=findobj(get(gcf,''Children''),''flat'',''tag'',''imageDataEdit'');'... + 'yyouneverfindme=findobj(get(gcf,''Children''),''flat'',''tag'',''variableListbox'');'... + 'allyouneverfindme=get(yyouneverfindme,''string'');'... + 'selectedyouneverfindme=allyouneverfindme{get(yyouneverfindme,''value'')};'... + 'set(xyouneverfindme,''string'',selectedyouneverfindme);'... + 'clear allyouneverfindme selectedyouneverfindme xyouneverfindme yyouneverfindme;']); +imageDataEdit = uicontrol(f,'style','edit','tag','imageDataEdit',... + 'unit','normalized',... + 'String', '', ... + 'BackgroundColor', 'w',... + 'position',[0.6 0.8 0.3 height]); + +if length(titles)>=2 +uicontrol(f,'style','text',... + 'unit','normalized',... + 'String', titles{2}, ... + 'position',[0.5 0.7 0.5 0.1/2]); +eventDataPush = uicontrol(f,'style','push',... + 'unit','normalized',... + 'String', '->', ... + 'position',[0.5 0.6 0.1 height],... + 'callback', [... + 'xyouneverfindme=findobj(get(gcf,''Children''),''flat'',''tag'',''eventDataEdit'');'... + 'yyouneverfindme=findobj(get(gcf,''Children''),''flat'',''tag'',''variableListbox'');'... + 'allyouneverfindme=get(yyouneverfindme,''string'');'... + 'selectedyouneverfindme=allyouneverfindme{get(yyouneverfindme,''value'')};'... + 'set(xyouneverfindme,''string'',selectedyouneverfindme);'... + 'clear allyouneverfindme selectedyouneverfindme xyouneverfindme yyouneverfindme;']); + +eventDataEdit = uicontrol(f,'style','edit','tag','eventDataEdit',... + 'unit','normalized',... + 'String', '', ... + 'BackgroundColor', 'w',... + 'position',[0.6 0.6 0.3 height]); +end +if length(titles)>=3 +uicontrol(f,'style','text',... + 'unit','normalized',... + 'String', titles{3}, ... + 'position',[0.5 0.5 0.5 0.1/2]); +correlatorDataPush = uicontrol(f,'style','push',... + 'unit','normalized',... + 'String', '->', ... + 'position',[0.5 0.4 0.1 height],... + 'callback', [... + 'xyouneverfindme=findobj(get(gcf,''Children''),''flat'',''tag'',''correlatorDataEdit'');'... + 'yyouneverfindme=findobj(get(gcf,''Children''),''flat'',''tag'',''variableListbox'');'... + 'allyouneverfindme=get(yyouneverfindme,''string'');'... + 'selectedyouneverfindme=allyouneverfindme{get(yyouneverfindme,''value'')};'... + 'set(xyouneverfindme,''string'',selectedyouneverfindme);'... + 'clear allyouneverfindme selectedyouneverfindme xyouneverfindme yyouneverfindme;']); + +correlatorDataEdit = uicontrol(f,'style','edit','tag','correlatorDataEdit',... + 'unit','normalized',... + 'String', '', ... + 'BackgroundColor', 'w',... + 'position',[0.6 0.4 0.3 height]); +end +if length(titles)>=4 +uicontrol(f,'style','text',... + 'unit','normalized',... + 'String', titles{4}, ... + 'position',[0.5 0.3 0.5 0.1/2]); +otherDataPush = uicontrol(f,'style','push',... + 'unit','normalized',... + 'String', '->', ... + 'position',[0.5 0.2 0.1 height],... + 'callback', [... + 'xyouneverfindme=findobj(get(gcf,''Children''),''flat'',''tag'',''otherDataEdit'');'... + 'yyouneverfindme=findobj(get(gcf,''Children''),''flat'',''tag'',''variableListbox'');'... + 'allyouneverfindme=get(yyouneverfindme,''string'');'... + 'selectedyouneverfindme=allyouneverfindme{get(yyouneverfindme,''value'')};'... + 'set(xyouneverfindme,''string'',selectedyouneverfindme);'... + 'clear allyouneverfindme selectedyouneverfindme xyouneverfindme yyouneverfindme;']); + +otherDataEdit = uicontrol(f,'style','edit','tag','otherDataEdit',... + 'unit','normalized',... + 'String', '', ... + 'BackgroundColor', 'w',... + 'position',[0.6 0.2 0.3 height]); +end + +uiwait(f); +try + var{1} = get(imageDataEdit,'string'); + if length(titles)>=2 + var{2} = get(eventDataEdit,'string'); + end + if length(titles)>=3 + var{3} = get(correlatorDataEdit,'string'); + end + if length(titles)>=4 + var{4} = get(otherDataEdit,'string'); + end + vars = var; + delete(f); +catch + vars = {}; + try + delete(f); + end +end + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% quit xjview +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_quit(hObject, eventdata, warnstate) +warning(warnstate) +% try +% rmdir('xjviewtmp'); +% end +delete(gcf); + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% mouse double click +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function figureMouseUpFcn(hObject, eventdata) +status = get(hObject, 'SelectionType'); + +% double click +if strcmp(status, 'open') + handles = guidata(hObject); + CallBack_loadImagePush(handles.loadImagePush, eventdata); +else + return +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% change edit image file name +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_imageFileEdit(hObject, eventdata) +handles = guidata(hObject); +filename = get(hObject, 'String'); +filename = str2cell(filename); +CallBack_loadImagePush(handles.loadImagePush, eventdata, filename); + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% click load image file button +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_loadImagePush(hObject, eventdata, thisfilename) +handles = guidata(hObject); + +handles.imageFileName=[]; handles.M=[]; handles.DIM=[]; handles.TF=[]; handles.df=[]; +handles.mni=[]; handles.intensity=[]; handles.currentmni=[]; handles.currentintensity=[]; handles.currentDisplayMNI=[]; handles.currentDisplayIntensity=[]; + +if ~exist('thisfilename') + thisfilename = ''; +end + +if isstruct(thisfilename) + handles.imageFileName = {''}; + handles.mni = {thisfilename.mni}; + handles.intensity = {thisfilename.intensity}; + handles.M = {thisfilename.M}; + handles.DIM = {thisfilename.DIM}; + handles.TF = {'S'}; + handles.df = {1}; + handles.pValue = 1; + set(handles.pValueEdit, 'string', '1'); + handles.clusterSizeThreshold = 0; + set(handles.clusterSizeThresholdEdit, 'String', '0'); +else + [handles.imageFileName, handles.M, handles.DIM, handles.TF, handles.df, handles.mni, handles.intensity] = getImageFile(thisfilename); +end + +if isempty(handles.imageFileName) + return +end + +different = 0; % image files are same or different? +if length(handles.TF)>1 + for ii=1:length(handles.TF) + if ( strcmp(handles.TF{ii},handles.TF{1}) || isempty(handles.TF{ii}) && isempty(handles.TF{1}) ) & isequal(handles.df{ii},handles.df{1}) & isequal(handles.M{ii},handles.M{1}) & isequal(handles.DIM{ii},handles.DIM{1}) + continue; + else + warndlg('Images are from different statistics or sources.', 'Warning'); + %set(handles.infoTextBox, 'string', 'Images are from different statistics or sources.'); + beep; + different = 1; + break; + end + end +end + +% reset files with empty df/TF to df=1 and TF='S'. 'S'=='T' but has a tag +% meaning it is changed. +resetTF = 0; +for ii=1:length(handles.TF) + if isempty(handles.TF{ii}) + handles.TF{ii} = 'S'; + resetTF = 1; + end + if isempty(handles.df{ii}) | isequal(handles.df{ii},0) + handles.df{ii} = 1; + resetTF = 1; + end +end + +handles.currentmni = handles.mni; +handles.currentintensity = handles.intensity; + +set(handles.dfEdit, 'String', cell2str(handles.df)); +set(handles.imageFileEdit, 'String', cell2str(handles.imageFileName)); % s=-log10(p) +maxs = maxcell(t2s(cellmax(handles.intensity,'abs'),handles.df, handles.TF),'abs'); +if isinf(maxs); maxs = 20; end +set(handles.slider, 'Max', maxs, 'Min', 0, 'sliderstep',[min(maxs/100,0.05),min(maxs/100,0.05)]); +if handles.TF{1}=='T' & different == 0 + str = [blanks(length(' intensit')) 'T=']; +elseif handles.TF{1}=='F' & different == 0 + str = [blanks(length(' intensit')) 'F=']; +else + str=' intensity='; +end +set(handles.intensityThresholdText, 'String', str); +set(handles.figure,'Name',['xjView: ' cell2str(handles.imageFileName)]); +try + for ii=1:length(handles.hLegend) + delete(handles.hLegend{ii}); + end +end +if length(handles.TF)>1 + colours = repmat([1 0 0;1 1 0;0 1 0;0 1 1;0 0 1;1 0 1],100,1); + for ii=1:min(length(handles.TF),10) + [tmp,filename] = fileparts(handles.imageFileName{ii}); + if ii == 10; filename = '......'; end + pos0 = handles.sectionViewPosition; + pos(1) = pos0(1)+pos0(3)/2+0.03; + pos(2) = pos0(2)+ii/50-0.03; + pos(3) = 0.12; + pos(4) = 0.02; + handles.hLegend{ii}=uicontrol(handles.figure, 'style','text',... + 'unit','normalized','position',pos,... + 'string', filename,... + 'horizontal','left',... + 'fontweight','bold',... + 'ForeGroundColor',colours(ii,:)); + end + c_names = {'red';'yellow';'green';'cyan';'blue';'magenta'}; +end +guidata(hObject, handles); + +global M_; +global DIM_; +global XJVIEWURL_; +M_ = handles.M{1}; +DIM_ = handles.DIM{1}; + +if resetTF==1 + set(handles.pValueEdit,'string',1); +end +CallBack_pValueEdit(handles.pValueEdit, eventdata); + +% display info +% try +% %urlread([XJVIEWURL_ '/guestbook/stat.php']); +% s = urlread([XJVIEWURL_ '/toUser.txt']); +% report{1} = s; +% catch +% report{1} = 'Welcome to xjView 8'; +% end + +for jj=1:length(handles.imageFileName) + report{2+6*(jj-1)} = cell2str(handles.imageFileName(jj)); + if handles.TF{jj} == 'T' | handles.TF{jj} == 'F' + report{3+6*(jj-1)} = ['This is a ' handles.TF{jj} ' test image.']; + else + report{3+6*(jj-1)} = '';%['I don''t know what test this image came from.']; + end + report{4+6*(jj-1)} = 'mat = '; + report{5+6*(jj-1)} = num2str(handles.M{jj}); + report{6+6*(jj-1)} = 'dimension = '; + report{7+6*(jj-1)} = num2str(handles.DIM{jj}); +end +set(handles.infoTextBox, 'string', report); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% pValueEdit +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_pValueEdit(hObject, eventdata) +handles = guidata(hObject); +tmp = str2double(get(hObject, 'String')); +tmps = -log10(tmp); +if isnan(tmp) | tmp < 0 | tmp > 1 + errordlg('I don''t understand the input.','error'); + set(hObject, 'String', handles.pValue); + return +end + +if tmps>get(handles.slider,'max') | tmps maxcell(cellmax(handles.intensity,'abs')) + tmp = maxcell(cellmax(handles.intensity,'abs')); +end + +handles.pValue = t2p(tmp, handles.df{1}, handles.TF{1}); +handles.intensityThreshold = p2t(num2cell(handles.pValue*ones(1,length(handles.TF))), handles.df, handles.TF); +set(handles.slider,'Value', -log10(handles.pValue)); +set(handles.intensityThresholdEdit, 'String', cell2str(handles.intensityThreshold)); +guidata(hObject, handles); +CallBack_slider(hObject, eventdata, -log10(handles.pValue)); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% slider bar +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_slider(hObject, eventdata, value) + +handles = guidata(hObject); + +if exist('value') + s = value; + if s > get(handles.slider,'max') + s = get(handles.slider,'max')*0.99; + end + if s < get(handles.slider,'min') + s = get(handles.slider,'min'); + end +else + s = get(hObject,'Value'); +end + +set(handles.slider, 'value', s); +pvalue = 10^(-s); +set(handles.pValueEdit,'String',num2str(pvalue)); +t = p2t(num2cell(pvalue*ones(1,length(handles.TF))), handles.df, handles.TF); +handles.intensityThreshold = t; +set(handles.intensityThresholdEdit, 'String', cell2str(handles.intensityThreshold)); +handles.pValue = pvalue; +for ii=1:length(handles.TF) + pos{ii} = find(abs(handles.intensity{ii})>=t{ii}); + handles.currentintensity{ii} = handles.intensity{ii}(pos{ii}); + handles.currentmni{ii} = handles.mni{ii}(pos{ii},:); +end + +guidata(hObject,handles); + +if get(handles.allIntensityRadio, 'Value') + CallBack_allIntensityRadio(handles.allIntensityRadio, eventdata); +elseif get(handles.positiveIntensityRadio, 'Value') + CallBack_allIntensityRadio(handles.positiveIntensityRadio, eventdata, '+'); +elseif get(handles.negativeIntensityRadio, 'Value') + CallBack_allIntensityRadio(handles.negativeIntensityRadio, eventdata, '-'); +end + +% set(handles.infoTextBox, 'string', {'Don''t drag the slider bar too fast. Release your mouse button at least 1 second later.', ... +% 'This sounds stupid. But there is a bug (probably MatLab bug) which I can'' fix right now.', ... +% 'I suggest you confirm the correctness of the current display by press Enter in the pValue edit box.'}); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% display intensity all+- radios +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_allIntensityRadio(hObject, eventdata, pnall) +% pnall = '+', '-', or 'c'. c means current (simply update drawing) +% +handles = guidata(hObject); + +currentselect = []; +if get(handles.allIntensityRadio, 'Value'); currentselect = handles.allIntensityRadio; thispnall = 'a'; end +if get(handles.positiveIntensityRadio, 'Value'); currentselect = handles.positiveIntensityRadio; thispnall = '+'; end +if get(handles.negativeIntensityRadio, 'Value'); currentselect = handles.negativeIntensityRadio; thispnall = '-'; end + +set(handles.allIntensityRadio, 'Value', 0); +set(handles.positiveIntensityRadio, 'Value', 0); +set(handles.negativeIntensityRadio, 'Value', 0); + +if exist('pnall') + if pnall=='c' + hObject = currentselect; + pnall = thispnall; + end +end + +set(hObject, 'Value', 1); + +if ~isfield(handles,'currentintensity') + return +end +for ii=1:length(handles.TF) + if exist('pnall') + if pnall == '-' + pos{ii} = find(handles.currentintensity{ii} < 0); + elseif pnall == '+' + pos{ii} = find(handles.currentintensity{ii} > 0); + elseif pnall == 'a' + pos{ii} = 1:length(handles.currentintensity{ii}); + end + else + pos{ii} = 1:length(handles.currentintensity{ii}); + end + intensity{ii} = handles.currentintensity{ii}(pos{ii}); + mni{ii} = handles.currentmni{ii}(pos{ii},:); + cor{ii} = mni2cor(mni{ii}, handles.M{ii}); + + if ~isempty(cor{ii}) + A = spm_clusters(cor{ii}'); + pos0 = []; + for kk = 1:max(A) + jj = find(A == kk); + if length(jj) >= handles.clusterSizeThreshold; pos0 = [pos0 jj]; end + end + handles.currentDisplayMNI{ii} = mni{ii}(pos0,:); + handles.currentDisplayIntensity{ii} = intensity{ii}(pos0); + else + handles.currentDisplayMNI{ii} = mni{ii}([],:); + handles.currentDisplayIntensity{ii} = intensity{ii}([]); + end +end +[handles.hReg, handles.hSection, handles.hcolorbar] = Draw(handles.currentDisplayMNI, handles.currentDisplayIntensity, hObject, handles); +if get(handles.allIntensityRadio, 'Value') & max(handles.currentDisplayIntensity{1}) < 0 + warndlg('No supra-threshold positive intensity. Only negative intensity is displayed.'); + set(handles.negativeIntensityRadio, 'Value', 1); + set(handles.allIntensityRadio, 'Value', 0); + return +end +if get(handles.allIntensityRadio, 'Value') & min(handles.currentDisplayIntensity{1}) > 0 + warndlg('No supra-threshold negative intensity. Only positive intensity is displayed.'); + set(handles.positiveIntensityRadio, 'Value', 1); + set(handles.allIntensityRadio, 'Value', 0); + return +end + +try + set(handles.figure,'currentaxes', handles.glassViewAxes); + xrange = xlim; + yrange = ylim; + try + delete(handles.hGlassText) + end + if ~isempty(handles.selectedCluster,1) + %handles.hGlassText = text(xrange(1)+diff(xrange)*0.6, yrange(1)+diff(yrange)*0.9, [num2str(size(handles.selectedCluster,1)) ' clusters selected']); + set(handles.infoTextBox, 'string', [num2str(size(handles.selectedCluster,1)) ' clusters selected']); + end +end +guidata(hObject, handles); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% render view check? +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_renderViewCheck(hObject, eventdata) +handles = guidata(hObject); +check = get(hObject, 'Value'); +%if check + CallBack_allIntensityRadio(hObject, eventdata, 'c'); +%end + +try +if check; % if display render, will make feed invisible + set(handles.feed,'visible','off'); + set(handles.feedclick,'visible','off'); + set(handles.postfeedclick,'visible','off'); + set(handles.refreshclick,'visible','off'); +else + set(handles.feed,'visible','on'); + set(handles.feedclick,'visible','on'); + set(handles.postfeedclick,'visible','on'); + set(handles.refreshclick,'visible','on'); +end +end +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% change rend style +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_renderStylePop(hObject, eventdata) +CallBack_renderViewCheck(hObject, eventdata); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% which section view target file? list box +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_sectionViewListbox(hObject, eventdata) +handles = guidata(hObject); +contents = get(handles.sectionViewListbox,'String'); +currentsel = contents{get(handles.sectionViewListbox,'Value')}; +handles.sectionViewTargetFile = getSectionViewTargetFile(handles.spmdir, currentsel); +guidata(hObject, handles); +CallBack_allIntensityRadio(hObject, eventdata, 'c'); + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% get sectionviewtargetfile +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function sectionViewTargetFile = getSectionViewTargetFile(spmdir, selectedcontent) +if findstr('SPM2',spm('ver')) + fileext = 'mnc'; +else%if findstr('SPM5',spm('ver')) + fileext = 'nii'; +end +currentsel = selectedcontent; +if ~isempty(strfind(currentsel, 'single')) + sectionViewTargetFile = fullfile(spmdir, 'canonical', ['single_subj_T1.' fileext]); +elseif ~isempty(strfind(currentsel, '152PD')) + sectionViewTargetFile = fullfile(spmdir, 'canonical', ['avg152PD.' fileext]); +elseif ~isempty(strfind(currentsel, '152T1')) + sectionViewTargetFile = fullfile(spmdir, 'canonical', ['avg152T1.' fileext]); +elseif ~isempty(strfind(currentsel, '152T2')) + sectionViewTargetFile = fullfile(spmdir, 'canonical', ['avg152T2.' fileext]); +elseif ~isempty(strfind(currentsel, '305T1')) + sectionViewTargetFile = fullfile(spmdir, 'canonical', ['avg305T1.' fileext]); +elseif strcmp(currentsel, 'ch2') + sectionViewTargetFile = fullfile(spmdir, 'canonical', 'ch2.img'); +elseif strcmp(currentsel, 'ch2bet') + sectionViewTargetFile = fullfile(spmdir, 'canonical', 'ch2bet.img'); +elseif strcmp(currentsel, 'aal') + sectionViewTargetFile = fullfile(spmdir, 'canonical', 'aal.img'); +elseif strcmp(currentsel, 'brodmann') + sectionViewTargetFile = fullfile(spmdir, 'canonical', 'brodmann.img'); +end +return + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% xhairs in section view? +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_xHairCheck(hObject, eventdata) +check = get(hObject, 'Value'); +if check + spm_orthviews('Xhairs','off'); +else + spm_orthviews('Xhairs','on'); +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% other target file for section view, push button +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_sectionViewMoreTargetPush(hObject, eventdata) +handles = guidata(hObject); +[filename, pathname, filterindex] = uigetfile('*', 'Pick an target file'); +if isequal(filename,0) | isequal(pathname,0) + return; +end +handles.sectionViewTargetFile = fullfile(pathname, filename); +guidata(hObject, handles); +CallBack_allIntensityRadio(hObject, eventdata, 'c'); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% set colorbar range +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_setTRangeEdit(hObject, eventdata) +global TMAX_; +handles = guidata(hObject); +TMAX_ = get(hObject, 'String'); +if isempty(str2num(TMAX_)) && ~strcmp(TMAX_, 'auto') + return; +end +guidata(hObject, handles); +CallBack_allIntensityRadio(hObject, eventdata, 'c'); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% change degree of freedome +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_dfEdit(hObject, eventdata) +handles = guidata(hObject); +tmp = str2cell(get(hObject, 'String')); +if iscellstr(tmp) + errordlg('Please input a valid number.','error'); + try + set(hObject, 'String', handles.df); + catch + set(hObject, 'String', ''); + end + return +end +handles.df=tmp; + + +if isfield(handles,'TF') + t = p2t(mat2cell(handles.pValue*ones(1,length(handles.TF))), handles.df, handles.TF); + handles.intensityThreshold = t; + set(handles.intensityThresholdEdit, 'String', cell2str(t)); +end + +guidata(hObject, handles); +CallBack_pValueEdit(handles.pValueEdit, eventdata); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% get structure push +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_getStructurePush(hObject, eventdata) +handles = guidata(hObject); +xyz = spm_XYZreg('GetCoords',handles.hReg); +tmp_coor = cuixuFindStructure(xyz', handles.DB); +set(handles.structureEdit,'String', tmp_coor{1}); +handles.currentxyz = xyz'; +guidata(hObject, handles); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% structure edit +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_structureEdit(action, xyz, thisfun, hReg) + +try + handles=guidata(hReg); +catch + return; +end + +handles.currentxyz = xyz'; +handles.hReg = hReg; +guidata(hReg, handles); + +try + [tmp_coor, cellstructure] = cuixuFindStructure(xyz', handles.DB); +catch + return; +end + +set(handles.structureEdit,'String', tmp_coor{1}); + +for ii=[5 3 2 6 1 4] + if strfind('undefined', cellstructure{ii}) + continue; + else + set(handles.searchContentEdit,'string', trimStructureStr(cellstructure{ii})); + return; + end +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% trim str +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function out = trimStructureStr(str) +pos = findstr('(', str); +if ~isempty(pos) + str(pos-1:end)=[]; +end +out = str; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% cluster size threshold edit +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_clusterSizeThresholdEdit(hObject, eventdata) +handles = guidata(hObject); +tmp = str2double(get(hObject, 'String')); +if isnan(tmp) | tmp<0 + errordlg('Please input a valid number.','error'); + try + set(hObject, 'String', handles.clusterSizeThreshold); + catch + set(hObject, 'String', '5'); + end + return +end +handles.clusterSizeThreshold=tmp; + +guidata(hObject, handles); +CallBack_allIntensityRadio(hObject, eventdata, 'c'); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% save image push button +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_saveImagePush(hObject, eventdata, thisfilename, isMask) +handles = guidata(hObject); + +if exist('thisfilename') + if ~strcmp(deblank(thisfilename), '') + if isfield(handles,'imageFileName') + if ~isempty(handles.imageFileName) + mni2mask(cell2mat(handles.currentDisplayMNI'), thisfilename, cell2mat(handles.currentDisplayIntensity), handles.M{1}, handles.DIM{1}, handles.imageFileName{1}); + return; + end + end + mni2mask(cell2mat(handles.currentDisplayMNI'), thisfilename, cell2mat(handles.currentDisplayIntensity), handles.M{1}, handles.DIM{1}); + return; + end +end + +[filename, pathname] = uiputfile('*.img', 'Save image file as', get(handles.saveImageFileEdit, 'string')); +if isequal(filename,0) | isequal(pathname,0) + return +else + thisfilename = fullfile(pathname, filename); +end + +if isfield(handles,'imageFileName') + if ~isempty(handles.imageFileName{1}) + mni2mask(cell2mat(handles.currentDisplayMNI'), thisfilename, cell2mat(handles.currentDisplayIntensity'), handles.M{1}, handles.DIM{1}, handles.imageFileName{1}, isMask); + return; + end +end + +mni2mask(cell2mat(handles.currentDisplayMNI'), thisfilename, cell2mat(handles.currentDisplayIntensity), handles.M{1}, handles.DIM{1}, '', isMask); + +return; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% save image edit +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_saveImageFileEdit(hObject, eventdata) +handles = guidata(hObject); +CallBack_saveImagePush(handles.saveImagePush, eventdata, get(hObject,'string')); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% save result push +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_saveResultPSPush(hObject, eventdata, thisfilename) + +handles = guidata(hObject); + +if exist('thisfilename') + if ~strcmp(deblank(thisfilename), '') + spm_print(handles.figure); + if strcmp(handles.system, 'linux') + system(['ps2pdf spm2.ps']); + system(['mv spm2.ps ' thisfilename]); + [p,f,ext]=fileparts(thisfilename); + system(['mv spm2.pdf ' fullfile(p,f) '.pdf']); + elseif strcmp(handles.system, 'windows') + system(['move spm2.ps ' '"' thisfilename '"']); + end + return; + end +end + +[filename, pathname] = uiputfile('*.ps', 'Save result as', get(handles.saveResultPSEdit, 'string')); +if isequal(filename,0) | isequal(pathname,0) + return +else + thisfilename = fullfile(pathname, filename); +end + + +% print +H = findobj(get(handles.figure,'Children'),'flat','Type','axes'); +un = cellstr(get(H,'Units')); +pos = get(H,'position'); +index = []; + +for ii=1:length(H) + if findstr('pixels', un{ii}) + continue; + end + if pos{ii}(1)>0.4 & pos{ii}(2) > 0.5 + set(H(ii),'position',[pos{ii}(1), pos{ii}(2), pos{ii}(3)*3/4, pos{ii}(4)]); + index = [index ii]; + end +end + + +spm_print(handles.figure); +if strcmp(handles.system, 'linux') + system(['ps2pdf spm2.ps']); + system(['mv spm2.ps ' thisfilename]); + [p,f,ext]=fileparts(thisfilename); + system(['mv spm2.pdf ' fullfile(p,f) '.pdf']); +elseif strcmp(handles.system, 'windows') + system(['move spm2.ps ' '"' thisfilename '"']); +end + +% set the position back +set(H(index), {'position'}, pos(index)); + + +% printstr = ['print -dpsc2 -painters -noui ' '''' thisfilename '''']; +% try +% orient portrait +% eval(printstr); +% printsuccess = 1; +% catch +% errordlg('Print to ps file failed', 'print error'); +% printsuccess = 0; +% end +%set(H,{'Units'},un); +% if strcmp(handles.system, 'linux') & printsuccess == 1 +% system(['ps2pdf ' '''' thisfilename '''']); +% end + +return; + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% save result edit +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_saveResultPSEdit(hObject, eventdata) +handles = guidata(hObject); +CallBack_saveResultPSPush(hObject, eventdata, get(handles.saveResultPSEdit,'string')); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% select a cluster +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_selectThisClusterPush(hObject, eventdata) +handles = guidata(hObject); + +try + xyz = handles.currentxyz'; +catch + xyz = spm_XYZreg('GetCoords',handles.hReg); +end +try + handles.selectedCluster = [handles.selectedCluster; xyz']; +catch + handles.selectedCluster = xyz'; +end + +set(handles.figure,'currentaxes', handles.glassViewAxes); +xrange = xlim; +yrange = ylim; +try + delete(handles.hGlassText) +end +%handles.hGlassText = text(xrange(1)+diff(xrange)*0.6, yrange(1)+diff(yrange)*0.9, [num2str(size(handles.selectedCluster,1)) ' clusters selected']); +set(handles.infoTextBox, 'string', [num2str(size(handles.selectedCluster,1)) ' clusters selected']); +guidata(hObject, handles); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% unselect a cluster +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_clearSelectedClusterPush(hObject, eventdata) +handles = guidata(hObject); +try + handles = rmfield(handles,'selectedCluster'); +end +set(handles.figure,'currentaxes', handles.glassViewAxes); +xrange = xlim; +yrange = ylim; +try + %delete(handles.hGlassText) + set(handles.infoTextBox, 'string', ['No clusters selected']); +end + +guidata(hObject, handles); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% pick a cluster +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_pickThisClusterPush(hObject, eventdata) +handles = guidata(hObject); +mni = cell2mat(handles.currentDisplayMNI'); +if isempty(mni) + %errordlg('No cluster is picked up.','oops'); + set(handles.infoTextBox, 'string', 'No cluster is picked up.'); + beep + return; +end + +if ~isfield(handles, 'selectedCluster') | isempty(handles.selectedCluster) + try + xyz = handles.currentxyz'; + catch + xyz = spm_XYZreg('GetCoords',handles.hReg); + end + handles.selectedCluster = xyz'; +end + +intensity = cell2mat(handles.currentDisplayIntensity'); +cor = mni2cor(mni, handles.M{1}); +A = spm_clusters(cor'); +xyzcor = mni2cor(handles.selectedCluster, handles.M{1}); + +pos = []; +for ii = 1:size(xyzcor,1) + pos0 = find(cor(:,1)==xyzcor(ii,1) & cor(:,2)==xyzcor(ii,2) & cor(:,3)==xyzcor(ii,3)); + if isempty(pos0) + continue; + end + pos = [pos find(A==A(pos0(1)))]; +end +if isempty(pos) + %errordlg('No cluster is picked up.','oops'); + set(handles.infoTextBox, 'string', 'No cluster is picked up.'); + beep + return +end + +pos = unique(pos); + +tmpmni = mni(pos,:); +tmpintensity = intensity(pos); + +[B,I,J] = unique(tmpmni, 'rows'); +handles.currentDisplayMNI = {B}; +handles.currentDisplayIntensity = {tmpintensity(I,:)}; + +handles.currentxyz = handles.selectedCluster(end,:); + +[handles.hReg, handles.hSection, handles.hcolorbar] = Draw(handles.currentDisplayMNI, handles.currentDisplayIntensity, hObject, handles); +set(handles.figure,'currentaxes', handles.glassViewAxes); +xrange = xlim; +yrange = ylim; + +handles.selectedCluster = []; +try + delete(handles.hGlassText) +end + +guidata(hObject, handles); + +set(handles.thisClusterSizeEdit,'string', num2str(size(handles.currentDisplayMNI{1},1))); +str = get(handles.searchContentEdit, 'string'); +pos = findstr(' ', str); +str(pos) = []; +files = []; +for ii=1:length(handles.imageFileName) + [a,b,c] = fileparts(handles.imageFileName{ii}); + files = [files b]; +end +if ~isempty(files) + files = ['_from_' files]; +end + +set(handles.saveImageFileEdit, 'string', [str files '.img']); + +% list structure of voxels in this cluster +[a, b] = cuixuFindStructure(cell2mat(handles.currentDisplayMNI'), handles.DB); +names = unique(b(:)); +index = NaN*zeros(length(b(:)),1); +for ii=1:length(names) + pos = find(strcmp(b(:),names{ii})); + index(pos) = ii; +end + +for ii=1:max(index) + report{ii,1} = names{ii}; + report{ii,2} = length(find(index==ii)); +end +for ii=1:size(report,1) + for jj=ii+1:size(report,1) + if report{ii,2} < report{jj,2} + tmp = report(ii,:); + report(ii,:) = report(jj,:); + report(jj,:) = tmp; + end + end +end +report = [{'structure','# voxels'}; {'--TOTAL # VOXELS--', length(a)}; report]; +% format long +% disp(b) +% disp(report) +% format +report2 = {sprintf('%s\t%s',report{1,2}, report{1,1}),''}; +for ii=2:size(report,1) + if strcmp('undefined', report{ii,1}); continue; end + report2 = [report2, {sprintf('%5d\t%s',report{ii,2}, report{ii,1})}]; +end + +report2 = [report2, {'','select and Ctrl-C to copy'}]; +% f = figure('unit','normalized', 'menubar','none', 'position', [0.3 0.2 0.2 min(0.7,0.016*length(report2))], 'name', 'cluster information', 'NumberTitle','off'); +% hEdit = uicontrol(f,'style','edit',... +% 'unit','normalized','position',[0 0 1 1],... +% 'horizontal','left',... +% 'BackgroundColor', 'w',... +% 'String', report2,... +% 'max',2,'min',0); + +set(handles.infoTextBox, 'string', report2); +return + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% report +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_reportPush(hObject, eventdata) +handles = guidata(hObject); + +set(handles.infoTextBox, 'string', 'This function will generate a report of this functional image. Check out your MatLab command window. You may copy and past to a file for later use.'); +if(length(handles.imageFileName)~=1) + set(handles.infoTextBox, 'string', 'This function only applies to 1 functional image.'); + return; +end + +mni = cell2mat(handles.currentDisplayMNI'); +if isempty(mni) + %errordlg('No cluster is picked up.','oops'); + set(handles.infoTextBox, 'string', 'No cluster is found. So no report will be generated.'); + beep + return; +end + +disp(handles.imageFileName{1}); +disp(['Type: ' num2str(handles.TF{1})]); +disp(['df: ' num2str(handles.df{1})]); +disp('Threshold') +disp(['-- p value = ' num2str(handles.pValue)]); +disp(['-- intensity = ' num2str(handles.intensityThreshold{1})]); +disp(['-- cluster size = ' num2str(handles.clusterSizeThreshold)]); + +intensity = cell2mat(handles.currentDisplayIntensity'); +cor = mni2cor(mni, handles.M{1}); +A = spm_clusters(cor'); + +clusterID = unique(A); +numClusters = length(clusterID); +disp(['Number of clusters found: ' num2str(numClusters)]); + +for mm = clusterID + pos = find(A == clusterID(mm)); + numVoxels = length(pos); + tmpmni = mni(pos,:); + tmpintensity = intensity(pos); + + peakpos = find(abs(tmpintensity) == max(abs(tmpintensity))); + peakpos = peakpos(1); + peakcoord = tmpmni(peakpos,:); + peakintensity = tmpintensity(peakpos); + + % list structure of voxels in this cluster + [a, b] = cuixuFindStructure(tmpmni, handles.DB); + names = unique(b(:)); + index = NaN*zeros(length(b(:)),1); + for ii=1:length(names) + pos = find(strcmp(b(:),names{ii})); + index(pos) = ii; + end + + report = {}; + + for ii=1:max(index) + report{ii,1} = names{ii}; + report{ii,2} = length(find(index==ii)); + end + for ii=1:size(report,1) + for jj=ii+1:size(report,1) + if report{ii,2} < report{jj,2} + tmp = report(ii,:); + report(ii,:) = report(jj,:); + report(jj,:) = tmp; + end + end + end + report = [{'structure','# voxels'}; {'--TOTAL # VOXELS--', length(a)}; report]; + + report2 = {sprintf('%s\t%s',report{1,2}, report{1,1}),''}; + for ii=2:size(report,1) + if strcmp('undefined', report{ii,1}); continue; end + report2 = [report2, {sprintf('%5d\t%s',report{ii,2}, report{ii,1})}]; + end + + disp(['----------------------']) + disp(['Cluster ' num2str(mm)]) + disp(['Number of voxels: ' num2str(numVoxels)]) + disp(['Peak MNI coordinate: ' num2str(peakcoord)]) + [a,b] = cuixuFindStructure(peakcoord, handles.DB); + disp(['Peak MNI coordinate region: ' a{1}]); + disp(['Peak intensity: ' num2str(peakintensity)]) + for kk=1:length(report2) + disp(report2{kk}); + end +end +return + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% sliceView push +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_sliceViewPush(hObject, eventdata) +if(nargin == 1) + eventdata = []; +end +handles = guidata(hObject); + + +global sliceview + +if(~isfield(sliceview, 'fig') || ~ishandle(sliceview.fig)) + sliceview.viewtype = 't'; + sliceview.row = 8; + sliceview.col = 8; + sliceview.spacing = 4; + sliceview.page = 1; + sliceview.data = {{},{},{}}; % t,s,c + sliceview.slices = {[],[],[]};% t,s,c + sliceview.colormap = ''; + sliceview.fig = figure('color','k', 'unit','normalized','position',[0.1 0.1 .6 .8],'toolbar','none', 'name', 'xjView slice view', 'NumberTitle','off'); + sliceview.ax = axes('Visible','on','DrawMode','fast','Parent',sliceview.fig,... + 'YDir','normal','Ydir','normal','XTick',[],'YTick',[], 'position', [0.15 0.05 .8 .9]); + %handles.sliceview.d = image([],'Tag','Transverse','Parent',handles.sliceview.ax); + set(sliceview.ax,'XTick',[],'YTick',[]); + axis equal + set(sliceview.ax,'color','k'); + %setcolormap(colormp) + width = 0.05; + height = 0.025; + step = 0.025; + labeloffset = step/2; + + uicontrol(sliceview.fig,'style','text',... + 'unit','normalized','position',[0 step-labeloffset width height],... + 'horizontal','left',... + 'BackGroundColor', 'k', ... + 'ForeGroundColor', [1 1 1], ... + 'String', 'row #'); + uicontrol(sliceview.fig,'style','text',... + 'unit','normalized','position',[0 step*3-labeloffset width height],... + 'horizontal','left',... + 'BackGroundColor', 'k', ... + 'ForeGroundColor', [1 1 1], ... + 'String', 'column #'); + uicontrol(sliceview.fig,'style','text',... + 'unit','normalized','position',[0 step*5-labeloffset width*2 height],... + 'horizontal','left',... + 'BackGroundColor', 'k', ... + 'ForeGroundColor', [1 1 1], ... + 'String', 'spacing (mm)'); + uicontrol(sliceview.fig,'style','text',... + 'unit','normalized','position',[0 step*7-labeloffset width height],... + 'horizontal','left',... + 'BackGroundColor', 'k', ... + 'ForeGroundColor', [1 1 1], ... + 'String', 'scroll'); + + uicontrol(sliceview.fig,'style','edit',... + 'unit','normalized','position',[0 0 width height],... + 'horizontal','left',... + 'BackgroundColor', 'w',... + 'String', sliceview.row,... + 'callback',@sliceViewRowChanged); + + uicontrol(sliceview.fig,'style','edit',... + 'unit','normalized','position',[0 step*2 width height],... + 'horizontal','left',... + 'BackgroundColor', 'w',... + 'String', sliceview.col,... + 'callback',@sliceViewColChanged); + + uicontrol(sliceview.fig,'style','edit',... + 'unit','normalized','position',[0 step*4 width height],... + 'horizontal','left',... + 'BackgroundColor', 'w',... + 'String', sliceview.spacing,... + 'callback',@sliceViewSpacingChanged); + +% uicontrol(sliceview.fig,'style','edit',... +% 'unit','normalized','position',[0 step*6 width height],... +% 'horizontal','left',... +% 'BackgroundColor', 'w',... +% 'String', sliceview.page,... +% 'callback',@sliceViewPageChanged); + uicontrol(sliceview.fig,'style','push',... + 'unit','normalized','position',[0 step*6 width height],... + 'horizontal','left',... + 'BackgroundColor', 'k',... + 'ForegroundColor', 'w', ... + 'String', 'up',... + 'callback',@sliceViewPageChangedP); + uicontrol(sliceview.fig,'style','push',... + 'unit','normalized','position',[0.05 step*6 width height],... + 'horizontal','left',... + 'BackgroundColor', 'k',... + 'ForegroundColor', 'w', ... + 'String', 'down',... + 'callback',@sliceViewPageChangedN); + uicontrol(... + 'Units','normalized', ... + 'ListboxTop',0, ... + 'Position',[0 step*8 width*2 height],... + 'String',{'transverse';'coronal';'sagittal'}, ... + 'Style','popupmenu', ... + 'value',1,... + 'callback',@sliceViewTypeChanged); + +end + +figure(sliceview.fig) + +viewtype = sliceview.viewtype; +row = sliceview.row; +col = sliceview.col; +spacing = sliceview.spacing; +page = sliceview.page; +slice_fig = sliceview.fig; +ax = sliceview.ax; +%d = handles.sliceview.d; + +[slicedata, colormp, slices] = cuixu_getSliceViewData(viewtype,row,col, spacing, page); + +if isempty(slices) + return; +end + +for ii=1:length(slices) + if(viewtype == 's') + postmp = find(slices(ii) - sliceview.slices{2} == 0); + if(isempty(postmp)) + sliceview.data{2}{end+1} = slicedata{ii}; + sliceview.slices{2}(end+1) = slices(ii); + end + elseif(viewtype == 't') + postmp = find(slices(ii) - sliceview.slices{1} == 0); + if(isempty(postmp)) + sliceview.data{1}{end+1} = slicedata{ii}; + sliceview.slices{1}(end+1) = slices(ii); + end + elseif(viewtype == 'c') + postmp = find(slices(ii) - sliceview.slices{3} == 0); + if(isempty(postmp)) + sliceview.data{3}{end+1} = slicedata{ii}; + sliceview.slices{3}(end+1) = slices(ii); + end + end + +end + + + +%slice_fig = figure('color','k', 'unit','normalized','position',[0.1 0.1 .6 .8],'toolbar','none'); + + +if(length(size(slicedata{1})) == 3) + [nx, ny, nz] = size(slicedata{1}); + slicedatafinal = zeros(nx*row, ny*col, nz ); + for ii=1:length(slicedata) + slicedatafinal(nx*(floor((ii-1)/col))+1:nx*(1+floor((ii-1)/col)), ny*(mod(ii-1,col))+1:ny*(mod(ii-1,col)+1), :) = slicedata{ii}; + end +else + [nx, ny] = size(slicedata{1}); + slicedatafinal = zeros(nx*row, ny*col ); + for ii=1:length(slicedata) + slicedatafinal(nx*(floor((ii-1)/col))+1:nx*(1+floor((ii-1)/col)), ny*(mod(ii-1,col))+1:ny*(mod(ii-1,col)+1)) = slicedata{ii}; + end +end + + +try + delete(handles.sliceview.d) +catch + []; +end + +handles.sliceview.d = image(slicedatafinal,'Tag','Transverse','Parent',sliceview.ax); + +% put slice positions +for ii=1:length(slicedata) + %text(nx*(floor((ii-1)/col))+1:nx*(1+floor((ii-1)/col)), ny*(mod(ii-1,col))+1:ny*(mod(ii-1,col)+1), num2str(slices(ii)), 'color', 'w'); + text(ny*(mod(ii-1,col))+1, nx*(floor((ii-1)/col))+1+20, num2str(slices(ii)), 'color', 'w'); +end + +set(sliceview.ax,'XTick',[],'YTick',[]); +axis(sliceview.ax, 'equal'); +set(sliceview.ax,'color','k'); +%setcolormap(colormp) +% for ii=1:length(slicedata) +% +% ax = subplot(row,col,ii); +% % ax = axes('Visible','on','DrawMode','fast','Parent',slice_fig,... +% % 'YDir','normal','Ydir','normal','XTick',[],'YTick',[]); +% set(ax,'Visible','on','DrawMode','fast','Parent',slice_fig,... +% 'YDir','normal','Ydir','normal','XTick',[],'YTick',[], 'color','k'); +% d = image(slicedata{ii},'Tag','Transverse','Parent',ax); +% set(ax,'XTick',[],'YTick',[]); +% title(pos{ii},'color','w') +% axis equal +% set(ax,'color','k'); +% +% %set(d,'Cdata',imgt); +% if mn*mx < 0 +% setcolormap('gray-hot-cold') +% elseif mx > 0 +% setcolormap('gray-hot'); +% else +% setcolormap('gray-cold') +% end +% end + +guidata(hObject, handles); + +function sliceViewRowChanged(hObject, eventdata) +global sliceview +sliceview.row = str2num(get(hObject,'string')); +CallBack_sliceViewPush(hObject, eventdata); + +function sliceViewColChanged(hObject, eventdata) +global sliceview +sliceview.col = str2num(get(hObject,'string')); +CallBack_sliceViewPush(hObject, eventdata); + +function sliceViewSpacingChanged(hObject, eventdata) +global sliceview +sliceview.spacing = str2num(get(hObject,'string')); +CallBack_sliceViewPush(hObject, eventdata); + +function sliceViewPageChangedN(hObject, eventdata) +global sliceview +sliceview.page = sliceview.page + 1; +CallBack_sliceViewPush(hObject, eventdata); + +function sliceViewPageChangedP(hObject, eventdata) +global sliceview +sliceview.page = sliceview.page - 1; +CallBack_sliceViewPush(hObject, eventdata); + +function sliceViewTypeChanged(hObject, eventdata) +global sliceview +type = get(hObject,'value'); +if(type == 1) + sliceview.viewtype = 't'; +elseif(type == 2) + sliceview.viewtype = 'c'; +else + sliceview.viewtype = 's'; +end +%sliceview.data = {{},{},{}}; +%sliceview.slices = {[],[],[]}; +sliceview.page = 0; +CallBack_sliceViewPush(hObject, eventdata); + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% get slice view data +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function [slicedata, colormp, slices] = cuixu_getSliceViewData(viewtype,row,col, spacing,page) + +global sliceview + + +slicedata = {}; +pos = {}; +slices = -80+(page-1)*spacing*col:spacing:100+(page)*spacing; + +if(isempty(slices)) + colormp = ''; + return +end + +global st +bb = st.bb; +Dims = round(diff(bb)'+1); +is = inv(st.Space); +cent = is(1:3,1:3)*st.centre(:) + is(1:3,4); + +kk = 1; +for slice = slices + if(kk > row*col) + slices = slices(1:kk-1); + break; + end + +if(viewtype == 's') + cent(2) = -slice; + postmp = find(slice - sliceview.slices{2} == 0); + if(~isempty(postmp)) + slicedata{kk} = sliceview.data{2}{postmp(1)}; + kk = kk+1; + continue; + end +elseif(viewtype == 't') + cent(3) = slice; + postmp = find(slice - sliceview.slices{1} == 0); + if(~isempty(postmp)) + slicedata{kk} = sliceview.data{1}{postmp(1)}; + kk = kk+1; + continue; + end +elseif(viewtype == 'c') + cent(1) = slice; + postmp = find(slice - sliceview.slices{3} == 0); + if(~isempty(postmp)) + slicedata{kk} = sliceview.data{3}{postmp(1)}; + kk = kk+1; + continue; + end +end + +if(viewtype == 's') + +for i = 1 + M = st.vols{i}.premul*st.vols{i}.mat; + + + CM0 = [ 1 0 0 -bb(1,1)+1 + 0 0 1 -bb(1,3)+1 + 0 1 0 -cent(2) + 0 0 0 1]; + CM = inv(CM0*(st.Space\M)); + CD = Dims([1 3]); + + + + ok=1; + + eval('imgc = (spm_slice_vol(st.vols{i},CM,CD,st.hld))'';','ok=0;'); + + if (ok==0), fprintf('Image "%s" can not be resampled\n', st.vols{i}.fname); + else, + % get min/max threshold + if strcmp(st.vols{i}.window,'auto') + mn = -Inf; + mx = Inf; + else + mn = min(st.vols{i}.window); + mx = max(st.vols{i}.window); + end; + % threshold images + imgc = max(imgc,mn); imgc = min(imgc,mx); + % compute intensity mapping, if histeq is available + if license('test','image_toolbox') == 0 + st.vols{i}.mapping = 'linear'; + end; + switch st.vols{i}.mapping, + case 'linear', + case 'histeq', + % scale images to a range between 0 and 1 + imgc1=(imgc-min(imgc(:)))/(max(imgc(:)-min(imgc(:)))+eps); + img = histeq([imgt1(:); imgc1(:); imgs1(:)],1024); + imgc = reshape(img(numel(imgt1)+[1:numel(imgc1)]),size(imgc1)); + mn = 0; + mx = 1; + case 'quadhisteq', + % scale images to a range between 0 and 1 + imgt1=(imgt-min(imgt(:)))/(max(imgt(:)-min(imgt(:)))+eps); + imgc1=(imgc-min(imgc(:)))/(max(imgc(:)-min(imgc(:)))+eps); + imgs1=(imgs-min(imgs(:)))/(max(imgs(:)-min(imgs(:)))+eps); + img = histeq([imgt1(:).^2; imgc1(:).^2; imgs1(:).^2],1024); + imgt = reshape(img(1:numel(imgt1)),size(imgt1)); + imgc = reshape(img(numel(imgt1)+[1:numel(imgc1)]),size(imgc1)); + imgs = reshape(img(numel(imgt1)+numel(imgc1)+[1:numel(imgs1)]),size(imgs1)); + mn = 0; + mx = 1; + case 'loghisteq', + warning off % messy - but it may avoid extra queries + imgt = log(imgt-min(imgt(:))); + imgc = log(imgc-min(imgc(:))); + imgs = log(imgs-min(imgs(:))); + warning on + imgt(~isfinite(imgt)) = 0; + imgc(~isfinite(imgc)) = 0; + imgs(~isfinite(imgs)) = 0; + % scale log images to a range between 0 and 1 + imgt1=(imgt-min(imgt(:)))/(max(imgt(:)-min(imgt(:)))+eps); + imgc1=(imgc-min(imgc(:)))/(max(imgc(:)-min(imgc(:)))+eps); + imgs1=(imgs-min(imgs(:)))/(max(imgs(:)-min(imgs(:)))+eps); + img = histeq([imgt1(:); imgc1(:); imgs1(:)],1024); + imgt = reshape(img(1:numel(imgt1)),size(imgt1)); + imgc = reshape(img(numel(imgt1)+[1:numel(imgc1)]),size(imgc1)); + imgs = reshape(img(numel(imgt1)+numel(imgc1)+[1:numel(imgs1)]),size(imgs1)); + mn = 0; + mx = 1; + end; + % recompute min/max for display + if strcmp(st.vols{i}.window,'auto') + mx = -inf; mn = inf; + end; + + if ~isempty(imgc), + tmp = imgc(isfinite(imgc)); + mx = max([mx max(max(tmp))]); + mn = min([mn min(min(tmp))]); + end; + + if mx==mn, mx=mn+eps; end; + + if isfield(st.vols{i},'blobs'), + if ~isfield(st.vols{i}.blobs{1},'colour'), + % Add blobs for display using the split colourmap + scal = 64/(mx-mn); + dcoff = -mn*scal; + imgc = imgc*scal+dcoff; + + if isfield(st.vols{i}.blobs{1},'max'), + mx = st.vols{i}.blobs{1}.max; + else, + mx = max([eps maxval(st.vols{i}.blobs{1}.vol)]); + st.vols{i}.blobs{1}.max = mx; + end; + if isfield(st.vols{i}.blobs{1},'min'), + mn = st.vols{i}.blobs{1}.min; + else, + mn = min([0 minval(st.vols{i}.blobs{1}.vol)]); + st.vols{i}.blobs{1}.min = mn; + end; + + vol = st.vols{i}.blobs{1}.vol; + M = st.vols{i}.premul*st.vols{i}.blobs{1}.mat; + + tmpc = spm_slice_vol(vol,inv(CM0*(st.Space\M)),CD,[0 NaN])'; + + + + %tmpt_z = find(tmpt==0);tmpt(tmpt_z) = NaN; + %tmpc_z = find(tmpc==0);tmpc(tmpc_z) = NaN; + %tmps_z = find(tmps==0);tmps(tmps_z) = NaN; + + sc = 64/(mx-mn); + off = 65.51-mn*sc; + msk = find(isfinite(tmpc)); imgc(msk) = off+tmpc(msk)*sc; + + cmap = get(st.fig,'Colormap'); + + %figure(st.fig) + if mn*mx < 0 + setcolormap('gray-hot-cold') + elseif mx > 0 + setcolormap('gray-hot'); + else + setcolormap('gray-cold') + end + % redraw_colourbar(i,1,[mn mx],[1:64]'+64); + elseif isstruct(st.vols{i}.blobs{1}.colour), + % Add blobs for display using a defined + % colourmap + + % colourmaps + gryc = [0:63]'*ones(1,3)/63; + actc = ... + st.vols{1}.blobs{1}.colour.cmap; + actp = ... + st.vols{1}.blobs{1}.colour.prop; + + % scale grayscale image, not finite -> black + imgc = scaletocmap(imgc,mn,mx,gryc,65); + gryc = [gryc; 0 0 0]; + + % get max for blob image + vol = st.vols{i}.blobs{1}.vol; + mat = st.vols{i}.premul*st.vols{i}.blobs{1}.mat; + if isfield(st.vols{i}.blobs{1},'max'), + cmx = st.vols{i}.blobs{1}.max; + else, + cmx = max([eps maxval(st.vols{i}.blobs{1}.vol)]); + end; + if isfield(st.vols{i}.blobs{1},'min'), + cmn = st.vols{i}.blobs{1}.min; + else, + cmn = -cmx; + end; + + % get blob data + vol = st.vols{i}.blobs{1}.vol; + M = st.vols{i}.premul*st.vols{i}.blobs{1}.mat; + tmpc = spm_slice_vol(vol,inv(CM0*(st.Space\M)),CD,[0 NaN])'; + + % actimg scaled round 0, black NaNs + topc = size(actc,1)+1; + tmpc = scaletocmap(tmpc,cmn,cmx,actc,topc); + actc = [actc; 0 0 0]; + + % combine gray and blob data to + % truecolour + imgc = reshape(actc(tmpc(:),:)*actp+ ... + gryc(imgc(:),:)*(1-actp), ... + [size(imgc) 3]); + + + else, + % Add full colour blobs - several sets at once + scal = 1/(mx-mn); + dcoff = -mn*scal; + + wc = zeros(size(imgc)); + + imgc = repmat(imgc*scal+dcoff,[1,1,3]); + + cimgc = zeros(size(imgc)); + + for j=1:length(st.vols{i}.blobs), % get colours of all images first + if isfield(st.vols{i}.blobs{j},'colour'), + colour(j,:) = reshape(st.vols{i}.blobs{j}.colour, [1 3]); + else, + colour(j,:) = [1 0 0]; + end; + end; + %colour = colour/max(sum(colour)); + + for j=1:length(st.vols{i}.blobs), + if isfield(st.vols{i}.blobs{j},'max'), + mx = st.vols{i}.blobs{j}.max; + else, + mx = max([eps max(st.vols{i}.blobs{j}.vol(:))]); + st.vols{i}.blobs{j}.max = mx; + end; + if isfield(st.vols{i}.blobs{j},'min'), + mn = st.vols{i}.blobs{j}.min; + else, + mn = min([0 min(st.vols{i}.blobs{j}.vol(:))]); + st.vols{i}.blobs{j}.min = mn; + end; + + vol = st.vols{i}.blobs{j}.vol; + M = st.Space\st.vols{i}.premul*st.vols{i}.blobs{j}.mat; + tmpc = spm_slice_vol(vol,inv(CM0*M),CD,[0 NaN])'; + % check min/max of sampled image + % against mn/mx as given in st + tmpc(tmpc(:)mx) = mx; + tmpc = (tmpc-mn)/(mx-mn); + tmpc(~isfinite(tmpc)) = 0; + + cimgc = cimgc + cat(3,tmpc*colour(j,1),tmpc*colour(j,2),tmpc*colour(j,3)); + + wc = wc + tmpc; + cdata=permute(shiftdim([1/64:1/64:1]'* ... + colour(j,:),-1),[2 1 3]); + redraw_colourbar(i,j,[mn mx],cdata); + end; + + imgc = repmat(1-wc,[1 1 3]).*imgc+cimgc; + + imgc(imgc<0)=0; imgc(imgc>1)=1; + end; + else, + scal = 64/(mx-mn); + dcoff = -mn*scal; + imgc = imgc*scal+dcoff; + end; + +% set(st.vols{i}.ax{1}.d,'HitTest','off', 'Cdata',imgt); +% set(st.vols{i}.ax{1}.lx,'HitTest','off',... +% 'Xdata',[0 TD(1)]+0.5,'Ydata',[1 1]*(cent(2)-bb(1,2)+1)); +% set(st.vols{i}.ax{1}.ly,'HitTest','off',... +% 'Ydata',[0 TD(2)]+0.5,'Xdata',[1 1]*(cent(1)-bb(1,1)+1)); +% +% set(st.vols{i}.ax{2}.d,'HitTest','off', 'Cdata',imgc); +% set(st.vols{i}.ax{2}.lx,'HitTest','off',... +% 'Xdata',[0 CD(1)]+0.5,'Ydata',[1 1]*(cent(3)-bb(1,3)+1)); +% set(st.vols{i}.ax{2}.ly,'HitTest','off',... +% 'Ydata',[0 CD(2)]+0.5,'Xdata',[1 1]*(cent(1)-bb(1,1)+1)); +% +% set(st.vols{i}.ax{3}.d,'HitTest','off','Cdata',imgs); +% if st.mode ==0, +% set(st.vols{i}.ax{3}.lx,'HitTest','off',... +% 'Xdata',[0 SD(1)]+0.5,'Ydata',[1 1]*(cent(2)-bb(1,2)+1)); +% set(st.vols{i}.ax{3}.ly,'HitTest','off',... +% 'Ydata',[0 SD(2)]+0.5,'Xdata',[1 1]*(cent(3)-bb(1,3)+1)); +% else, +% set(st.vols{i}.ax{3}.lx,'HitTest','off',... +% 'Xdata',[0 SD(1)]+0.5,'Ydata',[1 1]*(cent(3)-bb(1,3)+1)); +% set(st.vols{i}.ax{3}.ly,'HitTest','off',... +% 'Ydata',[0 SD(2)]+0.5,'Xdata',[1 1]*(bb(2,2)+1-cent(2))); +% end; + + + end; +end; + +elseif(viewtype == 't') + +for i = 1 + M = st.vols{i}.premul*st.vols{i}.mat; + TM0 = [ 1 0 0 -bb(1,1)+1 + 0 1 0 -bb(1,2)+1 + 0 0 1 -cent(3) + 0 0 0 1]; + TM = inv(TM0*(st.Space\M)); + TD = Dims([1 2]); + + + + ok=1; + eval('imgt = (spm_slice_vol(st.vols{i},TM,TD,st.hld))'';','ok=0;'); + + if (ok==0), fprintf('Image "%s" can not be resampled\n', st.vols{i}.fname); + else, + % get min/max threshold + if strcmp(st.vols{i}.window,'auto') + mn = -Inf; + mx = Inf; + else + mn = min(st.vols{i}.window); + mx = max(st.vols{i}.window); + end; + % threshold images + imgt = max(imgt,mn); imgt = min(imgt,mx); + % compute intensity mapping, if histeq is available + if license('test','image_toolbox') == 0 + st.vols{i}.mapping = 'linear'; + end; + switch st.vols{i}.mapping, + case 'linear', + case 'histeq', + % scale images to a range between 0 and 1 + imgt1=(imgt-min(imgt(:)))/(max(imgt(:)-min(imgt(:)))+eps); + img = histeq([imgt1(:); imgc1(:); imgs1(:)],1024); + imgt = reshape(img(1:numel(imgt1)),size(imgt1)); + mn = 0; + mx = 1; + case 'quadhisteq', + % scale images to a range between 0 and 1 + imgt1=(imgt-min(imgt(:)))/(max(imgt(:)-min(imgt(:)))+eps); + imgc1=(imgc-min(imgc(:)))/(max(imgc(:)-min(imgc(:)))+eps); + imgs1=(imgs-min(imgs(:)))/(max(imgs(:)-min(imgs(:)))+eps); + img = histeq([imgt1(:).^2; imgc1(:).^2; imgs1(:).^2],1024); + imgt = reshape(img(1:numel(imgt1)),size(imgt1)); + imgc = reshape(img(numel(imgt1)+[1:numel(imgc1)]),size(imgc1)); + imgs = reshape(img(numel(imgt1)+numel(imgc1)+[1:numel(imgs1)]),size(imgs1)); + mn = 0; + mx = 1; + case 'loghisteq', + warning off % messy - but it may avoid extra queries + imgt = log(imgt-min(imgt(:))); + imgc = log(imgc-min(imgc(:))); + imgs = log(imgs-min(imgs(:))); + warning on + imgt(~isfinite(imgt)) = 0; + imgc(~isfinite(imgc)) = 0; + imgs(~isfinite(imgs)) = 0; + % scale log images to a range between 0 and 1 + imgt1=(imgt-min(imgt(:)))/(max(imgt(:)-min(imgt(:)))+eps); + imgc1=(imgc-min(imgc(:)))/(max(imgc(:)-min(imgc(:)))+eps); + imgs1=(imgs-min(imgs(:)))/(max(imgs(:)-min(imgs(:)))+eps); + img = histeq([imgt1(:); imgc1(:); imgs1(:)],1024); + imgt = reshape(img(1:numel(imgt1)),size(imgt1)); + imgc = reshape(img(numel(imgt1)+[1:numel(imgc1)]),size(imgc1)); + imgs = reshape(img(numel(imgt1)+numel(imgc1)+[1:numel(imgs1)]),size(imgs1)); + mn = 0; + mx = 1; + end; + % recompute min/max for display + if strcmp(st.vols{i}.window,'auto') + mx = -inf; mn = inf; + end; + if ~isempty(imgt), + tmp = imgt(isfinite(imgt)); + mx = max([mx max(max(tmp))]); + mn = min([mn min(min(tmp))]); + end; + + if mx==mn, mx=mn+eps; end; + + if isfield(st.vols{i},'blobs'), + if ~isfield(st.vols{i}.blobs{1},'colour'), + % Add blobs for display using the split colourmap + scal = 64/(mx-mn); + dcoff = -mn*scal; + imgt = imgt*scal+dcoff; + + if isfield(st.vols{i}.blobs{1},'max'), + mx = st.vols{i}.blobs{1}.max; + else, + mx = max([eps maxval(st.vols{i}.blobs{1}.vol)]); + st.vols{i}.blobs{1}.max = mx; + end; + if isfield(st.vols{i}.blobs{1},'min'), + mn = st.vols{i}.blobs{1}.min; + else, + mn = min([0 minval(st.vols{i}.blobs{1}.vol)]); + st.vols{i}.blobs{1}.min = mn; + end; + + vol = st.vols{i}.blobs{1}.vol; + M = st.vols{i}.premul*st.vols{i}.blobs{1}.mat; + + tmpt = spm_slice_vol(vol,inv(TM0*(st.Space\M)),TD,[0 NaN])'; + + + %tmpt_z = find(tmpt==0);tmpt(tmpt_z) = NaN; + %tmpc_z = find(tmpc==0);tmpc(tmpc_z) = NaN; + %tmps_z = find(tmps==0);tmps(tmps_z) = NaN; + + sc = 64/(mx-mn); + off = 65.51-mn*sc; + msk = find(isfinite(tmpt)); imgt(msk) = off+tmpt(msk)*sc; + + cmap = get(st.fig,'Colormap'); + + %figure(st.fig) + if mn*mx < 0 + setcolormap('gray-hot-cold') + elseif mx > 0 + setcolormap('gray-hot'); + else + setcolormap('gray-cold') + end + % redraw_colourbar(i,1,[mn mx],[1:64]'+64); + elseif isstruct(st.vols{i}.blobs{1}.colour), + % Add blobs for display using a defined + % colourmap + + % colourmaps + gryc = [0:63]'*ones(1,3)/63; + actc = ... + st.vols{1}.blobs{1}.colour.cmap; + actp = ... + st.vols{1}.blobs{1}.colour.prop; + + % scale grayscale image, not finite -> black + imgt = scaletocmap(imgt,mn,mx,gryc,65); + gryc = [gryc; 0 0 0]; + + % get max for blob image + vol = st.vols{i}.blobs{1}.vol; + mat = st.vols{i}.premul*st.vols{i}.blobs{1}.mat; + if isfield(st.vols{i}.blobs{1},'max'), + cmx = st.vols{i}.blobs{1}.max; + else, + cmx = max([eps maxval(st.vols{i}.blobs{1}.vol)]); + end; + if isfield(st.vols{i}.blobs{1},'min'), + cmn = st.vols{i}.blobs{1}.min; + else, + cmn = -cmx; + end; + + % get blob data + vol = st.vols{i}.blobs{1}.vol; + M = st.vols{i}.premul*st.vols{i}.blobs{1}.mat; + tmpt = spm_slice_vol(vol,inv(TM0*(st.Space\M)),TD,[0 NaN])'; + + % actimg scaled round 0, black NaNs + topc = size(actc,1)+1; + tmpt = scaletocmap(tmpt,cmn,cmx,actc,topc); + actc = [actc; 0 0 0]; + + % combine gray and blob data to + % truecolour + imgt = reshape(actc(tmpt(:),:)*actp+ ... + gryc(imgt(:),:)*(1-actp), ... + [size(imgt) 3]); +% +% redraw_colourbar(i,1,[cmn cmx],[1:64]'+64); + + else, + % Add full colour blobs - several sets at once + scal = 1/(mx-mn); + dcoff = -mn*scal; + + wt = zeros(size(imgt)); + + imgt = repmat(imgt*scal+dcoff,[1,1,3]); + + cimgt = zeros(size(imgt)); + + for j=1:length(st.vols{i}.blobs), % get colours of all images first + if isfield(st.vols{i}.blobs{j},'colour'), + colour(j,:) = reshape(st.vols{i}.blobs{j}.colour, [1 3]); + else, + colour(j,:) = [1 0 0]; + end; + end; + %colour = colour/max(sum(colour)); + + for j=1:length(st.vols{i}.blobs), + if isfield(st.vols{i}.blobs{j},'max'), + mx = st.vols{i}.blobs{j}.max; + else, + mx = max([eps max(st.vols{i}.blobs{j}.vol(:))]); + st.vols{i}.blobs{j}.max = mx; + end; + if isfield(st.vols{i}.blobs{j},'min'), + mn = st.vols{i}.blobs{j}.min; + else, + mn = min([0 min(st.vols{i}.blobs{j}.vol(:))]); + st.vols{i}.blobs{j}.min = mn; + end; + + vol = st.vols{i}.blobs{j}.vol; + M = st.Space\st.vols{i}.premul*st.vols{i}.blobs{j}.mat; + tmpt = spm_slice_vol(vol,inv(TM0*M),TD,[0 NaN])'; + % check min/max of sampled image + % against mn/mx as given in st + tmpt(tmpt(:)mx) = mx; + tmpt = (tmpt-mn)/(mx-mn); + tmpt(~isfinite(tmpt)) = 0; + + cimgt = cimgt + cat(3,tmpt*colour(j,1),tmpt*colour(j,2),tmpt*colour(j,3)); + + wt = wt + tmpt; + cdata=permute(shiftdim([1/64:1/64:1]'* ... + colour(j,:),-1),[2 1 3]); + %redraw_colourbar(i,j,[mn mx],cdata); + end; + + imgt = repmat(1-wt,[1 1 3]).*imgt+cimgt; + + imgt(imgt<0)=0; imgt(imgt>1)=1; + end; + else, + scal = 64/(mx-mn); + dcoff = -mn*scal; + imgt = imgt*scal+dcoff; + end; + +% set(st.vols{i}.ax{1}.d,'HitTest','off', 'Cdata',imgt); +% set(st.vols{i}.ax{1}.lx,'HitTest','off',... +% 'Xdata',[0 TD(1)]+0.5,'Ydata',[1 1]*(cent(2)-bb(1,2)+1)); +% set(st.vols{i}.ax{1}.ly,'HitTest','off',... +% 'Ydata',[0 TD(2)]+0.5,'Xdata',[1 1]*(cent(1)-bb(1,1)+1)); +% +% set(st.vols{i}.ax{2}.d,'HitTest','off', 'Cdata',imgc); +% set(st.vols{i}.ax{2}.lx,'HitTest','off',... +% 'Xdata',[0 CD(1)]+0.5,'Ydata',[1 1]*(cent(3)-bb(1,3)+1)); +% set(st.vols{i}.ax{2}.ly,'HitTest','off',... +% 'Ydata',[0 CD(2)]+0.5,'Xdata',[1 1]*(cent(1)-bb(1,1)+1)); +% +% set(st.vols{i}.ax{3}.d,'HitTest','off','Cdata',imgs); +% if st.mode ==0, +% set(st.vols{i}.ax{3}.lx,'HitTest','off',... +% 'Xdata',[0 SD(1)]+0.5,'Ydata',[1 1]*(cent(2)-bb(1,2)+1)); +% set(st.vols{i}.ax{3}.ly,'HitTest','off',... +% 'Ydata',[0 SD(2)]+0.5,'Xdata',[1 1]*(cent(3)-bb(1,3)+1)); +% else, +% set(st.vols{i}.ax{3}.lx,'HitTest','off',... +% 'Xdata',[0 SD(1)]+0.5,'Ydata',[1 1]*(cent(3)-bb(1,3)+1)); +% set(st.vols{i}.ax{3}.ly,'HitTest','off',... +% 'Ydata',[0 SD(2)]+0.5,'Xdata',[1 1]*(bb(2,2)+1-cent(2))); +% end; + + + end; +end; + +elseif(viewtype == 'c') + +for i = 1 + M = st.vols{i}.premul*st.vols{i}.mat; + + + if st.mode ==0, + SM0 = [ 0 0 1 -bb(1,3)+1 + 0 1 0 -bb(1,2)+1 + 1 0 0 -cent(1) + 0 0 0 1]; + SM = inv(SM0*(st.Space\M)); SD = Dims([3 2]); + else, + SM0 = [ 0 1 0 -bb(1,2)+1 + 0 0 1 -bb(1,3)+1 + 1 0 0 -cent(1) + 0 0 0 1]; + SM0 = [ 0 -1 0 +bb(2,2)+1 + 0 0 1 -bb(1,3)+1 + 1 0 0 -cent(1) + 0 0 0 1]; + SM = inv(SM0*(st.Space\M)); + SD = Dims([2 3]); + end; + + ok=1; + + eval('imgs = (spm_slice_vol(st.vols{i},SM,SD,st.hld))'';','ok=0;'); + + if (ok==0), fprintf('Image "%s" can not be resampled\n', st.vols{i}.fname); + else, + % get min/max threshold + if strcmp(st.vols{i}.window,'auto') + mn = -Inf; + mx = Inf; + else + mn = min(st.vols{i}.window); + mx = max(st.vols{i}.window); + end; + % threshold images + imgs = max(imgs,mn); imgs = min(imgs,mx); + % compute intensity mapping, if histeq is available + if license('test','image_toolbox') == 0 + st.vols{i}.mapping = 'linear'; + end; + switch st.vols{i}.mapping, + case 'linear', + case 'histeq', + % scale images to a range between 0 and 1 + imgs1=(imgs-min(imgs(:)))/(max(imgs(:)-min(imgs(:)))+eps); + img = histeq([imgt1(:); imgc1(:); imgs1(:)],1024); + imgs = reshape(img(numel(imgt1)+numel(imgc1)+[1:numel(imgs1)]),size(imgs1)); + mn = 0; + mx = 1; + case 'quadhisteq', + % scale images to a range between 0 and 1 + imgs1=(imgs-min(imgs(:)))/(max(imgs(:)-min(imgs(:)))+eps); + img = histeq([imgt1(:).^2; imgc1(:).^2; imgs1(:).^2],1024); + imgs = reshape(img(numel(imgt1)+numel(imgc1)+[1:numel(imgs1)]),size(imgs1)); + mn = 0; + mx = 1; + case 'loghisteq', + warning off % messy - but it may avoid extra queries + imgs = log(imgs-min(imgs(:))); + warning on + imgs(~isfinite(imgs)) = 0; + % scale log images to a range between 0 and 1 + imgs1=(imgs-min(imgs(:)))/(max(imgs(:)-min(imgs(:)))+eps); + img = histeq([imgt1(:); imgc1(:); imgs1(:)],1024); + imgs = reshape(img(numel(imgt1)+numel(imgc1)+[1:numel(imgs1)]),size(imgs1)); + mn = 0; + mx = 1; + end; + % recompute min/max for display + if strcmp(st.vols{i}.window,'auto') + mx = -inf; mn = inf; + end; + + if ~isempty(imgs), + tmp = imgs(isfinite(imgs)); + mx = max([mx max(max(tmp))]); + mn = min([mn min(min(tmp))]); + end; + if mx==mn, mx=mn+eps; end; + + if isfield(st.vols{i},'blobs'), + if ~isfield(st.vols{i}.blobs{1},'colour'), + % Add blobs for display using the split colourmap + scal = 64/(mx-mn); + dcoff = -mn*scal; + imgs = imgs*scal+dcoff; + + if isfield(st.vols{i}.blobs{1},'max'), + mx = st.vols{i}.blobs{1}.max; + else, + mx = max([eps maxval(st.vols{i}.blobs{1}.vol)]); + st.vols{i}.blobs{1}.max = mx; + end; + if isfield(st.vols{i}.blobs{1},'min'), + mn = st.vols{i}.blobs{1}.min; + else, + mn = min([0 minval(st.vols{i}.blobs{1}.vol)]); + st.vols{i}.blobs{1}.min = mn; + end; + + vol = st.vols{i}.blobs{1}.vol; + M = st.vols{i}.premul*st.vols{i}.blobs{1}.mat; + + tmps = spm_slice_vol(vol,inv(SM0*(st.Space\M)),SD,[0 NaN])'; + + + + %tmpt_z = find(tmpt==0);tmpt(tmpt_z) = NaN; + %tmpc_z = find(tmpc==0);tmpc(tmpc_z) = NaN; + %tmps_z = find(tmps==0);tmps(tmps_z) = NaN; + + sc = 64/(mx-mn); + off = 65.51-mn*sc; + msk = find(isfinite(tmps)); imgs(msk) = off+tmps(msk)*sc; + + cmap = get(st.fig,'Colormap'); + + %figure(st.fig) + if mn*mx < 0 + setcolormap('gray-hot-cold') + elseif mx > 0 + setcolormap('gray-hot'); + else + setcolormap('gray-cold') + end + % redraw_colourbar(i,1,[mn mx],[1:64]'+64); + elseif isstruct(st.vols{i}.blobs{1}.colour), + % Add blobs for display using a defined + % colourmap + + % colourmaps + gryc = [0:63]'*ones(1,3)/63; + actc = ... + st.vols{1}.blobs{1}.colour.cmap; + actp = ... + st.vols{1}.blobs{1}.colour.prop; + + % scale grayscale image, not finite -> black + imgs = scaletocmap(imgs,mn,mx,gryc,65); + gryc = [gryc; 0 0 0]; + + % get max for blob image + vol = st.vols{i}.blobs{1}.vol; + mat = st.vols{i}.premul*st.vols{i}.blobs{1}.mat; + if isfield(st.vols{i}.blobs{1},'max'), + cmx = st.vols{i}.blobs{1}.max; + else, + cmx = max([eps maxval(st.vols{i}.blobs{1}.vol)]); + end; + if isfield(st.vols{i}.blobs{1},'min'), + cmn = st.vols{i}.blobs{1}.min; + else, + cmn = -cmx; + end; + + % get blob data + vol = st.vols{i}.blobs{1}.vol; + M = st.vols{i}.premul*st.vols{i}.blobs{1}.mat; + tmps = spm_slice_vol(vol,inv(SM0*(st.Space\M)),SD,[0 NaN])'; + + % actimg scaled round 0, black NaNs + topc = size(actc,1)+1; + tmps = scaletocmap(tmps,cmn,cmx,actc,topc); + actc = [actc; 0 0 0]; + + % combine gray and blob data to + % truecolour + + imgs = reshape(actc(tmps(:),:)*actp+ ... + gryc(imgs(:),:)*(1-actp), ... + [size(imgs) 3]); + + redraw_colourbar(i,1,[cmn cmx],[1:64]'+64); + + else, + % Add full colour blobs - several sets at once + scal = 1/(mx-mn); + dcoff = -mn*scal; + + ws = zeros(size(imgs)); + + imgs = repmat(imgs*scal+dcoff,[1,1,3]); + + cimgs = zeros(size(imgs)); + + for j=1:length(st.vols{i}.blobs), % get colours of all images first + if isfield(st.vols{i}.blobs{j},'colour'), + colour(j,:) = reshape(st.vols{i}.blobs{j}.colour, [1 3]); + else, + colour(j,:) = [1 0 0]; + end; + end; + %colour = colour/max(sum(colour)); + + for j=1:length(st.vols{i}.blobs), + if isfield(st.vols{i}.blobs{j},'max'), + mx = st.vols{i}.blobs{j}.max; + else, + mx = max([eps max(st.vols{i}.blobs{j}.vol(:))]); + st.vols{i}.blobs{j}.max = mx; + end; + if isfield(st.vols{i}.blobs{j},'min'), + mn = st.vols{i}.blobs{j}.min; + else, + mn = min([0 min(st.vols{i}.blobs{j}.vol(:))]); + st.vols{i}.blobs{j}.min = mn; + end; + + vol = st.vols{i}.blobs{j}.vol; + M = st.Space\st.vols{i}.premul*st.vols{i}.blobs{j}.mat; + + tmps = spm_slice_vol(vol,inv(SM0*M),SD,[0 NaN])'; + % check min/max of sampled image + % against mn/mx as given in st + + tmps(tmps(:)mx) = mx; + + tmps = (tmps-mn)/(mx-mn); + + tmps(~isfinite(tmps)) = 0; + + cimgs = cimgs + cat(3,tmps*colour(j,1),tmps*colour(j,2),tmps*colour(j,3)); + + ws = ws + tmps; + cdata=permute(shiftdim([1/64:1/64:1]'* ... + colour(j,:),-1),[2 1 3]); + redraw_colourbar(i,j,[mn mx],cdata); + end; + + imgs = repmat(1-ws,[1 1 3]).*imgs+cimgs; + + imgs(imgs<0)=0; imgs(imgs>1)=1; + end; + else, + scal = 64/(mx-mn); + dcoff = -mn*scal; + imgs = imgs*scal+dcoff; + end; + +% set(st.vols{i}.ax{1}.d,'HitTest','off', 'Cdata',imgt); +% set(st.vols{i}.ax{1}.lx,'HitTest','off',... +% 'Xdata',[0 TD(1)]+0.5,'Ydata',[1 1]*(cent(2)-bb(1,2)+1)); +% set(st.vols{i}.ax{1}.ly,'HitTest','off',... +% 'Ydata',[0 TD(2)]+0.5,'Xdata',[1 1]*(cent(1)-bb(1,1)+1)); +% +% set(st.vols{i}.ax{2}.d,'HitTest','off', 'Cdata',imgc); +% set(st.vols{i}.ax{2}.lx,'HitTest','off',... +% 'Xdata',[0 CD(1)]+0.5,'Ydata',[1 1]*(cent(3)-bb(1,3)+1)); +% set(st.vols{i}.ax{2}.ly,'HitTest','off',... +% 'Ydata',[0 CD(2)]+0.5,'Xdata',[1 1]*(cent(1)-bb(1,1)+1)); +% +% set(st.vols{i}.ax{3}.d,'HitTest','off','Cdata',imgs); +% if st.mode ==0, +% set(st.vols{i}.ax{3}.lx,'HitTest','off',... +% 'Xdata',[0 SD(1)]+0.5,'Ydata',[1 1]*(cent(2)-bb(1,2)+1)); +% set(st.vols{i}.ax{3}.ly,'HitTest','off',... +% 'Ydata',[0 SD(2)]+0.5,'Xdata',[1 1]*(cent(3)-bb(1,3)+1)); +% else, +% set(st.vols{i}.ax{3}.lx,'HitTest','off',... +% 'Xdata',[0 SD(1)]+0.5,'Ydata',[1 1]*(cent(3)-bb(1,3)+1)); +% set(st.vols{i}.ax{3}.ly,'HitTest','off',... +% 'Ydata',[0 SD(2)]+0.5,'Xdata',[1 1]*(bb(2,2)+1-cent(2))); +% end; + + end; +end; + +end + + + +if(viewtype == 's') + slicedata{kk} = flipdim(imgc,1); +elseif(viewtype == 't') + slicedata{kk} = flipdim(flipdim(permute(imgt,[2,1,3]),1),2); +elseif(viewtype == 'c') + slicedata{kk} = flipdim(imgs,1); +end +pos{kk} = slice; +kk = kk+1; +end +if(isempty(sliceview.colormap)) + if mn*mx < 0 + colormp = 'gray-hot-cold'; + elseif mx > 0 + colormp = 'gray-hot'; + else + colormp = 'gray-cold'; + end + sliceview.colormap = colormp; +else + colormp = sliceview.colormap; +end + +return; + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% reslice push +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_reslicePush(hObject, eventdata) + +handles = guidata(hObject); + +if findstr('SPM2',spm('ver')) + p = spm_get([],'*.img','Select the image(s) you want to reslice'); +else%if findstr('SPM5',spm('ver')) + p = spm_select([0:200],'IMAGE','Select the image(s) you want to reslice'); +end +if findstr('SPM2',spm('ver')) + target = spm_get([1],'*.img','Select the space-defining image'); +else%if findstr('SPM5',spm('ver')) + target = spm_select([1],'IMAGE','Select the space-defining image'); +end + +m = size(p,2); +n = length(target); +target1 = target; +template = target1; +if m>n + target1 = [template blanks(m-n)]; +else + p = [p repmat(blanks(n-m), size(p,1),1)]; +end +P = [target1; p]; +flags = struct('interp',1,'mask',0,'mean',0,'which',1,'wrap',[0 0 0]'); +tic;spm_reslice(P, flags);toc; + +guidata(hObject, handles); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% commonRegion push +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_commonRegionPush(hObject, eventdata) +handles = guidata(hObject); +if ~isfield(handles,'TF') | length(handles.TF)<=1 + %msgbox('Two or more images need to be loaded to find the common region.', 'info'); + set(handles.infoTextBox, 'string', 'Two or more images need to be loaded to find the common region.'); + beep + return; +end + +common = handles.currentDisplayMNI{1}; +for ii=2:length(handles.TF) + common = intersect(common, handles.currentDisplayMNI{ii}, 'rows'); +end + +if isempty(common) + %msgbox('No common region found.', 'info'); + set(handles.infoTextBox, 'string', 'No common region found.'); + beep + return; +end + +tmpMNI = cell2mat(handles.currentDisplayMNI'); +tmpIntensity = cell2mat(handles.currentDisplayIntensity'); +intensity = zeros(size(common,1),1); + +for ii=1:size(common,1) + pos = find(abs(tmpMNI(:,1)-common(ii,1))<0.1 & abs(tmpMNI(:,2)-common(ii,2))<0.1 & abs(tmpMNI(:,3)-common(ii,3))<0.1); + intensity(ii) = 1;%max(abs(tmpIntensity(pos))); %2009/11/18 + %intensity(ii) = prod(tmpIntensity(pos)); %2009/11/18 comment +end + +common = [common; cor2mni([1 1 1], handles.M{1}); cor2mni([1 1 2], handles.M{1})]; +intensity = [intensity; 0; 1.5]; +%intensity = max(intensity) * ones(size(intensity)) + randn(size(intensity))/10; + +handles.currentDisplayMNI = {common}; +handles.currentDisplayIntensity = {intensity}; +[handles.hReg, handles.hSection, handles.hcolorbar] = Draw(handles.currentDisplayMNI, handles.currentDisplayIntensity, hObject, handles); + +% if isfield(handles,'hLegend') +% try +% set(cell2mat(handles.hLegend'),'visible',{'off'}); +% end +% end +guidata(hObject, handles); + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% small volume push +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_smallVolume(hObject, eventdata) +handles = guidata(hObject); + +mni = cell2mat(handles.currentDisplayMNI'); +intensity = cell2mat(handles.currentDisplayIntensity'); + +xSPM.XYZ = mni2cor(mni, handles.M{1}); +xSPM.XYZ = xSPM.XYZ'; +xSPM.XYZmm = mni'; +xSPM.Z = (intensity'); +xSPM.M = handles.M{1}; +xSPM.DIM = handles.DIM{1}; +xSPM.STAT = handles.TF{1}; +if xSPM.STAT == 'T' + xSPM.STATstr = [xSPM.STAT '_{' num2str(handles.df{1}) '}']; + xSPM.df = [1 handles.df{1}]; +elseif xSPM.STAT == 'F' + xSPM.STATstr = [xSPM.STAT '_{' num2str(handles.df{1}(1)) ',' num2str(handles.df{1}(2)) '}']; + xSPM.df = [handles.df{1}]; +else + xSPM.STAT = 'T'; + xSPM.STATstr = [xSPM.STAT '_{' num2str(handles.df{1}) '}']; + xSPM.df = [1 handles.df{1}]; +end +xSPM.k = str2num(get(handles.clusterSizeThresholdEdit, 'string')); +xSPM.u = str2num(get(handles.intensityThresholdEdit, 'string')); +xSPM.u = xSPM.u(1); +xSPM.VOX = abs([xSPM.M(1,1) xSPM.M(2,2) xSPM.M(3,3)]); +xSPM.n = 1; +xSPM.uc = [4.7070 4.2941 338 338]; +xSPM.Pc = [0.0000 0.0000 0.0000 0.0668 0.1401 0.1723 0.1723 0.2412 0.2723 0.3092 0.5687 0.5687 0.7008 0.7008]; +xSPM.Pp = [0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0001 0.0001 0.0001 0.0005 0.0005 0.0015 0.0107 0.0121 0.0123 0.0383 0.0435 0.1019 0.1955 0.2257 0.2966 0.5612 0.6060 0.7725 0.9944]; + + +xSPM.S = length(handles.intensity{1}); +xSPM.R = [3 27.0931 276.3307 498.1985]; +xSPM.FWHM = [2.9746 3.1923 2.8600]; + +if get(handles.positiveIntensityRadio, 'Value') + xSPM.Z = abs(xSPM.Z); + if xSPM.STAT == 'T'; xSPM.Ps = (1-spm_Tcdf(handles.intensity{1}, xSPM.df(2))); end + if xSPM.STAT == 'F'; xSPM.Ps = (1-spm_Fcdf(handles.intensity{1}, xSPM.df)); end +end + +if get(handles.negativeIntensityRadio, 'Value'); + xSPM.Z = abs(xSPM.Z); + if xSPM.STAT == 'T'; xSPM.Ps = (1-spm_Tcdf(-handles.intensity{1}, xSPM.df(2))); end + if xSPM.STAT == 'F'; xSPM.Ps = (1-spm_Fcdf(-handles.intensity{1}, xSPM.df)); end +end + +if get(handles.allIntensityRadio, 'Value'); + if xSPM.STAT == 'T'; xSPM.Ps = (1-spm_Tcdf((handles.intensity{1}), xSPM.df(2))); end + if xSPM.STAT == 'F'; xSPM.Ps = (1-spm_Fcdf((handles.intensity{1}), xSPM.df)); end +end + +if findstr('SPM2',spm('ver')) + P = spm_get([0 1],'SPM.mat','locate the corresponding SPM.mat'); +else%if findstr('SPM5',spm('ver')) + P = spm_select([0:1],'SPM.mat','locate the corresponding SPM.mat'); +end + +if ~isempty(P) + load(P); + xSPM.FWHM = SPM.xVol.FWHM; + xSPM.R = SPM.xVol.R; + xSPM.S = SPM.xVol.S; +else + %warndlg(['You did not input SPM.mat. The listed result may not be correct.'], 'SPM.mat missing'); + warndlg('You must input SPM.mat.', 'SPM.mat missing'); + return; +end + +if findstr('SPM2',spm('ver')) + warndlg('Small volume correction does not work in spm 2', 'spm 2'); + return; +elseif findstr('SPM5',spm('ver')) + spm_VOI(SPM,xSPM,handles.hReg); +elseif findstr('SPM8',spm('ver')) + spm_VOI8(SPM,xSPM,handles.hReg); +end + +return; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% spm_VOI, for spm5, modified by Xu Cui, 2011/03/01 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function TabDat = spm_VOI(SPM,xSPM,hReg) +% List of local maxima and adjusted p-values for a small Volume of Interest +% FORMAT TabDat = spm_VOI(SPM,xSPM,hReg) +% +% SPM - structure containing analysis details (see spm_spm) +% +% xSPM - structure containing SPM, distribution & filtering details +% - required fields are: +% .swd - SPM working directory - directory containing current SPM.mat +% .Z - minimum of n Statistics {filtered on u and k} +% .n - number of conjoint tests +% .STAT - distribution {Z, T, X or F} +% .df - degrees of freedom [df{interest}, df{residual}] +% .u - height threshold +% .k - extent threshold {resels} +% .XYZ - location of voxels {voxel coords} +% .XYZmm - location of voxels {mm} +% .S - search Volume {voxels} +% .R - search Volume {resels} +% .FWHM - smoothness {voxels} +% .M - voxels - > mm matrix +% .VOX - voxel dimensions {mm} +% .DIM - image dimensions {voxels} - column vector +% .Vspm - Mapped statistic image(s) +% .Ps - P vlues in searched voxels (for FDR) +% +% hReg - Handle of results section XYZ registry (see spm_results_ui.m) +% +% TabDat - Structure containing table data +% - see spm_list for definition +% +%_______________________________________________________________________ +% +% spm_VOI is called by the SPM results section and takes variables in +% SPM to compute p-values corrected for a specified volume of interest. +% +% The volume of interest may be defined as a box or sphere centred on +% the current voxel or by a mask image. +% +% If the VOI is defined by a mask this mask must have been defined +% independently of the SPM (e.g.using a mask based on an orthogonal +% contrast) +% +% External mask images should be in the same orientation as the SPM +% (i.e. as the input used in stats estimation). The VOI is defined by +% voxels with values greater than 0. +% +% FDR computations are similarly resticted by the small search volume +% +% See also: spm_list +%_______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% Karl Friston +% $Id: spm_VOI.m 112 2005-05-04 18:20:52Z john $ + + +%-Parse arguments +%----------------------------------------------------------------------- +if nargin < 2, error('insufficient arguments'), end +if nargin < 3, hReg = []; end + +Num = 16; % maxima per cluster +Dis = 04; % distance among maxima (mm) + +%-Title +%----------------------------------------------------------------------- +spm('FigName',['SPM{',xSPM.STAT,'}: Small Volume Correction']); + +%-Get current location {mm} +%----------------------------------------------------------------------- +%xyzmm = spm_results_ui('GetCoords'); +xyzmm = spm_XYZreg('GetCoords',hReg);% added by xu cui + +% added by xu cui, open a figure so users can input parameters +f = figure('unit','normalized','position',[0.1 0.1 0.3 0.3]); + +%-Specify search volume +%----------------------------------------------------------------------- +str = sprintf(' at [%.0f,%.0f,%.0f]',xyzmm(1),xyzmm(2),xyzmm(3)); +SPACE = spm_input('Search volume...',-1,'m',... + {['Sphere',str],['Box',str],'Image'},['S','B','I']); + +% voxels in entire search volume {mm} +%----------------------------------------------------------------------- +XYZmm = SPM.xVol.M(1:3,:)*[SPM.xVol.XYZ; ones(1, SPM.xVol.S)]; +Q = ones(1,size(xSPM.XYZmm,2)); +O = ones(1,size( XYZmm,2)); +FWHM = xSPM.FWHM; + + +switch SPACE + + case 'S' %-Sphere + %--------------------------------------------------------------- + D = spm_input('radius of VOI {mm}',-2); + str = sprintf('%0.1fmm sphere',D); + j = find(sum((xSPM.XYZmm - xyzmm*Q).^2) <= D^2); + k = find(sum(( XYZmm - xyzmm*O).^2) <= D^2); + D = D./xSPM.VOX; + + + case 'B' %-Box + %--------------------------------------------------------------- + D = spm_input('box dimensions [k l m] {mm}',-2); + str = sprintf('%0.1f x %0.1f x %0.1f mm box',D(1),D(2),D(3)); + j = find(all(abs(xSPM.XYZmm - xyzmm*Q) <= D(:)*Q/2)); + k = find(all(abs( XYZmm - xyzmm*O) <= D(:)*O/2)); + D = D./xSPM.VOX; + + + case 'I' %-Mask Image + %--------------------------------------------------------------- + Msk = spm_select(1,'image','Image defining search volume'); + D = spm_vol(Msk); + str = sprintf('image mask: %s',spm_str_manip(Msk,'a30')); + VOX = sqrt(sum(D.mat(1:3,1:3).^2)); + FWHM = FWHM.*(xSPM.VOX./VOX); + XYZ = D.mat \ [xSPM.XYZmm; ones(1, size(xSPM.XYZmm, 2))]; + j = find(spm_sample_vol(D, XYZ(1,:), XYZ(2,:), XYZ(3,:),0) > 0); + XYZ = D.mat \ [ XYZmm; ones(1, size( XYZmm, 2))]; + k = find(spm_sample_vol(D, XYZ(1,:), XYZ(2,:), XYZ(3,:),0) > 0); + +end + +xSPM.S = length(k); +xSPM.R = spm_resels(FWHM,D,SPACE); +xSPM.Z = xSPM.Z(j); +xSPM.XYZ = xSPM.XYZ(:,j); +xSPM.XYZmm = xSPM.XYZmm(:,j); +xSPM.Ps = xSPM.Ps(k); + +%-Tabulate p values +%----------------------------------------------------------------------- +str = sprintf('search volume: %s',str); +if any(strcmp(SPACE,{'S','B'})) + str = sprintf('%s at [%.0f,%.0f,%.0f]',str,xyzmm(1),xyzmm(2),xyzmm(3)); +end + +TabDat = spm_list('List',xSPM,hReg,Num,Dis,str); + +%-Reset title +%----------------------------------------------------------------------- +spm('FigName',['SPM{',xSPM.STAT,'}: Results']); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% spm_VOI, for spm8, modified by Xu Cui, 2011/02/28 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function TabDat = spm_VOI8(SPM,xSPM,hReg) +% List of local maxima and adjusted p-values for a small Volume of Interest +% FORMAT TabDat = spm_VOI(SPM,xSPM,hReg) +% +% SPM - structure containing analysis details (see spm_spm) +% +% xSPM - structure containing SPM, distribution & filtering details +% - required fields are: +% .swd - SPM working directory - directory containing current SPM.mat +% .Z - minimum of n Statistics {filtered on u and k} +% .n - number of conjoint tests +% .STAT - distribution {Z, T, X or F} +% .df - degrees of freedom [df{interest}, df{residual}] +% .u - height threshold +% .k - extent threshold {resels} +% .XYZ - location of voxels {voxel coords} +% .XYZmm - location of voxels {mm} +% .S - search Volume {voxels} +% .R - search Volume {resels} +% .FWHM - smoothness {voxels} +% .M - voxels - > mm matrix +% .VOX - voxel dimensions {mm} +% .DIM - image dimensions {voxels} - column vector +% .Vspm - Mapped statistic image(s) +% .Ps - uncorrected P values in searched volume (for voxel FDR) +% .Pp - uncorrected P values of peaks (for peak FDR) +% .Pc - uncorrected P values of cluster extents (for cluster FDR) +% +% hReg - Handle of results section XYZ registry (see spm_results_ui.m) +% +% TabDat - Structure containing table data +% - see spm_list for definition +% +%__________________________________________________________________________ +% +% spm_VOI is called by the SPM results section and takes variables in +% SPM to compute p-values corrected for a specified volume of interest. +% +% The volume of interest may be defined as a box or sphere centred on +% the current voxel or by a mask image. +% +% If the VOI is defined by a mask this mask must have been defined +% independently of the SPM (e.g.using a mask based on an orthogonal +% contrast) +% +% External mask images should be in the same orientation as the SPM +% (i.e. as the input used in stats estimation). The VOI is defined by +% voxels with values greater than 0. +% +% FDR computations are similarly resticted by the small search volume +% +% See also: spm_list +%__________________________________________________________________________ +% Copyright (C) 2008 Wellcome Trust Centre for Neuroimaging + +% Karl Friston +% $Id: spm_VOI.m 2782 2009-02-24 18:53:13Z guillaume $ + + +%-Parse arguments +%-------------------------------------------------------------------------- +if nargin < 2, error('insufficient arguments'), end +if nargin < 3, hReg = []; end + +Num = 16; % maxima per cluster +Dis = 04; % distance among maxima (mm) + +%-Title +%-------------------------------------------------------------------------- +spm('FigName',['SPM{',xSPM.STAT,'}: Small Volume Correction']); + +%-Get current location {mm} +%-------------------------------------------------------------------------- +%xyzmm = spm_results_ui('GetCoords'); +xyzmm = spm_XYZreg('GetCoords',hReg);% added by xu cui + +% added by xu cui, open a figure so users can input parameters +f = figure('unit','normalized','position',[0.1 0.1 0.3 0.3]); + +%-Specify search volume +%-------------------------------------------------------------------------- +str = sprintf(' at [%.0f,%.0f,%.0f]',xyzmm(1),xyzmm(2),xyzmm(3)); +SPACE = spm_input('Search volume...',-1,'m',... + {['Sphere',str],['Box',str],'Image'},['S','B','I']); + +% voxels in entire search volume {mm} +%-------------------------------------------------------------------------- +XYZmm = SPM.xVol.M(1:3,:)*[SPM.xVol.XYZ; ones(1, SPM.xVol.S)]; +Q = ones(1,size(xSPM.XYZmm,2)); +O = ones(1,size( XYZmm,2)); +FWHM = xSPM.FWHM; + +switch SPACE + + case 'S' %-Sphere + %---------------------------------------------------------------------- + D = spm_input('radius of VOI {mm}',-2); + str = sprintf('%0.1fmm sphere',D); + j = find(sum((xSPM.XYZmm - xyzmm*Q).^2) <= D^2); + k = find(sum(( XYZmm - xyzmm*O).^2) <= D^2); + D = D./xSPM.VOX; + + + case 'B' %-Box + %---------------------------------------------------------------------- + D = spm_input('box dimensions [k l m] {mm}',-2); + if length(D)~=3, D = ones(1,3)*D(1); end + str = sprintf('%0.1f x %0.1f x %0.1f mm box',D(1),D(2),D(3)); + j = find(all(abs(xSPM.XYZmm - xyzmm*Q) <= D(:)*Q/2)); + k = find(all(abs( XYZmm - xyzmm*O) <= D(:)*O/2)); + D = D./xSPM.VOX; + + + case 'I' %-Mask Image + %---------------------------------------------------------------------- + Msk = spm_select(1,'image','Image defining search volume'); + D = spm_vol(Msk); + str = strrep(spm_str_manip(Msk,'a30'),'\','\\'); + str = strrep(str,'^','\^'); str = strrep(str,'_','\_'); + str = strrep(str,'{','\{'); str = strrep(str,'}','\}'); + str = sprintf('image mask: %s',str); + VOX = sqrt(sum(D.mat(1:3,1:3).^2)); + FWHM = FWHM.*(xSPM.VOX./VOX); + XYZ = D.mat \ [xSPM.XYZmm; ones(1, size(xSPM.XYZmm, 2))]; + j = find(spm_sample_vol(D, XYZ(1,:), XYZ(2,:), XYZ(3,:),0) > 0); + XYZ = D.mat \ [ XYZmm; ones(1, size( XYZmm, 2))]; + k = find(spm_sample_vol(D, XYZ(1,:), XYZ(2,:), XYZ(3,:),0) > 0); + +end + +xSPM.S = length(k); +xSPM.R = spm_resels(FWHM,D,SPACE); +xSPM.Z = xSPM.Z(j); +xSPM.XYZ = xSPM.XYZ(:,j); +xSPM.XYZmm = xSPM.XYZmm(:,j); + +%-Restrict FDR to the search volume +%-------------------------------------------------------------------------- +df = xSPM.df; +STAT = xSPM.STAT; +DIM = xSPM.DIM; +R = xSPM.R; +n = xSPM.n; +Z = xSPM.Z; +u = xSPM.u; +S = xSPM.S; + +try, xSPM.Ps = xSPM.Ps(k); end +[up, xSPM.Pp] = spm_uc_peakFDR(0.05,df,STAT,R,n,Z,SPM.xVol.XYZ(:,k),u); +try % if STAT == 'T' + V2R = 1/prod(xSPM.FWHM(DIM>1)); + [uc, xSPM.Pc, ue] = spm_uc_clusterFDR(0.05,df,STAT,R,n,Z,SPM.xVol.XYZ(:,k),V2R,u); +catch + uc = NaN; + ue = NaN; + xSPM.Pc = []; +end +uu = spm_uc(0.05,df,STAT,R,n,S); +xSPM.uc = [uu up ue uc]; + +%-Tabulate p values +%-------------------------------------------------------------------------- +str = sprintf('search volume: %s',str); +if any(strcmp(SPACE,{'S','B'})) + str = sprintf('%s at [%.0f,%.0f,%.0f]',str,xyzmm(1),xyzmm(2),xyzmm(3)); +end + +TabDat = spm_list8('List',xSPM,hReg,Num,Dis,str); + +%-Reset title +%-------------------------------------------------------------------------- +spm('FigName',['SPM{',xSPM.STAT,'}: Results']); + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% volume push +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_volumePush(hObject, eventdata) +handles = guidata(hObject); +mni = cell2mat(handles.currentDisplayMNI'); +intensity = cell2mat(handles.currentDisplayIntensity'); + +xSPM.XYZ = mni2cor(mni, handles.M{1}); +xSPM.XYZ = xSPM.XYZ'; +xSPM.XYZmm = mni'; +xSPM.Z = (intensity'); +xSPM.M = handles.M{1}; +xSPM.DIM = handles.DIM{1}; +xSPM.STAT = handles.TF{1}; +if xSPM.STAT == 'T' + xSPM.STATstr = [xSPM.STAT '_{' num2str(handles.df{1}) '}']; + xSPM.df = [1 handles.df{1}]; +elseif xSPM.STAT == 'F' + xSPM.STATstr = [xSPM.STAT '_{' num2str(handles.df{1}(1)) ',' num2str(handles.df{1}(2)) '}']; + xSPM.df = [handles.df{1}]; +else + xSPM.STAT = 'T'; + xSPM.STATstr = [xSPM.STAT '_{' num2str(handles.df{1}) '}']; + xSPM.df = [1 handles.df{1}]; +end +xSPM.k = str2num(get(handles.clusterSizeThresholdEdit, 'string')); +xSPM.u = str2num(get(handles.intensityThresholdEdit, 'string')); +xSPM.u = xSPM.u(1); +xSPM.VOX = abs([xSPM.M(1,1) xSPM.M(2,2) xSPM.M(3,3)]); +xSPM.n = 1; +xSPM.uc = [4.7070 4.2941 338 338]; +xSPM.Pc = [0.0000 0.0000 0.0000 0.0668 0.1401 0.1723 0.1723 0.2412 0.2723 0.3092 0.5687 0.5687 0.7008 0.7008]; +xSPM.Pp = [0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0001 0.0001 0.0001 0.0005 0.0005 0.0015 0.0107 0.0121 0.0123 0.0383 0.0435 0.1019 0.1955 0.2257 0.2966 0.5612 0.6060 0.7725 0.9944]; + +% SPM - structure containing SPM, distribution & filtering details +% - required fields are: +% .swd - SPM working directory - directory containing current SPM.mat +% .Z - minimum of n Statistics {filtered on u and k} +% .n - number of conjoint tests +% .STAT - distribution {Z, T, X or F} +% .df - degrees of freedom [df{interest}, df{residual}] +% .u - height threshold +% .k - extent threshold {voxels} +% .XYZ - location of voxels {voxel coords} +% .XYZmm - location of voxels {mm} +% .S - search Volume {voxels} +% .R - search Volume {resels} +% .FWHM - smoothness {voxels} +% .M - voxels - > mm matrix +% .VOX - voxel dimensions {mm} +% .Vspm - mapped statistic image(s) +% .Ps - uncorrected P values in searched volume (for FDR) + +xSPM.S = length(handles.intensity{1}); +xSPM.R = [3 27.0931 276.3307 498.1985]; +xSPM.FWHM = [2.9746 3.1923 2.8600]; + +if get(handles.positiveIntensityRadio, 'Value') + xSPM.Z = abs(xSPM.Z); + if xSPM.STAT == 'T'; xSPM.Ps = (1-spm_Tcdf(handles.intensity{1}, xSPM.df(2))); end + if xSPM.STAT == 'F'; xSPM.Ps = (1-spm_Fcdf(handles.intensity{1}, xSPM.df)); end +end + +if get(handles.negativeIntensityRadio, 'Value'); + xSPM.Z = abs(xSPM.Z); + if xSPM.STAT == 'T'; xSPM.Ps = (1-spm_Tcdf(-handles.intensity{1}, xSPM.df(2))); end + if xSPM.STAT == 'F'; xSPM.Ps = (1-spm_Fcdf(-handles.intensity{1}, xSPM.df)); end +end + +if get(handles.allIntensityRadio, 'Value'); + if xSPM.STAT == 'T'; xSPM.Ps = (1-spm_Tcdf((handles.intensity{1}), xSPM.df(2))); end + if xSPM.STAT == 'F'; xSPM.Ps = (1-spm_Fcdf((handles.intensity{1}), xSPM.df)); end +end + +if findstr('SPM2',spm('ver')) + P = spm_get([0 1],'SPM.mat','locate the corresponding SPM.mat'); +else%if findstr('SPM5',spm('ver')) + P = spm_select([0:1],'SPM.mat','locate the corresponding SPM.mat'); +end + +if ~isempty(P) + load(P); + xSPM.FWHM = SPM.xVol.FWHM; + xSPM.R = SPM.xVol.R; + xSPM.S = SPM.xVol.S; +else + warndlg(['You did not input SPM.mat. The listed result may not be correct.'], 'SPM.mat missing'); +end + +if findstr('SPM8',spm('ver')) + spm_list8('List',xSPM,handles.hReg); +else%if findstr('SPM5',spm('ver')) + spm_list('List',xSPM,handles.hReg); +end + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% display +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_displayPush(hObject, eventdata) +handles = guidata(hObject); +spm_image('init', handles.imageFileName); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% all in one +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_allinonePush(hObject, eventdata) +try + if findstr('SPM2',spm('ver')) + P = spm_get([0:100],'*IMAGE','Select image files'); + else%if findstr('SPM5',spm('ver')) + P = spm_select(Inf,'image','Select image files'); + end + +catch + return; +end +if isempty(P) + return +end + +handles = guidata(hObject); +%if ~isfield(handles,'hReg') | ~isfield(handles,'hSection') | ~isfield(handles,'hcolorbar') + tmp = [0 0 0]; + [handles.hReg, handles.hSection, handles.hcolorbar] = Draw(tmp([],:), [], hObject, handles); + %end + +colors = [1 0 0;1 1 0;0 1 0;0 1 1;0 0 1;1 0 1]; +for ii=1:size(P,1) + thisfilename = deblank(P(ii,:)); + [handles.imageFileName, handles.M, handles.DIM, handles.TF, handles.df, handles.mni, handles.intensity] = getImageFile(thisfilename); + cor = mni2cor(handles.mni, handles.M); + spm_orthviews('addcolouredblobs',1,cor',handles.intensity',handles.M,colors(mod(ii,6)+1,:)); +end +spm_orthviews('Redraw'); + +guidata(hObject, handles); + +% contents = get(handles.sectionViewListbox,'String'); +% currentsel = contents{get(handles.sectionViewListbox,'Value')}; +% sectionViewTargetFile = getSectionViewTargetFile(handles.spmdir, currentsel); +% +% % spm_image('init', sectionViewTargetFile); +% % spm_image('addblobs'); +% % return + +% guidata(hObject, handles); +% load xSPM; +% VOL.XYZ = xSPM.XYZ; +% VOL.Z = xSPM.Z; +% VOL.M = handles.M; + +%addcolouredimage(handles.hSection, '333.img',[1 0 1]); +% uigetfiles +% nblobs = 4; +% for i=1:nblobs, +% %[SPM,VOL] = spm_getSPM; +% %c = spm_input('Colour','+1','m','Red blobs|Yellow blobs|Green blobs|Cyan blobs|Blue blobs|Magenta blobs',[1 2 3 4 5 6],1); +% c = i; +% VOL.XYZ = ceil(rand(3,20)*20); +% VOL.Z = randn(1,20); +% VOL.M = handles.M; +% colours = [1 0 0;1 1 0;0 1 0;0 1 1;0 0 1;1 0 1]; +% spm_orthviews('addcolouredblobs',1,VOL.XYZ,VOL.Z,VOL.M,colours(c,:)); +% end; + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% overlay a brain region Push +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_overlayPush(hObject, eventdata) +handles = guidata(hObject); + +tobeoverlay = deblank(get(handles.overlayEdit, 'string')); + +if isempty(tobeoverlay) + return; +end + +tobeoverlay = str2cell(tobeoverlay, ' '); +if isnumeric(tobeoverlay{1}) + if length(tobeoverlay{1})>1 + errordlg(['Your input, ' deblank(get(handles.overlayEdit, 'string')) ', seems coincide with a matlab constant.'], 'error'); + return + end + for ii=1:length(tobeoverlay) + tobeoverlay{ii} = num2str(tobeoverlay{ii}); + end +end +fn = fieldnames(handles.wholeMaskMNIAll); +for ii=1:length(tobeoverlay) + pos{ii} = []; + for jj=1:length(fn) +% x = []; +% if ~isempty(str2num(tobeoverlay{ii})) +% y = str2cell(fn{jj},'_'); +% for kk=1:length(y) +% x = isequal(tobeoverlay{ii}, y{kk}); +% if x; break;end; +% end +% if x==0; x = []; end; +% else +% x = findstr(lower(tobeoverlay{ii}), lower(fn{jj})); +% end + if strcmp(tobeoverlay{ii}, fn{jj}) + pos{ii} = [pos{ii} jj]; + end + end +end +common = pos{1}; +for ii=2:length(pos) + common = intersect(common, pos{ii}); +end + +if isempty(common) + %warndlg(['I don'' find ' deblank(get(handles.overlayEdit, 'string')) '.'], 'oops'); + set(handles.infoTextBox, 'string', ['I don'' find ' deblank(get(handles.overlayEdit, 'string')) '.']); + return +end + +mask = []; +for ii=1:length(common) + eval(['mask = [mask; handles.wholeMaskMNIAll.' fn{common(ii)} '];']); +end + +if isempty(mask) + %warndlg(['I don'' find ' deblank(get(handles.overlayEdit, 'string')) '.'], 'oops'); + set(handles.infoTextBox, 'string', ['I don'' find ' deblank(get(handles.overlayEdit, 'string')) '.']); + return; +end + +try + handles.mni; +catch + delete(gcf); + xjview(mask); + return; +end + +handles.imageFileName = [handles.imageFileName, {deblank(get(handles.overlayEdit, 'string'))}]; +handles.M = [handles.M, {handles.M{1}}]; +handles.DIM = [handles.DIM, {handles.DIM{1}}]; +handles.currentDisplayMNI = [handles.currentDisplayMNI, {mask}]; +m = max(abs(cell2mat(handles.currentDisplayIntensity'))); +if ~isempty(m) + handles.currentDisplayIntensity = [handles.currentDisplayIntensity, {-2*m*ones(size(mask,1),1)}]; +else + handles.currentDisplayIntensity = [handles.currentDisplayIntensity, {ones(size(mask,1),1)}]; +end +[handles.hReg, handles.hSection, handles.hcolorbar] = Draw(handles.currentDisplayMNI, handles.currentDisplayIntensity, hObject, handles); +return + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% overlay a brain region Edit +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_overlayEdit(hObject, eventdata) +handles = guidata(hObject); +CallBack_overlayPush(handles.overlayPush, eventdata); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% overlay a brain region popup +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_overlayPop(hObject, eventdata) +handles = guidata(hObject); +names = get(hObject, 'string'); +value = get(hObject, 'value'); +set(handles.overlayEdit, 'string', names{value}); +CallBack_overlayEdit(handles.overlayEdit, eventdata); + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% search xBrain.org and other databases +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_searchPush(hObject, eventdata) + +try + handles = guidata(hObject); + searchContent = get(handles.searchContentEdit,'string'); + searchEngine = get(handles.searchEnginePop,'value'); + xbrainSearchField = 'region'; + if isempty(deblank(searchContent)) + xyz = spm_XYZreg('GetCoords',handles.hReg); + handles.currentxyz = xyz'; + searchContent = num2str(xyz'); + set(handles.searchContentEdit,'string',searchContent); + guidata(hObject, handles); + xbrainSearchField = 'mni or tal&mnidistance=20'; + end + + if searchEngine == 1 + urlstr = ['http://people.hnl.bcm.tmc.edu/cuixu/cgi-bin/bmd/paper.pl?search_content=' searchContent '&search_field=' xbrainSearchField]; + elseif searchEngine == 2 + urlstr = ['http://scholar.google.com/scholar?q=%22' searchContent '%22']; + elseif searchEngine == 3 + urlstr = ['http://www.ncbi.nlm.nih.gov/entrez/query.fcgi?cmd=PureSearch&db=pubmed&details_term=%22' searchContent '%22']; + elseif searchEngine == 4 + urlstr = ['http://en.wikipedia.org/w/index.php?search=%22' searchContent '%22']; + end +catch + urlstr = 'http://www.xbrain.org'; +end + +web(urlstr); + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% control panel on or off +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function controlHide(handles, status) + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% get multiple values from a string deliminated delim +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function out = str2cell(str, delim) +if ~exist('delim') + delim = '; '; +end + +[out{1},b] = strtok(str, delim); +ii = 2; +while ~isempty(b) + [out{ii},b] = strtok(b, delim); + ii = ii+1; +end + +for ii=1:length(out) + out2{ii} = str2num(out{ii}); + if isempty(out2{ii}) + return; + end +end + +for ii=1:length(out) + out{ii} = str2num(out{ii}); +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% cell2str +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function str = cell2str(acell, delim) +if ~exist('delim') + delim = ';'; +end + +str = []; + +if length(acell)==1 + if isstr(acell{1}) + str = acell{1}; + else + str = num2str(acell{1}); + end +else + for ii=1:length(acell) + if isstr(acell{ii}) + str = [str acell{ii}, delim ' ']; + else + str = [str num2str(acell{ii}), delim ' ']; + end + end + str(end-1:end)=[]; +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% cellmax, find max in each element, return a cell +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function MAXX = cellmax(acell, absolute) + +if ~exist('absolute') + absolute = ''; +end + +MAXX = []; +for ii=1:length(acell) + if strfind('abs',absolute) + MAXX = [MAXX max(abs(acell{ii}))]; + else + MAXX = [MAXX max(acell{ii})]; + end +end +MAXX = num2cell(MAXX); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% maxcell +%%% find max of all numbers in the whole cell, return a single value +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function MAXX = maxcell(acell, absolute) + +if ~exist('absolute') + absolute = ''; +end +MAXX = cellmax(acell, absolute); +MAXX = max(cell2mat(MAXX)); + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% p2t +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function t = p2t(p, df, TF) +if ~iscell(p) + if upper(TF)=='T' | upper(TF)=='S' + t = spm_invTcdf(1-p,df); + elseif upper(TF) == 'F' + t = spm_invFcdf(1-p,df); + end +else + for ii=1:length(p) + t{ii} = p2t(p{ii},df{ii},TF{ii}); + end +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% s2t, s is defined as -log10(p) +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function t = s2t(s, df, TF) + +if ~iscell(s) + p = 10^(-s); + if upper(TF)=='T' | upper(TF)=='S' + t = spm_invTcdf(1-p,df); + elseif upper(TF) == 'F' + t = spm_invFcdf(1-p,df); + end +else + for ii=1:length(p) + t{ii} = s2t(s{ii},df{ii},TF{ii}); + end +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% t2p +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function p = t2p(t, df, TF) +if ~iscell(t) + if upper(TF)=='T' | upper(TF)=='S' + p = 1-spm_Tcdf(t,df); + elseif upper(TF) == 'F' + p = 1-spm_Fcdf(t,df); + end +else + for ii=1:length(t) + p{ii} = t2p(t{ii},df{ii},TF{ii}); + end +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% t2s, s is defined as -log10(p) +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function s = t2s(t, df, TF) +if ~iscell(t) + if upper(TF)=='T' | upper(TF)=='S' + p = 1-spm_Tcdf(t,df); + elseif upper(TF) == 'F' + p = 1-spm_Fcdf(t,df); + else + p = 0.1; + end + s = -log10(p); +else + s = {10}; + for ii=1:length(t) + s{ii} = t2s(t{ii},df{ii},TF{ii}); + end +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% mni2mask +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function mask = mni2mask(coords, targetfilename, intensity, M, DIM, templateFile, isMask) +% function mask = mni2mask(coords, targetfilename, intensity, templateFile) +% make mask from coordinates +% +% coords: a Nx3 column of 3-d coordinates in MNI +% space +% targetfilename: (optional) the image files to be written +% intensity: (optional) Nx1, the values of each coordinate. +% M: rotation matrix +% DIM: dimension +% templateFile: (optional) the templateFile from which we can get the right +% dimensions. +% isMask: if this variable exist and equal to 1, then all non-zero +% intensities will be set to 1 +% +% Xu Cui +% 2004/11/18 + +if ~exist('intensity') + intensity = ones(size(coords,1),1); +end +thistemplateFile = ''; +if exist('templateFile') + if ~isempty(templateFile) + thistemplateFile = templateFile; + end +end + +if isempty(thistemplateFile) + V.mat = [... + -4 0 0 84; ... + 0 4 0 -116; ... + 0 0 4 -56; ... + 0 0 0 1]; + V.dim = [41 48 35 16]; + V.dt = [4,0]; + if exist('M') + V.mat = M; + end + if exist('DIM') + V.dim = DIM; + if findstr('SPM2',spm('ver')) + V.dim(4) = 16; + end + end + V.fname = targetfilename; + V.descrip = 'our own image'; +else + V = spm_vol(templateFile); + V.fname = targetfilename; + if isfield(V, 'descrip') + V.descrip = ['my image from ' V.descrip]; + else + V.descrip = 'my own image'; + end +end + +thisismask = 0; +if exist('isMask') + if isMask == 1 + thisismask = 1; + end +end +if thisismask + V.descrip = 'my mask'; + intensity = ones(size(intensity)); +end + +O = zeros(V.dim(1),V.dim(2),V.dim(3)); + +coords = mni2cor(coords,V.mat); + + +for ii=1:size(coords,1) + O(coords(ii,1),coords(ii,2),coords(ii,3)) = intensity(ii); +end + +if exist('targetfilename') + V = spm_write_vol(V,O); +end + +mask = O; + +return; + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% get image file information +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function [imageFile,M,DIM,TF,df,mni,intensity] = getImageFile(thisfilename) +% function imageFile = getImageFile(filename) +% get the information of this/these image file(s) +% +% thisfilename: (optional) if doesn't give file name, then show a open file +% dialog +% imageFile: the full name of the selected file (if user pressed cancel, +% imageFile == 0 +% M: M matrix (rotation matrix) +% DIM: dimension +% TF: t test or f test? 'T' or 'F' +% df: degree of freedome +% mni: mni coord +% intensity: intensity of each mni coord +% +% Note: The returned variables are cellarrays. +% +% Xu Cui +% last revised: 2005-05-03 + +if nargin < 1 | isempty(thisfilename) + if findstr('SPM2',spm('ver')) + P0 = spm_get([0:100],'*IMAGE','Select image files'); + else%if findstr('SPM5',spm('ver')) + P0 = spm_select(Inf,'image','Select image files'); + end + +% try +% P0 = spm_get([0:100],'*IMAGE','Select image files'); +% catch +% P0 = []; +% [FileName,PathName] = uigetfile({'*.img';'*.IMG';'*.*'},'Select image files','MultiSelect','on'); +% if isstr(FileName) +% P0 = {fullfile(PathName, FileName)}; +% elseif iscellstr(FileName) +% for ii=1:length(FileName) +% P0{ii} = fullfile(PathName, FileName{ii}); +% end +% else +% P0 = []; +% end +% end + try + if isempty(P0) + imageFile = '';M=[];DIM=[];TF=[];df=[];mni=[];intensity=[]; + return + end + end + for ii=1:size(P0,1) + P{ii} = deblank(P0(ii,:)); + end +else + if isstr(thisfilename) + P = {thisfilename}; + elseif iscellstr(thisfilename) + P = thisfilename; + else + disp('Error: In getImageFile: I don''t understand the input.'); + imageFile = '';M=[];DIM=[];TF=[];df=[];mni=[];intensity=[]; + return + end +end + +global LEFTRIGHTFLIP_; + +for ii=1:length(P) + imageFile{ii} = P{ii}; + [cor{ii}, intensity{ii}, tmp{ii}, M{ii}, DIM{ii}, TF{ii}, df{ii}] = mask2coord(imageFile{ii}, 0); + if LEFTRIGHTFLIP_ == 1 + M{ii}(1,:) = -M{ii}(1,:); + end + mni{ii} = cor2mni(cor{ii}, M{ii}); +end + + + + + + + + + + + + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% cuixuFindStructure +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function [onelinestructure, cellarraystructure] = cuixuFindStructure(mni, DB) +% function [onelinestructure, cellarraystructure] = cuixuFindStructure(mni, DB) +% +% this function converts MNI coordinate to a description of brain structure +% in aal +% +% mni: the coordinates (MNI) of some points, in mm. It is Nx3 matrix +% where each row is the coordinate for one point +% LDB: the database. This variable is available if you load +% TDdatabase.mat +% +% onelinestructure: description of the position, one line for each point +% cellarraystructure: description of the position, a cell array for each point +% +% Example: +% cuixuFindStructure([72 -34 -2; 50 22 0], DB) +% +% Xu Cui +% 2007-11-20 +% + +N = size(mni, 1); + +% round the coordinates +mni = round(mni/2) * 2; + +T = [... + 2 0 0 -92 + 0 2 0 -128 + 0 0 2 -74 + 0 0 0 1]; + +index = mni2cor(mni, T); + +cellarraystructure = cell(N, length(DB)); +onelinestructure = cell(N, 1); + +for ii=1:N + for jj=1:length(DB) + graylevel = DB{jj}.mnilist(index(ii, 1), index(ii, 2),index(ii, 3)); + if graylevel == 0 + thelabel = 'undefined'; + else + if jj==length(DB); tmp = ' (aal)'; else tmp = ''; end + thelabel = [DB{jj}.anatomy{graylevel} tmp]; + end + cellarraystructure{ii, jj} = thelabel; + onelinestructure{ii} = [ onelinestructure{ii} ' // ' thelabel ]; + end +end + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% mask2coord +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function [cor, intensity, cor_singlecolumn,M,DIM,TF,df] = mask2coord(mask, checkdimension) +% [cor, intensity, cor_singlecolumn] = mask2coord(mask, checkdimension) +% +% This is to retrieve the coordinate of a mask file, or a matrix of 3-D +% +% mask: an image file or a matrix (3-D), with some of the elements are +% non-zeros +% checkdimension: check if the dimension is checkdimension, if not, return empty +% matrix +% cor: a N*3 matrix, which each row a coordinate in matrix +% intensity: a N*1 matrix, which encodes the intensity of each voxel. +% cor_singlecolumn: a N*1 matrix, each row is the index in the matrix +% M: rotation matrix +% DIM: dimension +% TF: t test or f test? 'T','F' or [] +% df: degree of freedome for T/F test +% +% Example: +% mask = zeros(4,3,2); +% mask(1,2,1) = 1; +% mask(3,2,2) = 1; +% mask2coord(mask) +% +% mask2coord('spmT_0002.img') +% mask2coord('spmT_0002.img',[41 48 35]) +% +% Xu Cui +% 2004-9-20 +% last revised: 2005-04-30 + +if nargin < 2 + checkdimension = 0; +end + +if ischar(mask) + V = spm_vol(mask); + mask = spm_read_vols(V); + M = V.mat; + DIM = V.dim; + TF = 'T'; + T_start = strfind(V.descrip,'SPM{T_[')+length('SPM{T_['); + if isempty(T_start); T_start = strfind(V.descrip,'SPM{F_[')+length('SPM{F_['); TF='F'; end + if isempty(T_start) + TF=[]; df=[]; + else + T_end = strfind(V.descrip,']}')-1; + df = str2num(V.descrip(T_start:T_end)); + end +else + M = []; + TF = []; + df = []; +end + +dim = size(mask); +if length(checkdimension)==3 + if dim(1)~= checkdimension(1) | dim(2)~= checkdimension(2) | dim(3)~= checkdimension(3) + y = []; + disp('dimension doesn''t match') + return + end +end + +pos = find(mask~=0); +intensity = mask(pos); + +y = zeros(length(pos),3); + +y(:,3) = ceil(pos/(dim(1)*dim(2))); +pos = pos - (y(:,3)-1)*(dim(1)*dim(2)); +y(:,2) = ceil(pos/dim(1)); +pos = pos - (y(:,2)-1)*(dim(1)); +y(:,1) = pos; + +cor = y; +cor_singlecolumn = pos; +DIM = dim; +return + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% mni2cor +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function coordinate = mni2cor(mni, T) +% function coordinate = mni2cor(mni, T) +% convert mni coordinate to matrix coordinate +% +% mni: a Nx3 matrix of mni coordinate +% T: (optional) transform matrix +% coordinate is the returned coordinate in matrix +% +% caution: if T is not specified, we use: +% T = ... +% [-4 0 0 84;... +% 0 4 0 -116;... +% 0 0 4 -56;... +% 0 0 0 1]; +% +% xu cui +% 2004-8-18 +% + +if isempty(mni) + coordinate = []; + return; +end + +if nargin == 1 + T = ... + [-4 0 0 84;... + 0 4 0 -116;... + 0 0 4 -56;... + 0 0 0 1]; +end + +coordinate = [mni(:,1) mni(:,2) mni(:,3) ones(size(mni,1),1)]*(inv(T))'; +coordinate(:,4) = []; +coordinate = round(coordinate); +return; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% cor2mni +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function mni = cor2mni(cor, T) +% function mni = cor2mni(cor, T) +% convert matrix coordinate to mni coordinate +% +% cor: an Nx3 matrix +% T: (optional) rotation matrix +% mni is the returned coordinate in mni space +% +% caution: if T is not given, the default T is +% T = ... +% [-4 0 0 84;... +% 0 4 0 -116;... +% 0 0 4 -56;... +% 0 0 0 1]; +% +% xu cui +% 2004-8-18 +% last revised: 2005-04-30 + +if nargin == 1 + T = ... + [-4 0 0 84;... + 0 4 0 -116;... + 0 0 4 -56;... + 0 0 0 1]; +end + +cor = round(cor); +mni = T*[cor(:,1) cor(:,2) cor(:,3) ones(size(cor,1),1)]'; +mni = mni'; +mni(:,4) = []; +return; + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% draw +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function [hReg, hSection, hcolorbar] = Draw(mniCoord, intensity, hObject, handles) + +try + delete(handles.hcolorbar); +end +try + delete(handles.hReg); +end +try + delete(handles.hSection); +end +try + cla(handles.glassViewAxes); +end + +try + H = findobj(get(handles.figure,'Children'),'flat','Type','axes'); + un = cellstr(get(H,'Units')); + pos = get(H,'position'); + + for ii=1:length(H) + if findstr('pixels', un{ii}) + continue; + end + if pos{ii}(1)>0.4 + delete(H(ii)); + end + end +end + +sectionViewTargetFile = handles.sectionViewTargetFile; +rendStyle = get(handles.renderStylePop,'value'); +if(rendStyle == 1) + rendStyle = 'new'; +else + rendStyle = 'old'; +end + + +if ~iscell(mniCoord) | (iscell(mniCoord) & length(mniCoord)==1)% multiple input? no + if (iscell(mniCoord) & length(mniCoord)==1) + mniCoord = mniCoord{1}; + intensity = intensity{1}; + handles.M = handles.M{1}; + handles.DIM = handles.DIM{1}; + end + if max(intensity)*min(intensity) < 0 + [hReg, hSection, hcolorbar] = cuixuSectionView(mniCoord,intensity,sectionViewTargetFile,hObject,handles); + else + [hReg, hSection, hcolorbar] = cuixuSectionView(mniCoord,abs(intensity),sectionViewTargetFile,hObject,handles); + end + + if size(mniCoord,1)>1 + pos1 = find(intensity>=0); + pos2 = find(intensity<0); + if ~isempty(pos1) & ~isempty(pos2) + if get(handles.renderViewCheck, 'Value'); cuixuRenderView(rendStyle, mniCoord(pos1,:),intensity(pos1,:),mniCoord(pos2,:),-intensity(pos2,:) ); end + else + if get(handles.renderViewCheck, 'Value'); cuixuRenderView(rendStyle, mniCoord, abs(intensity)); end + end + end +else % multiple input? yes + [hReg, hSection, hcolorbar] = cuixuSectionView(mniCoord,intensity,sectionViewTargetFile,hObject,handles); + + mniCoordtmp=[]; + intensitytmp=[]; + for ii=1:length(mniCoord) + mniCoordtmp = [mniCoordtmp; mniCoord{ii}]; + intensitytmp = [intensitytmp; intensity{ii}]; + end + mniCoord = mniCoordtmp; + intensity = intensitytmp; + if size(mniCoord,1)>1 + pos1 = find(intensity>=0); + pos2 = find(intensity<0); + if ~isempty(pos1) & ~isempty(pos2) + if get(handles.renderViewCheck, 'Value'); cuixuRenderView(rendStyle, mniCoord(pos1,:),intensity(pos1,:),mniCoord(pos2,:),-intensity(pos2,:)); end + else + if get(handles.renderViewCheck, 'Value'); cuixuRenderView(rendStyle, mniCoord, abs(intensity)); end + end + end + +end + +try + spm_XYZreg('SetCoords',handles.currentxyz',hReg); +end + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% cuixuSectionView +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function [hReg, hSection, hcolorbar] = cuixuSectionView(mniCoord, intensity, targetFile, hObject,handles) +% function h = cuixuSectionView(mniCoord, intensity) +% This is to project your coordinate to section view +% +% mniCoord: the mni coordinate, N*3 matrix +% intensity: the plot intensity of the spots, 1*N matrix. The intensity +% could be t value, for example. +% +% a special feature of this function is: it automatically seperate the +% negative and positive intensity and use hot and cold color to represent +% them. +% +% h: the returned handle for the axes +% +% SEE ALSO: cuixuView cuixuGlassView cuixuRenderView +% +% Xu Cui +% 2004/11/11 + +if ~iscell(mniCoord) | (iscell(mniCoord) & length(mniCoord)==1)% multiple input? no + multiple = 0; + if (iscell(mniCoord) & length(mniCoord)==1) + mniCoord = mniCoord{1}; + intensity = intensity{1}; + handles.M = handles.M{1}; + handles.DIM = handles.DIM{1}; + end +else + multiple = 1; + mniCoordtmp = []; + intensitytmp = []; + for ii=1:length(mniCoord) + mniCoordtmp = [mniCoordtmp; mniCoord{ii}]; + intensitytmp = [intensitytmp; intensity{ii}]; + % for multiple input + cSPM{ii}.XYZ = mni2cor(mniCoord{ii}, handles.M{ii}); + cSPM{ii}.XYZ = cSPM{ii}.XYZ'; + cSPM{ii}.Z = abs(intensity{ii}); + cSPM{ii}.M = handles.M{ii}; + cSPM{ii}.DIM = handles.DIM{ii}'; + end + mniCoord = mniCoordtmp; + intensity = intensitytmp; + handles.M = handles.M{1}; + handles.DIM = handles.DIM{1}; +end + +SPM.XYZ = mni2cor(mniCoord, handles.M); +SPM.XYZ = SPM.XYZ'; +SPM.Z = intensity; +SPM.M = handles.M; +SPM.DIM = handles.DIM'; + +%%%%%%%%%%%%%%% Reg %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +handles.colormap = colormap; + +coor = mniCoord; +xSPM = SPM; +xSPM.XYZmm = mniCoord'; +axes(handles.glassViewAxes) +WS = spm('WinScale'); +FS = spm('FontSizes'); + +Finter = gcf; +hReg = uicontrol(Finter,'Style','Frame','Position',[60 100 300 300].*WS,... + 'Visible','off'); +[hReg,xyz] = spm_XYZreg('InitReg',hReg,xSPM.M,xSPM.DIM,[0;0;0]); + +hFxyz = spm_results_ui('DrawXYZgui',xSPM.M,xSPM.DIM,xSPM,xyz,Finter); +spm_XYZreg('XReg',hReg,hFxyz,'spm_results_ui'); + +hMIPax =gca ; +setcolormap('gray'); + +hMIPax = spm_mip_ui(xSPM.Z,coor',xSPM.M,xSPM.DIM,hMIPax); +spm_XYZreg('XReg',hReg,hMIPax,'spm_mip_ui'); + +colormap(handles.colormap); +setcolormap('gray-hot'); + +if isempty(handles.M) + spm_XYZreg('SetReg',handles.structureEdit,hReg); +else + set(handles.structureEdit, 'UserData', struct('hReg',hReg,'M',handles.M,'D', handles.DIM,'xyx',[0 0 0])); +end + +spm_XYZreg('Add2Reg',hReg,handles.structureEdit,@CallBack_structureEdit); + +%%%%%%%%%%%%%%% Reg end %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +if multiple == 0 + [hgraph, hSection, hcolorbar] = spm_sections(SPM,hReg,targetFile,handles.sectionViewPosition); +else + [hgraph, hSection, hcolorbar] = spm_sections(cSPM,hReg,targetFile,handles.sectionViewPosition); +end + +return + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% spm_sections +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function [Fgraph, hMe, hcolorbar] = spm_sections(SPM,hReg,targetFile,sectionViewPosition) +% rendering of regional effects [SPM{Z}] on orthogonal sections +% FORMAT spm_sections(SPM,hReg) +% +% SPM - xSPM structure containing details of excursion set +% hReg - handle of MIP register +% +% see spm_getSPM for details +%_______________________________________________________________________ +% +% spm_sections is called by spm_results and uses variables in SPM and +% VOL to create three orthogonal sections though a background image. +% Regional foci from the selected SPM are rendered on this image. +% +%_______________________________________________________________________ +% @(#)spm_sections.m 2.14 John Ashburner 02/09/05 + +Fgraph = gcf; + +spms = fullfile(spm('dir'),'canonical', 'single_subj_T1.mnc'); +if exist('targetFile') + spms = targetFile; +end + +spm_orthviews('Reset'); +global st +st.fig = Fgraph; +st.Space = spm_matrix([0 0 0 0 0 -pi/2])*st.Space; + +spm_orthviews('Image',spms,sectionViewPosition); % position +spm_orthviews('MaxBB'); +spm_orthviews('register',hReg); +if ~iscell(SPM)0 + spm_orthviews('addblobs',1,SPM.XYZ,SPM.Z,SPM.M); +elseif length(SPM) == 1 + SPM = SPM{1}; + spm_orthviews('addblobs',1,SPM.XYZ,SPM.Z,SPM.M); +else + colors = repmat([1 0 0;1 1 0;0 1 0;0 1 1;0 0 1;1 0 1],20,1); + for ii=1:length(SPM) + spm_orthviews('addcolouredblobs',1,SPM{ii}.XYZ, SPM{ii}.Z, SPM{ii}.M, colors(ii,:)); + end +end +spm_orthviews('Redraw'); + +hMe = st.registry.hMe; +try + hcolorbar = st.vols{1}.blobs{1}.cbar; +catch + hcolorbar = 0; +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% cuixuRenderView +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function h = cuixuRenderView(style, mniCoord, intensity, varargin) +% function h = cuixuRenderView(style, mniCoord1, intensity1, mniCoord2, intensity2, mniCoord3, intensity3) +% This is to project your coordinate to render view +% +% style: either 'new' or 'old' +% mniCoord: the mni coordinate, N*3 matrix +% intensity: the plot intensity of the spots, 1*N matrix. The intensity +% could be t value, for example. +% +% You can input 1, 2, or 3 pairs of coordinates and intensity. +% +% h: the returned handle for the figure +% +% Xu Cui +% 2004/11/11 + +global M_; +global DIM_; + +if nargin < 4 + dat.XYZ = mni2cor(mniCoord, M_); + dat.XYZ = dat.XYZ'; + if nargin < 2 + dat.t = ones(size(mniCoord,1),1); + else + dat.t = intensity; + end + dat.mat = M_; + dat.dim = DIM_; +else + if mod(nargin+1,2) ~=0 + disp('You should put even number of parameters.') + return; + end + for ii=1:(2+length(varargin))/2 + if ii==1 + dat(ii).XYZ = mni2cor(mniCoord, M_); + dat(ii).t = intensity; + else + dat(ii).XYZ = mni2cor(varargin{2*(ii-1)-1}, M_); + dat(ii).t = varargin{2*(ii-1)}; + end + dat(ii).XYZ = dat(ii).XYZ'; + dat(ii).mat = M_; + dat(ii).dim = DIM_; + end +end + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% spm_render +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +if strcmp(style, 'old') + brt = nan; +else + brt = 1; +end +h = spm_render(dat,brt,fullfile(spm('dir'), 'rend', 'render_single_subj.mat')); + +function Fgraph = spm_render(dat,brt,rendfile) +% Render blobs on surface of a 'standard' brain +% FORMAT spm_render(dat,brt,rendfile) +% +% dat - a vertical cell array of length 1 to 3 +% - each element is a structure containing: +% - XYZ - the x, y & z coordinates of the transformed t values. +% in units of voxels. +% - t - the SPM{.} values +% - mat - affine matrix mapping from XYZ voxels to Talairach. +% - dim - dimensions of volume from which XYZ is drawn. +% brt - brightness control: +% If NaN, then displays using the old style with hot +% metal for the blobs, and grey for the brain. +% Otherwise, it is used as a ``gamma correction'' to +% optionally brighten the blobs up a little. +% rendfile - the file containing the images to render on to. See also +% spm_xbrain.m. +% +% Without arguments, spm_render acts as its own UI. +%_______________________________________________________________________ +% +% spm_render prompts for details of up to three SPM{Z}s or SPM{t}s that +% are then displayed superimposed on the surface of a standard brain. +% +% The first is shown in red, then green then blue. +% +% The blobs which are displayed are the integral of all transformed t +% values, exponentially decayed according to their depth. Voxels that +% are 10mm behind the surface have half the intensity of ones at the +% surface. +%_______________________________________________________________________ +% @(#)spm_render.m 2.19 John Ashburner FIL 02/10/29 + +%-Parse arguments, get data if not passed as parameters +%======================================================================= +if nargin < 1 + SPMid = spm('FnBanner',mfilename,'2.19'); + [Finter,Fgraph,CmdLine] = spm('FnUIsetup','Results: render',0); + + num = spm_input('Number of sets',1,'1 set|2 sets|3 sets',[1 2 3]); + + for i = 1:num, + [SPM,VOL] = spm_getSPM; + dat(i) = struct( 'XYZ', VOL.XYZ,... + 't', VOL.Z',... + 'mat', VOL.M,... + 'dim', VOL.DIM); + end; + showbar = 1; +else, + num = length(dat); + showbar = 0; +end; + +% get surface +%----------------------------------------------------------------------- +if nargin < 3, + rendfile = spm_get(1,'render*.mat','Render file',fullfile(spm('Dir'),'rend')); +end; + +% get brightness +%----------------------------------------------------------------------- +if nargin < 2, + brt = 1; + if num==1, + brt = spm_input('Style',1,'new|old',[1 NaN], 1); + end; + if isfinite(brt), + brt = spm_input('Brighten blobs',1,'none|slightly|more|lots',[1 0.75 0.5 0.25], 1); + end; +end; + + + +% Perform the rendering +%======================================================================= +spm('Pointer','Watch') + +load(rendfile); + +if (exist('rend') ~= 1), % Assume old format... + rend = cell(size(Matrixes,1),1); + for i=1:size(Matrixes,1), + rend{i}=struct('M',eval(Matrixes(i,:)),... + 'ren',eval(Rens(i,:)),... + 'dep',eval(Depths(i,:))); + rend{i}.ren = rend{i}.ren/max(max(rend{i}.ren)); + end; +end; + +if showbar, spm_progress_bar('Init', size(dat,1)*length(rend),... + 'Formatting Renderings', 'Number completed'); end; +for i=1:length(rend), + rend{i}.max=0; + rend{i}.data = cell(size(dat,1),1); + if issparse(rend{i}.ren), + % Assume that images have been DCT compressed + % - the SPM99 distribution was originally too big. + d = size(rend{i}.ren); + B1 = spm_dctmtx(d(1),d(1)); + B2 = spm_dctmtx(d(2),d(2)); + rend{i}.ren = B1*rend{i}.ren*B2'; + % the depths did not compress so well with + % a straight DCT - therefore it was modified slightly + rend{i}.dep = exp(B1*rend{i}.dep*B2')-1; + end; + msk = find(rend{i}.ren>1);rend{i}.ren(msk)=1; + msk = find(rend{i}.ren<0);rend{i}.ren(msk)=0; + if showbar, spm_progress_bar('Set', i); end; +end; +if showbar, spm_progress_bar('Clear'); end; + +if showbar, spm_progress_bar('Init', length(dat)*length(rend),... + 'Making pictures', 'Number completed'); end; + +mx = zeros(length(rend),1)+eps; +mn = zeros(length(rend),1); + +for j=1:length(dat), + XYZ = dat(j).XYZ; + t = dat(j).t; + dim = dat(j).dim; + mat = dat(j).mat; + + for i=1:length(rend), + + % transform from Taliarach space to space of the rendered image + %------------------------------------------------------- + M1 = rend{i}.M*dat(j).mat; + zm = sum(M1(1:2,1:3).^2,2).^(-1/2); + M2 = diag([zm' 1 1]); + M = M2*M1; + cor = [1 1 1 ; dim(1) 1 1 ; 1 dim(2) 1; dim(1) dim(2) 1 ; + 1 1 dim(3) ; dim(1) 1 dim(3) ; 1 dim(2) dim(3); dim(1) dim(2) dim(3)]'; + tcor= M(1:3,1:3)*cor + M(1:3,4)*ones(1,8); + off = min(tcor(1:2,:)'); + M2 = spm_matrix(-off+1)*M2; + M = M2*M1; + xyz = (M(1:3,1:3)*XYZ + M(1:3,4)*ones(1,size(XYZ,2))); + d2 = ceil(max(xyz(1:2,:)')); + + % calculate 'depth' of values + %------------------------------------------------------- + dep = spm_slice_vol(rend{i}.dep,spm_matrix([0 0 1])*inv(M2),d2,1); + z1 = dep(round(xyz(1,:))+round(xyz(2,:)-1)*size(dep,1)); + + if ~isfinite(brt), msk = find(xyz(3,:) < (z1+20) & xyz(3,:) > (z1-5)); + else, msk = find(xyz(3,:) < (z1+60) & xyz(3,:) > (z1-5)); end; + + if ~isempty(msk), + + % generate an image of the integral of the blob values. + %----------------------------------------------- + xyz = xyz(:,msk); + if ~isfinite(brt), t0 = t(msk); + else, dst = xyz(3,:) - z1(msk); + dst = max(dst,0); + t0 = t(msk).*exp((log(0.5)/10)*dst)'; + end; + X0 = full(sparse(round(xyz(1,:)), round(xyz(2,:)), t0, d2(1), d2(2))); + hld = 1; if ~isfinite(brt), hld = 0; end; + X = spm_slice_vol(X0,spm_matrix([0 0 1])*M2,size(rend{i}.dep),hld); + msk = find(X<0); + X(msk) = 0; + else, + X = zeros(size(rend{i}.dep)); + end; + + % Brighten the blobs + if isfinite(brt), X = X.^brt; end; + + mx(j) = max([mx(j) max(max(X))]); + mn(j) = min([mn(j) min(min(X))]); + + rend{i}.data{j} = X; + + if showbar, spm_progress_bar('Set', i+(j-1)*length(rend)); end; + end; +end; + +mxmx = max(mx); +mnmn = min(mn); + +if showbar, spm_progress_bar('Clear'); end; +Fgraph = gcf;%spm_figure('GetWin','Graphics'); +%spm_results_ui('Clear',Fgraph); + +nrow = ceil(length(rend)/2); +hght = 0.25; +width = 0.25; +x0 = 0.5; +y0 = 0.01; +% subplot('Position',[0, 0, 1, hght]); +ax=axes('Parent',Fgraph,'units','normalized','Position',[0, 0, 0.5, hght],'Visible','off'); +%ax=axes; +%image(0,'Parent',ax); +set(ax,'YTick',[],'XTick',[]); + +if ~isfinite(brt), + % Old style split colourmap display. + %--------------------------------------------------------------- + load Split; + colormap(split); + for i=1:length(rend), + ren = rend{i}.ren; + X = (rend{i}.data{1}-mnmn)/(mxmx-mnmn); + msk = find(X); + ren(msk) = X(msk)+(1+1.51/64); + ax=axes('Parent',Fgraph,'units','normalized',... + 'Position',[x0+rem(i-1,2)*width, y0+floor((i-1)/2)*hght/nrow*2, width, hght/nrow*2],... + 'Visible','off'); + image(ren*64,'Parent',ax); + set(ax,'DataAspectRatio',[1 1 1], ... + 'PlotBoxAspectRatioMode','auto',... + 'YTick',[],'XTick',[],'XDir','normal','YDir','normal'); + end; +else, + % Combine the brain surface renderings with the blobs, and display using + % 24 bit colour. + %--------------------------------------------------------------- + for i=1:length(rend), + ren = rend{i}.ren; + X = cell(3,1); + for j=1:length(rend{i}.data), + X{j} = rend{i}.data{j}/(mxmx-mnmn)-mnmn; + end + for j=(length(rend{i}.data)+1):3 + X{j}=zeros(size(X{1})); + end + + rgb = zeros([size(ren) 3]); + tmp = ren.*max(1-X{1}-X{2}-X{3},0); + rgb(:,:,1) = tmp + X{1}; + rgb(:,:,2) = tmp + X{2}; + rgb(:,:,3) = tmp + X{3}; + + ax=axes('Parent',Fgraph,'units','normalized',... + 'Position',[x0+rem(i-1,2)*width, y0+floor((i-1)/2)*hght/nrow*2, width, hght/nrow*2],... + 'Visible','off'); + image(rgb,'Parent',ax); + set(ax,'DataAspectRatio',[1 1 1], ... + 'PlotBoxAspectRatioMode','auto',... + 'YTick',[],'XTick',[],... + 'XDir','normal','YDir','normal'); + end; +end; + +spm('Pointer') +return; + +function [P,p,Em,En,EN] = spm_P(c,k,Z,df,STAT,R,n,S) +% Returns the [un]corrected P value using unifed EC theory +% FORMAT [P p Em En EN] = spm_P(c,k,Z,df,STAT,R,n,S) +% +% c - cluster number +% k - extent {RESELS} +% Z - height {minimum over n values} +% df - [df{interest} df{error}] +% STAT - Statistical field +% 'Z' - Gaussian field +% 'T' - T - field +% 'X' - Chi squared field +% 'F' - F - field +% 'P' - Posterior probability +% R - RESEL Count {defining search volume} +% n - number of component SPMs in conjunction +% S - Voxel count +% +% P - corrected P value - P(n > kmax} +% p - uncorrected P value - P(n > k} +% Em - expected total number of maxima {m} +% En - expected total number of resels per cluster {n} +% EN - expected total number of voxels {N} +% +%__________________________________________________________________________ +% +% spm_P determines corrected and uncorrected p values, using the minimum +% of different valid methods. +% +% See the individual methods for details +% +% spm_P_RF +% spm_P_Bonf +% +%__________________________________________________________________________ +% Copyright (C) 2008 Wellcome Trust Centre for Neuroimaging + +% Thomas Nichols +% $Id: spm_P.m 2690 2009-02-04 21:44:28Z guillaume $ + + +% set global var NOBONF to 1 to turn off Bonferroni +%-------------------------------------------------------------------------- +global NOBONF; if ~isempty(NOBONF) && NOBONF, S = []; end + +if (nargin < 8), S = []; end + +[P,p,Em,En,EN] = spm_P_RF(c,k,Z,df,STAT,R,n); + +% Use lower Bonferroni P value (if possible) +%========================================================================== +if ~isempty(S) && (c == 1 && k == 0) && ~(length(R) == 1 && R == 1) + P = min(P,spm_P_Bonf(Z,df,STAT,S,n)); +end + +function [P,p,Em,En,EN] = spm_P_RF(c,k,Z,df,STAT,R,n) +% Returns the [un]corrected P value using unifed EC theory +% FORMAT [P p Em En EN] = spm_P_RF(c,k,Z,df,STAT,R,n) +% +% c - cluster number +% k - extent {RESELS} +% Z - height {minimum over n values} +% df - [df{interest} df{error}] +% STAT - Statistical field +% 'Z' - Gaussian field +% 'T' - T - field +% 'X' - Chi squared field +% 'F' - F - field +% R - RESEL Count {defining search volume} +% n - number of component SPMs in conjunction +% +% P - corrected P value - P(n > kmax} +% p - uncorrected P value - P(n > k} +% Em - expected total number of maxima {m} +% En - expected total number of resels per cluster {n} +% EN - expected total number of voxels {N} +% +%___________________________________________________________________________ +% +% spm_P_RF returns the probability of c or more clusters with more than +% k voxels in volume process of R RESELS thresholded at u. All p values +% can be considered special cases: +% +% spm_P_RF(1,0,Z,df,STAT,1,n) = uncorrected p value +% spm_P_RF(1,0,Z,df,STAT,R,n) = corrected p value {based on height Z) +% spm_P_RF(1,k,u,df,STAT,R,n) = corrected p value {based on extent k at u) +% spm_P_RF(c,k,u,df,STAT,R,n) = corrected p value {based on number c at k and u) +% spm_P_RF(c,0,u,df,STAT,R,n) = omnibus p value {based on number c at u) +% +% If n > 1 a conjunction probility over the n values of the statistic +% is returned +% +% Ref: Hasofer AM (1978) Upcrossings of random fields +% Suppl Adv Appl Prob 10:14-21 +% Ref: Friston et al (1993) Comparing functional images: Assessing +% the spatial extent of activation foci +% Ref: Worsley KJ et al 1996, Hum Brain Mapp. 4:58-73 +%___________________________________________________________________________ +% Copyright (C) 2008 Wellcome Trust Centre for Neuroimaging + +% Karl Friston +% $Id: spm_P_RF.m 2691 2009-02-04 21:46:04Z guillaume $ + + + +% get expectations +%=========================================================================== + +% get EC densities +%--------------------------------------------------------------------------- +D = find(R, 1, 'last' ); +R = R(1:D); +G = sqrt(pi)./gamma(([1:D])/2); +EC = spm_ECdensity(STAT,Z,df); +EC = EC([1:D]) + eps; + +% corrected p value +%--------------------------------------------------------------------------- +P = triu(toeplitz(EC'.*G))^n; +P = P(1,:); +EM = (R./G).*P; % over D dimensions +Em = sum(EM); % +EN = P(1)*R(D); % +En = EN/EM(D); % En = EN/EM(D); + +% get P{n > k} +%=========================================================================== + +% assume a Gaussian form for P{n > k} ~ exp(-beta*k^(2/D)) +% Appropriate for SPM{Z} and high d.f. SPM{T} +%--------------------------------------------------------------------------- +D = D - 1; +if ~k || ~D + + p = 1; + +elseif STAT == 'Z' + + beta = (gamma(D/2 + 1)/En)^(2/D); + p = exp(-beta*(k^(2/D))); + +elseif STAT == 'T' + + beta = (gamma(D/2 + 1)/En)^(2/D); + p = exp(-beta*(k^(2/D))); + +elseif STAT == 'X' + + beta = (gamma(D/2 + 1)/En)^(2/D); + p = exp(-beta*(k^(2/D))); + +elseif STAT == 'F' + + beta = (gamma(D/2 + 1)/En)^(2/D); + p = exp(-beta*(k^(2/D))); + +end + +% Poisson clumping heuristic {for multiple clusters} +%=========================================================================== +P = 1 - spm_Pcdf(c - 1,(Em + eps)*p); + + +% set P and p = [] for non-implemented cases +%+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +if k > 0 && n > 1 + P = []; p = []; +end +if k > 0 && (STAT == 'X' || STAT == 'F') + P = []; p = []; +end +%+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + + + +% spm_ECdensity +%=========================================================================== + +function [EC] = spm_ECdensity(STAT,t,df) +% Returns the EC density +%___________________________________________________________________________ +% +% Reference : Worsley KJ et al 1996, Hum Brain Mapp. 4:58-73 +% +%--------------------------------------------------------------------------- + +% EC densities (EC} +%--------------------------------------------------------------------------- +t = t(:)'; +if STAT == 'Z' + + % Gaussian Field + %------------------------------------------------------------------- + a = 4*log(2); + b = exp(-t.^2/2); + + EC(1,:) = 1 - spm_Ncdf(t); + EC(2,:) = a^(1/2)/(2*pi)*b; + EC(3,:) = a/((2*pi)^(3/2))*b.*t; + EC(4,:) = a^(3/2)/((2*pi)^2)*b.*(t.^2 - 1); + +elseif STAT == 'T' + + % T - Field + %------------------------------------------------------------------- + v = df(2); + a = 4*log(2); + b = exp(gammaln((v+1)/2) - gammaln(v/2)); + c = (1+t.^2/v).^((1-v)/2); + + EC(1,:) = 1 - spm_Tcdf(t,v); + EC(2,:) = a^(1/2)/(2*pi)*c; + EC(3,:) = a/((2*pi)^(3/2))*c.*t/((v/2)^(1/2))*b; + EC(4,:) = a^(3/2)/((2*pi)^2)*c.*((v-1)*(t.^2)/v - 1); + +elseif STAT == 'X' + + % X - Field + %------------------------------------------------------------------- + v = df(2); + a = (4*log(2))/(2*pi); + b = t.^(1/2*(v - 1)).*exp(-t/2-gammaln(v/2))/2^((v-2)/2); + + EC(1,:) = 1 - spm_Xcdf(t,v); + EC(2,:) = a^(1/2)*b; + EC(3,:) = a*b.*(t-(v-1)); + EC(4,:) = a^(3/2)*b.*(t.^2-(2*v-1)*t+(v-1)*(v-2)); + +elseif STAT == 'F' + + % F Field + %------------------------------------------------------------------- + k = df(1); + v = df(2); + a = (4*log(2))/(2*pi); + b = gammaln(v/2) + gammaln(k/2); + + EC(1,:) = 1 - spm_Fcdf(t,df); + EC(2,:) = a^(1/2)*exp(gammaln((v+k-1)/2)-b)*2^(1/2)... + *(k*t/v).^(1/2*(k-1)).*(1+k*t/v).^(-1/2*(v+k-2)); + EC(3,:) = a*exp(gammaln((v+k-2)/2)-b)*(k*t/v).^(1/2*(k-2))... + .*(1+k*t/v).^(-1/2*(v+k-2)).*((v-1)*k*t/v-(k-1)); + EC(4,:) = a^(3/2)*exp(gammaln((v+k-3)/2)-b)... + *2^(-1/2)*(k*t/v).^(1/2*(k-3)).*(1+k*t/v).^(-1/2*(v+k-2))... + .*((v-1)*(v-2)*(k*t/v).^2-(2*v*k-v-k-1)*(k*t/v)+(k-1)*(k-2)); +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% spm_list (copied from spm 5) +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function varargout = spm_list(varargin) +% Display and analysis of SPM{.} +% FORMAT TabDat = spm_list('List',SPM,hReg,[Num,Dis,Str]) +% Summary list of local maxima for entire volume of interest +% FORMAT TabDat = spm_list('ListCluster',SPM,hReg,[Num,Dis,Str]) +% List of local maxima for a single suprathreshold cluster +% +% SPM - structure containing SPM, distribution & filtering details +% - required fields are: +% .swd - SPM working directory - directory containing current SPM.mat +% .Z - minimum of n Statistics {filtered on u and k} +% .n - number of conjoint tests +% .STAT - distribution {Z, T, X or F} +% .df - degrees of freedom [df{interest}, df{residual}] +% .u - height threshold +% .k - extent threshold {voxels} +% .XYZ - location of voxels {voxel coords} +% .XYZmm - location of voxels {mm} +% .S - search Volume {voxels} +% .R - search Volume {resels} +% .FWHM - smoothness {voxels} +% .M - voxels - > mm matrix +% .VOX - voxel dimensions {mm} +% .Vspm - mapped statistic image(s) +% .Ps - uncorrected P values in searched volume (for FDR) +% +% (see spm_getSPM for further details of xSPM structures) +% +% hReg - Handle of results section XYZ registry (see spm_results_ui.m) +% +% Num - number of maxima per cluster +% Dis - distance among clusters (mm) +% Str - header string +% +% TabDat - Structure containing table data +% - fields are +% .tit - table Title (string) +% .hdr - table header (2x11 cell array) +% .fmt - fprintf format strings for table data (1x11 cell array) +% .str - table filtering note (string) +% .ftr - table footnote information (4x2 cell array) +% .dat - table data (Nx11 cell array) +% +% ---------------- +% +% FORMAT spm_list('TxtList',TabDat,c) +% Prints a tab-delimited text version of the table +% TabDat - Structure containing table data (format as above) +% c - Column of table data to start text table at +% (E.g. c=3 doesn't print set-level results contained in columns 1 & 2) +% ---------------- +% +% FORMAT spm_list('SetCoords',xyz,hAx,hC) +% Highlighting of table co-ordinates (used by results section registry) +% xyz - 3-vector of new co-ordinate +% hAx - table axis (the registry object for tables) +% hReg - Handle of caller (not used) +%_______________________________________________________________________ +% +% spm_list characterizes SPMs (thresholded at u and k) in terms of +% excursion sets (a collection of face, edge and vertex connected +% subsets or clusters). The currected significance of the results are +% based on set, cluster and voxel-level inferences using distributional +% approximations from the Theory of Gaussian Fields. These +% distributions assume that the SPM is a reasonable lattice +% approximation of a continuous random field with known component field +% smoothness. +% +% The p values are based on the probability of obtaining c, or more, +% clusters of k, or more, resels above u, in the volume S analysed = +% P(u,k,c). For specified thresholds u, k, the set-level inference is +% based on the observed number of clusters C, = P(u,k,C). For each +% cluster of size K the cluster-level inference is based on P(u,K,1) +% and for each voxel (or selected maxima) of height U, in that cluster, +% the voxel-level inference is based on P(U,0,1). All three levels of +% inference are supported with a tabular presentation of the p values +% and the underlying statistic: +% +% Set-level - c = number of suprathreshold clusters +% - P = prob(c or more clusters in the search volume) +% +% Cluster-level - k = number of voxels in this cluster +% - Pc = prob(k or more voxels in the search volume) +% - Pu = prob(k or more voxels in a cluster) +% +% Voxel-level - T/F = Statistic upon which the SPM is based +% - Ze = The eqivalent Z score - prob(Z > Ze) = prob(t > T) +% - Pc = prob(Ze or higher in the search volume) +% - Qu = Expd(Prop of false positives among voxels >= Ze) +% - Pu = prob(Ze or higher at that voxel) +% +% x,y,z (mm) - Coordinates of the voxel +% +% The table is grouped by regions and sorted on the Ze-variate of the +% primary maxima. Ze-variates (based on the uncorrected p value) are the +% Z score equivalent of the statistic. Volumes are expressed in voxels. +% +% Clicking on values in the table returns the value to the Matlab +% workspace. In addition, clicking on the co-ordinates jumps the +% results section cursor to that location. The table has a context menu +% (obtained by right-clicking in the background of the table), +% providing options to print the current table as a text table, or to +% extract the table data to the Matlab workspace. +% +%_______________________________________________________________________ +% @(#)spm_list.m 2.43 Karl Friston, Andrew Holmes 02/10/31 + + +% satellite figure global variable +%----------------------------------------------------------------------- +global SatWindow + +%======================================================================= +switch lower(varargin{1}), case 'list' %-List +%======================================================================= +% FORMAT TabDat = spm_list('list',SPM,hReg) + +%-Tolerance for p-value underflow, when computing equivalent Z's +%----------------------------------------------------------------------- +tol = eps*10; + +%-Parse arguments and set maxima number and separation +%----------------------------------------------------------------------- +if nargin < 2, error('insufficient arguments'), end +if nargin < 3, hReg = []; else, hReg = varargin{3}; end + + +%-Get current location (to highlight selected voxel in table) +%----------------------------------------------------------------------- +%xyzmm = spm_results_ui('GetCoords'); +xyzmm = [0 0 0]'; + +%-Extract data from xSPM +%----------------------------------------------------------------------- +S = varargin{2}.S; +R = varargin{2}.R; +FWHM = varargin{2}.FWHM; +VOX = varargin{2}.VOX; +n = varargin{2}.n; +STAT = varargin{2}.STAT; +df = varargin{2}.df; +u = varargin{2}.u; +M = varargin{2}.M; +v2r = 1/prod(FWHM(~isinf(FWHM))); %-voxels to resels +k = varargin{2}.k*v2r; +QPs = varargin{2}.Ps; % Needed for FDR +QPs = sort(QPs(:)); + +%-get number and separation for maxima to be reported +%----------------------------------------------------------------------- +if length(varargin) > 3 + + Num = varargin{4}; % number of maxima per cluster + Dis = varargin{5}; % distance among clusters (mm) +else + Num = 3; + Dis = 8; +end +if length(varargin) > 5 + + Title = varargin{6}; +else + Title = 'p-values adjusted for search volume'; +end + + +%-Setup graphics panel +%----------------------------------------------------------------------- +spm('Pointer','Watch') +if SatWindow + Fgraph = SatWindow; + figure(Fgraph); +else + Fgraph = figure('unit','normalized','position',[0.4,0.1,0.55,0.5],'Color','w',... + 'Name','volume', 'NumberTitle','off','resize','off','MenuBar','none'); + Fgraph = gcf; +end +%spm_results_ui('Clear',Fgraph) +FS = spm('FontSizes'); %-Scaled font sizes +PF = spm_platform('fonts'); %-Font names (for this platform) + + +%-Table header & footer +%======================================================================= + +%-Table axes & Title +%---------------------------------------------------------------------- +if SatWindow, ht = 0.85; bot = .14; else, ht = 0.8; bot = 0.15; end; + +if STAT == 'P' + Title = 'Posterior Probabilities'; +end + +hAx = axes('Position',[0.025 bot 0.9 ht],... + 'DefaultTextFontSize',FS(8),... + 'DefaultTextInterpreter','Tex',... + 'DefaultTextVerticalAlignment','Baseline',... + 'Units','points',... + 'Visible','off'); + +AxPos = get(hAx,'Position'); set(hAx,'YLim',[0,AxPos(4)]) +dy = FS(9); +y = floor(AxPos(4)) - dy; + +text(0,y,['Statistics: \it\fontsize{',num2str(FS(9)),'}',Title],... + 'FontSize',FS(11),'FontWeight','Bold'); y = y - dy/2; +line([0 1],[y y],'LineWidth',3,'Color','r'), y = y - 9*dy/8; + + +%-Construct table header +%----------------------------------------------------------------------- +set(gca,'DefaultTextFontName',PF.helvetica,'DefaultTextFontSize',FS(8)) + +Hc = []; +Hp = []; +h = text(0.01,y, 'set-level','FontSize',FS(9)); Hc = [Hc,h]; +h = line([0,0.11],[1,1]*(y-dy/4),'LineWidth',0.5,'Color','r'); Hc = [Hc,h]; +h = text(0.08,y-9*dy/8, '\itc '); Hc = [Hc,h]; +h = text(0.02,y-9*dy/8, '\itp '); Hc = [Hc,h]; + Hp = [Hp,h]; +text(0.22,y, 'cluster-level','FontSize',FS(9)); +line([0.15,0.41],[1,1]*(y-dy/4),'LineWidth',0.5,'Color','r'); +h = text(0.16,y-9*dy/8, '\itp \rm_{corrected}'); Hp = [Hp,h]; +h = text(0.33,y-9*dy/8, '\itp \rm_{uncorrected}'); Hp = [Hp,h]; +h = text(0.26,y-9*dy/8, '\itk \rm_E'); + +text(0.60,y, 'voxel-level','FontSize',FS(9)); +line([0.46,0.86],[1,1]*(y-dy/4),'LineWidth',0.5,'Color','r'); +h = text(0.46,y-9*dy/8, '\itp \rm_{FWE-corr}'); Hp = [Hp,h]; +h = text(0.55,y-9*dy/8, '\itp \rm_{FDR-corr}'); Hp = [Hp,h]; +h = text(0.79,y-9*dy/8, '\itp \rm_{uncorrected}'); Hp = [Hp,h]; +h = text(0.64,y-9*dy/8, sprintf('\\it%c',STAT)); +h = text(0.72,y-9*dy/8, '(\itZ\rm_\equiv)'); + +text(0.93,y - dy/2,['x,y,z \fontsize{',num2str(FS(8)),'}\{mm\}']); + + +%-Headers for text table... +%----------------------------------------------------------------------- +TabDat.tit = Title; +TabDat.hdr = { 'set', 'c';... + 'set', 'p';... + 'cluster', 'p(cor)';... + 'cluster', 'equivk';... + 'cluster', 'p(unc)';... + 'voxel', 'p(FWE-cor)';... + 'voxel', 'p(FDR-cor)';... + 'voxel', STAT;... + 'voxel', 'equivZ';... + 'voxel', 'p(unc)';... + '', 'x,y,z {mm}'}';... + +TabDat.fmt = { '%-0.3f','%g',... %-Set + '%0.3f', '%0.0f', '%0.3f',... %-Cluster + '%0.3f', '%0.3f', '%6.2f', '%5.2f', '%0.3f',... %-Voxel + '%3.0f %3.0f %3.0f'}; %-XYZ + +%-Column Locations +%----------------------------------------------------------------------- +tCol = [ 0.00 0.07 ... %-Set + 0.16 0.26 0.34 ... %-Cluster + 0.46 0.55 0.62 0.71 0.80 ...%-Voxel + 0.92]; %-XYZ + +% move to next vertial postion marker +%----------------------------------------------------------------------- +y = y - 7*dy/4; +line([0 1],[y y],'LineWidth',1,'Color','r') +y = y - 5*dy/4; +y0 = y; + + +%-Table filtering note +%----------------------------------------------------------------------- +if isinf(Num) + TabDat.str = sprintf('table shows all local maxima > %.1fmm apart',Dis); +else + TabDat.str = sprintf(['table shows %d local maxima ',... + 'more than %.1fmm apart'],Num,Dis); +end +text(0.5,4,TabDat.str,'HorizontalAlignment','Center','FontName',PF.helvetica,... + 'FontSize',FS(8),'FontAngle','Italic') + + +%-Volume, resels and smoothness (if classical inference) +%----------------------------------------------------------------------- +line([0 1],[0 0],'LineWidth',1,'Color','r') +if STAT ~= 'P' +%----------------------------------------------------------------------- +FWHMmm = FWHM.*VOX; % FWHM {mm} +Pz = spm_P(1,0,u,df,STAT,1,n,S); +Pu = spm_P(1,0,u,df,STAT,R,n,S); +Qu = spm_P_FDR(u,df,STAT,n,QPs); +[P Pn Em En EN] = spm_P(1,k,u,df,STAT,R,n,S); + + +%-Footnote with SPM parameters +%----------------------------------------------------------------------- +set(gca,'DefaultTextFontName',PF.helvetica,... + 'DefaultTextInterpreter','None','DefaultTextFontSize',FS(8)) +TabDat.ftr = cell(5,2); +TabDat.ftr{1} = ... + sprintf('Height threshold: %c = %0.2f, p = %0.3f (%0.3f)',... + STAT,u,Pz,Pu); +TabDat.ftr{2} = ... + sprintf('Extent threshold: k = %0.0f voxels, p = %0.3f (%0.3f)',... + k/v2r,Pn,P); +TabDat.ftr{3} = ... + sprintf('Expected voxels per cluster, = %0.3f',En/v2r); +TabDat.ftr{4} = ... + sprintf('Expected number of clusters, = %0.2f',Em*Pn); +TabDat.ftr{5} = ... + sprintf('Expected false discovery rate, <= %0.2f',Qu); +TabDat.ftr{6} = ... + sprintf('Degrees of freedom = [%0.1f, %0.1f]',df); +TabDat.ftr{7} = ... + sprintf(['Smoothness FWHM = %0.1f %0.1f %0.1f {mm} ',... + ' = %0.1f %0.1f %0.1f {voxels}'],FWHMmm,FWHM); +TabDat.ftr{8} = ... + sprintf('Search vol: %0.0f cmm; %0.0f voxels; %0.1f resels',S*prod(VOX),S,R(end)); +TabDat.ftr{9} = ... + sprintf(['Voxel size: [%0.1f, %0.1f, %0.1f] mm ',... + ' (1 resel = %0.2f voxels)'],VOX,prod(FWHM)); + +text(0.0,-1*dy,TabDat.ftr{1},... + 'UserData',[u,Pz,Pu,Qu],'ButtonDownFcn','get(gcbo,''UserData'')') +text(0.0,-2*dy,TabDat.ftr{2},... + 'UserData',[k/v2r,Pn,P],'ButtonDownFcn','get(gcbo,''UserData'')') +text(0.0,-3*dy,TabDat.ftr{3},... + 'UserData',En/v2r,'ButtonDownFcn','get(gcbo,''UserData'')') +text(0.0,-4*dy,TabDat.ftr{4},... + 'UserData',Em*Pn,'ButtonDownFcn','get(gcbo,''UserData'')') +text(0.0,-5*dy,TabDat.ftr{5},... + 'UserData',Qu,'ButtonDownFcn','get(gcbo,''UserData'')') +text(0.5,-1*dy,TabDat.ftr{6},... + 'UserData',df,'ButtonDownFcn','get(gcbo,''UserData'')') +text(0.5,-2*dy,TabDat.ftr{7},... + 'UserData',FWHMmm,'ButtonDownFcn','get(gcbo,''UserData'')') +text(0.5,-3*dy,TabDat.ftr{8},... + 'UserData',[S*prod(VOX),S,R(end)],... + 'ButtonDownFcn','get(gcbo,''UserData'')') +text(0.5,-4*dy,TabDat.ftr{9},... + 'UserData',[VOX,prod(FWHM)],... + 'ButtonDownFcn','get(gcbo,''UserData'')') + +end % Classical + + +%-Characterize excursion set in terms of maxima +% (sorted on Z values and grouped by regions) +%======================================================================= +if ~length(varargin{2}.Z) + text(0.5,y-6*dy,'no suprathreshold clusters',... + 'HorizontalAlignment','Center',... + 'FontAngle','Italic','FontWeight','Bold',... + 'FontSize',FS(16),'Color',[1,1,1]*.5); + TabDat.dat = cell(0,11); + varargout = {TabDat}; + spm('Pointer','Arrow') + return +end + +% Includes Darren Gitelman's code for working around +% spm_max for conjunctions with negative thresholds +%----------------------------------------------------------------------- +minz = abs(min(min(varargin{2}.Z))); +zscores = 1 + minz + varargin{2}.Z; +[N Z XYZ A] = spm_max(zscores,varargin{2}.XYZ); +Z = Z - minz - 1; + +%-Convert cluster sizes from voxels to resels +%----------------------------------------------------------------------- +if isfield(varargin{2},'VRvp') + V2R = spm_get_data(varargin{2}.VRvp,XYZ); +else + V2R = v2r; +end +N = N.*V2R; + +%-Convert maxima locations from voxels to mm +%----------------------------------------------------------------------- +XYZmm = M(1:3,:)*[XYZ; ones(1,size(XYZ,2))]; + + + +%-Table proper (& note all data in cell array) +%======================================================================= + +%-Pagination variables +%----------------------------------------------------------------------- +hPage = []; +set(gca,'DefaultTextFontName',PF.courier,'DefaultTextFontSize',FS(7)) + + +%-Set-level p values {c} - do not display if reporting a single cluster +%----------------------------------------------------------------------- +c = max(A); %-Number of clusters +if STAT ~= 'P' + Pc = spm_P(c,k,u,df,STAT,R,n,S); %-Set-level p-value +else + Pc = []; + set(Hp,'Visible','off') +end + +if c > 1; + h = text(tCol(1),y,sprintf(TabDat.fmt{1},Pc),'FontWeight','Bold',... + 'UserData',Pc,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + h = text(tCol(2),y,sprintf(TabDat.fmt{2},c),'FontWeight','Bold',... + 'UserData',c,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; +else + set(Hc,'Visible','off') +end + +TabDat.dat = {Pc,c}; %-Table data +TabLin = 1; %-Table data line + + +%-Local maxima p-values & statistics +%----------------------------------------------------------------------- +HlistXYZ = []; +while prod(size(find(isfinite(Z)))) + + % Paginate if necessary + %--------------------------------------------------------------- + if y < min(Num + 1,3)*dy + + % added Fgraph term to paginate on Satellite window + %------------------------------------------------------- + h = text(0.5,-5*dy,... + sprintf('Page %d',spm_figure('#page',Fgraph)),... + 'FontName',PF.helvetica,'FontAngle','Italic',... + 'FontSize',FS(8)); + + spm_figure('NewPage',[hPage,h]) + hPage = []; + y = y0; + end + + %-Find largest remaining local maximum + %--------------------------------------------------------------- + [U,i] = max(Z); % largest maxima + j = find(A == A(i)); % maxima in cluster + + + %-Compute cluster {k} and voxel-level {u} p values for this cluster + %--------------------------------------------------------------- + Nv = N(i)/v2r; % extent {voxels} + + + if STAT ~= 'P' + Pz = spm_P(1,0, U,df,STAT,1,n,S);% uncorrected p value + Pu = spm_P(1,0, U,df,STAT,R,n,S);% FWE-corrected {based on Z) + Qu = spm_P_FDR( U,df,STAT,n,QPs);% FDR-corrected {based on Z) + [Pk Pn] = spm_P(1,N(i),u,df,STAT,R,n,S);% [un]corrected {based on k) + + if Pz < tol % Equivalent Z-variate + Ze = Inf; % (underflow => can't compute) + else + Ze = spm_invNcdf(1 - Pz); + end + else + Pz = []; + Pu = []; + Qu = []; + Pk = []; + Pn = []; + Ze = spm_invNcdf(U); + end + + + %-Print cluster and maximum voxel-level p values {Z} + %--------------------------------------------------------------- + h = text(tCol(3),y,sprintf(TabDat.fmt{3},Pk),'FontWeight','Bold',... + 'UserData',Pk,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + + h = text(tCol(4),y,sprintf(TabDat.fmt{4},Nv),'FontWeight','Bold',... + 'UserData',Nv,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + h = text(tCol(5),y,sprintf(TabDat.fmt{5},Pn),'FontWeight','Bold',... + 'UserData',Pn,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + + h = text(tCol(6),y,sprintf(TabDat.fmt{6},Pu),'FontWeight','Bold',... + 'UserData',Pu,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + h = text(tCol(7),y,sprintf(TabDat.fmt{7},Qu),'FontWeight','Bold',... + 'UserData',Qu,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + h = text(tCol(8),y,sprintf(TabDat.fmt{8},U),'FontWeight','Bold',... + 'UserData',U,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + h = text(tCol(9),y,sprintf(TabDat.fmt{9},Ze),'FontWeight','Bold',... + 'UserData',Ze,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + h = ... + text(tCol(10),y,sprintf(TabDat.fmt{10},Pz),'FontWeight','Bold',... + 'UserData',Pz,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + + % Specifically changed so it properly finds hMIPax + %--------------------------------------------------------------------- + h = text(tCol(11),y,sprintf(TabDat.fmt{11},XYZmm(:,i)),... + 'FontWeight','Bold',... + 'Tag','ListXYZ',... + 'ButtonDownFcn',[... + 'hMIPax = findobj(''tag'',''hMIPax'');',... + 'spm_mip_ui(''SetCoords'',get(gcbo,''UserData''),hMIPax);'],... + 'Interruptible','off','BusyAction','Cancel',... + 'UserData',XYZmm(:,i)); + + HlistXYZ = [HlistXYZ, h]; + if spm_XYZreg('Edist',xyzmm,XYZmm(:,i)) Dis mm apart) + %--------------------------------------------------------------- + [l q] = sort(-Z(j)); % sort on Z value + D = i; + for i = 1:length(q) + d = j(q(i)); + if min(sqrt(sum((XYZmm(:,D)-XYZmm(:,d)*ones(1,size(D,2))).^2)))>Dis; + + if length(D) < Num + + % Paginate if necessary + %----------------------------------------------- + if y < dy + h = text(0.5,-5*dy,sprintf('Page %d',... + spm_figure('#page',Fgraph)),... + 'FontName',PF.helvetica,... + 'FontAngle','Italic',... + 'FontSize',FS(8)); + + spm_figure('NewPage',[hPage,h]) + hPage = []; + y = y0; + end + + % voxel-level p values {Z} + %----------------------------------------------- + if STAT ~= 'P' + Pz = spm_P(1,0,Z(d),df,STAT,1,n,S); + Pu = spm_P(1,0,Z(d),df,STAT,R,n,S); + Qu = spm_P_FDR(Z(d),df,STAT,n,QPs); + if Pz < tol + Ze = Inf; + else, Ze = spm_invNcdf(1 - Pz); end + else + Pz = []; + Pu = []; + Qu = []; + Ze = spm_invNcdf(Z(d)); + end + + h = text(tCol(6),y,sprintf(TabDat.fmt{6},Pu),... + 'UserData',Pu,... + 'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + + h = text(tCol(7),y,sprintf(TabDat.fmt{7},Qu),... + 'UserData',Qu,... + 'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + h = text(tCol(8),y,sprintf(TabDat.fmt{8},Z(d)),... + 'UserData',Z(d),... + 'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + h = text(tCol(9),y,sprintf(TabDat.fmt{9},Ze),... + 'UserData',Ze,... + 'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + h = text(tCol(10),y,sprintf(TabDat.fmt{10},Pz),... + 'UserData',Pz,... + 'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + + % specifically modified line to use hMIPax + %----------------------------------------------- + h = text(tCol(11),y,... + sprintf(TabDat.fmt{11},XYZmm(:,d)),... + 'Tag','ListXYZ',... + 'ButtonDownFcn',[... + 'hMIPax = findobj(''tag'',''hMIPax'');',... + 'spm_mip_ui(''SetCoords'',',... + 'get(gcbo,''UserData''),hMIPax);'],... + 'Interruptible','off','BusyAction','Cancel',... + 'UserData',XYZmm(:,d)); + + HlistXYZ = [HlistXYZ, h]; + if spm_XYZreg('Edist',xyzmm,XYZmm(:,d))1 + h = text(0.5,-5*dy,sprintf('Page %d/%d',spm_figure('#page',Fgraph)*[1,1]),... + 'FontName',PF.helvetica,'FontSize',FS(8),'FontAngle','Italic'); + spm_figure('NewPage',[hPage,h]) +end + +%-End: Store TabDat in UserData of axes & reset pointer +%======================================================================= +h = uicontextmenu('Tag','TabDat',... + 'UserData',TabDat); +set(gca,'UIContextMenu',h,... + 'Visible','on',... + 'XColor','w','YColor','w') +uimenu(h,'Label','Table') +uimenu(h,'Separator','on','Label','Print text table',... + 'Tag','TD_TxtTab',... + 'CallBack',... + 'spm_list(''txtlist'',get(get(gcbo,''Parent''),''UserData''),3)',... + 'Interruptible','off','BusyAction','Cancel'); +uimenu(h,'Separator','off','Label','Extract table data structure',... + 'Tag','TD_Xdat',... + 'CallBack','get(get(gcbo,''Parent''),''UserData'')',... + 'Interruptible','off','BusyAction','Cancel'); +uimenu(h,'Separator','on','Label','help',... + 'Tag','TD_Xdat',... + 'CallBack','spm_help(''spm_list'')',... + 'Interruptible','off','BusyAction','Cancel'); + +%-Setup registry +%----------------------------------------------------------------------- +set(hAx,'UserData',struct('hReg',hReg,'HlistXYZ',HlistXYZ)) +spm_XYZreg('Add2Reg',hReg,hAx,'spm_list'); + +%-Return TabDat structure & reset pointer +%----------------------------------------------------------------------- +varargout = {TabDat}; +spm('Pointer','Arrow') + + + + + +%======================================================================= +case 'listcluster' %-List for current cluster only +%======================================================================= +% FORMAT TabDat = spm_list('listcluster',SPM,hReg) + +spm('Pointer','Watch') + +%-Parse arguments +%----------------------------------------------------------------------- +if nargin < 2, error('insufficient arguments'), end +if nargin < 3, hReg = []; else, hReg = varargin{3}; end +SPM = varargin{2}; + +%-get number and separation for maxima to be reported +%----------------------------------------------------------------------- +if length(varargin) > 3 + + Num = varargin{4}; % number of maxima per cluster + Dis = varargin{5}; % distance among clusters (mm) +else + Num = 32; + Dis = 4; +end + + +%-if there are suprathreshold voxels, filter out all but current cluster +%----------------------------------------------------------------------- +if length(SPM.Z) + + %-Jump to voxel nearest current location + %-------------------------------------------------------------- + [xyzmm,i] = spm_XYZreg('NearestXYZ',... + spm_results_ui('GetCoords'),SPM.XYZmm); + spm_results_ui('SetCoords',SPM.XYZmm(:,i)); + + %-Find selected cluster + %-------------------------------------------------------------- + A = spm_clusters(SPM.XYZ); + j = find(A == A(i)); + SPM.Z = SPM.Z(j); + SPM.XYZ = SPM.XYZ(:,j); + SPM.XYZmm = SPM.XYZmm(:,j); + if isfield(SPM,'Rd'), SPM.Rd = SPM.Rd(:,j); end +end + +%-Call 'list' functionality to produce table +%----------------------------------------------------------------------- +varargout = {spm_list('list',SPM,hReg,Num,Dis)}; + + + + + +%======================================================================= +case 'txtlist' %-Print ASCII text table +%======================================================================= +% FORMAT spm_list('TxtList',TabDat,c) + +if nargin<2, error('Insufficient arguments'), end +if nargin<3, c=1; else, c=varargin{3}; end +TabDat = varargin{2}; + +%-Table Title +%----------------------------------------------------------------------- +fprintf('\n\nSTATISTICS: %s\n',TabDat.tit) +fprintf('%c','='*ones(1,80)), fprintf('\n') + +%-Table header +%----------------------------------------------------------------------- +fprintf('%s\t',TabDat.hdr{1,c:end-1}), fprintf('%s\n',TabDat.hdr{1,end}) +fprintf('%s\t',TabDat.hdr{2,c:end-1}), fprintf('%s\n',TabDat.hdr{2,end}) +fprintf('%c','-'*ones(1,80)), fprintf('\n') + +%-Table data +%----------------------------------------------------------------------- +for i = 1:size(TabDat.dat,1) + for j=c:size(TabDat.dat,2) + fprintf(TabDat.fmt{j},TabDat.dat{i,j}) + fprintf('\t') + end + fprintf('\n') +end +for i=1:max(1,11-size(TabDat.dat,1)), fprintf('\n'), end +fprintf('%s\n',TabDat.str) +fprintf('%c','-'*ones(1,80)), fprintf('\n') + +%-Table footer +%----------------------------------------------------------------------- +fprintf('%s\n',TabDat.ftr{:}) +fprintf('%c','='*ones(1,80)), fprintf('\n\n') + + + +%======================================================================= +case 'setcoords' %-Co-ordinate change +%======================================================================= +% FORMAT spm_list('SetCoords',xyz,hAx,hReg) +if nargin<3, error('Insufficient arguments'), end +hAx = varargin{3}; +xyz = varargin{2}; +UD = get(hAx,'UserData'); +HlistXYZ = UD.HlistXYZ(ishandle(UD.HlistXYZ)); + +%-Set all co-ord strings to black +%----------------------------------------------------------------------- +set(HlistXYZ,'Color','k') + +%-If co-ord matches a string, highlight it in red +%----------------------------------------------------------------------- +XYZ = get(HlistXYZ,'UserData'); +if iscell(XYZ), XYZ = cat(2,XYZ{:}); end +[null,i,d] = spm_XYZreg('NearestXYZ',xyz,XYZ); +if d mm matrix +% .VOX - voxel dimensions {mm} +% .Vspm - mapped statistic image(s) +% .Ps - uncorrected P values in searched volume (for voxel FDR) +% .Pp - uncorrected P values of peaks (for peak FDR) +% .Pc - uncorrected P values of cluster extents (for cluster FDR) +% .uc - 0.05 critical thresholds for FWEp, FDRp, FWEc, FDRc +% .thresDesc - description of height threshold (string) +% +% (see spm_getSPM for further details of xSPM structures) +% +% hReg - Handle of results section XYZ registry (see spm_results_ui.m) +% +% Num - number of maxima per cluster +% Dis - distance among clusters (mm) +% Str - header string +% +% TabDat - Structure containing table data +% - fields are +% .tit - table Title (string) +% .hdr - table header (2x12 cell array) +% .fmt - fprintf format strings for table data (1x12 cell array) +% .str - table filtering note (string) +% .ftr - table footnote information (5x2 cell array) +% .dat - table data (Nx12 cell array) +% +% ---------------- +% +% FORMAT spm_list('TxtList',TabDat,c) +% Prints a tab-delimited text version of the table +% TabDat - Structure containing table data (format as above) +% c - Column of table data to start text table at +% (E.g. c=3 doesn't print set-level results contained in columns 1 & 2) +% ---------------- +% +% FORMAT spm_list('SetCoords',xyz,hAx,hC) +% Highlighting of table co-ordinates (used by results section registry) +% xyz - 3-vector of new co-ordinate +% hAx - table axis (the registry object for tables) +% hReg - Handle of caller (not used) +%__________________________________________________________________________ +% +% spm_list characterizes SPMs (thresholded at u and k) in terms of +% excursion sets (a collection of face, edge and vertex connected +% subsets or clusters). The corrected significance of the results are +% based on set, cluster and voxel-level inferences using distributional +% approximations from the Theory of Gaussian Fields. These +% distributions assume that the SPM is a reasonable lattice +% approximation of a continuous random field with known component field +% smoothness. +% +% The p values are based on the probability of obtaining c, or more, +% clusters of k, or more, resels above u, in the volume S analysed = +% P(u,k,c). For specified thresholds u, k, the set-level inference is +% based on the observed number of clusters C, = P(u,k,C). For each +% cluster of size K the cluster-level inference is based on P(u,K,1) +% and for each voxel (or selected maxima) of height U, in that cluster, +% the voxel-level inference is based on P(U,0,1). All three levels of +% inference are supported with a tabular presentation of the p values +% and the underlying statistic: +% +% Set-level - c = number of suprathreshold clusters +% - P = prob(c or more clusters in the search volume) +% +% Cluster-level - k = number of voxels in this cluster +% - Pc = prob(k or more voxels in the search volume) +% - Pu = prob(k or more voxels in a cluster) +% - Qc = lowest FDR bound for which this cluster would be +% declared positive +% +% Peak-level - T/F = Statistic upon which the SPM is based +% - Ze = The equivalent Z score - prob(Z > Ze) = prob(t > T) +% - Pc = prob(Ze or higher in the search volume) +% - Qp = lowest FDR bound for which this peak would be +% declared positive +% - Pu = prob(Ze or higher at that voxel) +% +% Voxel-level - Qu = Expd(Prop of false positives among voxels >= Ze) +% +% x,y,z (mm) - Coordinates of the voxel +% +% The table is grouped by regions and sorted on the Ze-variate of the +% primary maxima. Ze-variates (based on the uncorrected p value) are the +% Z score equivalent of the statistic. Volumes are expressed in voxels. +% +% Clicking on values in the table returns the value to the Matlab +% workspace. In addition, clicking on the co-ordinates jumps the +% results section cursor to that location. The table has a context menu +% (obtained by right-clicking in the background of the table), +% providing options to print the current table as a text table, or to +% extract the table data to the Matlab workspace. +% +%__________________________________________________________________________ +% Copyright (C) 2008 Wellcome Trust Centre for Neuroimaging + +% Karl Friston & Andrew Holmes +% $Id: spm_list.m 2821 2009-03-03 19:54:19Z guillaume $ + + +% satellite figure global variable +%-------------------------------------------------------------------------- +global SatWindow + +% Choose between voxel-wise and topological FDR +%-------------------------------------------------------------------------- +defaults = spm('GetGlobal','defaults'); +try + topoFDR = defaults.stats.topoFDR; +catch + topoFDR = true; +end + +%========================================================================== +switch lower(varargin{1}), case 'list' %-List +%========================================================================== +% FORMAT TabDat = spm_list('list',SPM,hReg) + + %-Tolerance for p-value underflow, when computing equivalent Z's + %---------------------------------------------------------------------- + tol = eps*10; + + %-Parse arguments and set maxima number and separation + %---------------------------------------------------------------------- + if nargin < 2, error('insufficient arguments'), end + if nargin < 3, hReg = []; else hReg = varargin{3}; end + + + %-Get current location (to highlight selected voxel in table) + %---------------------------------------------------------------------- + %xyzmm = spm_results_ui('GetCoords'); + xyzmm = spm_XYZreg('GetCoords',hReg);% added by xu cui + + + %-Extract data from xSPM + %---------------------------------------------------------------------- + S = varargin{2}.S; + VOX = varargin{2}.VOX; + DIM = varargin{2}.DIM; + n = varargin{2}.n; + STAT = varargin{2}.STAT; + df = varargin{2}.df; + u = varargin{2}.u; + M = varargin{2}.M; + k = varargin{2}.k; + try, QPs = varargin{2}.Ps; end + try, QPp = varargin{2}.Pp; end + try, QPc = varargin{2}.Pc; end + try + thresDesc = sprintf('{%s}', varargin{2}.thresDesc); + catch + thresDesc = ''; + end + + if STAT~='P' + R = varargin{2}.R; + FWHM = varargin{2}.FWHM; + end + try + units = varargin{2}.units; + catch + units = {'mm' 'mm' 'mm'}; + end + units{1} = [units{1} ' ']; + units{2} = [units{2} ' ']; + + DIM = DIM > 1; % dimensions + VOX = VOX(DIM); % scaling + + if STAT~='P' + FWHM = FWHM(DIM); % Full width at max/2 + FWmm = FWHM.*VOX; % FWHM {units} + v2r = 1/prod(FWHM); % voxels to resels + k = k*v2r; % extent threshold in resels + R(find(~DIM) + 1) = []; % eliminate null resel counts + try, QPs = sort(QPs(:)); end % Needed for voxel FDR + try, QPp = sort(QPp(:)); end % Needed for peak FDR + try, QPc = sort(QPc(:)); end % Needed for cluster FDR + end + + %-get number and separation for maxima to be reported + %---------------------------------------------------------------------- + if length(varargin) > 3 + Num = varargin{4}; % number of maxima per cluster + Dis = varargin{5}; % distance among clusters (mm) + else + Num = 3; + Dis = 8; + end + if length(varargin) > 5 + Title = varargin{6}; + else + Title = 'p-values adjusted for search volume'; + end + + %-Setup graphics panel + %---------------------------------------------------------------------- + spm('Pointer','Watch') + if SatWindow + Fgraph = SatWindow; + figure(Fgraph); + else%edit by xu cui + Fgraph = figure('unit','normalized','position',[0.4,0.1,0.55,0.5],'Color','w',... + 'Name','volume', 'NumberTitle','off','resize','on','MenuBar','none'); + Fgraph = gcf; + %Fgraph = spm_figure('GetWin','Graphics'); + end + spm_results_ui('Clear',Fgraph) + FS = spm('FontSizes'); %-Scaled font sizes + PF = spm_platform('fonts'); %-Font names (for this platform) + + + %-Table header & footer + %====================================================================== + + %-Table axes & Title + %---------------------------------------------------------------------- + if SatWindow, ht = 0.85; bot = 0.14; else ht = 0.8; bot = 0.15; end % changed by xu cui + + if STAT == 'P' + Title = 'Posterior Probabilities'; + end + + hAx = axes('Position',[0.025 bot 0.9 ht],... + 'DefaultTextFontSize',FS(8),... + 'DefaultTextInterpreter','Tex',... + 'DefaultTextVerticalAlignment','Baseline',... + 'Units','points',... + 'Visible','off'); + + AxPos = get(hAx,'Position'); set(hAx,'YLim',[0,AxPos(4)]) + dy = FS(9); + y = floor(AxPos(4)) - dy; + + text(0,y,['Statistics: \it\fontsize{',num2str(FS(9)),'}',Title],... + 'FontSize',FS(11),'FontWeight','Bold'); y = y - dy/2; + line([0 1],[y y],'LineWidth',3,'Color','r'), y = y - 9*dy/8; + + %-Construct table header + %---------------------------------------------------------------------- + set(gca,'DefaultTextFontName',PF.helvetica,'DefaultTextFontSize',FS(8)) + + Hc = []; + Hp = []; + h = text(0.01,y, 'set-level','FontSize',FS(9)); Hc = [Hc,h]; + h = line([0,0.11],[1,1]*(y-dy/4),'LineWidth',0.5,'Color','r'); Hc = [Hc,h]; + h = text(0.08,y-9*dy/8, '\itc '); Hc = [Hc,h]; + h = text(0.02,y-9*dy/8, '\itp '); Hc = [Hc,h]; + Hp = [Hp,h]; + text(0.22,y, 'cluster-level','FontSize',FS(9)); + line([0.14,0.44],[1,1]*(y-dy/4),'LineWidth',0.5,'Color','r'); + h = text(0.15,y-9*dy/8, '\itp\rm_{FWE-corr}'); Hp = [Hp,h]; + h = text(0.24,y-9*dy/8, '\itq\rm_{FDR-corr}'); Hp = [Hp,h]; + h = text(0.39,y-9*dy/8, '\itp\rm_{uncorr}'); Hp = [Hp,h]; + h = text(0.34,y-9*dy/8, '\itk\rm_E'); + + text(0.64,y, 'peak-level','FontSize',FS(9)); + line([0.48,0.88],[1,1]*(y-dy/4),'LineWidth',0.5,'Color','r'); + h = text(0.49,y-9*dy/8, '\itp\rm_{FWE-corr}'); Hp = [Hp,h]; + h = text(0.58,y-9*dy/8, '\itq\rm_{FDR-corr}'); Hp = [Hp,h]; + h = text(0.82,y-9*dy/8, '\itp\rm_{uncorr}'); Hp = [Hp,h]; + h = text(0.67,y-9*dy/8, sprintf('\\it%c',STAT)); + h = text(0.75,y-9*dy/8, '(\itZ\rm_\equiv)'); + + text(0.92,y - dy/2,[units{:}],'Fontsize',FS(8)); + + + %-Headers for text table... + %----------------------------------------------------------------------- + TabDat.tit = Title; + TabDat.hdr = { 'set', 'c';... + 'set', 'p';... + 'cluster', 'p(FWE-cor)';... + 'cluster', 'p(FDR-cor)';... + 'cluster', 'equivk';... + 'cluster', 'p(unc)';... + 'peak', 'p(FWE-cor)';... + 'peak', 'p(FDR-cor)';... + 'peak', STAT;... + 'peak', 'equivZ';... + 'peak', 'p(unc)';... + '', 'x,y,z {mm}'}';... + + TabDat.fmt = { '%-0.3f','%g',... %-Set + '%0.3f', '%0.3f','%0.0f', '%0.3f',... %-Cluster + '%0.3f', '%0.3f', '%6.2f', '%5.2f', '%0.3f',... %-Peak + '%3.0f %3.0f %3.0f'}; %-XYZ + + %-Column Locations + %---------------------------------------------------------------------- + tCol = [ 0.01 0.08 ... %-Set + 0.15 0.24 0.33 0.39 ... %-Cluster + 0.49 0.58 0.65 0.74 0.83 ... %-Peak + 0.92]; %-XYZ + + %-Move to next vertical position marker + %---------------------------------------------------------------------- + y = y - 7*dy/4; + line([0 1],[y y],'LineWidth',1,'Color','r') + y = y - 5*dy/4; + y0 = y; + + + %-Table filtering note + %---------------------------------------------------------------------- + if isinf(Num) + TabDat.str = sprintf('table shows all local maxima > %.1fmm apart',Dis); + else + TabDat.str = sprintf(['table shows %d local maxima ',... + 'more than %.1fmm apart'],Num,Dis); + end + text(0.5,4,TabDat.str,'HorizontalAlignment','Center','FontName',PF.helvetica,... + 'FontSize',FS(8),'FontAngle','Italic') + + + %-Volume, resels and smoothness (if classical inference) + %---------------------------------------------------------------------- + line([0 1],[0 0],'LineWidth',1,'Color','r') + if STAT ~= 'P' + %------------------------------------------------------------------ + Pz = spm_P(1,0,u,df,STAT,1,n,S); + Pu = spm_P(1,0,u,df,STAT,R,n,S); + %Qu = spm_P_FDR(u,df,STAT,n,QPs); + [P Pn Em En EN] = spm_P(1,k,u,df,STAT,R,n,S); + + %-Footnote with SPM parameters + %------------------------------------------------------------------ + set(gca,'DefaultTextFontName',PF.helvetica,... + 'DefaultTextInterpreter','None','DefaultTextFontSize',FS(8)) + TabDat.ftr = cell(5,2); + TabDat.ftr{1} = ... + sprintf('Height threshold: %c = %0.2f, p = %0.3f (%0.3f)',... + STAT,u,Pz,Pu); + TabDat.ftr{2} = ... + sprintf('Extent threshold: k = %0.0f voxels, p = %0.3f (%0.3f)',... + k/v2r,Pn,P); + TabDat.ftr{3} = ... + sprintf('Expected voxels per cluster, = %0.3f',En/v2r); + TabDat.ftr{4} = ... + sprintf('Expected number of clusters, = %0.2f',Em*Pn); + if any(isnan(varargin{2}.uc)) + TabDat.ftr{5} = ... + sprintf('FWEp: %0.3f, FDRp: %0.3f',varargin{2}.uc(1:2)); + else + TabDat.ftr{5} = ... + sprintf('FWEp: %0.3f, FDRp: %0.3f, FWEc: %0.0f, FDRc: %0.0f',... + varargin{2}.uc); + end + TabDat.ftr{6} = ... + sprintf('Degrees of freedom = [%0.1f, %0.1f]',df); + TabDat.ftr{7} = ... + ['FWHM = ' sprintf('%0.1f ', FWmm) units{:} '; ' ... + sprintf('%0.1f ', FWHM) '{voxels}']; + TabDat.ftr{8} = ... + sprintf('Volume: %0.0f = %0.0f voxels = %0.1f resels', ... + S*prod(VOX),S,R(end)); + TabDat.ftr{9} = ... + ['Voxel size: ' sprintf('%0.1f ',VOX) units{:} '; ' ... + sprintf('(resel = %0.2f voxels)',prod(FWHM))]; + + text(0.0,-1*dy,TabDat.ftr{1},... + 'UserData',[u,Pz,Pu],'ButtonDownFcn','get(gcbo,''UserData'')') + text(0.0,-2*dy,TabDat.ftr{2},... + 'UserData',[k/v2r,Pn,P],'ButtonDownFcn','get(gcbo,''UserData'')') + text(0.0,-3*dy,TabDat.ftr{3},... + 'UserData',En/v2r,'ButtonDownFcn','get(gcbo,''UserData'')') + text(0.0,-4*dy,TabDat.ftr{4},... + 'UserData',Em*Pn,'ButtonDownFcn','get(gcbo,''UserData'')') + text(0.0,-5*dy,TabDat.ftr{5},... + 'UserData',varargin{2}.uc,'ButtonDownFcn','get(gcbo,''UserData'')') + text(0.5,-1*dy,TabDat.ftr{6},... + 'UserData',df,'ButtonDownFcn','get(gcbo,''UserData'')') + text(0.5,-2*dy,TabDat.ftr{7},... + 'UserData',FWmm,'ButtonDownFcn','get(gcbo,''UserData'')') + text(0.5,-3*dy,TabDat.ftr{8},... + 'UserData',[S*prod(VOX),S,R(end)],... + 'ButtonDownFcn','get(gcbo,''UserData'')') + text(0.5,-4*dy,TabDat.ftr{9},... + 'UserData',[VOX,prod(FWHM)],... + 'ButtonDownFcn','get(gcbo,''UserData'')') + else + TabDat.ftr = {}; + end + + + %-Characterize excursion set in terms of maxima + % (sorted on Z values and grouped by regions) + %====================================================================== + if isempty(varargin{2}.Z) + text(0.5,y-6*dy,'no suprathreshold clusters',... + 'HorizontalAlignment','Center',... + 'FontAngle','Italic','FontWeight','Bold',... + 'FontSize',FS(16),'Color',[1,1,1]*.5); + TabDat.dat = cell(0,12); + varargout = {TabDat}; + spm('Pointer','Arrow') + return + end + + % Includes Darren Gitelman's code for working around + % spm_max for conjunctions with negative thresholds + %---------------------------------------------------------------------- + minz = abs(min(min(varargin{2}.Z))); + zscores = 1 + minz + varargin{2}.Z; + [N Z XYZ A] = spm_max(zscores,varargin{2}.XYZ); + Z = Z - minz - 1; + + %-Convert cluster sizes from voxels to resels + %---------------------------------------------------------------------- + if STAT~='P' + if isfield(varargin{2},'VRvp') + V2R = spm_get_data(varargin{2}.VRvp,XYZ); + else + V2R = v2r; + end + N = N.*V2R; + end + + %-Convert maxima locations from voxels to mm + %---------------------------------------------------------------------- + XYZmm = M(1:3,:)*[XYZ; ones(1,size(XYZ,2))]; + + + + %-Table proper (& note all data in cell array) + %====================================================================== + + %-Pagination variables + %---------------------------------------------------------------------- + hPage = []; + set(gca,'DefaultTextFontName',PF.courier,'DefaultTextFontSize',FS(7)) + + + %-Set-level p values {c} - do not display if reporting a single cluster + %---------------------------------------------------------------------- + c = max(A); %-Number of clusters + if STAT ~= 'P' + Pc = spm_P(c,k,u,df,STAT,R,n,S); %-Set-level p-value + else + Pc = []; + set(Hp,'Visible','off') + end + + if c > 1; + h = text(tCol(1),y,sprintf(TabDat.fmt{1},Pc),'FontWeight','Bold',... + 'UserData',Pc,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + h = text(tCol(2),y,sprintf(TabDat.fmt{2},c),'FontWeight','Bold',... + 'UserData',c,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + else + set(Hc,'Visible','off') + end + + TabDat.dat = {Pc,c}; %-Table data + TabLin = 1; %-Table data line + + + %-Local maxima p-values & statistics + %---------------------------------------------------------------------- + HlistXYZ = []; + while numel(find(isfinite(Z))) + + % Paginate if necessary + %------------------------------------------------------------------ + if y < min(Num + 1,3)*dy + + % added Fgraph term to paginate on Satellite window + %-------------------------------------------------------------- + h = text(0.5,-5*dy,... + sprintf('Page %d',spm_figure('#page',Fgraph)),... + 'FontName',PF.helvetica,'FontAngle','Italic',... + 'FontSize',FS(8)); + + spm_figure('NewPage',[hPage,h]) + hPage = []; + y = y0; + end + + %-Find largest remaining local maximum + %------------------------------------------------------------------ + [U,i] = max(Z); % largest maxima + j = find(A == A(i)); % maxima in cluster + + + %-Compute cluster {k} and peak-level {u} p values for this cluster + %------------------------------------------------------------------ + if STAT ~= 'P' + Nv = N(i)/v2r; % extent {voxels} + + Pz = spm_P(1,0, U,df,STAT,1,n,S); % uncorrected p value + Pu = spm_P(1,0, U,df,STAT,R,n,S); % FWE-corrected {based on Z} + [Pk Pn] = spm_P(1,N(i),u,df,STAT,R,n,S); % [un]corrected {based on k} + if topoFDR + Qc = spm_P_clusterFDR(N(i),df,STAT,R,n,u,QPc); % cluster FDR-corrected {based on k} + Qp = spm_P_peakFDR(U,df,STAT,R,n,u,QPp); % peak FDR-corrected {based on Z} + Qu = []; + else + Qu = spm_P_FDR( U,df,STAT,n,QPs); % voxel FDR-corrected {based on Z} + Qc = []; + Qp = []; + end + + if Pz < tol % Equivalent Z-variate + Ze = Inf; % (underflow => can't compute) + else + Ze = spm_invNcdf(1 - Pz); + end + else + Nv = N(i); + + Pz = []; + Pu = []; + Qu = []; + Pk = []; + Pn = []; + Qc = []; + Qp = []; + Ze = spm_invNcdf(U); + end + + + %-Print cluster and maximum peak-level p values {Z} + %------------------------------------------------------------------ + h = text(tCol(3),y,sprintf(TabDat.fmt{3},Pk),'FontWeight','Bold',... + 'UserData',Pk,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + h = text(tCol(4),y,sprintf(TabDat.fmt{4},Qc),'FontWeight','Bold',... + 'UserData',Qc,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + h = text(tCol(5),y,sprintf(TabDat.fmt{5},Nv),'FontWeight','Bold',... + 'UserData',Nv,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + h = text(tCol(6),y,sprintf(TabDat.fmt{6},Pn),'FontWeight','Bold',... + 'UserData',Pn,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + + h = text(tCol(7),y,sprintf(TabDat.fmt{7},Pu),'FontWeight','Bold',... + 'UserData',Pu,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + if topoFDR + h = text(tCol(8),y,sprintf(TabDat.fmt{8},Qp),'FontWeight','Bold',... + 'UserData',Qp,'ButtonDownFcn','get(gcbo,''UserData'')'); + else + h = text(tCol(8),y,sprintf(TabDat.fmt{8},Qu),'FontWeight','Bold',... + 'UserData',Qu,'ButtonDownFcn','get(gcbo,''UserData'')'); + end + hPage = [hPage, h]; + h = text(tCol(9),y,sprintf(TabDat.fmt{9},U),'FontWeight','Bold',... + 'UserData',U,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + h = text(tCol(10),y,sprintf(TabDat.fmt{10},Ze),'FontWeight','Bold',... + 'UserData',Ze,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + h = ... + text(tCol(11),y,sprintf(TabDat.fmt{11},Pz),'FontWeight','Bold',... + 'UserData',Pz,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + + % Specifically changed so it properly finds hMIPax + %------------------------------------------------------------------ + h = text(tCol(12),y,sprintf(TabDat.fmt{12},XYZmm(:,i)),... + 'FontWeight','Bold',... + 'Tag','ListXYZ',... + 'ButtonDownFcn',[... + 'hMIPax = findobj(''tag'',''hMIPax'');',... + 'spm_mip_ui(''SetCoords'',get(gcbo,''UserData''),hMIPax);'],... + 'Interruptible','off','BusyAction','Cancel',... + 'UserData',XYZmm(:,i)); + + HlistXYZ = [HlistXYZ, h]; + if spm_XYZreg('Edist',xyzmm,XYZmm(:,i)) Dis mm apart) + %------------------------------------------------------------------ + [l q] = sort(-Z(j)); % sort on Z value + D = i; + for i = 1:length(q) + d = j(q(i)); + if min(sqrt(sum((XYZmm(:,D)-XYZmm(:,d)*ones(1,size(D,2))).^2)))>Dis; + + if length(D) < Num + + % Paginate if necessary + %------------------------------------------------------ + if y < dy + h = text(0.5,-5*dy,sprintf('Page %d',... + spm_figure('#page',Fgraph)),... + 'FontName',PF.helvetica,... + 'FontAngle','Italic',... + 'FontSize',FS(8)); + + spm_figure('NewPage',[hPage,h]) + hPage = []; + y = y0; + end + + % voxel-level p values {Z} + %------------------------------------------------------ + if STAT ~= 'P' + Pz = spm_P(1,0,Z(d),df,STAT,1,n,S); + Pu = spm_P(1,0,Z(d),df,STAT,R,n,S); + if topoFDR + Qp = spm_P_peakFDR(Z(d),df,STAT,R,n,u,QPp); + Qu = []; + else + Qu = spm_P_FDR(Z(d),df,STAT,n,QPs); + Qp = []; + end + if Pz < tol + Ze = Inf; + else + Ze = spm_invNcdf(1 - Pz); + end + else + Pz = []; + Pu = []; + Qu = []; + Qp = []; + Ze = spm_invNcdf(Z(d)); + end + + h = text(tCol(7),y,sprintf(TabDat.fmt{7},Pu),... + 'UserData',Pu,... + 'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + + if topoFDR + h = text(tCol(8),y,sprintf(TabDat.fmt{8},Qp),... + 'UserData',Qp,... + 'ButtonDownFcn','get(gcbo,''UserData'')'); + else + h = text(tCol(8),y,sprintf(TabDat.fmt{8},Qu),... + 'UserData',Qu,... + 'ButtonDownFcn','get(gcbo,''UserData'')'); + end + hPage = [hPage, h]; + h = text(tCol(9),y,sprintf(TabDat.fmt{9},Z(d)),... + 'UserData',Z(d),... + 'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + h = text(tCol(10),y,sprintf(TabDat.fmt{10},Ze),... + 'UserData',Ze,... + 'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + h = text(tCol(11),y,sprintf(TabDat.fmt{11},Pz),... + 'UserData',Pz,... + 'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + + % specifically modified line to use hMIPax + %------------------------------------------------------ + h = text(tCol(12),y,... + sprintf(TabDat.fmt{12},XYZmm(:,d)),... + 'Tag','ListXYZ',... + 'ButtonDownFcn',[... + 'hMIPax = findobj(''tag'',''hMIPax'');',... + 'spm_mip_ui(''SetCoords'',',... + 'get(gcbo,''UserData''),hMIPax);'],... + 'Interruptible','off','BusyAction','Cancel',... + 'UserData',XYZmm(:,d)); + + HlistXYZ = [HlistXYZ, h]; + if spm_XYZreg('Edist',xyzmm,XYZmm(:,d))1 + h = text(0.5,-5*dy,sprintf('Page %d/%d',spm_figure('#page',Fgraph)*[1,1]),... + 'FontName',PF.helvetica,'FontSize',FS(8),'FontAngle','Italic'); + spm_figure('NewPage',[hPage,h]) + end + + %-End: Store TabDat in UserData of axes & reset pointer + %====================================================================== + h = uicontextmenu('Tag','TabDat',... + 'UserData',TabDat); + set(gca,'UIContextMenu',h,... + 'Visible','on',... + 'XColor','w','YColor','w') + uimenu(h,'Label','Table') + uimenu(h,'Separator','on','Label','Print text table',... + 'Tag','TD_TxtTab',... + 'CallBack',... + 'spm_list(''txtlist'',get(get(gcbo,''Parent''),''UserData''),3)',... + 'Interruptible','off','BusyAction','Cancel'); + uimenu(h,'Separator','off','Label','Extract table data structure',... + 'Tag','TD_Xdat',... + 'CallBack','get(get(gcbo,''Parent''),''UserData'')',... + 'Interruptible','off','BusyAction','Cancel'); + uimenu(h,'Separator','on','Label','help',... + 'Tag','TD_Xdat',... + 'CallBack','spm_help(''spm_list'')',... + 'Interruptible','off','BusyAction','Cancel'); + + %-Setup registry + %---------------------------------------------------------------------- + set(hAx,'UserData',struct('hReg',hReg,'HlistXYZ',HlistXYZ)) + spm_XYZreg('Add2Reg',hReg,hAx,'spm_list'); + + %-Return TabDat structure & reset pointer + %---------------------------------------------------------------------- + varargout = {TabDat}; + spm('Pointer','Arrow') + + + %====================================================================== + case 'listcluster' %-List for current cluster only + %====================================================================== + % FORMAT TabDat = spm_list('listcluster',SPM,hReg) + + spm('Pointer','Watch') + + %-Parse arguments + %------------------------------------------------------------------ + if nargin < 2, error('insufficient arguments'), end + if nargin < 3, hReg = []; else hReg = varargin{3}; end + SPM = varargin{2}; + + %-get number and separation for maxima to be reported + %------------------------------------------------------------------ + if length(varargin) > 3 + + Num = varargin{4}; % number of maxima per cluster + Dis = varargin{5}; % distance among clusters (mm) + else + Num = 32; + Dis = 4; + end + + + %-if there are suprathreshold voxels, filter out all but current cluster + %------------------------------------------------------------------ + if ~isempty(SPM.Z) + + %-Jump to voxel nearest current location + %-------------------------------------------------------------- + [xyzmm,i] = spm_XYZreg('NearestXYZ',... + spm_results_ui('GetCoords'),SPM.XYZmm); + spm_results_ui('SetCoords',SPM.XYZmm(:,i)); + + %-Find selected cluster + %-------------------------------------------------------------- + A = spm_clusters(SPM.XYZ); + j = find(A == A(i)); + SPM.Z = SPM.Z(j); + SPM.XYZ = SPM.XYZ(:,j); + SPM.XYZmm = SPM.XYZmm(:,j); + if isfield(SPM,'Rd'), SPM.Rd = SPM.Rd(:,j); end + end + + %-Call 'list' functionality to produce table + %------------------------------------------------------------------ + varargout = {spm_list('list',SPM,hReg,Num,Dis)}; + + + %====================================================================== + case 'txtlist' %-Print ASCII text table + %====================================================================== + % FORMAT spm_list('TxtList',TabDat,c) + + if nargin<2, error('Insufficient arguments'), end + if nargin<3, c=1; else c=varargin{3}; end + TabDat = varargin{2}; + + %-Table Title + %------------------------------------------------------------------ + fprintf('\n\nSTATISTICS: %s\n',TabDat.tit) + fprintf('%c',repmat('=',1,80)), fprintf('\n') + + %-Table header + %------------------------------------------------------------------ + fprintf('%s\t',TabDat.hdr{1,c:end-1}), fprintf('%s\n',TabDat.hdr{1,end}) + fprintf('%s\t',TabDat.hdr{2,c:end-1}), fprintf('%s\n',TabDat.hdr{2,end}) + fprintf('%c',repmat('-',1,80)), fprintf('\n') + + %-Table data + %------------------------------------------------------------------ + for i = 1:size(TabDat.dat,1) + for j=c:size(TabDat.dat,2) + fprintf(TabDat.fmt{j},TabDat.dat{i,j}) + fprintf('\t') + end + fprintf('\n') + end + for i=1:max(1,12-size(TabDat.dat,1)), fprintf('\n'), end + fprintf('%s\n',TabDat.str) + fprintf('%c',repmat('-',1,80)), fprintf('\n') + + %-Table footer + %------------------------------------------------------------------ + fprintf('%s\n',TabDat.ftr{:}) + fprintf('%c',repmat('=',1,80)), fprintf('\n\n') + + + + %================================================================== + case 'setcoords' %-Co-ordinate change + %================================================================== + % FORMAT spm_list('SetCoords',xyz,hAx,hReg) + if nargin<3, error('Insufficient arguments'), end + hAx = varargin{3}; + xyz = varargin{2}; + UD = get(hAx,'UserData'); + HlistXYZ = UD.HlistXYZ(ishandle(UD.HlistXYZ)); + + %-Set all co-ord strings to black + %------------------------------------------------------------------ + set(HlistXYZ,'Color','k') + + %-If co-ord matches a string, highlight it in red + %------------------------------------------------------------------ + XYZ = get(HlistXYZ,'UserData'); + if iscell(XYZ), XYZ = cat(2,XYZ{:}); end + [null,i,d] = spm_XYZreg('NearestXYZ',xyz,XYZ); + if d=2, st.vols{H}.area = varargin{2}; end; + if isempty(st.bb), st.bb = maxbb; end; + bbox; + cm_pos; + end; + varargout{1} = H; + st.centre = mean(maxbb); + redraw_all + +case 'bb', + if length(varargin)> 0 & all(size(varargin{1})==[2 3]), st.bb = varargin{1}; end; + bbox; + redraw_all; + +case 'redraw', + redraw_all; + eval(st.callback); + if isfield(st,'registry'), + spm_XYZreg('SetCoords',st.centre,st.registry.hReg,st.registry.hMe); + end; + +case 'reposition', + if length(varargin)<1, tmp = findcent; + else, tmp = varargin{1}; end; + if length(tmp)==3 + h = valid_handles(st.snap); + if ~isempty(h) + tmp=st.vols{h(1)}.mat*... + round(inv(st.vols{h(1)}.mat)*[tmp; ... + 1]); + end; + st.centre = tmp(1:3); + end; + redraw_all; + eval(st.callback); + if isfield(st,'registry'), + spm_XYZreg('SetCoords',st.centre,st.registry.hReg,st.registry.hMe); + end; + cm_pos; + +case 'setcoords', + st.centre = varargin{1}; + st.centre = st.centre(:); + redraw_all; + eval(st.callback); + cm_pos; + +case 'space', + if length(varargin)<1, + st.Space = eye(4); + st.bb = maxbb; + bbox; + redraw_all; + else, + space(varargin{1}); + bbox; + redraw_all; + end; + +case 'maxbb', + st.bb = maxbb; + bbox; + redraw_all; + +case 'resolution', + resolution(varargin{1}); + bbox; + redraw_all; + +case 'window', + if length(varargin)<2, + win = 'auto'; + elseif length(varargin{2})==2, + win = varargin{2}; + end; + for i=valid_handles(varargin{1}), + st.vols{i}.window = win; + end; + redraw(varargin{1}); + +case 'delete', + my_delete(varargin{1}); + +case 'move', + move(varargin{1},varargin{2}); + % redraw_all; + +case 'reset', + my_reset; + +case 'pos', + if isempty(varargin), + H = st.centre(:); + else, + H = pos(varargin{1}); + end; + varargout{1} = H; + +case 'interp', + st.hld = varargin{1}; + redraw_all; + +case 'xhairs', + xhairs(varargin{1}); + +case 'register', + register(varargin{1}); + +case 'addblobs', + addblobs(varargin{1}, varargin{2},varargin{3},varargin{4}); + % redraw(varargin{1}); + +case 'addcolouredblobs', + addcolouredblobs(varargin{1}, varargin{2},varargin{3},varargin{4},varargin{5}); + % redraw(varargin{1}); + +case 'addimage', + addimage(varargin{1}, varargin{2}); + % redraw(varargin{1}); + +case 'addcolouredimage', + addcolouredimage(varargin{1}, varargin{2},varargin{3}); + % redraw(varargin{1}); + +case 'addtruecolourimage', + % spm_orthviews('Addtruecolourimage',handle,filename,colourmap,prop,mx,mn) + % Adds blobs from an image in true colour + % handle - image number to add blobs to [default 1] + % filename of image containing blob data [default - request via GUI] + % colourmap - colormap to display blobs in [GUI input] + % prop - intensity proportion of activation cf grayscale [0.4] + % mx - maximum intensity to scale to [maximum value in activation image] + % mn - minimum intensity to scale to [minimum value in activation image] + % + if nargin < 2 + varargin(1) = {1}; + end + if nargin < 3 + varargin(2) = {spm_select(1, 'image', 'Image with activation signal')}; + end + if nargin < 4 + actc = []; + while isempty(actc) + actc = getcmap(spm_input('Colourmap for activation image', '+1','s')); + end + varargin(3) = {actc}; + end + if nargin < 5 + varargin(4) = {0.4}; + end + if nargin < 6 + actv = spm_vol(varargin{2}); + varargin(5) = {max([eps maxval(actv)])}; + end + if nargin < 7 + varargin(6) = {min([0 minval(actv)])}; + end + + addtruecolourimage(varargin{1}, varargin{2},varargin{3}, varargin{4}, ... + varargin{5}, varargin{6}); + % redraw(varargin{1}); + +case 'addcolourbar', + addcolourbar(varargin{1}, varargin{2}); + +case 'rmblobs', + rmblobs(varargin{1}); + % redraw(varargin{1}); + +case 'addcontext', + if nargin == 1, + handles = 1:24; + else, + handles = varargin{1}; + end; + addcontexts(handles); + +case 'rmcontext', + if nargin == 1, + handles = 1:24; + else, + handles = varargin{1}; + end; + rmcontexts(handles); + +case 'context_menu', + c_menu(varargin{:}); + +case 'valid_handles', + if nargin == 1 + handles = 1:24; + else, + handles = varargin{1}; + end; + varargout{1} = valid_handles(handles); + +otherwise, + addonaction = strcmp(st.plugins,action); + if any(addonaction) + feval(['spm_ov_' st.plugins{addonaction}],varargin{:}); + else + warning('Unknown action string') + end; +end; + +spm('Pointer'); +return; + + +%_______________________________________________________________________ +%_______________________________________________________________________ +function addblobs(handle, xyz, t, mat) +global st +global TMAX_ +for i=valid_handles(handle), + if ~isempty(xyz), + rcp = round(xyz); + dim = max(rcp,[],2)'; + off = rcp(1,:) + dim(1)*(rcp(2,:)-1 + dim(2)*(rcp(3,:)-1)); + vol = zeros(dim)+NaN; + vol(off) = t; + vol = reshape(vol,dim); + st.vols{i}.blobs=cell(1,1); + if st.mode == 0, + axpos = get(st.vols{i}.ax{2}.ax,'Position'); + else, + axpos = get(st.vols{i}.ax{1}.ax,'Position'); + end; + mx = max([eps max(t)]); + mn = min([0 min(t)]); + if ~strcmp(TMAX_, 'auto') + mx = str2num(TMAX_); + end + st.vols{i}.blobs{1} = struct('vol',vol,'mat',mat,'max',mx, 'min',mn); + addcolourbar(handle,1); + end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function addimage(handle, fname) +global st +for i=valid_handles(handle), + if isstruct(fname), + vol = fname(1); + else, + vol = spm_vol(fname); + end; + mat = vol.mat; + st.vols{i}.blobs=cell(1,1); + mx = max([eps maxval(vol)]); + mn = min([0 minval(vol)]); + st.vols{i}.blobs{1} = struct('vol',vol,'mat',mat,'max',mx,'min',mn); + addcolourbar(handle,1); +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function addcolouredblobs(handle, xyz, t, mat,colour) +global st +for i=valid_handles(handle), + if ~isempty(xyz), + rcp = round(xyz); + dim = max(rcp,[],2)'; + off = rcp(1,:) + dim(1)*(rcp(2,:)-1 + dim(2)*(rcp(3,:)-1)); + vol = zeros(dim)+NaN; + vol(off) = t; + vol = reshape(vol,dim); + if ~isfield(st.vols{i},'blobs'), + st.vols{i}.blobs=cell(1,1); + bset = 1; + else, + bset = length(st.vols{i}.blobs)+1; + end; + mx = max([eps maxval(vol)]); + mn = min([0 minval(vol)]); + st.vols{i}.blobs{bset} = struct('vol',vol,'mat',mat,'max',mx,'min',mn,'colour',colour); + end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function addcolouredimage(handle, fname,colour) +global st +for i=valid_handles(handle), + if isstruct(fname), + vol = fname(1); + else, + vol = spm_vol(fname); + end; + mat = vol.mat; + if ~isfield(st.vols{i},'blobs'), + st.vols{i}.blobs=cell(1,1); + bset = 1; + else, + bset = length(st.vols{i}.blobs)+1; + end; + mx = max([eps maxval(vol)]); + mn = min([0 minval(vol)]); + st.vols{i}.blobs{bset} = struct('vol',vol,'mat',mat,'max',mx,'min',mn,'colour',colour); +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function addtruecolourimage(handle,fname,colourmap,prop,mx,mn) +% adds true colour image to current displayed image +global st +for i=valid_handles(handle), + if isstruct(fname), + vol = fname(1); + else, + vol = spm_vol(fname); + end; + mat = vol.mat; + if ~isfield(st.vols{i},'blobs'), + st.vols{i}.blobs=cell(1,1); + bset = 1; + else, + bset = length(st.vols{i}.blobs)+1; + end; + c = struct('cmap', colourmap,'prop',prop); + st.vols{i}.blobs{bset} = struct('vol',vol,'mat',mat,'max',mx, ... + 'min',mn,'colour',c); + addcolourbar(handle,bset); +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function addcolourbar(vh,bh) +global st +if st.mode == 0, + axpos = get(st.vols{vh}.ax{2}.ax,'Position'); +else, + axpos = get(st.vols{vh}.ax{1}.ax,'Position'); +end; +st.vols{vh}.blobs{bh}.cbar = axes('Parent',st.fig,... + 'Position',[(axpos(1)+axpos(3)+0.05+(bh-1)*.1) (axpos(2)+0.005) 0.05 (axpos(4)-0.01)],... + 'Box','on', 'YDir','normal', 'XTickLabel',[], 'XTick',[]); +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function rmblobs(handle) +global st +for i=valid_handles(handle), + if isfield(st.vols{i},'blobs'), + for j=1:length(st.vols{i}.blobs), + if isfield(st.vols{i}.blobs{j},'cbar') & ishandle(st.vols{i}.blobs{j}.cbar), + delete(st.vols{i}.blobs{j}.cbar); + end; + end; + st.vols{i} = rmfield(st.vols{i},'blobs'); + end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function register(hreg) +global st +tmp = uicontrol('Position',[0 0 1 1],'Visible','off','Parent',st.fig); +h = valid_handles(1:24); +if ~isempty(h), + tmp = st.vols{h(1)}.ax{1}.ax; + st.registry = struct('hReg',hreg,'hMe', tmp); + spm_XYZreg('Add2Reg',st.registry.hReg,st.registry.hMe, 'spm_orthviews'); +else, + warning('Nothing to register with'); +end; +st.centre = spm_XYZreg('GetCoords',st.registry.hReg); +st.centre = st.centre(:); +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function xhairs(arg1), +global st +st.xhairs = 0; +opt = 'on'; +if ~strcmp(arg1,'on'), + opt = 'off'; +else, + st.xhairs = 1; +end; +for i=valid_handles(1:24), + for j=1:3, + set(st.vols{i}.ax{j}.lx,'Visible',opt); + set(st.vols{i}.ax{j}.ly,'Visible',opt); + end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function H = pos(arg1) +global st +H = []; +for arg1=valid_handles(arg1), + is = inv(st.vols{arg1}.premul*st.vols{arg1}.mat); + H = is(1:3,1:3)*st.centre(:) + is(1:3,4); +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function my_reset +global st +if ~isempty(st) & isfield(st,'registry') & ishandle(st.registry.hMe), + delete(st.registry.hMe); st = rmfield(st,'registry'); +end; +my_delete(1:24); +reset_st; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function my_delete(arg1) +global st +for i=valid_handles(arg1), + kids = get(st.fig,'Children'); + for j=1:3, + if any(kids == st.vols{i}.ax{j}.ax), + set(get(st.vols{i}.ax{j}.ax,'Children'),'DeleteFcn',''); + delete(st.vols{i}.ax{j}.ax); + end; + end; + st.vols{i} = []; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function resolution(arg1) +global st +res = arg1/mean(svd(st.Space(1:3,1:3))); +Mat = diag([res res res 1]); +st.Space = st.Space*Mat; +st.bb = st.bb/res; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function move(handle,pos) +global st +for handle = valid_handles(handle), + st.vols{handle}.area = pos; +end; +bbox; +% redraw(valid_handles(handle)); +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function bb = maxbb +global st +mn = [Inf Inf Inf]; +mx = -mn; +for i=valid_handles(1:24), + bb = [[1 1 1];st.vols{i}.dim(1:3)]; + c = [ bb(1,1) bb(1,2) bb(1,3) 1 + bb(1,1) bb(1,2) bb(2,3) 1 + bb(1,1) bb(2,2) bb(1,3) 1 + bb(1,1) bb(2,2) bb(2,3) 1 + bb(2,1) bb(1,2) bb(1,3) 1 + bb(2,1) bb(1,2) bb(2,3) 1 + bb(2,1) bb(2,2) bb(1,3) 1 + bb(2,1) bb(2,2) bb(2,3) 1]'; + tc = st.Space\(st.vols{i}.premul*st.vols{i}.mat)*c; + tc = tc(1:3,:)'; + mx = max([tc ; mx]); + mn = min([tc ; mn]); +end; +bb = [mn ; mx]; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function space(arg1) +global st +if ~isempty(st.vols{arg1}) + num = arg1; + Mat = st.vols{num}.premul(1:3,1:3)*st.vols{num}.mat(1:3,1:3); + vox = sqrt(sum(Mat.^2)); + if det(Mat(1:3,1:3))<0, vox(1) = -vox(1); end; + Mat = diag([vox 1]); + Space = (st.vols{num}.mat)/Mat; + bb = [1 1 1;st.vols{num}.dim(1:3)]; + bb = [bb [1;1]]; + bb=bb*Mat'; + bb=bb(:,1:3); + bb=sort(bb); + st.Space = Space; + st.bb = bb; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function H = specify_image(arg1, arg2) +global st +H=[]; +ok = 1; +if isstruct(arg1), + V = arg1(1); +else, + try, + V = spm_vol(arg1); + catch, + fprintf('Can not use image "%s"\n', arg1); + return; + end; +end; + +ii = 1; +while ~isempty(st.vols{ii}), ii = ii + 1; end; + +DeleteFcn = ['spm_orthviews(''Delete'',' num2str(ii) ');']; +V.ax = cell(3,1); +for i=1:3, + ax = axes('Visible','off','DrawMode','fast','Parent',st.fig,'DeleteFcn',DeleteFcn,... + 'YDir','normal','ButtonDownFcn',... + ['if strcmp(get(gcf,''SelectionType''),''normal''),spm_orthviews(''Reposition'');',... + 'elseif strcmp(get(gcf,''SelectionType''),''extend''),spm_orthviews(''Reposition'');',... + 'spm_orthviews(''context_menu'',''ts'',1);end;']); + d = image(0,'Tag','Transverse','Parent',ax,... + 'DeleteFcn',DeleteFcn); + set(ax,'Ydir','normal','ButtonDownFcn',... + ['if strcmp(get(gcf,''SelectionType''),''normal''),spm_orthviews(''Reposition'');',... + 'elseif strcmp(get(gcf,''SelectionType''),''extend''),spm_orthviews(''reposition'');',... + 'spm_orthviews(''context_menu'',''ts'',1);end;']); + + lx = line(0,0,'Parent',ax,'DeleteFcn',DeleteFcn); + ly = line(0,0,'Parent',ax,'DeleteFcn',DeleteFcn); + if ~st.xhairs, + set(lx,'Visible','off'); + set(ly,'Visible','off'); + end; + V.ax{i} = struct('ax',ax,'d',d,'lx',lx,'ly',ly); +end; +V.premul = eye(4); +V.window = 'auto'; +V.mapping = 'linear'; +st.vols{ii} = V; + +H = ii; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function addcontexts(handles) +global st +for ii = valid_handles(handles), + cm_handle = addcontext(ii); + for i=1:3, + set(st.vols{ii}.ax{i}.ax,'UIcontextmenu',cm_handle); + st.vols{ii}.ax{i}.cm = cm_handle; + end; +end; +spm_orthviews('reposition',spm_orthviews('pos')); +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function rmcontexts(handles) +global st +for ii = valid_handles(handles), + for i=1:3, + set(st.vols{ii}.ax{i}.ax,'UIcontextmenu',[]); + st.vols{ii}.ax{i} = rmfield(st.vols{ii}.ax{i},'cm'); + end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function bbox +global st +Dims = diff(st.bb)'+1; + +TD = Dims([1 2])'; +CD = Dims([1 3])'; +if st.mode == 0, SD = Dims([3 2])'; else, SD = Dims([2 3])'; end; + +un = get(st.fig,'Units');set(st.fig,'Units','Pixels'); +sz = get(st.fig,'Position');set(st.fig,'Units',un); +sz = sz(3:4); +sz(2) = sz(2)-40; + +for i=valid_handles(1:24), + area = st.vols{i}.area(:); + area = [area(1)*sz(1) area(2)*sz(2) area(3)*sz(1) area(4)*sz(2)]; + if st.mode == 0, + sx = area(3)/(Dims(1)+Dims(3))/1.02; + else, + sx = area(3)/(Dims(1)+Dims(2))/1.02; + end; + sy = area(4)/(Dims(2)+Dims(3))/1.02; + s = min([sx sy]); + + offy = (area(4)-(Dims(2)+Dims(3))*1.02*s)/2 + area(2); + sky = s*(Dims(2)+Dims(3))*0.02; + if st.mode == 0, + offx = (area(3)-(Dims(1)+Dims(3))*1.02*s)/2 + area(1); + skx = s*(Dims(1)+Dims(3))*0.02; + else, + offx = (area(3)-(Dims(1)+Dims(2))*1.02*s)/2 + area(1); + skx = s*(Dims(1)+Dims(2))*0.02; + end; + + DeleteFcn = ['spm_orthviews(''Delete'',' num2str(i) ');']; + + % Transverse + set(st.vols{i}.ax{1}.ax,'Units','pixels', ... + 'Position',[offx offy s*Dims(1) s*Dims(2)],... + 'Units','normalized','Xlim',[0 TD(1)]+0.5,'Ylim',[0 TD(2)]+0.5,... + 'Visible','on','XTick',[],'YTick',[]); + + % Coronal + set(st.vols{i}.ax{2}.ax,'Units','Pixels',... + 'Position',[offx offy+s*Dims(2)+sky s*Dims(1) s*Dims(3)],... + 'Units','normalized','Xlim',[0 CD(1)]+0.5,'Ylim',[0 CD(2)]+0.5,... + 'Visible','on','XTick',[],'YTick',[]); + + % Sagittal + if st.mode == 0, + set(st.vols{i}.ax{3}.ax,'Units','Pixels', 'Box','on',... + 'Position',[offx+s*Dims(1)+skx offy s*Dims(3) s*Dims(2)],... + 'Units','normalized','Xlim',[0 SD(1)]+0.5,'Ylim',[0 SD(2)]+0.5,... + 'Visible','on','XTick',[],'YTick',[]); + else, + set(st.vols{i}.ax{3}.ax,'Units','Pixels', 'Box','on',... + 'Position',[offx+s*Dims(1)+skx offy+s*Dims(2)+sky s*Dims(2) s*Dims(3)],... + 'Units','normalized','Xlim',[0 SD(1)]+0.5,'Ylim',[0 SD(2)]+0.5,... + 'Visible','on','XTick',[],'YTick',[]); + end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function redraw_all +global st +redraw(1:24); +return; +%_______________________________________________________________________ +function mx = maxval(vol) +if isstruct(vol), + mx = -Inf; + for i=1:vol.dim(3), + tmp = spm_slice_vol(vol,spm_matrix([0 0 i]),vol.dim(1:2),0); + imx = max(tmp(find(isfinite(tmp)))); + if ~isempty(imx),mx = max(mx,imx);end + end; +else, + mx = max(vol(find(isfinite(vol)))); +end; +%_______________________________________________________________________ +function mn = minval(vol) +if isstruct(vol), + mn = Inf; + for i=1:vol.dim(3), + tmp = spm_slice_vol(vol,spm_matrix([0 0 i]),vol.dim(1:2),0); + imn = min(tmp(find(isfinite(tmp)))); + if ~isempty(imn),mn = min(mn,imn);end + end; +else, + mn = min(vol(find(isfinite(vol)))); +end; + +%_______________________________________________________________________ +%_______________________________________________________________________ +function redraw(arg1) +global st +bb = st.bb; +Dims = round(diff(bb)'+1); +is = inv(st.Space); +cent = is(1:3,1:3)*st.centre(:) + is(1:3,4); +% cent(1)=-40 +% cent(2)=-40 +% cent(3)=60 +for i = valid_handles(arg1), + M = st.vols{i}.premul*st.vols{i}.mat; + TM0 = [ 1 0 0 -bb(1,1)+1 + 0 1 0 -bb(1,2)+1 + 0 0 1 -cent(3) + 0 0 0 1]; + TM = inv(TM0*(st.Space\M)); + TD = Dims([1 2]); + + CM0 = [ 1 0 0 -bb(1,1)+1 + 0 0 1 -bb(1,3)+1 + 0 1 0 -cent(2) + 0 0 0 1]; + CM = inv(CM0*(st.Space\M)); + CD = Dims([1 3]); + + if st.mode ==0, + SM0 = [ 0 0 1 -bb(1,3)+1 + 0 1 0 -bb(1,2)+1 + 1 0 0 -cent(1) + 0 0 0 1]; + SM = inv(SM0*(st.Space\M)); SD = Dims([3 2]); + else, + SM0 = [ 0 1 0 -bb(1,2)+1 + 0 0 1 -bb(1,3)+1 + 1 0 0 -cent(1) + 0 0 0 1]; + SM0 = [ 0 -1 0 +bb(2,2)+1 + 0 0 1 -bb(1,3)+1 + 1 0 0 -cent(1) + 0 0 0 1]; + SM = inv(SM0*(st.Space\M)); + SD = Dims([2 3]); + end; + + ok=1; + eval('imgt = (spm_slice_vol(st.vols{i},TM,TD,st.hld))'';','ok=0;'); + eval('imgc = (spm_slice_vol(st.vols{i},CM,CD,st.hld))'';','ok=0;'); + eval('imgs = (spm_slice_vol(st.vols{i},SM,SD,st.hld))'';','ok=0;'); + if (ok==0), fprintf('Image "%s" can not be resampled\n', st.vols{i}.fname); + else, + % get min/max threshold + if strcmp(st.vols{i}.window,'auto') + mn = -Inf; + mx = Inf; + else + mn = min(st.vols{i}.window); + mx = max(st.vols{i}.window); + end; + % threshold images + imgt = max(imgt,mn); imgt = min(imgt,mx); + imgc = max(imgc,mn); imgc = min(imgc,mx); + imgs = max(imgs,mn); imgs = min(imgs,mx); + % compute intensity mapping, if histeq is available + if license('test','image_toolbox') == 0 + st.vols{i}.mapping = 'linear'; + end; + switch st.vols{i}.mapping, + case 'linear', + case 'histeq', + % scale images to a range between 0 and 1 + imgt1=(imgt-min(imgt(:)))/(max(imgt(:)-min(imgt(:)))+eps); + imgc1=(imgc-min(imgc(:)))/(max(imgc(:)-min(imgc(:)))+eps); + imgs1=(imgs-min(imgs(:)))/(max(imgs(:)-min(imgs(:)))+eps); + img = histeq([imgt1(:); imgc1(:); imgs1(:)],1024); + imgt = reshape(img(1:numel(imgt1)),size(imgt1)); + imgc = reshape(img(numel(imgt1)+[1:numel(imgc1)]),size(imgc1)); + imgs = reshape(img(numel(imgt1)+numel(imgc1)+[1:numel(imgs1)]),size(imgs1)); + mn = 0; + mx = 1; + case 'quadhisteq', + % scale images to a range between 0 and 1 + imgt1=(imgt-min(imgt(:)))/(max(imgt(:)-min(imgt(:)))+eps); + imgc1=(imgc-min(imgc(:)))/(max(imgc(:)-min(imgc(:)))+eps); + imgs1=(imgs-min(imgs(:)))/(max(imgs(:)-min(imgs(:)))+eps); + img = histeq([imgt1(:).^2; imgc1(:).^2; imgs1(:).^2],1024); + imgt = reshape(img(1:numel(imgt1)),size(imgt1)); + imgc = reshape(img(numel(imgt1)+[1:numel(imgc1)]),size(imgc1)); + imgs = reshape(img(numel(imgt1)+numel(imgc1)+[1:numel(imgs1)]),size(imgs1)); + mn = 0; + mx = 1; + case 'loghisteq', + warning off % messy - but it may avoid extra queries + imgt = log(imgt-min(imgt(:))); + imgc = log(imgc-min(imgc(:))); + imgs = log(imgs-min(imgs(:))); + warning on + imgt(~isfinite(imgt)) = 0; + imgc(~isfinite(imgc)) = 0; + imgs(~isfinite(imgs)) = 0; + % scale log images to a range between 0 and 1 + imgt1=(imgt-min(imgt(:)))/(max(imgt(:)-min(imgt(:)))+eps); + imgc1=(imgc-min(imgc(:)))/(max(imgc(:)-min(imgc(:)))+eps); + imgs1=(imgs-min(imgs(:)))/(max(imgs(:)-min(imgs(:)))+eps); + img = histeq([imgt1(:); imgc1(:); imgs1(:)],1024); + imgt = reshape(img(1:numel(imgt1)),size(imgt1)); + imgc = reshape(img(numel(imgt1)+[1:numel(imgc1)]),size(imgc1)); + imgs = reshape(img(numel(imgt1)+numel(imgc1)+[1:numel(imgs1)]),size(imgs1)); + mn = 0; + mx = 1; + end; + % recompute min/max for display + if strcmp(st.vols{i}.window,'auto') + mx = -inf; mn = inf; + end; + if ~isempty(imgt), + tmp = imgt(isfinite(imgt)); + mx = max([mx max(max(tmp))]); + mn = min([mn min(min(tmp))]); + end; + if ~isempty(imgc), + tmp = imgc(isfinite(imgc)); + mx = max([mx max(max(tmp))]); + mn = min([mn min(min(tmp))]); + end; + if ~isempty(imgs), + tmp = imgs(isfinite(imgs)); + mx = max([mx max(max(tmp))]); + mn = min([mn min(min(tmp))]); + end; + if mx==mn, mx=mn+eps; end; + + if isfield(st.vols{i},'blobs'), + if ~isfield(st.vols{i}.blobs{1},'colour'), + % Add blobs for display using the split colourmap + scal = 64/(mx-mn); + dcoff = -mn*scal; + imgt = imgt*scal+dcoff; + imgc = imgc*scal+dcoff; + imgs = imgs*scal+dcoff; + + if isfield(st.vols{i}.blobs{1},'max'), + mx = st.vols{i}.blobs{1}.max; + else, + mx = max([eps maxval(st.vols{i}.blobs{1}.vol)]); + st.vols{i}.blobs{1}.max = mx; + end; + if isfield(st.vols{i}.blobs{1},'min'), + mn = st.vols{i}.blobs{1}.min; + else, + mn = min([0 minval(st.vols{i}.blobs{1}.vol)]); + st.vols{i}.blobs{1}.min = mn; + end; + + vol = st.vols{i}.blobs{1}.vol; + M = st.vols{i}.premul*st.vols{i}.blobs{1}.mat; + tmpt = spm_slice_vol(vol,inv(TM0*(st.Space\M)),TD,[0 NaN])'; + tmpc = spm_slice_vol(vol,inv(CM0*(st.Space\M)),CD,[0 NaN])'; + tmps = spm_slice_vol(vol,inv(SM0*(st.Space\M)),SD,[0 NaN])'; + + %tmpt_z = find(tmpt==0);tmpt(tmpt_z) = NaN; + %tmpc_z = find(tmpc==0);tmpc(tmpc_z) = NaN; + %tmps_z = find(tmps==0);tmps(tmps_z) = NaN; + + sc = 64/(mx-mn); + off = 65.51-mn*sc; + msk = find(isfinite(tmpt)); imgt(msk) = off+tmpt(msk)*sc; + msk = find(isfinite(tmpc)); imgc(msk) = off+tmpc(msk)*sc; + msk = find(isfinite(tmps)); imgs(msk) = off+tmps(msk)*sc; + + cmap = get(st.fig,'Colormap'); + + figure(st.fig) + if mn*mx < 0 + setcolormap('gray-hot-cold') + elseif mx > 0 + setcolormap('gray-hot'); % can change this to cold color + else + setcolormap('gray-cold') + end + redraw_colourbar(i,1,[mn mx],[1:64]'+64); + elseif isstruct(st.vols{i}.blobs{1}.colour), + % Add blobs for display using a defined + % colourmap + + % colourmaps + gryc = [0:63]'*ones(1,3)/63; + actc = ... + st.vols{1}.blobs{1}.colour.cmap; + actp = ... + st.vols{1}.blobs{1}.colour.prop; + + % scale grayscale image, not finite -> black + imgt = scaletocmap(imgt,mn,mx,gryc,65); + imgc = scaletocmap(imgc,mn,mx,gryc,65); + imgs = scaletocmap(imgs,mn,mx,gryc,65); + gryc = [gryc; 0 0 0]; + + % get max for blob image + vol = st.vols{i}.blobs{1}.vol; + mat = st.vols{i}.premul*st.vols{i}.blobs{1}.mat; + if isfield(st.vols{i}.blobs{1},'max'), + cmx = st.vols{i}.blobs{1}.max; + else, + cmx = max([eps maxval(st.vols{i}.blobs{1}.vol)]); + end; + if isfield(st.vols{i}.blobs{1},'min'), + cmn = st.vols{i}.blobs{1}.min; + else, + cmn = -cmx; + end; + + % get blob data + vol = st.vols{i}.blobs{1}.vol; + M = st.vols{i}.premul*st.vols{i}.blobs{1}.mat; + tmpt = spm_slice_vol(vol,inv(TM0*(st.Space\M)),TD,[0 NaN])'; + tmpc = spm_slice_vol(vol,inv(CM0*(st.Space\M)),CD,[0 NaN])'; + tmps = spm_slice_vol(vol,inv(SM0*(st.Space\M)),SD,[0 NaN])'; + + % actimg scaled round 0, black NaNs + topc = size(actc,1)+1; + tmpt = scaletocmap(tmpt,cmn,cmx,actc,topc); + tmpc = scaletocmap(tmpc,cmn,cmx,actc,topc); + tmps = scaletocmap(tmps,cmn,cmx,actc,topc); + actc = [actc; 0 0 0]; + + % combine gray and blob data to + % truecolour + imgt = reshape(actc(tmpt(:),:)*actp+ ... + gryc(imgt(:),:)*(1-actp), ... + [size(imgt) 3]); + imgc = reshape(actc(tmpc(:),:)*actp+ ... + gryc(imgc(:),:)*(1-actp), ... + [size(imgc) 3]); + imgs = reshape(actc(tmps(:),:)*actp+ ... + gryc(imgs(:),:)*(1-actp), ... + [size(imgs) 3]); + + redraw_colourbar(i,1,[cmn cmx],[1:64]'+64); + + else, + % Add full colour blobs - several sets at once + scal = 1/(mx-mn); + dcoff = -mn*scal; + + wt = zeros(size(imgt)); + wc = zeros(size(imgc)); + ws = zeros(size(imgs)); + + imgt = repmat(imgt*scal+dcoff,[1,1,3]); + imgc = repmat(imgc*scal+dcoff,[1,1,3]); + imgs = repmat(imgs*scal+dcoff,[1,1,3]); + + cimgt = zeros(size(imgt)); + cimgc = zeros(size(imgc)); + cimgs = zeros(size(imgs)); + + for j=1:length(st.vols{i}.blobs), % get colours of all images first + if isfield(st.vols{i}.blobs{j},'colour'), + colour(j,:) = reshape(st.vols{i}.blobs{j}.colour, [1 3]); + else, + colour(j,:) = [1 0 0]; + end; + end; + %colour = colour/max(sum(colour)); + + for j=1:length(st.vols{i}.blobs), + if isfield(st.vols{i}.blobs{j},'max'), + mx = st.vols{i}.blobs{j}.max; + else, + mx = max([eps max(st.vols{i}.blobs{j}.vol(:))]); + st.vols{i}.blobs{j}.max = mx; + end; + if isfield(st.vols{i}.blobs{j},'min'), + mn = st.vols{i}.blobs{j}.min; + else, + mn = min([0 min(st.vols{i}.blobs{j}.vol(:))]); + st.vols{i}.blobs{j}.min = mn; + end; + + vol = st.vols{i}.blobs{j}.vol; + M = st.Space\st.vols{i}.premul*st.vols{i}.blobs{j}.mat; + tmpt = spm_slice_vol(vol,inv(TM0*M),TD,[0 NaN])'; + tmpc = spm_slice_vol(vol,inv(CM0*M),CD,[0 NaN])'; + tmps = spm_slice_vol(vol,inv(SM0*M),SD,[0 NaN])'; + % check min/max of sampled image + % against mn/mx as given in st + tmpt(tmpt(:)mx) = mx; + tmpc(tmpc(:)>mx) = mx; + tmps(tmps(:)>mx) = mx; + tmpt = (tmpt-mn)/(mx-mn); + tmpc = (tmpc-mn)/(mx-mn); + tmps = (tmps-mn)/(mx-mn); + tmpt(~isfinite(tmpt)) = 0; + tmpc(~isfinite(tmpc)) = 0; + tmps(~isfinite(tmps)) = 0; + + cimgt = cimgt + cat(3,tmpt*colour(j,1),tmpt*colour(j,2),tmpt*colour(j,3)); + cimgc = cimgc + cat(3,tmpc*colour(j,1),tmpc*colour(j,2),tmpc*colour(j,3)); + cimgs = cimgs + cat(3,tmps*colour(j,1),tmps*colour(j,2),tmps*colour(j,3)); + + wt = wt + tmpt; + wc = wc + tmpc; + ws = ws + tmps; + cdata=permute(shiftdim([1/64:1/64:1]'* ... + colour(j,:),-1),[2 1 3]); + redraw_colourbar(i,j,[mn mx],cdata); + end; + + imgt = repmat(1-wt,[1 1 3]).*imgt+cimgt; + imgc = repmat(1-wc,[1 1 3]).*imgc+cimgc; + imgs = repmat(1-ws,[1 1 3]).*imgs+cimgs; + + imgt(imgt<0)=0; imgt(imgt>1)=1; + imgc(imgc<0)=0; imgc(imgc>1)=1; + imgs(imgs<0)=0; imgs(imgs>1)=1; + end; + else, + scal = 64/(mx-mn); + dcoff = -mn*scal; + imgt = imgt*scal+dcoff; + imgc = imgc*scal+dcoff; + imgs = imgs*scal+dcoff; + end; + + set(st.vols{i}.ax{1}.d,'HitTest','off', 'Cdata',imgt); + set(st.vols{i}.ax{1}.lx,'HitTest','off',... + 'Xdata',[0 TD(1)]+0.5,'Ydata',[1 1]*(cent(2)-bb(1,2)+1)); + set(st.vols{i}.ax{1}.ly,'HitTest','off',... + 'Ydata',[0 TD(2)]+0.5,'Xdata',[1 1]*(cent(1)-bb(1,1)+1)); + + set(st.vols{i}.ax{2}.d,'HitTest','off', 'Cdata',imgc); + set(st.vols{i}.ax{2}.lx,'HitTest','off',... + 'Xdata',[0 CD(1)]+0.5,'Ydata',[1 1]*(cent(3)-bb(1,3)+1)); + set(st.vols{i}.ax{2}.ly,'HitTest','off',... + 'Ydata',[0 CD(2)]+0.5,'Xdata',[1 1]*(cent(1)-bb(1,1)+1)); + + set(st.vols{i}.ax{3}.d,'HitTest','off','Cdata',imgs); + if st.mode ==0, + set(st.vols{i}.ax{3}.lx,'HitTest','off',... + 'Xdata',[0 SD(1)]+0.5,'Ydata',[1 1]*(cent(2)-bb(1,2)+1)); + set(st.vols{i}.ax{3}.ly,'HitTest','off',... + 'Ydata',[0 SD(2)]+0.5,'Xdata',[1 1]*(cent(3)-bb(1,3)+1)); + else, + set(st.vols{i}.ax{3}.lx,'HitTest','off',... + 'Xdata',[0 SD(1)]+0.5,'Ydata',[1 1]*(cent(3)-bb(1,3)+1)); + set(st.vols{i}.ax{3}.ly,'HitTest','off',... + 'Ydata',[0 SD(2)]+0.5,'Xdata',[1 1]*(bb(2,2)+1-cent(2))); + end; + + if ~isempty(st.plugins) % process any addons + for k = 1:prod(size(st.plugins)) + if isfield(st.vols{i},st.plugins{k}) + feval(['spm_ov_', st.plugins{k}], ... + 'redraw', i, TM0, TD, CM0, CD, SM0, SD); + end; + end; + end; + end; +end; +drawnow; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function redraw_colourbar(vh,bh,interval,cdata) +global st +if isfield(st.vols{vh}.blobs{bh},'cbar') + if st.mode == 0, + axpos = get(st.vols{vh}.ax{2}.ax,'Position'); + else, + axpos = get(st.vols{vh}.ax{1}.ax,'Position'); + end; + % only scale cdata if we have out-of-range truecolour values + if ndims(cdata)==3 && max(cdata(:))>1 + cdata=cdata./max(cdata(:)); + end; + image([0 1],interval,cdata,'Parent',st.vols{vh}.blobs{bh}.cbar); + set(st.vols{vh}.blobs{bh}.cbar, ... + 'Position',[(axpos(1)+axpos(3)+0.05+(bh-1)*.1)... + (axpos(2)+0.005) 0.05 (axpos(4)-0.01)],... + 'YDir','normal','XTickLabel',[],'XTick',[]); +end; +%_______________________________________________________________________ +%_______________________________________________________________________ +function centre = findcent +global st +obj = get(st.fig,'CurrentObject'); +centre = []; +cent = []; +cp = []; +for i=valid_handles(1:24), + for j=1:3, + if ~isempty(obj), + if (st.vols{i}.ax{j}.ax == obj), + cp = get(obj,'CurrentPoint'); + end; + end; + if ~isempty(cp), + cp = cp(1,1:2); + is = inv(st.Space); + cent = is(1:3,1:3)*st.centre(:) + is(1:3,4); + switch j, + case 1, + cent([1 2])=[cp(1)+st.bb(1,1)-1 cp(2)+st.bb(1,2)-1]; + case 2, + cent([1 3])=[cp(1)+st.bb(1,1)-1 cp(2)+st.bb(1,3)-1]; + case 3, + if st.mode ==0, + cent([3 2])=[cp(1)+st.bb(1,3)-1 cp(2)+st.bb(1,2)-1]; + else, + cent([2 3])=[st.bb(2,2)+1-cp(1) cp(2)+st.bb(1,3)-1]; + end; + end; + break; + end; + end; + if ~isempty(cent), break; end; +end; +if ~isempty(cent), centre = st.Space(1:3,1:3)*cent(:) + st.Space(1:3,4); end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function handles = valid_handles(handles) +global st; +handles = handles(:)'; +handles = handles(find(handles<=24 & handles>=1 & ~rem(handles,1))); +for h=handles, + if isempty(st.vols{h}), handles(find(handles==h))=[]; end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function reset_st +global st +fig = spm_figure('FindWin','Graphics'); +bb = []; %[ [-78 78]' [-112 76]' [-50 85]' ]; +st = struct('n', 0, 'vols',[], 'bb',bb,'Space',eye(4),'centre',[0 0 0],'callback',';','xhairs',1,'hld',1,'fig',fig,'mode',1,'plugins',[],'snap',[]); +st.vols = cell(24,1); + +pluginpath = fullfile(spm('Dir'),'spm_orthviews'); +if isdir(pluginpath) + pluginfiles = dir(fullfile(pluginpath,'spm_ov_*.m')); + if ~isempty(pluginfiles) + addpath(pluginpath); + % fprintf('spm_orthviews: Using Plugins in %s\n', pluginpath); + for k = 1:length(pluginfiles) + [p pluginname e] = fileparts(pluginfiles(k).name); + st.plugins{k} = strrep(pluginname, 'spm_ov_',''); + % fprintf('%s\n',st.plugins{k}); + end; + end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function img = scaletocmap(inpimg,mn,mx,cmap,miscol) +if nargin < 5, miscol=1;end +cml = size(cmap,1); +scf = (cml-1)/(mx-mn); +img = round((inpimg-mn)*scf)+1; +img(find(img<1)) = 1; +img(find(img>cml)) = cml; +img(~isfinite(img)) = miscol; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function cmap = getcmap(acmapname) +% get colormap of name acmapname +if ~isempty(acmapname), + cmap = evalin('base',acmapname,'[]'); + if isempty(cmap), % not a matrix, is .mat file? + [p f e] = fileparts(acmapname); + acmat = fullfile(p, [f '.mat']); + if exist(acmat, 'file'), + s = struct2cell(load(acmat)); + cmap = s{1}; + end; + end; +end; +if size(cmap, 2)~=3, + warning('Colormap was not an N by 3 matrix') + cmap = []; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function item_parent = addcontext(volhandle) +global st; +%create context menu +fg = spm_figure('Findwin','Graphics');set(0,'CurrentFigure',fg); +%contextmenu +item_parent = uicontextmenu; + +%contextsubmenu 0 +item00 = uimenu(item_parent, 'Label','unknown image', 'Separator','on'); +spm_orthviews('context_menu','image_info',item00,volhandle); +item0a = uimenu(item_parent, 'UserData','pos_mm', 'Callback','spm_orthviews(''context_menu'',''repos_mm'');','Separator','on'); +item0b = uimenu(item_parent, 'UserData','pos_vx', 'Callback','spm_orthviews(''context_menu'',''repos_vx'');'); +item0c = uimenu(item_parent, 'UserData','v_value'); + +%contextsubmenu 1 +item1 = uimenu(item_parent,'Label','Zoom'); +item1_1 = uimenu(item1, 'Label','Full Volume', 'Callback','spm_orthviews(''context_menu'',''zoom'',6);', 'Checked','on'); +item1_2 = uimenu(item1, 'Label','160x160x160mm', 'Callback','spm_orthviews(''context_menu'',''zoom'',5);'); +item1_3 = uimenu(item1, 'Label','80x80x80mm', 'Callback','spm_orthviews(''context_menu'',''zoom'',4);'); +item1_4 = uimenu(item1, 'Label','40x40x40mm', 'Callback','spm_orthviews(''context_menu'',''zoom'',3);'); +item1_5 = uimenu(item1, 'Label','20x20x20mm', 'Callback','spm_orthviews(''context_menu'',''zoom'',2);'); +item1_6 = uimenu(item1, 'Label','10x10x10mm', 'Callback','spm_orthviews(''context_menu'',''zoom'',1);'); + +%contextsubmenu 2 +checked={'off','off'}; +checked{st.xhairs+1} = 'on'; +item2 = uimenu(item_parent,'Label','Crosshairs'); +item2_1 = uimenu(item2, 'Label','on', 'Callback','spm_orthviews(''context_menu'',''Xhair'',''on'');','Checked',checked{2}); +item2_2 = uimenu(item2, 'Label','off', 'Callback','spm_orthviews(''context_menu'',''Xhair'',''off'');','Checked',checked{1}); + +%contextsubmenu 3 +if st.Space == eye(4) + checked = {'off', 'on'}; +else + checked = {'on', 'off'}; +end; +item3 = uimenu(item_parent,'Label','Orientation'); +item3_1 = uimenu(item3, 'Label','World space', 'Callback','spm_orthviews(''context_menu'',''orientation'',3);','Checked',checked{2}); +item3_2 = uimenu(item3, 'Label','Voxel space (1st image)', 'Callback','spm_orthviews(''context_menu'',''orientation'',2);','Checked',checked{1}); +item3_3 = uimenu(item3, 'Label','Voxel space (this image)', 'Callback','spm_orthviews(''context_menu'',''orientation'',1);','Checked','off'); + +%contextsubmenu 3 +if isempty(st.snap) + checked = {'off', 'on'}; +else + checked = {'on', 'off'}; +end; +item3 = uimenu(item_parent,'Label','Snap to Grid'); +item3_1 = uimenu(item3, 'Label','Don''t snap', 'Callback','spm_orthviews(''context_menu'',''snap'',3);','Checked',checked{2}); +item3_2 = uimenu(item3, 'Label','Snap to 1st image', 'Callback','spm_orthviews(''context_menu'',''snap'',2);','Checked',checked{1}); +item3_3 = uimenu(item3, 'Label','Snap to this image', 'Callback','spm_orthviews(''context_menu'',''snap'',1);','Checked','off'); + +%contextsubmenu 4 +if st.hld == 0, + checked = {'off', 'off', 'on'}; +elseif st.hld > 0, + checked = {'off', 'on', 'off'}; +else, + checked = {'on', 'off', 'off'}; +end; +item4 = uimenu(item_parent,'Label','Interpolation'); +item4_1 = uimenu(item4, 'Label','NN', 'Callback','spm_orthviews(''context_menu'',''interpolation'',3);', 'Checked',checked{3}); +item4_2 = uimenu(item4, 'Label','Bilin', 'Callback','spm_orthviews(''context_menu'',''interpolation'',2);','Checked',checked{2}); +item4_3 = uimenu(item4, 'Label','Sinc', 'Callback','spm_orthviews(''context_menu'',''interpolation'',1);','Checked',checked{1}); + +%contextsubmenu 5 +% item5 = uimenu(item_parent,'Label','Position', 'Callback','spm_orthviews(''context_menu'',''position'');'); + +%contextsubmenu 6 +item6 = uimenu(item_parent,'Label','Image','Separator','on'); +item6_1 = uimenu(item6, 'Label','Window'); +item6_1_1 = uimenu(item6_1, 'Label','local'); +item6_1_1_1 = uimenu(item6_1_1, 'Label','auto', 'Callback','spm_orthviews(''context_menu'',''window'',2);'); +item6_1_1_2 = uimenu(item6_1_1, 'Label','manual', 'Callback','spm_orthviews(''context_menu'',''window'',1);'); +item6_1_2 = uimenu(item6_1, 'Label','global'); +item6_1_2_1 = uimenu(item6_1_2, 'Label','auto', 'Callback','spm_orthviews(''context_menu'',''window_gl'',2);'); +item6_1_2_2 = uimenu(item6_1_2, 'Label','manual', 'Callback','spm_orthviews(''context_menu'',''window_gl'',1);'); +if license('test','image_toolbox') == 1 + offon = {'off', 'on'}; + checked = offon(strcmp(st.vols{volhandle}.mapping, ... + {'linear', 'histeq', 'loghisteq', 'quadhisteq'})+1); + item6_2 = uimenu(item6, 'Label','Intensity mapping'); + item6_2_1 = uimenu(item6_2, 'Label','local'); + item6_2_1_1 = uimenu(item6_2_1, 'Label','Linear', 'Checked',checked{1}, ... + 'Callback','spm_orthviews(''context_menu'',''mapping'',''linear'');'); + item6_2_1_2 = uimenu(item6_2_1, 'Label','Equalised histogram', 'Checked',checked{2}, ... + 'Callback','spm_orthviews(''context_menu'',''mapping'',''histeq'');'); + item6_2_1_3 = uimenu(item6_2_1, 'Label','Equalised log-histogram', 'Checked',checked{3}, ... + 'Callback','spm_orthviews(''context_menu'',''mapping'',''loghisteq'');'); + item6_2_1_4 = uimenu(item6_2_1, 'Label','Equalised squared-histogram', 'Checked',checked{4}, ... + 'Callback','spm_orthviews(''context_menu'',''mapping'',''quadhisteq'');'); + item6_2_2 = uimenu(item6_2, 'Label','global'); + item6_2_2_1 = uimenu(item6_2_2, 'Label','Linear', 'Checked',checked{1}, ... + 'Callback','spm_orthviews(''context_menu'',''mapping_gl'',''linear'');'); + item6_2_2_2 = uimenu(item6_2_2, 'Label','Equalised histogram', 'Checked',checked{2}, ... + 'Callback','spm_orthviews(''context_menu'',''mapping_gl'',''histeq'');'); + item6_2_2_3 = uimenu(item6_2_2, 'Label','Equalised log-histogram', 'Checked',checked{3}, ... + 'Callback','spm_orthviews(''context_menu'',''mapping_gl'',''loghisteq'');'); + item6_2_2_4 = uimenu(item6_2_2, 'Label','Equalised squared-histogram', 'Checked',checked{4}, ... + 'Callback','spm_orthviews(''context_menu'',''mapping_gl'',''quadhisteq'');'); +end; +%contextsubmenu 7 +item7 = uimenu(item_parent,'Label','Blobs'); +item7_1 = uimenu(item7, 'Label','Add blobs'); +item7_1_1 = uimenu(item7_1, 'Label','local', 'Callback','spm_orthviews(''context_menu'',''add_blobs'',2);'); +item7_1_2 = uimenu(item7_1, 'Label','global', 'Callback','spm_orthviews(''context_menu'',''add_blobs'',1);'); +item7_2 = uimenu(item7, 'Label','Add image'); +item7_2_1 = uimenu(item7_2, 'Label','local', 'Callback','spm_orthviews(''context_menu'',''add_image'',2);'); +item7_2_2 = uimenu(item7_2, 'Label','global', 'Callback','spm_orthviews(''context_menu'',''add_image'',1);'); +item7_3 = uimenu(item7, 'Label','Add colored blobs','Separator','on'); +item7_3_1 = uimenu(item7_3, 'Label','local', 'Callback','spm_orthviews(''context_menu'',''add_c_blobs'',2);'); +item7_3_2 = uimenu(item7_3, 'Label','global', 'Callback','spm_orthviews(''context_menu'',''add_c_blobs'',1);'); +item7_4 = uimenu(item7, 'Label','Add colored image'); +item7_4_1 = uimenu(item7_4, 'Label','local', 'Callback','spm_orthviews(''context_menu'',''add_c_image'',2);'); +item7_4_2 = uimenu(item7_4, 'Label','global', 'Callback','spm_orthviews(''context_menu'',''add_c_image'',1);'); +item7_5 = uimenu(item7, 'Label','Remove blobs', 'Visible','off','Separator','on'); +item7_6 = uimenu(item7, 'Label','Remove colored blobs','Visible','off'); +item7_6_1 = uimenu(item7_6, 'Label','local', 'Visible','on'); +item7_6_2 = uimenu(item7_6, 'Label','global','Visible','on'); + +if ~isempty(st.plugins) % process any plugins + for k = 1:prod(size(st.plugins)), + feval(['spm_ov_', st.plugins{k}], ... + 'context_menu', volhandle, item_parent); + end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function c_menu(varargin) +global st + +switch lower(varargin{1}), +case 'image_info', + if nargin <3, + current_handle = get_current_handle; + else + current_handle = varargin{3}; + end; + if isfield(st.vols{current_handle},'fname'), + [p,n,e,v] = spm_fileparts(st.vols{current_handle}.fname); + if isfield(st.vols{current_handle},'n') + v = sprintf(',%d',st.vols{current_handle}.n); + end; + set(varargin{2}, 'Label',[n e v]); + end; + delete(get(varargin{2},'children')); + if exist('p','var') + item1 = uimenu(varargin{2}, 'Label', p); + end; + if isfield(st.vols{current_handle},'descrip'), + item2 = uimenu(varargin{2}, 'Label',... + st.vols{current_handle}.descrip); + end; + dt = st.vols{current_handle}.dt(1); + item3 = uimenu(varargin{2}, 'Label', sprintf('Data type: %s', spm_type(dt))); + str = 'Intensity: varied'; + if size(st.vols{current_handle}.pinfo,2) == 1, + if st.vols{current_handle}.pinfo(2), + str = sprintf('Intensity: Y = %g X + %g',... + st.vols{current_handle}.pinfo(1:2)'); + else, + str = sprintf('Intensity: Y = %g X', st.vols{current_handle}.pinfo(1)'); + end; + end; + item4 = uimenu(varargin{2}, 'Label',str); + item5 = uimenu(varargin{2}, 'Label', 'Image dims', 'Separator','on'); + item51 = uimenu(varargin{2}, 'Label',... + sprintf('%dx%dx%d', st.vols{current_handle}.dim(1:3))); + prms = spm_imatrix(st.vols{current_handle}.mat); + item6 = uimenu(varargin{2}, 'Label','Voxel size', 'Separator','on'); + item61 = uimenu(varargin{2}, 'Label', sprintf('%.2f %.2f %.2f', prms(7:9))); + item7 = uimenu(varargin{2}, 'Label','Origin', 'Separator','on'); + item71 = uimenu(varargin{2}, 'Label',... + sprintf('%.2f %.2f %.2f', prms(1:3))); + R = spm_matrix([0 0 0 prms(4:6)]); + item8 = uimenu(varargin{2}, 'Label','Rotations', 'Separator','on'); + item81 = uimenu(varargin{2}, 'Label', sprintf('%.2f %.2f %.2f', R(1,1:3))); + item82 = uimenu(varargin{2}, 'Label', sprintf('%.2f %.2f %.2f', R(2,1:3))); + item83 = uimenu(varargin{2}, 'Label', sprintf('%.2f %.2f %.2f', R(3,1:3))); + item9 = uimenu(varargin{2},... + 'Label','Specify other image...',... + 'Callback','spm_orthviews(''context_menu'',''swap_img'');',... + 'Separator','on'); + +case 'repos_mm', + oldpos_mm = spm_orthviews('pos'); + newpos_mm = spm_input('New Position (mm)','+1','r',sprintf('%.2f %.2f %.2f',oldpos_mm),3); + spm_orthviews('reposition',newpos_mm); + +case 'repos_vx' + current_handle = get_current_handle; + oldpos_vx = spm_orthviews('pos', current_handle); + newpos_vx = spm_input('New Position (voxels)','+1','r',sprintf('%.2f %.2f %.2f',oldpos_vx),3); + newpos_mm = st.vols{current_handle}.mat*[newpos_vx;1]; + spm_orthviews('reposition',newpos_mm(1:3)); + +case 'zoom' + zoom_all(varargin{2}); + bbox; + redraw_all; + +case 'xhair', + spm_orthviews('Xhairs',varargin{2}); + cm_handles = get_cm_handles; + for i = 1:length(cm_handles), + z_handle = get(findobj(cm_handles(i),'label','Crosshairs'),'Children'); + set(z_handle,'Checked','off'); %reset check + if strcmp(varargin{2},'off'), op = 1; else op = 2; end + set(z_handle(op),'Checked','on'); + end; + +case 'orientation', + cm_handles = get_cm_handles; + for i = 1:length(cm_handles), + z_handle = get(findobj(cm_handles(i),'label','Orientation'),'Children'); + set(z_handle,'Checked','off'); + end; + if varargin{2} == 3, + spm_orthviews('Space'); + elseif varargin{2} == 2, + spm_orthviews('Space',1); + else, + spm_orthviews('Space',get_current_handle); + z_handle = get(findobj(st.vols{get_current_handle}.ax{1}.cm,'label','Orientation'),'Children'); + set(z_handle(1),'Checked','on'); + return; + end; + for i = 1:length(cm_handles), + z_handle = get(findobj(cm_handles(i),'label','Orientation'),'Children'); + set(z_handle(varargin{2}),'Checked','on'); + end; + +case 'snap', + cm_handles = get_cm_handles; + for i = 1:length(cm_handles), + z_handle = get(findobj(cm_handles(i),'label','Snap to Grid'),'Children'); + set(z_handle,'Checked','off'); + end; + if varargin{2} == 3, + st.snap = []; + elseif varargin{2} == 2, + st.snap = 1; + else, + st.snap = get_current_handle; + z_handle = get(findobj(st.vols{get_current_handle}.ax{1}.cm,'label','Snap to Grid'),'Children'); + set(z_handle(1),'Checked','on'); + return; + end; + for i = 1:length(cm_handles), + z_handle = get(findobj(cm_handles(i),'label','Snap to Grid'),'Children'); + set(z_handle(varargin{2}),'Checked','on'); + end; + +case 'interpolation', + tmp = [-4 1 0]; + st.hld = tmp(varargin{2}); + cm_handles = get_cm_handles; + for i = 1:length(cm_handles), + z_handle = get(findobj(cm_handles(i),'label','Interpolation'),'Children'); + set(z_handle,'Checked','off'); + set(z_handle(varargin{2}),'Checked','on'); + end; + redraw_all; + +case 'window', + current_handle = get_current_handle; + if varargin{2} == 2, + spm_orthviews('window',current_handle); + else + if isnumeric(st.vols{current_handle}.window) + defstr = sprintf('%.2f %.2f', st.vols{current_handle}.window); + else + defstr = ''; + end; + spm_orthviews('window',current_handle,spm_input('Range','+1','e',defstr,2)); + end; + +case 'window_gl', + if varargin{2} == 2, + for i = 1:length(get_cm_handles), + st.vols{i}.window = 'auto'; + end; + else, + current_handle = get_current_handle; + if isnumeric(st.vols{current_handle}.window) + defstr = sprintf('%d %d', st.vols{current_handle}.window); + else + defstr = ''; + end; + data = spm_input('Range','+1','e',defstr,2); + + for i = 1:length(get_cm_handles), + st.vols{i}.window = data; + end; + end; + redraw_all; + +case 'mapping', + checked = strcmp(varargin{2}, ... + {'linear', 'histeq', 'loghisteq', ... + 'quadhisteq'}); + checked = checked(end:-1:1); % Handles are stored in inverse order + current_handle = get_current_handle; + cm_handles = get_cm_handles; + st.vols{current_handle}.mapping = varargin{2}; + z_handle = get(findobj(cm_handles(current_handle), ... + 'label','Intensity mapping'),'Children'); + for k = 1:numel(z_handle) + c_handle = get(z_handle(k), 'Children'); + set(c_handle, 'checked', 'off'); + set(c_handle(checked), 'checked', 'on'); + end; + redraw_all; + +case 'mapping_gl', + checked = strcmp(varargin{2}, ... + {'linear', 'histeq', 'loghisteq', 'quadhisteq'}); + checked = checked(end:-1:1); % Handles are stored in inverse order + cm_handles = get_cm_handles; + for k = valid_handles(1:24), + st.vols{k}.mapping = varargin{2}; + z_handle = get(findobj(cm_handles(k), ... + 'label','Intensity mapping'),'Children'); + for l = 1:numel(z_handle) + c_handle = get(z_handle(l), 'Children'); + set(c_handle, 'checked', 'off'); + set(c_handle(checked), 'checked', 'on'); + end; + end; + redraw_all; + +case 'swap_img', + current_handle = get_current_handle; + new_info = spm_vol(spm_select(1,'image','select new image')); + fn = fieldnames(new_info); + for k=1:numel(fn) + st.vols{current_handle}.(fn{k}) = new_info.(fn{k}); + end; + spm_orthviews('context_menu','image_info',get(gcbo, 'parent')); + redraw_all; + +case 'add_blobs', + % Add blobs to the image - in split colortable + cm_handles = valid_handles(1:24); + if varargin{2} == 2, cm_handles = get_current_handle; end; + spm_figure('Clear','Interactive'); + [SPM,VOL] = spm_getSPM; + for i = 1:length(cm_handles), + addblobs(cm_handles(i),VOL.XYZ,VOL.Z,VOL.M); + c_handle = findobj(findobj(st.vols{cm_handles(i)}.ax{1}.cm,'label','Blobs'),'Label','Remove blobs'); + set(c_handle,'Visible','on'); + delete(get(c_handle,'Children')); + item7_3_1 = uimenu(c_handle,'Label','local','Callback','spm_orthviews(''context_menu'',''remove_blobs'',2);'); + if varargin{2} == 1, + item7_3_2 = uimenu(c_handle,'Label','global','Callback','spm_orthviews(''context_menu'',''remove_blobs'',1);'); + end; + end; + redraw_all; + +case 'remove_blobs', + cm_handles = valid_handles(1:24); + if varargin{2} == 2, cm_handles = get_current_handle; end; + for i = 1:length(cm_handles), + rmblobs(cm_handles(i)); + c_handle = findobj(findobj(st.vols{cm_handles(i)}.ax{1}.cm,'label','Blobs'),'Label','Remove blobs'); + delete(get(c_handle,'Children')); + set(c_handle,'Visible','off'); + end; + redraw_all; + +case 'add_image', + % Add blobs to the image - in split colortable + cm_handles = valid_handles(1:24); + if varargin{2} == 2, cm_handles = get_current_handle; end; + spm_figure('Clear','Interactive'); + fname = spm_select(1,'image','select image'); + for i = 1:length(cm_handles), + addimage(cm_handles(i),fname); + c_handle = findobj(findobj(st.vols{cm_handles(i)}.ax{1}.cm,'label','Blobs'),'Label','Remove blobs'); + set(c_handle,'Visible','on'); + delete(get(c_handle,'Children')); + item7_3_1 = uimenu(c_handle,'Label','local','Callback','spm_orthviews(''context_menu'',''remove_blobs'',2);'); + if varargin{2} == 1, + item7_3_2 = uimenu(c_handle,'Label','global','Callback','spm_orthviews(''context_menu'',''remove_blobs'',1);'); + end; + end; + redraw_all; + +case 'add_c_blobs', + % Add blobs to the image - in full colour + cm_handles = valid_handles(1:24); + if varargin{2} == 2, cm_handles = get_current_handle; end; + spm_figure('Clear','Interactive'); + [SPM,VOL] = spm_getSPM; + c = spm_input('Colour','+1','m',... + 'Red blobs|Yellow blobs|Green blobs|Cyan blobs|Blue blobs|Magenta blobs',[1 2 3 4 5 6],1); + colours = [1 0 0;1 1 0;0 1 0;0 1 1;0 0 1;1 0 1]; + c_names = {'red';'yellow';'green';'cyan';'blue';'magenta'}; + hlabel = sprintf('%s (%s)',VOL.title,c_names{c}); + for i = 1:length(cm_handles), + addcolouredblobs(cm_handles(i),VOL.XYZ,VOL.Z,VOL.M,colours(c,:)); + addcolourbar(cm_handles(i),numel(st.vols{cm_handles(i)}.blobs)); + c_handle = findobj(findobj(st.vols{cm_handles(i)}.ax{1}.cm,'label','Blobs'),'Label','Remove colored blobs'); + ch_c_handle = get(c_handle,'Children'); + set(c_handle,'Visible','on'); + %set(ch_c_handle,'Visible',on'); + item7_4_1 = uimenu(ch_c_handle(2),'Label',hlabel,'ForegroundColor',colours(c,:),... + 'Callback','c = get(gcbo,''UserData'');spm_orthviews(''context_menu'',''remove_c_blobs'',2,c);',... + 'UserData',c); + if varargin{2} == 1, + item7_4_2 = uimenu(ch_c_handle(1),'Label',hlabel,'ForegroundColor',colours(c,:),... + 'Callback','c = get(gcbo,''UserData'');spm_orthviews(''context_menu'',''remove_c_blobs'',1,c);',... + 'UserData',c); + end; + end; + redraw_all; + +case 'remove_c_blobs', + cm_handles = valid_handles(1:24); + if varargin{2} == 2, cm_handles = get_current_handle; end; + colours = [1 0 0;1 1 0;0 1 0;0 1 1;0 0 1;1 0 1]; + c_names = {'red';'yellow';'green';'cyan';'blue';'magenta'}; + for i = 1:length(cm_handles), + if isfield(st.vols{cm_handles(i)},'blobs'), + for j = 1:length(st.vols{cm_handles(i)}.blobs), + if st.vols{cm_handles(i)}.blobs{j}.colour == colours(varargin{3},:); + if isfield(st.vols{cm_handles(i)}.blobs{j},'cbar') + delete(st.vols{cm_handles(i)}.blobs{j}.cbar); + end + st.vols{cm_handles(i)}.blobs(j) = []; + break; + end; + end; + rm_c_menu = findobj(st.vols{cm_handles(i)}.ax{1}.cm,'Label','Remove colored blobs'); + delete(findobj(rm_c_menu,'Label',c_names{varargin{3}})); + if isempty(st.vols{cm_handles(i)}.blobs), + st.vols{cm_handles(i)} = rmfield(st.vols{cm_handles(i)},'blobs'); + set(rm_c_menu, 'Visible', 'off'); + end; + end; + end; + redraw_all; + +case 'add_c_image', + % Add truecolored image + cm_handles = valid_handles(1:24); + if varargin{2} == 2, cm_handles = get_current_handle;end; + spm_figure('Clear','Interactive'); + fname = spm_select(1,'image','select image'); + c = spm_input('Colour','+1','m','Red blobs|Yellow blobs|Green blobs|Cyan blobs|Blue blobs|Magenta blobs',[1 2 3 4 5 6],1); + colours = [1 0 0;1 1 0;0 1 0;0 1 1;0 0 1;1 0 1]; + c_names = {'red';'yellow';'green';'cyan';'blue';'magenta'}; + hlabel = sprintf('%s (%s)',fname,c_names{c}); + for i = 1:length(cm_handles), + addcolouredimage(cm_handles(i),fname,colours(c,:)); + addcolourbar(cm_handles(i),numel(st.vols{cm_handles(i)}.blobs)); + c_handle = findobj(findobj(st.vols{cm_handles(i)}.ax{1}.cm,'label','Blobs'),'Label','Remove colored blobs'); + ch_c_handle = get(c_handle,'Children'); + set(c_handle,'Visible','on'); + %set(ch_c_handle,'Visible',on'); + item7_4_1 = uimenu(ch_c_handle(2),'Label',hlabel,'ForegroundColor',colours(c,:),... + 'Callback','c = get(gcbo,''UserData'');spm_orthviews(''context_menu'',''remove_c_blobs'',2,c);','UserData',c); + if varargin{2} == 1 + item7_4_2 = uimenu(ch_c_handle(1),'Label',hlabel,'ForegroundColor',colours(c,:),... + 'Callback','c = get(gcbo,''UserData'');spm_orthviews(''context_menu'',''remove_c_blobs'',1,c);',... + 'UserData',c); + end + end + redraw_all; +end; +%_______________________________________________________________________ +%_______________________________________________________________________ +function current_handle = get_current_handle +global st +cm_handle = get(gca,'UIContextMenu'); +cm_handles = get_cm_handles; +current_handle = find(cm_handles==cm_handle); +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function cm_pos +global st +for i = 1:length(valid_handles(1:24)), + if isfield(st.vols{i}.ax{1},'cm') + set(findobj(st.vols{i}.ax{1}.cm,'UserData','pos_mm'),... + 'Label',sprintf('mm: %.1f %.1f %.1f',spm_orthviews('pos'))); + pos = spm_orthviews('pos',i); + set(findobj(st.vols{i}.ax{1}.cm,'UserData','pos_vx'),... + 'Label',sprintf('vx: %.1f %.1f %.1f',pos)); + set(findobj(st.vols{i}.ax{1}.cm,'UserData','v_value'),... + 'Label',sprintf('Y = %g',spm_sample_vol(st.vols{i},pos(1),pos(2),pos(3),st.hld))); + end +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function cm_handles = get_cm_handles +global st +cm_handles = []; +for i=valid_handles(1:24), + cm_handles = [cm_handles st.vols{i}.ax{1}.cm]; +end +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function zoom_all(op) +global st +cm_handles = get_cm_handles; +res = [.125 .125 .25 .5 1 1]; +if op==6, + st.bb = maxbb; +else, + vx = sqrt(sum(st.Space(1:3,1:3).^2)); + vx = vx.^(-1); + pos = spm_orthviews('pos'); + pos = st.Space\[pos ; 1]; + pos = pos(1:3)'; + if op == 5, st.bb = [pos-80*vx ; pos+80*vx] ; + elseif op == 4, st.bb = [pos-40*vx ; pos+40*vx] ; + elseif op == 3, st.bb = [pos-20*vx ; pos+20*vx] ; + elseif op == 2, st.bb = [pos-10*vx ; pos+10*vx] ; + elseif op == 1; st.bb = [pos- 5*vx ; pos+ 5*vx] ; + else disp('no Zoom possible'); + end; +end +resolution(res(op)); +redraw_all; +for i = 1:length(cm_handles) + z_handle = get(findobj(cm_handles(i),'label','Zoom'),'Children'); + set(z_handle,'Checked','off'); + set(z_handle(op),'Checked','on'); +end +return; + + +function display_slices(imgs, colormaps, ranges) +% FORMAT display_slices(imgs, dispf) +% +% request a some parameters for slice_overlay routine +% while accepting many defaults +% SO structure contains all the parameters for display +% See slice_overlay.m for detailed comments +% +% Defaults to GUI if no arguments passed +% imgs - string or cell array of image names to display +% dispf - flag, if set, displays overlay (default = 1) +% +% Matthew Brett 5/00 V 0.3 +% revised by Xu Cui 1/4/2008 +% +% The first image file has to be a structural image + +if ischar(imgs) + imgs = cellstr(imgs); +end + +clear global SO +global SO + +% load images +nimgs = size(imgs); + +% process names +nchars = 20; +imgns = spm_str_manip(imgs, ['rck' num2str(nchars)]); + +% identify image types +cscale = []; +deftype = 1; +SO.cbar = []; +for i = 1:nimgs + SO.img(i).vol = spm_vol(imgs{i}); + options = {'Structural','Truecolour', ... + 'Blobs','Negative blobs'}; + % if there are SPM results in the workspace, add this option + if evalin('base','exist(''SPM'', ''var'')') + options = {'Structural with SPM blobs', options{:}}; + end +% itype = spm_input(sprintf('Img %d: %s - image type?', i, imgns{i}), '+1', ... +% 'm', char(options),options, deftype); + if i == 1 + itype = {'Structural'}; + else + itype = {'Blobs'}; + end + %imgns(i) = {sprintf('Img %d (%s)',i,itype{1})}; + [mx mn] = slice_overlay('volmaxmin', SO.img(i).vol); + if ~isempty(strmatch('Structural', itype)) + SO.img(i).cmap = gray; + SO.img(i).range = [mn mx]; + deftype = 2; + cscale = [cscale i]; + if strcmp(itype,'Structural with SPM blobs') + errstr = sprintf(['Cannot find SPM/VOL structs in the workspace\n'... + 'Please run SPM results GUI before' ... + ' display_slices']); + SPM = evalin('base', 'SPM', ['error(' errstr ')']); + VOL = evalin('base', 'VOL', ['error(' errstr ')']); + slice_overlay('addspm',SPM,VOL,0); + end + else + SO.cbar = [SO.cbar i]; + cprompt = ['Colormap: ' imgns{i}]; + switch itype{1} + case 'Truecolour' + dcmap = 'actc'; + drange = [mn mx]; + cscale = [cscale i]; + case 'Blobs' + dcmap = 'hot'; + drange = [0 mx]; + SO.img(i).prop = Inf; + case 'Negative blobs' + dcmap = 'winter'; + drange = [0 mn]; + SO.img(i).prop = Inf; + end + if isempty(colormaps{i}) + SO.img(i).cmap = return_cmap(cprompt, dcmap); + else + SO.img(i).cmap = colormaps{i};%return_cmap(cprompt, dcmap); + end + SO.img(i).range = ranges{i};%spm_input('Img val range for colormap','+1', 'e', drange, 2); + end +end +ncmaps=length(cscale); +if ncmaps == 1 + SO.img(cscale).prop = 1; +else + remcol=1; + for i = 1:ncmaps + ino = cscale(i); + SO.img(ino).prop = spm_input(sprintf('%s intensity',imgns{ino}),... + '+1', 'e', ... + remcol/(ncmaps-i+1),1); + remcol = remcol - SO.img(ino).prop; + end +end +% +% SO.transform = deblank(spm_input('Image orientation', '+1', ['Axial|' ... +% ' Coronal|Sagittal'], strvcat('axial','coronal','sagittal'), ... +% 1)); +SO.transform = deblank(questdlg('Image orientation?', ... + 'Image orientation', ... + 'Axial', 'Coronal', 'Sagittal', 'Axial')); + +% use SPM figure window +SO.figure = spm_figure('GetWin', 'Graphics'); + +% slices for display +slice_overlay('checkso'); +SO.slices = eval(sprintf('%0.0f:%0.0f:%0.0f',... + SO.slices(1),... + mean(diff(SO.slices)),... + SO.slices(end))); + +% and do the display + +slice_overlay + +return + +function cmap = return_cmap(prompt,defmapn) + +prompt={prompt}; +name='Input color'; +numlines=1; +defaultanswer={defmapn}; + +answer=inputdlg(prompt,name,numlines,defaultanswer); +answer = answer{1}; + +cmap = []; +while isempty(cmap) + cmap = slice_overlay('getcmap', answer); +end +return + +% slice_overlay() - Function to display + manage slice display +% +% Usage: +% >> slice_overlay(action, varargin); +% +% Inputs: +% +% Outputs: +% +% Author: Matthew Brett matthew@mrc-cbu.cam.ac.uk +% +% See also: show_2d(), show_3d() + +% $Log: slice_overlay.m,v $ +% Revision 1.1 2003/02/06 19:17:48 duann +% Initial revision +% + +function varargout = slice_overlay(action, varargin); +% Function to display + manage slice display +% Slice display works on a global structure SO +% with fields +% - img - array of images to display +% - img structs contain fields +% vol - vol struct info (see spm_vol) +% can also be vol containing image as 3d matrix +% set with slice_overlay('AddBlobs'...) call +% cmap - colormap for this image +% nancol - color for NaN. If scalar, this is an index into +% the image cmap. If 1x3 vector, it's a colour +% prop - proportion of intensity for this cmap/img +% if = Inf, gives split cmap effect where values of +% this cmap override previous image cmap values +% func - function to apply to image before scaling to cmap +% (and therefore before min/max thresholding. E.g. a func of +% 'i1(i1==0)=NaN' would convert zeros to NaNs +% range - 2x1 vector of values for image to distribute colormap across +% the first row of the colormap applies to the first +% value in 'range', and the last value to the second +% value in 'range' +% outofrange - behavior for image values to the left and +% right of image limits in 'range'. Left means +% colormap values < 1, i.e for image values < +% range(1), if (range(1) +% range(1) where (range(1)>range(2)). If missing, +% display min (for Left) and max (for Right) value from colormap. +% Otherwise should be a 2 element cell array, where +% the first element is the colour value for image values +% left of 'range', and the second is for image values +% right of 'range'. Scalar values for +% colour index the colormap, 3x1 vectors are colour +% values. An empty array attracts default settings +% appropriate to the mode - i.e. transparent colour (where +% SO.prop ~= Inf), or split colour. Empty cells +% default to 0. 0 specifies that voxels with this +% colour do not influence the image (split = +% background, true = black) +% hold - resampling order for image (see spm_sample_vol) - +% default 1 +% background - value when resampling outside image - default +% NaN +% +% - transform - either - 4x4 transformation to apply to image slice position, +% relative to mm given by slicedef, before display +% or - text string, one of axial, coronal, sagittal +% These orientations assume the image is currently +% (after its mat file has been applied) axially +% oriented +% - slicedef - 2x3 array specifying dimensions for slice images in mm +% where rows are x,and y of slice image, and cols are neg max dim, +% slice separation and pos max dim +% - slices - vector of slice positions in mm in z (of transformed image) +% - figure - figure handle for slice display figure +% - refreshf - flag - if set or empty, refresh axis info for figure +% else assume this is OK +% - clf - flag, non zero -> clear figure before display. Redundant +% if refreshf == 0 +% - area struct with fields +% position - bottom left, x size y size 1x4 vector of +% area in which to display slices +% units - one of +% inches,centimeters,normalized,points,{pixels} +% halign - one of left,{center},right +% valign - one of top,{middle},bottom +% - xslices - no of slices to display across figure (defaults to an optimum) +% - cbar - if empty, missing, no colourbar. If an array of integers, then +% indexes img array, and makes colourbar for each cmap for +% that img. Cbars specified in order of appearance L->R +% - labels - struct can be absent (-> default numerical labels) +% empty (SO.labels = []) (no labels) or contain fields +% colour - colour for label text +% size - font size in units normalized to slice axes +% format - if = cell array of strings = +% labels for each slice in Z. If is string, specifies +% sprintf format string for labelling in distance of the +% origin (Xmm=0, Ymm=0) of each slice from plane containing +% the AC, in mm, in the space of the transformed image +% - callback - callback string for button down on image panels. E.g. +% setting SO.callback to 'slice_overlay(''getpos'')' prints to +% the matlab window the equivalent position in mm of the +% position of a mouse click on one of the image slices +% - printstr - string for printing slice overlay figure window, e.g. +% 'print -dpsc -painters -noui' (the default) +% - printfile - name of file to print output to; default 'slices.ps' +% +% FORMAT slice_overlay +% Checks, fills SO struct (slice_overlay('checkso')), and +% displays slice overlay (slice_overlay('display')) +% +% FORMAT slice_overlay('checkso') +% Checks SO structure and sets defaults +% +% FORMAT cmap = slice_overlay('getcmap',cmapname) +% Gets colormap named in cmapname string +% +% FORMAT [mx mn] = slice_overlay('volmaxmin', vol) +% Returns maximum and minimum finite values from vol struct 'vol' +% +% FORMAT slice_overlay('addspm',SPM,VOL,dispf) +% Adds SPM blobs as new img to SO struct, split effect, 'hot' colormap, +% Structures SPM and VOL are generated by calls to SPM results +% if not passed, they are fetched from the workspace +% If dispf is not passed, or nonzero, displays resulting SO figure also +% +% FORMAT slice_overlay('addblobs', imgno, XYZ, vals, mat) +% adds SPM blobs to img no 'imgno', as specified in +% XYZ - 3xN voxel coordinates of N blob values +% vals - N blob intensity values +% mat - 4x4 matrix specifying voxels -> mm +% +% FORMAT vol = slice_overlay('blobs2vol', XYZ, vals, mat) +% returns (pseudo) vol struct for 3d blob volume specified +% in matrices as above +% +% FORMAT slice_overlay('addmatrix', imgno, mat3d, mat) +% adds 3d matrix image vol to img imgno. Optionally +% mat - 4x4 matrix specifying voxels -> mm +% +% FORMAT vol = slice_overlay('matrix2vol', mat3d, mat) +% returns (pseudo) vol struct for 3d matrix +% input matrices as above +% +% FORMAT mmpos = slice_overlay('getpos') +% returns equivalent position in mm of last click on current axes (gca) +% if the axes contain an image slice (empty otherwise) +% +% FORMAT vals = slice_overlay('pointvals', XYZmm, holdlist) +% returns IxN matrix with values of each image 1..I, at each +% point 1..N specified in 3xN mm coordinate matrix XYZmm +% If specified, 'holdlist' contains I values giving hold +% values for resampling for each image (see spm_sample_vol) +% +% FORMAT slice_overlay('display') +% Displays slice overlay from SO struct +% +% FORMAT slice_overlay('print', filename, printstr) +% Prints slice overlay figure, usually to file. If filename is not +% passed/empty gets filename from SO.printfile. If printstr is not +% passed/empty gets printstr from SO.printstr +% +% V 0.8 2/8/00 +% More or less beta - take care. Please report problems to +% Matthew Brett matthew@mrc-cbu.cam.ac.uk + +global SO + +if nargin < 1 + checkso; + action = 'display'; +else + action = lower(action); +end + +switch action + case 'checkso' + checkso; + case 'getcmap' + varargout = {getcmap(varargin{1})}; + case 'volmaxmin' + [mx mn] = volmaxmin(varargin{1}); + varargout = {mx, mn}; + case 'addspm' + if nargin < 2 + varargin(1) = {evalin('base', 'SPM', ['error(''Cannot find SPM' ... + ' struct'')'])}; + end + if nargin < 3 + varargin(2) = {evalin('base', 'VOL', ['error(''Cannot find VOL' ... + ' struct'')'])}; + end + if nargin < 4 + varargin{3} = 1; + end + newimg = length(SO.img)+1; + SO.img(newimg).vol = blobs2vol(varargin{1}.XYZ,varargin{1}.Z, varargin{2}.M); + SO.img(newimg).prop = Inf; + SO.img(newimg).cmap = hot; + SO.img(newimg).range = [0 max(varargin{1}.Z)]; + SO.cbar = [SO.cbar newimg]; + if varargin{3} + checkso; + slice_overlay('display'); + end + + case 'addblobs' + addblobs(varargin{1},varargin{2},varargin{3},varargin{4}); + case 'blobs2vol' + varargout = {blobs2vol(varargin{1},varargin{2},varargin{3})}; + case 'addmatrix' + if nargin<3,varargin{2}='';end + if nargin<4,varargin{3}='';end + addmatrix(varargin{1},varargin{2},varargin{3}); + case 'matrix2vol' + if nargin<3,varargin{2}=[];end + varargout = {matrix2vol(varargin{1},varargin{2})}; + case 'getpos' + varargout = {getpos}; + case 'pointvals' + varargout = {pointvals(varargin{1})}; + case 'print' + if nargin<2,varargin{1}='';end + if nargin<3,varargin{2}='';end + printfig(varargin{1}, varargin{2}); + case 'display' + +% get coordinates for plane +X=1;Y=2;Z=3; +dims = SO.slicedef; +xmm = dims(X,1):dims(X,2):dims(X,3); +ymm = dims(Y,1):dims(Y,2):dims(Y,3); +zmm = SO.slices; +[y x] = meshgrid(ymm,xmm'); +vdims = [length(xmm),length(ymm),length(zmm)]; + +% no of slices, and panels (an extra for colorbars) +nslices = vdims(Z); +minnpanels = nslices; +cbars = 0; +if is_there(SO,'cbar') + cbars = length(SO.cbar); + minnpanels = minnpanels+cbars; +end + +% get figure data +% if written to, the axes may be specified already +figno = figure(SO.figure); + +% (re)initialize axes and stuff + +% check if the figure is set up correctly +if ~SO.refreshf + axisd = flipud(findobj(SO.figure, 'Type','axes','Tag', 'slice overlay panel')); + npanels = length(axisd); + if npanels < vdims(Z)+cbars; + SO.refreshf = 1; + end +end +if SO.refreshf + % clear figure, axis store + if SO.clf, clf; end + axisd = []; + + % prevent print inversion problems + set(figno,'InvertHardCopy','off'); + + % calculate area of display in pixels + parea = SO.area.position; + if ~strcmp(SO.area.units, 'pixels') + ubu = get(SO.figure, 'units'); + set(SO.figure, 'units','pixels'); + tmp = get(SO.figure, 'Position'); + ascf = tmp(3:4); + if ~strcmp(SO.area.units, 'normalized') + set(SO.figure, 'units',SO.area.units); + tmp = get(SO.figure, 'Position'); + ascf = ascf ./ tmp(3:4); + end + set(figno, 'Units', ubu); + parea = parea .* repmat(ascf, 1, 2); + end + asz = parea(3:4); + + % by default, make most parsimonious fit to figure + yxratio = length(ymm)*dims(Y,2)/(length(xmm)*dims(X,2)); + if ~is_there(SO, 'xslices') + % iteration needed to optimize, surprisingly. Thanks to Ian NS + axlen(X,:)=asz(1):-1:1; + axlen(Y,:)=yxratio*axlen(X,:); + panels = floor(asz'*ones(1,size(axlen,2))./axlen); + estnpanels = prod(panels); + tmp = find(estnpanels >= minnpanels); + if isempty(tmp) + error('Whoops, cannot fit panels onto figure'); + end + b = tmp(1); % best fitting scaling + panels = panels(:,b); + axlen = axlen(:, b); + else + % if xslices is specified, assume X is flush with X figure dimensions + panels([X:Y],1) = [SO.xslices; 0]; + axlen([X:Y],1) = [asz(X)/panels(X); 0]; + end + + % Axis dimensions are in pixels. This prevents aspect ratio rescaling + panels(Y) = ceil(minnpanels/panels(X)); + axlen(Y) = axlen(X)*yxratio; + + % centre (etc) panels in display area as required + divs = [Inf 2 1];the_ds = [0;0]; + the_ds(X) = divs(strcmp(SO.area.halign, {'left','center','right'})); + the_ds(Y) = divs(strcmp(SO.area.valign, {'bottom','middle','top'})); + startc = parea(1:2)' + (asz'-(axlen.*panels))./the_ds; + + % make axes for panels + r=0;c=1; + npanels = prod(panels); + lastempty = npanels-cbars; + for i = 1:npanels + % panel userdata + if i<=nslices + u.type = 'slice'; + u.no = zmm(i); + elseif i > lastempty + u.type = 'cbar'; + u.no = i - lastempty; + else + u.type = 'empty'; + u.no = i - nslices; + end + axpos = [r*axlen(X)+startc(X) (panels(Y)-c)*axlen(Y)+startc(Y) axlen']; + axisd(i) = axes(... + 'Parent',figno,... + 'XTick',[],... + 'XTickLabel',[],... + 'YTick',[],... + 'YTickLabel',[],... + 'Box','on',... + 'XLim',[1 vdims(X)],... + 'YLim',[1 vdims(Y)],... + 'Units', 'pixels',... + 'Position',axpos,... + 'Tag','slice overlay panel',... + 'UserData',u); + r = r+1; + if r >= panels(X) + r = 0; + c = c+1; + end + end +end + +% sort out labels +if is_there(SO,'labels') + labels = SO.labels; + if iscell(labels.format) + if length(labels.format)~=vdims(Z) + error(... + sprintf('Oh dear, expecting %d labels, but found %d',... + vdims(Z), length(labels.contents))); + end + else + % format string for mm from AC labelling + fstr = labels.format; + labels.format = cell(vdims(Z),1); + acpt = SO.transform * [0 0 0 1]'; + for i = 1:vdims(Z) + labels.format(i) = {sprintf(fstr,zmm(i)-acpt(Z))}; + end + end +end + +% modify colormaps with any new colours +nimgs = length(SO.img); +lrn = zeros(nimgs,3); +cmaps = cell(nimgs); +for i = 1:nimgs + cmaps(i)={SO.img(i).cmap}; + lrnv = {SO.img(i).outofrange{:}, SO.img(i).nancol}; + for j = 1:length(lrnv) + if prod(size(lrnv{j}))==1 + lrn(i,j) = lrnv{j}; + else + cmaps(i) = {[cmaps{i}; lrnv{j}(1:3)]}; + lrn(i,j) = size(cmaps{i},1); + end + end +end + +% cycle through slices displaying images +nvox = prod(vdims(1:2)); +pandims = [vdims([2 1]) 3]; % NB XY transpose for display + +zimg = zeros(pandims); +for i = 1:nslices + ixyzmm = [x(:)';y(:)';ones(1,nvox)*zmm(i);ones(1,nvox)]; + img = zimg; + for j = 1:nimgs + thisimg = SO.img(j); + % to voxel space of image + vixyz = inv(SO.transform*thisimg.vol.mat)*ixyzmm; + % raw data + if is_there(thisimg.vol, 'imgdata') + V = thisimg.vol.imgdata; + else + V = thisimg.vol; + end + i1 = spm_sample_vol(V,vixyz(X,:),vixyz(Y,:),vixyz(Z,:), ... + [thisimg.hold thisimg.background]); + if is_there(thisimg, 'func') + eval(thisimg.func); + end + % transpose to reverse X and Y for figure + i1 = reshape(i1, vdims(1:2))'; + % rescale to colormap + [csdata badvals]= scaletocmap(... + i1,... + thisimg.range(1),... + thisimg.range(2),... + cmaps{j},... + lrn(j,:)); + % take indices from colormap to make true colour image + iimg = reshape(cmaps{j}(csdata(:),:),pandims); + tmp = repmat(logical(~badvals),[1 1 3]); + if thisimg.prop ~= Inf % truecolor overlay + img(tmp) = img(tmp) + iimg(tmp)*thisimg.prop; + else % split colormap effect + img(tmp) = iimg(tmp); + end + end + % threshold out of range values + img(img>1) = 1; + + image('Parent', axisd(i),... + 'ButtonDownFcn', SO.callback,... + 'CData',img); + if is_there(SO,'labels') + text('Parent',axisd(i),... + 'Color', labels.colour,... + 'FontUnits', 'normalized',... + 'VerticalAlignment','bottom',... + 'HorizontalAlignment','left',... + 'Position', [1 1],... + 'FontSize',labels.size,... + 'ButtonDownFcn', SO.callback,... + 'String', labels.format{i}); + end +end +for i = (nslices+1):npanels + set(axisd(i),'Color',[0 0 0]); +end +% add colorbar(s) +for i = 1:cbars + axno = axisd(end-cbars+i); + cbari = SO.img(SO.cbar(i)); + cml = size(cbari.cmap,1); + p = get(axno, 'Position');; % position of last axis + cw = p(3)*0.2; + ch = p(4)*0.75; + pc = p(3:4)/2; + [axlims idxs] = sort(cbari.range); + a=axes(... + 'Parent',figno,... + 'XTick',[],... + 'XTickLabel',[],... + 'Units', 'pixels',... + 'YLim', axlims,... + 'FontUnits', 'normalized',... + 'FontSize', 0.075,... + 'YColor',[1 1 1],... + 'Tag', 'cbar',... + 'Box', 'off',... + 'Position',[p(1)+pc(1)-cw/2,p(2)+pc(2)-ch/2,cw,ch]... + ); + ih = image('Parent', a,... + 'YData', axlims(idxs),... + 'CData', reshape(cbari.cmap,[cml,1,3])); + +end + + otherwise + error(sprintf('Unrecognized action string %s', action)); + +% end switch action +end + +return + +function checkso +% checks and fills SO structure +global SO + +% figure +if is_there(SO, 'figure') + try + if ~strcmp(get(SO.figure,'Type'),'figure') + error('Figure handle is not a figure') + end + catch + error('Figure handle is not a valid figure') + end +else + % no figure handle. Try spm figure, then gcf + SO.figure = spm_figure('FindWin', 'Graphics'); + if isempty(SO.figure) + SO.figure = gcf; + end +end +% set defaults for SPM figure +if strcmp(get(SO.figure, 'Tag'),'Graphics') + % position figure nicely for SPM + defstruct = struct('position', [0 0 1 0.92], 'units', 'normalized', ... + 'valign', 'top'); + SO = set_def(SO, 'area', defstruct); + SO.area = set_def(SO.area, 'position', defstruct.position); + SO.area = set_def(SO.area, 'units', defstruct.units); + SO.area = set_def(SO.area, 'valign', defstruct.valign); +end +SO = set_def(SO, 'clf', 1); + +% orientation; string or 4x4 matrix +orientn = []; +SO = set_def(SO, 'transform', 'axial'); +if ischar(SO.transform) + orientn = find(strcmpi(SO.transform, {'axial','coronal','sagittal'})); + if isempty(orientn) + error(sprintf('Unexpected orientation %s', SO.transform)); + end + ts = [0 0 0 0 0 0 1 1 1;... + 0 0 0 pi/2 0 0 1 -1 1;... + 0 0 0 pi/2 0 -pi/2 -1 1 1]; + SO.transform = spm_matrix(ts(orientn,:)); +end +% default slice size, slice matrix depends on orientation +if ~is_there(SO,'slicedef' | ~is_there(SO, 'slices')) + % take image sizes from first image + V = SO.img(1).vol; + D = V.dim(1:3); + T = SO.transform * V.mat; + vcorners = [1 1 1; D(1) 1 1; 1 D(2) 1; D(1:2) 1; ... + 1 1 D(3); D(1) 1 D(3); 1 D(2:3) ; D(1:3)]'; + corners = T * [vcorners; ones(1,8)]; + SC = sort(corners'); + vxsz = sqrt(sum(T(1:3,1:3).^2)); + + SO = set_def(SO, 'slicedef',... + [SC(1,1) vxsz(1) SC(8,1);SC(1,2) vxsz(2) SC(8,2)]); + SO = set_def(SO, 'slices',[SC(1,3):vxsz(3):SC(8,3)]); +end + +% no colourbars by default +SO = set_def(SO, 'cbars', []); + +% always refresh figure window, by default +SO = set_def(SO, 'refreshf', 1); + +% labels +defstruct = struct('colour',[1 1 1],'size',0.075,'format', '%+3.0f'); +if ~isfield(SO, 'labels') % no field, -> default + SO.labels = defstruct; +elseif ~isempty(SO.labels) % empty -> no labels + % colour for slice labels + SO.labels = set_def(SO.labels, 'colour', defstruct.colour); + % font size normalized to image axis + SO.labels = set_def(SO.labels, 'size', defstruct.size); + % format string for slice labels + SO.labels = set_def(SO.labels, 'format', defstruct.format); +end + +% callback +SO = set_def(SO, 'callback', ';'); + +% figure area stuff +defarea = struct('position',[0 0 1 1],'units','normalized'); +SO = set_def(SO, 'area', defarea); +if ~is_there(SO.area, 'position') + SO.area = defarea; +end +if ~is_there(SO.area,'units') + if (all(SO.area.position>=0 & SO.area.position<=1)) + SO.area.units = 'normalized'; + else + SO.area.units = 'pixels'; + end +end +SO.area = set_def(SO.area,'halign', 'center'); +SO.area = set_def(SO.area,'valign', 'middle'); + +% printing +SO = set_def(SO, 'printstr', 'print -dpsc -painters -noui'); +SO = set_def(SO, 'printfile', 'slices.ps'); + +% fill various img arguments +% would be nice to use set_def, but we can't + +% set colour intensities as we go +remcol = 1; +for i = 1:length(SO.img) + if ~is_there(SO.img(i),'hold') + if ~is_there(SO.img(i).vol,'imgdata') + % normal file vol struct + SO.img(i).hold = 1; + else + % 3d matrix vol struct + SO.img(i).hold = 0; + end + end + if ~is_there(SO.img(i),'background') + SO.img(i).background = NaN; + end + if ~is_there(SO.img(i),'prop') + % default is true colour + SO.img(i).prop = remcol/(length(SO.img)-i+1); + remcol = remcol - SO.img(i).prop; + end + if ~is_there(SO.img(i),'range') + [mx mn] = volmaxmin(SO.img(i).vol); + SO.img(i).range = [mn mx]; + end + if ~is_there(SO.img(i),'cmap') + if SO.img(i).prop == Inf; % split map + if SO.range(1) 1 +% img(img>=0 & img<=1)=1; +% else +% img = img*(cml-1)+1; +% end +% outvals = {img<1, img>cml, isnan(img)}; +% img= round(img); +% badvals = zeros(size(img)); +% for i = 1:length(lrn) +% if lrn(i) +% img(outvals{i}) = lrn(i); +% else +% badvals = badvals | outvals{i}; +% img(outvals{i}) = 1; +% end +% end +% return + +function st = set_def(st, fld, def) +if ~is_there(st, fld) + st = setfield(st, fld, def); +end +return + +% function addblobs(imgno, xyz,vals,mat) +% global SO +% if isempty(imgno) +% imgno = length(SO.img); +% end +% if ~isempty(xyz) +% SO.img(imgno).vol = blobs2vol(xyz,vals,mat); +% end + +function vol = blobs2vol(xyz,vals,mat) +vol = []; +if ~isempty(xyz), + rcp = round(xyz); + vol.dim = max(rcp,[],2)'; + off = rcp(1,:) + vol.dim(1)*(rcp(2,:)-1+vol.dim(2)*(rcp(3,:)-1)); + vol.imgdata = zeros(vol.dim)+NaN; + vol.imgdata(off) = vals; + vol.imgdata = reshape(vol.imgdata,vol.dim); + vol.mat = mat; +end +return + +function addmatrix(imgno,mat3d,mat) +global SO +if isempty(imgno) + imgno = length(SO.img); +end +if nargin<3 + mat = []; +end +if ~isempty(mat3d) + SO.img(imgno).vol = matrix2vol(mat3d,mat); +end + +function vol = matrix2vol(mat3d,mat) +if nargin < 2 + mat = spm_matrix([]); +end +if isempty(mat) + mat = spm_matrix([]); +end +vol = []; +if ~isempty(mat3d) + vol.imgdata = mat3d; + vol.mat = mat; + vol.dim = size(mat3d); +end +return + +function [mx,mn] = volmaxmin(vol) +if is_there(vol, 'imgdata') + tmp = vol.imgdata(isfinite(vol.imgdata)); + mx = max(tmp); + mn = min(tmp); +else + mx = -Inf;mn=Inf; + for i=1:vol.dim(3), + tmp = spm_slice_vol(vol,spm_matrix([0 0 i]),vol.dim(1:2),[0 NaN]); + tmp = tmp(find(isfinite(tmp(:)))); + if ~isempty(tmp) + mx = max([mx; tmp]); + mn = min([mn; tmp]); + end + end +end +return + +% function cmap = getcmap(acmapname) +% % get colormap of name acmapname +% if ~isempty(acmapname) +% cmap = evalin('base',acmapname,'[]'); +% if isempty(cmap) % not a matrix, is it... +% % a colour name? +% tmp = strcmp(acmapname, {'red','green','blue'}); +% if any(tmp) +% cmap = zeros(64,3); +% cmap(:,tmp) = ((0:63)/63)'; +% else +% % a .mat file? +% [p f e] = fileparts(acmapname); +% acmat = fullfile(p, [f '.mat']); +% if exist(acmat, 'file') +% s = struct2cell(load(acmat)); +% cmap = s{1}; +% end +% end +% end +% end +% if size(cmap, 2)~=3 +% warning('Colormap was not an N by 3 matrix') +% cmap = []; +% end +% return + +function mmpos = getpos +% returns point location from last click, in mm +global SO +mmpos=[]; +pos = get(gca, 'CurrentPoint'); +u = get(gca, 'UserData'); +if is_there(u, 'type') + if strcmp(u.type, 'slice') % is slice panel + mmpos = (pos(1,1:2)'-1).*SO.slicedef(:,2)+SO.slicedef(:,1); + mmpos = inv(SO.transform) * [mmpos; u.no; 1]; + mmpos = mmpos(1:3,1); + end +end +return + +function vals = pointvals(XYZmm, holdlist) +% returns values from all the images at points given in XYZmm +global SO +if nargin < 2 + holdlist = [SO.img(:).hold]; +end +X=1;Y=2;Z=3; +nimgs = length(SO.img); +nvals = size(XYZmm,2); +vals = zeros(nimgs,nvals)+NaN; +if size(XYZmm,1)~=4 + XYZmm = [XYZmm(X:Z,:); ones(1,nvals)]; +end +for i = 1:nimgs + I = SO.img(i); + XYZ = I.vol.mat\XYZmm; + if ~is_there(I.vol, 'imgdata') + vol = I.vol; + else + vol = I.vol.imgdata; + end + vals(i,:) = spm_sample_vol(vol, XYZ(X,:), XYZ(Y,:),XYZ(Z,:),[holdlist(i) ... + I.background]); +end +return + +function printfig(filename,printstr) +% print slice overlay figure +% based on spm_figure print, and including fix from thence for ps printing +global SO; +if nargin < 1 + filename = []; +end +if isempty(filename) + filename = SO.printfile; +end +if nargin < 2 + printstr = ''; +end +if isempty(printstr) + printstr = SO.printstr; +end + +%-Note current figure, & switch to figure to print +cF = get(0,'CurrentFigure'); +set(0,'CurrentFigure',SO.figure) + +%-Temporarily change all units to normalized prior to printing +% (Fixes bizzarre problem with stuff jumping around!) +%----------------------------------------------------------------------- +H = findobj(get(SO.figure,'Children'),'flat','Type','axes'); +un = cellstr(get(H,'Units')); +set(H,'Units','normalized') + +%-Print +%----------------------------------------------------------------------- +err = 0; +try, eval([printstr ' ' filename]), catch, err=1; end +if err + errstr = lasterr; + tmp = [find(abs(errstr)==10),length(errstr)+1]; + str = {errstr(1:tmp(1)-1)}; + for i = 1:length(tmp)-1 + if tmp(i)+1 < tmp(i+1) + str = [str, {errstr(tmp(i)+1:tmp(i+1)-1)}]; + end + end + str = {str{:}, '','- print command is:',[' ',printstr ' ' filename],... + '','- current directory is:',[' ',pwd],... + '',' * nothing has been printed *'}; + for i=1:length(str) + disp(str{i});end +end + +set(H,{'Units'},un) +set(0,'CurrentFigure',cF) + +return \ No newline at end of file diff --git a/xjview_labnic.m b/xjview_labnic.m new file mode 100644 index 0000000..81aaa27 --- /dev/null +++ b/xjview_labnic.m @@ -0,0 +1,8677 @@ +function xjview(varargin) +% xjview, version 4 +% +% usage 1: xjview (no argument) +% for displaying a result img file, or multiple image files, +% (which will be loaded later) and changing p-value or t/f-value +% usage 2: xjview(imagefilename) +% for displaying the result img file and changing p-value or +% t-value +% Example: xjView spmT_0002.img +% xjView('spmT_0002.img') +% xjView mymask.img +% usage 3: xjview(imagefilename1, imagefilename2, ...) +% for displaying the result img files and changing p-value or +% t/f-value +% Example: xjView spmT_0002.img spmT_0005.img spmT_0007.img +% xjView('spmT_0002.img', 'spmT_0003.img', 'spmT_0006.img') +% xjView myMask1.img myMask2.img myMask3.img +% usage 4: xjview(mnicoord, intensity) +% for displaying where are the mni coordinates +% mnicoord: Nx3 matrix of mni coordinates +% intensity: (optional) Nx1 matrix, usually t values of the +% corresponding voxels +% Example: xjView([20 10 1; -10 2 5],[1;2]) +% xjView([20 10 1; -10 2 5]) +% Note: to use xjview this way, you may need to modify the value +% of M and DIM in the begining of xjview.m +% +% http://people.hnl.bcm.tmc.edu/cuixu/xjView +% +% by Xu Cui and Jian Li 2/21/2005 +% last modified: 02/18/2007 (add colorbar max control) +% last modified: 11/16/2006 (keyboard shortcut for open image and open roi file) +% last modified: 06/16/2006 (spm5 compatible) +% last modified: 05/30/2006 (left/right flip, path of mask.img and templateFile.img) +% last modified: 05/08/2006 (debug CallBack_volumePush function, change handles.intensity{1} to intensity) +% last modified: 04/03/2006 (modify tr) +% last modified: 12/28/2005 (modify SPM process) +% +% Thank Sergey Pakhomov for sharing his database (MNI Space Utility). +% Thank Yuval Cohen for the maximize figure function (maximize.m) +% + +% TODO +% Send SPM to workspace + +warnstate = warning; +%warning off; +clc + +% pre-set values +% important! you need compare the display of xjview and spm. If you find +% xjview flipped the left/right, you need to set leftrightflip = 1; +% otherwise leave it to 0. +leftrightflip = 0; +% leftrightflip = 1 ; %must be on in the CMU... +% Now with spm-flip = 1; xjview-flip=1 + +% You only need to change M and DIM when you want to use xjview under +% 'usage 4'. +M = ... + [-4 0 0 84;... + 0 4 0 -116;... + 0 0 4 -56;... + 0 0 0 1]; +DIM = [41 48 35]'; +TR = 2; % you don't need to set TR is you only use the viewing part of xjview + +% system settings +try + spmdir = spm('dir'); + spm('defaults', 'fmri'); +catch + disp('Please add spm path.'); + warning(warnstate(1).state); + return +end + +if ispc + os = 'windows'; +elseif isunix + os = 'linux'; +else + warndlg('I don''t know what kind of computer you are using. I assumed it is unix.', 'What computer are you using?'); + os = 'linux'; +end +screenResolution = get(0,'ScreenSize'); + +xjviewpath = fileparts(which('xjview')); + +% pre-set values +pValue = 0.001; +intensityThreshold = 0; +clusterSizeThreshold = 5; + + +% Appearance Settings +figurePosition = [0.100, 0.050, 0.660, 0.880]; +sectionViewPosition = [0.5,0.61,0.45,0.45]; +glassViewAxesPosition = [0.000, 0.600, 0.464, 0.400]; +if screenResolution(3) <= 1024 + figurePosition = [0.100, 0.050, 0.700, 0.900]; + sectionViewPosition = [0.5, 0.61, 0.45, 0.46]; + glassViewAxesPosition = [0.000, 0.600, 0.464, 0.400]; +end + +left = 0.01; +editBoxHeight = 0.05; +editBoxWidth = 0.200; +editBoxLeft = 0.100; + +controlPanelPosition = [left, 0.080, 0.500, 0.500]; +stretchMatrix = diag([controlPanelPosition(3),controlPanelPosition(4),controlPanelPosition(3),controlPanelPosition(4)]); +controlPanelOffset = controlPanelPosition' .* [1,1,0,0]'; +heightUnit = 0.055; + +sliderPosition = stretchMatrix*[0.000, 0*heightUnit, 1.000, editBoxHeight]' + controlPanelOffset; +pValueTextPosition = stretchMatrix*[0.000, 1*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +pValueEditPosition = stretchMatrix*[0.110, 1*heightUnit, editBoxWidth*4/3, editBoxHeight]' + controlPanelOffset; +intensityThresholdTextPosition = stretchMatrix*[0.400, 1*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +intensityThresholdEditPosition = stretchMatrix*[0.520, 1*heightUnit, editBoxWidth*3/3, editBoxHeight]' + controlPanelOffset; +dfTextPosition = stretchMatrix*[0.740, 1*heightUnit, 0.8-0.74, editBoxHeight]' + controlPanelOffset; +dfEditPosition = stretchMatrix*[0.800, 1*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +clusterSizeThresholdTextPosition = stretchMatrix*[0.000, 2*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +clusterSizeThresholdEditPosition = stretchMatrix*[0.150, 2*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +pickThisClusterPushPosition = stretchMatrix*[0.400, 2*heightUnit, editBoxWidth*1, editBoxHeight]' + controlPanelOffset; +selectThisClusterPushPosition = stretchMatrix*[0.600, 2*heightUnit, editBoxWidth*1, editBoxHeight]' + controlPanelOffset; +clearSelectedClusterPushPosition = stretchMatrix*[0.800, 2*heightUnit, editBoxWidth*1, editBoxHeight]' + controlPanelOffset; +thisClusterSizeTextPosition = stretchMatrix*[0.600, 2*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +thisClusterSizeEditPosition = stretchMatrix*[0.800, 2*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +loadImagePushPosition = stretchMatrix*[0.000, 3*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +imageFileEditPosition = stretchMatrix*[0.200, 3*heightUnit, 1-editBoxWidth, editBoxHeight]' + controlPanelOffset; +saveImagePushPosition = stretchMatrix*[0.000, 4*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +saveImageFileEditPosition = stretchMatrix*[0.200, 4*heightUnit, 1-editBoxWidth, editBoxHeight]' + controlPanelOffset; +saveResultPSPushPosition = stretchMatrix*[0.000, 5*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +resultPSFileEditPosition = stretchMatrix*[0.200, 5*heightUnit, 1-editBoxWidth, editBoxHeight]' + controlPanelOffset; +displayIntensityTextPosition = stretchMatrix*[0.000, 3*heightUnit, editBoxWidth+0.1, editBoxHeight]' + controlPanelOffset; +allIntensityRadioPosition = stretchMatrix*[0.250, 3*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +positiveIntensityRadioPosition = stretchMatrix*[0.400, 3*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +negativeIntensityRadioPosition = stretchMatrix*[0.550, 3*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +renderViewCheckPosition = stretchMatrix*[0.780, 3*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +hideControlPushPosition = stretchMatrix*[0.200, 4*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +volumePushPosition = stretchMatrix*[0.000, 4*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +commonRegionPushPosition = stretchMatrix*[0.200, 4*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +%knd +paramestPushPosition = stretchMatrix*[0.800, 4*heightUnit, editBoxWidth*.5, editBoxHeight]' + controlPanelOffset; +%-- +displayPushPosition = stretchMatrix*[0.200, 4*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +allinonePushPosition = stretchMatrix*[0.400, 4*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +searchPushPosition = stretchMatrix*[0.000, 6*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +searchContentEditPosition = stretchMatrix*[0.200, 6*heightUnit, editBoxWidth*2, editBoxHeight]' + controlPanelOffset; +searchTextPosition = stretchMatrix*[0.600, 6*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +searchEnginePopPosition = stretchMatrix*[0.600, 6*heightUnit, 1-0.6, editBoxHeight]' + controlPanelOffset; +overlayPushPosition = stretchMatrix*[0.000, 5*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +overlayEditPosition = stretchMatrix*[0.200, 5*heightUnit, 0.6-editBoxWidth, editBoxHeight]' + controlPanelOffset; +overlayPopPosition = stretchMatrix*[0.600, 5*heightUnit, 1-0.6, editBoxHeight]' + controlPanelOffset; +helpPosition = stretchMatrix*[0.800, 5*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +infoTextBoxPosition = stretchMatrix*[0.000, 8*heightUnit, 1, editBoxHeight*9]' + controlPanelOffset; +%xjViewPosition = stretchMatrix*[0.400, 13*heightUnit, editBoxWidth*2.5, editBoxHeight*3]' + controlPanelOffset; +%connameTextBoxPosition = stretchMatrix*[0.000, 8*heightUnit, 1, editBoxHeight*9]' + controlPanelOffset; + +sectionViewListboxPosition = [sectionViewPosition(1)+0.4, sectionViewPosition(2)+0.02, 0.1, 0.14]; +sectionViewMoreTargetPushPosition = [sectionViewListboxPosition(1),sectionViewListboxPosition(2)-0.02,0.10,0.02]; +xHairCheckPosition = [sectionViewListboxPosition(1),sectionViewListboxPosition(2)+0.14,0.15,0.02]; +setTRangeEditPosition = [sectionViewListboxPosition(1),sectionViewListboxPosition(2)-0.06,0.10,0.02]; +setTRangeTextPosition = [sectionViewListboxPosition(1),sectionViewListboxPosition(2)-0.04,0.10,0.02]; + +getStructurePushPosition = [glassViewAxesPosition(1), glassViewAxesPosition(2)-0.06, editBoxWidth/2, editBoxHeight/2]; +structureEditPosition = [getStructurePushPosition(1), getStructurePushPosition(2), 1, getStructurePushPosition(4)]; +framePosition = (controlPanelPosition - controlPanelOffset')*1.05 + 0.95*controlPanelOffset'; + +%knd +setContrastNameTextPosition = [glassViewAxesPosition(1),glassViewAxesPosition(2)-0.026,0.6,0.03]; +setContrastListPosition = [setContrastNameTextPosition(1)+setContrastNameTextPosition(3)+.01,setContrastNameTextPosition(2),0.05,setContrastNameTextPosition(4)]; +setContrastListPosition = [glassViewAxesPosition(1)+.05*glassViewAxesPosition(3),glassViewAxesPosition(2)-0.026,glassViewAxesPosition(3)*.95,0.03]; +setIndivResultsListPosition = [setTRangeTextPosition(1)-0.25,setTRangeEditPosition(2),0.20,setTRangeEditPosition(4)]; +%-- knd + +% draw figure and control +figureBKcolor=[176/255 252/255 188/255]; +figureBKcolor=get(0,'Defaultuicontrolbackgroundcolor'); +f = figure('unit','normalized','position',figurePosition,'Color',figureBKcolor,'defaultuicontrolBackgroundColor', figureBKcolor,... + 'Name','xjView', 'Tag', 'xjView', 'NumberTitle','off','resize','off','CloseRequestFcn', {@CallBack_quit, warnstate(1).state}, 'visible','off'); +handles = guihandles(f); + +% databases +try + addpath g:\ndiayek\data\atlases\ + addpath ~/data/atlases + addpath /mount/usb/data/atlases +end +try + X = load('TDdatabase'); + handles.DB = X.DB; + handles.wholeMaskMNIAll = X.wholeMaskMNIAll; +catch + handles.DB = []; + handles.wholeMaskMNIAll = struct([]); + errordlg('I can''t find TDdatabase.mat','TDdatabase not found'); +end + +handles.figure = f; +handles.frame = uicontrol(handles.figure,'style','frame',... + 'unit','normalized',... + 'position',framePosition,... + 'Visible','off'); +handles.slider = uicontrol(handles.figure,'style','slider',... + 'unit','normalized',... + 'position',sliderPosition,... + 'max',1,'min',0,... + 'sliderstep',[0.01,0.10],... + 'callback',@CallBack_slider,... + 'value',0,'Visible','on'); +handles.pValueTextPosition = uicontrol(handles.figure,'style','text',... + 'unit','normalized','position',pValueTextPosition,... + 'string','pValue=','horizontal','left'); +handles.pValueEdit = uicontrol(handles.figure,'style','edit',... + 'unit','normalized','position',pValueEditPosition,... + 'horizontal','left',... + 'String', num2str(pValue),... + 'BackgroundColor', 'w',... + 'callback',@CallBack_pValueEdit); +handles.intensityThresholdText = uicontrol(handles.figure,'style','text',... + 'unit','normalized','position',intensityThresholdTextPosition,... + 'string',' intensity=','horizontal','left'); +handles.intensityThresholdEdit = uicontrol(handles.figure,'style','edit',... + 'unit','normalized','position',intensityThresholdEditPosition,... + 'horizontal','left',... + 'BackgroundColor', 'w',... + 'String', num2str(intensityThreshold),... + 'callback',@CallBack_intensityThresholdEdit); +handles.dfText = uicontrol(handles.figure,'style','text',... + 'unit','normalized','position',dfTextPosition,... + 'string','df= ','horizontal','right'); +handles.dfEdit = uicontrol(handles.figure,'style','edit',... + 'unit','normalized','position',dfEditPosition,... + 'horizontal','left',... + 'BackgroundColor', 'w',... + 'String', '',... + 'callback',@CallBack_dfEdit); +handles.clusterSizeThresholdText = uicontrol(handles.figure,'style','text',... + 'unit','normalized','position',clusterSizeThresholdTextPosition,... + 'string','cluster size >=','horizontal','left'); +handles.clusterSizeThresholdEdit = uicontrol(handles.figure,'style','edit',... + 'unit','normalized','position',clusterSizeThresholdEditPosition,... + 'horizontal','left',... + 'BackgroundColor', 'w',... + 'String', num2str(clusterSizeThreshold),... + 'callback',@CallBack_clusterSizeThresholdEdit); +handles.thisClusterSizeText = uicontrol(handles.figure,'style','text',... + 'unit','normalized','position',thisClusterSizeTextPosition,... + 'string','size= ','horizontal','right', 'visible', 'off'); +handles.thisClusterSizeEdit = uicontrol(handles.figure,'style','edit',... + 'unit','normalized','position',thisClusterSizeEditPosition,... + 'horizontal','left',... + 'Enable', 'inactive',... + 'String', '','visible','off'); + +handles.imageFileEdit = uicontrol(handles.figure,'style','edit',... + 'unit','normalized','position',imageFileEditPosition,... + 'horizontal','left',... + 'String', '',... + 'BackgroundColor', 'w',... + 'callback',@CallBack_imageFileEdit,... + 'visible','off'); +handles.saveImageFileEdit = uicontrol(handles.figure,'style','edit',... + 'unit','normalized','position',saveImageFileEditPosition,... + 'horizontal','left',... + 'BackgroundColor', 'w',... + 'String', 'myMask.img',... + 'callback',@CallBack_saveImageFileEdit,... + 'visible','off'); +handles.saveResultPSEdit = uicontrol(handles.figure,'style','edit',... + 'unit','normalized','position',resultPSFileEditPosition,... + 'horizontal','left',... + 'BackgroundColor', 'w',... + 'String', 'myResult.ps',... + 'callback',@CallBack_saveResultPSEdit,... + 'visible','off'); +handles.loadImagePush = uicontrol(handles.figure,'style','push',... + 'unit','normalized','position',loadImagePushPosition,... + 'string','Load Image','callback',@CallBack_loadImagePush,... + 'visible','off'); +handles.saveImagePush = uicontrol(handles.figure,'style','push',... + 'unit','normalized','position',saveImagePushPosition,... + 'string','Save Image','callback',@CallBack_saveImagePush,... + 'visible','off'); +handles.saveResultPSPush = uicontrol(handles.figure,'style','push',... + 'unit','normalized','position',saveResultPSPushPosition,... + 'string','Save Result','callback',@CallBack_saveResultPSPush,... + 'visible','off'); +handles.getStructurePush = uicontrol(handles.figure,'style','push',... + 'unit','normalized','position',getStructurePushPosition,... + 'string','Get Structure','callback',@CallBack_getStructurePush,'visible','off'); +handles.structureEdit = uicontrol(handles.figure,'style','edit',... + 'unit','normalized','position',structureEditPosition,... + 'horizontal','center',... + 'enable', 'on',... + 'UserData',struct(... + 'hReg', [],... + 'M', M,... + 'D', DIM,... + 'xyz', [0 0 0] )); +handles.pickThisClusterPush = uicontrol(handles.figure,'style','push',... + 'unit','normalized','position',pickThisClusterPushPosition,... + 'string','Pick Cluster/Info','callback',@CallBack_pickThisClusterPush); +handles.selectThisClusterPush = uicontrol(handles.figure,'style','push',... + 'unit','normalized','position',selectThisClusterPushPosition,... + 'string','Select Cluster','callback',@CallBack_selectThisClusterPush); +handles.clearSelectedClusterPush = uicontrol(handles.figure,'style','push',... + 'unit','normalized','position',clearSelectedClusterPushPosition,... + 'string','Clear Selection','callback',@CallBack_clearSelectedClusterPush); + +handles.displayIntensityText = uicontrol(handles.figure,'style','text',... + 'unit','normalized','position',displayIntensityTextPosition,... + 'string','display intensity','horizontal','left'); +handles.allIntensityRadio = uicontrol(handles.figure,'style','radio',... + 'unit','normalized',... + 'string','All',... + 'position',allIntensityRadioPosition,... + 'value', 1,... + 'callback',@CallBack_allIntensityRadio); +handles.positiveIntensityRadio = uicontrol(handles.figure,'style','radio',... + 'unit','normalized',... + 'string','Only +',... + 'position',positiveIntensityRadioPosition,... + 'callback',{@CallBack_allIntensityRadio,'+'}); +handles.negativeIntensityRadio = uicontrol(handles.figure,'style','radio',... + 'unit','normalized',... + 'string','Only -',... + 'position',negativeIntensityRadioPosition,... + 'callback',{@CallBack_allIntensityRadio,'-'}); +handles.renderViewCheck = uicontrol(handles.figure,'style','checkbox',... + 'unit','normalized',... + 'string','Render View' ,... + 'horizontal', 'right',... + 'position',renderViewCheckPosition,... + 'callback', @CallBack_renderViewCheck); + +handles.sectionViewListbox = uicontrol(handles.figure,'style','listbox',... + 'unit','normalized',... + 'String', {'single T1','avg152PD','avg152T1','avg152T2','avg305T1','ch2','ch2bet','aal','brodmann'}, ... + 'value',3,... + 'position',sectionViewListboxPosition,... + 'callback',@CallBack_sectionViewListbox); + +handles.xHairCheck = uicontrol(handles.figure,'style','checkbox',... + 'unit','normalized',... + 'string','XHairs Off' ,... + 'horizontal','left',... + 'position',xHairCheckPosition,... + 'callback',@CallBack_xHairCheck); +handles.sectionViewMoreTargetPush = uicontrol(handles.figure,'style','push',... + 'unit','normalized','position',sectionViewMoreTargetPushPosition,... + 'string','other ...','callback',@CallBack_sectionViewMoreTargetPush); +handles.setTRangeEdit = uicontrol(handles.figure,'style','edit',... + 'unit','normalized','position',setTRangeEditPosition,'BackgroundColor', 'w',... + 'string','auto','callback',@CallBack_setTRangeEdit); +handles.setTRangeText = uicontrol(handles.figure,'style','text',... + 'unit','normalized','position',setTRangeTextPosition,... + 'string','colorbar max'); +handles.hideControlPush = uicontrol(handles.figure, 'style', 'push',... + 'unit','normalized',... + 'String', '<', 'position', hideControlPushPosition,... + 'visible','off'); +handles.volumePush = uicontrol(handles.figure, 'style', 'push',... + 'unit','normalized',... + 'String', 'volume', ... + 'position', volumePushPosition,... + 'callback', @CallBack_volumePush); +handles.commonRegionPush = uicontrol(handles.figure, 'style', 'push',... + 'unit','normalized',... + 'String', 'common region', ... + 'position', commonRegionPushPosition,... + 'callback', @CallBack_commonRegionPush); +handles.displayPush = uicontrol(handles.figure, 'style', 'push',... + 'unit','normalized',... + 'String', 'display', ... + 'position', displayPushPosition,... + 'callback', @CallBack_displayPush,... + 'visible','off'); +handles.allinonePush = uicontrol(handles.figure, 'style', 'push',... + 'unit','normalized',... + 'String', 'all in one', ... + 'position', allinonePushPosition,... + 'callback', @CallBack_allinonePush,... + 'visible','off'); +%knd: +handles.setContrastNameText = uicontrol(handles.figure,'style','text',... + 'unit','normalized','position',setContrastNameTextPosition, 'FontSize', 13,... + 'string',' ... ','callback',@CallBack_setContrastNameTextPosition); +handles.contrastListPush = uicontrol(handles.figure, 'style', 'popup',... + 'unit','normalized',... + 'String', [ {''}'], ... % getfield(evalin('base','SS'),'sub2pr')),[],23)')], ... + 'position', setContrastListPosition,... + 'callback', @CallBack_contrastListPush,... + 'visible','on'); + +handles.indivResultsListPush(1) = uicontrol(handles.figure, 'style', 'popup',... + 'unit','normalized',... + 'String', [ {''}'], ... % getfield(evalin('base','SS'),'sub2pr')),[],23)')], ... + 'position', setIndivResultsListPosition,... + 'callback', @CallBack_indivResultsListPush,... + 'visible','on'); + +handles.paramestPush(1) = uicontrol(handles.figure, 'style', 'push',... + 'unit','normalized',... + 'String', 'Vox Betas', ... + 'position', paramestPushPosition'.*[1 1 2 1],... + 'callback', {@CallBack_paramestRegionPush, 'vox'},... + 'buttondownfcn', {@CallBack_paramestRegionPush, 'vox', 0},... + 'visible','on'); +handles.paramestPush(2) = uicontrol(handles.figure, 'style', 'push',... + 'unit','normalized',... + 'String', 'Cluster', ... + 'position', paramestPushPosition-[paramestPushPosition(3) 0 0 0]',... + 'callback', {@CallBack_paramestRegionPush, 'clu'},... + 'buttondownfcn', {@CallBack_paramestRegionPush, 'clu', 0},... + 'visible','on'); +handles.paramestPush(3) = uicontrol(handles.figure, 'style', 'push',... + 'unit','normalized',... + 'String', 'Sphere', ... + 'position', paramestPushPosition-2*[paramestPushPosition(3) 0 0 0]',... + 'callback', {@CallBack_paramestRegionPush, 'sph'},... + 'visible','on'); +%--knd + +handles.searchPush = uicontrol(handles.figure, 'style','push',... + 'unit','normalized','position',searchPushPosition,... + 'String', 'search','callback',@CallBack_searchPush, 'ForeGroundColor',[0 0 1]); +handles.searchContentEdit = uicontrol(handles.figure, 'style','edit',... + 'unit','normalized','position',searchContentEditPosition,... + 'ForeGroundColor',[0 0 1],... + 'BackgroundColor', 'w',... + 'horizontal','left','callback', @CallBack_searchContentEdit); +handles.searchText = uicontrol(handles.figure, 'style','text',... + 'unit','normalized','position',searchTextPosition,... + 'string', ' in',... + 'horizontal','left',... + 'visible','off'); +handles.searchEnginePop = uicontrol(... + 'Units','normalized', ... + 'ListboxTop',0, ... + 'Position',searchEnginePopPosition, ... + 'String',{'Brede';'Jede';'xBrain.org';'Google Scholar';'Pubmed';'Wikipedia'}, ... + 'Style','popupmenu', ... + 'value',1); +handles.overlayPush = uicontrol(handles.figure, 'style','push',... + 'unit','normalized','position',overlayPushPosition,... + 'String', 'overlay','callback',@CallBack_overlayPush); +handles.overlayEdit = uicontrol(handles.figure, 'style','edit',... + 'unit','normalized','position',overlayEditPosition,... + 'BackgroundColor', 'w',... + 'horizontal','left',... + 'callback', @CallBack_overlayEdit); +handles.overlayPop = uicontrol(handles.figure, 'style','popupmenu',... + 'unit','normalized','position',overlayPopPosition,... + 'string', sort(fieldnames(handles.wholeMaskMNIAll)),... + 'horizontal','left',... + 'callback', @CallBack_overlayPop); + +handles.helpPush = uicontrol(handles.figure, 'style','push',... + 'unit','normalized','position',helpPosition,... + 'String', 'help','callback','web http://people.hnl.bcm.tmc.edu/cuixu/xjView','ForeGroundColor',[0 0 1],... + 'horizontal','left', ... + 'visible','off'); +handles.infoTextBox = uicontrol(handles.figure, 'style','edit',... + 'unit','normalized','position',infoTextBoxPosition,... + 'String', 'Welcome to xjView 4','ForeGroundColor','k', 'BackgroundColor', 'w',... + 'horizontal','left', ...'fontname','times',... + 'max',2, 'min',0); +try + s = urlread('http://people.hnl.bcm.tmc.edu/cuixu/xjView/toUser.txt'); + set(handles.infoTextBox, 'String', s); +end + +handles.glassViewAxes = axes('unit','normalized','position',glassViewAxesPosition,'XTick',[],'YTick',[],'visible','off'); + + +handles.testEdit = uicontrol(handles.figure,'style','edit',... + 'unit','normalized','position',[0.1 0.4 0.2 0.05],... + 'horizontal','left',... + 'callback',@test,... + 'visible','off'); + +% menu +cSHH = get(0,'ShowHiddenHandles'); +set(0,'ShowHiddenHandles','on') +hMenuFile = findobj(get(handles.figure,'Children'),'flat','Label','&File'); +if ~isempty(hMenuFile) + hMenuFileOpen = findobj(get(handles.figure,'Children'),'Label','&Open...'); + set(hMenuFileOpen, 'label', 'Open Figure...'); + hMenuFileSave = findobj(get(handles.figure,'Children'),'Label','&Save'); + set(hMenuFileSave, 'label', 'Save Figure ...'); + hMenuFileSaveAs = findobj(get(handles.figure,'Children'),'Label','Save &As...'); + set(hMenuFileSaveAs, 'label', 'Save Figure As ...'); +else + hMenuFile = uimenu(handles.figure, 'label', '&File'); +end + +set(hMenuFile,'ForegroundColor',[0 0 1]); +set(findobj(hMenuFile,'Position',1),'Separator','on'); +%knd +uimenu('Parent',hMenuFile,'Position',1,'ForegroundColor',[0 0 1],... + 'Label','Open SPM file (SPM.mat) ...',... + 'CallBack',@CallBack_loadSPMmat, 'Accelerator', 'o'); +%--knd +uimenu('Parent',hMenuFile,'Position',1,'ForegroundColor',[0 0 1],... + 'Label','Open Images (*.img) ...',... + 'CallBack',@CallBack_loadImagePush, 'Accelerator', 'o'); +uimenu('Parent',hMenuFile,'Position',2,'ForegroundColor',[0 0 1],... + 'Label','Save Current Image (*.img) ...',... + 'CallBack',{@CallBack_saveImagePush, '', 0}); +uimenu('Parent',hMenuFile,'Position',3,'ForegroundColor',[0 0 1],... + 'Label','Save Current Image as Mask (*.img) ...',... + 'CallBack',{@CallBack_saveImagePush, '', 1}); +uimenu('Parent',hMenuFile,'Position',4,'ForegroundColor',[0 0 1],... + 'Label','Save Result (*.ps/pdf) ...',... + 'CallBack',@CallBack_saveResultPSPush); + +hMenuHelp = findobj(get(handles.figure,'Children'),'flat','Label','&Help'); +if isempty(hMenuHelp) + hMenuHelp = uimenu(handles.figure, 'label', 'xjView &Help'); +end +set(hMenuHelp,'ForegroundColor',[0 0 1]); +uimenu('Parent',hMenuHelp,'Position',1,'ForegroundColor',[0 0 1],... + 'Label','xBrain.org: brain mapping database',... + 'CallBack','web http://www.xbrain.org -browser'); +uimenu('Parent',hMenuHelp,'Position',2,... + 'Label','xjview help','ForegroundColor',[0 0 1],... + 'CallBack','web http://people.hnl.bcm.tmc.edu/cuixu/xjView'); +set(findobj(hMenuHelp,'Position',3),'Separator','on'); +set(0,'ShowHiddenHandles',cSHH) + +if exist('cuixuBOLDretrieve') + hMenuAnalyze = uimenu('label','&Analyze','ForegroundColor',[0 0 1],'visible','on'); + hMenuPreprocess = uimenu(hMenuAnalyze,'label','Preprocess','ForegroundColor',[0 0 1],'callback', @CallBack_preprocess); + hMenuProcess = uimenu(hMenuAnalyze,'label','Process (GLM estimation)','ForegroundColor',[0 0 1],'callback',@CallBack_process); + hMenuSPMProcess = uimenu(hMenuAnalyze,'label','SPMProcess (GLM using SPM)','ForegroundColor',[0 0 1],'callback',@CallBack_SPMProcess); + hMenuGLMPeak = uimenu(hMenuAnalyze,'label','GLM on peak BOLD','ForegroundColor',[0 0 1],'callback',@CallBack_GLMPeak); + hMenuContrast = uimenu(hMenuAnalyze,'label','Contrast','ForegroundColor',[0 0 1],'callback',@CallBack_contrast); + hMenuFDR = uimenu(hMenuAnalyze,'label','FDR','ForegroundColor',[0 0 1],'callback',@CallBack_fdr); + hMenuROI = uimenu(hMenuAnalyze,'label','ROI: retrieve signal','ForegroundColor',[0 0 1],'callback',@CallBack_timeSeries); + hMenuROIPlot = uimenu(hMenuAnalyze,'label','ROI: plot','ForegroundColor',[0 0 1],'callback',@CallBack_plotROI, 'Accelerator', 'm'); + hMenuROIIndividualPlot = uimenu(hMenuAnalyze,'label','ROI: individual plot','ForegroundColor',[0 0 1],'callback',@CallBack_plotIndividualROI); + hMenuROIIndividualPlotWithBehavior = uimenu(hMenuAnalyze,'label','ROI: individual plot with behavior','ForegroundColor',[0 0 1],'callback',@CallBack_plotIndividualROIWithBehavior); + hMenuROICorrelationPlot = uimenu(hMenuAnalyze,'label','ROI: correlation plot','ForegroundColor',[0 0 1],'callback',@CallBack_plotCorrelationROI); + %hMenuWholeBrainCorrelation = uimenu(hMenuAnalyze,'label','Whole brain correlation','ForegroundColor',[0 0 1],'callback',@CallBack_wholeBrainCorrelation); + hMenuBehaviorAnalysis = uimenu(hMenuAnalyze,'label','Behavior analsyis','ForegroundColor',[0 0 1],'callback',@CallBack_behaviorAnalysis); + hMenuHeadMovementAnalysis = uimenu(hMenuAnalyze,'label','Head movement analsyis','ForegroundColor',[0 0 1],'callback',@CallBack_headMovementAnalysis); + hMenuModelComparison = uimenu(hMenuAnalyze,'label','Linear model comparison','ForegroundColor',[0 0 1],'callback',@CallBack_modelComparison); + + hMenuHNLOnly = uimenu('label','For H&NL Only','ForegroundColor',[0 0 1],'visible','on'); + hMenuNew2Old = uimenu(hMenuHNLOnly,'label','Format convert','ForegroundColor',[0 0 1],'callback',@CallBack_new2old); + hMenuPreprocessCluster = uimenu(hMenuHNLOnly,'label','Preprocess (on cluster)','ForegroundColor',[0 0 1],'callback',{@CallBack_preprocess, 'cluster'}); + hMenuProcessCluster = uimenu(hMenuHNLOnly,'label','Process (GLM, on cluster)','ForegroundColor',[0 0 1],'callback',{@CallBack_process, 'cluster'}); + hMenuSPMProcessCluster = uimenu(hMenuHNLOnly,'label','SPM Process (GLM using SPM, on cluster)','ForegroundColor',[0 0 1],'callback',{@CallBack_SPMProcess, 'cluster'}); + hMenuGLMPeakCluster = uimenu(hMenuHNLOnly,'label','GLM on peak BOLD (on cluster)','ForegroundColor',[0 0 1],'callback',{@CallBack_GLMPeak, 'cluster'}); +end + +figurecm = uicontextmenu; +uimenu(figurecm,'label','Red','callback','set(gcf,''color'',''r'')'); +uimenu(figurecm,'label','White','callback','set(gcf,''color'',''w'')'); +uimenu(figurecm,'label','Gray','callback','set(gcf,''color'',[0.925, 0.914, 0.847])'); +%set(handles.figure,'uicontextmenu',figurecm); +set(handles.figure,'WindowButtonDownFcn',@figureMouseUpFcn); + +set(handles.figure,'visible','on'); + +% save pre-set values +handles.system = os; +handles.spmdir = spmdir; +handles.screenResolution = screenResolution; +handles.pValue = pValue; +handles.intensityThreshold = intensityThreshold; +handles.clusterSizeThreshold = clusterSizeThreshold; +handles.sectionViewPosition = sectionViewPosition; +handles.sectionViewTargetFile = getSectionViewTargetFile(spmdir, 'avg152T1'); +%knd +if exist('xjview.opt') + tmp=textread('xjview.opt','%s');eval(tmp{:}); + handles.sectionViewTargetFile = anatomy; +else + if ~isempty(findstr('gazemo', pwd)) + handles.sectionViewTargetFile = fullfile(fileparts(mfilename('fullpath')), 'group','anat', 'mean.img'); + end +end +guidata(f, handles); + +% global variables for rotation matrix M and dimension +global M_; +global DIM_; +global TR_; +global LEFTRIGHTFLIP_; +global TMAX_; % colorbar max to display in section view +M_ = M; +DIM_ = DIM; +TR_ = TR; +LEFTRIGHTFLIP_ = leftrightflip; +TMAX_ = 'auto'; + +% check input arguments +if length(varargin) == 0 + CallBack_loadSPMmat(handles.loadImagePush) + % if exist(fullfile(pwd,'SPM.mat'), 'file') + % xSPM=load(fullfile(pwd,'SPM.mat')); + % numc=listdlg('ListString' , {xSPM.SPM.xCon.name}, 'SelectionMode' , 'single', 'InitialValue', length(xSPM.SPM.xCon)); + % if ~isempty(numc) + % CallBack_loadImagePush(handles.loadImagePush, [], {fullfile(xSPM.SPM.swd,xSPM.SPM.xCon(numc).Vspm.fname)}); + % end + % end +elseif isstr(varargin{1}) + if isempty(findstr('.mat', varargin{1})) + CallBack_loadImagePush(handles.loadImagePush, [], varargin); + else + CallBack_loadSPMmat(handles.loadImagePush, [], varargin{1}); + end +else + mniCoord = varargin{1}; + if length(varargin) < 2 + intensity = ones(size(mniCoord,1),1); + else + intensity = varargin{2}; + end + thisStruct.mni = mniCoord; + thisStruct.intensity = intensity; + thisStruct.M = M; + thisStruct.DIM = DIM'; + CallBack_loadImagePush(handles.loadImagePush, [], thisStruct); +end + + + +function test(hObject, eventdata) +handles = guidata(gcbo); +set(hObject, 'String', num2str(handles.pValue)); +vars = evalin('base','who'); +vars +x = evalin('base',vars{1}); +x +handles.DIM + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% FDR +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_fdr(hObject, eventdata) +handles = guidata(hObject); +set(handles.infoTextBox, 'string', {'Right now FDR only works for T-test image.'}); +if length(handles.imageFileName) > 1 + set(handles.infoTextBox, 'string', {'I can only work for a single image file. You opened multiple files.'}); + return +end + +q = get(handles.pValueEdit,'String'); +q = str2num(q); + +if get(handles.allIntensityRadio, 'Value') + positive = 1; +elseif get(handles.positiveIntensityRadio, 'Value') + positive = 1; +elseif get(handles.negativeIntensityRadio, 'Value') + positive = -1; +end +xjviewpath = fileparts(which('xjview')); +maskImageFile = fullfile(xjviewpath, 'mask.img'); +[threshold, pvalue] = fdr(handles.imageFileName{1}, q, positive, maskImageFile); +set(handles.pValueEdit,'string',pvalue); +CallBack_pValueEdit(handles.pValueEdit, eventdata); + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% model comparison +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_modelComparison(hObject, eventdata) +answer = getVariable({'y','x (full model)', 'x (reduced model)'}); + +if isempty(answer) + return; +end + +if ~isempty(answer{1}) + y = evalin('base',answer{1}); +else + return; +end +if ~isempty(answer{2}) + xf = evalin('base',answer{2}); % full model +else + return; +end +if ~isempty(answer{3}) + xr = evalin('base',answer{3}); % reduced model +else + xr = []; +end + +% make vector column vector +[r, c] = size(y); +if r==1; y = y'; end +[r, c] = size(xf); +if r(0.5+ii/10); +end + +K = {zeros(1, size(C,1)),zeros(1, size(C,1)),zeros(1, size(C,1)),zeros(1, size(C,1))}; +for ii=1:size(C,1) + for jj=1:4 + K{jj}(ii) = sum(CC{jj}(ii,:)) - 1; + end +end + +x=[0:size(C,1)]; +for jj=1:4 + y{jj}=zeros(size(x)); +end + +for ii=1:length(x) + for jj=1:4 + y{jj}(ii) = sum(K{jj} == x(ii)); + end +end + +figure; +loglog(x,y{1}, x, y{2}, x, y{3}, x, y{4}); +xlabel('degree'); +ylabel('counts'); +legend('0.6', '0.7', '0.8', '0.9'); +axis equal + + +z = sum(abs(C))-1; +zx = 0:max(z); +for ii=1:length(zx)-1 + pos = find(z>zx(ii) & z<=zx(ii+1)); + zy(ii) = length(pos); +end +figure; +loglog(zx(1:length(zy)),zy); +xlabel('weighted degree'); +ylabel('counts'); +axis equal + +xjview(cor2mni(coord, handles.M{1}), z); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% get/select variable names in base workspace +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function vars = getVariable(titles) +vars = evalin('base','who'); +height = 0.07; +f = dialog('unit','normalized', 'menubar','none', 'position', [0.3 0.2 0.4 0.4], 'name', 'pick variables', 'NumberTitle','off'); +uicontrol(f,'style','text',... + 'unit','normalized',... + 'String', 'Available variables', ... + 'position',[0 0.9 0.5 0.1/2]); +variableListbox = uicontrol(f,'style','listbox','tag','variableListbox',... + 'unit','normalized',... + 'String', vars, ... + 'value',1,... + 'position',[0 0 0.5 0.9]); +okPush = uicontrol(f,'style','push',... + 'unit','normalized',... + 'String', 'OK', ... + 'position',[0.55 0.05 0.2 height],... + 'callback','uiresume'); +cancelPush = uicontrol(f,'style','push',... + 'unit','normalized',... + 'String', 'Cancel', ... + 'position',[0.75 0.05 0.2 height],... + 'callback','delete(gcf)'); + +uicontrol(f,'style','text',... + 'unit','normalized',... + 'String', titles{1}, ... + 'position',[0.5 0.9 0.5 0.1/2]); +imageDataPush = uicontrol(f,'style','push',... + 'unit','normalized',... + 'String', '->', ... + 'position',[0.5 0.8 0.1 height],... + 'callback', [... + 'xyouneverfindme=findobj(get(gcf,''Children''),''flat'',''tag'',''imageDataEdit'');'... + 'yyouneverfindme=findobj(get(gcf,''Children''),''flat'',''tag'',''variableListbox'');'... + 'allyouneverfindme=get(yyouneverfindme,''string'');'... + 'selectedyouneverfindme=allyouneverfindme{get(yyouneverfindme,''value'')};'... + 'set(xyouneverfindme,''string'',selectedyouneverfindme);'... + 'clear allyouneverfindme selectedyouneverfindme xyouneverfindme yyouneverfindme;']); +imageDataEdit = uicontrol(f,'style','edit','tag','imageDataEdit',... + 'unit','normalized',... + 'String', '', ... + 'BackgroundColor', 'w',... + 'position',[0.6 0.8 0.3 height]); + +if length(titles)>=2 + uicontrol(f,'style','text',... + 'unit','normalized',... + 'String', titles{2}, ... + 'position',[0.5 0.7 0.5 0.1/2]); + eventDataPush = uicontrol(f,'style','push',... + 'unit','normalized',... + 'String', '->', ... + 'position',[0.5 0.6 0.1 height],... + 'callback', [... + 'xyouneverfindme=findobj(get(gcf,''Children''),''flat'',''tag'',''eventDataEdit'');'... + 'yyouneverfindme=findobj(get(gcf,''Children''),''flat'',''tag'',''variableListbox'');'... + 'allyouneverfindme=get(yyouneverfindme,''string'');'... + 'selectedyouneverfindme=allyouneverfindme{get(yyouneverfindme,''value'')};'... + 'set(xyouneverfindme,''string'',selectedyouneverfindme);'... + 'clear allyouneverfindme selectedyouneverfindme xyouneverfindme yyouneverfindme;']); + + eventDataEdit = uicontrol(f,'style','edit','tag','eventDataEdit',... + 'unit','normalized',... + 'String', '', ... + 'BackgroundColor', 'w',... + 'position',[0.6 0.6 0.3 height]); +end +if length(titles)>=3 + uicontrol(f,'style','text',... + 'unit','normalized',... + 'String', titles{3}, ... + 'position',[0.5 0.5 0.5 0.1/2]); + correlatorDataPush = uicontrol(f,'style','push',... + 'unit','normalized',... + 'String', '->', ... + 'position',[0.5 0.4 0.1 height],... + 'callback', [... + 'xyouneverfindme=findobj(get(gcf,''Children''),''flat'',''tag'',''correlatorDataEdit'');'... + 'yyouneverfindme=findobj(get(gcf,''Children''),''flat'',''tag'',''variableListbox'');'... + 'allyouneverfindme=get(yyouneverfindme,''string'');'... + 'selectedyouneverfindme=allyouneverfindme{get(yyouneverfindme,''value'')};'... + 'set(xyouneverfindme,''string'',selectedyouneverfindme);'... + 'clear allyouneverfindme selectedyouneverfindme xyouneverfindme yyouneverfindme;']); + + correlatorDataEdit = uicontrol(f,'style','edit','tag','correlatorDataEdit',... + 'unit','normalized',... + 'String', '', ... + 'BackgroundColor', 'w',... + 'position',[0.6 0.4 0.3 height]); +end +if length(titles)>=4 + uicontrol(f,'style','text',... + 'unit','normalized',... + 'String', titles{4}, ... + 'position',[0.5 0.3 0.5 0.1/2]); + otherDataPush = uicontrol(f,'style','push',... + 'unit','normalized',... + 'String', '->', ... + 'position',[0.5 0.2 0.1 height],... + 'callback', [... + 'xyouneverfindme=findobj(get(gcf,''Children''),''flat'',''tag'',''otherDataEdit'');'... + 'yyouneverfindme=findobj(get(gcf,''Children''),''flat'',''tag'',''variableListbox'');'... + 'allyouneverfindme=get(yyouneverfindme,''string'');'... + 'selectedyouneverfindme=allyouneverfindme{get(yyouneverfindme,''value'')};'... + 'set(xyouneverfindme,''string'',selectedyouneverfindme);'... + 'clear allyouneverfindme selectedyouneverfindme xyouneverfindme yyouneverfindme;']); + + otherDataEdit = uicontrol(f,'style','edit','tag','otherDataEdit',... + 'unit','normalized',... + 'String', '', ... + 'BackgroundColor', 'w',... + 'position',[0.6 0.2 0.3 height]); +end + +uiwait(f); +try + var{1} = get(imageDataEdit,'string'); + if length(titles)>=2 + var{2} = get(eventDataEdit,'string'); + end + if length(titles)>=3 + var{3} = get(correlatorDataEdit,'string'); + end + if length(titles)>=4 + var{4} = get(otherDataEdit,'string'); + end + vars = var; + delete(f); +catch + vars = {}; + try + delete(f); + end +end + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% quit xjview +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_quit(hObject, eventdata, warnstate) +warning(warnstate) +% try +% rmdir('xjviewtmp'); +% end +delete(gcf); + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% mouse double click +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function figureMouseUpFcn(hObject, eventdata) +status = get(hObject, 'SelectionType'); + +% double click +if strcmp(status, 'extend') + handles = guidata(hObject); + CallBack_loadImagePush(handles.loadImagePush, eventdata); +elseif strcmp(status, 'open') + handles = guidata(hObject); + CallBack_loadSPMmat(handles.loadImagePush, eventdata); +else + return +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% change edit image file name +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_imageFileEdit(hObject, eventdata) +handles = guidata(hObject); +filename = get(hObject, 'String'); +filename = str2cell(filename); +CallBack_loadImagePush(handles.loadImagePush, eventdata, filename); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% click load image file button +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_contrastListPush(hObject, eventdata) +handles = guidata(hObject); +connum = get(handles.contrastListPush,'Value'); +[img.p, img.fname, img.ext]=fileparts(handles.imageFileName{1}); +spmfile=fullfile(img.p,'SPM.mat'); +if ~exist(spmfile) + set(handles.contrastListPush,'Value',0); +else + cons=getfield(getfield(load(spmfile, 'SPM'), 'SPM'), 'xCon'); + connames=get(handles.contrastListPush,'String'); + CallBack_loadImagePush(hObject,eventdata,fullfile(img.p, cons(connum).Vspm.fname), connum) +end + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% click load image file button +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_loadImagePush(hObject, eventdata, thisfilename, ContrastName) +handles = guidata(hObject); +handles.imageFileName=[]; handles.M=[]; handles.DIM=[]; handles.TF=[]; handles.df=[]; +handles.mni=[]; handles.intensity=[]; handles.currentmni=[]; handles.currentintensity=[]; handles.currentDisplayMNI=[]; handles.currentDisplayIntensity=[]; +if nargin>3 + handles.imageContrastName=ContrastName; +else + handles.imageContrastName=[]; +end +if ~exist('thisfilename') + thisfilename = ''; +end + +if isstruct(thisfilename) + handles.imageFileName = {''}; + handles.mni = {thisfilename.mni}; + handles.intensity = {thisfilename.intensity}; + handles.M = {thisfilename.M}; + handles.DIM = {thisfilename.DIM}; + handles.TF = {'S'}; + handles.df = {1e6}; + handles.pValue = 1; + set(handles.pValueEdit, 'string', '1'); + handles.clusterSizeThreshold = 0; + set(handles.clusterSizeThresholdEdit, 'String', '0'); + handles.imageContrastName={''}; +else + [handles.imageFileName, handles.M, handles.DIM, handles.TF, handles.df, handles.mni, handles.intensity] = getImageFile(thisfilename); +end + +if isempty(handles.imageFileName) + return +end + +% tries to find firts-level matching data +% knd +set(handles.indivResultsListPush(1),'String', ... + getfield2(dir(fileparts(fileparts(handles.imageFileName{1}))), 'name')); + +different = 0; % image files are same or different? +if length(handles.TF)>1 + for ii=1:length(handles.TF) + if strcmp(handles.TF{ii},handles.TF{1}) & isequal(handles.df{ii},handles.df{1}) & isequal(handles.M{ii},handles.M{1}) & isequal(handles.DIM{ii},handles.DIM{1}) + continue; + else + warndlg('Images are from different statistics or sources.', 'Warning'); + %set(handles.infoTextBox, 'string', 'Images are from different statistics or sources.'); + beep; + different = 1; + break; + end + end +end + +% reset files with empty df/TF to df=1 and TF='S'. 'S'=='T' but has a tag +% meaning it is changed. +resetTF = 0; +for ii=1:length(handles.TF) + if isempty(handles.TF{ii}) + handles.TF{ii} = 'S'; + resetTF = 1; + end + if isempty(handles.df{ii}) | isequal(handles.df{ii},0) + handles.df{ii} = 1e6; + resetTF = 1; + end +end + +handles.currentmni = handles.mni; +handles.currentintensity = handles.intensity; + +set(handles.dfEdit, 'String', cell2str(handles.df)); +set(handles.imageFileEdit, 'String', cell2str(handles.imageFileName)); % s=-log10(p) +maxs = maxcell(t2s(cellmax(handles.intensity,'abs'),handles.df, handles.TF),'abs'); +if isinf(maxs); maxs = 20; end +set(handles.slider, 'Max', maxs, 'Min', 0, 'sliderstep',[min(maxs/100,0.05),min(maxs/100,0.05)]); +if handles.TF{1}=='T' & different == 0 + str = [blanks(length(' intensit')) 'T=']; +elseif handles.TF{1}=='F' & different == 0 + str = [blanks(length(' intensit')) 'F=']; +else + str=' intensity='; +end +set(handles.intensityThresholdText, 'String', str); +set(handles.figure,'Name',['xjView: ' cell2str(handles.imageFileName)]); +try + for ii=1:length(handles.hLegend) + delete(handles.hLegend{ii}); + end +end +nimg=length(handles.TF); +if nimg>1 + colours = {'r';'g';'y';'c';'b';'m'}; + %colours = colours(mod(0:nimg-1,length(colours))+1); + %colours = repmat([1 0 0;1 1 0;0 1 0;0 1 1;0 0 1;1 0 1],100,1); + % read_conname = spm_input({'Retrieve contrasts names','Should I read SPM files to retrieve filenames?'},... + % 1,'bd',{'YES','NO'},[1,0]); + read_conname = 1; + for ii=1:nimg + [tmp,filename] = fileparts(handles.imageFileName{ii}); + c_names{ii} = filename ; + if read_conname & exist(fullfile(tmp, 'SPM.mat')) + tmp=load(fullfile(tmp, 'SPM.mat')); + tmp.Vspm=[tmp.SPM.xCon.Vspm]; + tmp.iCon=strmatch(filename, {tmp.Vspm.fname}); + if ~isempty(tmp.iCon) + c_names{ii} = [ [tmp.SPM.xCon(tmp.iCon).name] ' [' c_names{ii} ']']; + end + end + end + [tmp] = inputdlg({'colors' 'names'},'Loading',6,[{strvcat(c_names)};{strvcat(colours)}]); + colours = colorname2rgb(tmp{2}); + c_names = cellstr(tmp{1}); + + for ii=1:min(length(handles.TF),10) + filename = c_names{ii}; + if ii == 10; filename = '......'; end + pos0 = handles.sectionViewPosition; + pos(1) = pos0(1)+pos0(3)/2+0.03; + pos(2) = pos0(2)+ii/50-0.03; + pos(3) = 0.12; + pos(4) = 0.02; + handles.hLegend{ii}=uicontrol(handles.figure, 'style','text',... + 'unit','normalized','position',pos,... + 'string', filename,... + 'horizontal','left',... + 'fontweight','bold',... + 'ForeGroundColor',colours(ii,:)); + end + c_names = {'red';'yellow';'green';'cyan';'blue';'magenta'}; +end +guidata(hObject, handles); + +global M_; +global DIM_; +M_ = handles.M{1}; +DIM_ = handles.DIM{1}; + +if resetTF==1 + set(handles.pValueEdit,'string',1); +end +CallBack_pValueEdit(handles.pValueEdit, eventdata); + +% display info +try + s = urlread('http://people.hnl.bcm.tmc.edu/cuixu/xjView/toUser.txt'); + report{1} = s; +catch + report{1} = 'Welcome to xjView 4'; +end +for jj=1:length(handles.imageFileName) + report{2+6*(jj-1)} = cell2str(handles.imageFileName(jj)); + if handles.TF{jj} == 'T' | handles.TF{jj} == 'F' + report{3+6*(jj-1)} = ['This is a ' handles.TF{jj} ' test image.']; + else + report{3+6*(jj-1)} = '';%['I don''t know what test this image came from.']; + end + report{4+6*(jj-1)} = 'mat = '; + report{5+6*(jj-1)} = num2str(handles.M{jj}); + report{6+6*(jj-1)} = 'dimension = '; + report{7+6*(jj-1)} = num2str(handles.DIM{jj}); +end +if length(handles.imageFileName)==1 + [img.p, img.fname, img.ext]=fileparts(handles.imageFileName{1}); + spmfile=fullfile(img.p,'SPM.mat'); + if ~exist(spmfile) + if findstr('SPM2',spm('ver')) + spmfile = spm_get([0 1],'SPM.mat','locate the corresponding SPM.mat'); + elseif findstr('SPM5',spm('ver')) + spmfile = spm_select([0:1],'SPM.mat','locate the corresponding SPM.mat'); + end + end + if exist(spmfile) + cons=getfield(getfield(load(spmfile, 'SPM'), 'SPM'), 'xCon'); + for i=1:length(cons) + if not(isempty(cons(i).Vspm)) + consfilename{i}=cons(i).Vspm.fname; + else + consfilename{i}=''; + end + end + connum=strmatch([img.fname img.ext],consfilename); + if ~isempty(connum) + conlabel=cons(connum).name; + set(handles.figure,'Name',[get(handles.figure,'Name') ' :: ' conlabel]); + set(handles.contrastListPush,'String',{cons.name}) + set(handles.contrastListPush,'Value',connum) + set(handles.setContrastNameText,'String',[conlabel]); + report=[report(1:3) {['Contrast name in SPM.mat: (' num2str(connum) ') ' conlabel]} report(4:end)]; + else + set(handles.contrastListPush,'String',{'[none]'}) + set(handles.contrastListPush,'Value',0) + set(handles.contrastListPush,'Enable','on') + report=[report(1:3) {['Contrast has no matching name in SPM.mat']} report(4:end)]; + end + end +end +set(handles.infoTextBox, 'string', report); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% pValueEdit +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_pValueEdit(hObject, eventdata) +handles = guidata(hObject); +tmp = str2double(get(hObject, 'String')); +tmps = -log10(tmp); +if isnan(tmp) | tmp < 0 | tmp > 1 + errordlg('I don''t understand the input.','error'); + set(hObject, 'String', handles.pValue); + return +end + +if tmps>get(handles.slider,'max') | tmps maxcell(cellmax(handles.intensity,'abs')) + tmp = maxcell(cellmax(handles.intensity,'abs')); +end + +handles.pValue = t2p(tmp, handles.df{1}, handles.TF{1}); +handles.intensityThreshold = p2t(num2cell(handles.pValue*ones(1,length(handles.TF))), handles.df, handles.TF); +set(handles.slider,'Value', -log10(handles.pValue)); +set(handles.intensityThresholdEdit, 'String', cell2str(handles.intensityThreshold)); +guidata(hObject, handles); +CallBack_slider(hObject, eventdata, -log10(handles.pValue)); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% slider bar +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_slider(hObject, eventdata, value) + +handles = guidata(hObject); + +if exist('value') + s = value; + if s > get(handles.slider,'max') + s = get(handles.slider,'max')*0.99; + end + if s < get(handles.slider,'min') + s = get(handles.slider,'min'); + end +else + s = get(hObject,'Value'); +end + +set(handles.slider, 'value', s); +pvalue = 10^(-s); +set(handles.pValueEdit,'String',num2str(pvalue)); +t = p2t(num2cell(pvalue*ones(1,length(handles.TF))), handles.df, handles.TF); +handles.intensityThreshold = t; +set(handles.intensityThresholdEdit, 'String', cell2str(handles.intensityThreshold)); +handles.pValue = pvalue; +for ii=1:length(handles.TF) + pos{ii} = find(abs(handles.intensity{ii})>=t{ii}); + handles.currentintensity{ii} = handles.intensity{ii}(pos{ii}); + handles.currentmni{ii} = handles.mni{ii}(pos{ii},:); +end + +guidata(hObject,handles); + +if get(handles.allIntensityRadio, 'Value') + CallBack_allIntensityRadio(handles.allIntensityRadio, eventdata); +elseif get(handles.positiveIntensityRadio, 'Value') + CallBack_allIntensityRadio(handles.positiveIntensityRadio, eventdata, '+'); +elseif get(handles.negativeIntensityRadio, 'Value') + CallBack_allIntensityRadio(handles.negativeIntensityRadio, eventdata, '-'); +end + +set(handles.infoTextBox, 'string', {'Don''t drag the slider bar too fast. Release your mouse button at least 1 second later.', ... + 'This sounds stupid. But there is a bug (probably MatLab bug) which I can'' fix right now.', ... + 'I suggest you confirm the correctness of the current display by press Enter in the pValue edit box.'}); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% display intensity all+- radios +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_allIntensityRadio(hObject, eventdata, pnall) +% pnall = '+', '-', 'a' (all) or 'c' for current (simply update drawing) +% +handles = guidata(hObject); + +currentselect = []; +if get(handles.allIntensityRadio, 'Value'); currentselect = handles.allIntensityRadio; thispnall = 'a'; end +if get(handles.positiveIntensityRadio, 'Value'); currentselect = handles.positiveIntensityRadio; thispnall = '+'; end +if get(handles.negativeIntensityRadio, 'Value'); currentselect = handles.negativeIntensityRadio; thispnall = '-'; end + +set(handles.allIntensityRadio, 'Value', 0); +set(handles.positiveIntensityRadio, 'Value', 0); +set(handles.negativeIntensityRadio, 'Value', 0); + +if exist('pnall') + if pnall=='c' + hObject = currentselect; + pnall = thispnall; + end +end + +set(hObject, 'Value', 1); + +if ~isfield(handles,'currentintensity') + return +end +for ii=1:length(handles.TF) + if exist('pnall') + if pnall == '-' + pos{ii} = find(handles.currentintensity{ii} < 0); + elseif pnall == '+' + pos{ii} = find(handles.currentintensity{ii} > 0); + elseif pnall == 'a' + pos{ii} = 1:length(handles.currentintensity{ii}); + end + else + pos{ii} = 1:length(handles.currentintensity{ii}); + end + intensity{ii} = handles.currentintensity{ii}(pos{ii}); + mni{ii} = handles.currentmni{ii}(pos{ii},:); + cor{ii} = mni2cor(mni{ii}, handles.M{ii}); + + if ~isempty(cor{ii}) + A = spm_clusters(cor{ii}'); + pos0 = []; + for kk = 1:max(A) + jj = find(A == kk); + if length(jj) >= handles.clusterSizeThreshold; pos0 = [pos0 jj]; end + end + handles.currentDisplayMNI{ii} = mni{ii}(pos0,:); + handles.currentDisplayIntensity{ii} = intensity{ii}(pos0); + else + handles.currentDisplayMNI{ii} = mni{ii}([],:); + handles.currentDisplayIntensity{ii} = intensity{ii}([]); + end +end +[handles.hReg, handles.hSection, handles.hcolorbar] = Draw(handles.currentDisplayMNI, handles.currentDisplayIntensity, hObject, handles); + +% list of contrasts + +set(handles.contrastListPush,'String',regexprep(get(handles.contrastListPush,'String'),' \[Only [\+-a]\]$','')) +set(handles.setContrastNameText,'String',regexprep(get(handles.setContrastNameText,'String'),' \[Only [\+-a]\]$','')) + +if exist('pnall','var') + if ~isequal(pnall, 'a') + set(handles.setContrastNameText,'String',[get(handles.setContrastNameText,'String') ' [Only ' pnall ']']); + s=get(handles.contrastListPush,'String'); + s{get(handles.contrastListPush,'Value')}=[s{get(handles.contrastListPush,'Value')} ' [Only ' pnall ']']; + + set(handles.contrastListPush,'String',s); + end +end + +if get(handles.allIntensityRadio, 'Value') & max(handles.currentDisplayIntensity{1}) < 0 + warndlg('No supra-threshold positive intensity. Only negative intensity is displayed.'); +end + +try + set(handles.figure,'currentaxes', handles.glassViewAxes); + xrange = xlim; + yrange = ylim; + try + delete(handles.hGlassText) + end + if ~isempty(handles.selectedCluster,1) + %handles.hGlassText = text(xrange(1)+diff(xrange)*0.6, yrange(1)+diff(yrange)*0.9, [num2str(size(handles.selectedCluster,1)) ' clusters selected']); + set(handles.infoTextBox, 'string', [num2str(size(handles.selectedCluster,1)) ' clusters selected']); + end +end +guidata(hObject, handles); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% render view check? +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_renderViewCheck(hObject, eventdata) +check = get(hObject, 'Value'); +if check + CallBack_allIntensityRadio(hObject, eventdata, 'c'); +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% which section view target file? list box +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_sectionViewListbox(hObject, eventdata) +handles = guidata(hObject); +contents = get(handles.sectionViewListbox,'String'); +currentsel = contents{get(handles.sectionViewListbox,'Value')}; +handles.sectionViewTargetFile = getSectionViewTargetFile(handles.spmdir, currentsel); +guidata(hObject, handles); +CallBack_allIntensityRadio(hObject, eventdata, 'c'); + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% get sectionviewtargetfile +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function sectionViewTargetFile = getSectionViewTargetFile(spmdir, selectedcontent) +if findstr('SPM2',spm('ver')) + fileext = 'mnc'; +elseif findstr('SPM5',spm('ver')) + fileext = 'nii'; +else + fileext = 'nii'; +end +currentsel = selectedcontent; +if ~isempty(strfind(currentsel, 'single')) + sectionViewTargetFile = fullfile(spmdir, 'canonical', ['single_subj_T1.' fileext]); +elseif ~isempty(strfind(currentsel, '152PD')) + sectionViewTargetFile = fullfile(spmdir, 'canonical', ['avg152PD.' fileext]); +elseif ~isempty(strfind(currentsel, '152T1')) + sectionViewTargetFile = fullfile(spmdir, 'canonical', ['avg152T1.' fileext]); +elseif ~isempty(strfind(currentsel, '152T2')) + sectionViewTargetFile = fullfile(spmdir, 'canonical', ['avg152T2.' fileext]); +elseif ~isempty(strfind(currentsel, '305T1')) + sectionViewTargetFile = fullfile(spmdir, 'canonical', ['avg305T1.' fileext]); +elseif strcmp(currentsel, 'ch2') + sectionViewTargetFile = fullfile(spmdir, 'canonical', 'ch2.img'); +elseif strcmp(currentsel, 'ch2bet') + sectionViewTargetFile = fullfile(spmdir, 'canonical', 'ch2bet.img'); +elseif strcmp(currentsel, 'aal') + sectionViewTargetFile = fullfile(spmdir, 'canonical', 'aal.img'); +elseif strcmp(currentsel, 'brodmann') + sectionViewTargetFile = fullfile(spmdir, 'canonical', 'brodmann.img'); + %knd: +elseif ~isempty(strfind(currentsel, 'gazemo')) + sectionViewTargetFile = fullfile(fileparts(mfilename('fullpath')), 'group','anat', 'mean-no120.img'); + %sectionViewTargetFile = fullfile('D:\ndiayek\data\gazemo\group\anat', 'mean-no120.img'); +end +return + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% xhairs in section view? +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_xHairCheck(hObject, eventdata) +check = get(hObject, 'Value'); +if check + spm_orthviews('Xhairs','off'); +else + spm_orthviews('Xhairs','on'); +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% other target file for section view, push button +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_sectionViewMoreTargetPush(hObject, eventdata) +handles = guidata(hObject); +[filename, pathname, filterindex] = uigetfile('*', 'Pick an target file'); +if isequal(filename,0) | isequal(pathname,0) + return; +end +handles.sectionViewTargetFile = fullfile(pathname, filename); +guidata(hObject, handles); +CallBack_allIntensityRadio(hObject, eventdata, 'c'); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% set colorbar range +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_setTRangeEdit(hObject, eventdata) +global TMAX_; +handles = guidata(hObject); +TMAX_ = get(hObject, 'String'); +if isempty(str2num(TMAX_)) && ~strcmp(TMAX_, 'auto') + return; +end +guidata(hObject, handles); +CallBack_allIntensityRadio(hObject, eventdata, 'c'); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% change degree of freedome +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_dfEdit(hObject, eventdata) +handles = guidata(hObject); +tmp = str2cell(get(hObject, 'String')); +if iscellstr(tmp) + errordlg('Please input a valid number.','error'); + try + set(hObject, 'String', handles.df); + catch + set(hObject, 'String', ''); + end + return +end +handles.df=tmp; + + +if isfield(handles,'TF') + t = p2t(mat2cell(handles.pValue*ones(1,length(handles.TF))), handles.df, handles.TF); + handles.intensityThreshold = t; + set(handles.intensityThresholdEdit, 'String', cell2str(t)); +end + +guidata(hObject, handles); +CallBack_pValueEdit(handles.pValueEdit, eventdata); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% get structure push +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_getStructurePush(hObject, eventdata) +handles = guidata(hObject); +xyz = spm_XYZreg('GetCoords',handles.hReg); +tmp_coor = cuixuFindTDstructure(xyz', handles.DB, 0); +set(handles.structureEdit,'String', tmp_coor{1}); +handles.currentxyz = xyz'; +guidata(hObject, handles); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% structure edit +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_structureEdit(action, xyz, thisfun, hReg) + +try + handles=guidata(hReg); +catch + return; +end + +handles.currentxyz = xyz'; +guidata(hReg, handles); + +try + [tmp_coor, cellstructure] = cuixuFindTDstructure(xyz', handles.DB, 0); +catch + return; +end + +set(handles.structureEdit,'String', tmp_coor{1}); + +for ii=[5 3 2 1 4] + if strfind('Unidentified', cellstructure{ii}) + continue; + else + set(handles.searchContentEdit,'string', trimStructureStr(cellstructure{ii})); + set(handles.searchContentEdit,'UserData', 'auto'); + return; + end +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% trim str +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function out = trimStructureStr(str) +pos = findstr('(', str); +if ~isempty(pos) + str(pos-1:end)=[]; +end +out = str; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% cluster size threshold edit +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_clusterSizeThresholdEdit(hObject, eventdata) +handles = guidata(hObject); +tmp = str2double(get(hObject, 'String')); +if isnan(tmp) | tmp<0 + errordlg('Please input a valid number.','error'); + try + set(hObject, 'String', handles.clusterSizeThreshold); + catch + set(hObject, 'String', '5'); + end + return +end +handles.clusterSizeThreshold=tmp; + +guidata(hObject, handles); +CallBack_allIntensityRadio(hObject, eventdata, 'c'); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% save image push button +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_saveImagePush(hObject, eventdata, thisfilename, isMask) +handles = guidata(hObject); + +if exist('thisfilename') + if ~strcmp(deblank(thisfilename), '') + if isfield(handles,'imageFileName') + if ~isempty(handles.imageFileName) + mni2mask(cell2mat(handles.currentDisplayMNI'), thisfilename, cell2mat(handles.currentDisplayIntensity), handles.M{1}, handles.DIM{1}, handles.imageFileName{1}); + return; + end + end + mni2mask(cell2mat(handles.currentDisplayMNI'), thisfilename, cell2mat(handles.currentDisplayIntensity), handles.M{1}, handles.DIM{1}); + return; + end +end + +[filename, pathname] = uiputfile('*.img', 'Save image file as', get(handles.saveImageFileEdit, 'string')); +if isequal(filename,0) | isequal(pathname,0) + return +else + thisfilename = fullfile(pathname, filename); +end + +if isfield(handles,'imageFileName') + if ~isempty(handles.imageFileName{1}) + mni2mask(cell2mat(handles.currentDisplayMNI'), thisfilename, cell2mat(handles.currentDisplayIntensity'), handles.M{1}, handles.DIM{1}, handles.imageFileName{1}, isMask); + return; + end +end + +mni2mask(cell2mat(handles.currentDisplayMNI'), thisfilename, cell2mat(handles.currentDisplayIntensity), handles.M{1}, handles.DIM{1}, '', isMask); + +return; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% save image edit +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_saveImageFileEdit(hObject, eventdata) +handles = guidata(hObject); +CallBack_saveImagePush(handles.saveImagePush, eventdata, get(hObject,'string')); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% save result push +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_saveResultPSPush(hObject, eventdata, thisfilename) + +handles = guidata(hObject); + +if exist('thisfilename') + if ~strcmp(deblank(thisfilename), '') + spm_print(handles.figure); + if strcmp(handles.system, 'linux') + system(['ps2pdf spm2.ps']); + system(['mv spm2.ps ' thisfilename]); + [p,f,ext]=fileparts(thisfilename); + system(['mv spm2.pdf ' fullfile(p,f) '.pdf']); + elseif strcmp(handles.system, 'windows') + system(['move spm2.ps ' '"' thisfilename '"']); + end + return; + end +end + +[filename, pathname] = uiputfile('*.ps', 'Save result as', get(handles.saveResultPSEdit, 'string')); +if isequal(filename,0) | isequal(pathname,0) + return +else + thisfilename = fullfile(pathname, filename); +end + + +% print +H = findobj(get(handles.figure,'Children'),'flat','Type','axes'); +un = cellstr(get(H,'Units')); +pos = get(H,'position'); +index = []; + +for ii=1:length(H) + if findstr('pixels', un{ii}) + continue; + end + if pos{ii}(1)>0.4 & pos{ii}(2) > 0.5 + set(H(ii),'position',[pos{ii}(1), pos{ii}(2), pos{ii}(3)*3/4, pos{ii}(4)]); + index = [index ii]; + end +end + + +spm_print(handles.figure); +if strcmp(handles.system, 'linux') + system(['ps2pdf spm2.ps']); + system(['mv spm2.ps ' thisfilename]); + [p,f,ext]=fileparts(thisfilename); + system(['mv spm2.pdf ' fullfile(p,f) '.pdf']); +elseif strcmp(handles.system, 'windows') + system(['move spm2.ps ' '"' thisfilename '"']); +end + +% set the position back +set(H(index), {'position'}, pos(index)); + + +% printstr = ['print -dpsc2 -painters -noui ' '''' thisfilename '''']; +% try +% orient portrait +% eval(printstr); +% printsuccess = 1; +% catch +% errordlg('Print to ps file failed', 'print error'); +% printsuccess = 0; +% end +%set(H,{'Units'},un); +% if strcmp(handles.system, 'linux') & printsuccess == 1 +% system(['ps2pdf ' '''' thisfilename '''']); +% end + +return; + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% save result edit +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_saveResultPSEdit(hObject, eventdata) +handles = guidata(hObject); +CallBack_saveResultPSPush(hObject, eventdata, get(handles.saveResultPSEdit,'string')); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% select a cluster +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_selectThisClusterPush(hObject, eventdata) +handles = guidata(hObject); + +try + xyz = handles.currentxyz'; +catch + xyz = spm_XYZreg('GetCoords',handles.hReg); +end + +try + handles.selectedCluster = [handles.selectedCluster; xyz']; +catch + handles.selectedCluster = xyz'; +end + +disp('handles defines!') +assignin('base','handles',handles) + +set(handles.figure,'currentaxes', handles.glassViewAxes); +xrange = xlim; +yrange = ylim; +try + delete(handles.hGlassText) +end +%handles.hGlassText = text(xrange(1)+diff(xrange)*0.6, yrange(1)+diff(yrange)*0.9, [num2str(size(handles.selectedCluster,1)) ' clusters selected']); +set(handles.infoTextBox, 'string', [num2str(size(handles.selectedCluster,1)) ' clusters selected']); +guidata(hObject, handles); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% unselect a cluster +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_clearSelectedClusterPush(hObject, eventdata) +handles = guidata(hObject); +try + handles = rmfield(handles,'selectedCluster'); +end +set(handles.figure,'currentaxes', handles.glassViewAxes); +xrange = xlim; +yrange = ylim; +try + %delete(handles.hGlassText) + set(handles.infoTextBox, 'string', ['No clusters selected']); +end + +guidata(hObject, handles); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% pick a cluster +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_pickThisClusterPush(hObject, eventdata) +handles = guidata(hObject); +mni = cell2mat(handles.currentDisplayMNI'); +if isempty(mni) + %errordlg('No cluster is picked up.','oops'); + set(handles.infoTextBox, 'string', 'No cluster is picked up.'); + beep + return; +end + +if ~isfield(handles, 'selectedCluster') | isempty(handles.selectedCluster) + try + xyz = handles.currentxyz'; + catch + xyz = spm_XYZreg('GetCoords',handles.hReg); + end + handles.selectedCluster = xyz'; +end + +intensity = cell2mat(handles.currentDisplayIntensity'); +cor = mni2cor(mni, handles.M{1}); +A = spm_clusters(cor'); +xyzcor = mni2cor(handles.selectedCluster, handles.M{1}); + +pos = []; +for ii = 1:size(xyzcor,1) + pos0 = find(cor(:,1)==xyzcor(ii,1) & cor(:,2)==xyzcor(ii,2) & cor(:,3)==xyzcor(ii,3)); + if isempty(pos0) + continue; + end + pos = [pos find(A==A(pos0(1)))]; +end +if isempty(pos) + %errordlg('No cluster is picked up.','oops'); + set(handles.infoTextBox, 'string', 'No cluster is picked up.'); + beep + return +end + +pos = unique(pos); + +tmpmni = mni(pos,:); +tmpintensity = intensity(pos); + +[B,I,J] = unique(tmpmni, 'rows'); +handles.currentDisplayMNI = {B}; +fprintf('Cluster defined from: %s \n', handles.imageFileName{1}); +assignin('base', 'currentDisplayMNI', handles.currentDisplayMNI ); +handles.currentDisplayIntensity = {tmpintensity(I,:)}; + +handles.currentxyz = handles.selectedCluster(end,:); + +[handles.hReg, handles.hSection, handles.hcolorbar] = Draw(handles.currentDisplayMNI, handles.currentDisplayIntensity, hObject, handles); +set(handles.figure,'currentaxes', handles.glassViewAxes); +xrange = xlim; +yrange = ylim; + +handles.selectedCluster = []; +try + delete(handles.hGlassText) +end + +guidata(hObject, handles); + +set(handles.thisClusterSizeEdit,'string', num2str(size(handles.currentDisplayMNI{1},1))); +str = get(handles.searchContentEdit, 'string'); +pos = findstr(' ', str); +str(pos) = []; +files = []; +for ii=1:length(handles.imageFileName) + [a,b,c] = fileparts(handles.imageFileName{ii}); + files = [files b]; +end +if ~isempty(files) + files = ['_from_' files]; +end + +set(handles.saveImageFileEdit, 'string', [str files '.img']); + +% list structure of voxels in this cluster +[a, b] = cuixuFindTDstructure(cell2mat(handles.currentDisplayMNI'), handles.DB, 0); +names = unique(b(:)); +index = NaN*zeros(length(b(:)),1); +for ii=1:length(names) + pos = find(strcmp(b(:),names{ii})); + index(pos) = ii; +end + +for ii=1:max(index) + report{ii,1} = names{ii}; + report{ii,2} = length(find(index==ii)); +end +for ii=1:size(report,1) + for jj=ii+1:size(report,1) + if report{ii,2} < report{jj,2} + tmp = report(ii,:); + report(ii,:) = report(jj,:); + report(jj,:) = tmp; + end + end +end +report = [{'structure','# voxels'}; {'--TOTAL # VOXELS--', length(a)}; report]; +% format long +% disp(b) +% disp(report) +% format +report2 = {sprintf('%s\t%s',report{1,2}, report{1,1}),''}; +for ii=2:size(report,1) + if strcmp('Unidentified', report{ii,1}); continue; end + report2 = [report2, {sprintf('%5d\t%s',report{ii,2}, report{ii,1})}]; +end + +report2 = [report2, {'','select and Ctrl-C to copy'}]; +% f = figure('unit','normalized', 'menubar','none', 'position', [0.3 0.2 0.2 min(0.7,0.016*length(report2))], 'name', 'cluster information', 'NumberTitle','off'); +% hEdit = uicontrol(f,'style','edit',... +% 'unit','normalized','position',[0 0 1 1],... +% 'horizontal','left',... +% 'BackgroundColor', 'w',... +% 'String', report2,... +% 'max',2,'min',0); + +set(handles.infoTextBox, 'string', report2); +assignin('base', 'ans', handles.currentDisplayMNI) +disp('Cluster coordinates in "ans"') +return + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% commonRegion push +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_commonRegionPush(hObject, eventdata) +handles = guidata(hObject); +if ~isfield(handles,'TF') | length(handles.TF)<=1 + %msgbox('Two or more images need to be loaded to find the common region.', 'info'); + set(handles.infoTextBox, 'string', 'Two or more images need to be loaded to find the common region.'); + beep + return; +end + +common = handles.currentDisplayMNI{1}; +for ii=2:length(handles.TF) + common = intersect(common, handles.currentDisplayMNI{ii}, 'rows'); +end + +if isempty(common) + %msgbox('No common region found.', 'info'); + set(handles.infoTextBox, 'string', 'No common region found.'); + beep + return; +end + +tmpMNI = cell2mat(handles.currentDisplayMNI'); +tmpIntensity = cell2mat(handles.currentDisplayIntensity'); +intensity = zeros(size(common,1),1); + +for ii=1:size(common,1) + pos = find(abs(tmpMNI(:,1)-common(ii,1))<0.1 & abs(tmpMNI(:,2)-common(ii,2))<0.1 & abs(tmpMNI(:,3)-common(ii,3))<0.1); + intensity(ii) = prod(tmpIntensity(pos)); +end + +handles.currentDisplayMNI = {common}; +handles.currentDisplayIntensity = {intensity}; +[handles.hReg, handles.hSection, handles.hcolorbar] = Draw(handles.currentDisplayMNI, handles.currentDisplayIntensity, hObject, handles); + +% if isfield(handles,'hLegend') +% try +% set(cell2mat(handles.hLegend'),'visible',{'off'}); +% end +% end +guidata(hObject, handles); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% volume push +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_volumePush(hObject, eventdata) +handles = guidata(hObject); +mni = cell2mat(handles.currentDisplayMNI'); +intensity = cell2mat(handles.currentDisplayIntensity'); + +xSPM.XYZ = mni2cor(mni, handles.M{1}); +xSPM.XYZ = xSPM.XYZ'; +xSPM.XYZmm = mni'; +xSPM.Z = (intensity'); +xSPM.M = handles.M{1}; +xSPM.DIM = handles.DIM{1}; +xSPM.STAT = handles.TF{1}; +if xSPM.STAT == 'T' + xSPM.STATstr = [xSPM.STAT '_{' num2str(handles.df{1}) '}']; + xSPM.df = [1 handles.df{1}]; +elseif xSPM.STAT == 'F' + xSPM.STATstr = [xSPM.STAT '_{' num2str(handles.df{1}(1)) ',' num2str(handles.df{1}(2)) '}']; + xSPM.df = [handles.df{1}]; +else + xSPM.STAT = 'T'; + xSPM.STATstr = [xSPM.STAT '_{' num2str(handles.df{1}) '}']; + xSPM.df = [1 handles.df{1}]; +end +xSPM.k = str2num(get(handles.clusterSizeThresholdEdit, 'string')); +xSPM.u = str2num(get(handles.intensityThresholdEdit, 'string')); +xSPM.u = xSPM.u(1); +xSPM.VOX = abs([xSPM.M(1,1) xSPM.M(2,2) xSPM.M(3,3)]); +xSPM.n = 1; + + +% SPM - structure containing SPM, distribution & filtering details +% - required fields are: +% .swd - SPM working directory - directory containing current SPM.mat +% .Z - minimum of n Statistics {filtered on u and k} +% .n - number of conjoint tests +% .STAT - distribution {Z, T, X or F} +% .df - degrees of freedom [df{interest}, df{residual}] +% .u - height threshold +% .k - extent threshold {voxels} +% .XYZ - location of voxels {voxel coords} +% .XYZmm - location of voxels {mm} +% .S - search Volume {voxels} +% .R - search Volume {resels} +% .FWHM - smoothness {voxels} +% .M - voxels - > mm matrix +% .VOX - voxel dimensions {mm} +% .Vspm - mapped statistic image(s) +% .Ps - uncorrected P values in searched volume (for FDR) + +xSPM.S = length(handles.intensity{1}); +xSPM.R = [3 27.0931 276.3307 498.1985]; +xSPM.FWHM = [2.9746 3.1923 2.8600]; + +if get(handles.positiveIntensityRadio, 'Value') + xSPM.Z = abs(xSPM.Z); + if xSPM.STAT == 'T'; xSPM.Ps = (1-spm_Tcdf(intensity, xSPM.df(2))); end + if xSPM.STAT == 'F'; xSPM.Ps = (1-spm_Fcdf(intensity, xSPM.df)); end +end + +if get(handles.negativeIntensityRadio, 'Value'); + xSPM.Z = abs(xSPM.Z); + if xSPM.STAT == 'T'; xSPM.Ps = (1-spm_Tcdf(-intensity, xSPM.df(2))); end + if xSPM.STAT == 'F'; xSPM.Ps = (1-spm_Fcdf(-intensity, xSPM.df)); end +end + +if get(handles.allIntensityRadio, 'Value'); + if xSPM.STAT == 'T'; xSPM.Ps = (1-spm_Tcdf((intensity), xSPM.df(2))); end + if xSPM.STAT == 'F'; xSPM.Ps = (1-spm_Fcdf((intensity), xSPM.df)); end +end + +if findstr('SPM2',spm('ver')) + P = spm_get([0 1],'SPM.mat','locate the corresponding SPM.mat'); +elseif findstr('SPM5',spm('ver')) + P = spm_select([0:1],'SPM.mat','locate the corresponding SPM.mat'); +end + +if ~isempty(P) + load(P); + xSPM.FWHM = SPM.xVol.FWHM; + xSPM.R = SPM.xVol.R; + xSPM.S = SPM.xVol.S; +else + warndlg(['You did not input SPM.mat. The listed result may not be correct.'], 'SPM.mat missing'); +end + +spm_list('List',xSPM,handles.hReg); + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% display +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_displayPush(hObject, eventdata) +handles = guidata(hObject); +spm_image('init', handles.imageFileName); + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% all in one +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_allinonePush(hObject, eventdata) +try + if findstr('SPM2',spm('ver')) + P = spm_get([0:100],'*IMAGE','Select image files'); + elseif findstr('SPM5',spm('ver')) + P = spm_select(Inf,'image','Select image files'); + end + +catch + return; +end +if isempty(P) + return +end + +handles = guidata(hObject); +%if ~isfield(handles,'hReg') | ~isfield(handles,'hSection') | ~isfield(handles,'hcolorbar') +tmp = [0 0 0]; +[handles.hReg, handles.hSection, handles.hcolorbar] = Draw(tmp([],:), [], hObject, handles); +%end + +colors=mycolourset; +for ii=1:size(P,1) + thisfilename = deblank(P(ii,:)); + [handles.imageFileName, handles.M, handles.DIM, handles.TF, handles.df, handles.mni, handles.intensity] = getImageFile(thisfilename); + cor = mni2cor(handles.mni, handles.M); + spm_orthviews('addcolouredblobs',1,cor',handles.intensity',handles.M,colors(mod(ii,6)+1,:)); +end +spm_orthviews('Redraw'); + +guidata(hObject, handles); + +% contents = get(handles.sectionViewListbox,'String'); +% currentsel = contents{get(handles.sectionViewListbox,'Value')}; +% sectionViewTargetFile = getSectionViewTargetFile(handles.spmdir, currentsel); +% +% % spm_image('init', sectionViewTargetFile); +% % spm_image('addblobs'); +% % return + +% guidata(hObject, handles); +% load xSPM; +% VOL.XYZ = xSPM.XYZ; +% VOL.Z = xSPM.Z; +% VOL.M = handles.M; + +%addcolouredimage(handles.hSection, '333.img',[1 0 1]); +% uigetfiles +% nblobs = 4; +% for i=1:nblobs, +% %[SPM,VOL] = spm_getSPM; +% %c = spm_input('Colour','+1','m','Red blobs|Yellow blobs|Green blobs|Cyan blobs|Blue blobs|Magenta blobs',[1 2 3 4 5 6],1); +% c = i; +% VOL.XYZ = ceil(rand(3,20)*20); +% VOL.Z = randn(1,20); +% VOL.M = handles.M; +% colours = [1 0 0;1 1 0;0 1 0;0 1 1;0 0 1;1 0 1]; +% spm_orthviews('addcolouredblobs',1,VOL.XYZ,VOL.Z,VOL.M,colours(c,:)); +% end; + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% overlay a brain region Push +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_overlayPush(hObject, eventdata) +handles = guidata(hObject); + +tobeoverlay = deblank(get(handles.overlayEdit, 'string')); + +if isempty(tobeoverlay) + return; +end + +tobeoverlay = str2cell(tobeoverlay, ' '); +if isnumeric(tobeoverlay{1}) + if length(tobeoverlay{1})>1 + errordlg(['Your input, ' deblank(get(handles.overlayEdit, 'string')) ', seems coincide with a matlab constant.'], 'error'); + return + end + for ii=1:length(tobeoverlay) + tobeoverlay{ii} = num2str(tobeoverlay{ii}); + end +end +fn = fieldnames(handles.wholeMaskMNIAll); +for ii=1:length(tobeoverlay) + pos{ii} = []; + for jj=1:length(fn) + x = []; + if ~isempty(str2num(tobeoverlay{ii})) + y = str2cell(fn{jj},'_'); + for kk=1:length(y) + x = isequal(tobeoverlay{ii}, y{kk}); + if x; break;end; + end + if x==0; x = []; end; + else + x = findstr(lower(tobeoverlay{ii}), lower(fn{jj})); + end + if ~isempty(x) + pos{ii} = [pos{ii} jj]; + end + end +end +common = pos{1}; +for ii=2:length(pos) + common = intersect(common, pos{ii}); +end + +if isempty(common) + %warndlg(['I don'' find ' deblank(get(handles.overlayEdit, 'string')) '.'], 'oops'); + set(handles.infoTextBox, 'string', ['I don'' find ' deblank(get(handles.overlayEdit, 'string')) '.']); + return +end + +mask = []; +for ii=1:length(common) + eval(['mask = [mask; handles.wholeMaskMNIAll.' fn{common(ii)} '];']); +end + +if isempty(mask) + %warndlg(['I don'' find ' deblank(get(handles.overlayEdit, 'string')) '.'], 'oops'); + set(handles.infoTextBox, 'string', ['I don'' find ' deblank(get(handles.overlayEdit, 'string')) '.']); + return; +end + +try + handles.mni; +catch + delete(gcf); + xjview(mask); + return; +end + +handles.imageFileName = [handles.imageFileName, {deblank(get(handles.overlayEdit, 'string'))}]; +handles.M = [handles.M, {handles.M{1}}]; +handles.DIM = [handles.DIM, {handles.DIM{1}}]; +handles.currentDisplayMNI = [handles.currentDisplayMNI, {mask}]; +m = max(abs(cell2mat(handles.currentDisplayIntensity'))); +if ~isempty(m) + handles.currentDisplayIntensity = [handles.currentDisplayIntensity, {-2*m*ones(size(mask,1),1)}]; +else + handles.currentDisplayIntensity = [handles.currentDisplayIntensity, {ones(size(mask,1),1)}]; +end +[handles.hReg, handles.hSection, handles.hcolorbar] = Draw(handles.currentDisplayMNI, handles.currentDisplayIntensity, hObject, handles); +return + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% overlay a brain region Edit +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_overlayEdit(hObject, eventdata) +handles = guidata(hObject); +CallBack_overlayPush(handles.overlayPush, eventdata); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% overlay a brain region popup +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_overlayPop(hObject, eventdata) +handles = guidata(hObject); +names = get(hObject, 'string'); +value = get(hObject, 'value'); +set(handles.overlayEdit, 'string', names{value}); +CallBack_overlayEdit(handles.overlayEdit, eventdata); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% search xBrain.org and other databases +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_searchPush(hObject, eventdata) + +try + handles = guidata(hObject); + searchContent = get(handles.searchContentEdit,'string'); + searchEngine = get(handles.searchEnginePop,'string'); + searchEngine = searchEngine{get(handles.searchEnginePop,'value')}; + + + xyz = str2num(searchContent); + searchMode=get(handles.searchContentEdit,'UserData'); + + switch searchMode + case 'auto'; + xyz = spm_XYZreg('GetCoords',handles.hReg); + handles.currentxyz = xyz'; + guidata(hObject, handles); + [a,b] = cuixuFindTDstructure(xyz', handles.DB, 0); + try + brodmann = strmatch('Brodmann',b) + brodmann = b{brodmann}; + brodmann = brodmann(1:end-4); + end; + try + region = b{3}; + region = region(1:end-4); + end + + case 'manual' + if isempty(xyz) + searchMode='text'; + elseif length(xyz)==3 + searchMode='xyz'; + end + end + + + switch searchEngine + case 'Brede' + switch searchMode + case 'auto' + searchString = sprintf(' %0.0f',xyz') + set(handles.searchContentEdit,'string',searchString); + case 'xyz' + searchString = num2str(xyz');%searchContent=sprintf('+%0.0f',searchContent) + case 'text' + end + urlstr = ['http://hendrix.imm.dtu.dk/cgi-bin/brede_loc_query.pl?q=' searchString ]; + case 'xBrain.org' + switch searchMode + case { 'auto' 'xyz'} + xbrainSearchField = 'mni or tal&mnidistance=20'; + searchString = sprintf(' %0.1f',xyz')% searchString = num2str(xyz'); + case 'text' + xbrainSearchField = 'region'; + searchString = searchContent; + end + set(handles.searchContentEdit,'string',searchString); + urlstr = ['http://people.hnl.bcm.tmc.edu/cuixu/cgi-bin/bmd/paper.pl?search_content=' searchString '&search_field=' xbrainSearchField]; + case 'Google Scholar' + if isequal(searchMode,'auto' ) + searchString = { brodmann region }; + searchString(cellfun('isempty',searchString))=[]; + searchString = sprintf('%%22%s%%22|',searchString{:}); + try + searchString = searchString(1:end-1); + end + %set(handles.searchContentEdit,'string',searchString); + end + urlstr = ['http://scholar.google.com/scholar?q=' searchString ]; + case 'Pubmed' + switch searchMode + case 'auto' + searchString = { brodmann region }; + searchString(cellfun('isempty',searchString))=[]; + searchString = sprintf('%%22%s%%22 OR ',searchString{:}); + try + searchString = searchString(1:end-4); + end + %searchString = brodmann; + case 'text' + searchString = searchContent; + end + % urlstr = ['http://www.ncbi.nlm.nih.gov/entrez/query.fcgi?cmd=PureSearch&db=pubmed&details_term=(' searchString ')']; + urlstr = ['http://www.ncbi.nlm.nih.gov/sites/entrez?cmd=search&db=pubmed&pubmedfilters=true&term=(' searchString ')']; + case 'Wikipedia' + if isequal(searchMode,'auto') + searchString = region; + end + urlstr = ['http://en.wikipedia.org/w/index.php?search=%22' searchString '%22']; + end + set(handles.searchContentEdit,'UserData',searchMode); +catch + urlstr = 'http://www.google.com'; +end + +try + web(urlstr,'-browser'); +catch + if exist('c:\program Files\mozilla Firefox\firefox.exe','file') + system(['"c:\program Files\mozilla Firefox\firefox.exe" "' urlstr '"']) + else + web(urlstr); + end +end + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% control panel on or off +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function controlHide(handles, status) + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% get multiple values from a string deliminated delim +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function out = str2cell(str, delim) +if ~exist('delim') + delim = '; '; +end + +[out{1},b] = strtok(str, delim); +ii = 2; +while ~isempty(b) + [out{ii},b] = strtok(b, delim); + ii = ii+1; +end + +for ii=1:length(out) + out2{ii} = str2num(out{ii}); + if isempty(out2{ii}) + return; + end +end + +for ii=1:length(out) + out{ii} = str2num(out{ii}); +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% cell2str +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function str = cell2str(acell, delim) +if ~exist('delim') + delim = ';'; +end + +str = []; + +if length(acell)==1 + if isstr(acell{1}) + str = acell{1}; + else + str = num2str(acell{1}); + end +else + for ii=1:length(acell) + if isstr(acell{ii}) + str = [str acell{ii}, delim ' ']; + else + str = [str num2str(acell{ii}), delim ' ']; + end + end + str(end-1:end)=[]; +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% cellmax, find max in each element, return a cell +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function MAXX = cellmax(acell, absolute) + +if ~exist('absolute') + absolute = ''; +end + +MAXX = []; +for ii=1:length(acell) + if strfind('abs',absolute) + MAXX = [MAXX max(abs(acell{ii}))]; + else + MAXX = [MAXX max(acell{ii})]; + end +end +MAXX = num2cell(MAXX); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% maxcell +%%% find max of all numbers in the whole cell, return a single value +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function MAXX = maxcell(acell, absolute) + +if ~exist('absolute') + absolute = ''; +end +MAXX = cellmax(acell, absolute); +MAXX = max(cell2mat(MAXX)); + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% p2t +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function t = p2t(p, df, TF) +if ~iscell(p) + if upper(TF)=='T' | upper(TF)=='S' + t = spm_invTcdf(1-p,df); + elseif upper(TF) == 'F' + t = spm_invFcdf(1-p,df); + end +else + for ii=1:length(p) + t{ii} = p2t(p{ii},df{ii},TF{ii}); + end +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% s2t, s is defined as -log10(p) +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function t = s2t(s, df, TF) + +if ~iscell(s) + p = 10^(-s); + if upper(TF)=='T' | upper(TF)=='S' + t = spm_invTcdf(1-p,df); + elseif upper(TF) == 'F' + t = spm_invFcdf(1-p,df); + end +else + for ii=1:length(p) + t{ii} = s2t(s{ii},df{ii},TF{ii}); + end +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% t2p +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function p = t2p(t, df, TF) +if ~iscell(t) + if upper(TF)=='T' | upper(TF)=='S' + p = 1-spm_Tcdf(t,df); + elseif upper(TF) == 'F' + p = 1-spm_Fcdf(t,df); + end +else + for ii=1:length(t) + p{ii} = t2p(t{ii},df{ii},TF{ii}); + end +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% t2s, s is defined as -log10(p) +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function s = t2s(t, df, TF) +if ~iscell(t) + if upper(TF)=='T' | upper(TF)=='S' + p = 1-spm_Tcdf(t,df); + elseif upper(TF) == 'F' + p = 1-spm_Fcdf(t,df); + else + p = 0.1; + end + s = -log10(p); +else + for ii=1:length(t) + s{ii} = t2s(t{ii},df{ii},TF{ii}); + end +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% mni2mask +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function mask = mni2mask(coords, targetfilename, intensity, M, DIM, templateFile, isMask) +% function mask = mni2mask(coords, targetfilename, intensity, templateFile) +% make mask from coordinates +% +% coords: a Nx3 column of 3-d coordinates in MNI +% space +% targetfilename: (optional) the image files to be written +% intensity: (optional) Nx1, the values of each coordinate. +% M: rotation matrix +% DIM: dimension +% templateFile: (optional) the templateFile from which we can get the right +% dimensions. +% isMask: if this variable exist and equal to 1, then all non-zero +% intensities will be set to 1 +% +% Xu Cui +% 2004/11/18 + +if ~exist('intensity') + intensity = ones(size(coords,1),1); +end +thistemplateFile = ''; +if exist('templateFile') + if ~isempty(templateFile) + thistemplateFile = templateFile; + end +end + +if isempty(thistemplateFile) + V.mat = [... + -4 0 0 84; ... + 0 4 0 -116; ... + 0 0 4 -56; ... + 0 0 0 1]; + V.dim = [41 48 35 16]; + if exist('M') + V.mat = M; + end + if exist('DIM') + V.dim = DIM; + V.dim(4) = 16; + end + V.fname = targetfilename; + V.descrip = 'our own image'; +else + V = spm_vol(templateFile); + V.fname = targetfilename; + if isfield(V, 'descrip') + V.descrip = ['my image from ' V.descrip]; + else + V.descrip = 'my own image'; + end +end + +thisismask = 0; +if exist('isMask') + if isMask == 1 + thisismask = 1; + end +end +if thisismask + V.descrip = 'my mask'; + intensity = ones(size(intensity)); +end + +O = zeros(V.dim(1),V.dim(2),V.dim(3)); + +coords = mni2cor(coords,V.mat); + + +for ii=1:size(coords,1) + O(coords(ii,1),coords(ii,2),coords(ii,3)) = intensity(ii); +end + +if exist('targetfilename') + V = spm_write_vol(V,O); +end + +mask = O; + +return; + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% get image file information +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function [imageFile,M,DIM,TF,df,mni,intensity] = getImageFile(thisfilename) +% function imageFile = getImageFile(filename) +% get the information of this/these image file(s) +% +% thisfilename: (optional) if doesn't give file name, then show a open file +% dialog +% imageFile: the full name of the selected file (if user pressed cancel, +% imageFile == 0 +% M: M matrix (rotation matrix) +% DIM: dimension +% TF: t test or f test? 'T' or 'F' +% df: degree of freedome +% mni: mni coord +% intensity: intensity of each mni coord +% +% Note: The returned variables are cellarrays. +% +% Xu Cui +% last revised: 2005-05-03 + +if nargin < 1 | isempty(thisfilename) + if findstr('SPM2',spm('ver')) + P0 = spm_get([0:100],'*IMAGE','Select image files'); + elseif findstr('SPM5',spm('ver')) + P0 = spm_select(Inf,'image','Select image files'); + end + + % try + % P0 = spm_get([0:100],'*IMAGE','Select image files'); + % catch + % P0 = []; + % [FileName,PathName] = uigetfile({'*.img';'*.IMG';'*.*'},'Select image files','MultiSelect','on'); + % if isstr(FileName) + % P0 = {fullfile(PathName, FileName)}; + % elseif iscellstr(FileName) + % for ii=1:length(FileName) + % P0{ii} = fullfile(PathName, FileName{ii}); + % end + % else + % P0 = []; + % end + % end + try + if isempty(P0) + imageFile = '';M=[];DIM=[];TF=[];df=[];mni=[];intensity=[]; + return + end + end + for ii=1:size(P0,1) + P{ii} = deblank(P0(ii,:)); + end +else + if isstr(thisfilename) + P = {thisfilename}; + elseif iscellstr(thisfilename) + P = thisfilename; + else + disp('Error: In getImageFile: I don''t understand the input.'); + imageFile = '';M=[];DIM=[];TF=[];df=[];mni=[];intensity=[]; + return + end +end + +global LEFTRIGHTFLIP_; + +for ii=1:length(P) + imageFile{ii} = P{ii}; + [cor{ii}, intensity{ii}, tmp{ii}, M{ii}, DIM{ii}, TF{ii}, df{ii}] = mask2coord(imageFile{ii}, 0); + if LEFTRIGHTFLIP_ == 1 + M{ii}(1,:) = -M{ii}(1,:); + end + mni{ii} = cor2mni(cor{ii}, M{ii}); +end + + + + + + + + + + + + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% cuixuFindTDstructure +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function [onelinestructure, cellarraystructure] = cuixuFindTDstructure(pos, DB, TALMNI) +% function [onelinestructure, cellarraystructure] = cuixuFindTDstructure(pos, DB, TALMNI) +% +% this function converts Talairach Daemon coordinate or MNI coordinate to a description of +% brain structure. +% +% pos: the coordinate (MNI or Talairach coordinate, defined by TALMNI) of the position in the brain, in mm +% DB: the database structure (see below for detailed explanation). If you don't input this argument, there +% should be a file called 'TDdatabase.mat' in the current directory +% TALMNI: 1 if pos is talairach coordinate, 0 if pos is MNI coordinate. 1 +% by default. +% +% onelinestructure: a one-line description of the returned brain +% structure +% cellarraystructure: a cell array of size 5, each cell contains a string +% describing the structure of the brain in a certain level. +% +% Example: +% [onelinestructure, cellarraystructure] = cuixuFindTDstructure([-24 24 48], DB, 1) +% [onelinestructure, cellarraystructure] = cuixuFindTDstructure([-24 24 48]) +% then +% onelinestructure = // Left Cerebrum // Frontal Lobe (L) // Middle Frontal Gyrus (L) // Gray Matter (L) // Brodmann area 8 (L) +% cellarraystructure = 'Left Cerebrum' 'Frontal Lobe (L)' [1x24 char] 'Gray Matter (L)' 'Brodmann area 8 (L)' +% +% Xu Cui +% 2004-6-28 +% + +%---------------------------------------------------------------------------------- +% DB strcture +%---------------------------------------------------------------------------------- +%------------------------------- +% Grid specification parameters: +%------------------------------- +% minX - min X (mm) +% maxX - max X (mm) +% voxX - voxel size (mm) in X direction +% minY - min Y (mm) +% maxY - max Y (mm) +% voxY - voxel size (mm) in Y direction +% minZ - min Z (mm) +% maxZ - max Z (mm) +% voxZ - voxel size (mm) in Z direction +% nVoxX - number of voxels in X direction +% nVoxY - number of voxels in Y direction +% nVoxZ - number of voxels in Z direction +%------------------------------- +% Classification parameters: +%------------------------------- +% numClass - number of classification types +% cNames - cNames{i} - cell array of class names for i-th CT +% numClassSize - numClassSize(i) - number of classes for i-th CT +% indUnidentified - indUnidentified(i) - index of "indUnidentified" class for i-th CT +% volClass - volClass{i}(j) - number of voxels in class j for i-th CT +% +% data - N x numClass matrix of referencies; let +% x y z coordinates in mm (on the grid) and +% nx = (x-minX)/voxX +% ny = (y-minY)/voxY +% nz = (z-minZ)/voxZ +% ind = nz*nVoxX*nVoxY + ny*nVoxX + nx + 1 +% data(ind, i) - index of the class for i-th CT in cNames{i} to +% which (x y z) belongs, i.e. +% cNames{i}{data(ind, i)} name of class for i-th CT +%---------------------------------------------------------------------------------- + +if nargin==1 + load('TDdatabase.mat'); + TALMNI = 1; +elseif nargin == 2 + TALMNI = 1; +end + +if(TALMNI == 1) + pos = tal2mni(pos); +elseif(TALMNI == 0) + []; +else + disp('TALMNI should be 1 or 0.'); +end + +pos(:,1) = DB.voxX*round(pos(:,1)/DB.voxX); +pos(:,2) = DB.voxY*round(pos(:,2)/DB.voxY); +pos(:,3) = DB.voxZ*round(pos(:,3)/DB.voxZ); + +min = []; +vox = []; +for(i=1:size(pos,1)) + min = [min; DB.minX DB.minY DB.minZ]; + vox = [vox; DB.voxX DB.voxY DB.voxZ]; +end +n_pos = (pos - min)./vox; + +nx = n_pos(:,1); +ny = n_pos(:,2); +nz = n_pos(:,3); +index = nz*DB.nVoxX*DB.nVoxY + ny*DB.nVoxX + nx + 1; +indMax = size(DB.data, 1); + +onelinestructuretmp = []; +onelinestructure = []; +for(j=1:size(pos,1)) + onelinestructuretmp = []; + for(i=1:DB.numClass) + + if (index(j) <= 0 | index(j) > indMax) + ind(j) = DB.indUnidentified(i); + else + ind(j) = DB.data(index(j), i); + end + + cellarraystructure{j,i} = DB.cNames{i}{ind(j)}; + onelinestructuretmp = [onelinestructuretmp ' // ' cellarraystructure{j,i}]; + end + onelinestructure{j} = onelinestructuretmp; +end + +return + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% mask2coord +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function [cor, intensity, cor_singlecolumn,M,DIM,TF,df] = mask2coord(mask, checkdimension) +% [cor, intensity, cor_singlecolumn] = mask2coord(mask, checkdimension) +% +% This is to retrieve the coordinate of a mask file, or a matrix of 3-D +% +% mask: an image file or a matrix (3-D), with some of the elements are +% non-zeros +% checkdimension: check if the dimension is checkdimension, if not, return empty +% matrix +% cor: a N*3 matrix, which each row a coordinate in matrix +% intensity: a N*1 matrix, which encodes the intensity of each voxel. +% cor_singlecolumn: a N*1 matrix, each row is the index in the matrix +% M: rotation matrix +% DIM: dimension +% TF: t test or f test? 'T','F' or [] +% df: degree of freedome for T/F test +% +% Example: +% mask = zeros(4,3,2); +% mask(1,2,1) = 1; +% mask(3,2,2) = 1; +% mask2coord(mask) +% +% mask2coord('spmT_0002.img') +% mask2coord('spmT_0002.img',[41 48 35]) +% +% Xu Cui +% 2004-9-20 +% last revised: 2005-04-30 + +if nargin < 2 + checkdimension = 0; +end + +if ischar(mask) + V = spm_vol(mask); + mask = spm_read_vols(V); + M = V.mat; + DIM = V.dim; + TF = 'T'; + T_start = strfind(V.descrip,'SPM{T_[')+length('SPM{T_['); + if isempty(T_start); T_start = strfind(V.descrip,'SPM{F_[')+length('SPM{F_['); TF='F'; end + if isempty(T_start) + TF=[]; df=[]; + else + T_end = strfind(V.descrip,']}')-1; + df = str2num(V.descrip(T_start:T_end)); + end +else + M = []; + TF = []; + df = []; +end + +dim = size(mask); +if length(checkdimension)==3 + if dim(1)~= checkdimension(1) | dim(2)~= checkdimension(2) | dim(3)~= checkdimension(3) + y = []; + disp('dimension doesn''t match') + return + end +end + +pos = find(mask~=0); +intensity = mask(pos); + +y = zeros(length(pos),3); + +y(:,3) = ceil(pos/(dim(1)*dim(2))); +pos = pos - (y(:,3)-1)*(dim(1)*dim(2)); +y(:,2) = ceil(pos/dim(1)); +pos = pos - (y(:,2)-1)*(dim(1)); +y(:,1) = pos; + +cor = y; +cor_singlecolumn = pos; +DIM = dim; +return + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% mni2cor +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function coordinate = mni2cor(mni, T) +% function coordinate = mni2cor(mni, T) +% convert mni coordinate to matrix coordinate +% +% mni: a Nx3 matrix of mni coordinate +% T: (optional) transform matrix +% coordinate is the returned coordinate in matrix +% +% caution: if T is not specified, we use: +% T = ... +% [-4 0 0 84;... +% 0 4 0 -116;... +% 0 0 4 -56;... +% 0 0 0 1]; +% +% xu cui +% 2004-8-18 +% + +if isempty(mni) + coordinate = []; + return; +end + +if ~exist('T') + T = ... + [-4 0 0 84;... + 0 4 0 -116;... + 0 0 4 -56;... + 0 0 0 1]; +end + +coordinate = [mni(:,1) mni(:,2) mni(:,3) ones(size(mni,1),1)]*(inv(T))'; +coordinate(:,4) = []; +coordinate = round(coordinate); +return; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% cor2mni +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function mni = cor2mni(cor, T) +% function mni = cor2mni(cor, T) +% convert matrix coordinate to mni coordinate +% +% cor: an Nx3 matrix +% T: (optional) rotation matrix +% mni is the returned coordinate in mni space +% +% caution: if T is not given, the default T is +% T = ... +% [-4 0 0 84;... +% 0 4 0 -116;... +% 0 0 4 -56;... +% 0 0 0 1]; +% +% xu cui +% 2004-8-18 +% last revised: 2005-04-30 + +if nargin == 1 + T = ... + [-4 0 0 84;... + 0 4 0 -116;... + 0 0 4 -56;... + 0 0 0 1]; +end + +cor = round(cor); +mni = T*[cor(:,1) cor(:,2) cor(:,3) ones(size(cor,1),1)]'; +mni = mni'; +mni(:,4) = []; +return; + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% draw +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function [hReg, hSection, hcolorbar] = Draw(mniCoord, intensity, hObject, handles) + +try + delete(handles.hcolorbar); +end +try + delete(handles.hReg); +end +try + delete(handles.hSection); +end +try + cla(handles.glassViewAxes); +end + +try + H = findobj(get(handles.figure,'Children'),'flat','Type','axes'); + un = cellstr(get(H,'Units')); + pos = get(H,'position'); + + for ii=1:length(H) + if findstr('pixels', un{ii}) + continue; + end + if pos{ii}(1)>0.4 + delete(H(ii)); + end + end +end + +sectionViewTargetFile = handles.sectionViewTargetFile; + +if ~iscell(mniCoord) | (iscell(mniCoord) & length(mniCoord)==1)% multiple input? no + if (iscell(mniCoord) & length(mniCoord)==1) + mniCoord = mniCoord{1}; + intensity = intensity{1}; + handles.M = handles.M{1}; + handles.DIM = handles.DIM{1}; + end + if max(intensity)*min(intensity) < 0 + [hReg, hSection, hcolorbar] = cuixuSectionView(mniCoord,intensity,sectionViewTargetFile,hObject,handles); + else + [hReg, hSection, hcolorbar] = cuixuSectionView(mniCoord,abs(intensity),sectionViewTargetFile,hObject,handles); + end + + if size(mniCoord,1)>1 + pos1 = find(intensity>=0); + pos2 = find(intensity<0); + if ~isempty(pos1) & ~isempty(pos2) + if get(handles.renderViewCheck, 'Value'); cuixuRenderView(mniCoord(pos1,:),intensity(pos1,:),mniCoord(pos2,:),-intensity(pos2,:)); end + else + if get(handles.renderViewCheck, 'Value'); cuixuRenderView(mniCoord, abs(intensity)); end + end + end +else % multiple input? yes + [hReg, hSection, hcolorbar] = cuixuSectionView(mniCoord,intensity,sectionViewTargetFile,hObject,handles); + + mniCoordtmp=[]; + intensitytmp=[]; + for ii=1:length(mniCoord) + mniCoordtmp = [mniCoordtmp; mniCoord{ii}]; + intensitytmp = [intensitytmp; intensity{ii}]; + end + mniCoord = mniCoordtmp; + intensity = intensitytmp; + if size(mniCoord,1)>1 + pos1 = find(intensity>=0); + pos2 = find(intensity<0); + if ~isempty(pos1) & ~isempty(pos2) + if get(handles.renderViewCheck, 'Value'); cuixuRenderView(mniCoord(pos1,:),intensity(pos1,:),mniCoord(pos2,:),-intensity(pos2,:)); end + else + if get(handles.renderViewCheck, 'Value'); cuixuRenderView(mniCoord, abs(intensity)); end + end + end + +end + +try + spm_XYZreg('SetCoords',handles.currentxyz',hReg); +end + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% cuixuSectionView +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function [hReg, hSection, hcolorbar] = cuixuSectionView(mniCoord, intensity, targetFile, hObject,handles) +% function h = cuixuSectionView(mniCoord, intensity) +% This is to project your coordinate to section view +% +% mniCoord: the mni coordinate, N*3 matrix +% intensity: the plot intensity of the spots, 1*N matrix. The intensity +% could be t value, for example. +% +% a special feature of this function is: it automatically seperate the +% negative and positive intensity and use hot and cold color to represent +% them. +% +% h: the returned handle for the axes +% +% SEE ALSO: cuixuView cuixuGlassView cuixuRenderView +% +% Xu Cui +% 2004/11/11 + +if ~iscell(mniCoord) | (iscell(mniCoord) & length(mniCoord)==1)% multiple input? no + multiple = 0; + if (iscell(mniCoord) & length(mniCoord)==1) + mniCoord = mniCoord{1}; + intensity = intensity{1}; + handles.M = handles.M{1}; + handles.DIM = handles.DIM{1}; + end +else + multiple = 1; + mniCoordtmp = []; + intensitytmp = []; + for ii=1:length(mniCoord) + mniCoordtmp = [mniCoordtmp; mniCoord{ii}]; + intensitytmp = [intensitytmp; intensity{ii}]; + % for multiple input + cSPM{ii}.XYZ = mni2cor(mniCoord{ii}, handles.M{ii}); + cSPM{ii}.XYZ = cSPM{ii}.XYZ'; + cSPM{ii}.Z = abs(intensity{ii}); + cSPM{ii}.M = handles.M{ii}; + cSPM{ii}.DIM = handles.DIM{ii}'; + end + mniCoord = mniCoordtmp; + intensity = intensitytmp; + handles.M = handles.M{1}; + handles.DIM = handles.DIM{1}; +end + +SPM.XYZ = mni2cor(mniCoord, handles.M); +SPM.XYZ = SPM.XYZ'; +SPM.Z = intensity; +SPM.M = handles.M; +SPM.DIM = handles.DIM'; + +%%%%%%%%%%%%%%% Reg %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +handles.colormap = colormap; + +coor = mniCoord; +xSPM = SPM; +xSPM.XYZmm = mniCoord'; +axes(handles.glassViewAxes) +WS = spm('WinScale'); +FS = spm('FontSizes'); + +Finter = gcf; +hReg = uicontrol(Finter,'Style','Frame','Position',[60 100 300 300].*WS,... + 'Visible','off'); +[hReg,xyz] = spm_XYZreg('InitReg',hReg,xSPM.M,xSPM.DIM,[0;0;0]); + +hFxyz = spm_results_ui('DrawXYZgui',xSPM.M,xSPM.DIM,xSPM,xyz,Finter); +spm_XYZreg('XReg',hReg,hFxyz,'spm_results_ui'); + +hMIPax =gca ; +setcolormap('gray'); + +hMIPax = spm_mip_ui(xSPM.Z,coor',xSPM.M,xSPM.DIM,hMIPax); +spm_XYZreg('XReg',hReg,hMIPax,'spm_mip_ui'); + +colormap(handles.colormap); +setcolormap('gray-hot'); + +if isempty(handles.M) + spm_XYZreg('SetReg',handles.structureEdit,hReg); +else + set(handles.structureEdit, 'UserData', struct('hReg',hReg,'M',handles.M,'D', handles.DIM,'xyx',[0 0 0])); +end + +spm_XYZreg('Add2Reg',hReg,handles.structureEdit,@CallBack_structureEdit); + +%%%%%%%%%%%%%%% Reg end %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +if multiple == 0 + [hgraph, hSection, hcolorbar] = spm_sections(SPM,hReg,targetFile,handles.sectionViewPosition); +else + [hgraph, hSection, hcolorbar] = spm_sections(cSPM,hReg,targetFile,handles.sectionViewPosition); +end + +return + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% spm_sections +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function [Fgraph, hMe, hcolorbar] = spm_sections(SPM,hReg,targetFile,sectionViewPosition) +% rendering of regional effects [SPM{Z}] on orthogonal sections +% FORMAT spm_sections(SPM,hReg) +% +% SPM - xSPM structure containing details of excursion set +% hReg - handle of MIP register +% +% see spm_getSPM for details +%_______________________________________________________________________ +% +% spm_sections is called by spm_results and uses variables in SPM and +% VOL to create three orthogonal sections though a background image. +% Regional foci from the selected SPM are rendered on this image. +% +%_______________________________________________________________________ +% @(#)spm_sections.m 2.14 John Ashburner 02/09/05 + +Fgraph = gcf; + +spms = fullfile(spm('dir'),'canonical', 'single_subj_T1.mnc'); +if exist('targetFile') + spms = targetFile; +end + +spm_orthviews('Reset'); +global st +st.fig = Fgraph; +st.Space = spm_matrix([0 0 0 0 0 -pi/2])*st.Space; + +spm_orthviews('Image',spms,sectionViewPosition); % position +spm_orthviews('MaxBB'); +spm_orthviews('register',hReg); +if ~iscell(SPM) + spm_orthviews('addblobs',1,SPM.XYZ,SPM.Z,SPM.M); +elseif length(SPM) == 1 + SPM = SPM{1}; + spm_orthviews('addblobs',1,SPM.XYZ,SPM.Z,SPM.M); +else + colors = mycolourset; + for ii=1:length(SPM) + spm_orthviews('addcolouredblobs',1,SPM{ii}.XYZ, SPM{ii}.Z, SPM{ii}.M, colors(ii,:)); + end +end +spm_orthviews('Redraw'); + +hMe = st.registry.hMe; +try + hcolorbar = st.vols{1}.blobs{1}.cbar; +catch + hcolorbar = 0; +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% cuixuRenderView +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function h = cuixuRenderView(mniCoord, intensity, varargin) +% function h = cuixuRenderView(mniCoord1, intensity1, mniCoord2, intensity2, mniCoord3, intensity3) +% This is to project your coordinate to render view +% +% mniCoord: the mni coordinate, N*3 matrix +% intensity: the plot intensity of the spots, 1*N matrix. The intensity +% could be t value, for example. +% +% You can input 1, 2, or 3 pairs of coordinates and intensity. +% +% h: the returned handle for the figure +% +% Xu Cui +% 2004/11/11 + +global M_; +global DIM_; + +if nargin < 3 + dat.XYZ = mni2cor(mniCoord, M_); + dat.XYZ = dat.XYZ'; + if nargin < 2 + dat.t = ones(size(mniCoord,1),1); + else + dat.t = intensity; + end + dat.mat = M_; + dat.dim = DIM_; +else + if mod(nargin,2) ~=0 + disp('You should put even number of parameters.') + return; + end + for ii=1:(2+length(varargin))/2 + if ii==1 + dat(ii).XYZ = mni2cor(mniCoord, M_); + dat(ii).t = intensity; + else + dat(ii).XYZ = mni2cor(varargin{2*(ii-1)-1}, M_); + dat(ii).t = varargin{2*(ii-1)}; + end + dat(ii).XYZ = dat(ii).XYZ'; + dat(ii).mat = M_; + dat(ii).dim = DIM_; + end +end + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% spm_render +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +h = spm_render(dat,1,fullfile(spm('dir'), 'rend', 'render_single_subj.mat')); +return + +function Fgraph = spm_render(dat,brt,rendfile) +% Render blobs on surface of a 'standard' brain +% FORMAT spm_render(dat,brt,rendfile) +% +% dat - a vertical cell array of length 1 to 3 +% - each element is a structure containing: +% - XYZ - the x, y & z coordinates of the transformed t values. +% in units of voxels. +% - t - the SPM{.} values +% - mat - affine matrix mapping from XYZ voxels to Talairach. +% - dim - dimensions of volume from which XYZ is drawn. +% brt - brightness control: +% If NaN, then displays using the old style with hot +% metal for the blobs, and grey for the brain. +% Otherwise, it is used as a ``gamma correction'' to +% optionally brighten the blobs up a little. +% rendfile - the file containing the images to render on to. See also +% spm_xbrain.m. +% +% Without arguments, spm_render acts as its own UI. +%_______________________________________________________________________ +% +% spm_render prompts for details of up to three SPM{Z}s or SPM{t}s that +% are then displayed superimposed on the surface of a standard brain. +% +% The first is shown in red, then green then blue. +% +% The blobs which are displayed are the integral of all transformed t +% values, exponentially decayed according to their depth. Voxels that +% are 10mm behind the surface have half the intensity of ones at the +% surface. +%_______________________________________________________________________ +% @(#)spm_render.m 2.19 John Ashburner FIL 02/10/29 + +%-Parse arguments, get data if not passed as parameters +%======================================================================= +if nargin < 1 + SPMid = spm('FnBanner',mfilename,'2.19'); + [Finter,Fgraph,CmdLine] = spm('FnUIsetup','Results: render',0); + + num = spm_input('Number of sets',1,'1 set|2 sets|3 sets',[1 2 3]); + + for i = 1:num, + [SPM,VOL] = spm_getSPM; + dat(i) = struct( 'XYZ', VOL.XYZ,... + 't', VOL.Z',... + 'mat', VOL.M,... + 'dim', VOL.DIM); + end; + showbar = 1; +else, + num = length(dat); + showbar = 0; +end; + +% get surface +%----------------------------------------------------------------------- +if nargin < 3, + rendfile = spm_get(1,'render*.mat','Render file',fullfile(spm('Dir'),'rend')); +end; + +% get brightness +%----------------------------------------------------------------------- +if nargin < 2, + brt = 1; + if num==1, + brt = spm_input('Style',1,'new|old',[1 NaN], 1); + end; + if finite(brt), + brt = spm_input('Brighten blobs',1,'none|slightly|more|lots',[1 0.75 0.5 0.25], 1); + end; +end; + + + +% Perform the rendering +%======================================================================= +spm('Pointer','Watch') + +load(rendfile); + +if (exist('rend') ~= 1), % Assume old format... + rend = cell(size(Matrixes,1),1); + for i=1:size(Matrixes,1), + rend{i}=struct('M',eval(Matrixes(i,:)),... + 'ren',eval(Rens(i,:)),... + 'dep',eval(Depths(i,:))); + rend{i}.ren = rend{i}.ren/max(max(rend{i}.ren)); + end; +end; + +if showbar, spm_progress_bar('Init', size(dat,1)*length(rend),... + 'Formatting Renderings', 'Number completed'); end; +for i=1:length(rend), + rend{i}.max=0; + rend{i}.data = cell(size(dat,1),1); + if issparse(rend{i}.ren), + % Assume that images have been DCT compressed + % - the SPM99 distribution was originally too big. + d = size(rend{i}.ren); + B1 = spm_dctmtx(d(1),d(1)); + B2 = spm_dctmtx(d(2),d(2)); + rend{i}.ren = B1*rend{i}.ren*B2'; + % the depths did not compress so well with + % a straight DCT - therefore it was modified slightly + rend{i}.dep = exp(B1*rend{i}.dep*B2')-1; + end; + msk = find(rend{i}.ren>1);rend{i}.ren(msk)=1; + msk = find(rend{i}.ren<0);rend{i}.ren(msk)=0; + if showbar, spm_progress_bar('Set', i); end; +end; +if showbar, spm_progress_bar('Clear'); end; + +if showbar, spm_progress_bar('Init', length(dat)*length(rend),... + 'Making pictures', 'Number completed'); end; + +mx = zeros(length(rend),1)+eps; +mn = zeros(length(rend),1); + +for j=1:length(dat), + XYZ = dat(j).XYZ; + t = dat(j).t; + dim = dat(j).dim; + mat = dat(j).mat; + + for i=1:length(rend), + + % transform from Taliarach space to space of the rendered image + %------------------------------------------------------- + M1 = rend{i}.M*dat(j).mat; + zm = sum(M1(1:2,1:3).^2,2).^(-1/2); + M2 = diag([zm' 1 1]); + M = M2*M1; + cor = [1 1 1 ; dim(1) 1 1 ; 1 dim(2) 1; dim(1) dim(2) 1 ; + 1 1 dim(3) ; dim(1) 1 dim(3) ; 1 dim(2) dim(3); dim(1) dim(2) dim(3)]'; + tcor= M(1:3,1:3)*cor + M(1:3,4)*ones(1,8); + off = min(tcor(1:2,:)'); + M2 = spm_matrix(-off+1)*M2; + M = M2*M1; + xyz = (M(1:3,1:3)*XYZ + M(1:3,4)*ones(1,size(XYZ,2))); + d2 = ceil(max(xyz(1:2,:)')); + + % calculate 'depth' of values + %------------------------------------------------------- + dep = spm_slice_vol(rend{i}.dep,spm_matrix([0 0 1])*inv(M2),d2,1); + z1 = dep(round(xyz(1,:))+round(xyz(2,:)-1)*size(dep,1)); + + if ~finite(brt), msk = find(xyz(3,:) < (z1+20) & xyz(3,:) > (z1-5)); + else, msk = find(xyz(3,:) < (z1+60) & xyz(3,:) > (z1-5)); end; + + if ~isempty(msk), + + % generate an image of the integral of the blob values. + %----------------------------------------------- + xyz = xyz(:,msk); + if ~finite(brt), t0 = t(msk); + else, dst = xyz(3,:) - z1(msk); + dst = max(dst,0); + t0 = t(msk).*exp((log(0.5)/10)*dst)'; + end; + X0 = full(sparse(round(xyz(1,:)), round(xyz(2,:)), t0, d2(1), d2(2))); + hld = 1; if ~finite(brt), hld = 0; end; + X = spm_slice_vol(X0,spm_matrix([0 0 1])*M2,size(rend{i}.dep),hld); + msk = find(X<0); + X(msk) = 0; + else, + X = zeros(size(rend{i}.dep)); + end; + + % Brighten the blobs + if finite(brt), X = X.^brt; end; + + mx(j) = max([mx(j) max(max(X))]); + mn(j) = min([mn(j) min(min(X))]); + + rend{i}.data{j} = X; + + if showbar, spm_progress_bar('Set', i+(j-1)*length(rend)); end; + end; +end; + +mxmx = max(mx); +mnmn = min(mn); + +if showbar, spm_progress_bar('Clear'); end; +Fgraph = gcf;%spm_figure('GetWin','Graphics'); +%spm_results_ui('Clear',Fgraph); + +nrow = ceil(length(rend)/2); +hght = 0.25; +width = 0.25; +x0 = 0.5; +y0 = 0.01; +% subplot('Position',[0, 0, 1, hght]); +ax=axes('Parent',Fgraph,'units','normalized','Position',[0, 0, 0.5, hght],'Visible','off'); +%ax=axes; +%image(0,'Parent',ax); +set(ax,'YTick',[],'XTick',[]); + +if ~finite(brt), + % Old style split colourmap display. + %--------------------------------------------------------------- + load Split; + colormap(split); + for i=1:length(rend), + ren = rend{i}.ren; + X = (rend{i}.data{1}-mnmn)/(mxmx-mnmn); + msk = find(X); + ren(msk) = X(msk)+(1+1.51/64); + ax=axes('Parent',Fgraph,'units','normalized',... + 'Position',[x0+rem(i-1,2)*width, y0+floor((i-1)/2)*hght/nrow, width, hght/nrow],... + 'Visible','off'); + image(ren*64,'Parent',ax); + set(ax,'DataAspectRatio',[1 1 1], ... + 'PlotBoxAspectRatioMode','auto',... + 'YTick',[],'XTick',[],'XDir','normal','YDir','normal'); + end; +else, + % Combine the brain surface renderings with the blobs, and display using + % 24 bit colour. + %--------------------------------------------------------------- + for i=1:length(rend), + ren = rend{i}.ren; + X = cell(3,1); + for j=1:length(rend{i}.data), + X{j} = rend{i}.data{j}/(mxmx-mnmn)-mnmn; + end + for j=(length(rend{i}.data)+1):3 + X{j}=zeros(size(X{1})); + end + + rgb = zeros([size(ren) 3]); + tmp = ren.*max(1-X{1}-X{2}-X{3},0); + rgb(:,:,1) = tmp + X{1}; + rgb(:,:,2) = tmp + X{2}; + rgb(:,:,3) = tmp + X{3}; + + ax=axes('Parent',Fgraph,'units','normalized',... + 'Position',[x0+rem(i-1,2)*width, y0+floor((i-1)/2)*hght/nrow*2, width, hght/nrow*2],... + 'Visible','off'); + image(rgb,'Parent',ax); + set(ax,'DataAspectRatio',[1 1 1], ... + 'PlotBoxAspectRatioMode','auto',... + 'YTick',[],'XTick',[],... + 'XDir','normal','YDir','normal'); + end; +end; + +spm('Pointer') +return; + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% spm_list +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function varargout = spm_list(varargin) +% Display and analysis of SPM{.} +% FORMAT TabDat = spm_list('List',SPM,hReg,[Num,Dis,Str]) +% Summary list of local maxima for entire volume of interest +% FORMAT TabDat = spm_list('ListCluster',SPM,hReg,[Num,Dis,Str]) +% List of local maxima for a single suprathreshold cluster +% +% SPM - structure containing SPM, distribution & filtering details +% - required fields are: +% .swd - SPM working directory - directory containing current SPM.mat +% .Z - minimum of n Statistics {filtered on u and k} +% .n - number of conjoint tests +% .STAT - distribution {Z, T, X or F} +% .df - degrees of freedom [df{interest}, df{residual}] +% .u - height threshold +% .k - extent threshold {voxels} +% .XYZ - location of voxels {voxel coords} +% .XYZmm - location of voxels {mm} +% .S - search Volume {voxels} +% .R - search Volume {resels} +% .FWHM - smoothness {voxels} +% .M - voxels - > mm matrix +% .VOX - voxel dimensions {mm} +% .Vspm - mapped statistic image(s) +% .Ps - uncorrected P values in searched volume (for FDR) +% +% (see spm_getSPM for further details of xSPM structures) +% +% hReg - Handle of results section XYZ registry (see spm_results_ui.m) +% +% Num - number of maxima per cluster +% Dis - distance among clusters (mm) +% Str - header string +% +% TabDat - Structure containing table data +% - fields are +% .tit - table Title (string) +% .hdr - table header (2x11 cell array) +% .fmt - fprintf format strings for table data (1x11 cell array) +% .str - table filtering note (string) +% .ftr - table footnote information (4x2 cell array) +% .dat - table data (Nx11 cell array) +% +% ---------------- +% +% FORMAT spm_list('TxtList',TabDat,c) +% Prints a tab-delimited text version of the table +% TabDat - Structure containing table data (format as above) +% c - Column of table data to start text table at +% (E.g. c=3 doesn't print set-level results contained in columns 1 & 2) +% ---------------- +% +% FORMAT spm_list('SetCoords',xyz,hAx,hC) +% Highlighting of table co-ordinates (used by results section registry) +% xyz - 3-vector of new co-ordinate +% hAx - table axis (the registry object for tables) +% hReg - Handle of caller (not used) +%_______________________________________________________________________ +% +% spm_list characterizes SPMs (thresholded at u and k) in terms of +% excursion sets (a collection of face, edge and vertex connected +% subsets or clusters). The currected significance of the results are +% based on set, cluster and voxel-level inferences using distributional +% approximations from the Theory of Gaussian Fields. These +% distributions assume that the SPM is a reasonable lattice +% approximation of a continuous random field with known component field +% smoothness. +% +% The p values are based on the probability of obtaining c, or more, +% clusters of k, or more, resels above u, in the volume S analysed = +% P(u,k,c). For specified thresholds u, k, the set-level inference is +% based on the observed number of clusters C, = P(u,k,C). For each +% cluster of size K the cluster-level inference is based on P(u,K,1) +% and for each voxel (or selected maxima) of height U, in that cluster, +% the voxel-level inference is based on P(U,0,1). All three levels of +% inference are supported with a tabular presentation of the p values +% and the underlying statistic: +% +% Set-level - c = number of suprathreshold clusters +% - P = prob(c or more clusters in the search volume) +% +% Cluster-level - k = number of voxels in this cluster +% - Pc = prob(k or more voxels in the search volume) +% - Pu = prob(k or more voxels in a cluster) +% +% Voxel-level - T/F = Statistic upon which the SPM is based +% - Ze = The eqivalent Z score - prob(Z > Ze) = prob(t > T) +% - Pc = prob(Ze or higher in the search volume) +% - Qu = Expd(Prop of false positives among voxels >= Ze) +% - Pu = prob(Ze or higher at that voxel) +% +% x,y,z (mm) - Coordinates of the voxel +% +% The table is grouped by regions and sorted on the Ze-variate of the +% primary maxima. Ze-variates (based on the uncorrected p value) are the +% Z score equivalent of the statistic. Volumes are expressed in voxels. +% +% Clicking on values in the table returns the value to the Matlab +% workspace. In addition, clicking on the co-ordinates jumps the +% results section cursor to that location. The table has a context menu +% (obtained by right-clicking in the background of the table), +% providing options to print the current table as a text table, or to +% extract the table data to the Matlab workspace. +% +%_______________________________________________________________________ +% @(#)spm_list.m 2.43 Karl Friston, Andrew Holmes 02/10/31 + + +% satellite figure global variable +%----------------------------------------------------------------------- +global SatWindow + +%======================================================================= +switch lower(varargin{1}), case 'list' %-List + %======================================================================= + % FORMAT TabDat = spm_list('list',SPM,hReg) + + %-Tolerance for p-value underflow, when computing equivalent Z's + %----------------------------------------------------------------------- + tol = eps*10; + + %-Parse arguments and set maxima number and separation + %----------------------------------------------------------------------- + if nargin < 2, error('insufficient arguments'), end + if nargin < 3, hReg = []; else, hReg = varargin{3}; end + + + %-Get current location (to highlight selected voxel in table) + %----------------------------------------------------------------------- + %xyzmm = spm_results_ui('GetCoords'); + xyzmm = [0 0 0]'; + + %-Extract data from xSPM + %----------------------------------------------------------------------- + S = varargin{2}.S; + R = varargin{2}.R; + FWHM = varargin{2}.FWHM; + VOX = varargin{2}.VOX; + n = varargin{2}.n; + STAT = varargin{2}.STAT; + df = varargin{2}.df; + u = varargin{2}.u; + M = varargin{2}.M; + v2r = 1/prod(FWHM(~isinf(FWHM))); %-voxels to resels + k = varargin{2}.k*v2r; + QPs = varargin{2}.Ps; % Needed for FDR + QPs = sort(QPs(:)); + + %-get number and separation for maxima to be reported + %----------------------------------------------------------------------- + if length(varargin) > 3 + + Num = varargin{4}; % number of maxima per cluster + Dis = varargin{5}; % distance among clusters (mm) + else + Num = 3; + Dis = 8; + end + if length(varargin) > 5 + + Title = varargin{6}; + else + Title = 'p-values adjusted for search volume'; + end + + + %-Setup graphics panel + %----------------------------------------------------------------------- + spm('Pointer','Watch') + if SatWindow + Fgraph = SatWindow; + figure(Fgraph); + else + Fgraph = figure('unit','normalized','position',[0.5,0.1,0.55,0.5],'Color','w',... + 'Name','volume', 'NumberTitle','off','resize','off','MenuBar','none'); + Fgraph = gcf; + end + %spm_results_ui('Clear',Fgraph) + FS = spm('FontSizes'); %-Scaled font sizes + PF = spm_platform('fonts'); %-Font names (for this platform) + + + %-Table header & footer + %======================================================================= + + %-Table axes & Title + %---------------------------------------------------------------------- + if SatWindow, ht = 0.85; bot = .14; else, ht = 0.8; bot = 0.15; end; + + if STAT == 'P' + Title = 'Posterior Probabilities'; + end + + hAx = axes('Position',[0.025 bot 0.9 ht],... + 'DefaultTextFontSize',FS(8),... + 'DefaultTextInterpreter','Tex',... + 'DefaultTextVerticalAlignment','Baseline',... + 'Units','points',... + 'Visible','off'); + + AxPos = get(hAx,'Position'); set(hAx,'YLim',[0,AxPos(4)]) + dy = FS(9); + y = floor(AxPos(4)) - dy; + + text(0,y,['Statistics: \it\fontsize{',num2str(FS(9)),'}',Title],... + 'FontSize',FS(11),'FontWeight','Bold'); y = y - dy/2; + line([0 1],[y y],'LineWidth',3,'Color','r'), y = y - 9*dy/8; + + + %-Construct table header + %----------------------------------------------------------------------- + set(gca,'DefaultTextFontName',PF.helvetica,'DefaultTextFontSize',FS(8)) + + Hc = []; + Hp = []; + h = text(0.01,y, 'set-level','FontSize',FS(9)); Hc = [Hc,h]; + h = line([0,0.11],[1,1]*(y-dy/4),'LineWidth',0.5,'Color','r'); Hc = [Hc,h]; + h = text(0.08,y-9*dy/8, '\itc '); Hc = [Hc,h]; + h = text(0.02,y-9*dy/8, '\itp '); Hc = [Hc,h]; + Hp = [Hp,h]; + text(0.22,y, 'cluster-level','FontSize',FS(9)); + line([0.15,0.41],[1,1]*(y-dy/4),'LineWidth',0.5,'Color','r'); + h = text(0.16,y-9*dy/8, '\itp \rm_{corrected}'); Hp = [Hp,h]; + h = text(0.33,y-9*dy/8, '\itp \rm_{uncorrected}'); Hp = [Hp,h]; + h = text(0.26,y-9*dy/8, '\itk \rm_E'); + + text(0.60,y, 'voxel-level','FontSize',FS(9)); + line([0.46,0.86],[1,1]*(y-dy/4),'LineWidth',0.5,'Color','r'); + h = text(0.46,y-9*dy/8, '\itp \rm_{FWE-corr}'); Hp = [Hp,h]; + h = text(0.55,y-9*dy/8, '\itp \rm_{FDR-corr}'); Hp = [Hp,h]; + h = text(0.79,y-9*dy/8, '\itp \rm_{uncorrected}'); Hp = [Hp,h]; + h = text(0.64,y-9*dy/8, sprintf('\\it%c',STAT)); + h = text(0.72,y-9*dy/8, '(\itZ\rm_\equiv)'); + + text(0.93,y - dy/2,['x,y,z \fontsize{',num2str(FS(8)),'}\{mm\}']); + + + %-Headers for text table... + %----------------------------------------------------------------------- + TabDat.tit = Title; + TabDat.hdr = { 'set', 'c';... + 'set', 'p';... + 'cluster', 'p(cor)';... + 'cluster', 'equivk';... + 'cluster', 'p(unc)';... + 'voxel', 'p(FWE-cor)';... + 'voxel', 'p(FDR-cor)';... + 'voxel', STAT;... + 'voxel', 'equivZ';... + 'voxel', 'p(unc)';... + '', 'x,y,z {mm}'}';... + + TabDat.fmt = { '%-0.3f','%g',... %-Set + '%0.3f', '%0.0f', '%0.3f',... %-Cluster + '%0.3f', '%0.3f', '%6.2f', '%5.2f', '%0.3f',... %-Voxel + '%3.0f %3.0f %3.0f'}; %-XYZ + + %-Column Locations + %----------------------------------------------------------------------- + tCol = [ 0.00 0.07 ... %-Set + 0.16 0.26 0.34 ... %-Cluster + 0.46 0.55 0.62 0.71 0.80 ...%-Voxel + 0.92]; %-XYZ + + % move to next vertial postion marker + %----------------------------------------------------------------------- + y = y - 7*dy/4; + line([0 1],[y y],'LineWidth',1,'Color','r') + y = y - 5*dy/4; + y0 = y; + + + %-Table filtering note + %----------------------------------------------------------------------- + if isinf(Num) + TabDat.str = sprintf('table shows all local maxima > %.1fmm apart',Dis); + else + TabDat.str = sprintf(['table shows %d local maxima ',... + 'more than %.1fmm apart'],Num,Dis); + end + text(0.5,4,TabDat.str,'HorizontalAlignment','Center','FontName',PF.helvetica,... + 'FontSize',FS(8),'FontAngle','Italic') + + + %-Volume, resels and smoothness (if classical inference) + %----------------------------------------------------------------------- + line([0 1],[0 0],'LineWidth',1,'Color','r') + if STAT ~= 'P' + %----------------------------------------------------------------------- + FWHMmm = FWHM.*VOX; % FWHM {mm} + Pz = spm_P(1,0,u,df,STAT,1,n,S); + Pu = spm_P(1,0,u,df,STAT,R,n,S); + Qu = spm_P_FDR(u,df,STAT,n,QPs); + [P Pn Em En EN] = spm_P(1,k,u,df,STAT,R,n,S); + + + %-Footnote with SPM parameters + %----------------------------------------------------------------------- + set(gca,'DefaultTextFontName',PF.helvetica,... + 'DefaultTextInterpreter','None','DefaultTextFontSize',FS(8)) + TabDat.ftr = cell(5,2); + TabDat.ftr{1} = ... + sprintf('Height threshold: %c = %0.2f, p = %0.3f (%0.3f)',... + STAT,u,Pz,Pu); + TabDat.ftr{2} = ... + sprintf('Extent threshold: k = %0.0f voxels, p = %0.3f (%0.3f)',... + k/v2r,Pn,P); + TabDat.ftr{3} = ... + sprintf('Expected voxels per cluster, = %0.3f',En/v2r); + TabDat.ftr{4} = ... + sprintf('Expected number of clusters, = %0.2f',Em*Pn); + TabDat.ftr{5} = ... + sprintf('Expected false discovery rate, <= %0.2f',Qu); + TabDat.ftr{6} = ... + sprintf('Degrees of freedom = [%0.1f, %0.1f]',df); + TabDat.ftr{7} = ... + sprintf(['Smoothness FWHM = %0.1f %0.1f %0.1f {mm} ',... + ' = %0.1f %0.1f %0.1f {voxels}'],FWHMmm,FWHM); + TabDat.ftr{8} = ... + sprintf('Search vol: %0.0f cmm; %0.0f voxels; %0.1f resels',S*prod(VOX),S,R(end)); + TabDat.ftr{9} = ... + sprintf(['Voxel size: [%0.1f, %0.1f, %0.1f] mm ',... + ' (1 resel = %0.2f voxels)'],VOX,prod(FWHM)); + + text(0.0,-1*dy,TabDat.ftr{1},... + 'UserData',[u,Pz,Pu,Qu],'ButtonDownFcn','get(gcbo,''UserData'')') + text(0.0,-2*dy,TabDat.ftr{2},... + 'UserData',[k/v2r,Pn,P],'ButtonDownFcn','get(gcbo,''UserData'')') + text(0.0,-3*dy,TabDat.ftr{3},... + 'UserData',En/v2r,'ButtonDownFcn','get(gcbo,''UserData'')') + text(0.0,-4*dy,TabDat.ftr{4},... + 'UserData',Em*Pn,'ButtonDownFcn','get(gcbo,''UserData'')') + text(0.0,-5*dy,TabDat.ftr{5},... + 'UserData',Qu,'ButtonDownFcn','get(gcbo,''UserData'')') + text(0.5,-1*dy,TabDat.ftr{6},... + 'UserData',df,'ButtonDownFcn','get(gcbo,''UserData'')') + text(0.5,-2*dy,TabDat.ftr{7},... + 'UserData',FWHMmm,'ButtonDownFcn','get(gcbo,''UserData'')') + text(0.5,-3*dy,TabDat.ftr{8},... + 'UserData',[S*prod(VOX),S,R(end)],... + 'ButtonDownFcn','get(gcbo,''UserData'')') + text(0.5,-4*dy,TabDat.ftr{9},... + 'UserData',[VOX,prod(FWHM)],... + 'ButtonDownFcn','get(gcbo,''UserData'')') + + end % Classical + + + %-Characterize excursion set in terms of maxima + % (sorted on Z values and grouped by regions) + %======================================================================= + if ~length(varargin{2}.Z) + text(0.5,y-6*dy,'no suprathreshold clusters',... + 'HorizontalAlignment','Center',... + 'FontAngle','Italic','FontWeight','Bold',... + 'FontSize',FS(16),'Color',[1,1,1]*.5); + TabDat.dat = cell(0,11); + varargout = {TabDat}; + spm('Pointer','Arrow') + return + end + + % Includes Darren Gitelman's code for working around + % spm_max for conjunctions with negative thresholds + %----------------------------------------------------------------------- + minz = abs(min(min(varargin{2}.Z))); + zscores = 1 + minz + varargin{2}.Z; + [N Z XYZ A] = spm_max(zscores,varargin{2}.XYZ); + Z = Z - minz - 1; + + %-Convert cluster sizes from voxels to resels + %----------------------------------------------------------------------- + if isfield(varargin{2},'VRvp') + V2R = spm_get_data(varargin{2}.VRvp,XYZ); + else + V2R = v2r; + end + N = N.*V2R; + + %-Convert maxima locations from voxels to mm + %----------------------------------------------------------------------- + XYZmm = M(1:3,:)*[XYZ; ones(1,size(XYZ,2))]; + + + + %-Table proper (& note all data in cell array) + %======================================================================= + + %-Pagination variables + %----------------------------------------------------------------------- + hPage = []; + set(gca,'DefaultTextFontName',PF.courier,'DefaultTextFontSize',FS(7)) + + + %-Set-level p values {c} - do not display if reporting a single cluster + %----------------------------------------------------------------------- + c = max(A); %-Number of clusters + if STAT ~= 'P' + Pc = spm_P(c,k,u,df,STAT,R,n,S); %-Set-level p-value + else + Pc = []; + set(Hp,'Visible','off') + end + + if c > 1; + h = text(tCol(1),y,sprintf(TabDat.fmt{1},Pc),'FontWeight','Bold',... + 'UserData',Pc,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + h = text(tCol(2),y,sprintf(TabDat.fmt{2},c),'FontWeight','Bold',... + 'UserData',c,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + else + set(Hc,'Visible','off') + end + + TabDat.dat = {Pc,c}; %-Table data + TabLin = 1; %-Table data line + + + %-Local maxima p-values & statistics + %----------------------------------------------------------------------- + HlistXYZ = []; + while prod(size(find(finite(Z)))) + + % Paginate if necessary + %--------------------------------------------------------------- + if y < min(Num + 1,3)*dy + + % added Fgraph term to paginate on Satellite window + %------------------------------------------------------- + h = text(0.5,-5*dy,... + sprintf('Page %d',spm_figure('#page',Fgraph)),... + 'FontName',PF.helvetica,'FontAngle','Italic',... + 'FontSize',FS(8)); + + spm_figure('NewPage',[hPage,h]) + hPage = []; + y = y0; + end + + %-Find largest remaining local maximum + %--------------------------------------------------------------- + [U,i] = max(Z); % largest maxima + j = find(A == A(i)); % maxima in cluster + + + %-Compute cluster {k} and voxel-level {u} p values for this cluster + %--------------------------------------------------------------- + Nv = N(i)/v2r; % extent {voxels} + + + if STAT ~= 'P' + Pz = spm_P(1,0, U,df,STAT,1,n,S);% uncorrected p value + Pu = spm_P(1,0, U,df,STAT,R,n,S);% FWE-corrected {based on Z) + Qu = spm_P_FDR( U,df,STAT,n,QPs);% FDR-corrected {based on Z) + [Pk Pn] = spm_P(1,N(i),u,df,STAT,R,n,S);% [un]corrected {based on k) + + if Pz < tol % Equivalent Z-variate + Ze = Inf; % (underflow => can't compute) + else + Ze = spm_invNcdf(1 - Pz); + end + else + Pz = []; + Pu = []; + Qu = []; + Pk = []; + Pn = []; + Ze = spm_invNcdf(U); + end + + + %-Print cluster and maximum voxel-level p values {Z} + %--------------------------------------------------------------- + h = text(tCol(3),y,sprintf(TabDat.fmt{3},Pk),'FontWeight','Bold',... + 'UserData',Pk,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + + h = text(tCol(4),y,sprintf(TabDat.fmt{4},Nv),'FontWeight','Bold',... + 'UserData',Nv,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + h = text(tCol(5),y,sprintf(TabDat.fmt{5},Pn),'FontWeight','Bold',... + 'UserData',Pn,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + + h = text(tCol(6),y,sprintf(TabDat.fmt{6},Pu),'FontWeight','Bold',... + 'UserData',Pu,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + h = text(tCol(7),y,sprintf(TabDat.fmt{7},Qu),'FontWeight','Bold',... + 'UserData',Qu,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + h = text(tCol(8),y,sprintf(TabDat.fmt{8},U),'FontWeight','Bold',... + 'UserData',U,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + h = text(tCol(9),y,sprintf(TabDat.fmt{9},Ze),'FontWeight','Bold',... + 'UserData',Ze,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + h = ... + text(tCol(10),y,sprintf(TabDat.fmt{10},Pz),'FontWeight','Bold',... + 'UserData',Pz,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + + % Specifically changed so it properly finds hMIPax + %--------------------------------------------------------------------- + h = text(tCol(11),y,sprintf(TabDat.fmt{11},XYZmm(:,i)),... + 'FontWeight','Bold',... + 'Tag','ListXYZ',... + 'ButtonDownFcn',[... + 'hMIPax = findobj(''tag'',''hMIPax'');',... + 'spm_mip_ui(''SetCoords'',get(gcbo,''UserData''),hMIPax);'],... + 'Interruptible','off','BusyAction','Cancel',... + 'UserData',XYZmm(:,i)); + + HlistXYZ = [HlistXYZ, h]; + if spm_XYZreg('Edist',xyzmm,XYZmm(:,i)) Dis mm apart) + %--------------------------------------------------------------- + [l q] = sort(-Z(j)); % sort on Z value + D = i; + for i = 1:length(q) + d = j(q(i)); + if min(sqrt(sum((XYZmm(:,D)-XYZmm(:,d)*ones(1,size(D,2))).^2)))>Dis; + + if length(D) < Num + + % Paginate if necessary + %----------------------------------------------- + if y < dy + h = text(0.5,-5*dy,sprintf('Page %d',... + spm_figure('#page',Fgraph)),... + 'FontName',PF.helvetica,... + 'FontAngle','Italic',... + 'FontSize',FS(8)); + + spm_figure('NewPage',[hPage,h]) + hPage = []; + y = y0; + end + + % voxel-level p values {Z} + %----------------------------------------------- + if STAT ~= 'P' + Pz = spm_P(1,0,Z(d),df,STAT,1,n,S); + Pu = spm_P(1,0,Z(d),df,STAT,R,n,S); + Qu = spm_P_FDR(Z(d),df,STAT,n,QPs); + if Pz < tol + Ze = Inf; + else, Ze = spm_invNcdf(1 - Pz); end + else + Pz = []; + Pu = []; + Qu = []; + Ze = spm_invNcdf(Z(d)); + end + + h = text(tCol(6),y,sprintf(TabDat.fmt{6},Pu),... + 'UserData',Pu,... + 'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + + h = text(tCol(7),y,sprintf(TabDat.fmt{7},Qu),... + 'UserData',Qu,... + 'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + h = text(tCol(8),y,sprintf(TabDat.fmt{8},Z(d)),... + 'UserData',Z(d),... + 'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + h = text(tCol(9),y,sprintf(TabDat.fmt{9},Ze),... + 'UserData',Ze,... + 'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + h = text(tCol(10),y,sprintf(TabDat.fmt{10},Pz),... + 'UserData',Pz,... + 'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + + % specifically modified line to use hMIPax + %----------------------------------------------- + h = text(tCol(11),y,... + sprintf(TabDat.fmt{11},XYZmm(:,d)),... + 'Tag','ListXYZ',... + 'ButtonDownFcn',[... + 'hMIPax = findobj(''tag'',''hMIPax'');',... + 'spm_mip_ui(''SetCoords'',',... + 'get(gcbo,''UserData''),hMIPax);'],... + 'Interruptible','off','BusyAction','Cancel',... + 'UserData',XYZmm(:,d)); + + HlistXYZ = [HlistXYZ, h]; + if spm_XYZreg('Edist',xyzmm,XYZmm(:,d))1 + h = text(0.5,-5*dy,sprintf('Page %d/%d',spm_figure('#page',Fgraph)*[1,1]),... + 'FontName',PF.helvetica,'FontSize',FS(8),'FontAngle','Italic'); + spm_figure('NewPage',[hPage,h]) + end + + %-End: Store TabDat in UserData of axes & reset pointer + %======================================================================= + h = uicontextmenu('Tag','TabDat',... + 'UserData',TabDat); + set(gca,'UIContextMenu',h,... + 'Visible','on',... + 'XColor','w','YColor','w') + uimenu(h,'Label','Table') + uimenu(h,'Separator','on','Label','Print text table',... + 'Tag','TD_TxtTab',... + 'CallBack',... + 'spm_list(''txtlist'',get(get(gcbo,''Parent''),''UserData''),3)',... + 'Interruptible','off','BusyAction','Cancel'); + uimenu(h,'Separator','off','Label','Extract table data structure',... + 'Tag','TD_Xdat',... + 'CallBack','get(get(gcbo,''Parent''),''UserData'')',... + 'Interruptible','off','BusyAction','Cancel'); + uimenu(h,'Separator','on','Label','help',... + 'Tag','TD_Xdat',... + 'CallBack','spm_help(''spm_list'')',... + 'Interruptible','off','BusyAction','Cancel'); + + %-Setup registry + %----------------------------------------------------------------------- + set(hAx,'UserData',struct('hReg',hReg,'HlistXYZ',HlistXYZ)) + spm_XYZreg('Add2Reg',hReg,hAx,'spm_list'); + + %-Return TabDat structure & reset pointer + %----------------------------------------------------------------------- + varargout = {TabDat}; + spm('Pointer','Arrow') + + + + + + %======================================================================= + case 'listcluster' %-List for current cluster only + %======================================================================= + % FORMAT TabDat = spm_list('listcluster',SPM,hReg) + + spm('Pointer','Watch') + + %-Parse arguments + %----------------------------------------------------------------------- + if nargin < 2, error('insufficient arguments'), end + if nargin < 3, hReg = []; else, hReg = varargin{3}; end + SPM = varargin{2}; + + %-get number and separation for maxima to be reported + %----------------------------------------------------------------------- + if length(varargin) > 3 + + Num = varargin{4}; % number of maxima per cluster + Dis = varargin{5}; % distance among clusters (mm) + else + Num = 32; + Dis = 4; + end + + + %-if there are suprathreshold voxels, filter out all but current cluster + %----------------------------------------------------------------------- + if length(SPM.Z) + + %-Jump to voxel nearest current location + %-------------------------------------------------------------- + [xyzmm,i] = spm_XYZreg('NearestXYZ',... + spm_results_ui('GetCoords'),SPM.XYZmm); + spm_results_ui('SetCoords',SPM.XYZmm(:,i)); + + %-Find selected cluster + %-------------------------------------------------------------- + A = spm_clusters(SPM.XYZ); + j = find(A == A(i)); + SPM.Z = SPM.Z(j); + SPM.XYZ = SPM.XYZ(:,j); + SPM.XYZmm = SPM.XYZmm(:,j); + if isfield(SPM,'Rd'), SPM.Rd = SPM.Rd(:,j); end + end + + %-Call 'list' functionality to produce table + %----------------------------------------------------------------------- + varargout = {spm_list('list',SPM,hReg,Num,Dis)}; + + + + + + %======================================================================= + case 'txtlist' %-Print ASCII text table + %======================================================================= + % FORMAT spm_list('TxtList',TabDat,c) + + if nargin<2, error('Insufficient arguments'), end + if nargin<3, c=1; else, c=varargin{3}; end + TabDat = varargin{2}; + + %-Table Title + %----------------------------------------------------------------------- + fprintf('\n\nSTATISTICS: %s\n',TabDat.tit) + fprintf('%c','='*ones(1,80)), fprintf('\n') + + %-Table header + %----------------------------------------------------------------------- + fprintf('%s\t',TabDat.hdr{1,c:end-1}), fprintf('%s\n',TabDat.hdr{1,end}) + fprintf('%s\t',TabDat.hdr{2,c:end-1}), fprintf('%s\n',TabDat.hdr{2,end}) + fprintf('%c','-'*ones(1,80)), fprintf('\n') + + %-Table data + %----------------------------------------------------------------------- + for i = 1:size(TabDat.dat,1) + for j=c:size(TabDat.dat,2) + fprintf(TabDat.fmt{j},TabDat.dat{i,j}) + fprintf('\t') + end + fprintf('\n') + end + for i=1:max(1,11-size(TabDat.dat,1)), fprintf('\n'), end + fprintf('%s\n',TabDat.str) + fprintf('%c','-'*ones(1,80)), fprintf('\n') + + %-Table footer + %----------------------------------------------------------------------- + fprintf('%s\n',TabDat.ftr{:}) + fprintf('%c','='*ones(1,80)), fprintf('\n\n') + + + + %======================================================================= + case 'setcoords' %-Co-ordinate change + %======================================================================= + % FORMAT spm_list('SetCoords',xyz,hAx,hReg) + if nargin<3, error('Insufficient arguments'), end + hAx = varargin{3}; + xyz = varargin{2}; + UD = get(hAx,'UserData'); + HlistXYZ = UD.HlistXYZ(ishandle(UD.HlistXYZ)); + + %-Set all co-ord strings to black + %----------------------------------------------------------------------- + set(HlistXYZ,'Color','k') + + %-If co-ord matches a string, highlight it in red + %----------------------------------------------------------------------- + XYZ = get(HlistXYZ,'UserData'); + if iscell(XYZ), XYZ = cat(2,XYZ{:}); end + [null,i,d] = spm_XYZreg('NearestXYZ',xyz,XYZ); + if d=2, st.vols{H}.area = varargin{2}; end; + if isempty(st.bb), st.bb = maxbb; end; + bbox; + cm_pos; + end; + varargout{1} = H; + st.centre = mean(maxbb); + redraw_all + + case 'bb', + if length(varargin)> 0 & all(size(varargin{1})==[2 3]), st.bb = varargin{1}; end; + bbox; + redraw_all; + + case 'redraw', + redraw_all; + eval(st.callback); + if isfield(st,'registry'), + spm_XYZreg('SetCoords',st.centre,st.registry.hReg,st.registry.hMe); + end; + + case 'reposition', + if length(varargin)<1, tmp = findcent; + else, tmp = varargin{1}; end; + if length(tmp)==3 + h = valid_handles(st.snap); + if ~isempty(h) + tmp=st.vols{h(1)}.mat*... + round(inv(st.vols{h(1)}.mat)*[tmp; ... + 1]); + end; + st.centre = tmp(1:3); + end; + redraw_all; + eval(st.callback); + if isfield(st,'registry'), + spm_XYZreg('SetCoords',st.centre,st.registry.hReg,st.registry.hMe); + end; + cm_pos; + + case 'setcoords', + st.centre = varargin{1}; + st.centre = st.centre(:); + redraw_all; + eval(st.callback); + cm_pos; + + case 'space', + if length(varargin)<1, + st.Space = eye(4); + st.bb = maxbb; + bbox; + redraw_all; + else, + space(varargin{1}); + bbox; + redraw_all; + end; + + case 'maxbb', + st.bb = maxbb; + bbox; + redraw_all; + + case 'resolution', + resolution(varargin{1}); + bbox; + redraw_all; + + case 'window', + if length(varargin)<2, + win = 'auto'; + elseif length(varargin{2})==2, + win = varargin{2}; + end; + for i=valid_handles(varargin{1}), + st.vols{i}.window = win; + end; + redraw(varargin{1}); + + case 'delete', + my_delete(varargin{1}); + + case 'move', + move(varargin{1},varargin{2}); + % redraw_all; + + case 'reset', + my_reset; + + case 'pos', + if isempty(varargin), + H = st.centre(:); + else, + H = pos(varargin{1}); + end; + varargout{1} = H; + + case 'interp', + st.hld = varargin{1}; + redraw_all; + + case 'xhairs', + xhairs(varargin{1}); + + case 'register', + register(varargin{1}); + + case 'addblobs', + addblobs(varargin{1}, varargin{2},varargin{3},varargin{4}); + % redraw(varargin{1}); + + case 'addcolouredblobs', + addcolouredblobs(varargin{1}, varargin{2},varargin{3},varargin{4},varargin{5}); + % redraw(varargin{1}); + + case 'addimage', + addimage(varargin{1}, varargin{2}); + % redraw(varargin{1}); + + case 'addcolouredimage', + addcolouredimage(varargin{1}, varargin{2},varargin{3}); + % redraw(varargin{1}); + + case 'addtruecolourimage', + % spm_orthviews('Addtruecolourimage',handle,filename,colourmap,prop,mx,mn) + % Adds blobs from an image in true colour + % handle - image number to add blobs to [default 1] + % filename of image containing blob data [default - request via GUI] + % colourmap - colormap to display blobs in [GUI input] + % prop - intensity proportion of activation cf grayscale [0.4] + % mx - maximum intensity to scale to [maximum value in activation image] + % mn - minimum intensity to scale to [minimum value in activation image] + % + if nargin < 2 + varargin(1) = {1}; + end + if nargin < 3 + varargin(2) = {spm_select(1, 'image', 'Image with activation signal')}; + end + if nargin < 4 + actc = []; + while isempty(actc) + actc = getcmap(spm_input('Colourmap for activation image', '+1','s')); + end + varargin(3) = {actc}; + end + if nargin < 5 + varargin(4) = {0.4}; + end + if nargin < 6 + actv = spm_vol(varargin{2}); + varargin(5) = {max([eps maxval(actv)])}; + end + if nargin < 7 + varargin(6) = {min([0 minval(actv)])}; + end + + addtruecolourimage(varargin{1}, varargin{2},varargin{3}, varargin{4}, ... + varargin{5}, varargin{6}); + % redraw(varargin{1}); + + case 'addcolourbar', + addcolourbar(varargin{1}, varargin{2}); + + case 'rmblobs', + rmblobs(varargin{1}); + % redraw(varargin{1}); + + case 'addcontext', + if nargin == 1, + handles = 1:24; + else, + handles = varargin{1}; + end; + addcontexts(handles); + + case 'rmcontext', + if nargin == 1, + handles = 1:24; + else, + handles = varargin{1}; + end; + rmcontexts(handles); + + case 'context_menu', + c_menu(varargin{:}); + + case 'valid_handles', + if nargin == 1 + handles = 1:24; + else, + handles = varargin{1}; + end; + varargout{1} = valid_handles(handles); + + otherwise, + addonaction = strcmp(st.plugins,action); + if any(addonaction) + feval(['spm_ov_' st.plugins{addonaction}],varargin{:}); + else + warning('Unknown action string') + end; +end; + +spm('Pointer'); +return; + + +%_______________________________________________________________________ +%_______________________________________________________________________ +function addblobs(handle, xyz, t, mat) +global st +global TMAX_ +for i=valid_handles(handle), + if ~isempty(xyz), + rcp = round(xyz); + dim = max(rcp,[],2)'; + off = rcp(1,:) + dim(1)*(rcp(2,:)-1 + dim(2)*(rcp(3,:)-1)); + vol = zeros(dim)+NaN; + vol(off) = t; + vol = reshape(vol,dim); + st.vols{i}.blobs=cell(1,1); + if st.mode == 0, + axpos = get(st.vols{i}.ax{2}.ax,'Position'); + else, + axpos = get(st.vols{i}.ax{1}.ax,'Position'); + end; + mx = max([eps max(t)]); + mn = min([0 min(t)]); + if ~strcmp(TMAX_, 'auto') + mx = str2num(TMAX_); + end + %KND: + if numel(mx)==2 + mn=mx(1); + mx=mx(2); + end + st.vols{i}.blobs{1} = struct('vol',vol,'mat',mat,'max',mx, 'min',mn); + addcolourbar(handle,1); + end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function addimage(handle, fname) +global st +for i=valid_handles(handle), + if isstruct(fname), + vol = fname(1); + else, + vol = spm_vol(fname); + end; + mat = vol.mat; + st.vols{i}.blobs=cell(1,1); + mx = max([eps maxval(vol)]); + mn = min([0 minval(vol)]); + st.vols{i}.blobs{1} = struct('vol',vol,'mat',mat,'max',mx,'min',mn); + addcolourbar(handle,1); +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function addcolouredblobs(handle, xyz, t, mat,colour) +global st +for i=valid_handles(handle), + if ~isempty(xyz), + rcp = round(xyz); + dim = max(rcp,[],2)'; + off = rcp(1,:) + dim(1)*(rcp(2,:)-1 + dim(2)*(rcp(3,:)-1)); + vol = zeros(dim)+NaN; + vol(off) = t; + vol = reshape(vol,dim); + if ~isfield(st.vols{i},'blobs'), + st.vols{i}.blobs=cell(1,1); + bset = 1; + else, + bset = length(st.vols{i}.blobs)+1; + end; + mx = max([eps maxval(vol)]); + mn = min([0 minval(vol)]); + st.vols{i}.blobs{bset} = struct('vol',vol,'mat',mat,'max',mx,'min',mn,'colour',colour); + end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function addcolouredimage(handle, fname,colour) +global st +for i=valid_handles(handle), + if isstruct(fname), + vol = fname(1); + else, + vol = spm_vol(fname); + end; + mat = vol.mat; + if ~isfield(st.vols{i},'blobs'), + st.vols{i}.blobs=cell(1,1); + bset = 1; + else, + bset = length(st.vols{i}.blobs)+1; + end; + mx = max([eps maxval(vol)]); + mn = min([0 minval(vol)]); + st.vols{i}.blobs{bset} = struct('vol',vol,'mat',mat,'max',mx,'min',mn,'colour',colour); +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function addtruecolourimage(handle,fname,colourmap,prop,mx,mn) +% adds true colour image to current displayed image +global st +for i=valid_handles(handle), + if isstruct(fname), + vol = fname(1); + else, + vol = spm_vol(fname); + end; + mat = vol.mat; + if ~isfield(st.vols{i},'blobs'), + st.vols{i}.blobs=cell(1,1); + bset = 1; + else, + bset = length(st.vols{i}.blobs)+1; + end; + c = struct('cmap', colourmap,'prop',prop); + st.vols{i}.blobs{bset} = struct('vol',vol,'mat',mat,'max',mx, ... + 'min',mn,'colour',c); + addcolourbar(handle,bset); +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function addcolourbar(vh,bh) +global st +if st.mode == 0, + axpos = get(st.vols{vh}.ax{2}.ax,'Position'); +else, + axpos = get(st.vols{vh}.ax{1}.ax,'Position'); +end; +st.vols{vh}.blobs{bh}.cbar = axes('Parent',st.fig,... + 'Position',[(axpos(1)+axpos(3)+0.05+(bh-1)*.1) (axpos(2)+0.005) 0.05 (axpos(4)-0.01)],... + 'Box','on', 'YDir','normal', 'XTickLabel',[], 'XTick',[]); +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function rmblobs(handle) +global st +for i=valid_handles(handle), + if isfield(st.vols{i},'blobs'), + for j=1:length(st.vols{i}.blobs), + if isfield(st.vols{i}.blobs{j},'cbar') & ishandle(st.vols{i}.blobs{j}.cbar), + delete(st.vols{i}.blobs{j}.cbar); + end; + end; + st.vols{i} = rmfield(st.vols{i},'blobs'); + end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function register(hreg) +global st +tmp = uicontrol('Position',[0 0 1 1],'Visible','off','Parent',st.fig); +h = valid_handles(1:24); +if ~isempty(h), + tmp = st.vols{h(1)}.ax{1}.ax; + st.registry = struct('hReg',hreg,'hMe', tmp); + spm_XYZreg('Add2Reg',st.registry.hReg,st.registry.hMe, 'spm_orthviews'); +else, + warning('Nothing to register with'); +end; +st.centre = spm_XYZreg('GetCoords',st.registry.hReg); +st.centre = st.centre(:); +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function xhairs(arg1), +global st +st.xhairs = 0; +opt = 'on'; +if ~strcmp(arg1,'on'), + opt = 'off'; +else, + st.xhairs = 1; +end; +for i=valid_handles(1:24), + for j=1:3, + set(st.vols{i}.ax{j}.lx,'Visible',opt); + set(st.vols{i}.ax{j}.ly,'Visible',opt); + end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function H = pos(arg1) +global st +H = []; +for arg1=valid_handles(arg1), + is = inv(st.vols{arg1}.premul*st.vols{arg1}.mat); + H = is(1:3,1:3)*st.centre(:) + is(1:3,4); +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function my_reset +global st +if ~isempty(st) & isfield(st,'registry') & ishandle(st.registry.hMe), + delete(st.registry.hMe); st = rmfield(st,'registry'); +end; +my_delete(1:24); +reset_st; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function my_delete(arg1) +global st +for i=valid_handles(arg1), + kids = get(st.fig,'Children'); + for j=1:3, + if any(kids == st.vols{i}.ax{j}.ax), + set(get(st.vols{i}.ax{j}.ax,'Children'),'DeleteFcn',''); + delete(st.vols{i}.ax{j}.ax); + end; + end; + st.vols{i} = []; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function resolution(arg1) +global st +res = arg1/mean(svd(st.Space(1:3,1:3))); +Mat = diag([res res res 1]); +st.Space = st.Space*Mat; +st.bb = st.bb/res; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function move(handle,pos) +global st +for handle = valid_handles(handle), + st.vols{handle}.area = pos; +end; +bbox; +% redraw(valid_handles(handle)); +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function bb = maxbb +global st +mn = [Inf Inf Inf]; +mx = -mn; +for i=valid_handles(1:24), + bb = [[1 1 1];st.vols{i}.dim(1:3)]; + c = [ bb(1,1) bb(1,2) bb(1,3) 1 + bb(1,1) bb(1,2) bb(2,3) 1 + bb(1,1) bb(2,2) bb(1,3) 1 + bb(1,1) bb(2,2) bb(2,3) 1 + bb(2,1) bb(1,2) bb(1,3) 1 + bb(2,1) bb(1,2) bb(2,3) 1 + bb(2,1) bb(2,2) bb(1,3) 1 + bb(2,1) bb(2,2) bb(2,3) 1]'; + tc = st.Space\(st.vols{i}.premul*st.vols{i}.mat)*c; + tc = tc(1:3,:)'; + mx = max([tc ; mx]); + mn = min([tc ; mn]); +end; +bb = [mn ; mx]; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function space(arg1) +global st +if ~isempty(st.vols{arg1}) + num = arg1; + Mat = st.vols{num}.premul(1:3,1:3)*st.vols{num}.mat(1:3,1:3); + vox = sqrt(sum(Mat.^2)); + if det(Mat(1:3,1:3))<0, vox(1) = -vox(1); end; + Mat = diag([vox 1]); + Space = (st.vols{num}.mat)/Mat; + bb = [1 1 1;st.vols{num}.dim(1:3)]; + bb = [bb [1;1]]; + bb=bb*Mat'; + bb=bb(:,1:3); + bb=sort(bb); + st.Space = Space; + st.bb = bb; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function H = specify_image(arg1, arg2) +global st +H=[]; +ok = 1; +if isstruct(arg1), + V = arg1(1); +else, + try, + V = spm_vol(arg1); + catch, + fprintf('Can not use image "%s"\n', arg1); + return; + end; +end; + +ii = 1; +while ~isempty(st.vols{ii}), ii = ii + 1; end; + +DeleteFcn = ['spm_orthviews(''Delete'',' num2str(ii) ');']; +V.ax = cell(3,1); +for i=1:3, + ax = axes('Visible','off','DrawMode','fast','Parent',st.fig,'DeleteFcn',DeleteFcn,... + 'YDir','normal','ButtonDownFcn',... + ['if strcmp(get(gcf,''SelectionType''),''normal''),spm_orthviews(''Reposition'');',... + 'elseif strcmp(get(gcf,''SelectionType''),''extend''),spm_orthviews(''Reposition'');',... + 'spm_orthviews(''context_menu'',''ts'',1);end;']); + d = image(0,'Tag','Transverse','Parent',ax,... + 'DeleteFcn',DeleteFcn); + set(ax,'Ydir','normal','ButtonDownFcn',... + ['if strcmp(get(gcf,''SelectionType''),''normal''),spm_orthviews(''Reposition'');',... + 'elseif strcmp(get(gcf,''SelectionType''),''extend''),spm_orthviews(''reposition'');',... + 'spm_orthviews(''context_menu'',''ts'',1);end;']); + + lx = line(0,0,'Parent',ax,'DeleteFcn',DeleteFcn); + ly = line(0,0,'Parent',ax,'DeleteFcn',DeleteFcn); + if ~st.xhairs, + set(lx,'Visible','off'); + set(ly,'Visible','off'); + end; + V.ax{i} = struct('ax',ax,'d',d,'lx',lx,'ly',ly); +end; +V.premul = eye(4); +V.window = 'auto'; +V.mapping = 'linear'; +st.vols{ii} = V; + +H = ii; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function addcontexts(handles) +global st +for ii = valid_handles(handles), + cm_handle = addcontext(ii); + for i=1:3, + set(st.vols{ii}.ax{i}.ax,'UIcontextmenu',cm_handle); + st.vols{ii}.ax{i}.cm = cm_handle; + end; +end; +spm_orthviews('reposition',spm_orthviews('pos')); +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function rmcontexts(handles) +global st +for ii = valid_handles(handles), + for i=1:3, + set(st.vols{ii}.ax{i}.ax,'UIcontextmenu',[]); + st.vols{ii}.ax{i} = rmfield(st.vols{ii}.ax{i},'cm'); + end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function bbox +global st +Dims = diff(st.bb)'+1; + +TD = Dims([1 2])'; +CD = Dims([1 3])'; +if st.mode == 0, SD = Dims([3 2])'; else, SD = Dims([2 3])'; end; + +un = get(st.fig,'Units');set(st.fig,'Units','Pixels'); +sz = get(st.fig,'Position');set(st.fig,'Units',un); +sz = sz(3:4); +sz(2) = sz(2)-40; + +for i=valid_handles(1:24), + area = st.vols{i}.area(:); + area = [area(1)*sz(1) area(2)*sz(2) area(3)*sz(1) area(4)*sz(2)]; + if st.mode == 0, + sx = area(3)/(Dims(1)+Dims(3))/1.02; + else, + sx = area(3)/(Dims(1)+Dims(2))/1.02; + end; + sy = area(4)/(Dims(2)+Dims(3))/1.02; + s = min([sx sy]); + + offy = (area(4)-(Dims(2)+Dims(3))*1.02*s)/2 + area(2); + sky = s*(Dims(2)+Dims(3))*0.02; + if st.mode == 0, + offx = (area(3)-(Dims(1)+Dims(3))*1.02*s)/2 + area(1); + skx = s*(Dims(1)+Dims(3))*0.02; + else, + offx = (area(3)-(Dims(1)+Dims(2))*1.02*s)/2 + area(1); + skx = s*(Dims(1)+Dims(2))*0.02; + end; + + DeleteFcn = ['spm_orthviews(''Delete'',' num2str(i) ');']; + + % Transverse + set(st.vols{i}.ax{1}.ax,'Units','pixels', ... + 'Position',[offx offy s*Dims(1) s*Dims(2)],... + 'Units','normalized','Xlim',[0 TD(1)]+0.5,'Ylim',[0 TD(2)]+0.5,... + 'Visible','on','XTick',[],'YTick',[]); + + % Coronal + set(st.vols{i}.ax{2}.ax,'Units','Pixels',... + 'Position',[offx offy+s*Dims(2)+sky s*Dims(1) s*Dims(3)],... + 'Units','normalized','Xlim',[0 CD(1)]+0.5,'Ylim',[0 CD(2)]+0.5,... + 'Visible','on','XTick',[],'YTick',[]); + + % Sagittal + if st.mode == 0, + set(st.vols{i}.ax{3}.ax,'Units','Pixels', 'Box','on',... + 'Position',[offx+s*Dims(1)+skx offy s*Dims(3) s*Dims(2)],... + 'Units','normalized','Xlim',[0 SD(1)]+0.5,'Ylim',[0 SD(2)]+0.5,... + 'Visible','on','XTick',[],'YTick',[]); + else, + set(st.vols{i}.ax{3}.ax,'Units','Pixels', 'Box','on',... + 'Position',[offx+s*Dims(1)+skx offy+s*Dims(2)+sky s*Dims(2) s*Dims(3)],... + 'Units','normalized','Xlim',[0 SD(1)]+0.5,'Ylim',[0 SD(2)]+0.5,... + 'Visible','on','XTick',[],'YTick',[]); + end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function redraw_all +global st +redraw(1:24); +return; +%_______________________________________________________________________ +function mx = maxval(vol) +if isstruct(vol), + mx = -Inf; + for i=1:vol.dim(3), + tmp = spm_slice_vol(vol,spm_matrix([0 0 i]),vol.dim(1:2),0); + imx = max(tmp(find(finite(tmp)))); + if ~isempty(imx),mx = max(mx,imx);end + end; +else, + mx = max(vol(find(finite(vol)))); +end; +%_______________________________________________________________________ +function mn = minval(vol) +if isstruct(vol), + mn = Inf; + for i=1:vol.dim(3), + tmp = spm_slice_vol(vol,spm_matrix([0 0 i]),vol.dim(1:2),0); + imn = min(tmp(find(finite(tmp)))); + if ~isempty(imn),mn = min(mn,imn);end + end; +else, + mn = min(vol(find(finite(vol)))); +end; + +%_______________________________________________________________________ +%_______________________________________________________________________ +function redraw(arg1) +global st +bb = st.bb; +Dims = round(diff(bb)'+1); +is = inv(st.Space); +cent = is(1:3,1:3)*st.centre(:) + is(1:3,4); + +for i = valid_handles(arg1), + M = st.vols{i}.premul*st.vols{i}.mat; + TM0 = [ 1 0 0 -bb(1,1)+1 + 0 1 0 -bb(1,2)+1 + 0 0 1 -cent(3) + 0 0 0 1]; + TM = inv(TM0*(st.Space\M)); + TD = Dims([1 2]); + + CM0 = [ 1 0 0 -bb(1,1)+1 + 0 0 1 -bb(1,3)+1 + 0 1 0 -cent(2) + 0 0 0 1]; + CM = inv(CM0*(st.Space\M)); + CD = Dims([1 3]); + + if st.mode ==0, + SM0 = [ 0 0 1 -bb(1,3)+1 + 0 1 0 -bb(1,2)+1 + 1 0 0 -cent(1) + 0 0 0 1]; + SM = inv(SM0*(st.Space\M)); SD = Dims([3 2]); + else, + SM0 = [ 0 1 0 -bb(1,2)+1 + 0 0 1 -bb(1,3)+1 + 1 0 0 -cent(1) + 0 0 0 1]; + SM0 = [ 0 -1 0 +bb(2,2)+1 + 0 0 1 -bb(1,3)+1 + 1 0 0 -cent(1) + 0 0 0 1]; + SM = inv(SM0*(st.Space\M)); + SD = Dims([2 3]); + end; + + ok=1; + eval('imgt = (spm_slice_vol(st.vols{i},TM,TD,st.hld))'';','ok=0;'); + eval('imgc = (spm_slice_vol(st.vols{i},CM,CD,st.hld))'';','ok=0;'); + eval('imgs = (spm_slice_vol(st.vols{i},SM,SD,st.hld))'';','ok=0;'); + if (ok==0), fprintf('Image "%s" can not be resampled\n', st.vols{i}.fname); + else, + % get min/max threshold + if strcmp(st.vols{i}.window,'auto') + mn = -Inf; + mx = Inf; + else + mn = min(st.vols{i}.window); + mx = max(st.vols{i}.window); + end; + % threshold images + imgt = max(imgt,mn); imgt = min(imgt,mx); + imgc = max(imgc,mn); imgc = min(imgc,mx); + imgs = max(imgs,mn); imgs = min(imgs,mx); + % compute intensity mapping, if histeq is available + if license('test','image_toolbox') == 0 + st.vols{i}.mapping = 'linear'; + end; + switch st.vols{i}.mapping, + case 'linear', + case 'histeq', + % scale images to a range between 0 and 1 + imgt1=(imgt-min(imgt(:)))/(max(imgt(:)-min(imgt(:)))+eps); + imgc1=(imgc-min(imgc(:)))/(max(imgc(:)-min(imgc(:)))+eps); + imgs1=(imgs-min(imgs(:)))/(max(imgs(:)-min(imgs(:)))+eps); + img = histeq([imgt1(:); imgc1(:); imgs1(:)],1024); + imgt = reshape(img(1:numel(imgt1)),size(imgt1)); + imgc = reshape(img(numel(imgt1)+[1:numel(imgc1)]),size(imgc1)); + imgs = reshape(img(numel(imgt1)+numel(imgc1)+[1:numel(imgs1)]),size(imgs1)); + mn = 0; + mx = 1; + case 'quadhisteq', + % scale images to a range between 0 and 1 + imgt1=(imgt-min(imgt(:)))/(max(imgt(:)-min(imgt(:)))+eps); + imgc1=(imgc-min(imgc(:)))/(max(imgc(:)-min(imgc(:)))+eps); + imgs1=(imgs-min(imgs(:)))/(max(imgs(:)-min(imgs(:)))+eps); + img = histeq([imgt1(:).^2; imgc1(:).^2; imgs1(:).^2],1024); + imgt = reshape(img(1:numel(imgt1)),size(imgt1)); + imgc = reshape(img(numel(imgt1)+[1:numel(imgc1)]),size(imgc1)); + imgs = reshape(img(numel(imgt1)+numel(imgc1)+[1:numel(imgs1)]),size(imgs1)); + mn = 0; + mx = 1; + case 'loghisteq', + warning off % messy - but it may avoid extra queries + imgt = log(imgt-min(imgt(:))); + imgc = log(imgc-min(imgc(:))); + imgs = log(imgs-min(imgs(:))); + warning on + imgt(~isfinite(imgt)) = 0; + imgc(~isfinite(imgc)) = 0; + imgs(~isfinite(imgs)) = 0; + % scale log images to a range between 0 and 1 + imgt1=(imgt-min(imgt(:)))/(max(imgt(:)-min(imgt(:)))+eps); + imgc1=(imgc-min(imgc(:)))/(max(imgc(:)-min(imgc(:)))+eps); + imgs1=(imgs-min(imgs(:)))/(max(imgs(:)-min(imgs(:)))+eps); + img = histeq([imgt1(:); imgc1(:); imgs1(:)],1024); + imgt = reshape(img(1:numel(imgt1)),size(imgt1)); + imgc = reshape(img(numel(imgt1)+[1:numel(imgc1)]),size(imgc1)); + imgs = reshape(img(numel(imgt1)+numel(imgc1)+[1:numel(imgs1)]),size(imgs1)); + mn = 0; + mx = 1; + end; + % recompute min/max for display + if strcmp(st.vols{i}.window,'auto') + mx = -inf; mn = inf; + end; + if ~isempty(imgt), + tmp = imgt(isfinite(imgt)); + mx = max([mx max(max(tmp))]); + mn = min([mn min(min(tmp))]); + end; + if ~isempty(imgc), + tmp = imgc(isfinite(imgc)); + mx = max([mx max(max(tmp))]); + mn = min([mn min(min(tmp))]); + end; + if ~isempty(imgs), + tmp = imgs(isfinite(imgs)); + mx = max([mx max(max(tmp))]); + mn = min([mn min(min(tmp))]); + end; + if mx==mn, mx=mn+eps; end; + + if isfield(st.vols{i},'blobs'), + if ~isfield(st.vols{i}.blobs{1},'colour'), + % Add blobs for display using the split colourmap + scal = 64/(mx-mn); + dcoff = -mn*scal; + imgt = imgt*scal+dcoff; + imgc = imgc*scal+dcoff; + imgs = imgs*scal+dcoff; + + if isfield(st.vols{i}.blobs{1},'max'), + mx = st.vols{i}.blobs{1}.max; + else, + mx = max([eps maxval(st.vols{i}.blobs{1}.vol)]); + st.vols{i}.blobs{1}.max = mx; + end; + if isfield(st.vols{i}.blobs{1},'min'), + mn = st.vols{i}.blobs{1}.min; + else, + mn = min([0 minval(st.vols{i}.blobs{1}.vol)]); + st.vols{i}.blobs{1}.min = mn; + end; + + vol = st.vols{i}.blobs{1}.vol; + M = st.vols{i}.premul*st.vols{i}.blobs{1}.mat; + tmpt = spm_slice_vol(vol,inv(TM0*(st.Space\M)),TD,[0 NaN])'; + tmpc = spm_slice_vol(vol,inv(CM0*(st.Space\M)),CD,[0 NaN])'; + tmps = spm_slice_vol(vol,inv(SM0*(st.Space\M)),SD,[0 NaN])'; + + %tmpt_z = find(tmpt==0);tmpt(tmpt_z) = NaN; + %tmpc_z = find(tmpc==0);tmpc(tmpc_z) = NaN; + %tmps_z = find(tmps==0);tmps(tmps_z) = NaN; + + sc = 64/(mx-mn); + off = 65.51-mn*sc; + msk = find(isfinite(tmpt)); imgt(msk) = off+tmpt(msk)*sc; + msk = find(isfinite(tmpc)); imgc(msk) = off+tmpc(msk)*sc; + msk = find(isfinite(tmps)); imgs(msk) = off+tmps(msk)*sc; + + cmap = get(st.fig,'Colormap'); + + figure(st.fig) + if mn*mx < 0 + setcolormap('gray-hot-cold') + elseif mx > 0 + setcolormap('gray-hot'); + else + setcolormap('gray-cold') + end + redraw_colourbar(i,1,[mn mx],[1:64]'+64); + elseif isstruct(st.vols{i}.blobs{1}.colour), + % Add blobs for display using a defined + % colourmap + + % colourmaps + gryc = [0:63]'*ones(1,3)/63; + actc = ... + st.vols{1}.blobs{1}.colour.cmap; + actp = ... + st.vols{1}.blobs{1}.colour.prop; + + % scale grayscale image, not finite -> black + imgt = scaletocmap(imgt,mn,mx,gryc,65); + imgc = scaletocmap(imgc,mn,mx,gryc,65); + imgs = scaletocmap(imgs,mn,mx,gryc,65); + gryc = [gryc; 0 0 0]; + + % get max for blob image + vol = st.vols{i}.blobs{1}.vol; + mat = st.vols{i}.premul*st.vols{i}.blobs{1}.mat; + if isfield(st.vols{i}.blobs{1},'max'), + cmx = st.vols{i}.blobs{1}.max; + else, + cmx = max([eps maxval(st.vols{i}.blobs{1}.vol)]); + end; + if isfield(st.vols{i}.blobs{1},'min'), + cmn = st.vols{i}.blobs{1}.min; + else, + cmn = -cmx; + end; + + % get blob data + vol = st.vols{i}.blobs{1}.vol; + M = st.vols{i}.premul*st.vols{i}.blobs{1}.mat; + tmpt = spm_slice_vol(vol,inv(TM0*(st.Space\M)),TD,[0 NaN])'; + tmpc = spm_slice_vol(vol,inv(CM0*(st.Space\M)),CD,[0 NaN])'; + tmps = spm_slice_vol(vol,inv(SM0*(st.Space\M)),SD,[0 NaN])'; + + % actimg scaled round 0, black NaNs + topc = size(actc,1)+1; + tmpt = scaletocmap(tmpt,cmn,cmx,actc,topc); + tmpc = scaletocmap(tmpc,cmn,cmx,actc,topc); + tmps = scaletocmap(tmps,cmn,cmx,actc,topc); + actc = [actc; 0 0 0]; + + % combine gray and blob data to + % truecolour + imgt = reshape(actc(tmpt(:),:)*actp+ ... + gryc(imgt(:),:)*(1-actp), ... + [size(imgt) 3]); + imgc = reshape(actc(tmpc(:),:)*actp+ ... + gryc(imgc(:),:)*(1-actp), ... + [size(imgc) 3]); + imgs = reshape(actc(tmps(:),:)*actp+ ... + gryc(imgs(:),:)*(1-actp), ... + [size(imgs) 3]); + + redraw_colourbar(i,1,[cmn cmx],[1:64]'+64); + + else, + % Add full colour blobs - several sets at once + scal = 1/(mx-mn); + dcoff = -mn*scal; + + wt = zeros(size(imgt)); + wc = zeros(size(imgc)); + ws = zeros(size(imgs)); + + imgt = repmat(imgt*scal+dcoff,[1,1,3]); + imgc = repmat(imgc*scal+dcoff,[1,1,3]); + imgs = repmat(imgs*scal+dcoff,[1,1,3]); + + cimgt = zeros(size(imgt)); + cimgc = zeros(size(imgc)); + cimgs = zeros(size(imgs)); + + for j=1:length(st.vols{i}.blobs), % get colours of all images first + if isfield(st.vols{i}.blobs{j},'colour'), + colour(j,:) = reshape(st.vols{i}.blobs{j}.colour, [1 3]); + else, + colour(j,:) = [1 0 0]; + end; + end; + %colour = colour/max(sum(colour)); + + for j=1:length(st.vols{i}.blobs), + if isfield(st.vols{i}.blobs{j},'max'), + mx = st.vols{i}.blobs{j}.max; + else, + mx = max([eps max(st.vols{i}.blobs{j}.vol(:))]); + st.vols{i}.blobs{j}.max = mx; + end; + if isfield(st.vols{i}.blobs{j},'min'), + mn = st.vols{i}.blobs{j}.min; + else, + mn = min([0 min(st.vols{i}.blobs{j}.vol(:))]); + st.vols{i}.blobs{j}.min = mn; + end; + + vol = st.vols{i}.blobs{j}.vol; + M = st.Space\st.vols{i}.premul*st.vols{i}.blobs{j}.mat; + tmpt = spm_slice_vol(vol,inv(TM0*M),TD,[0 NaN])'; + tmpc = spm_slice_vol(vol,inv(CM0*M),CD,[0 NaN])'; + tmps = spm_slice_vol(vol,inv(SM0*M),SD,[0 NaN])'; + % check min/max of sampled image + % against mn/mx as given in st + tmpt(tmpt(:)mx) = mx; + tmpc(tmpc(:)>mx) = mx; + tmps(tmps(:)>mx) = mx; + tmpt = (tmpt-mn)/(mx-mn); + tmpc = (tmpc-mn)/(mx-mn); + tmps = (tmps-mn)/(mx-mn); + tmpt(~finite(tmpt)) = 0; + tmpc(~finite(tmpc)) = 0; + tmps(~finite(tmps)) = 0; + + cimgt = cimgt + cat(3,tmpt*colour(j,1),tmpt*colour(j,2),tmpt*colour(j,3)); + cimgc = cimgc + cat(3,tmpc*colour(j,1),tmpc*colour(j,2),tmpc*colour(j,3)); + cimgs = cimgs + cat(3,tmps*colour(j,1),tmps*colour(j,2),tmps*colour(j,3)); + + wt = wt + tmpt; + wc = wc + tmpc; + ws = ws + tmps; + cdata=permute(shiftdim([1/64:1/64:1]'* ... + colour(j,:),-1),[2 1 3]); + redraw_colourbar(i,j,[mn mx],cdata); + end; + + imgt = repmat(1-wt,[1 1 3]).*imgt+cimgt; + imgc = repmat(1-wc,[1 1 3]).*imgc+cimgc; + imgs = repmat(1-ws,[1 1 3]).*imgs+cimgs; + + imgt(imgt<0)=0; imgt(imgt>1)=1; + imgc(imgc<0)=0; imgc(imgc>1)=1; + imgs(imgs<0)=0; imgs(imgs>1)=1; + end; + else, + scal = 64/(mx-mn); + dcoff = -mn*scal; + imgt = imgt*scal+dcoff; + imgc = imgc*scal+dcoff; + imgs = imgs*scal+dcoff; + end; + + set(st.vols{i}.ax{1}.d,'HitTest','off', 'Cdata',imgt); + set(st.vols{i}.ax{1}.lx,'HitTest','off',... + 'Xdata',[0 TD(1)]+0.5,'Ydata',[1 1]*(cent(2)-bb(1,2)+1)); + set(st.vols{i}.ax{1}.ly,'HitTest','off',... + 'Ydata',[0 TD(2)]+0.5,'Xdata',[1 1]*(cent(1)-bb(1,1)+1)); + + set(st.vols{i}.ax{2}.d,'HitTest','off', 'Cdata',imgc); + set(st.vols{i}.ax{2}.lx,'HitTest','off',... + 'Xdata',[0 CD(1)]+0.5,'Ydata',[1 1]*(cent(3)-bb(1,3)+1)); + set(st.vols{i}.ax{2}.ly,'HitTest','off',... + 'Ydata',[0 CD(2)]+0.5,'Xdata',[1 1]*(cent(1)-bb(1,1)+1)); + + set(st.vols{i}.ax{3}.d,'HitTest','off','Cdata',imgs); + if st.mode ==0, + set(st.vols{i}.ax{3}.lx,'HitTest','off',... + 'Xdata',[0 SD(1)]+0.5,'Ydata',[1 1]*(cent(2)-bb(1,2)+1)); + set(st.vols{i}.ax{3}.ly,'HitTest','off',... + 'Ydata',[0 SD(2)]+0.5,'Xdata',[1 1]*(cent(3)-bb(1,3)+1)); + else, + set(st.vols{i}.ax{3}.lx,'HitTest','off',... + 'Xdata',[0 SD(1)]+0.5,'Ydata',[1 1]*(cent(3)-bb(1,3)+1)); + set(st.vols{i}.ax{3}.ly,'HitTest','off',... + 'Ydata',[0 SD(2)]+0.5,'Xdata',[1 1]*(bb(2,2)+1-cent(2))); + end; + + if ~isempty(st.plugins) % process any addons + for k = 1:prod(size(st.plugins)) + if isfield(st.vols{i},st.plugins{k}) + feval(['spm_ov_', st.plugins{k}], ... + 'redraw', i, TM0, TD, CM0, CD, SM0, SD); + end; + end; + end; + end; +end; +drawnow; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function redraw_colourbar(vh,bh,interval,cdata) +global st +if isfield(st.vols{vh}.blobs{bh},'cbar') + if st.mode == 0, + axpos = get(st.vols{vh}.ax{2}.ax,'Position'); + else, + axpos = get(st.vols{vh}.ax{1}.ax,'Position'); + end; + % only scale cdata if we have out-of-range truecolour values + if ndims(cdata)==3 && max(cdata(:))>1 + cdata=cdata./max(cdata(:)); + end; + image([0 1],interval,cdata,'Parent',st.vols{vh}.blobs{bh}.cbar); + set(st.vols{vh}.blobs{bh}.cbar, ... + 'Position',[(axpos(1)+axpos(3)+0.05+(bh-1)*.1)... + (axpos(2)+0.005) 0.05 (axpos(4)-0.01)],... + 'YDir','normal','XTickLabel',[],'XTick',[]); +end; +%_______________________________________________________________________ +%_______________________________________________________________________ +function centre = findcent +global st +obj = get(st.fig,'CurrentObject'); +centre = []; +cent = []; +cp = []; +for i=valid_handles(1:24), + for j=1:3, + if ~isempty(obj), + if (st.vols{i}.ax{j}.ax == obj), + cp = get(obj,'CurrentPoint'); + end; + end; + if ~isempty(cp), + cp = cp(1,1:2); + is = inv(st.Space); + cent = is(1:3,1:3)*st.centre(:) + is(1:3,4); + switch j, + case 1, + cent([1 2])=[cp(1)+st.bb(1,1)-1 cp(2)+st.bb(1,2)-1]; + case 2, + cent([1 3])=[cp(1)+st.bb(1,1)-1 cp(2)+st.bb(1,3)-1]; + case 3, + if st.mode ==0, + cent([3 2])=[cp(1)+st.bb(1,3)-1 cp(2)+st.bb(1,2)-1]; + else, + cent([2 3])=[st.bb(2,2)+1-cp(1) cp(2)+st.bb(1,3)-1]; + end; + end; + break; + end; + end; + if ~isempty(cent), break; end; +end; +if ~isempty(cent), centre = st.Space(1:3,1:3)*cent(:) + st.Space(1:3,4); end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function handles = valid_handles(handles) +global st; +handles = handles(:)'; +handles = handles(find(handles<=24 & handles>=1 & ~rem(handles,1))); +for h=handles, + if isempty(st.vols{h}), handles(find(handles==h))=[]; end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function reset_st +global st +fig = spm_figure('FindWin','Graphics'); +bb = []; %[ [-78 78]' [-112 76]' [-50 85]' ]; +st = struct('n', 0, 'vols',[], 'bb',bb,'Space',eye(4),'centre',[0 0 0],'callback',';','xhairs',1,'hld',1,'fig',fig,'mode',1,'plugins',[],'snap',[]); +st.vols = cell(24,1); + +pluginpath = fullfile(spm('Dir'),'spm_orthviews'); +if isdir(pluginpath) + pluginfiles = dir(fullfile(pluginpath,'spm_ov_*.m')); + if ~isempty(pluginfiles) + addpath(pluginpath); + % fprintf('spm_orthviews: Using Plugins in %s\n', pluginpath); + for k = 1:length(pluginfiles) + [p pluginname e v] = fileparts(pluginfiles(k).name); + st.plugins{k} = strrep(pluginname, 'spm_ov_',''); + % fprintf('%s\n',st.plugins{k}); + end; + end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function img = scaletocmap(inpimg,mn,mx,cmap,miscol) +if nargin < 5, miscol=1;end +cml = size(cmap,1); +scf = (cml-1)/(mx-mn); +img = round((inpimg-mn)*scf)+1; +img(find(img<1)) = 1; +img(find(img>cml)) = cml; +img(~finite(img)) = miscol; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function cmap = getcmap(acmapname) +% get colormap of name acmapname +if ~isempty(acmapname), + cmap = evalin('base',acmapname,'[]'); + if isempty(cmap), % not a matrix, is .mat file? + [p f e] = fileparts(acmapname); + acmat = fullfile(p, [f '.mat']); + if exist(acmat, 'file'), + s = struct2cell(load(acmat)); + cmap = s{1}; + end; + end; +end; +if size(cmap, 2)~=3, + warning('Colormap was not an N by 3 matrix') + cmap = []; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function item_parent = addcontext(volhandle) +global st; +%create context menu +fg = spm_figure('Findwin','Graphics');set(0,'CurrentFigure',fg); +%contextmenu +item_parent = uicontextmenu; + +%contextsubmenu 0 +item00 = uimenu(item_parent, 'Label','unknown image', 'Separator','on'); +spm_orthviews('context_menu','image_info',item00,volhandle); +item0a = uimenu(item_parent, 'UserData','pos_mm', 'Callback','spm_orthviews(''context_menu'',''repos_mm'');','Separator','on'); +item0b = uimenu(item_parent, 'UserData','pos_vx', 'Callback','spm_orthviews(''context_menu'',''repos_vx'');'); +item0c = uimenu(item_parent, 'UserData','v_value'); + +%contextsubmenu 1 +item1 = uimenu(item_parent,'Label','Zoom'); +item1_1 = uimenu(item1, 'Label','Full Volume', 'Callback','spm_orthviews(''context_menu'',''zoom'',6);', 'Checked','on'); +item1_2 = uimenu(item1, 'Label','160x160x160mm', 'Callback','spm_orthviews(''context_menu'',''zoom'',5);'); +item1_3 = uimenu(item1, 'Label','80x80x80mm', 'Callback','spm_orthviews(''context_menu'',''zoom'',4);'); +item1_4 = uimenu(item1, 'Label','40x40x40mm', 'Callback','spm_orthviews(''context_menu'',''zoom'',3);'); +item1_5 = uimenu(item1, 'Label','20x20x20mm', 'Callback','spm_orthviews(''context_menu'',''zoom'',2);'); +item1_6 = uimenu(item1, 'Label','10x10x10mm', 'Callback','spm_orthviews(''context_menu'',''zoom'',1);'); + +%contextsubmenu 2 +checked={'off','off'}; +checked{st.xhairs+1} = 'on'; +item2 = uimenu(item_parent,'Label','Crosshairs'); +item2_1 = uimenu(item2, 'Label','on', 'Callback','spm_orthviews(''context_menu'',''Xhair'',''on'');','Checked',checked{2}); +item2_2 = uimenu(item2, 'Label','off', 'Callback','spm_orthviews(''context_menu'',''Xhair'',''off'');','Checked',checked{1}); + +%contextsubmenu 3 +if st.Space == eye(4) + checked = {'off', 'on'}; +else + checked = {'on', 'off'}; +end; +item3 = uimenu(item_parent,'Label','Orientation'); +item3_1 = uimenu(item3, 'Label','World space', 'Callback','spm_orthviews(''context_menu'',''orientation'',3);','Checked',checked{2}); +item3_2 = uimenu(item3, 'Label','Voxel space (1st image)', 'Callback','spm_orthviews(''context_menu'',''orientation'',2);','Checked',checked{1}); +item3_3 = uimenu(item3, 'Label','Voxel space (this image)', 'Callback','spm_orthviews(''context_menu'',''orientation'',1);','Checked','off'); + +%contextsubmenu 3 +if isempty(st.snap) + checked = {'off', 'on'}; +else + checked = {'on', 'off'}; +end; +item3 = uimenu(item_parent,'Label','Snap to Grid'); +item3_1 = uimenu(item3, 'Label','Don''t snap', 'Callback','spm_orthviews(''context_menu'',''snap'',3);','Checked',checked{2}); +item3_2 = uimenu(item3, 'Label','Snap to 1st image', 'Callback','spm_orthviews(''context_menu'',''snap'',2);','Checked',checked{1}); +item3_3 = uimenu(item3, 'Label','Snap to this image', 'Callback','spm_orthviews(''context_menu'',''snap'',1);','Checked','off'); + +%contextsubmenu 4 +if st.hld == 0, + checked = {'off', 'off', 'on'}; +elseif st.hld > 0, + checked = {'off', 'on', 'off'}; +else, + checked = {'on', 'off', 'off'}; +end; +item4 = uimenu(item_parent,'Label','Interpolation'); +item4_1 = uimenu(item4, 'Label','NN', 'Callback','spm_orthviews(''context_menu'',''interpolation'',3);', 'Checked',checked{3}); +item4_2 = uimenu(item4, 'Label','Bilin', 'Callback','spm_orthviews(''context_menu'',''interpolation'',2);','Checked',checked{2}); +item4_3 = uimenu(item4, 'Label','Sinc', 'Callback','spm_orthviews(''context_menu'',''interpolation'',1);','Checked',checked{1}); + +%contextsubmenu 5 +% item5 = uimenu(item_parent,'Label','Position', 'Callback','spm_orthviews(''context_menu'',''position'');'); + +%contextsubmenu 6 +item6 = uimenu(item_parent,'Label','Image','Separator','on'); +item6_1 = uimenu(item6, 'Label','Window'); +item6_1_1 = uimenu(item6_1, 'Label','local'); +item6_1_1_1 = uimenu(item6_1_1, 'Label','auto', 'Callback','spm_orthviews(''context_menu'',''window'',2);'); +item6_1_1_2 = uimenu(item6_1_1, 'Label','manual', 'Callback','spm_orthviews(''context_menu'',''window'',1);'); +item6_1_2 = uimenu(item6_1, 'Label','global'); +item6_1_2_1 = uimenu(item6_1_2, 'Label','auto', 'Callback','spm_orthviews(''context_menu'',''window_gl'',2);'); +item6_1_2_2 = uimenu(item6_1_2, 'Label','manual', 'Callback','spm_orthviews(''context_menu'',''window_gl'',1);'); +if license('test','image_toolbox') == 1 + offon = {'off', 'on'}; + checked = offon(strcmp(st.vols{volhandle}.mapping, ... + {'linear', 'histeq', 'loghisteq', 'quadhisteq'})+1); + item6_2 = uimenu(item6, 'Label','Intensity mapping'); + item6_2_1 = uimenu(item6_2, 'Label','local'); + item6_2_1_1 = uimenu(item6_2_1, 'Label','Linear', 'Checked',checked{1}, ... + 'Callback','spm_orthviews(''context_menu'',''mapping'',''linear'');'); + item6_2_1_2 = uimenu(item6_2_1, 'Label','Equalised histogram', 'Checked',checked{2}, ... + 'Callback','spm_orthviews(''context_menu'',''mapping'',''histeq'');'); + item6_2_1_3 = uimenu(item6_2_1, 'Label','Equalised log-histogram', 'Checked',checked{3}, ... + 'Callback','spm_orthviews(''context_menu'',''mapping'',''loghisteq'');'); + item6_2_1_4 = uimenu(item6_2_1, 'Label','Equalised squared-histogram', 'Checked',checked{4}, ... + 'Callback','spm_orthviews(''context_menu'',''mapping'',''quadhisteq'');'); + item6_2_2 = uimenu(item6_2, 'Label','global'); + item6_2_2_1 = uimenu(item6_2_2, 'Label','Linear', 'Checked',checked{1}, ... + 'Callback','spm_orthviews(''context_menu'',''mapping_gl'',''linear'');'); + item6_2_2_2 = uimenu(item6_2_2, 'Label','Equalised histogram', 'Checked',checked{2}, ... + 'Callback','spm_orthviews(''context_menu'',''mapping_gl'',''histeq'');'); + item6_2_2_3 = uimenu(item6_2_2, 'Label','Equalised log-histogram', 'Checked',checked{3}, ... + 'Callback','spm_orthviews(''context_menu'',''mapping_gl'',''loghisteq'');'); + item6_2_2_4 = uimenu(item6_2_2, 'Label','Equalised squared-histogram', 'Checked',checked{4}, ... + 'Callback','spm_orthviews(''context_menu'',''mapping_gl'',''quadhisteq'');'); +end; +%contextsubmenu 7 +item7 = uimenu(item_parent,'Label','Blobs'); +item7_1 = uimenu(item7, 'Label','Add blobs'); +item7_1_1 = uimenu(item7_1, 'Label','local', 'Callback','spm_orthviews(''context_menu'',''add_blobs'',2);'); +item7_1_2 = uimenu(item7_1, 'Label','global', 'Callback','spm_orthviews(''context_menu'',''add_blobs'',1);'); +item7_2 = uimenu(item7, 'Label','Add image'); +item7_2_1 = uimenu(item7_2, 'Label','local', 'Callback','spm_orthviews(''context_menu'',''add_image'',2);'); +item7_2_2 = uimenu(item7_2, 'Label','global', 'Callback','spm_orthviews(''context_menu'',''add_image'',1);'); +item7_3 = uimenu(item7, 'Label','Add colored blobs','Separator','on'); +item7_3_1 = uimenu(item7_3, 'Label','local', 'Callback','spm_orthviews(''context_menu'',''add_c_blobs'',2);'); +item7_3_2 = uimenu(item7_3, 'Label','global', 'Callback','spm_orthviews(''context_menu'',''add_c_blobs'',1);'); +item7_4 = uimenu(item7, 'Label','Add colored image'); +item7_4_1 = uimenu(item7_4, 'Label','local', 'Callback','spm_orthviews(''context_menu'',''add_c_image'',2);'); +item7_4_2 = uimenu(item7_4, 'Label','global', 'Callback','spm_orthviews(''context_menu'',''add_c_image'',1);'); +item7_5 = uimenu(item7, 'Label','Remove blobs', 'Visible','off','Separator','on'); +item7_6 = uimenu(item7, 'Label','Remove colored blobs','Visible','off'); +item7_6_1 = uimenu(item7_6, 'Label','local', 'Visible','on'); +item7_6_2 = uimenu(item7_6, 'Label','global','Visible','on'); + +if ~isempty(st.plugins) % process any plugins + for k = 1:prod(size(st.plugins)), + feval(['spm_ov_', st.plugins{k}], ... + 'context_menu', volhandle, item_parent); + end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function c_menu(varargin) +global st + +switch lower(varargin{1}), + case 'image_info', + if nargin <3, + current_handle = get_current_handle; + else + current_handle = varargin{3}; + end; + if isfield(st.vols{current_handle},'fname'), + [p,n,e,v] = spm_fileparts(st.vols{current_handle}.fname); + if isfield(st.vols{current_handle},'n') + v = sprintf(',%d',st.vols{current_handle}.n); + end; + set(varargin{2}, 'Label',[n e v]); + end; + delete(get(varargin{2},'children')); + if exist('p','var') + item1 = uimenu(varargin{2}, 'Label', p); + end; + if isfield(st.vols{current_handle},'descrip'), + item2 = uimenu(varargin{2}, 'Label',... + st.vols{current_handle}.descrip); + end; + dt = st.vols{current_handle}.dt(1); + item3 = uimenu(varargin{2}, 'Label', sprintf('Data type: %s', spm_type(dt))); + str = 'Intensity: varied'; + if size(st.vols{current_handle}.pinfo,2) == 1, + if st.vols{current_handle}.pinfo(2), + str = sprintf('Intensity: Y = %g X + %g',... + st.vols{current_handle}.pinfo(1:2)'); + else, + str = sprintf('Intensity: Y = %g X', st.vols{current_handle}.pinfo(1)'); + end; + end; + item4 = uimenu(varargin{2}, 'Label',str); + item5 = uimenu(varargin{2}, 'Label', 'Image dims', 'Separator','on'); + item51 = uimenu(varargin{2}, 'Label',... + sprintf('%dx%dx%d', st.vols{current_handle}.dim(1:3))); + prms = spm_imatrix(st.vols{current_handle}.mat); + item6 = uimenu(varargin{2}, 'Label','Voxel size', 'Separator','on'); + item61 = uimenu(varargin{2}, 'Label', sprintf('%.2f %.2f %.2f', prms(7:9))); + item7 = uimenu(varargin{2}, 'Label','Origin', 'Separator','on'); + item71 = uimenu(varargin{2}, 'Label',... + sprintf('%.2f %.2f %.2f', prms(1:3))); + R = spm_matrix([0 0 0 prms(4:6)]); + item8 = uimenu(varargin{2}, 'Label','Rotations', 'Separator','on'); + item81 = uimenu(varargin{2}, 'Label', sprintf('%.2f %.2f %.2f', R(1,1:3))); + item82 = uimenu(varargin{2}, 'Label', sprintf('%.2f %.2f %.2f', R(2,1:3))); + item83 = uimenu(varargin{2}, 'Label', sprintf('%.2f %.2f %.2f', R(3,1:3))); + item9 = uimenu(varargin{2},... + 'Label','Specify other image...',... + 'Callback','spm_orthviews(''context_menu'',''swap_img'');',... + 'Separator','on'); + + case 'repos_mm', + oldpos_mm = spm_orthviews('pos'); + newpos_mm = spm_input('New Position (mm)','+1','r',sprintf('%.2f %.2f %.2f',oldpos_mm),3); + spm_orthviews('reposition',newpos_mm); + + case 'repos_vx' + current_handle = get_current_handle; + oldpos_vx = spm_orthviews('pos', current_handle); + newpos_vx = spm_input('New Position (voxels)','+1','r',sprintf('%.2f %.2f %.2f',oldpos_vx),3); + newpos_mm = st.vols{current_handle}.mat*[newpos_vx;1]; + spm_orthviews('reposition',newpos_mm(1:3)); + + case 'zoom' + zoom_all(varargin{2}); + bbox; + redraw_all; + + case 'xhair', + spm_orthviews('Xhairs',varargin{2}); + cm_handles = get_cm_handles; + for i = 1:length(cm_handles), + z_handle = get(findobj(cm_handles(i),'label','Crosshairs'),'Children'); + set(z_handle,'Checked','off'); %reset check + if strcmp(varargin{2},'off'), op = 1; else op = 2; end + set(z_handle(op),'Checked','on'); + end; + + case 'orientation', + cm_handles = get_cm_handles; + for i = 1:length(cm_handles), + z_handle = get(findobj(cm_handles(i),'label','Orientation'),'Children'); + set(z_handle,'Checked','off'); + end; + if varargin{2} == 3, + spm_orthviews('Space'); + elseif varargin{2} == 2, + spm_orthviews('Space',1); + else, + spm_orthviews('Space',get_current_handle); + z_handle = get(findobj(st.vols{get_current_handle}.ax{1}.cm,'label','Orientation'),'Children'); + set(z_handle(1),'Checked','on'); + return; + end; + for i = 1:length(cm_handles), + z_handle = get(findobj(cm_handles(i),'label','Orientation'),'Children'); + set(z_handle(varargin{2}),'Checked','on'); + end; + + case 'snap', + cm_handles = get_cm_handles; + for i = 1:length(cm_handles), + z_handle = get(findobj(cm_handles(i),'label','Snap to Grid'),'Children'); + set(z_handle,'Checked','off'); + end; + if varargin{2} == 3, + st.snap = []; + elseif varargin{2} == 2, + st.snap = 1; + else, + st.snap = get_current_handle; + z_handle = get(findobj(st.vols{get_current_handle}.ax{1}.cm,'label','Snap to Grid'),'Children'); + set(z_handle(1),'Checked','on'); + return; + end; + for i = 1:length(cm_handles), + z_handle = get(findobj(cm_handles(i),'label','Snap to Grid'),'Children'); + set(z_handle(varargin{2}),'Checked','on'); + end; + + case 'interpolation', + tmp = [-4 1 0]; + st.hld = tmp(varargin{2}); + cm_handles = get_cm_handles; + for i = 1:length(cm_handles), + z_handle = get(findobj(cm_handles(i),'label','Interpolation'),'Children'); + set(z_handle,'Checked','off'); + set(z_handle(varargin{2}),'Checked','on'); + end; + redraw_all; + + case 'window', + current_handle = get_current_handle; + if varargin{2} == 2, + spm_orthviews('window',current_handle); + else + if isnumeric(st.vols{current_handle}.window) + defstr = sprintf('%.2f %.2f', st.vols{current_handle}.window); + else + defstr = ''; + end; + spm_orthviews('window',current_handle,spm_input('Range','+1','e',defstr,2)); + end; + + case 'window_gl', + if varargin{2} == 2, + for i = 1:length(get_cm_handles), + st.vols{i}.window = 'auto'; + end; + else, + current_handle = get_current_handle; + if isnumeric(st.vols{current_handle}.window) + defstr = sprintf('%d %d', st.vols{current_handle}.window); + else + defstr = ''; + end; + data = spm_input('Range','+1','e',defstr,2); + + for i = 1:length(get_cm_handles), + st.vols{i}.window = data; + end; + end; + redraw_all; + + case 'mapping', + checked = strcmp(varargin{2}, ... + {'linear', 'histeq', 'loghisteq', ... + 'quadhisteq'}); + checked = checked(end:-1:1); % Handles are stored in inverse order + current_handle = get_current_handle; + cm_handles = get_cm_handles; + st.vols{current_handle}.mapping = varargin{2}; + z_handle = get(findobj(cm_handles(current_handle), ... + 'label','Intensity mapping'),'Children'); + for k = 1:numel(z_handle) + c_handle = get(z_handle(k), 'Children'); + set(c_handle, 'checked', 'off'); + set(c_handle(checked), 'checked', 'on'); + end; + redraw_all; + + case 'mapping_gl', + checked = strcmp(varargin{2}, ... + {'linear', 'histeq', 'loghisteq', 'quadhisteq'}); + checked = checked(end:-1:1); % Handles are stored in inverse order + cm_handles = get_cm_handles; + for k = valid_handles(1:24), + st.vols{k}.mapping = varargin{2}; + z_handle = get(findobj(cm_handles(k), ... + 'label','Intensity mapping'),'Children'); + for l = 1:numel(z_handle) + c_handle = get(z_handle(l), 'Children'); + set(c_handle, 'checked', 'off'); + set(c_handle(checked), 'checked', 'on'); + end; + end; + redraw_all; + + case 'swap_img', + current_handle = get_current_handle; + new_info = spm_vol(spm_select(1,'image','select new image')); + fn = fieldnames(new_info); + for k=1:numel(fn) + st.vols{current_handle}.(fn{k}) = new_info.(fn{k}); + end; + spm_orthviews('context_menu','image_info',get(gcbo, 'parent')); + redraw_all; + + case 'add_blobs', + % Add blobs to the image - in split colortable + cm_handles = valid_handles(1:24); + if varargin{2} == 2, cm_handles = get_current_handle; end; + spm_figure('Clear','Interactive'); + [SPM,VOL] = spm_getSPM; + for i = 1:length(cm_handles), + addblobs(cm_handles(i),VOL.XYZ,VOL.Z,VOL.M); + c_handle = findobj(findobj(st.vols{cm_handles(i)}.ax{1}.cm,'label','Blobs'),'Label','Remove blobs'); + set(c_handle,'Visible','on'); + delete(get(c_handle,'Children')); + item7_3_1 = uimenu(c_handle,'Label','local','Callback','spm_orthviews(''context_menu'',''remove_blobs'',2);'); + if varargin{2} == 1, + item7_3_2 = uimenu(c_handle,'Label','global','Callback','spm_orthviews(''context_menu'',''remove_blobs'',1);'); + end; + end; + redraw_all; + + case 'remove_blobs', + cm_handles = valid_handles(1:24); + if varargin{2} == 2, cm_handles = get_current_handle; end; + for i = 1:length(cm_handles), + rmblobs(cm_handles(i)); + c_handle = findobj(findobj(st.vols{cm_handles(i)}.ax{1}.cm,'label','Blobs'),'Label','Remove blobs'); + delete(get(c_handle,'Children')); + set(c_handle,'Visible','off'); + end; + redraw_all; + + case 'add_image', + % Add blobs to the image - in split colortable + cm_handles = valid_handles(1:24); + if varargin{2} == 2, cm_handles = get_current_handle; end; + spm_figure('Clear','Interactive'); + fname = spm_select(1,'image','select image'); + for i = 1:length(cm_handles), + addimage(cm_handles(i),fname); + c_handle = findobj(findobj(st.vols{cm_handles(i)}.ax{1}.cm,'label','Blobs'),'Label','Remove blobs'); + set(c_handle,'Visible','on'); + delete(get(c_handle,'Children')); + item7_3_1 = uimenu(c_handle,'Label','local','Callback','spm_orthviews(''context_menu'',''remove_blobs'',2);'); + if varargin{2} == 1, + item7_3_2 = uimenu(c_handle,'Label','global','Callback','spm_orthviews(''context_menu'',''remove_blobs'',1);'); + end; + end; + redraw_all; + + case 'add_c_blobs', + % Add blobs to the image - in full colour + cm_handles = valid_handles(1:24); + if varargin{2} == 2, cm_handles = get_current_handle; end; + spm_figure('Clear','Interactive'); + [SPM,VOL] = spm_getSPM; + c = spm_input('Colour','+1','m',... + 'Red blobs|Green blobs|Yellow blobs|Cyan blobs|Blue blobs|Magenta blobs',[1 2 3 4 5 6],1); + colours = [1 0 0;0 1 0;1 1 0;0 0 1;0 1 1;1 0 1]; + c_names = {'red';'green';'yellow';'blue';'cyan';'magenta'}; + hlabel = sprintf('%s (%s)',VOL.title,c_names{c}); + for i = 1:length(cm_handles), + addcolouredblobs(cm_handles(i),VOL.XYZ,VOL.Z,VOL.M,colours(c,:)); + addcolourbar(cm_handles(i),numel(st.vols{cm_handles(i)}.blobs)); + c_handle = findobj(findobj(st.vols{cm_handles(i)}.ax{1}.cm,'label','Blobs'),'Label','Remove colored blobs'); + ch_c_handle = get(c_handle,'Children'); + set(c_handle,'Visible','on'); + %set(ch_c_handle,'Visible',on'); + item7_4_1 = uimenu(ch_c_handle(2),'Label',hlabel,'ForegroundColor',colours(c,:),... + 'Callback','c = get(gcbo,''UserData'');spm_orthviews(''context_menu'',''remove_c_blobs'',2,c);',... + 'UserData',c); + if varargin{2} == 1, + item7_4_2 = uimenu(ch_c_handle(1),'Label',hlabel,'ForegroundColor',colours(c,:),... + 'Callback','c = get(gcbo,''UserData'');spm_orthviews(''context_menu'',''remove_c_blobs'',1,c);',... + 'UserData',c); + end; + end; + redraw_all; + + case 'remove_c_blobs', + cm_handles = valid_handles(1:24); + if varargin{2} == 2, cm_handles = get_current_handle; end; + colours = [1 0 0;1 1 0;0 1 0;0 1 1;0 0 1;1 0 1]; + c_names = {'red';'yellow';'green';'cyan';'blue';'magenta'}; + for i = 1:length(cm_handles), + if isfield(st.vols{cm_handles(i)},'blobs'), + for j = 1:length(st.vols{cm_handles(i)}.blobs), + if st.vols{cm_handles(i)}.blobs{j}.colour == colours(varargin{3},:); + if isfield(st.vols{cm_handles(i)}.blobs{j},'cbar') + delete(st.vols{cm_handles(i)}.blobs{j}.cbar); + end + st.vols{cm_handles(i)}.blobs(j) = []; + break; + end; + end; + rm_c_menu = findobj(st.vols{cm_handles(i)}.ax{1}.cm,'Label','Remove colored blobs'); + delete(findobj(rm_c_menu,'Label',c_names{varargin{3}})); + if isempty(st.vols{cm_handles(i)}.blobs), + st.vols{cm_handles(i)} = rmfield(st.vols{cm_handles(i)},'blobs'); + set(rm_c_menu, 'Visible', 'off'); + end; + end; + end; + redraw_all; + + case 'add_c_image', + % Add truecolored image + cm_handles = valid_handles(1:24); + if varargin{2} == 2, cm_handles = get_current_handle;end; + spm_figure('Clear','Interactive'); + fname = spm_select(1,'image','select image'); + c = spm_input('Colour','+1','m','Red blobs|Yellow blobs|Green blobs|Cyan blobs|Blue blobs|Magenta blobs',[1 2 3 4 5 6],1); + colours = [1 0 0;1 1 0;0 1 0;0 1 1;0 0 1;1 0 1]; + c_names = {'red';'yellow';'green';'cyan';'blue';'magenta'}; + hlabel = sprintf('%s (%s)',fname,c_names{c}); + for i = 1:length(cm_handles), + addcolouredimage(cm_handles(i),fname,colours(c,:)); + addcolourbar(cm_handles(i),numel(st.vols{cm_handles(i)}.blobs)); + c_handle = findobj(findobj(st.vols{cm_handles(i)}.ax{1}.cm,'label','Blobs'),'Label','Remove colored blobs'); + ch_c_handle = get(c_handle,'Children'); + set(c_handle,'Visible','on'); + %set(ch_c_handle,'Visible',on'); + item7_4_1 = uimenu(ch_c_handle(2),'Label',hlabel,'ForegroundColor',colours(c,:),... + 'Callback','c = get(gcbo,''UserData'');spm_orthviews(''context_menu'',''remove_c_blobs'',2,c);','UserData',c); + if varargin{2} == 1 + item7_4_2 = uimenu(ch_c_handle(1),'Label',hlabel,'ForegroundColor',colours(c,:),... + 'Callback','c = get(gcbo,''UserData'');spm_orthviews(''context_menu'',''remove_c_blobs'',1,c);',... + 'UserData',c); + end + end + redraw_all; +end; +%_______________________________________________________________________ +%_______________________________________________________________________ +function current_handle = get_current_handle +global st +cm_handle = get(gca,'UIContextMenu'); +cm_handles = get_cm_handles; +current_handle = find(cm_handles==cm_handle); +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function cm_pos +global st +for i = 1:length(valid_handles(1:24)), + if isfield(st.vols{i}.ax{1},'cm') + set(findobj(st.vols{i}.ax{1}.cm,'UserData','pos_mm'),... + 'Label',sprintf('mm: %.1f %.1f %.1f',spm_orthviews('pos'))); + pos = spm_orthviews('pos',i); + set(findobj(st.vols{i}.ax{1}.cm,'UserData','pos_vx'),... + 'Label',sprintf('vx: %.1f %.1f %.1f',pos)); + set(findobj(st.vols{i}.ax{1}.cm,'UserData','v_value'),... + 'Label',sprintf('Y = %g',spm_sample_vol(st.vols{i},pos(1),pos(2),pos(3),st.hld))); + end +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function cm_handles = get_cm_handles +global st +cm_handles = []; +for i=valid_handles(1:24), + cm_handles = [cm_handles st.vols{i}.ax{1}.cm]; +end +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function zoom_all(op) +global st +cm_handles = get_cm_handles; +res = [.125 .125 .25 .5 1 1]; +if op==6, + st.bb = maxbb; +else, + vx = sqrt(sum(st.Space(1:3,1:3).^2)); + vx = vx.^(-1); + pos = spm_orthviews('pos'); + pos = st.Space\[pos ; 1]; + pos = pos(1:3)'; + if op == 5, st.bb = [pos-80*vx ; pos+80*vx] ; + elseif op == 4, st.bb = [pos-40*vx ; pos+40*vx] ; + elseif op == 3, st.bb = [pos-20*vx ; pos+20*vx] ; + elseif op == 2, st.bb = [pos-10*vx ; pos+10*vx] ; + elseif op == 1; st.bb = [pos- 5*vx ; pos+ 5*vx] ; + else disp('no Zoom possible'); + end; +end +resolution(res(op)); +redraw_all; +for i = 1:length(cm_handles) + z_handle = get(findobj(cm_handles(i),'label','Zoom'),'Children'); + set(z_handle,'Checked','off'); + set(z_handle(op),'Checked','on'); +end +return; + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% ROI: TimeSeries +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_searchContentEdit(hObject, eventdata) +handles = guidata(hObject); +set(handles.searchContentEdit,'UserData', 'manual'); + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% click load SPM file button +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_loadSPMmat(hObject, eventdata, spmfile) +handles = guidata(hObject); +if nargin < 3 | isempty(spmfile) + %if exist(fullfile(pwd,'SPM.mat'), 'file') + if ~isempty(findstr('SPM2',spm('ver'))) & exist('spm_get')==2 + spmfile = spm_get([1],'SPM.mat','Select a SPM file'); + elseif ( ~isempty(findstr('SPM8',spm('ver'))) || ~isempty(findstr('SPM5',spm('ver')))) && exist('spm_select')==2 + spmfile = spm_select(1,'SPM.mat','Select a SPM file'); + else + if exist('spm')==2 + error('Check for inconsistencies in your SPM install.\nspm(''ver'') = %s\n', spm('ver')) + else + error('No SPM found'); + end + end +end +xSPM=load(spmfile); +if isfield(xSPM.SPM, 'xCon') + + if ~exist(xSPM.SPM.swd, 'dir') + nswd = fileparts(spmfile); + warning('Directory specified in SPM.swd is missing (%s).\nWill use: %s',xSPM.SPM.swd,nswd); + xSPM.SPM.swd=nswd; + end + numc=listdlg('ListString' , {xSPM.SPM.xCon.name}, 'SelectionMode' , 'single', 'InitialValue', min(2,numel(xSPM.SPM.xCon)) );%length(xSPM.SPM.xCon)); + if ~exist(fullfile(xSPM.SPM.swd,xSPM.SPM.xCon(numc).Vspm.fname)) & ... + exist(subarray(fullfile(xSPM.SPM.swd,xSPM.SPM.xCon(numc).Vspm.fname),1:2,-2)) + warning('Changing path') + xSPM.SPM.swd=xSPM.SPM.swd(3:end); + end + if ~isempty(numc) + CallBack_loadImagePush(handles.loadImagePush, [],... + {fullfile(xSPM.SPM.swd,xSPM.SPM.xCon(numc).Vspm.fname)},... + {xSPM.SPM.xCon(numc).name}); + end +else + msgbox('No contrast in this file') +end +return + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% paramest push (Parameter estimates) +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_indivResultsListPush(hObject, eventdata) +handles = guidata(hObject); +if ischar(handles.imageFileName) + handles.imageFileName={handles.imageFileName}; +end +[datadir,condir,condext]=fileparts(handles.imageFileName{1}); +if datadir(2)==':' + datadir=datadir(3:end); +end +[datadir,subdir]=fileparts(datadir); +if isempty(findstr('indiv/', datadir)) +elseif isempty(findstr('rfx/', datadir)) + xSPM=load(fullfile(datadir,'SPM.mat')); + indivdir=xSPM.xY.P; +else + return +end +req = get(handles.indivResultsListPush,'String'); +req = req{get(handles.indivResultsListPush, 'value')}; + +if isequal(req,'Group') + datadir=fileparts(datadir); + condir=handles.imageContrastName{1}; + condir=strrep(condir, '>', 'vs'); + condir=strrep(condir, '<', 'vs'); + condir=strrep(condir, '+', '_'); + condir=strrep(condir, ':', ' at'); + condir=deblank(condir); + condir=fullfile(datadir,'rfx',condir); + if ~exist(condir,'dir') + return + end + xSPM=load(fullfile(condir,'SPM.mat')); + numc=2; + CallBack_loadImagePush(handles.loadImagePush, [],... + {fullfile(xSPM.SPM.swd,xSPM.SPM.xCon(numc).Vspm.fname)},... + {xSPM.SPM.xCon(numc).name}); + return +elseif exist('swds','var') + CallBack_loadImagePush(handles.loadImagePush, [],... + swds(get(gcbo, 'value'),:),... + handles.imageContrastName); +else + if exist(fullfile(datadir,req,'SPM.mat')) + xSPM=load(fullfile(datadir,req,'SPM.mat')); + if ~isfield(handles,'imageContrastName') + return + end + % numc=strmatch(handles.imageContrastName{1},{xSPM.SPM.xCon.name},'exact'); + numc=get(handles.contrastListPush, 'Value'); + filename=fullfile(xSPM.SPM.swd,xSPM.SPM.xCon(numc).Vspm.fname); + else + % SPM adds a comma for 4D volumes + condext = strtok(condext,','); + filename = fullfile(datadir,req,[condir condext]); + warning('Using same filename in %s: ', req, filename) + end + if filename(2)==':' + filename=filename(3:end); + end + if ~exist(filename,'file') + error('File doesn''t exist: %s',filename) + return + end + set(handles.sectionViewListbox, 'Value', [1]); + % handles.sectionViewTargetFile = + % fullfile('\ndiayek\data\gazemo\rawdata\',req,'anat','wf_0001.img'); + guidata(hObject, handles); + if exist('xSPM','var') + CallBack_loadImagePush(handles.loadImagePush, [],... + {filename},... + {xSPM.SPM.xCon(numc).name}); + else + CallBack_loadImagePush(handles.loadImagePush, [],... + {filename},... + '???'); + end + % CallBack_allIntensityRadio(hObject, eventdata, 'c'); +end +uicontrol(hObject) + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% paramest push (Parameter estimates) +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_paramestRegionPush(hObject, eventdata, roi,readdata) +if nargin<4 + readdata=1; +end +roi=struct('type', roi); +handles = guidata(hObject); +roi.name=get(handles.structureEdit, 'String'); + +[img.p, img.fname, img.ext]=fileparts(handles.imageFileName{1}); +spmfile=fullfile(img.p,'SPM.mat'); + +if exist(spmfile(3:end), 'file') + warning('Possible disk swapping in xjview.m'); + spmfile=spmfile(3:end); +end +if ~exist(spmfile) + if findstr('SPM2',spm('ver')) + spmfile = spm_get([0 1],'SPM.mat','locate the RFX SPM.mat'); + elseif findstr('SPM5',spm('ver')) + spmfile = spm_select([0:1],'SPM.mat','locate the RFX SPM.mat'); + end +end +if ~exist(spmfile) + return +end +xSPM=load(spmfile, 'SPM'); +[glmpath, glmpath]=fileparts(fileparts(fileparts(xSPM.SPM.swd))); + +RetrieveRawData = 0; +status = get(gcbf, 'SelectionType') +% right click +if strcmp(status, 'alt') + RetrieveRawData = 1; +end + +%[xyzmm,i] = spm_XYZreg('NearestXYZ',... +% spm_results_ui('GetCoords'),handles.currentxyz); +%spm_results_ui('SetCoords',xSPM.XYZmm(:,i)); +switch roi.type + case 'vox' + if not(isfield(handles,'currentxyz')) %%% + error('Wrong selection') + return + end + xyz = handles.currentxyz; + case 'clu' + if not(isfield(handles,'selectedCluster')) | not(isempty(handles.selectedCluster)) + switch questdlg('Load from workspace','cluster?', 'currentDisplayMNI{1}','...','Cancel','currentDisplayMNI{1}') + case 'currentDisplayMNI{1}' + xyz=evalin('base', 'currentDisplayMNI{1}'); + case '...' + case 'Cancel' + return + end + delete(findobj('Tag', 'paramest')) + + else + delete(findobj('Tag', 'paramest')) + xyz =handles.currentDisplayMNI{1}; + end + case 'sph' + if not(isfield(handles,'currentxyz')) %%% + error('Wrong selection') + return + end + delete(findobj('Tag', 'paramest')) + xyz = handles.currentxyz; + roi.radius=inputdlg('Radius of the sphere?'); + if isempty(roi.radius) + return + end + roi.radius=str2num(roi.radius{1}) + % XYZmm = xSPM.SPM.xVol.M(1:3,:)*[xSPM.SPM.xVol.XYZ; ones(1,size(xSPM.SPM.xVol.XYZ,2))]; + % xyz = sqrt( XYZmm-repmat(xyz,[1 size(XYZmm,2)]) ) + % xyz = XYZmm(:, xyz < roi.radius) + +end +% handles.currentDisplayMNI{1} +% ha=axes('Tag', 'paramest', 'Parent',gcf,'units','normalized','Position',[0.55, 0.05, 0.4, 0.4]); +if isequal(roi.type, 'sph') + if roi.radius > 20 + if not(spm_input({'Many many voxels may be retrieve','Radius of the sphere (in mm):',... + num2str(roi.radius),... + 'Continue anyway?'},... + 1,'bd',{'OK','NO'},[1,0])) + return + end + end + +end +if size(xyz,1)>40 + if not(spm_input({'Many many voxels to retrieve','Number of voxel:',... + num2str(size(xyz,1)),... + 'Continue anyway?'},... + 1,'bd',{'OK','NO'},[1,0])) + return + end +end + +allbetas= []; +allcons= []; +allxyz = []; +allvxyz = []; +roi.nvox = []; +hwait = waitbar(0,'Reading 1st level data'); +lcd=cd; +handles.currentxyz +if isfield(handles, 'paramest') + handles.paramest=[]; +end + +roi.stat.TF=handles.TF; +roi.stat.pValue=handles.pValue; +roi.stat.df=handles.df; +% roi.stat.Intensity = +% handles.currentDisplayIntensity{1}(find(all(handles.currentDisplayMNI{1}==repmat(handles.currentxyz, 3,1),2))); +[i,j]=ismember(handles.mni{1}, flipud(xyz), 'rows'); +roi.stat.intensity(j(i)) = handles.intensity{1}(i); + +if isstruct(xSPM.SPM.xX.K) | ~readdata + n=1; + % Ic=strmatch('F Task',{xSPM.SPM.xCon.name}) + % + % allbetas=[] + % vcon= + % XYZ = SPM.xVol.XYZ; + % XYZmm = SPM.xVol.M(1:3,:)*[XYZ; ones(1,size(XYZ,2))]; + % tmpallbetas= []; + % [nxyz,i] = spm_XYZreg('NearestXYZ',xyz(cluvox,:),XYZmm); + % allbetas = [allbetas;(mean(tmpallbetas,1))]; + % allxyz= [allxyz;mean(tmpallxyz,1)]; + +else + n= length(xSPM.SPM.xY.P) +end + +% Is that a correlation? +PlotCorrelation = (n>1) && not(isequal(all(xSPM.SPM.xX.X==1), [1])); + +% try +% roi.sub2pr = str2num(subarray(strvcat(xSPM.SPM.xY.P),55:56,2)); +% end + +for sub = 1:n + + waitbar(sub/n,hwait); + + if n>1 + subdir = fileparts(xSPM.SPM.xY.P{sub}); + else + subdir = xSPM.SPM.swd; + end + subdir0 =subdir; + if ~exist(subdir, 'dir') + subdir = regexprep(subdir, '[A-Z]?\:',''); + end + if ~exist(subdir, 'dir') + subdir = regexprep(subdir, {'^\\ndiayek', '^\\ndiaye'},'') + end + + + if ~exist(fullfile(subdir, 'SPM.mat'), 'file') & ... + numel(dir(subarray(fullfile(subdir, 'SPM.mat'),1:2,-2)))>0 + subdir = subdir(3:end); + end + if exist(fullfile(subdir, 'SPM.mat'), 'file') + load(fullfile(subdir, 'SPM.mat')); + fprintf('Retrieving betas from: %s\n', fullfile(subdir, 'SPM.mat')) + + if sub==1 + % [cn.path cn.fname cn.ext]=fileparts(char(xSPM.SPM.xY.P(sub,:))); + % consfiles=[SPM.xCon.Vcon]; + + % retrieves the f map (where all betas should be) + Ic = [... + strmatch('f anim', lower({SPM.xCon.name})) ... + strmatch('f task', lower({SPM.xCon.name})) ... + strmatch('f map', lower({SPM.xCon.name}))]; + if isempty(Ic) + Ic = listdlg('ListString',{SPM.xCon.name},'InitialValue',Ic); + end + Ic=Ic(end); + conname=SPM.xCon(Ic).name; + end + + %------- + Ic = strmatch(conname,{SPM.xCon.name}, 'exact') ; %contrast number + if isempty(Ic) + Ic = listdlg('ListString',{SPM.xCon.name}); + end + %------- + else + SPM = xSPM.SPM + %using raw data + Ic=NaN; + end + try + XYZ = SPM.xVol.XYZ; + XYZmm = SPM.xVol.M(1:3,:)*[XYZ; ones(1,size(XYZ,2))]; + tmpallbetas= []; + tmpallcons= []; + tmpallxyz = []; + cd(xSPM.SPM.swd) + cd(subdir) + for cluvox = 1:size(xyz,1) + switch (roi.type) + case 'sph' + [d] = spm_XYZreg('Edist',xyz(cluvox,:),XYZmm); + i=find(d<=roi.radius); + govox = ~isempty(i); + nxyz=XYZmm(:,i); + case {'vox', 'clu'} + govox = 1; + [nxyz,i] = spm_XYZreg('NearestXYZ',xyz(cluvox,:),XYZmm); + if sqrt(sum((nxyz'-xyz(cluvox,:)).^2))>=sqrt(3) %one voxel in each dimZ + govox= spm_input({'No data stored for this voxel','Closest voxels with data are:',... + num2str(xyz(cluvox,:)),... + 'Continue anyway?'},... + 1,'bd',{'OK','NO'},[1,0]); + end + end + if govox==1 + vXYZ = XYZ(:,i) ; % coordinates in voxels + + %-Get parameter and hyperparameter estimates + %======================================================================= + % ResMS = spm_get_data(SPM.VResMS,vXYZ); + % Bcov = ResMS*SPM.xX.Bcov; + CI = 1.96; % bilateral = spm_invNcdf(1 - (0.05/2)); + % compute contrast of parameter estimates and 95% C.I. + %---------------------------------------------------------- + %---- + xSPM.SPM.xY.VY(sub).fname = strrep(xSPM.SPM.xY.VY(sub).fname, subdir0, subdir); + tmpallcons = [tmpallcons ; spm_get_data(xSPM.SPM.xY.VY(sub), vXYZ)]; + if ~isnan(Ic) + beta = spm_get_data(SPM.Vbeta, vXYZ); + tmpallbetas = [tmpallbetas; (SPM.xCon(Ic).c'*beta)']; + else + tmpallbetas = tmpallcons; + end + + if RetrieveRawData + if sub==1 + fprintf('\tRetrieving rawdata from: %s\n', fullfile(subdir, 'SPM.mat')) + tmpallcons = [ spm_get_data(xSPM.SPM.xY.VY, vXYZ)']; + end + end + tmpallxyz = [tmpallxyz; nxyz']; + end + end + allbetas = [allbetas;(mean(tmpallbetas,1))]; + allcons = [allcons;(mean(tmpallcons,1))]; + allxyz= [allxyz;mean(tmpallxyz,1)]; + allvxyz= [allvxyz mean(vXYZ,2)]; + roi.nvox = [ roi.nvox; size(tmpallbetas,1)]; + tmp = mean(tmpallxyz); + % disp(['meancluster = ' num2str(mean(tmpallxyz,1))]); + catch + warning('Error with data from: %s', subdir) + roi.SPM =xSPM.SPM; + roi.xyz=xyz; + assignin('base','roi_tmp',roi) + end +end +close(hwait) +cd(lcd); +% +if ~isnan(Ic) + regnames=cellstr(subarray(strvcat(strrep(SPM.xX.name(any(SPM.xCon(Ic).c )),'*bf(1)', '')'), 1:6, -2)); + regnames=cellstr(subarray(strvcat(strrep(SPM.xX.name(any(SPM.xCon(Ic).c')),'*bf(1)', '')'), 1:6, -2)); + [regnames, ireg,ireg2]=unique(regnames); + a=[ repmat(' ',length(regnames),1) strvcat(strrep(regnames, '_', ' '))]; + a=dataread('string',a', '%s'); + leg=a(isort(ireg)); +else + leg={'??'}; +end +%a=reshape(a, [],108])'; +% +% n=factor(size(allbetas,2)); +% n=sort([n(end) prod(n(1:end-1))]); +% +% prompt={'Enter the matrix size for x^2:','Enter the colormap name:'}; +% def={'20','hsv'}; +% dlgTitle='Input for Peaks function'; +% lineNo=1; +% answer=inputdlg(prompt,dlgTitle,lineNo,def); +% +% AddOpts.Resize='on'; +% AddOpts.WindowStyle='normal'; +% AddOpts.Interpreter='tex'; +% answer=inputdlg(prompt,dlgTitle,lineNo,def,AddOpts); +% +assignin('base', 'allbetas', allbetas) +% ReshapeData = []; +% xbars=[]; +% fxorder=[2 3 1]; +% switch size(allbetas,2) +% case 8 +% ReshapeData = [4 2]; +% case 4 +% if all(ismember(a,{'Neutral' 'Fearful' 'Angry' 'Happy'})) +% ReshapeData = [4 1]; +% end +% case 18 +% ReshapeData = [18 1]; +% case 16 +% % ReshapeData = [2 8]; +% % xtick=[1 3 4 5 7 8 9 11]; +% % fxorder=[3 2 1]; +% ReshapeData = [16 1]; +% end +% if isempty(ReshapeData) +% ReshapeData = [fliplr(factor(size(allbetas,2))) 1]; +% ReshapeData = [ ReshapeData(1) prod(ReshapeData(2:end))] +% end +% allbetas=reshape(allbetas, [],ReshapeData(1),ReshapeData(2)); +% allbetas = permute(allbetas,fxorder); + +% allxyz=reshape(allxyz, [], 1, 3); +% allxyz=permute(allxyz, [3 2 1]); + +%assignin('base', 'allbetas', allbetas) +%assignin('base', 'allxyz', xyz) + + +roi.allbetas=allbetas; +roi.allxyz=allxyz'; +roi.allvxyz=allvxyz; +roi.allcons=allcons'; +roi.XYZmm=xyz'; +roi.xyz=mean(allxyz,1)'; +assignin('base', 'roi', roi) +%Icc = find(xSPM.SPM.xX.X*xSPM.SPM.xCon(strmatch(handles.imageContrastName{1}, {xSPM.SPM.xCon.name})).c); +% if ~isempty(handles.imageContrastName) +% fn=handles.imageContrastName{1}; +% else +[fp,fn,fe] = fileparts(handles.imageFileName{1}); +fn=[fn fe]; +cn=[xSPM.SPM.xCon.Vspm]; + +% end +Icc = strmatch(fn, {cn.fname}) ; +roi.fname = fullfile(fp,fn); +roi.cn = cn; +roi.Icc=Icc; +roi.leg=leg; +assignin('base', 'roi', roi) + + +try + plot_betas(roi,glmpath,handles) +catch + if n>1 + try + figure;h=barerrorbar(1:(prod(size(roi.allbetas))/size(roi.allbetas,1)),mean(roi.allbetas,1)',stderrw(roi.allbetas,2:ndims(roi.allbetas),1)',NaN) + catch + end + else + figure;bar(roi.allbetas) + end + xlabel('Task') + set(gca, 'XTickLabel', leg) + set(gca, 'XTickLabel', {'Self Pos' 'Self Neg' 'Other Pos' 'Other Neg' 'Word Pos' 'Word Neg'}) + xlabel('Task') + %legend({'Positive Words' 'Negative Words'},'Location', 'Best') + delete(legend) +end +title(sprintf('XYZ = %+0.1f %+0.1f %+0.1f', roi.XYZmm(1:3))); +setappdata(gca, 'ROI', roi) + +if ~isempty(Icc) + if ~isempty(xSPM.SPM.xCon(Icc).c) + roi.allregressors = xSPM.SPM.xX.X*xSPM.SPM.xCon(Icc).c; + else + roi.allregressors = []; + end + assignin('base', 'roi', roi) +end +if PlotCorrelation + %correlation plot + figure; + cla; + plot(roi.allregressors, roi.allcons, 'x') + xY=strvcat(xSPM.SPM.xY.P); + xY=cellstr(xY(:,~allthesame(xY))); + text(roi.allregressors, roi.allcons,xY,'tag','Labels'); + %hl=legend({'test'}, 0); + [s,o,r2,sce,p]=linearfit(roi.allcons,roi.allregressors); + text(median(roi.allregressors), quantile(roi.allcons,.90), {'Correlation: ' sprintf('r2 = %g\np = %g', r2,p)}) + xlabel(xSPM.SPM.xX.name(find(xSPM.SPM.xCon(Icc).c))) + ylabel(xSPM.SPM.xY.VY(1).descrip) + + [xx]=axis; + hold on + plot(xx(1:2), s*xx(1:2)+o, '--') + hold off + title(sprintf('XYZ = %+0.1f %+0.1f %+0.1f', roi.XYZmm(1:3))); +end + +if RetrieveRawData + figure; + if n==1 + subplot(2,1,1); + plot(tmpallcons); + legend('BOLD',0) + subplot(2,1,2); + % iReg=any(SPM.xCon(handles.imageContrastName).c,2); + % plot(normalize(SPM.xX.X(:,iReg), 'unity')*diff(subarray(axis, [3 4]))/5+subarray(axis, 3)) + % legend([{'BOLD'} SPM.xX.name(iReg)],1) + Ic = handles.imageContrastName; + if isempty(Ic) || ~isnumeric(Ic) + Ic = get(handles.contrastListPush, 'Value'); + end + plot( [ + spm_FcUtil('Yc',SPM.xCon(Ic),SPM.xX.xKXs,beta) ... + SPM.xX.X*SPM.xCon(Ic).c*pinv(SPM.xCon(Ic).c)*beta ... + (1-2*get(handles.negativeIntensityRadio, 'Value'))*SPM.xX.X*SPM.xCon(Ic).c ]) + legend({ 'adjusted', 'predicted' SPM.xCon(Ic).name},0) + end +end + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% paramest push (Parameter estimates) +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function [c_num, c_let, c_word]=mycolourset +c_num = [1 0 0;0 1 0;1 1 0;0 1 1;0 0 1;1 0 1]; +c_let = {'r';'g';'y';'c';'b';'m'}; +c_word= {'red';'green';'yellow';'cyan';'blue';'magenta'}; \ No newline at end of file diff --git a/xjview_markus.m b/xjview_markus.m new file mode 100644 index 0000000..5b82323 --- /dev/null +++ b/xjview_markus.m @@ -0,0 +1,8547 @@ +function xjview_karim(varargin) +% PWD=pwd; eval('cd D:\Gschwind\Data\VISIONCHIM'); +% %P = genpath(pwd);rmpath(P); +% cd(PWD) +addpath('C:\Program Files\MATLAB\R2006b\work\xjview\matlab') +% xjview, version 4 +% +% usage 1: xjview (no argument) +% for displaying a result img file, or multiple image files, +% (which will be loaded later) and changing p-value or t/f-value +% usage 2: xjview(imagefilename) +% for displaying the result img file and changing p-value or +% t-value +% Example: xjView spmT_0002.img +% xjView('spmT_0002.img') +% xjView mymask.img +% usage 3: xjview(imagefilename1, imagefilename2, ...) +% for displaying the result img files and changing p-value or +% t/f-value +% Example: xjView spmT_0002.img spmT_0005.img spmT_0007.img +% xjView('spmT_0002.img', 'spmT_0003.img', 'spmT_0006.img') +% xjView myMask1.img myMask2.img myMask3.img +% usage 4: xjview(mnicoord, intensity) +% for displaying where are the mni coordinates +% mnicoord: Nx3 matrix of mni coordinates +% intensity: (optional) Nx1 matrix, usually t values of the +% corresponding voxels +% Example: xjView([20 10 1; -10 2 5],[1;2]) +% xjView([20 10 1; -10 2 5]) +% Note: to use xjview this way, you may need to modify the value +% of M and DIM in the begining of xjview.m +% +% http://people.hnl.bcm.tmc.edu/cuixu/xjView +% +% by Xu Cui and Jian Li 2/21/2005 +% last modified: 02/18/2007 (add colorbar max control) +% last modified: 11/16/2006 (keyboard shortcut for open image and open roi file) +% last modified: 06/16/2006 (spm5 compatible) +% last modified: 05/30/2006 (left/right flip, path of mask.img and templateFile.img) +% last modified: 05/08/2006 (debug CallBack_volumePush function, change handles.intensity{1} to intensity) +% last modified: 04/03/2006 (modify tr) +% last modified: 12/28/2005 (modify SPM process) +% +% Thank Sergey Pakhomov for sharing his database (MNI Space Utility). +% Thank Yuval Cohen for the maximize figure function (maximize.m) +% + +% TODO +% Send SPM to workspace + +warnstate = warning; +warning off; + +% pre-set values +% important! you need compare the display of xjview and spm. If you find +% xjview flipped the left/right, you need to set leftrightflip = 1; +% otherwise leave it to 0. +leftrightflip = 0; +% leftrightflip = 1 ; %must be on in the CMU... +% Now with spm-flip = 1; xjview-flip=1 + +% You only need to change M and DIM when you want to use xjview under +% 'usage 4'. +M = ... + [-4 0 0 84;... + 0 4 0 -116;... + 0 0 4 -56;... + 0 0 0 1]; +DIM = [41 48 35]'; +TR = 2; % you don't need to set TR is you only use the viewing part of xjview + +% system settings +try + spmdir = spm('dir'); + spm('defaults', 'fmri'); +catch + disp('Please add spm path.'); + warning(warnstate(1).state); + return +end + +if ispc + os = 'windows'; +elseif isunix + os = 'linux'; +else + warndlg('I don''t know what kind of computer you are using. I assumed it is unix.', 'What computer are you using?'); + os = 'linux'; +end +screenResolution = get(0,'ScreenSize'); + +xjviewpath = fileparts(which('xjview')); + +% pre-set values +pValue = 0.001; +intensityThreshold = 0; +clusterSizeThreshold = 5; + + +% Appearance Settings +figurePosition = [0.100, 0.050, 0.660, 0.880]; +sectionViewPosition = [0.5,0.61,0.45,0.45]; +glassViewAxesPosition = [0.000, 0.600, 0.464, 0.400]; +if screenResolution(3) <= 1024 + figurePosition = [0.100, 0.050, 0.700, 0.900]; + sectionViewPosition = [0.5, 0.61, 0.45, 0.46]; + glassViewAxesPosition = [0.000, 0.600, 0.464, 0.400]; +end + +left = 0.01; +editBoxHeight = 0.05; +editBoxWidth = 0.200; +editBoxLeft = 0.100; + +controlPanelPosition = [left, 0.080, 0.500, 0.500]; +stretchMatrix = diag([controlPanelPosition(3),controlPanelPosition(4),controlPanelPosition(3),controlPanelPosition(4)]); +controlPanelOffset = controlPanelPosition' .* [1,1,0,0]'; +heightUnit = 0.055; + +sliderPosition = stretchMatrix*[0.000, 0*heightUnit, 1.000, editBoxHeight]' + controlPanelOffset; +pValueTextPosition = stretchMatrix*[0.000, 1*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +pValueEditPosition = stretchMatrix*[0.110, 1*heightUnit, editBoxWidth*4/3, editBoxHeight]' + controlPanelOffset; +intensityThresholdTextPosition = stretchMatrix*[0.400, 1*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +intensityThresholdEditPosition = stretchMatrix*[0.520, 1*heightUnit, editBoxWidth*3/3, editBoxHeight]' + controlPanelOffset; +dfTextPosition = stretchMatrix*[0.740, 1*heightUnit, 0.8-0.74, editBoxHeight]' + controlPanelOffset; +dfEditPosition = stretchMatrix*[0.800, 1*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +clusterSizeThresholdTextPosition = stretchMatrix*[0.000, 2*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +clusterSizeThresholdEditPosition = stretchMatrix*[0.150, 2*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +pickThisClusterPushPosition = stretchMatrix*[0.400, 2*heightUnit, editBoxWidth*1, editBoxHeight]' + controlPanelOffset; +selectThisClusterPushPosition = stretchMatrix*[0.600, 2*heightUnit, editBoxWidth*1, editBoxHeight]' + controlPanelOffset; +clearSelectedClusterPushPosition = stretchMatrix*[0.800, 2*heightUnit, editBoxWidth*1, editBoxHeight]' + controlPanelOffset; +thisClusterSizeTextPosition = stretchMatrix*[0.600, 2*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +thisClusterSizeEditPosition = stretchMatrix*[0.800, 2*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +loadImagePushPosition = stretchMatrix*[0.000, 3*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +imageFileEditPosition = stretchMatrix*[0.200, 3*heightUnit, 1-editBoxWidth, editBoxHeight]' + controlPanelOffset; +saveImagePushPosition = stretchMatrix*[0.000, 4*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +saveImageFileEditPosition = stretchMatrix*[0.200, 4*heightUnit, 1-editBoxWidth, editBoxHeight]' + controlPanelOffset; +saveResultPSPushPosition = stretchMatrix*[0.000, 5*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +resultPSFileEditPosition = stretchMatrix*[0.200, 5*heightUnit, 1-editBoxWidth, editBoxHeight]' + controlPanelOffset; +displayIntensityTextPosition = stretchMatrix*[0.000, 3*heightUnit, editBoxWidth+0.1, editBoxHeight]' + controlPanelOffset; +allIntensityRadioPosition = stretchMatrix*[0.250, 3*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +positiveIntensityRadioPosition = stretchMatrix*[0.400, 3*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +negativeIntensityRadioPosition = stretchMatrix*[0.550, 3*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +renderViewCheckPosition = stretchMatrix*[0.780, 3*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +hideControlPushPosition = stretchMatrix*[0.200, 4*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +volumePushPosition = stretchMatrix*[0.000, 4*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +commonRegionPushPosition = stretchMatrix*[0.200, 4*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +%knd +paramestPushPosition = stretchMatrix*[0.800, 4*heightUnit, editBoxWidth*.5, editBoxHeight]' + controlPanelOffset; +%-- +displayPushPosition = stretchMatrix*[0.200, 4*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +allinonePushPosition = stretchMatrix*[0.400, 4*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +searchPushPosition = stretchMatrix*[0.000, 6*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +searchContentEditPosition = stretchMatrix*[0.200, 6*heightUnit, editBoxWidth*2, editBoxHeight]' + controlPanelOffset; +searchTextPosition = stretchMatrix*[0.600, 6*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +searchEnginePopPosition = stretchMatrix*[0.600, 6*heightUnit, 1-0.6, editBoxHeight]' + controlPanelOffset; +overlayPushPosition = stretchMatrix*[0.000, 5*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +overlayEditPosition = stretchMatrix*[0.200, 5*heightUnit, 0.6-editBoxWidth, editBoxHeight]' + controlPanelOffset; +overlayPopPosition = stretchMatrix*[0.600, 5*heightUnit, 1-0.6, editBoxHeight]' + controlPanelOffset; +helpPosition = stretchMatrix*[0.800, 5*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +infoTextBoxPosition = stretchMatrix*[0.000, 8*heightUnit, 1, editBoxHeight*9]' + controlPanelOffset; +%xjViewPosition = stretchMatrix*[0.400, 13*heightUnit, editBoxWidth*2.5, editBoxHeight*3]' + controlPanelOffset; +%connameTextBoxPosition = stretchMatrix*[0.000, 8*heightUnit, 1, editBoxHeight*9]' + controlPanelOffset; + +sectionViewListboxPosition = [sectionViewPosition(1)+0.4, sectionViewPosition(2)+0.02, 0.1, 0.14]; +sectionViewMoreTargetPushPosition = [sectionViewListboxPosition(1),sectionViewListboxPosition(2)-0.02,0.10,0.02]; +xHairCheckPosition = [sectionViewListboxPosition(1),sectionViewListboxPosition(2)+0.14,0.15,0.02]; +setTRangeEditPosition = [sectionViewListboxPosition(1),sectionViewListboxPosition(2)-0.06,0.10,0.02]; +setTRangeTextPosition = [sectionViewListboxPosition(1),sectionViewListboxPosition(2)-0.04,0.10,0.02]; + +getStructurePushPosition = [glassViewAxesPosition(1), glassViewAxesPosition(2)-0.06, editBoxWidth/2, editBoxHeight/2]; +structureEditPosition = [getStructurePushPosition(1), getStructurePushPosition(2), 1, getStructurePushPosition(4)]; +framePosition = (controlPanelPosition - controlPanelOffset')*1.05 + 0.95*controlPanelOffset'; + +%knd +setContrastNameTextPosition = [glassViewAxesPosition(1),glassViewAxesPosition(2)-0.026,0.6,0.03]; +setContrastListPosition = [setContrastNameTextPosition(1)+setContrastNameTextPosition(3)+.01,setContrastNameTextPosition(2),0.05,setContrastNameTextPosition(4)]; +setContrastListPosition = [glassViewAxesPosition(1)+.05*glassViewAxesPosition(3),glassViewAxesPosition(2)-0.026,glassViewAxesPosition(3)*.95,0.03]; +setIndivResultsListPosition = [setTRangeTextPosition(1)-0.25,setTRangeEditPosition(2),0.20,setTRangeEditPosition(4)]; +%-- knd + +% draw figure and control +figureBKcolor=[176/255 252/255 188/255]; +figureBKcolor=get(0,'Defaultuicontrolbackgroundcolor'); +f = figure('unit','normalized','position',figurePosition,'Color',figureBKcolor,'defaultuicontrolBackgroundColor', figureBKcolor,... + 'Name','xjView', 'Tag', 'xjView', 'NumberTitle','off','resize','off','CloseRequestFcn', {@CallBack_quit, warnstate(1).state}, 'visible','off'); +handles = guihandles(f); + +% databases +try + X = load('TDdatabase'); + handles.DB = X.DB; + handles.wholeMaskMNIAll = X.wholeMaskMNIAll; +catch + errordlg('I can''t find TDdatabase.mat','TDdatabase not found'); +end + +handles.figure = f; +handles.frame = uicontrol(handles.figure,'style','frame',... + 'unit','normalized',... + 'position',framePosition,... + 'Visible','off'); +handles.slider = uicontrol(handles.figure,'style','slider',... + 'unit','normalized',... + 'position',sliderPosition,... + 'max',1,'min',0,... + 'sliderstep',[0.01,0.10],... + 'callback',@CallBack_slider,... + 'value',0,'Visible','on'); +handles.pValueTextPosition = uicontrol(handles.figure,'style','text',... + 'unit','normalized','position',pValueTextPosition,... + 'string','pValue=','horizontal','left'); +handles.pValueEdit = uicontrol(handles.figure,'style','edit',... + 'unit','normalized','position',pValueEditPosition,... + 'horizontal','left',... + 'String', num2str(pValue),... + 'BackgroundColor', 'w',... + 'callback',@CallBack_pValueEdit); +handles.intensityThresholdText = uicontrol(handles.figure,'style','text',... + 'unit','normalized','position',intensityThresholdTextPosition,... + 'string',' intensity=','horizontal','left'); +handles.intensityThresholdEdit = uicontrol(handles.figure,'style','edit',... + 'unit','normalized','position',intensityThresholdEditPosition,... + 'horizontal','left',... + 'BackgroundColor', 'w',... + 'String', num2str(intensityThreshold),... + 'callback',@CallBack_intensityThresholdEdit); +handles.dfText = uicontrol(handles.figure,'style','text',... + 'unit','normalized','position',dfTextPosition,... + 'string','df= ','horizontal','right'); +handles.dfEdit = uicontrol(handles.figure,'style','edit',... + 'unit','normalized','position',dfEditPosition,... + 'horizontal','left',... + 'BackgroundColor', 'w',... + 'String', '',... + 'callback',@CallBack_dfEdit); +handles.clusterSizeThresholdText = uicontrol(handles.figure,'style','text',... + 'unit','normalized','position',clusterSizeThresholdTextPosition,... + 'string','cluster size >=','horizontal','left'); +handles.clusterSizeThresholdEdit = uicontrol(handles.figure,'style','edit',... + 'unit','normalized','position',clusterSizeThresholdEditPosition,... + 'horizontal','left',... + 'BackgroundColor', 'w',... + 'String', num2str(clusterSizeThreshold),... + 'callback',@CallBack_clusterSizeThresholdEdit); +handles.thisClusterSizeText = uicontrol(handles.figure,'style','text',... + 'unit','normalized','position',thisClusterSizeTextPosition,... + 'string','size= ','horizontal','right', 'visible', 'off'); +handles.thisClusterSizeEdit = uicontrol(handles.figure,'style','edit',... + 'unit','normalized','position',thisClusterSizeEditPosition,... + 'horizontal','left',... + 'Enable', 'inactive',... + 'String', '','visible','off'); + +handles.imageFileEdit = uicontrol(handles.figure,'style','edit',... + 'unit','normalized','position',imageFileEditPosition,... + 'horizontal','left',... + 'String', '',... + 'BackgroundColor', 'w',... + 'callback',@CallBack_imageFileEdit,... + 'visible','off'); +handles.saveImageFileEdit = uicontrol(handles.figure,'style','edit',... + 'unit','normalized','position',saveImageFileEditPosition,... + 'horizontal','left',... + 'BackgroundColor', 'w',... + 'String', 'myMask.img',... + 'callback',@CallBack_saveImageFileEdit,... + 'visible','off'); +handles.saveResultPSEdit = uicontrol(handles.figure,'style','edit',... + 'unit','normalized','position',resultPSFileEditPosition,... + 'horizontal','left',... + 'BackgroundColor', 'w',... + 'String', 'myResult.ps',... + 'callback',@CallBack_saveResultPSEdit,... + 'visible','off'); +handles.loadImagePush = uicontrol(handles.figure,'style','push',... + 'unit','normalized','position',loadImagePushPosition,... + 'string','Load Image','callback',@CallBack_loadImagePush,... + 'visible','off'); +handles.saveImagePush = uicontrol(handles.figure,'style','push',... + 'unit','normalized','position',saveImagePushPosition,... + 'string','Save Image','callback',@CallBack_saveImagePush,... + 'visible','off'); +handles.saveResultPSPush = uicontrol(handles.figure,'style','push',... + 'unit','normalized','position',saveResultPSPushPosition,... + 'string','Save Result','callback',@CallBack_saveResultPSPush,... + 'visible','off'); +handles.getStructurePush = uicontrol(handles.figure,'style','push',... + 'unit','normalized','position',getStructurePushPosition,... + 'string','Get Structure','callback',@CallBack_getStructurePush,'visible','off'); +handles.structureEdit = uicontrol(handles.figure,'style','edit',... + 'unit','normalized','position',structureEditPosition,... + 'horizontal','center',... + 'enable', 'on',... + 'UserData',struct(... + 'hReg', [],... + 'M', M,... + 'D', DIM,... + 'xyz', [0 0 0] )); +handles.pickThisClusterPush = uicontrol(handles.figure,'style','push',... + 'unit','normalized','position',pickThisClusterPushPosition,... + 'string','Pick Cluster/Info','callback',@CallBack_pickThisClusterPush); +handles.selectThisClusterPush = uicontrol(handles.figure,'style','push',... + 'unit','normalized','position',selectThisClusterPushPosition,... + 'string','Select Cluster','callback',@CallBack_selectThisClusterPush); +handles.clearSelectedClusterPush = uicontrol(handles.figure,'style','push',... + 'unit','normalized','position',clearSelectedClusterPushPosition,... + 'string','Clear Selection','callback',@CallBack_clearSelectedClusterPush); + +handles.displayIntensityText = uicontrol(handles.figure,'style','text',... + 'unit','normalized','position',displayIntensityTextPosition,... + 'string','display intensity','horizontal','left'); +handles.allIntensityRadio = uicontrol(handles.figure,'style','radio',... + 'unit','normalized',... + 'string','All',... + 'position',allIntensityRadioPosition,... + 'value', 1,... + 'callback',@CallBack_allIntensityRadio); +handles.positiveIntensityRadio = uicontrol(handles.figure,'style','radio',... + 'unit','normalized',... + 'string','Only +',... + 'position',positiveIntensityRadioPosition,... + 'callback',{@CallBack_allIntensityRadio,'+'}); +handles.negativeIntensityRadio = uicontrol(handles.figure,'style','radio',... + 'unit','normalized',... + 'string','Only -',... + 'position',negativeIntensityRadioPosition,... + 'callback',{@CallBack_allIntensityRadio,'-'}); +handles.renderViewCheck = uicontrol(handles.figure,'style','checkbox',... + 'unit','normalized',... + 'string','Render View' ,... + 'horizontal', 'right',... + 'position',renderViewCheckPosition,... + 'callback', @CallBack_renderViewCheck); + +handles.sectionViewListbox = uicontrol(handles.figure,'style','listbox',... + 'unit','normalized',... + 'String', {'single T1','avg152PD','avg152T1','avg152T2','avg305T1','ch2','ch2bet','aal','brodmann'}, ... + 'value',3,... + 'position',sectionViewListboxPosition,... + 'callback',@CallBack_sectionViewListbox); + +handles.xHairCheck = uicontrol(handles.figure,'style','checkbox',... + 'unit','normalized',... + 'string','XHairs Off' ,... + 'horizontal','left',... + 'position',xHairCheckPosition,... + 'callback',@CallBack_xHairCheck); +handles.sectionViewMoreTargetPush = uicontrol(handles.figure,'style','push',... + 'unit','normalized','position',sectionViewMoreTargetPushPosition,... + 'string','other ...','callback',@CallBack_sectionViewMoreTargetPush); +handles.setTRangeEdit = uicontrol(handles.figure,'style','edit',... + 'unit','normalized','position',setTRangeEditPosition,'BackgroundColor', 'w',... + 'string','auto','callback',@CallBack_setTRangeEdit); +handles.setTRangeText = uicontrol(handles.figure,'style','text',... + 'unit','normalized','position',setTRangeTextPosition,... + 'string','colorbar max'); +handles.hideControlPush = uicontrol(handles.figure, 'style', 'push',... + 'unit','normalized',... + 'String', '<', 'position', hideControlPushPosition,... + 'visible','off'); +handles.volumePush = uicontrol(handles.figure, 'style', 'push',... + 'unit','normalized',... + 'String', 'volume', ... + 'position', volumePushPosition,... + 'callback', @CallBack_volumePush); +handles.commonRegionPush = uicontrol(handles.figure, 'style', 'push',... + 'unit','normalized',... + 'String', 'common region', ... + 'position', commonRegionPushPosition,... + 'callback', @CallBack_commonRegionPush); +handles.displayPush = uicontrol(handles.figure, 'style', 'push',... + 'unit','normalized',... + 'String', 'display', ... + 'position', displayPushPosition,... + 'callback', @CallBack_displayPush,... + 'visible','off'); +handles.allinonePush = uicontrol(handles.figure, 'style', 'push',... + 'unit','normalized',... + 'String', 'all in one', ... + 'position', allinonePushPosition,... + 'callback', @CallBack_allinonePush,... + 'visible','off'); +%knd: +handles.setContrastNameText = uicontrol(handles.figure,'style','text',... + 'unit','normalized','position',setContrastNameTextPosition, 'FontSize', 13,... + 'string',' ... ','callback',@CallBack_setContrastNameTextPosition); +handles.contrastListPush = uicontrol(handles.figure, 'style', 'popup',... + 'unit','normalized',... + 'String', [ {''}'], ... % getfield(evalin('base','SS'),'sub2pr')),[],23)')], ... + 'position', setContrastListPosition,... + 'callback', @CallBack_contrastListPush,... + 'visible','on'); + +handles.indivResultsListPush(1) = uicontrol(handles.figure, 'style', 'popup',... + 'unit','normalized',... + 'String', [ {''}'], ... % getfield(evalin('base','SS'),'sub2pr')),[],23)')], ... + 'position', setIndivResultsListPosition,... + 'callback', @CallBack_indivResultsListPush,... + 'visible','on'); + +handles.paramestPush(1) = uicontrol(handles.figure, 'style', 'push',... + 'unit','normalized',... + 'String', 'Vox Betas', ... + 'position', paramestPushPosition'.*[1 1 2 1],... + 'callback', {@CallBack_paramestRegionPush, 'vox'},... + 'buttondownfcn', {@CallBack_paramestRegionPush, 'vox', 0},... + 'visible','on'); +handles.paramestPush(2) = uicontrol(handles.figure, 'style', 'push',... + 'unit','normalized',... + 'String', 'Cluster', ... + 'position', paramestPushPosition-[paramestPushPosition(3) 0 0 0]',... + 'callback', {@CallBack_paramestRegionPush, 'clu'},... + 'buttondownfcn', {@CallBack_paramestRegionPush, 'clu', 0},... + 'visible','on'); +handles.paramestPush(3) = uicontrol(handles.figure, 'style', 'push',... + 'unit','normalized',... + 'String', 'Sphere', ... + 'position', paramestPushPosition-2*[paramestPushPosition(3) 0 0 0]',... + 'callback', {@CallBack_paramestRegionPush, 'sph'},... + 'visible','on'); +%--knd + +handles.searchPush = uicontrol(handles.figure, 'style','push',... + 'unit','normalized','position',searchPushPosition,... + 'String', 'search','callback',@CallBack_searchPush, 'ForeGroundColor',[0 0 1]); +handles.searchContentEdit = uicontrol(handles.figure, 'style','edit',... + 'unit','normalized','position',searchContentEditPosition,... + 'ForeGroundColor',[0 0 1],... + 'BackgroundColor', 'w',... + 'horizontal','left','callback', @CallBack_searchContentEdit); +handles.searchText = uicontrol(handles.figure, 'style','text',... + 'unit','normalized','position',searchTextPosition,... + 'string', ' in',... + 'horizontal','left',... + 'visible','off'); +handles.searchEnginePop = uicontrol(... + 'Units','normalized', ... + 'ListboxTop',0, ... + 'Position',searchEnginePopPosition, ... + 'String',{'Brede';'Jede';'xBrain.org';'Google Scholar';'Pubmed';'Wikipedia'}, ... + 'Style','popupmenu', ... + 'value',1); +handles.overlayPush = uicontrol(handles.figure, 'style','push',... + 'unit','normalized','position',overlayPushPosition,... + 'String', 'overlay','callback',@CallBack_overlayPush); +handles.overlayEdit = uicontrol(handles.figure, 'style','edit',... + 'unit','normalized','position',overlayEditPosition,... + 'BackgroundColor', 'w',... + 'horizontal','left',... + 'callback', @CallBack_overlayEdit); +handles.overlayPop = uicontrol(handles.figure, 'style','popupmenu',... + 'unit','normalized','position',overlayPopPosition,... + 'string', sort(fieldnames(handles.wholeMaskMNIAll)),... + 'horizontal','left',... + 'callback', @CallBack_overlayPop); + +handles.helpPush = uicontrol(handles.figure, 'style','push',... + 'unit','normalized','position',helpPosition,... + 'String', 'help','callback','web http://people.hnl.bcm.tmc.edu/cuixu/xjView','ForeGroundColor',[0 0 1],... + 'horizontal','left', ... + 'visible','off'); +handles.infoTextBox = uicontrol(handles.figure, 'style','edit',... + 'unit','normalized','position',infoTextBoxPosition,... + 'String', 'Welcome to xjView 4','ForeGroundColor','k', 'BackgroundColor', 'w',... + 'horizontal','left', ...'fontname','times',... + 'max',2, 'min',0); +try + s = urlread('http://people.hnl.bcm.tmc.edu/cuixu/xjView/toUser.txt'); + set(handles.infoTextBox, 'String', s); +end + +handles.glassViewAxes = axes('unit','normalized','position',glassViewAxesPosition,'XTick',[],'YTick',[],'visible','off'); + + +handles.testEdit = uicontrol(handles.figure,'style','edit',... + 'unit','normalized','position',[0.1 0.4 0.2 0.05],... + 'horizontal','left',... + 'callback',@test,... + 'visible','off'); + +% menu +cSHH = get(0,'ShowHiddenHandles'); +set(0,'ShowHiddenHandles','on') +hMenuFile = findobj(get(handles.figure,'Children'),'flat','Label','&File'); +if ~isempty(hMenuFile) + hMenuFileOpen = findobj(get(handles.figure,'Children'),'Label','&Open...'); + set(hMenuFileOpen, 'label', 'Open Figure...'); + hMenuFileSave = findobj(get(handles.figure,'Children'),'Label','&Save'); + set(hMenuFileSave, 'label', 'Save Figure ...'); + hMenuFileSaveAs = findobj(get(handles.figure,'Children'),'Label','Save &As...'); + set(hMenuFileSaveAs, 'label', 'Save Figure As ...'); +else + hMenuFile = uimenu(handles.figure, 'label', '&File'); +end + +set(hMenuFile,'ForegroundColor',[0 0 1]); +set(findobj(hMenuFile,'Position',1),'Separator','on'); +%knd +uimenu('Parent',hMenuFile,'Position',1,'ForegroundColor',[0 0 1],... + 'Label','Open SPM file (SPM.mat) ...',... + 'CallBack',@CallBack_loadSPMmat, 'Accelerator', 'o'); +%--knd +uimenu('Parent',hMenuFile,'Position',1,'ForegroundColor',[0 0 1],... + 'Label','Open Images (*.img) ...',... + 'CallBack',@CallBack_loadImagePush, 'Accelerator', 'o'); +uimenu('Parent',hMenuFile,'Position',2,'ForegroundColor',[0 0 1],... + 'Label','Save Current Image (*.img) ...',... + 'CallBack',{@CallBack_saveImagePush, '', 0}); +uimenu('Parent',hMenuFile,'Position',3,'ForegroundColor',[0 0 1],... + 'Label','Save Current Image as Mask (*.img) ...',... + 'CallBack',{@CallBack_saveImagePush, '', 1}); +uimenu('Parent',hMenuFile,'Position',4,'ForegroundColor',[0 0 1],... + 'Label','Save Result (*.ps/pdf) ...',... + 'CallBack',@CallBack_saveResultPSPush); + +hMenuHelp = findobj(get(handles.figure,'Children'),'flat','Label','&Help'); +if isempty(hMenuHelp) + hMenuHelp = uimenu(handles.figure, 'label', 'xjView &Help'); +end +set(hMenuHelp,'ForegroundColor',[0 0 1]); +uimenu('Parent',hMenuHelp,'Position',1,'ForegroundColor',[0 0 1],... + 'Label','xBrain.org: brain mapping database',... + 'CallBack','web http://www.xbrain.org -browser'); +uimenu('Parent',hMenuHelp,'Position',2,... + 'Label','xjview help','ForegroundColor',[0 0 1],... + 'CallBack','web http://people.hnl.bcm.tmc.edu/cuixu/xjView'); +set(findobj(hMenuHelp,'Position',3),'Separator','on'); +set(0,'ShowHiddenHandles',cSHH) + +if exist('cuixuBOLDretrieve') + hMenuAnalyze = uimenu('label','&Analyze','ForegroundColor',[0 0 1],'visible','on'); + hMenuPreprocess = uimenu(hMenuAnalyze,'label','Preprocess','ForegroundColor',[0 0 1],'callback', @CallBack_preprocess); + hMenuProcess = uimenu(hMenuAnalyze,'label','Process (GLM estimation)','ForegroundColor',[0 0 1],'callback',@CallBack_process); + hMenuSPMProcess = uimenu(hMenuAnalyze,'label','SPMProcess (GLM using SPM)','ForegroundColor',[0 0 1],'callback',@CallBack_SPMProcess); + hMenuGLMPeak = uimenu(hMenuAnalyze,'label','GLM on peak BOLD','ForegroundColor',[0 0 1],'callback',@CallBack_GLMPeak); + hMenuContrast = uimenu(hMenuAnalyze,'label','Contrast','ForegroundColor',[0 0 1],'callback',@CallBack_contrast); + hMenuFDR = uimenu(hMenuAnalyze,'label','FDR','ForegroundColor',[0 0 1],'callback',@CallBack_fdr); + hMenuROI = uimenu(hMenuAnalyze,'label','ROI: retrieve signal','ForegroundColor',[0 0 1],'callback',@CallBack_timeSeries); + hMenuROIPlot = uimenu(hMenuAnalyze,'label','ROI: plot','ForegroundColor',[0 0 1],'callback',@CallBack_plotROI, 'Accelerator', 'm'); + hMenuROIIndividualPlot = uimenu(hMenuAnalyze,'label','ROI: individual plot','ForegroundColor',[0 0 1],'callback',@CallBack_plotIndividualROI); + hMenuROIIndividualPlotWithBehavior = uimenu(hMenuAnalyze,'label','ROI: individual plot with behavior','ForegroundColor',[0 0 1],'callback',@CallBack_plotIndividualROIWithBehavior); + hMenuROICorrelationPlot = uimenu(hMenuAnalyze,'label','ROI: correlation plot','ForegroundColor',[0 0 1],'callback',@CallBack_plotCorrelationROI); + %hMenuWholeBrainCorrelation = uimenu(hMenuAnalyze,'label','Whole brain correlation','ForegroundColor',[0 0 1],'callback',@CallBack_wholeBrainCorrelation); + hMenuBehaviorAnalysis = uimenu(hMenuAnalyze,'label','Behavior analsyis','ForegroundColor',[0 0 1],'callback',@CallBack_behaviorAnalysis); + hMenuHeadMovementAnalysis = uimenu(hMenuAnalyze,'label','Head movement analsyis','ForegroundColor',[0 0 1],'callback',@CallBack_headMovementAnalysis); + hMenuModelComparison = uimenu(hMenuAnalyze,'label','Linear model comparison','ForegroundColor',[0 0 1],'callback',@CallBack_modelComparison); + + hMenuHNLOnly = uimenu('label','For H&NL Only','ForegroundColor',[0 0 1],'visible','on'); + hMenuNew2Old = uimenu(hMenuHNLOnly,'label','Format convert','ForegroundColor',[0 0 1],'callback',@CallBack_new2old); + hMenuPreprocessCluster = uimenu(hMenuHNLOnly,'label','Preprocess (on cluster)','ForegroundColor',[0 0 1],'callback',{@CallBack_preprocess, 'cluster'}); + hMenuProcessCluster = uimenu(hMenuHNLOnly,'label','Process (GLM, on cluster)','ForegroundColor',[0 0 1],'callback',{@CallBack_process, 'cluster'}); + hMenuSPMProcessCluster = uimenu(hMenuHNLOnly,'label','SPM Process (GLM using SPM, on cluster)','ForegroundColor',[0 0 1],'callback',{@CallBack_SPMProcess, 'cluster'}); + hMenuGLMPeakCluster = uimenu(hMenuHNLOnly,'label','GLM on peak BOLD (on cluster)','ForegroundColor',[0 0 1],'callback',{@CallBack_GLMPeak, 'cluster'}); +end + +figurecm = uicontextmenu; +uimenu(figurecm,'label','Red','callback','set(gcf,''color'',''r'')'); +uimenu(figurecm,'label','White','callback','set(gcf,''color'',''w'')'); +uimenu(figurecm,'label','Gray','callback','set(gcf,''color'',[0.925, 0.914, 0.847])'); +%set(handles.figure,'uicontextmenu',figurecm); +set(handles.figure,'WindowButtonDownFcn',@figureMouseUpFcn); + +set(handles.figure,'visible','on'); + +% save pre-set values +handles.system = os; +handles.spmdir = spmdir; +handles.screenResolution = screenResolution; +handles.pValue = pValue; +handles.intensityThreshold = intensityThreshold; +handles.clusterSizeThreshold = clusterSizeThreshold; +handles.sectionViewPosition = sectionViewPosition; +handles.sectionViewTargetFile = getSectionViewTargetFile(spmdir, 'avg152T1'); +%knd +if ~isempty(findstr('gazemo', pwd)) + handles.sectionViewTargetFile = fullfile(fileparts(mfilename('fullpath')), 'group','anat', 'mean-no120.img'); +end +guidata(f, handles); + +% global variables for rotation matrix M and dimension +global M_; +global DIM_; +global TR_; +global LEFTRIGHTFLIP_; +global TMAX_; % colorbar max to display in section view +M_ = M; +DIM_ = DIM; +TR_ = TR; +LEFTRIGHTFLIP_ = leftrightflip; +TMAX_ = 'auto'; + +% check input arguments +if length(varargin) == 0 + %CallBack_loadSPMmat(handles.loadImagePush) + + if exist(fullfile(pwd,'SPM.mat'), 'file') + xSPM=load(fullfile(pwd,'SPM.mat')); + numc=listdlg('ListString' , {xSPM.SPM.xCon.name}, 'SelectionMode' , 'single', 'InitialValue', length(xSPM.SPM.xCon)); + if ~isempty(numc) + %MG:02.04.2008 CallBack_loadImagePush(handles.loadImagePush, [], {fullfile(xSPM.SPM.swd,xSPM.SPM.xCon(numc).Vspm.fname)}); + CallBack_loadImagePush(handles.loadImagePush, [], fullfile(xSPM.SPM.swd,xSPM.SPM.xCon(numc).Vspm.fname)); + end + end + +elseif isstr(varargin{1}) + if isempty(findstr('.mat', varargin{1})) + CallBack_loadImagePush(handles.loadImagePush, [], varargin); + else + CallBack_loadSPMmat(handles.loadImagePush, [], varargin{1}); + end +else + mniCoord = varargin{1}; + if length(varargin) < 2 + intensity = ones(size(mniCoord,1),1); + else + intensity = varargin{2}; + end + thisStruct.mni = mniCoord; + thisStruct.intensity = intensity; + thisStruct.M = M; + thisStruct.DIM = DIM'; + CallBack_loadImagePush(handles.loadImagePush, [], thisStruct); +end + + + +function test(hObject, eventdata) +handles = guidata(gcbo); +set(hObject, 'String', num2str(handles.pValue)); +vars = evalin('base','who'); +vars +x = evalin('base',vars{1}); +x +handles.DIM + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% FDR +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_fdr(hObject, eventdata) +handles = guidata(hObject); +set(handles.infoTextBox, 'string', {'Right now FDR only works for T-test image.'}); +if length(handles.imageFileName) > 1 + set(handles.infoTextBox, 'string', {'I can only work for a single image file. You opened multiple files.'}); + return +end + +q = get(handles.pValueEdit,'String'); +q = str2num(q); +if get(handles.allIntensityRadio, 'Value') + positive = 1; +elseif get(handles.positiveIntensityRadio, 'Value') + positive = 1; +elseif get(handles.negativeIntensityRadio, 'Value') + positive = -1; +end +xjviewpath = fileparts(which('xjview')); +maskImageFile = fullfile(xjviewpath, 'mask.img'); +[threshold, pvalue] = fdr(handles.imageFileName{1}, q, positive, maskImageFile); +set(handles.pValueEdit,'string',pvalue); +CallBack_pValueEdit(handles.pValueEdit, eventdata); + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% model comparison +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_modelComparison(hObject, eventdata) +answer = getVariable({'y','x (full model)', 'x (reduced model)'}); + +if isempty(answer) + return; +end + +if ~isempty(answer{1}) + y = evalin('base',answer{1}); +else + return; +end +if ~isempty(answer{2}) + xf = evalin('base',answer{2}); % full model +else + return; +end +if ~isempty(answer{3}) + xr = evalin('base',answer{3}); % reduced model +else + xr = []; +end + +% make vector column vector +[r, c] = size(y); +if r==1; y = y'; end +[r, c] = size(xf); +if r(0.5+ii/10); +end + +K = {zeros(1, size(C,1)),zeros(1, size(C,1)),zeros(1, size(C,1)),zeros(1, size(C,1))}; +for ii=1:size(C,1) + for jj=1:4 + K{jj}(ii) = sum(CC{jj}(ii,:)) - 1; + end +end + +x=[0:size(C,1)]; +for jj=1:4 + y{jj}=zeros(size(x)); +end + +for ii=1:length(x) + for jj=1:4 + y{jj}(ii) = sum(K{jj} == x(ii)); + end +end + +figure; +loglog(x,y{1}, x, y{2}, x, y{3}, x, y{4}); +xlabel('degree'); +ylabel('counts'); +legend('0.6', '0.7', '0.8', '0.9'); +axis equal + + +z = sum(abs(C))-1; +zx = 0:max(z); +for ii=1:length(zx)-1 + pos = find(z>zx(ii) & z<=zx(ii+1)); + zy(ii) = length(pos); +end +figure; +loglog(zx(1:length(zy)),zy); +xlabel('weighted degree'); +ylabel('counts'); +axis equal + +xjview(cor2mni(coord, handles.M{1}), z); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% get/select variable names in base workspace +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function vars = getVariable(titles) +vars = evalin('base','who'); +height = 0.07; +f = dialog('unit','normalized', 'menubar','none', 'position', [0.3 0.2 0.4 0.4], 'name', 'pick variables', 'NumberTitle','off'); +uicontrol(f,'style','text',... + 'unit','normalized',... + 'String', 'Available variables', ... + 'position',[0 0.9 0.5 0.1/2]); +variableListbox = uicontrol(f,'style','listbox','tag','variableListbox',... + 'unit','normalized',... + 'String', vars, ... + 'value',1,... + 'position',[0 0 0.5 0.9]); +okPush = uicontrol(f,'style','push',... + 'unit','normalized',... + 'String', 'OK', ... + 'position',[0.55 0.05 0.2 height],... + 'callback','uiresume'); +cancelPush = uicontrol(f,'style','push',... + 'unit','normalized',... + 'String', 'Cancel', ... + 'position',[0.75 0.05 0.2 height],... + 'callback','delete(gcf)'); + +uicontrol(f,'style','text',... + 'unit','normalized',... + 'String', titles{1}, ... + 'position',[0.5 0.9 0.5 0.1/2]); +imageDataPush = uicontrol(f,'style','push',... + 'unit','normalized',... + 'String', '->', ... + 'position',[0.5 0.8 0.1 height],... + 'callback', [... + 'xyouneverfindme=findobj(get(gcf,''Children''),''flat'',''tag'',''imageDataEdit'');'... + 'yyouneverfindme=findobj(get(gcf,''Children''),''flat'',''tag'',''variableListbox'');'... + 'allyouneverfindme=get(yyouneverfindme,''string'');'... + 'selectedyouneverfindme=allyouneverfindme{get(yyouneverfindme,''value'')};'... + 'set(xyouneverfindme,''string'',selectedyouneverfindme);'... + 'clear allyouneverfindme selectedyouneverfindme xyouneverfindme yyouneverfindme;']); +imageDataEdit = uicontrol(f,'style','edit','tag','imageDataEdit',... + 'unit','normalized',... + 'String', '', ... + 'BackgroundColor', 'w',... + 'position',[0.6 0.8 0.3 height]); + +if length(titles)>=2 + uicontrol(f,'style','text',... + 'unit','normalized',... + 'String', titles{2}, ... + 'position',[0.5 0.7 0.5 0.1/2]); + eventDataPush = uicontrol(f,'style','push',... + 'unit','normalized',... + 'String', '->', ... + 'position',[0.5 0.6 0.1 height],... + 'callback', [... + 'xyouneverfindme=findobj(get(gcf,''Children''),''flat'',''tag'',''eventDataEdit'');'... + 'yyouneverfindme=findobj(get(gcf,''Children''),''flat'',''tag'',''variableListbox'');'... + 'allyouneverfindme=get(yyouneverfindme,''string'');'... + 'selectedyouneverfindme=allyouneverfindme{get(yyouneverfindme,''value'')};'... + 'set(xyouneverfindme,''string'',selectedyouneverfindme);'... + 'clear allyouneverfindme selectedyouneverfindme xyouneverfindme yyouneverfindme;']); + + eventDataEdit = uicontrol(f,'style','edit','tag','eventDataEdit',... + 'unit','normalized',... + 'String', '', ... + 'BackgroundColor', 'w',... + 'position',[0.6 0.6 0.3 height]); +end +if length(titles)>=3 + uicontrol(f,'style','text',... + 'unit','normalized',... + 'String', titles{3}, ... + 'position',[0.5 0.5 0.5 0.1/2]); + correlatorDataPush = uicontrol(f,'style','push',... + 'unit','normalized',... + 'String', '->', ... + 'position',[0.5 0.4 0.1 height],... + 'callback', [... + 'xyouneverfindme=findobj(get(gcf,''Children''),''flat'',''tag'',''correlatorDataEdit'');'... + 'yyouneverfindme=findobj(get(gcf,''Children''),''flat'',''tag'',''variableListbox'');'... + 'allyouneverfindme=get(yyouneverfindme,''string'');'... + 'selectedyouneverfindme=allyouneverfindme{get(yyouneverfindme,''value'')};'... + 'set(xyouneverfindme,''string'',selectedyouneverfindme);'... + 'clear allyouneverfindme selectedyouneverfindme xyouneverfindme yyouneverfindme;']); + + correlatorDataEdit = uicontrol(f,'style','edit','tag','correlatorDataEdit',... + 'unit','normalized',... + 'String', '', ... + 'BackgroundColor', 'w',... + 'position',[0.6 0.4 0.3 height]); +end +if length(titles)>=4 + uicontrol(f,'style','text',... + 'unit','normalized',... + 'String', titles{4}, ... + 'position',[0.5 0.3 0.5 0.1/2]); + otherDataPush = uicontrol(f,'style','push',... + 'unit','normalized',... + 'String', '->', ... + 'position',[0.5 0.2 0.1 height],... + 'callback', [... + 'xyouneverfindme=findobj(get(gcf,''Children''),''flat'',''tag'',''otherDataEdit'');'... + 'yyouneverfindme=findobj(get(gcf,''Children''),''flat'',''tag'',''variableListbox'');'... + 'allyouneverfindme=get(yyouneverfindme,''string'');'... + 'selectedyouneverfindme=allyouneverfindme{get(yyouneverfindme,''value'')};'... + 'set(xyouneverfindme,''string'',selectedyouneverfindme);'... + 'clear allyouneverfindme selectedyouneverfindme xyouneverfindme yyouneverfindme;']); + + otherDataEdit = uicontrol(f,'style','edit','tag','otherDataEdit',... + 'unit','normalized',... + 'String', '', ... + 'BackgroundColor', 'w',... + 'position',[0.6 0.2 0.3 height]); +end + +uiwait(f); +try + var{1} = get(imageDataEdit,'string'); + if length(titles)>=2 + var{2} = get(eventDataEdit,'string'); + end + if length(titles)>=3 + var{3} = get(correlatorDataEdit,'string'); + end + if length(titles)>=4 + var{4} = get(otherDataEdit,'string'); + end + vars = var; + delete(f); +catch + vars = {}; + try + delete(f); + end +end + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% quit xjview +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_quit(hObject, eventdata, warnstate) +warning(warnstate) +% try +% rmdir('xjviewtmp'); +% end +delete(gcf); + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% mouse double click +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function figureMouseUpFcn(hObject, eventdata) +status = get(hObject, 'SelectionType'); + +% double click +if strcmp(status, 'extend') + handles = guidata(hObject); + CallBack_loadImagePush(handles.loadImagePush, eventdata); +elseif strcmp(status, 'open') + handles = guidata(hObject); + CallBack_loadSPMmat(handles.loadImagePush, eventdata); +else + return +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% change edit image file name +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_imageFileEdit(hObject, eventdata) +handles = guidata(hObject); +filename = get(hObject, 'String'); +filename = str2cell(filename); +CallBack_loadImagePush(handles.loadImagePush, eventdata, filename); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% click load image file button +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_contrastListPush(hObject, eventdata) +handles = guidata(hObject); +connum = get(handles.contrastListPush,'Value'); +[img.p, img.fname, img.ext]=fileparts(handles.imageFileName{1}); +spmfile=fullfile(img.p,'SPM.mat'); +if ~exist(spmfile) + set(handles.contrastListPush,'Value',0); +else + cons=getfield(getfield(load(spmfile, 'SPM'), 'SPM'), 'xCon'); + connames=get(handles.contrastListPush,'String'); + CallBack_loadImagePush(hObject,eventdata,fullfile(img.p, cons(connum).Vspm.fname), connum) +end + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% click load image file button +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_loadImagePush(hObject, eventdata, thisfilename, ContrastName) +handles = guidata(hObject); +handles.imageFileName=[]; handles.M=[]; handles.DIM=[]; handles.TF=[]; handles.df=[]; +handles.mni=[]; handles.intensity=[]; handles.currentmni=[]; handles.currentintensity=[]; handles.currentDisplayMNI=[]; handles.currentDisplayIntensity=[]; +if nargin>3 + handles.imageContrastName=ContrastName; +else + handles.imageContrastName=[]; +end +if ~exist('thisfilename') + thisfilename = ''; +end + +if isstruct(thisfilename) + handles.imageFileName = {''}; + handles.mni = {thisfilename.mni}; + handles.intensity = {thisfilename.intensity}; + handles.M = {thisfilename.M}; + handles.DIM = {thisfilename.DIM}; + handles.TF = {'S'}; + handles.df = {1e6}; + handles.pValue = 1; + set(handles.pValueEdit, 'string', '1'); + handles.clusterSizeThreshold = 0; + set(handles.clusterSizeThresholdEdit, 'String', '0'); + handles.imageContrastName={''}; +else + [handles.imageFileName, handles.M, handles.DIM, handles.TF, handles.df, handles.mni, handles.intensity] = getImageFile(thisfilename); +end + +if isempty(handles.imageFileName) + return +end + +% tries to find first-level matching data +% knd +set(handles.indivResultsListPush(1),'String', ... + getfield2(dir(fileparts(fileparts(handles.imageFileName{1}))), 'name')); + +different = 0; % image files are same or different? +if length(handles.TF)>1 + for ii=1:length(handles.TF) + if strcmp(handles.TF{ii},handles.TF{1}) & isequal(handles.df{ii},handles.df{1}) & isequal(handles.M{ii},handles.M{1}) & isequal(handles.DIM{ii},handles.DIM{1}) + continue; + else + warndlg('Images are from different statistics or sources.', 'Warning'); + %set(handles.infoTextBox, 'string', 'Images are from different statistics or sources.'); + beep; + different = 1; + break; + end + end +end + +% reset files with empty df/TF to df=1 and TF='S'. 'S'=='T' but has a tag +% meaning it is changed. +resetTF = 0; +for ii=1:length(handles.TF) + if isempty(handles.TF{ii}) + handles.TF{ii} = 'S'; + resetTF = 1; + end + if isempty(handles.df{ii}) | isequal(handles.df{ii},0) + handles.df{ii} = 1e6; + resetTF = 1; + end +end + +handles.currentmni = handles.mni; +handles.currentintensity = handles.intensity; + +set(handles.dfEdit, 'String', cell2str(handles.df)); +set(handles.imageFileEdit, 'String', cell2str(handles.imageFileName)); % s=-log10(p) +maxs = maxcell(t2s(cellmax(handles.intensity,'abs'),handles.df, handles.TF),'abs'); +if isinf(maxs); maxs = 20; end +set(handles.slider, 'Max', maxs, 'Min', 0, 'sliderstep',[min(maxs/100,0.05),min(maxs/100,0.05)]); +if handles.TF{1}=='T' & different == 0 + str = [blanks(length(' intensit')) 'T=']; +elseif handles.TF{1}=='F' & different == 0 + str = [blanks(length(' intensit')) 'F=']; +else + str=' intensity='; +end +set(handles.intensityThresholdText, 'String', str); +set(handles.figure,'Name',['xjView: ' cell2str(handles.imageFileName)]); +try + for ii=1:length(handles.hLegend) + delete(handles.hLegend{ii}); + end +end +nimg=length(handles.TF); +if nimg>1 + colours = {'r';'g';'y';'c';'b';'m'}; + %colours = colours(mod(0:nimg-1,length(colours))+1); + %colours = repmat([1 0 0;1 1 0;0 1 0;0 1 1;0 0 1;1 0 1],100,1); + % read_conname = spm_input({'Retrieve contrasts names','Should I read SPM files to retrieve filenames?'},... + % 1,'bd',{'YES','NO'},[1,0]); + read_conname = 1; + for ii=1:nimg + [tmp,filename] = fileparts(handles.imageFileName{ii}); + c_names{ii} = filename ; + if read_conname & exist(fullfile(tmp, 'SPM.mat')) + tmp=load(fullfile(tmp, 'SPM.mat')); + tmp.Vspm=[tmp.SPM.xCon.Vspm]; + tmp.iCon=strmatch(filename, {tmp.Vspm.fname}); + if ~isempty(tmp.iCon) + c_names{ii} = [ [tmp.SPM.xCon(tmp.iCon).name] ' [' c_names{ii} ']']; + end + end + end + [tmp] = inputdlg({'colors' 'names'},'Loading',6,[{strvcat(c_names)};{strvcat(colours)}]); + colours = colorname2rgb(tmp{2}); + c_names = cellstr(tmp{1}); + + for ii=1:min(length(handles.TF),10) + filename = c_names{ii}; + if ii == 10; filename = '......'; end + pos0 = handles.sectionViewPosition; + pos(1) = pos0(1)+pos0(3)/2+0.03; + pos(2) = pos0(2)+ii/50-0.03; + pos(3) = 0.12; + pos(4) = 0.02; + handles.hLegend{ii}=uicontrol(handles.figure, 'style','text',... + 'unit','normalized','position',pos,... + 'string', filename,... + 'horizontal','left',... + 'fontweight','bold',... + 'ForeGroundColor',colours(ii,:)); + end + c_names = {'red';'yellow';'green';'cyan';'blue';'magenta'}; +end +guidata(hObject, handles); + +global M_; +global DIM_; +M_ = handles.M{1}; +DIM_ = handles.DIM{1}; + +if resetTF==1 + set(handles.pValueEdit,'string',1); +end +CallBack_pValueEdit(handles.pValueEdit, eventdata); + +% display info +try + s = urlread('http://people.hnl.bcm.tmc.edu/cuixu/xjView/toUser.txt'); + report{1} = s; +catch + report{1} = 'Welcome to xjView 4'; +end +for jj=1:length(handles.imageFileName) + report{2+6*(jj-1)} = cell2str(handles.imageFileName(jj)); + if handles.TF{jj} == 'T' | handles.TF{jj} == 'F' + report{3+6*(jj-1)} = ['This is a ' handles.TF{jj} ' test image.']; + else + report{3+6*(jj-1)} = '';%['I don''t know what test this image came from.']; + end + report{4+6*(jj-1)} = 'mat = '; + report{5+6*(jj-1)} = num2str(handles.M{jj}); + report{6+6*(jj-1)} = 'dimension = '; + report{7+6*(jj-1)} = num2str(handles.DIM{jj}); +end +if length(handles.imageFileName)==1 + [img.p, img.fname, img.ext]=fileparts(handles.imageFileName{1}); + spmfile=fullfile(img.p,'SPM.mat'); + if ~exist(spmfile) + if findstr('SPM2',spm('ver')) + spmfile = spm_get([0 1],'SPM.mat','locate the corresponding SPM.mat'); + elseif findstr('SPM5',spm('ver')) + spmfile = spm_select([0:1],'SPM.mat','locate the corresponding SPM.mat'); + end + end + if exist(spmfile) + cons=getfield(getfield(load(spmfile, 'SPM'), 'SPM'), 'xCon'); + for i=1:length(cons) + if not(isempty(cons(i).Vspm)) + consfilename{i}=cons(i).Vspm.fname; + else + consfilename{i}=''; + end + end + connum=strmatch([img.fname img.ext],consfilename); + if ~isempty(connum) + conlabel=cons(connum).name; + set(handles.figure,'Name',[get(handles.figure,'Name') ' :: ' conlabel]); + set(handles.contrastListPush,'String',{cons.name}) + set(handles.contrastListPush,'Value',connum) + set(handles.setContrastNameText,'String',[conlabel]); + report=[report(1:3) {['Contrast name in SPM.mat: (' num2str(connum) ') ' conlabel]} report(4:end)]; + else + set(handles.contrastListPush,'String',{'[none]'}) + set(handles.contrastListPush,'Value',0) + set(handles.contrastListPush,'Enable','on') + report=[report(1:3) {['Contrast has no matching name in SPM.mat']} report(4:end)]; + end + end +end +set(handles.infoTextBox, 'string', report); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% pValueEdit +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_pValueEdit(hObject, eventdata) +handles = guidata(hObject); +tmp = str2double(get(hObject, 'String')); +tmps = -log10(tmp); +if isnan(tmp) | tmp < 0 | tmp > 1 + errordlg('I don''t understand the input.','error'); + set(hObject, 'String', handles.pValue); + return +end + +if tmps>get(handles.slider,'max') | tmps maxcell(cellmax(handles.intensity,'abs')) + tmp = maxcell(cellmax(handles.intensity,'abs')); +end + +handles.pValue = t2p(tmp, handles.df{1}, handles.TF{1}); +handles.intensityThreshold = p2t(num2cell(handles.pValue*ones(1,length(handles.TF))), handles.df, handles.TF); +set(handles.slider,'Value', -log10(handles.pValue)); +set(handles.intensityThresholdEdit, 'String', cell2str(handles.intensityThreshold)); +guidata(hObject, handles); +CallBack_slider(hObject, eventdata, -log10(handles.pValue)); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% slider bar +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_slider(hObject, eventdata, value) + +handles = guidata(hObject); + +if exist('value') + s = value; + if s > get(handles.slider,'max') + s = get(handles.slider,'max')*0.99; + end + if s < get(handles.slider,'min') + s = get(handles.slider,'min'); + end +else + s = get(hObject,'Value'); +end + +set(handles.slider, 'value', s); +pvalue = 10^(-s); +set(handles.pValueEdit,'String',num2str(pvalue)); +t = p2t(num2cell(pvalue*ones(1,length(handles.TF))), handles.df, handles.TF); +handles.intensityThreshold = t; +set(handles.intensityThresholdEdit, 'String', cell2str(handles.intensityThreshold)); +handles.pValue = pvalue; +for ii=1:length(handles.TF) + pos{ii} = find(abs(handles.intensity{ii})>=t{ii}); + handles.currentintensity{ii} = handles.intensity{ii}(pos{ii}); + handles.currentmni{ii} = handles.mni{ii}(pos{ii},:); +end + +guidata(hObject,handles); + +if get(handles.allIntensityRadio, 'Value') + CallBack_allIntensityRadio(handles.allIntensityRadio, eventdata); +elseif get(handles.positiveIntensityRadio, 'Value') + CallBack_allIntensityRadio(handles.positiveIntensityRadio, eventdata, '+'); +elseif get(handles.negativeIntensityRadio, 'Value') + CallBack_allIntensityRadio(handles.negativeIntensityRadio, eventdata, '-'); +end + +set(handles.infoTextBox, 'string', {'Don''t drag the slider bar too fast. Release your mouse button at least 1 second later.', ... + 'This sounds stupid. But there is a bug (probably MatLab bug) which I can''t fix right now.', ... + 'I suggest you confirm the correctness of the current display by press Enter in the pValue edit box.'}); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% display intensity all+- radios +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_allIntensityRadio(hObject, eventdata, pnall) +% pnall = '+', '-', or 'c'. c means current (simply update drawing) +% +handles = guidata(hObject); + +currentselect = []; +if get(handles.allIntensityRadio, 'Value'); currentselect = handles.allIntensityRadio; thispnall = 'a'; end +if get(handles.positiveIntensityRadio, 'Value'); currentselect = handles.positiveIntensityRadio; thispnall = '+'; end +if get(handles.negativeIntensityRadio, 'Value'); currentselect = handles.negativeIntensityRadio; thispnall = '-'; end + +set(handles.allIntensityRadio, 'Value', 0); +set(handles.positiveIntensityRadio, 'Value', 0); +set(handles.negativeIntensityRadio, 'Value', 0); + +if exist('pnall') + if pnall=='c' + hObject = currentselect; + pnall = thispnall; + end +end + +set(hObject, 'Value', 1); + +if ~isfield(handles,'currentintensity') + return +end +for ii=1:length(handles.TF) + if exist('pnall') + if pnall == '-' + pos{ii} = find(handles.currentintensity{ii} < 0); + elseif pnall == '+' + pos{ii} = find(handles.currentintensity{ii} > 0); + elseif pnall == 'a' + pos{ii} = 1:length(handles.currentintensity{ii}); + end + else + pos{ii} = 1:length(handles.currentintensity{ii}); + end + intensity{ii} = handles.currentintensity{ii}(pos{ii}); + mni{ii} = handles.currentmni{ii}(pos{ii},:); + cor{ii} = mni2cor(mni{ii}, handles.M{ii}); + + if ~isempty(cor{ii}) + A = spm_clusters(cor{ii}'); + pos0 = []; + for kk = 1:max(A) + jj = find(A == kk); + if length(jj) >= handles.clusterSizeThreshold; pos0 = [pos0 jj]; end + end + handles.currentDisplayMNI{ii} = mni{ii}(pos0,:); + handles.currentDisplayIntensity{ii} = intensity{ii}(pos0); + else + handles.currentDisplayMNI{ii} = mni{ii}([],:); + handles.currentDisplayIntensity{ii} = intensity{ii}([]); + end +end +[handles.hReg, handles.hSection, handles.hcolorbar] = Draw(handles.currentDisplayMNI, handles.currentDisplayIntensity, hObject, handles); +set(handles.contrastListPush,'String',regexprep(get(handles.contrastListPush,'String'),' \[Only [\+-a]\]$','')) +set(handles.setContrastNameText,'String',regexprep(get(handles.setContrastNameText,'String'),' \[Only [\+-a]\]$','')) + +if exist('pnall') + if ~isequal(pnall, 'a') + set(handles.setContrastNameText,'String',[get(handles.setContrastNameText,'String') ' [Only ' pnall ']']); + s=get(handles.contrastListPush,'String'); + s{get(handles.contrastListPush,'Value')}=[s{get(handles.contrastListPush,'Value')} ' [Only ' pnall ']']; + set(handles.contrastListPush,'String',s); + end +end + +if get(handles.allIntensityRadio, 'Value') & max(handles.currentDisplayIntensity{1}) < 0 + warndlg('No supra-threshold positive intensity. Only negative intensity is displayed.'); +end + +try + set(handles.figure,'currentaxes', handles.glassViewAxes); + xrange = xlim; + yrange = ylim; + try + delete(handles.hGlassText) + end + if ~isempty(handles.selectedCluster,1) + %handles.hGlassText = text(xrange(1)+diff(xrange)*0.6, yrange(1)+diff(yrange)*0.9, [num2str(size(handles.selectedCluster,1)) ' clusters selected']); + set(handles.infoTextBox, 'string', [num2str(size(handles.selectedCluster,1)) ' clusters selected']); + end +end +guidata(hObject, handles); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% render view check? +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_renderViewCheck(hObject, eventdata) +check = get(hObject, 'Value'); +if check + CallBack_allIntensityRadio(hObject, eventdata, 'c'); +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% which section view target file? list box +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_sectionViewListbox(hObject, eventdata) +handles = guidata(hObject); +contents = get(handles.sectionViewListbox,'String'); +currentsel = contents{get(handles.sectionViewListbox,'Value')}; +handles.sectionViewTargetFile = getSectionViewTargetFile(handles.spmdir, currentsel); +guidata(hObject, handles); +CallBack_allIntensityRadio(hObject, eventdata, 'c'); + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% get sectionviewtargetfile +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function sectionViewTargetFile = getSectionViewTargetFile(spmdir, selectedcontent) +if findstr('SPM2',spm('ver')) + fileext = 'mnc'; +elseif findstr('SPM5',spm('ver')) + fileext = 'nii'; +end +currentsel = selectedcontent; +if ~isempty(strfind(currentsel, 'single')) + sectionViewTargetFile = fullfile(spmdir, 'canonical', ['single_subj_T1.' fileext]); +elseif ~isempty(strfind(currentsel, '152PD')) + sectionViewTargetFile = fullfile(spmdir, 'canonical', ['avg152PD.' fileext]); +elseif ~isempty(strfind(currentsel, '152T1')) + sectionViewTargetFile = fullfile(spmdir, 'canonical', ['avg152T1.' fileext]); +elseif ~isempty(strfind(currentsel, '152T2')) + sectionViewTargetFile = fullfile(spmdir, 'canonical', ['avg152T2.' fileext]); +elseif ~isempty(strfind(currentsel, '305T1')) + sectionViewTargetFile = fullfile(spmdir, 'canonical', ['avg305T1.' fileext]); +elseif strcmp(currentsel, 'ch2') + sectionViewTargetFile = fullfile(spmdir, 'canonical', 'ch2.img'); +elseif strcmp(currentsel, 'ch2bet') + sectionViewTargetFile = fullfile(spmdir, 'canonical', 'ch2bet.img'); +elseif strcmp(currentsel, 'aal') + sectionViewTargetFile = fullfile(spmdir, 'canonical', 'aal.img'); +elseif strcmp(currentsel, 'brodmann') + sectionViewTargetFile = fullfile(spmdir, 'canonical', 'brodmann.img'); + %knd: +elseif ~isempty(strfind(currentsel, 'gazemo')) + sectionViewTargetFile = fullfile(fileparts(mfilename('fullpath')), 'group','anat', 'mean-no120.img'); + %sectionViewTargetFile = fullfile('D:\ndiayek\data\gazemo\group\anat', 'mean-no120.img'); +end +return + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% xhairs in section view? +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_xHairCheck(hObject, eventdata) +check = get(hObject, 'Value'); +if check + spm_orthviews('Xhairs','off'); +else + spm_orthviews('Xhairs','on'); +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% other target file for section view, push button +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_sectionViewMoreTargetPush(hObject, eventdata) +handles = guidata(hObject); +[filename, pathname, filterindex] = uigetfile('*', 'Pick an target file'); +if isequal(filename,0) | isequal(pathname,0) + return; +end +handles.sectionViewTargetFile = fullfile(pathname, filename); +guidata(hObject, handles); +CallBack_allIntensityRadio(hObject, eventdata, 'c'); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% set colorbar range +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_setTRangeEdit(hObject, eventdata) +global TMAX_; +handles = guidata(hObject); +TMAX_ = get(hObject, 'String'); +if isempty(str2num(TMAX_)) && ~strcmp(TMAX_, 'auto') + return; +end +guidata(hObject, handles); +CallBack_allIntensityRadio(hObject, eventdata, 'c'); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% change degree of freedome +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_dfEdit(hObject, eventdata) +handles = guidata(hObject); +tmp = str2cell(get(hObject, 'String')); +if iscellstr(tmp) + errordlg('Please input a valid number.','error'); + try + set(hObject, 'String', handles.df); + catch + set(hObject, 'String', ''); + end + return +end +handles.df=tmp; + + +if isfield(handles,'TF') + t = p2t(mat2cell(handles.pValue*ones(1,length(handles.TF))), handles.df, handles.TF); + handles.intensityThreshold = t; + set(handles.intensityThresholdEdit, 'String', cell2str(t)); +end + +guidata(hObject, handles); +CallBack_pValueEdit(handles.pValueEdit, eventdata); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% get structure push +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_getStructurePush(hObject, eventdata) +handles = guidata(hObject); +xyz = spm_XYZreg('GetCoords',handles.hReg); +tmp_coor = cuixuFindTDstructure(xyz', handles.DB, 0); +set(handles.structureEdit,'String', tmp_coor{1}); +handles.currentxyz = xyz'; +guidata(hObject, handles); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% structure edit +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_structureEdit(action, xyz, thisfun, hReg) + +try + handles=guidata(hReg); +catch + return; +end + +handles.currentxyz = xyz'; +guidata(hReg, handles); + +try + [tmp_coor, cellstructure] = cuixuFindTDstructure(xyz', handles.DB, 0); +catch + return; +end + +set(handles.structureEdit,'String', tmp_coor{1}); + +for ii=[5 3 2 1 4] + if strfind('Unidentified', cellstructure{ii}) + continue; + else + set(handles.searchContentEdit,'string', trimStructureStr(cellstructure{ii})); + set(handles.searchContentEdit,'UserData', 'auto'); + return; + end +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% trim str +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function out = trimStructureStr(str) +pos = findstr('(', str); +if ~isempty(pos) + str(pos-1:end)=[]; +end +out = str; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% cluster size threshold edit +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_clusterSizeThresholdEdit(hObject, eventdata) +handles = guidata(hObject); +tmp = str2double(get(hObject, 'String')); +if isnan(tmp) | tmp<0 + errordlg('Please input a valid number.','error'); + try + set(hObject, 'String', handles.clusterSizeThreshold); + catch + set(hObject, 'String', '5'); + end + return +end +handles.clusterSizeThreshold=tmp; + +guidata(hObject, handles); +CallBack_allIntensityRadio(hObject, eventdata, 'c'); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% save image push button +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_saveImagePush(hObject, eventdata, thisfilename, isMask) +handles = guidata(hObject); + +if exist('thisfilename') + if ~strcmp(deblank(thisfilename), '') + if isfield(handles,'imageFileName') + if ~isempty(handles.imageFileName) + mni2mask(cell2mat(handles.currentDisplayMNI'), thisfilename, cell2mat(handles.currentDisplayIntensity), handles.M{1}, handles.DIM{1}, handles.imageFileName{1}); + return; + end + end + mni2mask(cell2mat(handles.currentDisplayMNI'), thisfilename, cell2mat(handles.currentDisplayIntensity), handles.M{1}, handles.DIM{1}); + return; + end +end + +[filename, pathname] = uiputfile('*.img', 'Save image file as', get(handles.saveImageFileEdit, 'string')); +if isequal(filename,0) | isequal(pathname,0) + return +else + thisfilename = fullfile(pathname, filename); +end + +if isfield(handles,'imageFileName') + if ~isempty(handles.imageFileName{1}) + mni2mask(cell2mat(handles.currentDisplayMNI'), thisfilename, cell2mat(handles.currentDisplayIntensity'), handles.M{1}, handles.DIM{1}, handles.imageFileName{1}, isMask); + return; + end +end + +mni2mask(cell2mat(handles.currentDisplayMNI'), thisfilename, cell2mat(handles.currentDisplayIntensity), handles.M{1}, handles.DIM{1}, '', isMask); + +return; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% save image edit +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_saveImageFileEdit(hObject, eventdata) +handles = guidata(hObject); +CallBack_saveImagePush(handles.saveImagePush, eventdata, get(hObject,'string')); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% save result push +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_saveResultPSPush(hObject, eventdata, thisfilename) + +handles = guidata(hObject); + +if exist('thisfilename') + if ~strcmp(deblank(thisfilename), '') + spm_print(handles.figure); + if strcmp(handles.system, 'linux') + system(['ps2pdf spm2.ps']); + system(['mv spm2.ps ' thisfilename]); + [p,f,ext]=fileparts(thisfilename); + system(['mv spm2.pdf ' fullfile(p,f) '.pdf']); + elseif strcmp(handles.system, 'windows') + system(['move spm2.ps ' '"' thisfilename '"']); + end + return; + end +end + +[filename, pathname] = uiputfile('*.ps', 'Save result as', get(handles.saveResultPSEdit, 'string')); +if isequal(filename,0) | isequal(pathname,0) + return +else + thisfilename = fullfile(pathname, filename); +end + + +% print +H = findobj(get(handles.figure,'Children'),'flat','Type','axes'); +un = cellstr(get(H,'Units')); +pos = get(H,'position'); +index = []; + +for ii=1:length(H) + if findstr('pixels', un{ii}) + continue; + end + if pos{ii}(1)>0.4 & pos{ii}(2) > 0.5 + set(H(ii),'position',[pos{ii}(1), pos{ii}(2), pos{ii}(3)*3/4, pos{ii}(4)]); + index = [index ii]; + end +end + + +spm_print(handles.figure); +if strcmp(handles.system, 'linux') + system(['ps2pdf spm2.ps']); + system(['mv spm2.ps ' thisfilename]); + [p,f,ext]=fileparts(thisfilename); + system(['mv spm2.pdf ' fullfile(p,f) '.pdf']); +elseif strcmp(handles.system, 'windows') + system(['move spm2.ps ' '"' thisfilename '"']); +end + +% set the position back +set(H(index), {'position'}, pos(index)); + + +% printstr = ['print -dpsc2 -painters -noui ' '''' thisfilename '''']; +% try +% orient portrait +% eval(printstr); +% printsuccess = 1; +% catch +% errordlg('Print to ps file failed', 'print error'); +% printsuccess = 0; +% end +%set(H,{'Units'},un); +% if strcmp(handles.system, 'linux') & printsuccess == 1 +% system(['ps2pdf ' '''' thisfilename '''']); +% end + +return; + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% save result edit +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_saveResultPSEdit(hObject, eventdata) +handles = guidata(hObject); +CallBack_saveResultPSPush(hObject, eventdata, get(handles.saveResultPSEdit,'string')); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% select a cluster +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_selectThisClusterPush(hObject, eventdata) +handles = guidata(hObject); + +try + xyz = handles.currentxyz'; +catch + xyz = spm_XYZreg('GetCoords',handles.hReg); +end + +try + handles.selectedCluster = [handles.selectedCluster; xyz']; +catch + handles.selectedCluster = xyz'; +end +set(handles.figure,'currentaxes', handles.glassViewAxes); +xrange = xlim; +yrange = ylim; +try + delete(handles.hGlassText) +end +%handles.hGlassText = text(xrange(1)+diff(xrange)*0.6, yrange(1)+diff(yrange)*0.9, [num2str(size(handles.selectedCluster,1)) ' clusters selected']); +set(handles.infoTextBox, 'string', [num2str(size(handles.selectedCluster,1)) ' clusters selected']); +guidata(hObject, handles); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% unselect a cluster +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_clearSelectedClusterPush(hObject, eventdata) +handles = guidata(hObject); +try + handles = rmfield(handles,'selectedCluster'); +end +set(handles.figure,'currentaxes', handles.glassViewAxes); +xrange = xlim; +yrange = ylim; +try + %delete(handles.hGlassText) + set(handles.infoTextBox, 'string', ['No clusters selected']); +end + +guidata(hObject, handles); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% pick a cluster +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_pickThisClusterPush(hObject, eventdata) +handles = guidata(hObject); +mni = cell2mat(handles.currentDisplayMNI'); +if isempty(mni) + %errordlg('No cluster is picked up.','oops'); + set(handles.infoTextBox, 'string', 'No cluster is picked up.'); + beep + return; +end + +if ~isfield(handles, 'selectedCluster') | isempty(handles.selectedCluster) + try + xyz = handles.currentxyz'; + catch + xyz = spm_XYZreg('GetCoords',handles.hReg); + end + handles.selectedCluster = xyz'; +end + +intensity = cell2mat(handles.currentDisplayIntensity'); +cor = mni2cor(mni, handles.M{1}); +A = spm_clusters(cor'); +xyzcor = mni2cor(handles.selectedCluster, handles.M{1}); + +pos = []; +for ii = 1:size(xyzcor,1) + pos0 = find(cor(:,1)==xyzcor(ii,1) & cor(:,2)==xyzcor(ii,2) & cor(:,3)==xyzcor(ii,3)); + if isempty(pos0) + continue; + end + pos = [pos find(A==A(pos0(1)))]; +end +if isempty(pos) + %errordlg('No cluster is picked up.','oops'); + set(handles.infoTextBox, 'string', 'No cluster is picked up.'); + beep + return +end + +pos = unique(pos); + +tmpmni = mni(pos,:); +tmpintensity = intensity(pos); + +[B,I,J] = unique(tmpmni, 'rows'); +handles.currentDisplayMNI = {B}; +fprintf('Cluster defined from: %s \n', handles.imageFileName{1}); +assignin('base', 'currentDisplayMNI', handles.currentDisplayMNI ); +handles.currentDisplayIntensity = {tmpintensity(I,:)}; + +handles.currentxyz = handles.selectedCluster(end,:); + +[handles.hReg, handles.hSection, handles.hcolorbar] = Draw(handles.currentDisplayMNI, handles.currentDisplayIntensity, hObject, handles); +set(handles.figure,'currentaxes', handles.glassViewAxes); +xrange = xlim; +yrange = ylim; + +handles.selectedCluster = []; +try + delete(handles.hGlassText) +end + +guidata(hObject, handles); + +set(handles.thisClusterSizeEdit,'string', num2str(size(handles.currentDisplayMNI{1},1))); +str = get(handles.searchContentEdit, 'string'); +pos = findstr(' ', str); +str(pos) = []; +files = []; +for ii=1:length(handles.imageFileName) + [a,b,c] = fileparts(handles.imageFileName{ii}); + files = [files b]; +end +if ~isempty(files) + files = ['_from_' files]; +end + +set(handles.saveImageFileEdit, 'string', [str files '.img']); + +% list structure of voxels in this cluster +[a, b] = cuixuFindTDstructure(cell2mat(handles.currentDisplayMNI'), handles.DB, 0); +names = unique(b(:)); +index = NaN*zeros(length(b(:)),1); +for ii=1:length(names) + pos = find(strcmp(b(:),names{ii})); + index(pos) = ii; +end + +for ii=1:max(index) + report{ii,1} = names{ii}; + report{ii,2} = length(find(index==ii)); +end +for ii=1:size(report,1) + for jj=ii+1:size(report,1) + if report{ii,2} < report{jj,2} + tmp = report(ii,:); + report(ii,:) = report(jj,:); + report(jj,:) = tmp; + end + end +end +report = [{'structure','# voxels'}; {'--TOTAL # VOXELS--', length(a)}; report]; +% format long +% disp(b) +% disp(report) +% format +report2 = {sprintf('%s\t%s',report{1,2}, report{1,1}),''}; +for ii=2:size(report,1) + if strcmp('Unidentified', report{ii,1}); continue; end + report2 = [report2, {sprintf('%5d\t%s',report{ii,2}, report{ii,1})}]; +end + +report2 = [report2, {'','select and Ctrl-C to copy'}]; +% f = figure('unit','normalized', 'menubar','none', 'position', [0.3 0.2 0.2 min(0.7,0.016*length(report2))], 'name', 'cluster information', 'NumberTitle','off'); +% hEdit = uicontrol(f,'style','edit',... +% 'unit','normalized','position',[0 0 1 1],... +% 'horizontal','left',... +% 'BackgroundColor', 'w',... +% 'String', report2,... +% 'max',2,'min',0); + +set(handles.infoTextBox, 'string', report2); +return + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% commonRegion push +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_commonRegionPush(hObject, eventdata) +handles = guidata(hObject); +if ~isfield(handles,'TF') | length(handles.TF)<=1 + %msgbox('Two or more images need to be loaded to find the common region.', 'info'); + set(handles.infoTextBox, 'string', 'Two or more images need to be loaded to find the common region.'); + beep + return; +end + +common = handles.currentDisplayMNI{1}; +for ii=2:length(handles.TF) + common = intersect(common, handles.currentDisplayMNI{ii}, 'rows'); +end + +if isempty(common) + %msgbox('No common region found.', 'info'); + set(handles.infoTextBox, 'string', 'No common region found.'); + beep + return; +end + +tmpMNI = cell2mat(handles.currentDisplayMNI'); +tmpIntensity = cell2mat(handles.currentDisplayIntensity'); +intensity = zeros(size(common,1),1); + +for ii=1:size(common,1) + pos = find(abs(tmpMNI(:,1)-common(ii,1))<0.1 & abs(tmpMNI(:,2)-common(ii,2))<0.1 & abs(tmpMNI(:,3)-common(ii,3))<0.1); + intensity(ii) = prod(tmpIntensity(pos)); +end + +handles.currentDisplayMNI = {common}; +handles.currentDisplayIntensity = {intensity}; +[handles.hReg, handles.hSection, handles.hcolorbar] = Draw(handles.currentDisplayMNI, handles.currentDisplayIntensity, hObject, handles); + +% if isfield(handles,'hLegend') +% try +% set(cell2mat(handles.hLegend'),'visible',{'off'}); +% end +% end +guidata(hObject, handles); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% volume push +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_volumePush(hObject, eventdata) +handles = guidata(hObject); +mni = cell2mat(handles.currentDisplayMNI'); +intensity = cell2mat(handles.currentDisplayIntensity'); + +xSPM.XYZ = mni2cor(mni, handles.M{1}); +xSPM.XYZ = xSPM.XYZ'; +xSPM.XYZmm = mni'; +xSPM.Z = (intensity'); +xSPM.M = handles.M{1}; +xSPM.DIM = handles.DIM{1}; +xSPM.STAT = handles.TF{1}; +if xSPM.STAT == 'T' + xSPM.STATstr = [xSPM.STAT '_{' num2str(handles.df{1}) '}']; + xSPM.df = [1 handles.df{1}]; +elseif xSPM.STAT == 'F' + xSPM.STATstr = [xSPM.STAT '_{' num2str(handles.df{1}(1)) ',' num2str(handles.df{1}(2)) '}']; + xSPM.df = [handles.df{1}]; +else + xSPM.STAT = 'T'; + xSPM.STATstr = [xSPM.STAT '_{' num2str(handles.df{1}) '}']; + xSPM.df = [1 handles.df{1}]; +end +xSPM.k = str2num(get(handles.clusterSizeThresholdEdit, 'string')); +xSPM.u = str2num(get(handles.intensityThresholdEdit, 'string')); +xSPM.u = xSPM.u(1); +xSPM.VOX = abs([xSPM.M(1,1) xSPM.M(2,2) xSPM.M(3,3)]); +xSPM.n = 1; + + +% SPM - structure containing SPM, distribution & filtering details +% - required fields are: +% .swd - SPM working directory - directory containing current SPM.mat +% .Z - minimum of n Statistics {filtered on u and k} +% .n - number of conjoint tests +% .STAT - distribution {Z, T, X or F} +% .df - degrees of freedom [df{interest}, df{residual}] +% .u - height threshold +% .k - extent threshold {voxels} +% .XYZ - location of voxels {voxel coords} +% .XYZmm - location of voxels {mm} +% .S - search Volume {voxels} +% .R - search Volume {resels} +% .FWHM - smoothness {voxels} +% .M - voxels - > mm matrix +% .VOX - voxel dimensions {mm} +% .Vspm - mapped statistic image(s) +% .Ps - uncorrected P values in searched volume (for FDR) + +xSPM.S = length(handles.intensity{1}); +xSPM.R = [3 27.0931 276.3307 498.1985]; +xSPM.FWHM = [2.9746 3.1923 2.8600]; + +if get(handles.positiveIntensityRadio, 'Value') + xSPM.Z = abs(xSPM.Z); + if xSPM.STAT == 'T'; xSPM.Ps = (1-spm_Tcdf(intensity, xSPM.df(2))); end + if xSPM.STAT == 'F'; xSPM.Ps = (1-spm_Fcdf(intensity, xSPM.df)); end +end + +if get(handles.negativeIntensityRadio, 'Value'); + xSPM.Z = abs(xSPM.Z); + if xSPM.STAT == 'T'; xSPM.Ps = (1-spm_Tcdf(-intensity, xSPM.df(2))); end + if xSPM.STAT == 'F'; xSPM.Ps = (1-spm_Fcdf(-intensity, xSPM.df)); end +end + +if get(handles.allIntensityRadio, 'Value'); + if xSPM.STAT == 'T'; xSPM.Ps = (1-spm_Tcdf((intensity), xSPM.df(2))); end + if xSPM.STAT == 'F'; xSPM.Ps = (1-spm_Fcdf((intensity), xSPM.df)); end +end + +if findstr('SPM2',spm('ver')) + P = spm_get([0 1],'SPM.mat','locate the corresponding SPM.mat'); +elseif findstr('SPM5',spm('ver')) + P = spm_select([0:1],'SPM.mat','locate the corresponding SPM.mat'); +end + +if ~isempty(P) + load(P); + xSPM.FWHM = SPM.xVol.FWHM; + xSPM.R = SPM.xVol.R; + xSPM.S = SPM.xVol.S; +else + warndlg(['You did not input SPM.mat. The listed result may not be correct.'], 'SPM.mat missing'); +end + +spm_list('List',xSPM,handles.hReg); + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% display +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_displayPush(hObject, eventdata) +handles = guidata(hObject); +spm_image('init', handles.imageFileName); + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% all in one +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_allinonePush(hObject, eventdata) +try + if findstr('SPM2',spm('ver')) + P = spm_get([0:100],'*IMAGE','Select image files'); + elseif findstr('SPM5',spm('ver')) + P = spm_select(Inf,'image','Select image files'); + end + +catch + return; +end +if isempty(P) + return +end + +handles = guidata(hObject); +%if ~isfield(handles,'hReg') | ~isfield(handles,'hSection') | ~isfield(handles,'hcolorbar') +tmp = [0 0 0]; +[handles.hReg, handles.hSection, handles.hcolorbar] = Draw(tmp([],:), [], hObject, handles); +%end + +colors=mycolourset; +for ii=1:size(P,1) + thisfilename = deblank(P(ii,:)); + [handles.imageFileName, handles.M, handles.DIM, handles.TF, handles.df, handles.mni, handles.intensity] = getImageFile(thisfilename); + cor = mni2cor(handles.mni, handles.M); + spm_orthviews('addcolouredblobs',1,cor',handles.intensity',handles.M,colors(mod(ii,6)+1,:)); +end +spm_orthviews('Redraw'); + +guidata(hObject, handles); + +% contents = get(handles.sectionViewListbox,'String'); +% currentsel = contents{get(handles.sectionViewListbox,'Value')}; +% sectionViewTargetFile = getSectionViewTargetFile(handles.spmdir, currentsel); +% +% % spm_image('init', sectionViewTargetFile); +% % spm_image('addblobs'); +% % return + +% guidata(hObject, handles); +% load xSPM; +% VOL.XYZ = xSPM.XYZ; +% VOL.Z = xSPM.Z; +% VOL.M = handles.M; + +%addcolouredimage(handles.hSection, '333.img',[1 0 1]); +% uigetfiles +% nblobs = 4; +% for i=1:nblobs, +% %[SPM,VOL] = spm_getSPM; +% %c = spm_input('Colour','+1','m','Red blobs|Yellow blobs|Green blobs|Cyan blobs|Blue blobs|Magenta blobs',[1 2 3 4 5 6],1); +% c = i; +% VOL.XYZ = ceil(rand(3,20)*20); +% VOL.Z = randn(1,20); +% VOL.M = handles.M; +% colours = [1 0 0;1 1 0;0 1 0;0 1 1;0 0 1;1 0 1]; +% spm_orthviews('addcolouredblobs',1,VOL.XYZ,VOL.Z,VOL.M,colours(c,:)); +% end; + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% overlay a brain region Push +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_overlayPush(hObject, eventdata) +handles = guidata(hObject); + +tobeoverlay = deblank(get(handles.overlayEdit, 'string')); + +if isempty(tobeoverlay) + return; +end + +tobeoverlay = str2cell(tobeoverlay, ' '); +if isnumeric(tobeoverlay{1}) + if length(tobeoverlay{1})>1 + errordlg(['Your input, ' deblank(get(handles.overlayEdit, 'string')) ', seems coincide with a matlab constant.'], 'error'); + return + end + for ii=1:length(tobeoverlay) + tobeoverlay{ii} = num2str(tobeoverlay{ii}); + end +end +fn = fieldnames(handles.wholeMaskMNIAll); +for ii=1:length(tobeoverlay) + pos{ii} = []; + for jj=1:length(fn) + x = []; + if ~isempty(str2num(tobeoverlay{ii})) + y = str2cell(fn{jj},'_'); + for kk=1:length(y) + x = isequal(tobeoverlay{ii}, y{kk}); + if x; break;end; + end + if x==0; x = []; end; + else + x = findstr(lower(tobeoverlay{ii}), lower(fn{jj})); + end + if ~isempty(x) + pos{ii} = [pos{ii} jj]; + end + end +end +common = pos{1}; +for ii=2:length(pos) + common = intersect(common, pos{ii}); +end + +if isempty(common) + %warndlg(['I don'' find ' deblank(get(handles.overlayEdit, 'string')) '.'], 'oops'); + set(handles.infoTextBox, 'string', ['I don'' find ' deblank(get(handles.overlayEdit, 'string')) '.']); + return +end + +mask = []; +for ii=1:length(common) + eval(['mask = [mask; handles.wholeMaskMNIAll.' fn{common(ii)} '];']); +end + +if isempty(mask) + %warndlg(['I don'' find ' deblank(get(handles.overlayEdit, 'string')) '.'], 'oops'); + set(handles.infoTextBox, 'string', ['I don'' find ' deblank(get(handles.overlayEdit, 'string')) '.']); + return; +end + +try + handles.mni; +catch + delete(gcf); + xjview(mask); + return; +end + +handles.imageFileName = [handles.imageFileName, {deblank(get(handles.overlayEdit, 'string'))}]; +handles.M = [handles.M, {handles.M{1}}]; +handles.DIM = [handles.DIM, {handles.DIM{1}}]; +handles.currentDisplayMNI = [handles.currentDisplayMNI, {mask}]; +m = max(abs(cell2mat(handles.currentDisplayIntensity'))); +if ~isempty(m) + handles.currentDisplayIntensity = [handles.currentDisplayIntensity, {-2*m*ones(size(mask,1),1)}]; +else + handles.currentDisplayIntensity = [handles.currentDisplayIntensity, {ones(size(mask,1),1)}]; +end +[handles.hReg, handles.hSection, handles.hcolorbar] = Draw(handles.currentDisplayMNI, handles.currentDisplayIntensity, hObject, handles); +return + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% overlay a brain region Edit +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_overlayEdit(hObject, eventdata) +handles = guidata(hObject); +CallBack_overlayPush(handles.overlayPush, eventdata); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% overlay a brain region popup +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_overlayPop(hObject, eventdata) +handles = guidata(hObject); +names = get(hObject, 'string'); +value = get(hObject, 'value'); +set(handles.overlayEdit, 'string', names{value}); +CallBack_overlayEdit(handles.overlayEdit, eventdata); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% search xBrain.org and other databases +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_searchPush(hObject, eventdata) + +try + handles = guidata(hObject); + searchContent = get(handles.searchContentEdit,'string'); + searchEngine = get(handles.searchEnginePop,'string'); + searchEngine = searchEngine{get(handles.searchEnginePop,'value')}; + + + xyz = str2num(searchContent); + searchMode=get(handles.searchContentEdit,'UserData'); + + switch searchMode + case 'auto'; + xyz = spm_XYZreg('GetCoords',handles.hReg); + handles.currentxyz = xyz'; + guidata(hObject, handles); + [a,b] = cuixuFindTDstructure(xyz', handles.DB, 0); + try + brodmann = strmatch('Brodmann',b) + brodmann = b{brodmann}; + brodmann = brodmann(1:end-4); + end; + try + region = b{3}; + region = region(1:end-4); + end + + case 'manual' + if isempty(xyz) + searchMode='text'; + elseif length(xyz)==3 + searchMode='xyz'; + end + end + + + switch searchEngine + case 'Brede' + switch searchMode + case 'auto' + searchString = sprintf(' %0.0f',xyz') + set(handles.searchContentEdit,'string',searchString); + case 'xyz' + searchString = num2str(xyz');%searchContent=sprintf('+%0.0f',searchContent) + case 'text' + end + urlstr = ['http://hendrix.imm.dtu.dk/cgi-bin/brede_loc_query.pl?q=' searchString ]; + case 'xBrain.org' + switch searchMode + case { 'auto' 'xyz'} + xbrainSearchField = 'mni or tal&mnidistance=20'; + searchString = sprintf(' %0.1f',xyz')% searchString = num2str(xyz'); + case 'text' + xbrainSearchField = 'region'; + searchString = searchContent; + end + set(handles.searchContentEdit,'string',searchString); + urlstr = ['http://people.hnl.bcm.tmc.edu/cuixu/cgi-bin/bmd/paper.pl?search_content=' searchString '&search_field=' xbrainSearchField]; + case 'Google Scholar' + if isequal(searchMode,'auto' ) + searchString = { brodmann region }; + searchString(cellfun('isempty',searchString))=[]; + searchString = sprintf('%%22%s%%22|',searchString{:}); + try + searchString = searchString(1:end-1); + end + %set(handles.searchContentEdit,'string',searchString); + end + urlstr = ['http://scholar.google.com/scholar?q=' searchString ]; + case 'Pubmed' + switch searchMode + case 'auto' + searchString = { brodmann region }; + searchString(cellfun('isempty',searchString))=[]; + searchString = sprintf('%%22%s%%22 OR ',searchString{:}); + try + searchString = searchString(1:end-4); + end + %searchString = brodmann; + case 'text' + searchString = searchContent; + end + % urlstr = ['http://www.ncbi.nlm.nih.gov/entrez/query.fcgi?cmd=PureSearch&db=pubmed&details_term=(' searchString ')']; + urlstr = ['http://www.ncbi.nlm.nih.gov/sites/entrez?cmd=search&db=pubmed&pubmedfilters=true&term=(' searchString ')']; + case 'Wikipedia' + if isequal(searchMode,'auto') + searchString = region; + end + urlstr = ['http://en.wikipedia.org/w/index.php?search=%22' searchString '%22']; + end + set(handles.searchContentEdit,'UserData',searchMode); +catch + urlstr = 'http://www.google.com'; +end + +try + web(urlstr,'-browser'); +catch + if exist('c:\program Files\mozilla Firefox\firefox.exe','file') + system(['"c:\program Files\mozilla Firefox\firefox.exe" "' urlstr '"']) + else + web(urlstr); + end +end + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% control panel on or off +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function controlHide(handles, status) + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% get multiple values from a string deliminated delim +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function out = str2cell(str, delim) +if ~exist('delim') + delim = '; '; +end + +[out{1},b] = strtok(str, delim); +ii = 2; +while ~isempty(b) + [out{ii},b] = strtok(b, delim); + ii = ii+1; +end + +for ii=1:length(out) + out2{ii} = str2num(out{ii}); + if isempty(out2{ii}) + return; + end +end + +for ii=1:length(out) + out{ii} = str2num(out{ii}); +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% cell2str +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function str = cell2str(acell, delim) +if ~exist('delim') + delim = ';'; +end + +str = []; + +if length(acell)==1 + if isstr(acell{1}) + str = acell{1}; + else + str = num2str(acell{1}); + end +else + for ii=1:length(acell) + if isstr(acell{ii}) + str = [str acell{ii}, delim ' ']; + else + str = [str num2str(acell{ii}), delim ' ']; + end + end + str(end-1:end)=[]; +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% cellmax, find max in each element, return a cell +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function MAXX = cellmax(acell, absolute) + +if ~exist('absolute') + absolute = ''; +end + +MAXX = []; +for ii=1:length(acell) + if strfind('abs',absolute) + MAXX = [MAXX max(abs(acell{ii}))]; + else + MAXX = [MAXX max(acell{ii})]; + end +end +MAXX = num2cell(MAXX); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% maxcell +%%% find max of all numbers in the whole cell, return a single value +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function MAXX = maxcell(acell, absolute) + +if ~exist('absolute') + absolute = ''; +end +MAXX = cellmax(acell, absolute); +MAXX = max(cell2mat(MAXX)); + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% p2t +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function t = p2t(p, df, TF) +if ~iscell(p) + if upper(TF)=='T' | upper(TF)=='S' + t = spm_invTcdf(1-p,df); + elseif upper(TF) == 'F' + t = spm_invFcdf(1-p,df); + end +else + for ii=1:length(p) + t{ii} = p2t(p{ii},df{ii},TF{ii}); + end +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% s2t, s is defined as -log10(p) +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function t = s2t(s, df, TF) + +if ~iscell(s) + p = 10^(-s); + if upper(TF)=='T' | upper(TF)=='S' + t = spm_invTcdf(1-p,df); + elseif upper(TF) == 'F' + t = spm_invFcdf(1-p,df); + end +else + for ii=1:length(p) + t{ii} = s2t(s{ii},df{ii},TF{ii}); + end +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% t2p +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function p = t2p(t, df, TF) +if ~iscell(t) + if upper(TF)=='T' | upper(TF)=='S' + p = 1-spm_Tcdf(t,df); + elseif upper(TF) == 'F' + p = 1-spm_Fcdf(t,df); + end +else + for ii=1:length(t) + p{ii} = t2p(t{ii},df{ii},TF{ii}); + end +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% t2s, s is defined as -log10(p) +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function s = t2s(t, df, TF) +if ~iscell(t) + if upper(TF)=='T' | upper(TF)=='S' + p = 1-spm_Tcdf(t,df); + elseif upper(TF) == 'F' + p = 1-spm_Fcdf(t,df); + else + p = 0.1; + end + s = -log10(p); +else + for ii=1:length(t) + s{ii} = t2s(t{ii},df{ii},TF{ii}); + end +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% mni2mask +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function mask = mni2mask(coords, targetfilename, intensity, M, DIM, templateFile, isMask) +% function mask = mni2mask(coords, targetfilename, intensity, templateFile) +% make mask from coordinates +% +% coords: a Nx3 column of 3-d coordinates in MNI +% space +% targetfilename: (optional) the image files to be written +% intensity: (optional) Nx1, the values of each coordinate. +% M: rotation matrix +% DIM: dimension +% templateFile: (optional) the templateFile from which we can get the right +% dimensions. +% isMask: if this variable exist and equal to 1, then all non-zero +% intensities will be set to 1 +% +% Xu Cui +% 2004/11/18 + +if ~exist('intensity') + intensity = ones(size(coords,1),1); +end +thistemplateFile = ''; +if exist('templateFile') + if ~isempty(templateFile) + thistemplateFile = templateFile; + end +end + +if isempty(thistemplateFile) + V.mat = [... + -4 0 0 84; ... + 0 4 0 -116; ... + 0 0 4 -56; ... + 0 0 0 1]; + V.dim = [41 48 35 16]; + if exist('M') + V.mat = M; + end + if exist('DIM') + V.dim = DIM; + V.dim(4) = 16; + end + V.fname = targetfilename; + V.descrip = 'our own image'; +else + V = spm_vol(templateFile); + V.fname = targetfilename; + if isfield(V, 'descrip') + V.descrip = ['my image from ' V.descrip]; + else + V.descrip = 'my own image'; + end +end + +thisismask = 0; +if exist('isMask') + if isMask == 1 + thisismask = 1; + end +end +if thisismask + V.descrip = 'my mask'; + intensity = ones(size(intensity)); +end + +O = zeros(V.dim(1),V.dim(2),V.dim(3)); + +coords = mni2cor(coords,V.mat); + + +for ii=1:size(coords,1) + O(coords(ii,1),coords(ii,2),coords(ii,3)) = intensity(ii); +end + +if exist('targetfilename') + V = spm_write_vol(V,O); +end + +mask = O; + +return; + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% get image file information +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function [imageFile,M,DIM,TF,df,mni,intensity] = getImageFile(thisfilename) +% function imageFile = getImageFile(filename) +% get the information of this/these image file(s) +% +% thisfilename: (optional) if doesn't give file name, then show a open file +% dialog +% imageFile: the full name of the selected file (if user pressed cancel, +% imageFile == 0 +% M: M matrix (rotation matrix) +% DIM: dimension +% TF: t test or f test? 'T' or 'F' +% df: degree of freedome +% mni: mni coord +% intensity: intensity of each mni coord +% +% Note: The returned variables are cellarrays. +% +% Xu Cui +% last revised: 2005-05-03 + +if nargin < 1 | isempty(thisfilename) + if findstr('SPM2',spm('ver')) + P0 = spm_get([0:100],'*IMAGE','Select image files'); + elseif findstr('SPM5',spm('ver')) + P0 = spm_select(Inf,'image','Select image files'); + end + + % try + % P0 = spm_get([0:100],'*IMAGE','Select image files'); + % catch + % P0 = []; + % [FileName,PathName] = uigetfile({'*.img';'*.IMG';'*.*'},'Select image files','MultiSelect','on'); + % if isstr(FileName) + % P0 = {fullfile(PathName, FileName)}; + % elseif iscellstr(FileName) + % for ii=1:length(FileName) + % P0{ii} = fullfile(PathName, FileName{ii}); + % end + % else + % P0 = []; + % end + % end + try + if isempty(P0) + imageFile = '';M=[];DIM=[];TF=[];df=[];mni=[];intensity=[]; + return + end + end + for ii=1:size(P0,1) + P{ii} = deblank(P0(ii,:)); + end +else + if isstr(thisfilename) + P = {thisfilename}; + elseif iscellstr(thisfilename) + P = thisfilename; + else + disp('Error: In getImageFile: I don''t understand the input.'); + imageFile = '';M=[];DIM=[];TF=[];df=[];mni=[];intensity=[]; + return + end +end + +global LEFTRIGHTFLIP_; + +for ii=1:length(P) + imageFile{ii} = P{ii}; + [cor{ii}, intensity{ii}, tmp{ii}, M{ii}, DIM{ii}, TF{ii}, df{ii}] = mask2coord(imageFile{ii}, 0); + if LEFTRIGHTFLIP_ == 1 + M{ii}(1,:) = -M{ii}(1,:); + end + mni{ii} = cor2mni(cor{ii}, M{ii}); +end + + + + + + + + + + + + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% cuixuFindTDstructure +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function [onelinestructure, cellarraystructure] = cuixuFindTDstructure(pos, DB, TALMNI) +% function [onelinestructure, cellarraystructure] = cuixuFindTDstructure(pos, DB, TALMNI) +% +% this function converts Talairach Daemon coordinate or MNI coordinate to a description of +% brain structure. +% +% pos: the coordinate (MNI or Talairach coordinate, defined by TALMNI) of the position in the brain, in mm +% DB: the database structure (see below for detailed explanation). If you don't input this argument, there +% should be a file called 'TDdatabase.mat' in the current directory +% TALMNI: 1 if pos is talairach coordinate, 0 if pos is MNI coordinate. 1 +% by default. +% +% onelinestructure: a one-line description of the returned brain +% structure +% cellarraystructure: a cell array of size 5, each cell contains a string +% describing the structure of the brain in a certain level. +% +% Example: +% [onelinestructure, cellarraystructure] = cuixuFindTDstructure([-24 24 48], DB, 1) +% [onelinestructure, cellarraystructure] = cuixuFindTDstructure([-24 24 48]) +% then +% onelinestructure = // Left Cerebrum // Frontal Lobe (L) // Middle Frontal Gyrus (L) // Gray Matter (L) // Brodmann area 8 (L) +% cellarraystructure = 'Left Cerebrum' 'Frontal Lobe (L)' [1x24 char] 'Gray Matter (L)' 'Brodmann area 8 (L)' +% +% Xu Cui +% 2004-6-28 +% + +%---------------------------------------------------------------------------------- +% DB strcture +%---------------------------------------------------------------------------------- +%------------------------------- +% Grid specification parameters: +%------------------------------- +% minX - min X (mm) +% maxX - max X (mm) +% voxX - voxel size (mm) in X direction +% minY - min Y (mm) +% maxY - max Y (mm) +% voxY - voxel size (mm) in Y direction +% minZ - min Z (mm) +% maxZ - max Z (mm) +% voxZ - voxel size (mm) in Z direction +% nVoxX - number of voxels in X direction +% nVoxY - number of voxels in Y direction +% nVoxZ - number of voxels in Z direction +%------------------------------- +% Classification parameters: +%------------------------------- +% numClass - number of classification types +% cNames - cNames{i} - cell array of class names for i-th CT +% numClassSize - numClassSize(i) - number of classes for i-th CT +% indUnidentified - indUnidentified(i) - index of "indUnidentified" class for i-th CT +% volClass - volClass{i}(j) - number of voxels in class j for i-th CT +% +% data - N x numClass matrix of referencies; let +% x y z coordinates in mm (on the grid) and +% nx = (x-minX)/voxX +% ny = (y-minY)/voxY +% nz = (z-minZ)/voxZ +% ind = nz*nVoxX*nVoxY + ny*nVoxX + nx + 1 +% data(ind, i) - index of the class for i-th CT in cNames{i} to +% which (x y z) belongs, i.e. +% cNames{i}{data(ind, i)} name of class for i-th CT +%---------------------------------------------------------------------------------- + +if nargin==1 + load('TDdatabase.mat'); + TALMNI = 1; +elseif nargin == 2 + TALMNI = 1; +end + +if(TALMNI == 1) + pos = tal2mni(pos); +elseif(TALMNI == 0) + []; +else + disp('TALMNI should be 1 or 0.'); +end + +pos(:,1) = DB.voxX*round(pos(:,1)/DB.voxX); +pos(:,2) = DB.voxY*round(pos(:,2)/DB.voxY); +pos(:,3) = DB.voxZ*round(pos(:,3)/DB.voxZ); + +min = []; +vox = []; +for(i=1:size(pos,1)) + min = [min; DB.minX DB.minY DB.minZ]; + vox = [vox; DB.voxX DB.voxY DB.voxZ]; +end +n_pos = (pos - min)./vox; + +nx = n_pos(:,1); +ny = n_pos(:,2); +nz = n_pos(:,3); +index = nz*DB.nVoxX*DB.nVoxY + ny*DB.nVoxX + nx + 1; +indMax = size(DB.data, 1); + +onelinestructuretmp = []; +onelinestructure = []; +for(j=1:size(pos,1)) + onelinestructuretmp = []; + for(i=1:DB.numClass) + + if (index(j) <= 0 | index(j) > indMax) + ind(j) = DB.indUnidentified(i); + else + ind(j) = DB.data(index(j), i); + end + + cellarraystructure{j,i} = DB.cNames{i}{ind(j)}; + onelinestructuretmp = [onelinestructuretmp ' // ' cellarraystructure{j,i}]; + end + onelinestructure{j} = onelinestructuretmp; +end + +return + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% mask2coord +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function [cor, intensity, cor_singlecolumn,M,DIM,TF,df] = mask2coord(mask, checkdimension) +% [cor, intensity, cor_singlecolumn] = mask2coord(mask, checkdimension) +% +% This is to retrieve the coordinate of a mask file, or a matrix of 3-D +% +% mask: an image file or a matrix (3-D), with some of the elements are +% non-zeros +% checkdimension: check if the dimension is checkdimension, if not, return empty +% matrix +% cor: a N*3 matrix, which each row a coordinate in matrix +% intensity: a N*1 matrix, which encodes the intensity of each voxel. +% cor_singlecolumn: a N*1 matrix, each row is the index in the matrix +% M: rotation matrix +% DIM: dimension +% TF: t test or f test? 'T','F' or [] +% df: degree of freedome for T/F test +% +% Example: +% mask = zeros(4,3,2); +% mask(1,2,1) = 1; +% mask(3,2,2) = 1; +% mask2coord(mask) +% +% mask2coord('spmT_0002.img') +% mask2coord('spmT_0002.img',[41 48 35]) +% +% Xu Cui +% 2004-9-20 +% last revised: 2005-04-30 + +if nargin < 2 + checkdimension = 0; +end + +if ischar(mask) + V = spm_vol(mask); + mask = spm_read_vols(V); + M = V.mat; + DIM = V.dim; + TF = 'T'; + T_start = strfind(V.descrip,'SPM{T_[')+length('SPM{T_['); + if isempty(T_start); T_start = strfind(V.descrip,'SPM{F_[')+length('SPM{F_['); TF='F'; end + if isempty(T_start) + TF=[]; df=[]; + else + T_end = strfind(V.descrip,']}')-1; + df = str2num(V.descrip(T_start:T_end)); + end +else + M = []; + TF = []; + df = []; +end + +dim = size(mask); +if length(checkdimension)==3 + if dim(1)~= checkdimension(1) | dim(2)~= checkdimension(2) | dim(3)~= checkdimension(3) + y = []; + disp('dimension doesn''t match') + return + end +end + +pos = find(mask~=0); +intensity = mask(pos); + +y = zeros(length(pos),3); + +y(:,3) = ceil(pos/(dim(1)*dim(2))); +pos = pos - (y(:,3)-1)*(dim(1)*dim(2)); +y(:,2) = ceil(pos/dim(1)); +pos = pos - (y(:,2)-1)*(dim(1)); +y(:,1) = pos; + +cor = y; +cor_singlecolumn = pos; +DIM = dim; +return + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% mni2cor +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function coordinate = mni2cor(mni, T) +% function coordinate = mni2cor(mni, T) +% convert mni coordinate to matrix coordinate +% +% mni: a Nx3 matrix of mni coordinate +% T: (optional) transform matrix +% coordinate is the returned coordinate in matrix +% +% caution: if T is not specified, we use: +% T = ... +% [-4 0 0 84;... +% 0 4 0 -116;... +% 0 0 4 -56;... +% 0 0 0 1]; +% +% xu cui +% 2004-8-18 +% + +if isempty(mni) + coordinate = []; + return; +end + +if ~exist('T') + T = ... + [-4 0 0 84;... + 0 4 0 -116;... + 0 0 4 -56;... + 0 0 0 1]; +end + +coordinate = [mni(:,1) mni(:,2) mni(:,3) ones(size(mni,1),1)]*(inv(T))'; +coordinate(:,4) = []; +coordinate = round(coordinate); +return; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% cor2mni +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function mni = cor2mni(cor, T) +% function mni = cor2mni(cor, T) +% convert matrix coordinate to mni coordinate +% +% cor: an Nx3 matrix +% T: (optional) rotation matrix +% mni is the returned coordinate in mni space +% +% caution: if T is not given, the default T is +% T = ... +% [-4 0 0 84;... +% 0 4 0 -116;... +% 0 0 4 -56;... +% 0 0 0 1]; +% +% xu cui +% 2004-8-18 +% last revised: 2005-04-30 + +if nargin == 1 + T = ... + [-4 0 0 84;... + 0 4 0 -116;... + 0 0 4 -56;... + 0 0 0 1]; +end + +cor = round(cor); +mni = T*[cor(:,1) cor(:,2) cor(:,3) ones(size(cor,1),1)]'; +mni = mni'; +mni(:,4) = []; +return; + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% draw +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function [hReg, hSection, hcolorbar] = Draw(mniCoord, intensity, hObject, handles) + +try + delete(handles.hcolorbar); +end +try + delete(handles.hReg); +end +try + delete(handles.hSection); +end +try + cla(handles.glassViewAxes); +end + +try + H = findobj(get(handles.figure,'Children'),'flat','Type','axes'); + un = cellstr(get(H,'Units')); + pos = get(H,'position'); + + for ii=1:length(H) + if findstr('pixels', un{ii}) + continue; + end + if pos{ii}(1)>0.4 + delete(H(ii)); + end + end +end + +sectionViewTargetFile = handles.sectionViewTargetFile; + +if ~iscell(mniCoord) | (iscell(mniCoord) & length(mniCoord)==1)% multiple input? no + if (iscell(mniCoord) & length(mniCoord)==1) + mniCoord = mniCoord{1}; + intensity = intensity{1}; + handles.M = handles.M{1}; + handles.DIM = handles.DIM{1}; + end + if max(intensity)*min(intensity) < 0 + [hReg, hSection, hcolorbar] = cuixuSectionView(mniCoord,intensity,sectionViewTargetFile,hObject,handles); + else + [hReg, hSection, hcolorbar] = cuixuSectionView(mniCoord,abs(intensity),sectionViewTargetFile,hObject,handles); + end + + if size(mniCoord,1)>1 + pos1 = find(intensity>=0); + pos2 = find(intensity<0); + if ~isempty(pos1) & ~isempty(pos2) + if get(handles.renderViewCheck, 'Value'); cuixuRenderView(mniCoord(pos1,:),intensity(pos1,:),mniCoord(pos2,:),-intensity(pos2,:)); end + else + if get(handles.renderViewCheck, 'Value'); cuixuRenderView(mniCoord, abs(intensity)); end + end + end +else % multiple input? yes + [hReg, hSection, hcolorbar] = cuixuSectionView(mniCoord,intensity,sectionViewTargetFile,hObject,handles); + + mniCoordtmp=[]; + intensitytmp=[]; + for ii=1:length(mniCoord) + mniCoordtmp = [mniCoordtmp; mniCoord{ii}]; + intensitytmp = [intensitytmp; intensity{ii}]; + end + mniCoord = mniCoordtmp; + intensity = intensitytmp; + if size(mniCoord,1)>1 + pos1 = find(intensity>=0); + pos2 = find(intensity<0); + if ~isempty(pos1) & ~isempty(pos2) + if get(handles.renderViewCheck, 'Value'); cuixuRenderView(mniCoord(pos1,:),intensity(pos1,:),mniCoord(pos2,:),-intensity(pos2,:)); end + else + if get(handles.renderViewCheck, 'Value'); cuixuRenderView(mniCoord, abs(intensity)); end + end + end + +end + +try + spm_XYZreg('SetCoords',handles.currentxyz',hReg); +end + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% cuixuSectionView +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function [hReg, hSection, hcolorbar] = cuixuSectionView(mniCoord, intensity, targetFile, hObject,handles) +% function h = cuixuSectionView(mniCoord, intensity) +% This is to project your coordinate to section view +% +% mniCoord: the mni coordinate, N*3 matrix +% intensity: the plot intensity of the spots, 1*N matrix. The intensity +% could be t value, for example. +% +% a special feature of this function is: it automatically seperate the +% negative and positive intensity and use hot and cold color to represent +% them. +% +% h: the returned handle for the axes +% +% SEE ALSO: cuixuView cuixuGlassView cuixuRenderView +% +% Xu Cui +% 2004/11/11 + +if ~iscell(mniCoord) | (iscell(mniCoord) & length(mniCoord)==1)% multiple input? no + multiple = 0; + if (iscell(mniCoord) & length(mniCoord)==1) + mniCoord = mniCoord{1}; + intensity = intensity{1}; + handles.M = handles.M{1}; + handles.DIM = handles.DIM{1}; + end +else + multiple = 1; + mniCoordtmp = []; + intensitytmp = []; + for ii=1:length(mniCoord) + mniCoordtmp = [mniCoordtmp; mniCoord{ii}]; + intensitytmp = [intensitytmp; intensity{ii}]; + % for multiple input + cSPM{ii}.XYZ = mni2cor(mniCoord{ii}, handles.M{ii}); + cSPM{ii}.XYZ = cSPM{ii}.XYZ'; + cSPM{ii}.Z = abs(intensity{ii}); + cSPM{ii}.M = handles.M{ii}; + cSPM{ii}.DIM = handles.DIM{ii}'; + end + mniCoord = mniCoordtmp; + intensity = intensitytmp; + handles.M = handles.M{1}; + handles.DIM = handles.DIM{1}; +end + +SPM.XYZ = mni2cor(mniCoord, handles.M); +SPM.XYZ = SPM.XYZ'; +SPM.Z = intensity; +SPM.M = handles.M; +SPM.DIM = handles.DIM'; + +%%%%%%%%%%%%%%% Reg %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +handles.colormap = colormap; + +coor = mniCoord; +xSPM = SPM; +xSPM.XYZmm = mniCoord'; +axes(handles.glassViewAxes) +WS = spm('WinScale'); +FS = spm('FontSizes'); + +Finter = gcf; +hReg = uicontrol(Finter,'Style','Frame','Position',[60 100 300 300].*WS,... + 'Visible','off'); +[hReg,xyz] = spm_XYZreg('InitReg',hReg,xSPM.M,xSPM.DIM,[0;0;0]); + +hFxyz = spm_results_ui('DrawXYZgui',xSPM.M,xSPM.DIM,xSPM,xyz,Finter); +spm_XYZreg('XReg',hReg,hFxyz,'spm_results_ui'); + +hMIPax =gca ; +setcolormap('gray'); + +hMIPax = spm_mip_ui(xSPM.Z,coor',xSPM.M,xSPM.DIM,hMIPax); +spm_XYZreg('XReg',hReg,hMIPax,'spm_mip_ui'); + +colormap(handles.colormap); +setcolormap('gray-hot'); + +if isempty(handles.M) + spm_XYZreg('SetReg',handles.structureEdit,hReg); +else + set(handles.structureEdit, 'UserData', struct('hReg',hReg,'M',handles.M,'D', handles.DIM,'xyx',[0 0 0])); +end + +spm_XYZreg('Add2Reg',hReg,handles.structureEdit,@CallBack_structureEdit); + +%%%%%%%%%%%%%%% Reg end %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +if multiple == 0 + [hgraph, hSection, hcolorbar] = spm_sections(SPM,hReg,targetFile,handles.sectionViewPosition); +else + [hgraph, hSection, hcolorbar] = spm_sections(cSPM,hReg,targetFile,handles.sectionViewPosition); +end + +return + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% spm_sections +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function [Fgraph, hMe, hcolorbar] = spm_sections(SPM,hReg,targetFile,sectionViewPosition) +% rendering of regional effects [SPM{Z}] on orthogonal sections +% FORMAT spm_sections(SPM,hReg) +% +% SPM - xSPM structure containing details of excursion set +% hReg - handle of MIP register +% +% see spm_getSPM for details +%_______________________________________________________________________ +% +% spm_sections is called by spm_results and uses variables in SPM and +% VOL to create three orthogonal sections though a background image. +% Regional foci from the selected SPM are rendered on this image. +% +%_______________________________________________________________________ +% @(#)spm_sections.m 2.14 John Ashburner 02/09/05 + +Fgraph = gcf; + +spms = fullfile(spm('dir'),'canonical', 'single_subj_T1.mnc'); +if exist('targetFile') + spms = targetFile; +end + +spm_orthviews('Reset'); +global st +st.fig = Fgraph; +st.Space = spm_matrix([0 0 0 0 0 -pi/2])*st.Space; + +spm_orthviews('Image',spms,sectionViewPosition); % position +spm_orthviews('MaxBB'); +spm_orthviews('register',hReg); +if ~iscell(SPM) + spm_orthviews('addblobs',1,SPM.XYZ,SPM.Z,SPM.M); +elseif length(SPM) == 1 + SPM = SPM{1}; + spm_orthviews('addblobs',1,SPM.XYZ,SPM.Z,SPM.M); +else + colors = mycolourset; + for ii=1:length(SPM) + spm_orthviews('addcolouredblobs',1,SPM{ii}.XYZ, SPM{ii}.Z, SPM{ii}.M, colors(ii,:)); + end +end +spm_orthviews('Redraw'); + +hMe = st.registry.hMe; +try + hcolorbar = st.vols{1}.blobs{1}.cbar; +catch + hcolorbar = 0; +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% cuixuRenderView +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function h = cuixuRenderView(mniCoord, intensity, varargin) +% function h = cuixuRenderView(mniCoord1, intensity1, mniCoord2, intensity2, mniCoord3, intensity3) +% This is to project your coordinate to render view +% +% mniCoord: the mni coordinate, N*3 matrix +% intensity: the plot intensity of the spots, 1*N matrix. The intensity +% could be t value, for example. +% +% You can input 1, 2, or 3 pairs of coordinates and intensity. +% +% h: the returned handle for the figure +% +% Xu Cui +% 2004/11/11 + +global M_; +global DIM_; + +if nargin < 3 + dat.XYZ = mni2cor(mniCoord, M_); + dat.XYZ = dat.XYZ'; + if nargin < 2 + dat.t = ones(size(mniCoord,1),1); + else + dat.t = intensity; + end + dat.mat = M_; + dat.dim = DIM_; +else + if mod(nargin,2) ~=0 + disp('You should put even number of parameters.') + return; + end + for ii=1:(2+length(varargin))/2 + if ii==1 + dat(ii).XYZ = mni2cor(mniCoord, M_); + dat(ii).t = intensity; + else + dat(ii).XYZ = mni2cor(varargin{2*(ii-1)-1}, M_); + dat(ii).t = varargin{2*(ii-1)}; + end + dat(ii).XYZ = dat(ii).XYZ'; + dat(ii).mat = M_; + dat(ii).dim = DIM_; + end +end + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% spm_render +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +h = spm_render(dat,1,fullfile(spm('dir'), 'rend', 'render_single_subj.mat')); +return + +function Fgraph = spm_render(dat,brt,rendfile) +% Render blobs on surface of a 'standard' brain +% FORMAT spm_render(dat,brt,rendfile) +% +% dat - a vertical cell array of length 1 to 3 +% - each element is a structure containing: +% - XYZ - the x, y & z coordinates of the transformed t values. +% in units of voxels. +% - t - the SPM{.} values +% - mat - affine matrix mapping from XYZ voxels to Talairach. +% - dim - dimensions of volume from which XYZ is drawn. +% brt - brightness control: +% If NaN, then displays using the old style with hot +% metal for the blobs, and grey for the brain. +% Otherwise, it is used as a ``gamma correction'' to +% optionally brighten the blobs up a little. +% rendfile - the file containing the images to render on to. See also +% spm_xbrain.m. +% +% Without arguments, spm_render acts as its own UI. +%_______________________________________________________________________ +% +% spm_render prompts for details of up to three SPM{Z}s or SPM{t}s that +% are then displayed superimposed on the surface of a standard brain. +% +% The first is shown in red, then green then blue. +% +% The blobs which are displayed are the integral of all transformed t +% values, exponentially decayed according to their depth. Voxels that +% are 10mm behind the surface have half the intensity of ones at the +% surface. +%_______________________________________________________________________ +% @(#)spm_render.m 2.19 John Ashburner FIL 02/10/29 + +%-Parse arguments, get data if not passed as parameters +%======================================================================= +if nargin < 1 + SPMid = spm('FnBanner',mfilename,'2.19'); + [Finter,Fgraph,CmdLine] = spm('FnUIsetup','Results: render',0); + + num = spm_input('Number of sets',1,'1 set|2 sets|3 sets',[1 2 3]); + + for i = 1:num, + [SPM,VOL] = spm_getSPM; + dat(i) = struct( 'XYZ', VOL.XYZ,... + 't', VOL.Z',... + 'mat', VOL.M,... + 'dim', VOL.DIM); + end; + showbar = 1; +else, + num = length(dat); + showbar = 0; +end; + +% get surface +%----------------------------------------------------------------------- +if nargin < 3, + rendfile = spm_get(1,'render*.mat','Render file',fullfile(spm('Dir'),'rend')); +end; + +% get brightness +%----------------------------------------------------------------------- +if nargin < 2, + brt = 1; + if num==1, + brt = spm_input('Style',1,'new|old',[1 NaN], 1); + end; + if finite(brt), + brt = spm_input('Brighten blobs',1,'none|slightly|more|lots',[1 0.75 0.5 0.25], 1); + end; +end; + + + +% Perform the rendering +%======================================================================= +spm('Pointer','Watch') + +load(rendfile); + +if (exist('rend') ~= 1), % Assume old format... + rend = cell(size(Matrixes,1),1); + for i=1:size(Matrixes,1), + rend{i}=struct('M',eval(Matrixes(i,:)),... + 'ren',eval(Rens(i,:)),... + 'dep',eval(Depths(i,:))); + rend{i}.ren = rend{i}.ren/max(max(rend{i}.ren)); + end; +end; + +if showbar, spm_progress_bar('Init', size(dat,1)*length(rend),... + 'Formatting Renderings', 'Number completed'); end; +for i=1:length(rend), + rend{i}.max=0; + rend{i}.data = cell(size(dat,1),1); + if issparse(rend{i}.ren), + % Assume that images have been DCT compressed + % - the SPM99 distribution was originally too big. + d = size(rend{i}.ren); + B1 = spm_dctmtx(d(1),d(1)); + B2 = spm_dctmtx(d(2),d(2)); + rend{i}.ren = B1*rend{i}.ren*B2'; + % the depths did not compress so well with + % a straight DCT - therefore it was modified slightly + rend{i}.dep = exp(B1*rend{i}.dep*B2')-1; + end; + msk = find(rend{i}.ren>1);rend{i}.ren(msk)=1; + msk = find(rend{i}.ren<0);rend{i}.ren(msk)=0; + if showbar, spm_progress_bar('Set', i); end; +end; +if showbar, spm_progress_bar('Clear'); end; + +if showbar, spm_progress_bar('Init', length(dat)*length(rend),... + 'Making pictures', 'Number completed'); end; + +mx = zeros(length(rend),1)+eps; +mn = zeros(length(rend),1); + +for j=1:length(dat), + XYZ = dat(j).XYZ; + t = dat(j).t; + dim = dat(j).dim; + mat = dat(j).mat; + + for i=1:length(rend), + + % transform from Taliarach space to space of the rendered image + %------------------------------------------------------- + M1 = rend{i}.M*dat(j).mat; + zm = sum(M1(1:2,1:3).^2,2).^(-1/2); + M2 = diag([zm' 1 1]); + M = M2*M1; + cor = [1 1 1 ; dim(1) 1 1 ; 1 dim(2) 1; dim(1) dim(2) 1 ; + 1 1 dim(3) ; dim(1) 1 dim(3) ; 1 dim(2) dim(3); dim(1) dim(2) dim(3)]'; + tcor= M(1:3,1:3)*cor + M(1:3,4)*ones(1,8); + off = min(tcor(1:2,:)'); + M2 = spm_matrix(-off+1)*M2; + M = M2*M1; + xyz = (M(1:3,1:3)*XYZ + M(1:3,4)*ones(1,size(XYZ,2))); + d2 = ceil(max(xyz(1:2,:)')); + + % calculate 'depth' of values + %------------------------------------------------------- + dep = spm_slice_vol(rend{i}.dep,spm_matrix([0 0 1])*inv(M2),d2,1); + z1 = dep(round(xyz(1,:))+round(xyz(2,:)-1)*size(dep,1)); + + if ~finite(brt), msk = find(xyz(3,:) < (z1+20) & xyz(3,:) > (z1-5)); + else, msk = find(xyz(3,:) < (z1+60) & xyz(3,:) > (z1-5)); end; + + if ~isempty(msk), + + % generate an image of the integral of the blob values. + %----------------------------------------------- + xyz = xyz(:,msk); + if ~finite(brt), t0 = t(msk); + else, dst = xyz(3,:) - z1(msk); + dst = max(dst,0); + t0 = t(msk).*exp((log(0.5)/10)*dst)'; + end; + X0 = full(sparse(round(xyz(1,:)), round(xyz(2,:)), t0, d2(1), d2(2))); + hld = 1; if ~finite(brt), hld = 0; end; + X = spm_slice_vol(X0,spm_matrix([0 0 1])*M2,size(rend{i}.dep),hld); + msk = find(X<0); + X(msk) = 0; + else, + X = zeros(size(rend{i}.dep)); + end; + + % Brighten the blobs + if finite(brt), X = X.^brt; end; + + mx(j) = max([mx(j) max(max(X))]); + mn(j) = min([mn(j) min(min(X))]); + + rend{i}.data{j} = X; + + if showbar, spm_progress_bar('Set', i+(j-1)*length(rend)); end; + end; +end; + +mxmx = max(mx); +mnmn = min(mn); + +if showbar, spm_progress_bar('Clear'); end; +Fgraph = gcf;%spm_figure('GetWin','Graphics'); +%spm_results_ui('Clear',Fgraph); + +nrow = ceil(length(rend)/2); +hght = 0.25; +width = 0.25; +x0 = 0.5; +y0 = 0.01; +% subplot('Position',[0, 0, 1, hght]); +ax=axes('Parent',Fgraph,'units','normalized','Position',[0, 0, 0.5, hght],'Visible','off'); +%ax=axes; +%image(0,'Parent',ax); +set(ax,'YTick',[],'XTick',[]); + +if ~finite(brt), + % Old style split colourmap display. + %--------------------------------------------------------------- + load Split; + colormap(split); + for i=1:length(rend), + ren = rend{i}.ren; + X = (rend{i}.data{1}-mnmn)/(mxmx-mnmn); + msk = find(X); + ren(msk) = X(msk)+(1+1.51/64); + ax=axes('Parent',Fgraph,'units','normalized',... + 'Position',[x0+rem(i-1,2)*width, y0+floor((i-1)/2)*hght/nrow, width, hght/nrow],... + 'Visible','off'); + image(ren*64,'Parent',ax); + set(ax,'DataAspectRatio',[1 1 1], ... + 'PlotBoxAspectRatioMode','auto',... + 'YTick',[],'XTick',[],'XDir','normal','YDir','normal'); + end; +else, + % Combine the brain surface renderings with the blobs, and display using + % 24 bit colour. + %--------------------------------------------------------------- + for i=1:length(rend), + ren = rend{i}.ren; + X = cell(3,1); + for j=1:length(rend{i}.data), + X{j} = rend{i}.data{j}/(mxmx-mnmn)-mnmn; + end + for j=(length(rend{i}.data)+1):3 + X{j}=zeros(size(X{1})); + end + + rgb = zeros([size(ren) 3]); + tmp = ren.*max(1-X{1}-X{2}-X{3},0); + rgb(:,:,1) = tmp + X{1}; + rgb(:,:,2) = tmp + X{2}; + rgb(:,:,3) = tmp + X{3}; + + ax=axes('Parent',Fgraph,'units','normalized',... + 'Position',[x0+rem(i-1,2)*width, y0+floor((i-1)/2)*hght/nrow*2, width, hght/nrow*2],... + 'Visible','off'); + image(rgb,'Parent',ax); + set(ax,'DataAspectRatio',[1 1 1], ... + 'PlotBoxAspectRatioMode','auto',... + 'YTick',[],'XTick',[],... + 'XDir','normal','YDir','normal'); + end; +end; + +spm('Pointer') +return; + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% spm_list +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function varargout = spm_list(varargin) +% Display and analysis of SPM{.} +% FORMAT TabDat = spm_list('List',SPM,hReg,[Num,Dis,Str]) +% Summary list of local maxima for entire volume of interest +% FORMAT TabDat = spm_list('ListCluster',SPM,hReg,[Num,Dis,Str]) +% List of local maxima for a single suprathreshold cluster +% +% SPM - structure containing SPM, distribution & filtering details +% - required fields are: +% .swd - SPM working directory - directory containing current SPM.mat +% .Z - minimum of n Statistics {filtered on u and k} +% .n - number of conjoint tests +% .STAT - distribution {Z, T, X or F} +% .df - degrees of freedom [df{interest}, df{residual}] +% .u - height threshold +% .k - extent threshold {voxels} +% .XYZ - location of voxels {voxel coords} +% .XYZmm - location of voxels {mm} +% .S - search Volume {voxels} +% .R - search Volume {resels} +% .FWHM - smoothness {voxels} +% .M - voxels - > mm matrix +% .VOX - voxel dimensions {mm} +% .Vspm - mapped statistic image(s) +% .Ps - uncorrected P values in searched volume (for FDR) +% +% (see spm_getSPM for further details of xSPM structures) +% +% hReg - Handle of results section XYZ registry (see spm_results_ui.m) +% +% Num - number of maxima per cluster +% Dis - distance among clusters (mm) +% Str - header string +% +% TabDat - Structure containing table data +% - fields are +% .tit - table Title (string) +% .hdr - table header (2x11 cell array) +% .fmt - fprintf format strings for table data (1x11 cell array) +% .str - table filtering note (string) +% .ftr - table footnote information (4x2 cell array) +% .dat - table data (Nx11 cell array) +% +% ---------------- +% +% FORMAT spm_list('TxtList',TabDat,c) +% Prints a tab-delimited text version of the table +% TabDat - Structure containing table data (format as above) +% c - Column of table data to start text table at +% (E.g. c=3 doesn't print set-level results contained in columns 1 & 2) +% ---------------- +% +% FORMAT spm_list('SetCoords',xyz,hAx,hC) +% Highlighting of table co-ordinates (used by results section registry) +% xyz - 3-vector of new co-ordinate +% hAx - table axis (the registry object for tables) +% hReg - Handle of caller (not used) +%_______________________________________________________________________ +% +% spm_list characterizes SPMs (thresholded at u and k) in terms of +% excursion sets (a collection of face, edge and vertex connected +% subsets or clusters). The currected significance of the results are +% based on set, cluster and voxel-level inferences using distributional +% approximations from the Theory of Gaussian Fields. These +% distributions assume that the SPM is a reasonable lattice +% approximation of a continuous random field with known component field +% smoothness. +% +% The p values are based on the probability of obtaining c, or more, +% clusters of k, or more, resels above u, in the volume S analysed = +% P(u,k,c). For specified thresholds u, k, the set-level inference is +% based on the observed number of clusters C, = P(u,k,C). For each +% cluster of size K the cluster-level inference is based on P(u,K,1) +% and for each voxel (or selected maxima) of height U, in that cluster, +% the voxel-level inference is based on P(U,0,1). All three levels of +% inference are supported with a tabular presentation of the p values +% and the underlying statistic: +% +% Set-level - c = number of suprathreshold clusters +% - P = prob(c or more clusters in the search volume) +% +% Cluster-level - k = number of voxels in this cluster +% - Pc = prob(k or more voxels in the search volume) +% - Pu = prob(k or more voxels in a cluster) +% +% Voxel-level - T/F = Statistic upon which the SPM is based +% - Ze = The eqivalent Z score - prob(Z > Ze) = prob(t > T) +% - Pc = prob(Ze or higher in the search volume) +% - Qu = Expd(Prop of false positives among voxels >= Ze) +% - Pu = prob(Ze or higher at that voxel) +% +% x,y,z (mm) - Coordinates of the voxel +% +% The table is grouped by regions and sorted on the Ze-variate of the +% primary maxima. Ze-variates (based on the uncorrected p value) are the +% Z score equivalent of the statistic. Volumes are expressed in voxels. +% +% Clicking on values in the table returns the value to the Matlab +% workspace. In addition, clicking on the co-ordinates jumps the +% results section cursor to that location. The table has a context menu +% (obtained by right-clicking in the background of the table), +% providing options to print the current table as a text table, or to +% extract the table data to the Matlab workspace. +% +%_______________________________________________________________________ +% @(#)spm_list.m 2.43 Karl Friston, Andrew Holmes 02/10/31 + + +% satellite figure global variable +%----------------------------------------------------------------------- +global SatWindow + +%======================================================================= +switch lower(varargin{1}), case 'list' %-List + %======================================================================= + % FORMAT TabDat = spm_list('list',SPM,hReg) + + %-Tolerance for p-value underflow, when computing equivalent Z's + %----------------------------------------------------------------------- + tol = eps*10; + + %-Parse arguments and set maxima number and separation + %----------------------------------------------------------------------- + if nargin < 2, error('insufficient arguments'), end + if nargin < 3, hReg = []; else, hReg = varargin{3}; end + + + %-Get current location (to highlight selected voxel in table) + %----------------------------------------------------------------------- + %xyzmm = spm_results_ui('GetCoords'); + xyzmm = [0 0 0]'; + + %-Extract data from xSPM + %----------------------------------------------------------------------- + S = varargin{2}.S; + R = varargin{2}.R; + FWHM = varargin{2}.FWHM; + VOX = varargin{2}.VOX; + n = varargin{2}.n; + STAT = varargin{2}.STAT; + df = varargin{2}.df; + u = varargin{2}.u; + M = varargin{2}.M; + v2r = 1/prod(FWHM(~isinf(FWHM))); %-voxels to resels + k = varargin{2}.k*v2r; + QPs = varargin{2}.Ps; % Needed for FDR + QPs = sort(QPs(:)); + + %-get number and separation for maxima to be reported + %----------------------------------------------------------------------- + if length(varargin) > 3 + + Num = varargin{4}; % number of maxima per cluster + Dis = varargin{5}; % distance among clusters (mm) + else + Num = 3; + Dis = 8; + end + if length(varargin) > 5 + + Title = varargin{6}; + else + Title = 'p-values adjusted for search volume'; + end + + + %-Setup graphics panel + %----------------------------------------------------------------------- + spm('Pointer','Watch') + if SatWindow + Fgraph = SatWindow; + figure(Fgraph); + else + Fgraph = figure('unit','normalized','position',[0.5,0.1,0.55,0.5],'Color','w',... + 'Name','volume', 'NumberTitle','off','resize','off','MenuBar','none'); + Fgraph = gcf; + end + %spm_results_ui('Clear',Fgraph) + FS = spm('FontSizes'); %-Scaled font sizes + PF = spm_platform('fonts'); %-Font names (for this platform) + + + %-Table header & footer + %======================================================================= + + %-Table axes & Title + %---------------------------------------------------------------------- + if SatWindow, ht = 0.85; bot = .14; else, ht = 0.8; bot = 0.15; end; + + if STAT == 'P' + Title = 'Posterior Probabilities'; + end + + hAx = axes('Position',[0.025 bot 0.9 ht],... + 'DefaultTextFontSize',FS(8),... + 'DefaultTextInterpreter','Tex',... + 'DefaultTextVerticalAlignment','Baseline',... + 'Units','points',... + 'Visible','off'); + + AxPos = get(hAx,'Position'); set(hAx,'YLim',[0,AxPos(4)]) + dy = FS(9); + y = floor(AxPos(4)) - dy; + + text(0,y,['Statistics: \it\fontsize{',num2str(FS(9)),'}',Title],... + 'FontSize',FS(11),'FontWeight','Bold'); y = y - dy/2; + line([0 1],[y y],'LineWidth',3,'Color','r'), y = y - 9*dy/8; + + + %-Construct table header + %----------------------------------------------------------------------- + set(gca,'DefaultTextFontName',PF.helvetica,'DefaultTextFontSize',FS(8)) + + Hc = []; + Hp = []; + h = text(0.01,y, 'set-level','FontSize',FS(9)); Hc = [Hc,h]; + h = line([0,0.11],[1,1]*(y-dy/4),'LineWidth',0.5,'Color','r'); Hc = [Hc,h]; + h = text(0.08,y-9*dy/8, '\itc '); Hc = [Hc,h]; + h = text(0.02,y-9*dy/8, '\itp '); Hc = [Hc,h]; + Hp = [Hp,h]; + text(0.22,y, 'cluster-level','FontSize',FS(9)); + line([0.15,0.41],[1,1]*(y-dy/4),'LineWidth',0.5,'Color','r'); + h = text(0.16,y-9*dy/8, '\itp \rm_{corrected}'); Hp = [Hp,h]; + h = text(0.33,y-9*dy/8, '\itp \rm_{uncorrected}'); Hp = [Hp,h]; + h = text(0.26,y-9*dy/8, '\itk \rm_E'); + + text(0.60,y, 'voxel-level','FontSize',FS(9)); + line([0.46,0.86],[1,1]*(y-dy/4),'LineWidth',0.5,'Color','r'); + h = text(0.46,y-9*dy/8, '\itp \rm_{FWE-corr}'); Hp = [Hp,h]; + h = text(0.55,y-9*dy/8, '\itp \rm_{FDR-corr}'); Hp = [Hp,h]; + h = text(0.79,y-9*dy/8, '\itp \rm_{uncorrected}'); Hp = [Hp,h]; + h = text(0.64,y-9*dy/8, sprintf('\\it%c',STAT)); + h = text(0.72,y-9*dy/8, '(\itZ\rm_\equiv)'); + + text(0.93,y - dy/2,['x,y,z \fontsize{',num2str(FS(8)),'}\{mm\}']); + + + %-Headers for text table... + %----------------------------------------------------------------------- + TabDat.tit = Title; + TabDat.hdr = { 'set', 'c';... + 'set', 'p';... + 'cluster', 'p(cor)';... + 'cluster', 'equivk';... + 'cluster', 'p(unc)';... + 'voxel', 'p(FWE-cor)';... + 'voxel', 'p(FDR-cor)';... + 'voxel', STAT;... + 'voxel', 'equivZ';... + 'voxel', 'p(unc)';... + '', 'x,y,z {mm}'}';... + + TabDat.fmt = { '%-0.3f','%g',... %-Set + '%0.3f', '%0.0f', '%0.3f',... %-Cluster + '%0.3f', '%0.3f', '%6.2f', '%5.2f', '%0.3f',... %-Voxel + '%3.0f %3.0f %3.0f'}; %-XYZ + + %-Column Locations + %----------------------------------------------------------------------- + tCol = [ 0.00 0.07 ... %-Set + 0.16 0.26 0.34 ... %-Cluster + 0.46 0.55 0.62 0.71 0.80 ...%-Voxel + 0.92]; %-XYZ + + % move to next vertial postion marker + %----------------------------------------------------------------------- + y = y - 7*dy/4; + line([0 1],[y y],'LineWidth',1,'Color','r') + y = y - 5*dy/4; + y0 = y; + + + %-Table filtering note + %----------------------------------------------------------------------- + if isinf(Num) + TabDat.str = sprintf('table shows all local maxima > %.1fmm apart',Dis); + else + TabDat.str = sprintf(['table shows %d local maxima ',... + 'more than %.1fmm apart'],Num,Dis); + end + text(0.5,4,TabDat.str,'HorizontalAlignment','Center','FontName',PF.helvetica,... + 'FontSize',FS(8),'FontAngle','Italic') + + + %-Volume, resels and smoothness (if classical inference) + %----------------------------------------------------------------------- + line([0 1],[0 0],'LineWidth',1,'Color','r') + if STAT ~= 'P' + %----------------------------------------------------------------------- + FWHMmm = FWHM.*VOX; % FWHM {mm} + Pz = spm_P(1,0,u,df,STAT,1,n,S); + Pu = spm_P(1,0,u,df,STAT,R,n,S); + Qu = spm_P_FDR(u,df,STAT,n,QPs); + [P Pn Em En EN] = spm_P(1,k,u,df,STAT,R,n,S); + + + %-Footnote with SPM parameters + %----------------------------------------------------------------------- + set(gca,'DefaultTextFontName',PF.helvetica,... + 'DefaultTextInterpreter','None','DefaultTextFontSize',FS(8)) + TabDat.ftr = cell(5,2); + TabDat.ftr{1} = ... + sprintf('Height threshold: %c = %0.2f, p = %0.3f (%0.3f)',... + STAT,u,Pz,Pu); + TabDat.ftr{2} = ... + sprintf('Extent threshold: k = %0.0f voxels, p = %0.3f (%0.3f)',... + k/v2r,Pn,P); + TabDat.ftr{3} = ... + sprintf('Expected voxels per cluster, = %0.3f',En/v2r); + TabDat.ftr{4} = ... + sprintf('Expected number of clusters, = %0.2f',Em*Pn); + TabDat.ftr{5} = ... + sprintf('Expected false discovery rate, <= %0.2f',Qu); + TabDat.ftr{6} = ... + sprintf('Degrees of freedom = [%0.1f, %0.1f]',df); + TabDat.ftr{7} = ... + sprintf(['Smoothness FWHM = %0.1f %0.1f %0.1f {mm} ',... + ' = %0.1f %0.1f %0.1f {voxels}'],FWHMmm,FWHM); + TabDat.ftr{8} = ... + sprintf('Search vol: %0.0f cmm; %0.0f voxels; %0.1f resels',S*prod(VOX),S,R(end)); + TabDat.ftr{9} = ... + sprintf(['Voxel size: [%0.1f, %0.1f, %0.1f] mm ',... + ' (1 resel = %0.2f voxels)'],VOX,prod(FWHM)); + + text(0.0,-1*dy,TabDat.ftr{1},... + 'UserData',[u,Pz,Pu,Qu],'ButtonDownFcn','get(gcbo,''UserData'')') + text(0.0,-2*dy,TabDat.ftr{2},... + 'UserData',[k/v2r,Pn,P],'ButtonDownFcn','get(gcbo,''UserData'')') + text(0.0,-3*dy,TabDat.ftr{3},... + 'UserData',En/v2r,'ButtonDownFcn','get(gcbo,''UserData'')') + text(0.0,-4*dy,TabDat.ftr{4},... + 'UserData',Em*Pn,'ButtonDownFcn','get(gcbo,''UserData'')') + text(0.0,-5*dy,TabDat.ftr{5},... + 'UserData',Qu,'ButtonDownFcn','get(gcbo,''UserData'')') + text(0.5,-1*dy,TabDat.ftr{6},... + 'UserData',df,'ButtonDownFcn','get(gcbo,''UserData'')') + text(0.5,-2*dy,TabDat.ftr{7},... + 'UserData',FWHMmm,'ButtonDownFcn','get(gcbo,''UserData'')') + text(0.5,-3*dy,TabDat.ftr{8},... + 'UserData',[S*prod(VOX),S,R(end)],... + 'ButtonDownFcn','get(gcbo,''UserData'')') + text(0.5,-4*dy,TabDat.ftr{9},... + 'UserData',[VOX,prod(FWHM)],... + 'ButtonDownFcn','get(gcbo,''UserData'')') + + end % Classical + + + %-Characterize excursion set in terms of maxima + % (sorted on Z values and grouped by regions) + %======================================================================= + if ~length(varargin{2}.Z) + text(0.5,y-6*dy,'no suprathreshold clusters',... + 'HorizontalAlignment','Center',... + 'FontAngle','Italic','FontWeight','Bold',... + 'FontSize',FS(16),'Color',[1,1,1]*.5); + TabDat.dat = cell(0,11); + varargout = {TabDat}; + spm('Pointer','Arrow') + return + end + + % Includes Darren Gitelman's code for working around + % spm_max for conjunctions with negative thresholds + %----------------------------------------------------------------------- + minz = abs(min(min(varargin{2}.Z))); + zscores = 1 + minz + varargin{2}.Z; + [N Z XYZ A] = spm_max(zscores,varargin{2}.XYZ); + Z = Z - minz - 1; + + %-Convert cluster sizes from voxels to resels + %----------------------------------------------------------------------- + if isfield(varargin{2},'VRvp') + V2R = spm_get_data(varargin{2}.VRvp,XYZ); + else + V2R = v2r; + end + N = N.*V2R; + + %-Convert maxima locations from voxels to mm + %----------------------------------------------------------------------- + XYZmm = M(1:3,:)*[XYZ; ones(1,size(XYZ,2))]; + + + + %-Table proper (& note all data in cell array) + %======================================================================= + + %-Pagination variables + %----------------------------------------------------------------------- + hPage = []; + set(gca,'DefaultTextFontName',PF.courier,'DefaultTextFontSize',FS(7)) + + + %-Set-level p values {c} - do not display if reporting a single cluster + %----------------------------------------------------------------------- + c = max(A); %-Number of clusters + if STAT ~= 'P' + Pc = spm_P(c,k,u,df,STAT,R,n,S); %-Set-level p-value + else + Pc = []; + set(Hp,'Visible','off') + end + + if c > 1; + h = text(tCol(1),y,sprintf(TabDat.fmt{1},Pc),'FontWeight','Bold',... + 'UserData',Pc,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + h = text(tCol(2),y,sprintf(TabDat.fmt{2},c),'FontWeight','Bold',... + 'UserData',c,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + else + set(Hc,'Visible','off') + end + + TabDat.dat = {Pc,c}; %-Table data + TabLin = 1; %-Table data line + + + %-Local maxima p-values & statistics + %----------------------------------------------------------------------- + HlistXYZ = []; + while prod(size(find(finite(Z)))) + + % Paginate if necessary + %--------------------------------------------------------------- + if y < min(Num + 1,3)*dy + + % added Fgraph term to paginate on Satellite window + %------------------------------------------------------- + h = text(0.5,-5*dy,... + sprintf('Page %d',spm_figure('#page',Fgraph)),... + 'FontName',PF.helvetica,'FontAngle','Italic',... + 'FontSize',FS(8)); + + spm_figure('NewPage',[hPage,h]) + hPage = []; + y = y0; + end + + %-Find largest remaining local maximum + %--------------------------------------------------------------- + [U,i] = max(Z); % largest maxima + j = find(A == A(i)); % maxima in cluster + + + %-Compute cluster {k} and voxel-level {u} p values for this cluster + %--------------------------------------------------------------- + Nv = N(i)/v2r; % extent {voxels} + + + if STAT ~= 'P' + Pz = spm_P(1,0, U,df,STAT,1,n,S);% uncorrected p value + Pu = spm_P(1,0, U,df,STAT,R,n,S);% FWE-corrected {based on Z) + Qu = spm_P_FDR( U,df,STAT,n,QPs);% FDR-corrected {based on Z) + [Pk Pn] = spm_P(1,N(i),u,df,STAT,R,n,S);% [un]corrected {based on k) + + if Pz < tol % Equivalent Z-variate + Ze = Inf; % (underflow => can't compute) + else + Ze = spm_invNcdf(1 - Pz); + end + else + Pz = []; + Pu = []; + Qu = []; + Pk = []; + Pn = []; + Ze = spm_invNcdf(U); + end + + + %-Print cluster and maximum voxel-level p values {Z} + %--------------------------------------------------------------- + h = text(tCol(3),y,sprintf(TabDat.fmt{3},Pk),'FontWeight','Bold',... + 'UserData',Pk,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + + h = text(tCol(4),y,sprintf(TabDat.fmt{4},Nv),'FontWeight','Bold',... + 'UserData',Nv,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + h = text(tCol(5),y,sprintf(TabDat.fmt{5},Pn),'FontWeight','Bold',... + 'UserData',Pn,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + + h = text(tCol(6),y,sprintf(TabDat.fmt{6},Pu),'FontWeight','Bold',... + 'UserData',Pu,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + h = text(tCol(7),y,sprintf(TabDat.fmt{7},Qu),'FontWeight','Bold',... + 'UserData',Qu,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + h = text(tCol(8),y,sprintf(TabDat.fmt{8},U),'FontWeight','Bold',... + 'UserData',U,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + h = text(tCol(9),y,sprintf(TabDat.fmt{9},Ze),'FontWeight','Bold',... + 'UserData',Ze,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + h = ... + text(tCol(10),y,sprintf(TabDat.fmt{10},Pz),'FontWeight','Bold',... + 'UserData',Pz,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + + % Specifically changed so it properly finds hMIPax + %--------------------------------------------------------------------- + h = text(tCol(11),y,sprintf(TabDat.fmt{11},XYZmm(:,i)),... + 'FontWeight','Bold',... + 'Tag','ListXYZ',... + 'ButtonDownFcn',[... + 'hMIPax = findobj(''tag'',''hMIPax'');',... + 'spm_mip_ui(''SetCoords'',get(gcbo,''UserData''),hMIPax);'],... + 'Interruptible','off','BusyAction','Cancel',... + 'UserData',XYZmm(:,i)); + + HlistXYZ = [HlistXYZ, h]; + if spm_XYZreg('Edist',xyzmm,XYZmm(:,i)) Dis mm apart) + %--------------------------------------------------------------- + [l q] = sort(-Z(j)); % sort on Z value + D = i; + for i = 1:length(q) + d = j(q(i)); + if min(sqrt(sum((XYZmm(:,D)-XYZmm(:,d)*ones(1,size(D,2))).^2)))>Dis; + + if length(D) < Num + + % Paginate if necessary + %----------------------------------------------- + if y < dy + h = text(0.5,-5*dy,sprintf('Page %d',... + spm_figure('#page',Fgraph)),... + 'FontName',PF.helvetica,... + 'FontAngle','Italic',... + 'FontSize',FS(8)); + + spm_figure('NewPage',[hPage,h]) + hPage = []; + y = y0; + end + + % voxel-level p values {Z} + %----------------------------------------------- + if STAT ~= 'P' + Pz = spm_P(1,0,Z(d),df,STAT,1,n,S); + Pu = spm_P(1,0,Z(d),df,STAT,R,n,S); + Qu = spm_P_FDR(Z(d),df,STAT,n,QPs); + if Pz < tol + Ze = Inf; + else, Ze = spm_invNcdf(1 - Pz); end + else + Pz = []; + Pu = []; + Qu = []; + Ze = spm_invNcdf(Z(d)); + end + + h = text(tCol(6),y,sprintf(TabDat.fmt{6},Pu),... + 'UserData',Pu,... + 'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + + h = text(tCol(7),y,sprintf(TabDat.fmt{7},Qu),... + 'UserData',Qu,... + 'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + h = text(tCol(8),y,sprintf(TabDat.fmt{8},Z(d)),... + 'UserData',Z(d),... + 'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + h = text(tCol(9),y,sprintf(TabDat.fmt{9},Ze),... + 'UserData',Ze,... + 'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + h = text(tCol(10),y,sprintf(TabDat.fmt{10},Pz),... + 'UserData',Pz,... + 'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + + % specifically modified line to use hMIPax + %----------------------------------------------- + h = text(tCol(11),y,... + sprintf(TabDat.fmt{11},XYZmm(:,d)),... + 'Tag','ListXYZ',... + 'ButtonDownFcn',[... + 'hMIPax = findobj(''tag'',''hMIPax'');',... + 'spm_mip_ui(''SetCoords'',',... + 'get(gcbo,''UserData''),hMIPax);'],... + 'Interruptible','off','BusyAction','Cancel',... + 'UserData',XYZmm(:,d)); + + HlistXYZ = [HlistXYZ, h]; + if spm_XYZreg('Edist',xyzmm,XYZmm(:,d))1 + h = text(0.5,-5*dy,sprintf('Page %d/%d',spm_figure('#page',Fgraph)*[1,1]),... + 'FontName',PF.helvetica,'FontSize',FS(8),'FontAngle','Italic'); + spm_figure('NewPage',[hPage,h]) + end + + %-End: Store TabDat in UserData of axes & reset pointer + %======================================================================= + h = uicontextmenu('Tag','TabDat',... + 'UserData',TabDat); + set(gca,'UIContextMenu',h,... + 'Visible','on',... + 'XColor','w','YColor','w') + uimenu(h,'Label','Table') + uimenu(h,'Separator','on','Label','Print text table',... + 'Tag','TD_TxtTab',... + 'CallBack',... + 'spm_list(''txtlist'',get(get(gcbo,''Parent''),''UserData''),3)',... + 'Interruptible','off','BusyAction','Cancel'); + uimenu(h,'Separator','off','Label','Extract table data structure',... + 'Tag','TD_Xdat',... + 'CallBack','get(get(gcbo,''Parent''),''UserData'')',... + 'Interruptible','off','BusyAction','Cancel'); + uimenu(h,'Separator','on','Label','help',... + 'Tag','TD_Xdat',... + 'CallBack','spm_help(''spm_list'')',... + 'Interruptible','off','BusyAction','Cancel'); + + %-Setup registry + %----------------------------------------------------------------------- + set(hAx,'UserData',struct('hReg',hReg,'HlistXYZ',HlistXYZ)) + spm_XYZreg('Add2Reg',hReg,hAx,'spm_list'); + + %-Return TabDat structure & reset pointer + %----------------------------------------------------------------------- + varargout = {TabDat}; + spm('Pointer','Arrow') + + + + + + %======================================================================= + case 'listcluster' %-List for current cluster only + %======================================================================= + % FORMAT TabDat = spm_list('listcluster',SPM,hReg) + + spm('Pointer','Watch') + + %-Parse arguments + %----------------------------------------------------------------------- + if nargin < 2, error('insufficient arguments'), end + if nargin < 3, hReg = []; else, hReg = varargin{3}; end + SPM = varargin{2}; + + %-get number and separation for maxima to be reported + %----------------------------------------------------------------------- + if length(varargin) > 3 + + Num = varargin{4}; % number of maxima per cluster + Dis = varargin{5}; % distance among clusters (mm) + else + Num = 32; + Dis = 4; + end + + + %-if there are suprathreshold voxels, filter out all but current cluster + %----------------------------------------------------------------------- + if length(SPM.Z) + + %-Jump to voxel nearest current location + %-------------------------------------------------------------- + [xyzmm,i] = spm_XYZreg('NearestXYZ',... + spm_results_ui('GetCoords'),SPM.XYZmm); + spm_results_ui('SetCoords',SPM.XYZmm(:,i)); + + %-Find selected cluster + %-------------------------------------------------------------- + A = spm_clusters(SPM.XYZ); + j = find(A == A(i)); + SPM.Z = SPM.Z(j); + SPM.XYZ = SPM.XYZ(:,j); + SPM.XYZmm = SPM.XYZmm(:,j); + if isfield(SPM,'Rd'), SPM.Rd = SPM.Rd(:,j); end + end + + %-Call 'list' functionality to produce table + %----------------------------------------------------------------------- + varargout = {spm_list('list',SPM,hReg,Num,Dis)}; + + + + + + %======================================================================= + case 'txtlist' %-Print ASCII text table + %======================================================================= + % FORMAT spm_list('TxtList',TabDat,c) + + if nargin<2, error('Insufficient arguments'), end + if nargin<3, c=1; else, c=varargin{3}; end + TabDat = varargin{2}; + + %-Table Title + %----------------------------------------------------------------------- + fprintf('\n\nSTATISTICS: %s\n',TabDat.tit) + fprintf('%c','='*ones(1,80)), fprintf('\n') + + %-Table header + %----------------------------------------------------------------------- + fprintf('%s\t',TabDat.hdr{1,c:end-1}), fprintf('%s\n',TabDat.hdr{1,end}) + fprintf('%s\t',TabDat.hdr{2,c:end-1}), fprintf('%s\n',TabDat.hdr{2,end}) + fprintf('%c','-'*ones(1,80)), fprintf('\n') + + %-Table data + %----------------------------------------------------------------------- + for i = 1:size(TabDat.dat,1) + for j=c:size(TabDat.dat,2) + fprintf(TabDat.fmt{j},TabDat.dat{i,j}) + fprintf('\t') + end + fprintf('\n') + end + for i=1:max(1,11-size(TabDat.dat,1)), fprintf('\n'), end + fprintf('%s\n',TabDat.str) + fprintf('%c','-'*ones(1,80)), fprintf('\n') + + %-Table footer + %----------------------------------------------------------------------- + fprintf('%s\n',TabDat.ftr{:}) + fprintf('%c','='*ones(1,80)), fprintf('\n\n') + + + + %======================================================================= + case 'setcoords' %-Co-ordinate change + %======================================================================= + % FORMAT spm_list('SetCoords',xyz,hAx,hReg) + if nargin<3, error('Insufficient arguments'), end + hAx = varargin{3}; + xyz = varargin{2}; + UD = get(hAx,'UserData'); + HlistXYZ = UD.HlistXYZ(ishandle(UD.HlistXYZ)); + + %-Set all co-ord strings to black + %----------------------------------------------------------------------- + set(HlistXYZ,'Color','k') + + %-If co-ord matches a string, highlight it in red + %----------------------------------------------------------------------- + XYZ = get(HlistXYZ,'UserData'); + if iscell(XYZ), XYZ = cat(2,XYZ{:}); end + [null,i,d] = spm_XYZreg('NearestXYZ',xyz,XYZ); + if d=2, st.vols{H}.area = varargin{2}; end; + if isempty(st.bb), st.bb = maxbb; end; + bbox; + cm_pos; + end; + varargout{1} = H; + st.centre = mean(maxbb); + redraw_all + + case 'bb', + if length(varargin)> 0 & all(size(varargin{1})==[2 3]), st.bb = varargin{1}; end; + bbox; + redraw_all; + + case 'redraw', + redraw_all; + eval(st.callback); + if isfield(st,'registry'), + spm_XYZreg('SetCoords',st.centre,st.registry.hReg,st.registry.hMe); + end; + + case 'reposition', + if length(varargin)<1, tmp = findcent; + else, tmp = varargin{1}; end; + if length(tmp)==3 + h = valid_handles(st.snap); + if ~isempty(h) + tmp=st.vols{h(1)}.mat*... + round(inv(st.vols{h(1)}.mat)*[tmp; ... + 1]); + end; + st.centre = tmp(1:3); + end; + redraw_all; + eval(st.callback); + if isfield(st,'registry'), + spm_XYZreg('SetCoords',st.centre,st.registry.hReg,st.registry.hMe); + end; + cm_pos; + + case 'setcoords', + st.centre = varargin{1}; + st.centre = st.centre(:); + redraw_all; + eval(st.callback); + cm_pos; + + case 'space', + if length(varargin)<1, + st.Space = eye(4); + st.bb = maxbb; + bbox; + redraw_all; + else, + space(varargin{1}); + bbox; + redraw_all; + end; + + case 'maxbb', + st.bb = maxbb; + bbox; + redraw_all; + + case 'resolution', + resolution(varargin{1}); + bbox; + redraw_all; + + case 'window', + if length(varargin)<2, + win = 'auto'; + elseif length(varargin{2})==2, + win = varargin{2}; + end; + for i=valid_handles(varargin{1}), + st.vols{i}.window = win; + end; + redraw(varargin{1}); + + case 'delete', + my_delete(varargin{1}); + + case 'move', + move(varargin{1},varargin{2}); + % redraw_all; + + case 'reset', + my_reset; + + case 'pos', + if isempty(varargin), + H = st.centre(:); + else, + H = pos(varargin{1}); + end; + varargout{1} = H; + + case 'interp', + st.hld = varargin{1}; + redraw_all; + + case 'xhairs', + xhairs(varargin{1}); + + case 'register', + register(varargin{1}); + + case 'addblobs', + addblobs(varargin{1}, varargin{2},varargin{3},varargin{4}); + % redraw(varargin{1}); + + case 'addcolouredblobs', + addcolouredblobs(varargin{1}, varargin{2},varargin{3},varargin{4},varargin{5}); + % redraw(varargin{1}); + + case 'addimage', + addimage(varargin{1}, varargin{2}); + % redraw(varargin{1}); + + case 'addcolouredimage', + addcolouredimage(varargin{1}, varargin{2},varargin{3}); + % redraw(varargin{1}); + + case 'addtruecolourimage', + % spm_orthviews('Addtruecolourimage',handle,filename,colourmap,prop,mx,mn) + % Adds blobs from an image in true colour + % handle - image number to add blobs to [default 1] + % filename of image containing blob data [default - request via GUI] + % colourmap - colormap to display blobs in [GUI input] + % prop - intensity proportion of activation cf grayscale [0.4] + % mx - maximum intensity to scale to [maximum value in activation image] + % mn - minimum intensity to scale to [minimum value in activation image] + % + if nargin < 2 + varargin(1) = {1}; + end + if nargin < 3 + varargin(2) = {spm_select(1, 'image', 'Image with activation signal')}; + end + if nargin < 4 + actc = []; + while isempty(actc) + actc = getcmap(spm_input('Colourmap for activation image', '+1','s')); + end + varargin(3) = {actc}; + end + if nargin < 5 + varargin(4) = {0.4}; + end + if nargin < 6 + actv = spm_vol(varargin{2}); + varargin(5) = {max([eps maxval(actv)])}; + end + if nargin < 7 + varargin(6) = {min([0 minval(actv)])}; + end + + addtruecolourimage(varargin{1}, varargin{2},varargin{3}, varargin{4}, ... + varargin{5}, varargin{6}); + % redraw(varargin{1}); + + case 'addcolourbar', + addcolourbar(varargin{1}, varargin{2}); + + case 'rmblobs', + rmblobs(varargin{1}); + % redraw(varargin{1}); + + case 'addcontext', + if nargin == 1, + handles = 1:24; + else, + handles = varargin{1}; + end; + addcontexts(handles); + + case 'rmcontext', + if nargin == 1, + handles = 1:24; + else, + handles = varargin{1}; + end; + rmcontexts(handles); + + case 'context_menu', + c_menu(varargin{:}); + + case 'valid_handles', + if nargin == 1 + handles = 1:24; + else, + handles = varargin{1}; + end; + varargout{1} = valid_handles(handles); + + otherwise, + addonaction = strcmp(st.plugins,action); + if any(addonaction) + feval(['spm_ov_' st.plugins{addonaction}],varargin{:}); + else + warning('Unknown action string') + end; +end; + +spm('Pointer'); +return; + + +%_______________________________________________________________________ +%_______________________________________________________________________ +function addblobs(handle, xyz, t, mat) +global st +global TMAX_ +for i=valid_handles(handle), + if ~isempty(xyz), + rcp = round(xyz); + dim = max(rcp,[],2)'; + off = rcp(1,:) + dim(1)*(rcp(2,:)-1 + dim(2)*(rcp(3,:)-1)); + vol = zeros(dim)+NaN; + vol(off) = t; + vol = reshape(vol,dim); + st.vols{i}.blobs=cell(1,1); + if st.mode == 0, + axpos = get(st.vols{i}.ax{2}.ax,'Position'); + else, + axpos = get(st.vols{i}.ax{1}.ax,'Position'); + end; + mx = max([eps max(t)]); + mn = min([0 min(t)]); + if ~strcmp(TMAX_, 'auto') + mx = str2num(TMAX_); + end + %KND: + if numel(mx)==2 + mn=mx(1); + mx=mx(2); + end + st.vols{i}.blobs{1} = struct('vol',vol,'mat',mat,'max',mx, 'min',mn); + addcolourbar(handle,1); + end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function addimage(handle, fname) +global st +for i=valid_handles(handle), + if isstruct(fname), + vol = fname(1); + else, + vol = spm_vol(fname); + end; + mat = vol.mat; + st.vols{i}.blobs=cell(1,1); + mx = max([eps maxval(vol)]); + mn = min([0 minval(vol)]); + st.vols{i}.blobs{1} = struct('vol',vol,'mat',mat,'max',mx,'min',mn); + addcolourbar(handle,1); +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function addcolouredblobs(handle, xyz, t, mat,colour) +global st +for i=valid_handles(handle), + if ~isempty(xyz), + rcp = round(xyz); + dim = max(rcp,[],2)'; + off = rcp(1,:) + dim(1)*(rcp(2,:)-1 + dim(2)*(rcp(3,:)-1)); + vol = zeros(dim)+NaN; + vol(off) = t; + vol = reshape(vol,dim); + if ~isfield(st.vols{i},'blobs'), + st.vols{i}.blobs=cell(1,1); + bset = 1; + else, + bset = length(st.vols{i}.blobs)+1; + end; + mx = max([eps maxval(vol)]); + mn = min([0 minval(vol)]); + st.vols{i}.blobs{bset} = struct('vol',vol,'mat',mat,'max',mx,'min',mn,'colour',colour); + end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function addcolouredimage(handle, fname,colour) +global st +for i=valid_handles(handle), + if isstruct(fname), + vol = fname(1); + else, + vol = spm_vol(fname); + end; + mat = vol.mat; + if ~isfield(st.vols{i},'blobs'), + st.vols{i}.blobs=cell(1,1); + bset = 1; + else, + bset = length(st.vols{i}.blobs)+1; + end; + mx = max([eps maxval(vol)]); + mn = min([0 minval(vol)]); + st.vols{i}.blobs{bset} = struct('vol',vol,'mat',mat,'max',mx,'min',mn,'colour',colour); +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function addtruecolourimage(handle,fname,colourmap,prop,mx,mn) +% adds true colour image to current displayed image +global st +for i=valid_handles(handle), + if isstruct(fname), + vol = fname(1); + else, + vol = spm_vol(fname); + end; + mat = vol.mat; + if ~isfield(st.vols{i},'blobs'), + st.vols{i}.blobs=cell(1,1); + bset = 1; + else, + bset = length(st.vols{i}.blobs)+1; + end; + c = struct('cmap', colourmap,'prop',prop); + st.vols{i}.blobs{bset} = struct('vol',vol,'mat',mat,'max',mx, ... + 'min',mn,'colour',c); + addcolourbar(handle,bset); +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function addcolourbar(vh,bh) +global st +if st.mode == 0, + axpos = get(st.vols{vh}.ax{2}.ax,'Position'); +else, + axpos = get(st.vols{vh}.ax{1}.ax,'Position'); +end; +st.vols{vh}.blobs{bh}.cbar = axes('Parent',st.fig,... + 'Position',[(axpos(1)+axpos(3)+0.05+(bh-1)*.1) (axpos(2)+0.005) 0.05 (axpos(4)-0.01)],... + 'Box','on', 'YDir','normal', 'XTickLabel',[], 'XTick',[]); +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function rmblobs(handle) +global st +for i=valid_handles(handle), + if isfield(st.vols{i},'blobs'), + for j=1:length(st.vols{i}.blobs), + if isfield(st.vols{i}.blobs{j},'cbar') & ishandle(st.vols{i}.blobs{j}.cbar), + delete(st.vols{i}.blobs{j}.cbar); + end; + end; + st.vols{i} = rmfield(st.vols{i},'blobs'); + end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function register(hreg) +global st +tmp = uicontrol('Position',[0 0 1 1],'Visible','off','Parent',st.fig); +h = valid_handles(1:24); +if ~isempty(h), + tmp = st.vols{h(1)}.ax{1}.ax; + st.registry = struct('hReg',hreg,'hMe', tmp); + spm_XYZreg('Add2Reg',st.registry.hReg,st.registry.hMe, 'spm_orthviews'); +else, + warning('Nothing to register with'); +end; +st.centre = spm_XYZreg('GetCoords',st.registry.hReg); +st.centre = st.centre(:); +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function xhairs(arg1), +global st +st.xhairs = 0; +opt = 'on'; +if ~strcmp(arg1,'on'), + opt = 'off'; +else, + st.xhairs = 1; +end; +for i=valid_handles(1:24), + for j=1:3, + set(st.vols{i}.ax{j}.lx,'Visible',opt); + set(st.vols{i}.ax{j}.ly,'Visible',opt); + end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function H = pos(arg1) +global st +H = []; +for arg1=valid_handles(arg1), + is = inv(st.vols{arg1}.premul*st.vols{arg1}.mat); + H = is(1:3,1:3)*st.centre(:) + is(1:3,4); +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function my_reset +global st +if ~isempty(st) & isfield(st,'registry') & ishandle(st.registry.hMe), + delete(st.registry.hMe); st = rmfield(st,'registry'); +end; +my_delete(1:24); +reset_st; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function my_delete(arg1) +global st +for i=valid_handles(arg1), + kids = get(st.fig,'Children'); + for j=1:3, + if any(kids == st.vols{i}.ax{j}.ax), + set(get(st.vols{i}.ax{j}.ax,'Children'),'DeleteFcn',''); + delete(st.vols{i}.ax{j}.ax); + end; + end; + st.vols{i} = []; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function resolution(arg1) +global st +res = arg1/mean(svd(st.Space(1:3,1:3))); +Mat = diag([res res res 1]); +st.Space = st.Space*Mat; +st.bb = st.bb/res; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function move(handle,pos) +global st +for handle = valid_handles(handle), + st.vols{handle}.area = pos; +end; +bbox; +% redraw(valid_handles(handle)); +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function bb = maxbb +global st +mn = [Inf Inf Inf]; +mx = -mn; +for i=valid_handles(1:24), + bb = [[1 1 1];st.vols{i}.dim(1:3)]; + c = [ bb(1,1) bb(1,2) bb(1,3) 1 + bb(1,1) bb(1,2) bb(2,3) 1 + bb(1,1) bb(2,2) bb(1,3) 1 + bb(1,1) bb(2,2) bb(2,3) 1 + bb(2,1) bb(1,2) bb(1,3) 1 + bb(2,1) bb(1,2) bb(2,3) 1 + bb(2,1) bb(2,2) bb(1,3) 1 + bb(2,1) bb(2,2) bb(2,3) 1]'; + tc = st.Space\(st.vols{i}.premul*st.vols{i}.mat)*c; + tc = tc(1:3,:)'; + mx = max([tc ; mx]); + mn = min([tc ; mn]); +end; +bb = [mn ; mx]; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function space(arg1) +global st +if ~isempty(st.vols{arg1}) + num = arg1; + Mat = st.vols{num}.premul(1:3,1:3)*st.vols{num}.mat(1:3,1:3); + vox = sqrt(sum(Mat.^2)); + if det(Mat(1:3,1:3))<0, vox(1) = -vox(1); end; + Mat = diag([vox 1]); + Space = (st.vols{num}.mat)/Mat; + bb = [1 1 1;st.vols{num}.dim(1:3)]; + bb = [bb [1;1]]; + bb=bb*Mat'; + bb=bb(:,1:3); + bb=sort(bb); + st.Space = Space; + st.bb = bb; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function H = specify_image(arg1, arg2) +global st +H=[]; +ok = 1; +if isstruct(arg1), + V = arg1(1); +else, + try, + V = spm_vol(arg1); + catch, + fprintf('Can not use image "%s"\n', arg1); + return; + end; +end; + +ii = 1; +while ~isempty(st.vols{ii}), ii = ii + 1; end; + +DeleteFcn = ['spm_orthviews(''Delete'',' num2str(ii) ');']; +V.ax = cell(3,1); +for i=1:3, + ax = axes('Visible','off','DrawMode','fast','Parent',st.fig,'DeleteFcn',DeleteFcn,... + 'YDir','normal','ButtonDownFcn',... + ['if strcmp(get(gcf,''SelectionType''),''normal''),spm_orthviews(''Reposition'');',... + 'elseif strcmp(get(gcf,''SelectionType''),''extend''),spm_orthviews(''Reposition'');',... + 'spm_orthviews(''context_menu'',''ts'',1);end;']); + d = image(0,'Tag','Transverse','Parent',ax,... + 'DeleteFcn',DeleteFcn); + set(ax,'Ydir','normal','ButtonDownFcn',... + ['if strcmp(get(gcf,''SelectionType''),''normal''),spm_orthviews(''Reposition'');',... + 'elseif strcmp(get(gcf,''SelectionType''),''extend''),spm_orthviews(''reposition'');',... + 'spm_orthviews(''context_menu'',''ts'',1);end;']); + + lx = line(0,0,'Parent',ax,'DeleteFcn',DeleteFcn); + ly = line(0,0,'Parent',ax,'DeleteFcn',DeleteFcn); + if ~st.xhairs, + set(lx,'Visible','off'); + set(ly,'Visible','off'); + end; + V.ax{i} = struct('ax',ax,'d',d,'lx',lx,'ly',ly); +end; +V.premul = eye(4); +V.window = 'auto'; +V.mapping = 'linear'; +st.vols{ii} = V; + +H = ii; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function addcontexts(handles) +global st +for ii = valid_handles(handles), + cm_handle = addcontext(ii); + for i=1:3, + set(st.vols{ii}.ax{i}.ax,'UIcontextmenu',cm_handle); + st.vols{ii}.ax{i}.cm = cm_handle; + end; +end; +spm_orthviews('reposition',spm_orthviews('pos')); +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function rmcontexts(handles) +global st +for ii = valid_handles(handles), + for i=1:3, + set(st.vols{ii}.ax{i}.ax,'UIcontextmenu',[]); + st.vols{ii}.ax{i} = rmfield(st.vols{ii}.ax{i},'cm'); + end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function bbox +global st +Dims = diff(st.bb)'+1; + +TD = Dims([1 2])'; +CD = Dims([1 3])'; +if st.mode == 0, SD = Dims([3 2])'; else, SD = Dims([2 3])'; end; + +un = get(st.fig,'Units');set(st.fig,'Units','Pixels'); +sz = get(st.fig,'Position');set(st.fig,'Units',un); +sz = sz(3:4); +sz(2) = sz(2)-40; + +for i=valid_handles(1:24), + area = st.vols{i}.area(:); + area = [area(1)*sz(1) area(2)*sz(2) area(3)*sz(1) area(4)*sz(2)]; + if st.mode == 0, + sx = area(3)/(Dims(1)+Dims(3))/1.02; + else, + sx = area(3)/(Dims(1)+Dims(2))/1.02; + end; + sy = area(4)/(Dims(2)+Dims(3))/1.02; + s = min([sx sy]); + + offy = (area(4)-(Dims(2)+Dims(3))*1.02*s)/2 + area(2); + sky = s*(Dims(2)+Dims(3))*0.02; + if st.mode == 0, + offx = (area(3)-(Dims(1)+Dims(3))*1.02*s)/2 + area(1); + skx = s*(Dims(1)+Dims(3))*0.02; + else, + offx = (area(3)-(Dims(1)+Dims(2))*1.02*s)/2 + area(1); + skx = s*(Dims(1)+Dims(2))*0.02; + end; + + DeleteFcn = ['spm_orthviews(''Delete'',' num2str(i) ');']; + + % Transverse + set(st.vols{i}.ax{1}.ax,'Units','pixels', ... + 'Position',[offx offy s*Dims(1) s*Dims(2)],... + 'Units','normalized','Xlim',[0 TD(1)]+0.5,'Ylim',[0 TD(2)]+0.5,... + 'Visible','on','XTick',[],'YTick',[]); + + % Coronal + set(st.vols{i}.ax{2}.ax,'Units','Pixels',... + 'Position',[offx offy+s*Dims(2)+sky s*Dims(1) s*Dims(3)],... + 'Units','normalized','Xlim',[0 CD(1)]+0.5,'Ylim',[0 CD(2)]+0.5,... + 'Visible','on','XTick',[],'YTick',[]); + + % Sagittal + if st.mode == 0, + set(st.vols{i}.ax{3}.ax,'Units','Pixels', 'Box','on',... + 'Position',[offx+s*Dims(1)+skx offy s*Dims(3) s*Dims(2)],... + 'Units','normalized','Xlim',[0 SD(1)]+0.5,'Ylim',[0 SD(2)]+0.5,... + 'Visible','on','XTick',[],'YTick',[]); + else, + set(st.vols{i}.ax{3}.ax,'Units','Pixels', 'Box','on',... + 'Position',[offx+s*Dims(1)+skx offy+s*Dims(2)+sky s*Dims(2) s*Dims(3)],... + 'Units','normalized','Xlim',[0 SD(1)]+0.5,'Ylim',[0 SD(2)]+0.5,... + 'Visible','on','XTick',[],'YTick',[]); + end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function redraw_all +global st +redraw(1:24); +return; +%_______________________________________________________________________ +function mx = maxval(vol) +if isstruct(vol), + mx = -Inf; + for i=1:vol.dim(3), + tmp = spm_slice_vol(vol,spm_matrix([0 0 i]),vol.dim(1:2),0); + imx = max(tmp(find(finite(tmp)))); + if ~isempty(imx),mx = max(mx,imx);end + end; +else, + mx = max(vol(find(finite(vol)))); +end; +%_______________________________________________________________________ +function mn = minval(vol) +if isstruct(vol), + mn = Inf; + for i=1:vol.dim(3), + tmp = spm_slice_vol(vol,spm_matrix([0 0 i]),vol.dim(1:2),0); + imn = min(tmp(find(finite(tmp)))); + if ~isempty(imn),mn = min(mn,imn);end + end; +else, + mn = min(vol(find(finite(vol)))); +end; + +%_______________________________________________________________________ +%_______________________________________________________________________ +function redraw(arg1) +global st +bb = st.bb; +Dims = round(diff(bb)'+1); +is = inv(st.Space); +cent = is(1:3,1:3)*st.centre(:) + is(1:3,4); + +for i = valid_handles(arg1), + M = st.vols{i}.premul*st.vols{i}.mat; + TM0 = [ 1 0 0 -bb(1,1)+1 + 0 1 0 -bb(1,2)+1 + 0 0 1 -cent(3) + 0 0 0 1]; + TM = inv(TM0*(st.Space\M)); + TD = Dims([1 2]); + + CM0 = [ 1 0 0 -bb(1,1)+1 + 0 0 1 -bb(1,3)+1 + 0 1 0 -cent(2) + 0 0 0 1]; + CM = inv(CM0*(st.Space\M)); + CD = Dims([1 3]); + + if st.mode ==0, + SM0 = [ 0 0 1 -bb(1,3)+1 + 0 1 0 -bb(1,2)+1 + 1 0 0 -cent(1) + 0 0 0 1]; + SM = inv(SM0*(st.Space\M)); SD = Dims([3 2]); + else, + SM0 = [ 0 1 0 -bb(1,2)+1 + 0 0 1 -bb(1,3)+1 + 1 0 0 -cent(1) + 0 0 0 1]; + SM0 = [ 0 -1 0 +bb(2,2)+1 + 0 0 1 -bb(1,3)+1 + 1 0 0 -cent(1) + 0 0 0 1]; + SM = inv(SM0*(st.Space\M)); + SD = Dims([2 3]); + end; + + ok=1; + eval('imgt = (spm_slice_vol(st.vols{i},TM,TD,st.hld))'';','ok=0;'); + eval('imgc = (spm_slice_vol(st.vols{i},CM,CD,st.hld))'';','ok=0;'); + eval('imgs = (spm_slice_vol(st.vols{i},SM,SD,st.hld))'';','ok=0;'); + if (ok==0), fprintf('Image "%s" can not be resampled\n', st.vols{i}.fname); + else, + % get min/max threshold + if strcmp(st.vols{i}.window,'auto') + mn = -Inf; + mx = Inf; + else + mn = min(st.vols{i}.window); + mx = max(st.vols{i}.window); + end; + % threshold images + imgt = max(imgt,mn); imgt = min(imgt,mx); + imgc = max(imgc,mn); imgc = min(imgc,mx); + imgs = max(imgs,mn); imgs = min(imgs,mx); + % compute intensity mapping, if histeq is available + if license('test','image_toolbox') == 0 + st.vols{i}.mapping = 'linear'; + end; + switch st.vols{i}.mapping, + case 'linear', + case 'histeq', + % scale images to a range between 0 and 1 + imgt1=(imgt-min(imgt(:)))/(max(imgt(:)-min(imgt(:)))+eps); + imgc1=(imgc-min(imgc(:)))/(max(imgc(:)-min(imgc(:)))+eps); + imgs1=(imgs-min(imgs(:)))/(max(imgs(:)-min(imgs(:)))+eps); + img = histeq([imgt1(:); imgc1(:); imgs1(:)],1024); + imgt = reshape(img(1:numel(imgt1)),size(imgt1)); + imgc = reshape(img(numel(imgt1)+[1:numel(imgc1)]),size(imgc1)); + imgs = reshape(img(numel(imgt1)+numel(imgc1)+[1:numel(imgs1)]),size(imgs1)); + mn = 0; + mx = 1; + case 'quadhisteq', + % scale images to a range between 0 and 1 + imgt1=(imgt-min(imgt(:)))/(max(imgt(:)-min(imgt(:)))+eps); + imgc1=(imgc-min(imgc(:)))/(max(imgc(:)-min(imgc(:)))+eps); + imgs1=(imgs-min(imgs(:)))/(max(imgs(:)-min(imgs(:)))+eps); + img = histeq([imgt1(:).^2; imgc1(:).^2; imgs1(:).^2],1024); + imgt = reshape(img(1:numel(imgt1)),size(imgt1)); + imgc = reshape(img(numel(imgt1)+[1:numel(imgc1)]),size(imgc1)); + imgs = reshape(img(numel(imgt1)+numel(imgc1)+[1:numel(imgs1)]),size(imgs1)); + mn = 0; + mx = 1; + case 'loghisteq', + warning off % messy - but it may avoid extra queries + imgt = log(imgt-min(imgt(:))); + imgc = log(imgc-min(imgc(:))); + imgs = log(imgs-min(imgs(:))); + warning on + imgt(~isfinite(imgt)) = 0; + imgc(~isfinite(imgc)) = 0; + imgs(~isfinite(imgs)) = 0; + % scale log images to a range between 0 and 1 + imgt1=(imgt-min(imgt(:)))/(max(imgt(:)-min(imgt(:)))+eps); + imgc1=(imgc-min(imgc(:)))/(max(imgc(:)-min(imgc(:)))+eps); + imgs1=(imgs-min(imgs(:)))/(max(imgs(:)-min(imgs(:)))+eps); + img = histeq([imgt1(:); imgc1(:); imgs1(:)],1024); + imgt = reshape(img(1:numel(imgt1)),size(imgt1)); + imgc = reshape(img(numel(imgt1)+[1:numel(imgc1)]),size(imgc1)); + imgs = reshape(img(numel(imgt1)+numel(imgc1)+[1:numel(imgs1)]),size(imgs1)); + mn = 0; + mx = 1; + end; + % recompute min/max for display + if strcmp(st.vols{i}.window,'auto') + mx = -inf; mn = inf; + end; + if ~isempty(imgt), + tmp = imgt(finite(imgt)); + mx = max([mx max(max(tmp))]); + mn = min([mn min(min(tmp))]); + end; + if ~isempty(imgc), + tmp = imgc(finite(imgc)); + mx = max([mx max(max(tmp))]); + mn = min([mn min(min(tmp))]); + end; + if ~isempty(imgs), + tmp = imgs(finite(imgs)); + mx = max([mx max(max(tmp))]); + mn = min([mn min(min(tmp))]); + end; + if mx==mn, mx=mn+eps; end; + + if isfield(st.vols{i},'blobs'), + if ~isfield(st.vols{i}.blobs{1},'colour'), + % Add blobs for display using the split colourmap + scal = 64/(mx-mn); + dcoff = -mn*scal; + imgt = imgt*scal+dcoff; + imgc = imgc*scal+dcoff; + imgs = imgs*scal+dcoff; + + if isfield(st.vols{i}.blobs{1},'max'), + mx = st.vols{i}.blobs{1}.max; + else, + mx = max([eps maxval(st.vols{i}.blobs{1}.vol)]); + st.vols{i}.blobs{1}.max = mx; + end; + if isfield(st.vols{i}.blobs{1},'min'), + mn = st.vols{i}.blobs{1}.min; + else, + mn = min([0 minval(st.vols{i}.blobs{1}.vol)]); + st.vols{i}.blobs{1}.min = mn; + end; + + vol = st.vols{i}.blobs{1}.vol; + M = st.vols{i}.premul*st.vols{i}.blobs{1}.mat; + tmpt = spm_slice_vol(vol,inv(TM0*(st.Space\M)),TD,[0 NaN])'; + tmpc = spm_slice_vol(vol,inv(CM0*(st.Space\M)),CD,[0 NaN])'; + tmps = spm_slice_vol(vol,inv(SM0*(st.Space\M)),SD,[0 NaN])'; + + %tmpt_z = find(tmpt==0);tmpt(tmpt_z) = NaN; + %tmpc_z = find(tmpc==0);tmpc(tmpc_z) = NaN; + %tmps_z = find(tmps==0);tmps(tmps_z) = NaN; + + sc = 64/(mx-mn); + off = 65.51-mn*sc; + msk = find(finite(tmpt)); imgt(msk) = off+tmpt(msk)*sc; + msk = find(finite(tmpc)); imgc(msk) = off+tmpc(msk)*sc; + msk = find(finite(tmps)); imgs(msk) = off+tmps(msk)*sc; + + cmap = get(st.fig,'Colormap'); + + figure(st.fig) + if mn*mx < 0 + setcolormap('gray-hot-cold') + elseif mx > 0 + setcolormap('gray-hot'); + else + setcolormap('gray-cold') + end + redraw_colourbar(i,1,[mn mx],[1:64]'+64); + elseif isstruct(st.vols{i}.blobs{1}.colour), + % Add blobs for display using a defined + % colourmap + + % colourmaps + gryc = [0:63]'*ones(1,3)/63; + actc = ... + st.vols{1}.blobs{1}.colour.cmap; + actp = ... + st.vols{1}.blobs{1}.colour.prop; + + % scale grayscale image, not finite -> black + imgt = scaletocmap(imgt,mn,mx,gryc,65); + imgc = scaletocmap(imgc,mn,mx,gryc,65); + imgs = scaletocmap(imgs,mn,mx,gryc,65); + gryc = [gryc; 0 0 0]; + + % get max for blob image + vol = st.vols{i}.blobs{1}.vol; + mat = st.vols{i}.premul*st.vols{i}.blobs{1}.mat; + if isfield(st.vols{i}.blobs{1},'max'), + cmx = st.vols{i}.blobs{1}.max; + else, + cmx = max([eps maxval(st.vols{i}.blobs{1}.vol)]); + end; + if isfield(st.vols{i}.blobs{1},'min'), + cmn = st.vols{i}.blobs{1}.min; + else, + cmn = -cmx; + end; + + % get blob data + vol = st.vols{i}.blobs{1}.vol; + M = st.vols{i}.premul*st.vols{i}.blobs{1}.mat; + tmpt = spm_slice_vol(vol,inv(TM0*(st.Space\M)),TD,[0 NaN])'; + tmpc = spm_slice_vol(vol,inv(CM0*(st.Space\M)),CD,[0 NaN])'; + tmps = spm_slice_vol(vol,inv(SM0*(st.Space\M)),SD,[0 NaN])'; + + % actimg scaled round 0, black NaNs + topc = size(actc,1)+1; + tmpt = scaletocmap(tmpt,cmn,cmx,actc,topc); + tmpc = scaletocmap(tmpc,cmn,cmx,actc,topc); + tmps = scaletocmap(tmps,cmn,cmx,actc,topc); + actc = [actc; 0 0 0]; + + % combine gray and blob data to + % truecolour + imgt = reshape(actc(tmpt(:),:)*actp+ ... + gryc(imgt(:),:)*(1-actp), ... + [size(imgt) 3]); + imgc = reshape(actc(tmpc(:),:)*actp+ ... + gryc(imgc(:),:)*(1-actp), ... + [size(imgc) 3]); + imgs = reshape(actc(tmps(:),:)*actp+ ... + gryc(imgs(:),:)*(1-actp), ... + [size(imgs) 3]); + + redraw_colourbar(i,1,[cmn cmx],[1:64]'+64); + + else, + % Add full colour blobs - several sets at once + scal = 1/(mx-mn); + dcoff = -mn*scal; + + wt = zeros(size(imgt)); + wc = zeros(size(imgc)); + ws = zeros(size(imgs)); + + imgt = repmat(imgt*scal+dcoff,[1,1,3]); + imgc = repmat(imgc*scal+dcoff,[1,1,3]); + imgs = repmat(imgs*scal+dcoff,[1,1,3]); + + cimgt = zeros(size(imgt)); + cimgc = zeros(size(imgc)); + cimgs = zeros(size(imgs)); + + for j=1:length(st.vols{i}.blobs), % get colours of all images first + if isfield(st.vols{i}.blobs{j},'colour'), + colour(j,:) = reshape(st.vols{i}.blobs{j}.colour, [1 3]); + else, + colour(j,:) = [1 0 0]; + end; + end; + %colour = colour/max(sum(colour)); + + for j=1:length(st.vols{i}.blobs), + if isfield(st.vols{i}.blobs{j},'max'), + mx = st.vols{i}.blobs{j}.max; + else, + mx = max([eps max(st.vols{i}.blobs{j}.vol(:))]); + st.vols{i}.blobs{j}.max = mx; + end; + if isfield(st.vols{i}.blobs{j},'min'), + mn = st.vols{i}.blobs{j}.min; + else, + mn = min([0 min(st.vols{i}.blobs{j}.vol(:))]); + st.vols{i}.blobs{j}.min = mn; + end; + + vol = st.vols{i}.blobs{j}.vol; + M = st.Space\st.vols{i}.premul*st.vols{i}.blobs{j}.mat; + tmpt = spm_slice_vol(vol,inv(TM0*M),TD,[0 NaN])'; + tmpc = spm_slice_vol(vol,inv(CM0*M),CD,[0 NaN])'; + tmps = spm_slice_vol(vol,inv(SM0*M),SD,[0 NaN])'; + % check min/max of sampled image + % against mn/mx as given in st + tmpt(tmpt(:)mx) = mx; + tmpc(tmpc(:)>mx) = mx; + tmps(tmps(:)>mx) = mx; + tmpt = (tmpt-mn)/(mx-mn); + tmpc = (tmpc-mn)/(mx-mn); + tmps = (tmps-mn)/(mx-mn); + tmpt(~finite(tmpt)) = 0; + tmpc(~finite(tmpc)) = 0; + tmps(~finite(tmps)) = 0; + + cimgt = cimgt + cat(3,tmpt*colour(j,1),tmpt*colour(j,2),tmpt*colour(j,3)); + cimgc = cimgc + cat(3,tmpc*colour(j,1),tmpc*colour(j,2),tmpc*colour(j,3)); + cimgs = cimgs + cat(3,tmps*colour(j,1),tmps*colour(j,2),tmps*colour(j,3)); + + wt = wt + tmpt; + wc = wc + tmpc; + ws = ws + tmps; + cdata=permute(shiftdim([1/64:1/64:1]'* ... + colour(j,:),-1),[2 1 3]); + redraw_colourbar(i,j,[mn mx],cdata); + end; + + imgt = repmat(1-wt,[1 1 3]).*imgt+cimgt; + imgc = repmat(1-wc,[1 1 3]).*imgc+cimgc; + imgs = repmat(1-ws,[1 1 3]).*imgs+cimgs; + + imgt(imgt<0)=0; imgt(imgt>1)=1; + imgc(imgc<0)=0; imgc(imgc>1)=1; + imgs(imgs<0)=0; imgs(imgs>1)=1; + end; + else, + scal = 64/(mx-mn); + dcoff = -mn*scal; + imgt = imgt*scal+dcoff; + imgc = imgc*scal+dcoff; + imgs = imgs*scal+dcoff; + end; + + set(st.vols{i}.ax{1}.d,'HitTest','off', 'Cdata',imgt); + set(st.vols{i}.ax{1}.lx,'HitTest','off',... + 'Xdata',[0 TD(1)]+0.5,'Ydata',[1 1]*(cent(2)-bb(1,2)+1)); + set(st.vols{i}.ax{1}.ly,'HitTest','off',... + 'Ydata',[0 TD(2)]+0.5,'Xdata',[1 1]*(cent(1)-bb(1,1)+1)); + + set(st.vols{i}.ax{2}.d,'HitTest','off', 'Cdata',imgc); + set(st.vols{i}.ax{2}.lx,'HitTest','off',... + 'Xdata',[0 CD(1)]+0.5,'Ydata',[1 1]*(cent(3)-bb(1,3)+1)); + set(st.vols{i}.ax{2}.ly,'HitTest','off',... + 'Ydata',[0 CD(2)]+0.5,'Xdata',[1 1]*(cent(1)-bb(1,1)+1)); + + set(st.vols{i}.ax{3}.d,'HitTest','off','Cdata',imgs); + if st.mode ==0, + set(st.vols{i}.ax{3}.lx,'HitTest','off',... + 'Xdata',[0 SD(1)]+0.5,'Ydata',[1 1]*(cent(2)-bb(1,2)+1)); + set(st.vols{i}.ax{3}.ly,'HitTest','off',... + 'Ydata',[0 SD(2)]+0.5,'Xdata',[1 1]*(cent(3)-bb(1,3)+1)); + else, + set(st.vols{i}.ax{3}.lx,'HitTest','off',... + 'Xdata',[0 SD(1)]+0.5,'Ydata',[1 1]*(cent(3)-bb(1,3)+1)); + set(st.vols{i}.ax{3}.ly,'HitTest','off',... + 'Ydata',[0 SD(2)]+0.5,'Xdata',[1 1]*(bb(2,2)+1-cent(2))); + end; + + if ~isempty(st.plugins) % process any addons + for k = 1:prod(size(st.plugins)) + if isfield(st.vols{i},st.plugins{k}) + feval(['spm_ov_', st.plugins{k}], ... + 'redraw', i, TM0, TD, CM0, CD, SM0, SD); + end; + end; + end; + end; +end; +drawnow; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function redraw_colourbar(vh,bh,interval,cdata) +global st +if isfield(st.vols{vh}.blobs{bh},'cbar') + if st.mode == 0, + axpos = get(st.vols{vh}.ax{2}.ax,'Position'); + else, + axpos = get(st.vols{vh}.ax{1}.ax,'Position'); + end; + % only scale cdata if we have out-of-range truecolour values + if ndims(cdata)==3 && max(cdata(:))>1 + cdata=cdata./max(cdata(:)); + end; + image([0 1],interval,cdata,'Parent',st.vols{vh}.blobs{bh}.cbar); + set(st.vols{vh}.blobs{bh}.cbar, ... + 'Position',[(axpos(1)+axpos(3)+0.05+(bh-1)*.1)... + (axpos(2)+0.005) 0.05 (axpos(4)-0.01)],... + 'YDir','normal','XTickLabel',[],'XTick',[]); +end; +%_______________________________________________________________________ +%_______________________________________________________________________ +function centre = findcent +global st +obj = get(st.fig,'CurrentObject'); +centre = []; +cent = []; +cp = []; +for i=valid_handles(1:24), + for j=1:3, + if ~isempty(obj), + if (st.vols{i}.ax{j}.ax == obj), + cp = get(obj,'CurrentPoint'); + end; + end; + if ~isempty(cp), + cp = cp(1,1:2); + is = inv(st.Space); + cent = is(1:3,1:3)*st.centre(:) + is(1:3,4); + switch j, + case 1, + cent([1 2])=[cp(1)+st.bb(1,1)-1 cp(2)+st.bb(1,2)-1]; + case 2, + cent([1 3])=[cp(1)+st.bb(1,1)-1 cp(2)+st.bb(1,3)-1]; + case 3, + if st.mode ==0, + cent([3 2])=[cp(1)+st.bb(1,3)-1 cp(2)+st.bb(1,2)-1]; + else, + cent([2 3])=[st.bb(2,2)+1-cp(1) cp(2)+st.bb(1,3)-1]; + end; + end; + break; + end; + end; + if ~isempty(cent), break; end; +end; +if ~isempty(cent), centre = st.Space(1:3,1:3)*cent(:) + st.Space(1:3,4); end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function handles = valid_handles(handles) +global st; +handles = handles(:)'; +handles = handles(find(handles<=24 & handles>=1 & ~rem(handles,1))); +for h=handles, + if isempty(st.vols{h}), handles(find(handles==h))=[]; end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function reset_st +global st +fig = spm_figure('FindWin','Graphics'); +bb = []; %[ [-78 78]' [-112 76]' [-50 85]' ]; +st = struct('n', 0, 'vols',[], 'bb',bb,'Space',eye(4),'centre',[0 0 0],'callback',';','xhairs',1,'hld',1,'fig',fig,'mode',1,'plugins',[],'snap',[]); +st.vols = cell(24,1); + +pluginpath = fullfile(spm('Dir'),'spm_orthviews'); +if isdir(pluginpath) + pluginfiles = dir(fullfile(pluginpath,'spm_ov_*.m')); + if ~isempty(pluginfiles) + addpath(pluginpath); + % fprintf('spm_orthviews: Using Plugins in %s\n', pluginpath); + for k = 1:length(pluginfiles) + [p pluginname e v] = fileparts(pluginfiles(k).name); + st.plugins{k} = strrep(pluginname, 'spm_ov_',''); + % fprintf('%s\n',st.plugins{k}); + end; + end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function img = scaletocmap(inpimg,mn,mx,cmap,miscol) +if nargin < 5, miscol=1;end +cml = size(cmap,1); +scf = (cml-1)/(mx-mn); +img = round((inpimg-mn)*scf)+1; +img(find(img<1)) = 1; +img(find(img>cml)) = cml; +img(~finite(img)) = miscol; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function cmap = getcmap(acmapname) +% get colormap of name acmapname +if ~isempty(acmapname), + cmap = evalin('base',acmapname,'[]'); + if isempty(cmap), % not a matrix, is .mat file? + [p f e] = fileparts(acmapname); + acmat = fullfile(p, [f '.mat']); + if exist(acmat, 'file'), + s = struct2cell(load(acmat)); + cmap = s{1}; + end; + end; +end; +if size(cmap, 2)~=3, + warning('Colormap was not an N by 3 matrix') + cmap = []; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function item_parent = addcontext(volhandle) +global st; +%create context menu +fg = spm_figure('Findwin','Graphics');set(0,'CurrentFigure',fg); +%contextmenu +item_parent = uicontextmenu; + +%contextsubmenu 0 +item00 = uimenu(item_parent, 'Label','unknown image', 'Separator','on'); +spm_orthviews('context_menu','image_info',item00,volhandle); +item0a = uimenu(item_parent, 'UserData','pos_mm', 'Callback','spm_orthviews(''context_menu'',''repos_mm'');','Separator','on'); +item0b = uimenu(item_parent, 'UserData','pos_vx', 'Callback','spm_orthviews(''context_menu'',''repos_vx'');'); +item0c = uimenu(item_parent, 'UserData','v_value'); + +%contextsubmenu 1 +item1 = uimenu(item_parent,'Label','Zoom'); +item1_1 = uimenu(item1, 'Label','Full Volume', 'Callback','spm_orthviews(''context_menu'',''zoom'',6);', 'Checked','on'); +item1_2 = uimenu(item1, 'Label','160x160x160mm', 'Callback','spm_orthviews(''context_menu'',''zoom'',5);'); +item1_3 = uimenu(item1, 'Label','80x80x80mm', 'Callback','spm_orthviews(''context_menu'',''zoom'',4);'); +item1_4 = uimenu(item1, 'Label','40x40x40mm', 'Callback','spm_orthviews(''context_menu'',''zoom'',3);'); +item1_5 = uimenu(item1, 'Label','20x20x20mm', 'Callback','spm_orthviews(''context_menu'',''zoom'',2);'); +item1_6 = uimenu(item1, 'Label','10x10x10mm', 'Callback','spm_orthviews(''context_menu'',''zoom'',1);'); + +%contextsubmenu 2 +checked={'off','off'}; +checked{st.xhairs+1} = 'on'; +item2 = uimenu(item_parent,'Label','Crosshairs'); +item2_1 = uimenu(item2, 'Label','on', 'Callback','spm_orthviews(''context_menu'',''Xhair'',''on'');','Checked',checked{2}); +item2_2 = uimenu(item2, 'Label','off', 'Callback','spm_orthviews(''context_menu'',''Xhair'',''off'');','Checked',checked{1}); + +%contextsubmenu 3 +if st.Space == eye(4) + checked = {'off', 'on'}; +else + checked = {'on', 'off'}; +end; +item3 = uimenu(item_parent,'Label','Orientation'); +item3_1 = uimenu(item3, 'Label','World space', 'Callback','spm_orthviews(''context_menu'',''orientation'',3);','Checked',checked{2}); +item3_2 = uimenu(item3, 'Label','Voxel space (1st image)', 'Callback','spm_orthviews(''context_menu'',''orientation'',2);','Checked',checked{1}); +item3_3 = uimenu(item3, 'Label','Voxel space (this image)', 'Callback','spm_orthviews(''context_menu'',''orientation'',1);','Checked','off'); + +%contextsubmenu 3 +if isempty(st.snap) + checked = {'off', 'on'}; +else + checked = {'on', 'off'}; +end; +item3 = uimenu(item_parent,'Label','Snap to Grid'); +item3_1 = uimenu(item3, 'Label','Don''t snap', 'Callback','spm_orthviews(''context_menu'',''snap'',3);','Checked',checked{2}); +item3_2 = uimenu(item3, 'Label','Snap to 1st image', 'Callback','spm_orthviews(''context_menu'',''snap'',2);','Checked',checked{1}); +item3_3 = uimenu(item3, 'Label','Snap to this image', 'Callback','spm_orthviews(''context_menu'',''snap'',1);','Checked','off'); + +%contextsubmenu 4 +if st.hld == 0, + checked = {'off', 'off', 'on'}; +elseif st.hld > 0, + checked = {'off', 'on', 'off'}; +else, + checked = {'on', 'off', 'off'}; +end; +item4 = uimenu(item_parent,'Label','Interpolation'); +item4_1 = uimenu(item4, 'Label','NN', 'Callback','spm_orthviews(''context_menu'',''interpolation'',3);', 'Checked',checked{3}); +item4_2 = uimenu(item4, 'Label','Bilin', 'Callback','spm_orthviews(''context_menu'',''interpolation'',2);','Checked',checked{2}); +item4_3 = uimenu(item4, 'Label','Sinc', 'Callback','spm_orthviews(''context_menu'',''interpolation'',1);','Checked',checked{1}); + +%contextsubmenu 5 +% item5 = uimenu(item_parent,'Label','Position', 'Callback','spm_orthviews(''context_menu'',''position'');'); + +%contextsubmenu 6 +item6 = uimenu(item_parent,'Label','Image','Separator','on'); +item6_1 = uimenu(item6, 'Label','Window'); +item6_1_1 = uimenu(item6_1, 'Label','local'); +item6_1_1_1 = uimenu(item6_1_1, 'Label','auto', 'Callback','spm_orthviews(''context_menu'',''window'',2);'); +item6_1_1_2 = uimenu(item6_1_1, 'Label','manual', 'Callback','spm_orthviews(''context_menu'',''window'',1);'); +item6_1_2 = uimenu(item6_1, 'Label','global'); +item6_1_2_1 = uimenu(item6_1_2, 'Label','auto', 'Callback','spm_orthviews(''context_menu'',''window_gl'',2);'); +item6_1_2_2 = uimenu(item6_1_2, 'Label','manual', 'Callback','spm_orthviews(''context_menu'',''window_gl'',1);'); +if license('test','image_toolbox') == 1 + offon = {'off', 'on'}; + checked = offon(strcmp(st.vols{volhandle}.mapping, ... + {'linear', 'histeq', 'loghisteq', 'quadhisteq'})+1); + item6_2 = uimenu(item6, 'Label','Intensity mapping'); + item6_2_1 = uimenu(item6_2, 'Label','local'); + item6_2_1_1 = uimenu(item6_2_1, 'Label','Linear', 'Checked',checked{1}, ... + 'Callback','spm_orthviews(''context_menu'',''mapping'',''linear'');'); + item6_2_1_2 = uimenu(item6_2_1, 'Label','Equalised histogram', 'Checked',checked{2}, ... + 'Callback','spm_orthviews(''context_menu'',''mapping'',''histeq'');'); + item6_2_1_3 = uimenu(item6_2_1, 'Label','Equalised log-histogram', 'Checked',checked{3}, ... + 'Callback','spm_orthviews(''context_menu'',''mapping'',''loghisteq'');'); + item6_2_1_4 = uimenu(item6_2_1, 'Label','Equalised squared-histogram', 'Checked',checked{4}, ... + 'Callback','spm_orthviews(''context_menu'',''mapping'',''quadhisteq'');'); + item6_2_2 = uimenu(item6_2, 'Label','global'); + item6_2_2_1 = uimenu(item6_2_2, 'Label','Linear', 'Checked',checked{1}, ... + 'Callback','spm_orthviews(''context_menu'',''mapping_gl'',''linear'');'); + item6_2_2_2 = uimenu(item6_2_2, 'Label','Equalised histogram', 'Checked',checked{2}, ... + 'Callback','spm_orthviews(''context_menu'',''mapping_gl'',''histeq'');'); + item6_2_2_3 = uimenu(item6_2_2, 'Label','Equalised log-histogram', 'Checked',checked{3}, ... + 'Callback','spm_orthviews(''context_menu'',''mapping_gl'',''loghisteq'');'); + item6_2_2_4 = uimenu(item6_2_2, 'Label','Equalised squared-histogram', 'Checked',checked{4}, ... + 'Callback','spm_orthviews(''context_menu'',''mapping_gl'',''quadhisteq'');'); +end; +%contextsubmenu 7 +item7 = uimenu(item_parent,'Label','Blobs'); +item7_1 = uimenu(item7, 'Label','Add blobs'); +item7_1_1 = uimenu(item7_1, 'Label','local', 'Callback','spm_orthviews(''context_menu'',''add_blobs'',2);'); +item7_1_2 = uimenu(item7_1, 'Label','global', 'Callback','spm_orthviews(''context_menu'',''add_blobs'',1);'); +item7_2 = uimenu(item7, 'Label','Add image'); +item7_2_1 = uimenu(item7_2, 'Label','local', 'Callback','spm_orthviews(''context_menu'',''add_image'',2);'); +item7_2_2 = uimenu(item7_2, 'Label','global', 'Callback','spm_orthviews(''context_menu'',''add_image'',1);'); +item7_3 = uimenu(item7, 'Label','Add colored blobs','Separator','on'); +item7_3_1 = uimenu(item7_3, 'Label','local', 'Callback','spm_orthviews(''context_menu'',''add_c_blobs'',2);'); +item7_3_2 = uimenu(item7_3, 'Label','global', 'Callback','spm_orthviews(''context_menu'',''add_c_blobs'',1);'); +item7_4 = uimenu(item7, 'Label','Add colored image'); +item7_4_1 = uimenu(item7_4, 'Label','local', 'Callback','spm_orthviews(''context_menu'',''add_c_image'',2);'); +item7_4_2 = uimenu(item7_4, 'Label','global', 'Callback','spm_orthviews(''context_menu'',''add_c_image'',1);'); +item7_5 = uimenu(item7, 'Label','Remove blobs', 'Visible','off','Separator','on'); +item7_6 = uimenu(item7, 'Label','Remove colored blobs','Visible','off'); +item7_6_1 = uimenu(item7_6, 'Label','local', 'Visible','on'); +item7_6_2 = uimenu(item7_6, 'Label','global','Visible','on'); + +if ~isempty(st.plugins) % process any plugins + for k = 1:prod(size(st.plugins)), + feval(['spm_ov_', st.plugins{k}], ... + 'context_menu', volhandle, item_parent); + end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function c_menu(varargin) +global st + +switch lower(varargin{1}), + case 'image_info', + if nargin <3, + current_handle = get_current_handle; + else + current_handle = varargin{3}; + end; + if isfield(st.vols{current_handle},'fname'), + [p,n,e,v] = spm_fileparts(st.vols{current_handle}.fname); + if isfield(st.vols{current_handle},'n') + v = sprintf(',%d',st.vols{current_handle}.n); + end; + set(varargin{2}, 'Label',[n e v]); + end; + delete(get(varargin{2},'children')); + if exist('p','var') + item1 = uimenu(varargin{2}, 'Label', p); + end; + if isfield(st.vols{current_handle},'descrip'), + item2 = uimenu(varargin{2}, 'Label',... + st.vols{current_handle}.descrip); + end; + dt = st.vols{current_handle}.dt(1); + item3 = uimenu(varargin{2}, 'Label', sprintf('Data type: %s', spm_type(dt))); + str = 'Intensity: varied'; + if size(st.vols{current_handle}.pinfo,2) == 1, + if st.vols{current_handle}.pinfo(2), + str = sprintf('Intensity: Y = %g X + %g',... + st.vols{current_handle}.pinfo(1:2)'); + else, + str = sprintf('Intensity: Y = %g X', st.vols{current_handle}.pinfo(1)'); + end; + end; + item4 = uimenu(varargin{2}, 'Label',str); + item5 = uimenu(varargin{2}, 'Label', 'Image dims', 'Separator','on'); + item51 = uimenu(varargin{2}, 'Label',... + sprintf('%dx%dx%d', st.vols{current_handle}.dim(1:3))); + prms = spm_imatrix(st.vols{current_handle}.mat); + item6 = uimenu(varargin{2}, 'Label','Voxel size', 'Separator','on'); + item61 = uimenu(varargin{2}, 'Label', sprintf('%.2f %.2f %.2f', prms(7:9))); + item7 = uimenu(varargin{2}, 'Label','Origin', 'Separator','on'); + item71 = uimenu(varargin{2}, 'Label',... + sprintf('%.2f %.2f %.2f', prms(1:3))); + R = spm_matrix([0 0 0 prms(4:6)]); + item8 = uimenu(varargin{2}, 'Label','Rotations', 'Separator','on'); + item81 = uimenu(varargin{2}, 'Label', sprintf('%.2f %.2f %.2f', R(1,1:3))); + item82 = uimenu(varargin{2}, 'Label', sprintf('%.2f %.2f %.2f', R(2,1:3))); + item83 = uimenu(varargin{2}, 'Label', sprintf('%.2f %.2f %.2f', R(3,1:3))); + item9 = uimenu(varargin{2},... + 'Label','Specify other image...',... + 'Callback','spm_orthviews(''context_menu'',''swap_img'');',... + 'Separator','on'); + + case 'repos_mm', + oldpos_mm = spm_orthviews('pos'); + newpos_mm = spm_input('New Position (mm)','+1','r',sprintf('%.2f %.2f %.2f',oldpos_mm),3); + spm_orthviews('reposition',newpos_mm); + + case 'repos_vx' + current_handle = get_current_handle; + oldpos_vx = spm_orthviews('pos', current_handle); + newpos_vx = spm_input('New Position (voxels)','+1','r',sprintf('%.2f %.2f %.2f',oldpos_vx),3); + newpos_mm = st.vols{current_handle}.mat*[newpos_vx;1]; + spm_orthviews('reposition',newpos_mm(1:3)); + + case 'zoom' + zoom_all(varargin{2}); + bbox; + redraw_all; + + case 'xhair', + spm_orthviews('Xhairs',varargin{2}); + cm_handles = get_cm_handles; + for i = 1:length(cm_handles), + z_handle = get(findobj(cm_handles(i),'label','Crosshairs'),'Children'); + set(z_handle,'Checked','off'); %reset check + if strcmp(varargin{2},'off'), op = 1; else op = 2; end + set(z_handle(op),'Checked','on'); + end; + + case 'orientation', + cm_handles = get_cm_handles; + for i = 1:length(cm_handles), + z_handle = get(findobj(cm_handles(i),'label','Orientation'),'Children'); + set(z_handle,'Checked','off'); + end; + if varargin{2} == 3, + spm_orthviews('Space'); + elseif varargin{2} == 2, + spm_orthviews('Space',1); + else, + spm_orthviews('Space',get_current_handle); + z_handle = get(findobj(st.vols{get_current_handle}.ax{1}.cm,'label','Orientation'),'Children'); + set(z_handle(1),'Checked','on'); + return; + end; + for i = 1:length(cm_handles), + z_handle = get(findobj(cm_handles(i),'label','Orientation'),'Children'); + set(z_handle(varargin{2}),'Checked','on'); + end; + + case 'snap', + cm_handles = get_cm_handles; + for i = 1:length(cm_handles), + z_handle = get(findobj(cm_handles(i),'label','Snap to Grid'),'Children'); + set(z_handle,'Checked','off'); + end; + if varargin{2} == 3, + st.snap = []; + elseif varargin{2} == 2, + st.snap = 1; + else, + st.snap = get_current_handle; + z_handle = get(findobj(st.vols{get_current_handle}.ax{1}.cm,'label','Snap to Grid'),'Children'); + set(z_handle(1),'Checked','on'); + return; + end; + for i = 1:length(cm_handles), + z_handle = get(findobj(cm_handles(i),'label','Snap to Grid'),'Children'); + set(z_handle(varargin{2}),'Checked','on'); + end; + + case 'interpolation', + tmp = [-4 1 0]; + st.hld = tmp(varargin{2}); + cm_handles = get_cm_handles; + for i = 1:length(cm_handles), + z_handle = get(findobj(cm_handles(i),'label','Interpolation'),'Children'); + set(z_handle,'Checked','off'); + set(z_handle(varargin{2}),'Checked','on'); + end; + redraw_all; + + case 'window', + current_handle = get_current_handle; + if varargin{2} == 2, + spm_orthviews('window',current_handle); + else + if isnumeric(st.vols{current_handle}.window) + defstr = sprintf('%.2f %.2f', st.vols{current_handle}.window); + else + defstr = ''; + end; + spm_orthviews('window',current_handle,spm_input('Range','+1','e',defstr,2)); + end; + + case 'window_gl', + if varargin{2} == 2, + for i = 1:length(get_cm_handles), + st.vols{i}.window = 'auto'; + end; + else, + current_handle = get_current_handle; + if isnumeric(st.vols{current_handle}.window) + defstr = sprintf('%d %d', st.vols{current_handle}.window); + else + defstr = ''; + end; + data = spm_input('Range','+1','e',defstr,2); + + for i = 1:length(get_cm_handles), + st.vols{i}.window = data; + end; + end; + redraw_all; + + case 'mapping', + checked = strcmp(varargin{2}, ... + {'linear', 'histeq', 'loghisteq', ... + 'quadhisteq'}); + checked = checked(end:-1:1); % Handles are stored in inverse order + current_handle = get_current_handle; + cm_handles = get_cm_handles; + st.vols{current_handle}.mapping = varargin{2}; + z_handle = get(findobj(cm_handles(current_handle), ... + 'label','Intensity mapping'),'Children'); + for k = 1:numel(z_handle) + c_handle = get(z_handle(k), 'Children'); + set(c_handle, 'checked', 'off'); + set(c_handle(checked), 'checked', 'on'); + end; + redraw_all; + + case 'mapping_gl', + checked = strcmp(varargin{2}, ... + {'linear', 'histeq', 'loghisteq', 'quadhisteq'}); + checked = checked(end:-1:1); % Handles are stored in inverse order + cm_handles = get_cm_handles; + for k = valid_handles(1:24), + st.vols{k}.mapping = varargin{2}; + z_handle = get(findobj(cm_handles(k), ... + 'label','Intensity mapping'),'Children'); + for l = 1:numel(z_handle) + c_handle = get(z_handle(l), 'Children'); + set(c_handle, 'checked', 'off'); + set(c_handle(checked), 'checked', 'on'); + end; + end; + redraw_all; + + case 'swap_img', + current_handle = get_current_handle; + new_info = spm_vol(spm_select(1,'image','select new image')); + fn = fieldnames(new_info); + for k=1:numel(fn) + st.vols{current_handle}.(fn{k}) = new_info.(fn{k}); + end; + spm_orthviews('context_menu','image_info',get(gcbo, 'parent')); + redraw_all; + + case 'add_blobs', + % Add blobs to the image - in split colortable + cm_handles = valid_handles(1:24); + if varargin{2} == 2, cm_handles = get_current_handle; end; + spm_figure('Clear','Interactive'); + [SPM,VOL] = spm_getSPM; + for i = 1:length(cm_handles), + addblobs(cm_handles(i),VOL.XYZ,VOL.Z,VOL.M); + c_handle = findobj(findobj(st.vols{cm_handles(i)}.ax{1}.cm,'label','Blobs'),'Label','Remove blobs'); + set(c_handle,'Visible','on'); + delete(get(c_handle,'Children')); + item7_3_1 = uimenu(c_handle,'Label','local','Callback','spm_orthviews(''context_menu'',''remove_blobs'',2);'); + if varargin{2} == 1, + item7_3_2 = uimenu(c_handle,'Label','global','Callback','spm_orthviews(''context_menu'',''remove_blobs'',1);'); + end; + end; + redraw_all; + + case 'remove_blobs', + cm_handles = valid_handles(1:24); + if varargin{2} == 2, cm_handles = get_current_handle; end; + for i = 1:length(cm_handles), + rmblobs(cm_handles(i)); + c_handle = findobj(findobj(st.vols{cm_handles(i)}.ax{1}.cm,'label','Blobs'),'Label','Remove blobs'); + delete(get(c_handle,'Children')); + set(c_handle,'Visible','off'); + end; + redraw_all; + + case 'add_image', + % Add blobs to the image - in split colortable + cm_handles = valid_handles(1:24); + if varargin{2} == 2, cm_handles = get_current_handle; end; + spm_figure('Clear','Interactive'); + fname = spm_select(1,'image','select image'); + for i = 1:length(cm_handles), + addimage(cm_handles(i),fname); + c_handle = findobj(findobj(st.vols{cm_handles(i)}.ax{1}.cm,'label','Blobs'),'Label','Remove blobs'); + set(c_handle,'Visible','on'); + delete(get(c_handle,'Children')); + item7_3_1 = uimenu(c_handle,'Label','local','Callback','spm_orthviews(''context_menu'',''remove_blobs'',2);'); + if varargin{2} == 1, + item7_3_2 = uimenu(c_handle,'Label','global','Callback','spm_orthviews(''context_menu'',''remove_blobs'',1);'); + end; + end; + redraw_all; + + case 'add_c_blobs', + % Add blobs to the image - in full colour + cm_handles = valid_handles(1:24); + if varargin{2} == 2, cm_handles = get_current_handle; end; + spm_figure('Clear','Interactive'); + [SPM,VOL] = spm_getSPM; + c = spm_input('Colour','+1','m',... + 'Red blobs|Green blobs|Yellow blobs|Cyan blobs|Blue blobs|Magenta blobs',[1 2 3 4 5 6],1); + colours = [1 0 0;0 1 0;1 1 0;0 0 1;0 1 1;1 0 1]; + c_names = {'red';'green';'yellow';'blue';'cyan';'magenta'}; + hlabel = sprintf('%s (%s)',VOL.title,c_names{c}); + for i = 1:length(cm_handles), + addcolouredblobs(cm_handles(i),VOL.XYZ,VOL.Z,VOL.M,colours(c,:)); + addcolourbar(cm_handles(i),numel(st.vols{cm_handles(i)}.blobs)); + c_handle = findobj(findobj(st.vols{cm_handles(i)}.ax{1}.cm,'label','Blobs'),'Label','Remove colored blobs'); + ch_c_handle = get(c_handle,'Children'); + set(c_handle,'Visible','on'); + %set(ch_c_handle,'Visible',on'); + item7_4_1 = uimenu(ch_c_handle(2),'Label',hlabel,'ForegroundColor',colours(c,:),... + 'Callback','c = get(gcbo,''UserData'');spm_orthviews(''context_menu'',''remove_c_blobs'',2,c);',... + 'UserData',c); + if varargin{2} == 1, + item7_4_2 = uimenu(ch_c_handle(1),'Label',hlabel,'ForegroundColor',colours(c,:),... + 'Callback','c = get(gcbo,''UserData'');spm_orthviews(''context_menu'',''remove_c_blobs'',1,c);',... + 'UserData',c); + end; + end; + redraw_all; + + case 'remove_c_blobs', + cm_handles = valid_handles(1:24); + if varargin{2} == 2, cm_handles = get_current_handle; end; + colours = [1 0 0;1 1 0;0 1 0;0 1 1;0 0 1;1 0 1]; + c_names = {'red';'yellow';'green';'cyan';'blue';'magenta'}; + for i = 1:length(cm_handles), + if isfield(st.vols{cm_handles(i)},'blobs'), + for j = 1:length(st.vols{cm_handles(i)}.blobs), + if st.vols{cm_handles(i)}.blobs{j}.colour == colours(varargin{3},:); + if isfield(st.vols{cm_handles(i)}.blobs{j},'cbar') + delete(st.vols{cm_handles(i)}.blobs{j}.cbar); + end + st.vols{cm_handles(i)}.blobs(j) = []; + break; + end; + end; + rm_c_menu = findobj(st.vols{cm_handles(i)}.ax{1}.cm,'Label','Remove colored blobs'); + delete(findobj(rm_c_menu,'Label',c_names{varargin{3}})); + if isempty(st.vols{cm_handles(i)}.blobs), + st.vols{cm_handles(i)} = rmfield(st.vols{cm_handles(i)},'blobs'); + set(rm_c_menu, 'Visible', 'off'); + end; + end; + end; + redraw_all; + + case 'add_c_image', + % Add truecolored image + cm_handles = valid_handles(1:24); + if varargin{2} == 2, cm_handles = get_current_handle;end; + spm_figure('Clear','Interactive'); + fname = spm_select(1,'image','select image'); + c = spm_input('Colour','+1','m','Red blobs|Yellow blobs|Green blobs|Cyan blobs|Blue blobs|Magenta blobs',[1 2 3 4 5 6],1); + colours = [1 0 0;1 1 0;0 1 0;0 1 1;0 0 1;1 0 1]; + c_names = {'red';'yellow';'green';'cyan';'blue';'magenta'}; + hlabel = sprintf('%s (%s)',fname,c_names{c}); + for i = 1:length(cm_handles), + addcolouredimage(cm_handles(i),fname,colours(c,:)); + addcolourbar(cm_handles(i),numel(st.vols{cm_handles(i)}.blobs)); + c_handle = findobj(findobj(st.vols{cm_handles(i)}.ax{1}.cm,'label','Blobs'),'Label','Remove colored blobs'); + ch_c_handle = get(c_handle,'Children'); + set(c_handle,'Visible','on'); + %set(ch_c_handle,'Visible',on'); + item7_4_1 = uimenu(ch_c_handle(2),'Label',hlabel,'ForegroundColor',colours(c,:),... + 'Callback','c = get(gcbo,''UserData'');spm_orthviews(''context_menu'',''remove_c_blobs'',2,c);','UserData',c); + if varargin{2} == 1 + item7_4_2 = uimenu(ch_c_handle(1),'Label',hlabel,'ForegroundColor',colours(c,:),... + 'Callback','c = get(gcbo,''UserData'');spm_orthviews(''context_menu'',''remove_c_blobs'',1,c);',... + 'UserData',c); + end + end + redraw_all; +end; +%_______________________________________________________________________ +%_______________________________________________________________________ +function current_handle = get_current_handle +global st +cm_handle = get(gca,'UIContextMenu'); +cm_handles = get_cm_handles; +current_handle = find(cm_handles==cm_handle); +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function cm_pos +global st +for i = 1:length(valid_handles(1:24)), + if isfield(st.vols{i}.ax{1},'cm') + set(findobj(st.vols{i}.ax{1}.cm,'UserData','pos_mm'),... + 'Label',sprintf('mm: %.1f %.1f %.1f',spm_orthviews('pos'))); + pos = spm_orthviews('pos',i); + set(findobj(st.vols{i}.ax{1}.cm,'UserData','pos_vx'),... + 'Label',sprintf('vx: %.1f %.1f %.1f',pos)); + set(findobj(st.vols{i}.ax{1}.cm,'UserData','v_value'),... + 'Label',sprintf('Y = %g',spm_sample_vol(st.vols{i},pos(1),pos(2),pos(3),st.hld))); + end +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function cm_handles = get_cm_handles +global st +cm_handles = []; +for i=valid_handles(1:24), + cm_handles = [cm_handles st.vols{i}.ax{1}.cm]; +end +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function zoom_all(op) +global st +cm_handles = get_cm_handles; +res = [.125 .125 .25 .5 1 1]; +if op==6, + st.bb = maxbb; +else, + vx = sqrt(sum(st.Space(1:3,1:3).^2)); + vx = vx.^(-1); + pos = spm_orthviews('pos'); + pos = st.Space\[pos ; 1]; + pos = pos(1:3)'; + if op == 5, st.bb = [pos-80*vx ; pos+80*vx] ; + elseif op == 4, st.bb = [pos-40*vx ; pos+40*vx] ; + elseif op == 3, st.bb = [pos-20*vx ; pos+20*vx] ; + elseif op == 2, st.bb = [pos-10*vx ; pos+10*vx] ; + elseif op == 1; st.bb = [pos- 5*vx ; pos+ 5*vx] ; + else disp('no Zoom possible'); + end; +end +resolution(res(op)); +redraw_all; +for i = 1:length(cm_handles) + z_handle = get(findobj(cm_handles(i),'label','Zoom'),'Children'); + set(z_handle,'Checked','off'); + set(z_handle(op),'Checked','on'); +end +return; + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% ROI: TimeSeries +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_searchContentEdit(hObject, eventdata) +handles = guidata(hObject); +set(handles.searchContentEdit,'UserData', 'manual'); + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% click load SPM file button +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_loadSPMmat(hObject, eventdata, spmfile) +handles = guidata(hObject); +if nargin < 3 | isempty(spmfile) + %if exist(fullfile(pwd,'SPM.mat'), 'file') + if findstr('SPM2',spm('ver')) + spmfile = spm_get([1],'SPM.mat','Select a SPM file'); + elseif findstr('SPM5',spm('ver')) + spmfile = spm_select(1,'SPM.mat','Select a SPM file'); + end +end +xSPM=load(spmfile); +if isfield(xSPM.SPM, 'xCon') + numc=listdlg('ListString' , {xSPM.SPM.xCon.name}, 'SelectionMode' , 'single', 'InitialValue', 2);%length(xSPM.SPM.xCon)); + if ~exist(fullfile(xSPM.SPM.swd,xSPM.SPM.xCon(numc).Vspm.fname)) & ... + exist(subarray(fullfile(xSPM.SPM.swd,xSPM.SPM.xCon(numc).Vspm.fname),1:2,-2)) + warning('Changing path') + xSPM.SPM.swd=xSPM.SPM.swd(3:end); + end + if ~isempty(numc) + if exist(xSPM.SPM.swd, 'dir') + CallBack_loadImagePush(handles.loadImagePush, [],... + {fullfile(xSPM.SPM.swd,xSPM.SPM.xCon(numc).Vspm.fname)},... + {xSPM.SPM.xCon(numc).name}); + else + CallBack_loadImagePush(handles.loadImagePush, [],... + {fullfile(fileparts(spmfile),xSPM.SPM.xCon(numc).Vspm.fname)},... + {xSPM.SPM.xCon(numc).name}); + end + end +else + msgbox('No contrast in this file') +end +return + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% paramest push (Parameter estimates) +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_indivResultsListPush(hObject, eventdata) +handles = guidata(hObject); +if ischar(handles.imageFileName) + handles.imageFileName={handles.imageFileName}; +end +[datadir,condir]=fileparts(handles.imageFileName{1}); + +%MG 15.09.2008 if datadir(2)==':' +% datadir=datadir(3:end); +% end +%--MG +[datadir,subdir]=fileparts(datadir); +if isempty(findstr('indiv/', datadir)) +elseif isempty(findstr('rfx/', datadir)) + xSPM=load(fullfile(datadir,'SPM.mat')); + indivdir=xSPM.xY.P; +else + return +end + +req = get(handles.indivResultsListPush,'String'); +req = req{get(handles.indivResultsListPush, 'value')}; + +if isequal(req,'Group') + datadir=fileparts(datadir); + condir=handles.imageContrastName{1}; + condir=strrep(condir, '>', 'vs'); + condir=strrep(condir, '<', 'vs'); + condir=strrep(condir, '+', '_'); + condir=strrep(condir, ':', ' at'); + condir=deblank(condir); + condir=fullfile(datadir,'rfx',condir); + if ~exist(condir,'dir') + return + end + xSPM=load(fullfile(condir,'SPM.mat')); + numc=2; + CallBack_loadImagePush(handles.loadImagePush, [],... + {fullfile(datadir,req,xSPM.SPM.xCon(numc).Vspm.fname)},...%MG:02.04.2008{fullfile(xSPM.SPM.swd,xSPM.SPM.xCon(numc).Vspm.fname)},... + {xSPM.SPM.xCon(numc).name}); + return +elseif exist('swds','var') + CallBack_loadImagePush(handles.loadImagePush, [],... + swds(get(gcbo, 'value'),:),... + handles.imageContrastName); +else + + %MG 15.09.2008 + if strmatch('.',req,'exact')>0 + return + elseif strmatch('..', req,'exact')>0 + newdir=spm_select(1,'dir','...Select directory containing the new SPM.mat!',''); newdir=fileparts(newdir) + eval(['cd ' newdir]) + xSPM=load(spm_select('List',newdir,'SPM.mat')) + [newdatadir,newreq]=fileparts(newdir); + datadir=newdatadir; + req=newreq; + newnumc=length(xSPM.SPM.xCon); + numc=newnumc; + elseif strmatch('GROUP',req)>0 + newdir=spm_select(1,'dir','...Select directory containing the new SPM.mat!',''); newdir=fileparts(newdir) + eval(['cd ' newdir]) + xSPM=load(spm_select('List',newdir,'SPM.mat')) + [newdatadir,newreq]=fileparts(newdir); + datadir=newdatadir; + req=newreq; + newnumc=length(xSPM.SPM.xCon); + numc=newnumc; + +% xSPM=load(spm_select(1,'any','select SPM.mat!','',datadir,'SPM.mat')) +% [newdatadir,newreq]=fileparts(xSPM.SPM.swd); +% datadir=newdatadir; +% req=newreq; +% newnumc=length(xSPM.SPM.xCon); +% numc=newnumc; +% cd([datadir '\' req]) + else + cd([datadir '\' req]) + xSPM=load(fullfile(datadir,req,'SPM.mat')); + end + %--MG + + if ~isfield(handles,'imageContrastName') + return + end + % numc=strmatch(handles.imageContrastName{1},{xSPM.SPM.xCon.name},'exact'); + [datadir1,req1]=fileparts(datadir); + if strfind('GROUP',req1)>0 + numc=2; + else + numc=get(handles.contrastListPush, 'Value'); + end + %%MG 02.04.2008 filename=fullfile(xSPM.SPM.swd,xSPM.SPM.xCon(numc).Vspm.fname); + filename=fullfile(datadir,req,xSPM.SPM.xCon(numc).Vspm.fname) + %--MG + + if filename(2)==':' + filename=filename(3:end); + end + set(handles.sectionViewListbox, 'Value', [1]); + % handles.sectionViewTargetFile = fullfile('\ndiayek\data\gazemo\rawdata\',req,'anat','wf_0001.img'); + guidata(hObject, handles); + CallBack_loadImagePush(handles.loadImagePush, [],... + {filename},... + {xSPM.SPM.xCon(numc).name}); + +% CallBack_allIntensityRadio(hObject, eventdata, 'c'); +end + +%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% paramest push (Parameter estimates) +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_paramestRegionPush(hObject, eventdata, roi,readdata) +if nargin<4 + readdata=1; +end +roi=struct('type', roi); +handles = guidata(hObject); +roi.name=get(handles.structureEdit, 'String'); + +[img.p, img.fname, img.ext]=fileparts(handles.imageFileName{1}); +spmfile=fullfile(img.p,'SPM.mat'); + +if exist(spmfile(3:end), 'file') + warning('Possible disk swapping in xjview.m'); + spmfile=spmfile(3:end); +end +if ~exist(spmfile) + if findstr('SPM2',spm('ver')) + spmfile = spm_get([0 1],'SPM.mat','locate the RFX SPM.mat'); + elseif findstr('SPM5',spm('ver')) + spmfile = spm_select([0:1],'SPM.mat','locate the RFX SPM.mat'); + end +end +if ~exist(spmfile) + return +end +xSPM=load(spmfile, 'SPM'); + + +[glmpath, glmpath]=fileparts(fileparts(fileparts(xSPM.SPM.swd))); + +%[xyzmm,i] = spm_XYZreg('NearestXYZ',... +% spm_results_ui('GetCoords'),handles.currentxyz); +%spm_results_ui('SetCoords',xSPM.XYZmm(:,i)); +switch roi.type + case 'vox' + if not(isfield(handles,'currentxyz')) %%% + error('Wrong selection') + return + end + xyz = handles.currentxyz; + case 'clu' + if not(isfield(handles,'selectedCluster')) | not(isempty(handles.selectedCluster)) + switch questdlg('Load from workspace','cluster?', 'currentDisplayMNI{1}','...','Cancel','currentDisplayMNI{1}') + case 'currentDisplayMNI{1}' + xyz=evalin('base', 'currentDisplayMNI{1}'); + case '...' + case 'Cancel' + return + end + delete(findobj('Tag', 'paramest')) + + else + delete(findobj('Tag', 'paramest')) + xyz =handles.currentDisplayMNI{1}; + end + case 'sph' + if not(isfield(handles,'currentxyz')) %%% + error('Wrong selection') + return + end + delete(findobj('Tag', 'paramest')) + xyz = handles.currentxyz; + roi.radius=inputdlg('Radius of the sphere?'); + if isempty(roi.radius) + return + end + roi.radius=str2num(roi.radius{1}) + % XYZmm = xSPM.SPM.xVol.M(1:3,:)*[xSPM.SPM.xVol.XYZ; ones(1,size(xSPM.SPM.xVol.XYZ,2))]; + % xyz = sqrt( XYZmm-repmat(xyz,[1 size(XYZmm,2)]) ) + % xyz = XYZmm(:, xyz < roi.radius) + +end +% handles.currentDisplayMNI{1} +% ha=axes('Tag', 'paramest', 'Parent',gcf,'units','normalized','Position',[0.55, 0.05, 0.4, 0.4]); +if isequal(roi.type, 'sph') + if roi.radius > 20 + if not(spm_input({'Many many voxels may be retrieve','Radius of the sphere (in mm):',... + num2str(roi.radius),... + 'Continue anyway?'},... + 1,'bd',{'OK','NO'},[1,0])) + return + end + end + +end +if size(xyz,1)>20 + if not(spm_input({'Many many voxels to retrieve','Number of voxel:',... + num2str(size(xyz,1)),... + 'Continue anyway?'},... + 1,'bd',{'OK','NO'},[1,0])) + return + end +end + +allbetas= []; +allxyz = []; +roi.nvox = []; +hwait = waitbar(0,'Reading 1st level data'); +lcd=cd; +handles.currentxyz +if isfield(handles, 'paramest') + handles.paramest=[]; +end + +roi.stat.TF=handles.TF; +roi.stat.pValue=handles.pValue; +roi.stat.df=handles.df; +% roi.stat.Intensity = +% handles.currentDisplayIntensity{1}(find(all(handles.currentDisplayMNI{1}==repmat(handles.currentxyz, 3,1),2))); +[i,j]=ismember(handles.mni{1}, flipud(xyz), 'rows'); +roi.stat.intensity(j(i)) = handles.intensity{1}(i); + +if isstruct(xSPM.SPM.xX.K) | ~readdata + n=1; + % Ic=strmatch('F Task',{xSPM.SPM.xCon.name}) + % + % allbetas=[] + % vcon= + % XYZ = SPM.xVol.XYZ; + % XYZmm = SPM.xVol.M(1:3,:)*[XYZ; ones(1,size(XYZ,2))]; + % tmpallbetas= []; + % [nxyz,i] = spm_XYZreg('NearestXYZ',xyz(cluvox,:),XYZmm); + % allbetas = [allbetas;(mean(tmpallbetas,1))]; + % allxyz= [allxyz;mean(tmpallxyz,1)]; + +else + n= length(xSPM.SPM.xY.P) +end + +for sub = 1:n + + waitbar(sub/n,hwait); + if n>1 + subdir = fileparts(xSPM.SPM.xY.P{sub}); + else + subdir = xSPM.SPM.swd; + end + try + if ~exist(fullfile(subdir, 'SPM.mat'), 'file') & ... + exist(subarray(fullfile(subdir, 'SPM.mat'),1:2,-2),'file') + subdir = subdir(3:end); + end + load(fullfile(subdir, 'SPM.mat')); + fprintf('Retrieving betas from: %s\n', fullfile(subdir, 'SPM.mat')) + + if sub==1 + % [cn.path cn.fname cn.ext]=fileparts(char(xSPM.SPM.xY.P(sub,:))); + % consfiles=[SPM.xCon.Vcon]; + Ic = [strmatch('f anim', lower({SPM.xCon.name})) strmatch('f task', lower({SPM.xCon.name})) strmatch('f map', lower({SPM.xCon.name}))]; + if isempty(Ic) + Ic = listdlg('ListString',{SPM.xCon.name},'InitialValue',Ic); + end + Ic=Ic(end); + conname=SPM.xCon(Ic).name; + end + + %------- + Ic = strmatch(conname,{SPM.xCon.name}, 'exact') ; %contrast number + if isempty(Ic) + Ic = listdlg('ListString',{SPM.xCon.name}); + end + %------- + + XYZ = SPM.xVol.XYZ; + XYZmm = SPM.xVol.M(1:3,:)*[XYZ; ones(1,size(XYZ,2))]; + tmpallbetas= []; + tmpallxyz = []; + cd(subdir) + for cluvox = 1:size(xyz,1) + switch (roi.type) + case 'sph' + [d] = spm_XYZreg('Edist',xyz(cluvox,:),XYZmm); + i=find(d<=roi.radius); + govox = ~isempty(i); + nxyz=XYZmm(:,i); + case {'vox', 'clu'} + govox = 1; + [nxyz,i] = spm_XYZreg('NearestXYZ',xyz(cluvox,:),XYZmm); + if sqrt(sum((nxyz'-xyz(cluvox,:)).^2))>=sqrt(3) %one voxel in each dimZ + govox= spm_input({'No data stored for this voxel','Closest voxels with data are:',... + num2str(xyz(cluvox,:)),... + 'Continue anyway?'},... + 1,'bd',{'OK','NO'},[1,0]); + end + end + if govox==1 + vXYZ = XYZ(:,i) ; % coordinates in voxels + %-Get parameter and hyperparameter estimates + %======================================================================= + beta = spm_get_data(SPM.Vbeta, vXYZ); + % ResMS = spm_get_data(SPM.VResMS,vXYZ); + % Bcov = ResMS*SPM.xX.Bcov; + CI = 1.6449; % = spm_invNcdf(1 - 0.05); + % compute contrast of parameter estimates and 90% C.I. + %-------------------------------------------------------------- + tmpallbetas = [tmpallbetas; (SPM.xCon(Ic).c'*beta)']; + tmpallxyz = [tmpallxyz; nxyz']; + end + end + allbetas = [allbetas;(mean(tmpallbetas,1))]; + allxyz= [allxyz;mean(tmpallxyz,1)]; + roi.nvox = [ roi.nvox; size(tmpallbetas,1)]; + tmp = mean(tmpallxyz); + % disp(['meancluster = ' num2str(mean(tmpallxyz,1))]); + catch + warning('Error with data from: %s', subdir) + end +end +close(hwait) +cd(lcd); +% +regnames=cellstr(subarray(strvcat(strrep(SPM.xX.name(any(SPM.xCon(Ic).c )),'*bf(1)', '')'), 1:6, -2)); +regnames=cellstr(subarray(strvcat(strrep(SPM.xX.name(any(SPM.xCon(Ic).c')),'*bf(1)', '')'), 1:6, -2)); +[regnames, ireg,ireg2]=unique(regnames); +a=[ repmat(' ',length(regnames),1) strvcat(strrep(regnames, '_', ' '))]; +a=dataread('string',a', '%s'); +leg=a(isort(ireg)); +%a=reshape(a, [],108])'; +% +% n=factor(size(allbetas,2)); +% n=sort([n(end) prod(n(1:end-1))]); +% +% prompt={'Enter the matrix size for x^2:','Enter the colormap name:'}; +% def={'20','hsv'}; +% dlgTitle='Input for Peaks function'; +% lineNo=1; +% answer=inputdlg(prompt,dlgTitle,lineNo,def); +% +% AddOpts.Resize='on'; +% AddOpts.WindowStyle='normal'; +% AddOpts.Interpreter='tex'; +% answer=inputdlg(prompt,dlgTitle,lineNo,def,AddOpts); +% +assignin('base', 'allbetas', allbetas) +ReshapeData = []; +xbars=[]; +fxorder=[2 3 1]; +switch size(allbetas,2) + case 8 + ReshapeData = [4 2]; + case 4 + if all(ismember(a,{'Neutral' 'Fearful' 'Angry' 'Happy'})) + ReshapeData = [4 1]; + end + case 18 + ReshapeData = [18 1]; + case 16 + % ReshapeData = [2 8]; + % xtick=[1 3 4 5 7 8 9 11]; + % fxorder=[3 2 1]; + ReshapeData = [16 1]; +end +if isempty(ReshapeData) + ReshapeData = [fliplr(factor(size(allbetas,2))) 1]; + ReshapeData = [ ReshapeData(1) prod(ReshapeData(2:end))] +end +allbetas=reshape(allbetas, [],ReshapeData(1),ReshapeData(2)); +allbetas = permute(allbetas,fxorder); +% allxyz=reshape(allxyz, [], 1, 3); +% allxyz=permute(allxyz, [3 2 1]); + +%assignin('base', 'allbetas', allbetas) +%assignin('base', 'allxyz', xyz) +roi.allbetas=allbetas; +roi.allxyz=allxyz'; +roi.XYZmm=xyz'; +roi.xyz=mean(allxyz,1)'; +assignin('base', 'roi', roi) + +%%%%%%%%%%%%%%%%%%%%%%%% +% Plot betas in figures >> EDIT MG: +try + plot_betas(roi,glmpath,handles) +catch + if n>1 + figure;barerrorbar(1:size(roi.allbetas,1),mean(roi.allbetas, ndims(roi.allbetas)),stderrw(roi.allbetas, 1:2, ndims(roi.allbetas)),NaN) + else + figure;bar(roi.allbetas) + end + mytitle=([roi.xyz(1,:) roi.xyz(2,:) roi.xyz(3,:)]); + title(['Coordinates (',num2str(mytitle),')']) + set(gca, 'XTickLabel', {'FULL_R' 'FULL_L' 'CHIM_b' 'CHIM_R' 'CHIM_L' 'DJ_b' 'DJ_R' 'DJ_L' 'UR' 'URx' 'UL' 'ULx' 'Rest'}) + xlabel('Condition') + ylabel('beta parameter') + legend({'Session1' 'Session2' 'Session3'},'Location', 'Best') +end + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% paramest push (Parameter estimates) +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function [c_num, c_let, c_word]=mycolourset +c_num = [1 0 0;0 1 0;1 1 0;0 1 1;0 0 1;1 0 1]; +c_let = {'r';'g';'y';'c';'b';'m'}; +c_word= {'red';'green';'yellow';'cyan';'blue';'magenta'}; \ No newline at end of file diff --git a/xjview_v4.m b/xjview_v4.m new file mode 100644 index 0000000..bce66ca --- /dev/null +++ b/xjview_v4.m @@ -0,0 +1,8669 @@ +function xjview(varargin) +% xjview, version 4 +% +% usage 1: xjview (no argument) +% for displaying a result img file, or multiple image files, +% (which will be loaded later) and changing p-value or t/f-value +% usage 2: xjview(imagefilename) +% for displaying the result img file and changing p-value or +% t-value +% Example: xjView spmT_0002.img +% xjView('spmT_0002.img') +% xjView mymask.img +% usage 3: xjview(imagefilename1, imagefilename2, ...) +% for displaying the result img files and changing p-value or +% t/f-value +% Example: xjView spmT_0002.img spmT_0005.img spmT_0007.img +% xjView('spmT_0002.img', 'spmT_0003.img', 'spmT_0006.img') +% xjView myMask1.img myMask2.img myMask3.img +% usage 4: xjview(mnicoord, intensity) +% for displaying where are the mni coordinates +% mnicoord: Nx3 matrix of mni coordinates +% intensity: (optional) Nx1 matrix, usually t values of the +% corresponding voxels +% Example: xjView([20 10 1; -10 2 5],[1;2]) +% xjView([20 10 1; -10 2 5]) +% Note: to use xjview this way, you may need to modify the value +% of M and DIM in the begining of xjview.m +% +% http://people.hnl.bcm.tmc.edu/cuixu/xjView +% +% by Xu Cui and Jian Li 2/21/2005 +% last modified: 02/18/2007 (add colorbar max control) +% last modified: 11/16/2006 (keyboard shortcut for open image and open roi file) +% last modified: 06/16/2006 (spm5 compatible) +% last modified: 05/30/2006 (left/right flip, path of mask.img and templateFile.img) +% last modified: 05/08/2006 (debug CallBack_volumePush function, change handles.intensity{1} to intensity) +% last modified: 04/03/2006 (modify tr) +% last modified: 12/28/2005 (modify SPM process) +% +% Thank Sergey Pakhomov for sharing his database (MNI Space Utility). +% Thank Yuval Cohen for the maximize figure function (maximize.m) +% + +% TODO +% Send SPM to workspace + +warnstate = warning; +warning off; + +% pre-set values +% important! you need compare the display of xjview and spm. If you find +% xjview flipped the left/right, you need to set leftrightflip = 1; +% otherwise leave it to 0. +leftrightflip = 0; +% leftrightflip = 1 ; %must be on in the CMU... +% Now with spm-flip = 1; xjview-flip=1 + +% You only need to change M and DIM when you want to use xjview under +% 'usage 4'. +M = ... + [-4 0 0 84;... + 0 4 0 -116;... + 0 0 4 -56;... + 0 0 0 1]; +DIM = [41 48 35]'; +TR = 2; % you don't need to set TR is you only use the viewing part of xjview + +% system settings +try + spmdir = spm('dir'); + spm('defaults', 'fmri'); +catch + disp('Please add spm path.'); + warning(warnstate(1).state); + return +end + +if ispc + os = 'windows'; +elseif isunix + os = 'linux'; +else + warndlg('I don''t know what kind of computer you are using. I assumed it is unix.', 'What computer are you using?'); + os = 'linux'; +end +screenResolution = get(0,'ScreenSize'); + +xjviewpath = fileparts(which('xjview')); + +% pre-set values +pValue = 0.001; +intensityThreshold = 0; +clusterSizeThreshold = 5; + + +% Appearance Settings +figurePosition = [0.100, 0.050, 0.660, 0.880]; +sectionViewPosition = [0.5,0.61,0.45,0.45]; +glassViewAxesPosition = [0.000, 0.600, 0.464, 0.400]; +if screenResolution(3) <= 1024 + figurePosition = [0.100, 0.050, 0.700, 0.900]; + sectionViewPosition = [0.5, 0.61, 0.45, 0.46]; + glassViewAxesPosition = [0.000, 0.600, 0.464, 0.400]; +end + +left = 0.01; +editBoxHeight = 0.05; +editBoxWidth = 0.200; +editBoxLeft = 0.100; + +controlPanelPosition = [left, 0.080, 0.500, 0.500]; +stretchMatrix = diag([controlPanelPosition(3),controlPanelPosition(4),controlPanelPosition(3),controlPanelPosition(4)]); +controlPanelOffset = controlPanelPosition' .* [1,1,0,0]'; +heightUnit = 0.055; + +sliderPosition = stretchMatrix*[0.000, 0*heightUnit, 1.000, editBoxHeight]' + controlPanelOffset; +pValueTextPosition = stretchMatrix*[0.000, 1*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +pValueEditPosition = stretchMatrix*[0.110, 1*heightUnit, editBoxWidth*4/3, editBoxHeight]' + controlPanelOffset; +intensityThresholdTextPosition = stretchMatrix*[0.400, 1*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +intensityThresholdEditPosition = stretchMatrix*[0.520, 1*heightUnit, editBoxWidth*3/3, editBoxHeight]' + controlPanelOffset; +dfTextPosition = stretchMatrix*[0.740, 1*heightUnit, 0.8-0.74, editBoxHeight]' + controlPanelOffset; +dfEditPosition = stretchMatrix*[0.800, 1*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +clusterSizeThresholdTextPosition = stretchMatrix*[0.000, 2*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +clusterSizeThresholdEditPosition = stretchMatrix*[0.150, 2*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +pickThisClusterPushPosition = stretchMatrix*[0.400, 2*heightUnit, editBoxWidth*1, editBoxHeight]' + controlPanelOffset; +selectThisClusterPushPosition = stretchMatrix*[0.600, 2*heightUnit, editBoxWidth*1, editBoxHeight]' + controlPanelOffset; +clearSelectedClusterPushPosition = stretchMatrix*[0.800, 2*heightUnit, editBoxWidth*1, editBoxHeight]' + controlPanelOffset; +thisClusterSizeTextPosition = stretchMatrix*[0.600, 2*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +thisClusterSizeEditPosition = stretchMatrix*[0.800, 2*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +loadImagePushPosition = stretchMatrix*[0.000, 3*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +imageFileEditPosition = stretchMatrix*[0.200, 3*heightUnit, 1-editBoxWidth, editBoxHeight]' + controlPanelOffset; +saveImagePushPosition = stretchMatrix*[0.000, 4*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +saveImageFileEditPosition = stretchMatrix*[0.200, 4*heightUnit, 1-editBoxWidth, editBoxHeight]' + controlPanelOffset; +saveResultPSPushPosition = stretchMatrix*[0.000, 5*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +resultPSFileEditPosition = stretchMatrix*[0.200, 5*heightUnit, 1-editBoxWidth, editBoxHeight]' + controlPanelOffset; +displayIntensityTextPosition = stretchMatrix*[0.000, 3*heightUnit, editBoxWidth+0.1, editBoxHeight]' + controlPanelOffset; +allIntensityRadioPosition = stretchMatrix*[0.250, 3*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +positiveIntensityRadioPosition = stretchMatrix*[0.400, 3*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +negativeIntensityRadioPosition = stretchMatrix*[0.550, 3*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +renderViewCheckPosition = stretchMatrix*[0.780, 3*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +hideControlPushPosition = stretchMatrix*[0.200, 4*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +volumePushPosition = stretchMatrix*[0.000, 4*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +commonRegionPushPosition = stretchMatrix*[0.200, 4*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +%knd +paramestPushPosition = stretchMatrix*[0.800, 4*heightUnit, editBoxWidth*.5, editBoxHeight]' + controlPanelOffset; +%-- +displayPushPosition = stretchMatrix*[0.200, 4*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +allinonePushPosition = stretchMatrix*[0.400, 4*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +searchPushPosition = stretchMatrix*[0.000, 6*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +searchContentEditPosition = stretchMatrix*[0.200, 6*heightUnit, editBoxWidth*2, editBoxHeight]' + controlPanelOffset; +searchTextPosition = stretchMatrix*[0.600, 6*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +searchEnginePopPosition = stretchMatrix*[0.600, 6*heightUnit, 1-0.6, editBoxHeight]' + controlPanelOffset; +overlayPushPosition = stretchMatrix*[0.000, 5*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +overlayEditPosition = stretchMatrix*[0.200, 5*heightUnit, 0.6-editBoxWidth, editBoxHeight]' + controlPanelOffset; +overlayPopPosition = stretchMatrix*[0.600, 5*heightUnit, 1-0.6, editBoxHeight]' + controlPanelOffset; +helpPosition = stretchMatrix*[0.800, 5*heightUnit, editBoxWidth, editBoxHeight]' + controlPanelOffset; +infoTextBoxPosition = stretchMatrix*[0.000, 8*heightUnit, 1, editBoxHeight*9]' + controlPanelOffset; +%xjViewPosition = stretchMatrix*[0.400, 13*heightUnit, editBoxWidth*2.5, editBoxHeight*3]' + controlPanelOffset; +%connameTextBoxPosition = stretchMatrix*[0.000, 8*heightUnit, 1, editBoxHeight*9]' + controlPanelOffset; + +sectionViewListboxPosition = [sectionViewPosition(1)+0.4, sectionViewPosition(2)+0.02, 0.1, 0.14]; +sectionViewMoreTargetPushPosition = [sectionViewListboxPosition(1),sectionViewListboxPosition(2)-0.02,0.10,0.02]; +xHairCheckPosition = [sectionViewListboxPosition(1),sectionViewListboxPosition(2)+0.14,0.15,0.02]; +setTRangeEditPosition = [sectionViewListboxPosition(1),sectionViewListboxPosition(2)-0.06,0.10,0.02]; +setTRangeTextPosition = [sectionViewListboxPosition(1),sectionViewListboxPosition(2)-0.04,0.10,0.02]; + +getStructurePushPosition = [glassViewAxesPosition(1), glassViewAxesPosition(2)-0.06, editBoxWidth/2, editBoxHeight/2]; +structureEditPosition = [getStructurePushPosition(1), getStructurePushPosition(2), 1, getStructurePushPosition(4)]; +framePosition = (controlPanelPosition - controlPanelOffset')*1.05 + 0.95*controlPanelOffset'; + +%knd +setContrastNameTextPosition = [glassViewAxesPosition(1),glassViewAxesPosition(2)-0.026,0.6,0.03]; +setContrastListPosition = [setContrastNameTextPosition(1)+setContrastNameTextPosition(3)+.01,setContrastNameTextPosition(2),0.05,setContrastNameTextPosition(4)]; +setContrastListPosition = [glassViewAxesPosition(1)+.05*glassViewAxesPosition(3),glassViewAxesPosition(2)-0.026,glassViewAxesPosition(3)*.95,0.03]; +setIndivResultsListPosition = [setTRangeTextPosition(1)-0.25,setTRangeEditPosition(2),0.20,setTRangeEditPosition(4)]; +%-- knd + +% draw figure and control +figureBKcolor=[176/255 252/255 188/255]; +figureBKcolor=get(0,'Defaultuicontrolbackgroundcolor'); +f = figure('unit','normalized','position',figurePosition,'Color',figureBKcolor,'defaultuicontrolBackgroundColor', figureBKcolor,... + 'Name','xjView', 'Tag', 'xjView', 'NumberTitle','off','resize','off','CloseRequestFcn', {@CallBack_quit, warnstate(1).state}, 'visible','off'); +handles = guihandles(f); + +% databases +try + addpath g:\ndiayek\data\atlases\ + addpath ~/data/atlases + addpath /mount/usb/data/atlases +end +try + X = load('TDdatabase'); + handles.DB = X.DB; + handles.wholeMaskMNIAll = X.wholeMaskMNIAll; +catch + handles.DB = []; + handles.wholeMaskMNIAll = struct([]); + errordlg('I can''t find TDdatabase.mat','TDdatabase not found'); +end + +handles.figure = f; +handles.frame = uicontrol(handles.figure,'style','frame',... + 'unit','normalized',... + 'position',framePosition,... + 'Visible','off'); +handles.slider = uicontrol(handles.figure,'style','slider',... + 'unit','normalized',... + 'position',sliderPosition,... + 'max',1,'min',0,... + 'sliderstep',[0.01,0.10],... + 'callback',@CallBack_slider,... + 'value',0,'Visible','on'); +handles.pValueTextPosition = uicontrol(handles.figure,'style','text',... + 'unit','normalized','position',pValueTextPosition,... + 'string','pValue=','horizontal','left'); +handles.pValueEdit = uicontrol(handles.figure,'style','edit',... + 'unit','normalized','position',pValueEditPosition,... + 'horizontal','left',... + 'String', num2str(pValue),... + 'BackgroundColor', 'w',... + 'callback',@CallBack_pValueEdit); +handles.intensityThresholdText = uicontrol(handles.figure,'style','text',... + 'unit','normalized','position',intensityThresholdTextPosition,... + 'string',' intensity=','horizontal','left'); +handles.intensityThresholdEdit = uicontrol(handles.figure,'style','edit',... + 'unit','normalized','position',intensityThresholdEditPosition,... + 'horizontal','left',... + 'BackgroundColor', 'w',... + 'String', num2str(intensityThreshold),... + 'callback',@CallBack_intensityThresholdEdit); +handles.dfText = uicontrol(handles.figure,'style','text',... + 'unit','normalized','position',dfTextPosition,... + 'string','df= ','horizontal','right'); +handles.dfEdit = uicontrol(handles.figure,'style','edit',... + 'unit','normalized','position',dfEditPosition,... + 'horizontal','left',... + 'BackgroundColor', 'w',... + 'String', '',... + 'callback',@CallBack_dfEdit); +handles.clusterSizeThresholdText = uicontrol(handles.figure,'style','text',... + 'unit','normalized','position',clusterSizeThresholdTextPosition,... + 'string','cluster size >=','horizontal','left'); +handles.clusterSizeThresholdEdit = uicontrol(handles.figure,'style','edit',... + 'unit','normalized','position',clusterSizeThresholdEditPosition,... + 'horizontal','left',... + 'BackgroundColor', 'w',... + 'String', num2str(clusterSizeThreshold),... + 'callback',@CallBack_clusterSizeThresholdEdit); +handles.thisClusterSizeText = uicontrol(handles.figure,'style','text',... + 'unit','normalized','position',thisClusterSizeTextPosition,... + 'string','size= ','horizontal','right', 'visible', 'off'); +handles.thisClusterSizeEdit = uicontrol(handles.figure,'style','edit',... + 'unit','normalized','position',thisClusterSizeEditPosition,... + 'horizontal','left',... + 'Enable', 'inactive',... + 'String', '','visible','off'); + +handles.imageFileEdit = uicontrol(handles.figure,'style','edit',... + 'unit','normalized','position',imageFileEditPosition,... + 'horizontal','left',... + 'String', '',... + 'BackgroundColor', 'w',... + 'callback',@CallBack_imageFileEdit,... + 'visible','off'); +handles.saveImageFileEdit = uicontrol(handles.figure,'style','edit',... + 'unit','normalized','position',saveImageFileEditPosition,... + 'horizontal','left',... + 'BackgroundColor', 'w',... + 'String', 'myMask.img',... + 'callback',@CallBack_saveImageFileEdit,... + 'visible','off'); +handles.saveResultPSEdit = uicontrol(handles.figure,'style','edit',... + 'unit','normalized','position',resultPSFileEditPosition,... + 'horizontal','left',... + 'BackgroundColor', 'w',... + 'String', 'myResult.ps',... + 'callback',@CallBack_saveResultPSEdit,... + 'visible','off'); +handles.loadImagePush = uicontrol(handles.figure,'style','push',... + 'unit','normalized','position',loadImagePushPosition,... + 'string','Load Image','callback',@CallBack_loadImagePush,... + 'visible','off'); +handles.saveImagePush = uicontrol(handles.figure,'style','push',... + 'unit','normalized','position',saveImagePushPosition,... + 'string','Save Image','callback',@CallBack_saveImagePush,... + 'visible','off'); +handles.saveResultPSPush = uicontrol(handles.figure,'style','push',... + 'unit','normalized','position',saveResultPSPushPosition,... + 'string','Save Result','callback',@CallBack_saveResultPSPush,... + 'visible','off'); +handles.getStructurePush = uicontrol(handles.figure,'style','push',... + 'unit','normalized','position',getStructurePushPosition,... + 'string','Get Structure','callback',@CallBack_getStructurePush,'visible','off'); +handles.structureEdit = uicontrol(handles.figure,'style','edit',... + 'unit','normalized','position',structureEditPosition,... + 'horizontal','center',... + 'enable', 'on',... + 'UserData',struct(... + 'hReg', [],... + 'M', M,... + 'D', DIM,... + 'xyz', [0 0 0] )); +handles.pickThisClusterPush = uicontrol(handles.figure,'style','push',... + 'unit','normalized','position',pickThisClusterPushPosition,... + 'string','Pick Cluster/Info','callback',@CallBack_pickThisClusterPush); +handles.selectThisClusterPush = uicontrol(handles.figure,'style','push',... + 'unit','normalized','position',selectThisClusterPushPosition,... + 'string','Select Cluster','callback',@CallBack_selectThisClusterPush); +handles.clearSelectedClusterPush = uicontrol(handles.figure,'style','push',... + 'unit','normalized','position',clearSelectedClusterPushPosition,... + 'string','Clear Selection','callback',@CallBack_clearSelectedClusterPush); + +handles.displayIntensityText = uicontrol(handles.figure,'style','text',... + 'unit','normalized','position',displayIntensityTextPosition,... + 'string','display intensity','horizontal','left'); +handles.allIntensityRadio = uicontrol(handles.figure,'style','radio',... + 'unit','normalized',... + 'string','All',... + 'position',allIntensityRadioPosition,... + 'value', 1,... + 'callback',@CallBack_allIntensityRadio); +handles.positiveIntensityRadio = uicontrol(handles.figure,'style','radio',... + 'unit','normalized',... + 'string','Only +',... + 'position',positiveIntensityRadioPosition,... + 'callback',{@CallBack_allIntensityRadio,'+'}); +handles.negativeIntensityRadio = uicontrol(handles.figure,'style','radio',... + 'unit','normalized',... + 'string','Only -',... + 'position',negativeIntensityRadioPosition,... + 'callback',{@CallBack_allIntensityRadio,'-'}); +handles.renderViewCheck = uicontrol(handles.figure,'style','checkbox',... + 'unit','normalized',... + 'string','Render View' ,... + 'horizontal', 'right',... + 'position',renderViewCheckPosition,... + 'callback', @CallBack_renderViewCheck); + +handles.sectionViewListbox = uicontrol(handles.figure,'style','listbox',... + 'unit','normalized',... + 'String', {'single T1','avg152PD','avg152T1','avg152T2','avg305T1','ch2','ch2bet','aal','brodmann'}, ... + 'value',3,... + 'position',sectionViewListboxPosition,... + 'callback',@CallBack_sectionViewListbox); + +handles.xHairCheck = uicontrol(handles.figure,'style','checkbox',... + 'unit','normalized',... + 'string','XHairs Off' ,... + 'horizontal','left',... + 'position',xHairCheckPosition,... + 'callback',@CallBack_xHairCheck); +handles.sectionViewMoreTargetPush = uicontrol(handles.figure,'style','push',... + 'unit','normalized','position',sectionViewMoreTargetPushPosition,... + 'string','other ...','callback',@CallBack_sectionViewMoreTargetPush); +handles.setTRangeEdit = uicontrol(handles.figure,'style','edit',... + 'unit','normalized','position',setTRangeEditPosition,'BackgroundColor', 'w',... + 'string','auto','callback',@CallBack_setTRangeEdit); +handles.setTRangeText = uicontrol(handles.figure,'style','text',... + 'unit','normalized','position',setTRangeTextPosition,... + 'string','colorbar max'); +handles.hideControlPush = uicontrol(handles.figure, 'style', 'push',... + 'unit','normalized',... + 'String', '<', 'position', hideControlPushPosition,... + 'visible','off'); +handles.volumePush = uicontrol(handles.figure, 'style', 'push',... + 'unit','normalized',... + 'String', 'volume', ... + 'position', volumePushPosition,... + 'callback', @CallBack_volumePush); +handles.commonRegionPush = uicontrol(handles.figure, 'style', 'push',... + 'unit','normalized',... + 'String', 'common region', ... + 'position', commonRegionPushPosition,... + 'callback', @CallBack_commonRegionPush); +handles.displayPush = uicontrol(handles.figure, 'style', 'push',... + 'unit','normalized',... + 'String', 'display', ... + 'position', displayPushPosition,... + 'callback', @CallBack_displayPush,... + 'visible','off'); +handles.allinonePush = uicontrol(handles.figure, 'style', 'push',... + 'unit','normalized',... + 'String', 'all in one', ... + 'position', allinonePushPosition,... + 'callback', @CallBack_allinonePush,... + 'visible','off'); +%knd: +handles.setContrastNameText = uicontrol(handles.figure,'style','text',... + 'unit','normalized','position',setContrastNameTextPosition, 'FontSize', 13,... + 'string',' ... ','callback',@CallBack_setContrastNameTextPosition); +handles.contrastListPush = uicontrol(handles.figure, 'style', 'popup',... + 'unit','normalized',... + 'String', [ {''}'], ... % getfield(evalin('base','SS'),'sub2pr')),[],23)')], ... + 'position', setContrastListPosition,... + 'callback', @CallBack_contrastListPush,... + 'visible','on'); + +handles.indivResultsListPush(1) = uicontrol(handles.figure, 'style', 'popup',... + 'unit','normalized',... + 'String', [ {''}'], ... % getfield(evalin('base','SS'),'sub2pr')),[],23)')], ... + 'position', setIndivResultsListPosition,... + 'callback', @CallBack_indivResultsListPush,... + 'visible','on'); + +handles.paramestPush(1) = uicontrol(handles.figure, 'style', 'push',... + 'unit','normalized',... + 'String', 'Vox Betas', ... + 'position', paramestPushPosition'.*[1 1 2 1],... + 'callback', {@CallBack_paramestRegionPush, 'vox'},... + 'buttondownfcn', {@CallBack_paramestRegionPush, 'vox', 0},... + 'visible','on'); +handles.paramestPush(2) = uicontrol(handles.figure, 'style', 'push',... + 'unit','normalized',... + 'String', 'Cluster', ... + 'position', paramestPushPosition-[paramestPushPosition(3) 0 0 0]',... + 'callback', {@CallBack_paramestRegionPush, 'clu'},... + 'buttondownfcn', {@CallBack_paramestRegionPush, 'clu', 0},... + 'visible','on'); +handles.paramestPush(3) = uicontrol(handles.figure, 'style', 'push',... + 'unit','normalized',... + 'String', 'Sphere', ... + 'position', paramestPushPosition-2*[paramestPushPosition(3) 0 0 0]',... + 'callback', {@CallBack_paramestRegionPush, 'sph'},... + 'visible','on'); +%--knd + +handles.searchPush = uicontrol(handles.figure, 'style','push',... + 'unit','normalized','position',searchPushPosition,... + 'String', 'search','callback',@CallBack_searchPush, 'ForeGroundColor',[0 0 1]); +handles.searchContentEdit = uicontrol(handles.figure, 'style','edit',... + 'unit','normalized','position',searchContentEditPosition,... + 'ForeGroundColor',[0 0 1],... + 'BackgroundColor', 'w',... + 'horizontal','left','callback', @CallBack_searchContentEdit); +handles.searchText = uicontrol(handles.figure, 'style','text',... + 'unit','normalized','position',searchTextPosition,... + 'string', ' in',... + 'horizontal','left',... + 'visible','off'); +handles.searchEnginePop = uicontrol(... + 'Units','normalized', ... + 'ListboxTop',0, ... + 'Position',searchEnginePopPosition, ... + 'String',{'Brede';'Jede';'xBrain.org';'Google Scholar';'Pubmed';'Wikipedia'}, ... + 'Style','popupmenu', ... + 'value',1); +handles.overlayPush = uicontrol(handles.figure, 'style','push',... + 'unit','normalized','position',overlayPushPosition,... + 'String', 'overlay','callback',@CallBack_overlayPush); +handles.overlayEdit = uicontrol(handles.figure, 'style','edit',... + 'unit','normalized','position',overlayEditPosition,... + 'BackgroundColor', 'w',... + 'horizontal','left',... + 'callback', @CallBack_overlayEdit); +handles.overlayPop = uicontrol(handles.figure, 'style','popupmenu',... + 'unit','normalized','position',overlayPopPosition,... + 'string', sort(fieldnames(handles.wholeMaskMNIAll)),... + 'horizontal','left',... + 'callback', @CallBack_overlayPop); + +handles.helpPush = uicontrol(handles.figure, 'style','push',... + 'unit','normalized','position',helpPosition,... + 'String', 'help','callback','web http://people.hnl.bcm.tmc.edu/cuixu/xjView','ForeGroundColor',[0 0 1],... + 'horizontal','left', ... + 'visible','off'); +handles.infoTextBox = uicontrol(handles.figure, 'style','edit',... + 'unit','normalized','position',infoTextBoxPosition,... + 'String', 'Welcome to xjView 4','ForeGroundColor','k', 'BackgroundColor', 'w',... + 'horizontal','left', ...'fontname','times',... + 'max',2, 'min',0); +try + s = urlread('http://people.hnl.bcm.tmc.edu/cuixu/xjView/toUser.txt'); + set(handles.infoTextBox, 'String', s); +end + +handles.glassViewAxes = axes('unit','normalized','position',glassViewAxesPosition,'XTick',[],'YTick',[],'visible','off'); + + +handles.testEdit = uicontrol(handles.figure,'style','edit',... + 'unit','normalized','position',[0.1 0.4 0.2 0.05],... + 'horizontal','left',... + 'callback',@test,... + 'visible','off'); + +% menu +cSHH = get(0,'ShowHiddenHandles'); +set(0,'ShowHiddenHandles','on') +hMenuFile = findobj(get(handles.figure,'Children'),'flat','Label','&File'); +if ~isempty(hMenuFile) + hMenuFileOpen = findobj(get(handles.figure,'Children'),'Label','&Open...'); + set(hMenuFileOpen, 'label', 'Open Figure...'); + hMenuFileSave = findobj(get(handles.figure,'Children'),'Label','&Save'); + set(hMenuFileSave, 'label', 'Save Figure ...'); + hMenuFileSaveAs = findobj(get(handles.figure,'Children'),'Label','Save &As...'); + set(hMenuFileSaveAs, 'label', 'Save Figure As ...'); +else + hMenuFile = uimenu(handles.figure, 'label', '&File'); +end + +set(hMenuFile,'ForegroundColor',[0 0 1]); +set(findobj(hMenuFile,'Position',1),'Separator','on'); +%knd +uimenu('Parent',hMenuFile,'Position',1,'ForegroundColor',[0 0 1],... + 'Label','Open SPM file (SPM.mat) ...',... + 'CallBack',@CallBack_loadSPMmat, 'Accelerator', 'o'); +%--knd +uimenu('Parent',hMenuFile,'Position',1,'ForegroundColor',[0 0 1],... + 'Label','Open Images (*.img) ...',... + 'CallBack',@CallBack_loadImagePush, 'Accelerator', 'o'); +uimenu('Parent',hMenuFile,'Position',2,'ForegroundColor',[0 0 1],... + 'Label','Save Current Image (*.img) ...',... + 'CallBack',{@CallBack_saveImagePush, '', 0}); +uimenu('Parent',hMenuFile,'Position',3,'ForegroundColor',[0 0 1],... + 'Label','Save Current Image as Mask (*.img) ...',... + 'CallBack',{@CallBack_saveImagePush, '', 1}); +uimenu('Parent',hMenuFile,'Position',4,'ForegroundColor',[0 0 1],... + 'Label','Save Result (*.ps/pdf) ...',... + 'CallBack',@CallBack_saveResultPSPush); + +hMenuHelp = findobj(get(handles.figure,'Children'),'flat','Label','&Help'); +if isempty(hMenuHelp) + hMenuHelp = uimenu(handles.figure, 'label', 'xjView &Help'); +end +set(hMenuHelp,'ForegroundColor',[0 0 1]); +uimenu('Parent',hMenuHelp,'Position',1,'ForegroundColor',[0 0 1],... + 'Label','xBrain.org: brain mapping database',... + 'CallBack','web http://www.xbrain.org -browser'); +uimenu('Parent',hMenuHelp,'Position',2,... + 'Label','xjview help','ForegroundColor',[0 0 1],... + 'CallBack','web http://people.hnl.bcm.tmc.edu/cuixu/xjView'); +set(findobj(hMenuHelp,'Position',3),'Separator','on'); +set(0,'ShowHiddenHandles',cSHH) + +if exist('cuixuBOLDretrieve') + hMenuAnalyze = uimenu('label','&Analyze','ForegroundColor',[0 0 1],'visible','on'); + hMenuPreprocess = uimenu(hMenuAnalyze,'label','Preprocess','ForegroundColor',[0 0 1],'callback', @CallBack_preprocess); + hMenuProcess = uimenu(hMenuAnalyze,'label','Process (GLM estimation)','ForegroundColor',[0 0 1],'callback',@CallBack_process); + hMenuSPMProcess = uimenu(hMenuAnalyze,'label','SPMProcess (GLM using SPM)','ForegroundColor',[0 0 1],'callback',@CallBack_SPMProcess); + hMenuGLMPeak = uimenu(hMenuAnalyze,'label','GLM on peak BOLD','ForegroundColor',[0 0 1],'callback',@CallBack_GLMPeak); + hMenuContrast = uimenu(hMenuAnalyze,'label','Contrast','ForegroundColor',[0 0 1],'callback',@CallBack_contrast); + hMenuFDR = uimenu(hMenuAnalyze,'label','FDR','ForegroundColor',[0 0 1],'callback',@CallBack_fdr); + hMenuROI = uimenu(hMenuAnalyze,'label','ROI: retrieve signal','ForegroundColor',[0 0 1],'callback',@CallBack_timeSeries); + hMenuROIPlot = uimenu(hMenuAnalyze,'label','ROI: plot','ForegroundColor',[0 0 1],'callback',@CallBack_plotROI, 'Accelerator', 'm'); + hMenuROIIndividualPlot = uimenu(hMenuAnalyze,'label','ROI: individual plot','ForegroundColor',[0 0 1],'callback',@CallBack_plotIndividualROI); + hMenuROIIndividualPlotWithBehavior = uimenu(hMenuAnalyze,'label','ROI: individual plot with behavior','ForegroundColor',[0 0 1],'callback',@CallBack_plotIndividualROIWithBehavior); + hMenuROICorrelationPlot = uimenu(hMenuAnalyze,'label','ROI: correlation plot','ForegroundColor',[0 0 1],'callback',@CallBack_plotCorrelationROI); + %hMenuWholeBrainCorrelation = uimenu(hMenuAnalyze,'label','Whole brain correlation','ForegroundColor',[0 0 1],'callback',@CallBack_wholeBrainCorrelation); + hMenuBehaviorAnalysis = uimenu(hMenuAnalyze,'label','Behavior analsyis','ForegroundColor',[0 0 1],'callback',@CallBack_behaviorAnalysis); + hMenuHeadMovementAnalysis = uimenu(hMenuAnalyze,'label','Head movement analsyis','ForegroundColor',[0 0 1],'callback',@CallBack_headMovementAnalysis); + hMenuModelComparison = uimenu(hMenuAnalyze,'label','Linear model comparison','ForegroundColor',[0 0 1],'callback',@CallBack_modelComparison); + + hMenuHNLOnly = uimenu('label','For H&NL Only','ForegroundColor',[0 0 1],'visible','on'); + hMenuNew2Old = uimenu(hMenuHNLOnly,'label','Format convert','ForegroundColor',[0 0 1],'callback',@CallBack_new2old); + hMenuPreprocessCluster = uimenu(hMenuHNLOnly,'label','Preprocess (on cluster)','ForegroundColor',[0 0 1],'callback',{@CallBack_preprocess, 'cluster'}); + hMenuProcessCluster = uimenu(hMenuHNLOnly,'label','Process (GLM, on cluster)','ForegroundColor',[0 0 1],'callback',{@CallBack_process, 'cluster'}); + hMenuSPMProcessCluster = uimenu(hMenuHNLOnly,'label','SPM Process (GLM using SPM, on cluster)','ForegroundColor',[0 0 1],'callback',{@CallBack_SPMProcess, 'cluster'}); + hMenuGLMPeakCluster = uimenu(hMenuHNLOnly,'label','GLM on peak BOLD (on cluster)','ForegroundColor',[0 0 1],'callback',{@CallBack_GLMPeak, 'cluster'}); +end + +figurecm = uicontextmenu; +uimenu(figurecm,'label','Red','callback','set(gcf,''color'',''r'')'); +uimenu(figurecm,'label','White','callback','set(gcf,''color'',''w'')'); +uimenu(figurecm,'label','Gray','callback','set(gcf,''color'',[0.925, 0.914, 0.847])'); +%set(handles.figure,'uicontextmenu',figurecm); +set(handles.figure,'WindowButtonDownFcn',@figureMouseUpFcn); + +set(handles.figure,'visible','on'); + +% save pre-set values +handles.system = os; +handles.spmdir = spmdir; +handles.screenResolution = screenResolution; +handles.pValue = pValue; +handles.intensityThreshold = intensityThreshold; +handles.clusterSizeThreshold = clusterSizeThreshold; +handles.sectionViewPosition = sectionViewPosition; +handles.sectionViewTargetFile = getSectionViewTargetFile(spmdir, 'avg152T1'); +%knd +if exist('xjview.opt') + tmp=textread('xjview.opt','%s');eval(tmp{:}); + handles.sectionViewTargetFile = anatomy; +else + if ~isempty(findstr('gazemo', pwd)) + handles.sectionViewTargetFile = fullfile(fileparts(mfilename('fullpath')), 'group','anat', 'mean.img'); + end +end +guidata(f, handles); + +% global variables for rotation matrix M and dimension +global M_; +global DIM_; +global TR_; +global LEFTRIGHTFLIP_; +global TMAX_; % colorbar max to display in section view +M_ = M; +DIM_ = DIM; +TR_ = TR; +LEFTRIGHTFLIP_ = leftrightflip; +TMAX_ = 'auto'; + +% check input arguments +if length(varargin) == 0 + CallBack_loadSPMmat(handles.loadImagePush) + % if exist(fullfile(pwd,'SPM.mat'), 'file') + % xSPM=load(fullfile(pwd,'SPM.mat')); + % numc=listdlg('ListString' , {xSPM.SPM.xCon.name}, 'SelectionMode' , 'single', 'InitialValue', length(xSPM.SPM.xCon)); + % if ~isempty(numc) + % CallBack_loadImagePush(handles.loadImagePush, [], {fullfile(xSPM.SPM.swd,xSPM.SPM.xCon(numc).Vspm.fname)}); + % end + % end +elseif isstr(varargin{1}) + if isempty(findstr('.mat', varargin{1})) + CallBack_loadImagePush(handles.loadImagePush, [], varargin); + else + CallBack_loadSPMmat(handles.loadImagePush, [], varargin{1}); + end +else + mniCoord = varargin{1}; + if length(varargin) < 2 + intensity = ones(size(mniCoord,1),1); + else + intensity = varargin{2}; + end + thisStruct.mni = mniCoord; + thisStruct.intensity = intensity; + thisStruct.M = M; + thisStruct.DIM = DIM'; + CallBack_loadImagePush(handles.loadImagePush, [], thisStruct); +end + + + +function test(hObject, eventdata) +handles = guidata(gcbo); +set(hObject, 'String', num2str(handles.pValue)); +vars = evalin('base','who'); +vars +x = evalin('base',vars{1}); +x +handles.DIM + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% FDR +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_fdr(hObject, eventdata) +handles = guidata(hObject); +set(handles.infoTextBox, 'string', {'Right now FDR only works for T-test image.'}); +if length(handles.imageFileName) > 1 + set(handles.infoTextBox, 'string', {'I can only work for a single image file. You opened multiple files.'}); + return +end + +q = get(handles.pValueEdit,'String'); +q = str2num(q); + +if get(handles.allIntensityRadio, 'Value') + positive = 1; +elseif get(handles.positiveIntensityRadio, 'Value') + positive = 1; +elseif get(handles.negativeIntensityRadio, 'Value') + positive = -1; +end +xjviewpath = fileparts(which('xjview')); +maskImageFile = fullfile(xjviewpath, 'mask.img'); +[threshold, pvalue] = fdr(handles.imageFileName{1}, q, positive, maskImageFile); +set(handles.pValueEdit,'string',pvalue); +CallBack_pValueEdit(handles.pValueEdit, eventdata); + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% model comparison +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_modelComparison(hObject, eventdata) +answer = getVariable({'y','x (full model)', 'x (reduced model)'}); + +if isempty(answer) + return; +end + +if ~isempty(answer{1}) + y = evalin('base',answer{1}); +else + return; +end +if ~isempty(answer{2}) + xf = evalin('base',answer{2}); % full model +else + return; +end +if ~isempty(answer{3}) + xr = evalin('base',answer{3}); % reduced model +else + xr = []; +end + +% make vector column vector +[r, c] = size(y); +if r==1; y = y'; end +[r, c] = size(xf); +if r(0.5+ii/10); +end + +K = {zeros(1, size(C,1)),zeros(1, size(C,1)),zeros(1, size(C,1)),zeros(1, size(C,1))}; +for ii=1:size(C,1) + for jj=1:4 + K{jj}(ii) = sum(CC{jj}(ii,:)) - 1; + end +end + +x=[0:size(C,1)]; +for jj=1:4 + y{jj}=zeros(size(x)); +end + +for ii=1:length(x) + for jj=1:4 + y{jj}(ii) = sum(K{jj} == x(ii)); + end +end + +figure; +loglog(x,y{1}, x, y{2}, x, y{3}, x, y{4}); +xlabel('degree'); +ylabel('counts'); +legend('0.6', '0.7', '0.8', '0.9'); +axis equal + + +z = sum(abs(C))-1; +zx = 0:max(z); +for ii=1:length(zx)-1 + pos = find(z>zx(ii) & z<=zx(ii+1)); + zy(ii) = length(pos); +end +figure; +loglog(zx(1:length(zy)),zy); +xlabel('weighted degree'); +ylabel('counts'); +axis equal + +xjview(cor2mni(coord, handles.M{1}), z); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% get/select variable names in base workspace +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function vars = getVariable(titles) +vars = evalin('base','who'); +height = 0.07; +f = dialog('unit','normalized', 'menubar','none', 'position', [0.3 0.2 0.4 0.4], 'name', 'pick variables', 'NumberTitle','off'); +uicontrol(f,'style','text',... + 'unit','normalized',... + 'String', 'Available variables', ... + 'position',[0 0.9 0.5 0.1/2]); +variableListbox = uicontrol(f,'style','listbox','tag','variableListbox',... + 'unit','normalized',... + 'String', vars, ... + 'value',1,... + 'position',[0 0 0.5 0.9]); +okPush = uicontrol(f,'style','push',... + 'unit','normalized',... + 'String', 'OK', ... + 'position',[0.55 0.05 0.2 height],... + 'callback','uiresume'); +cancelPush = uicontrol(f,'style','push',... + 'unit','normalized',... + 'String', 'Cancel', ... + 'position',[0.75 0.05 0.2 height],... + 'callback','delete(gcf)'); + +uicontrol(f,'style','text',... + 'unit','normalized',... + 'String', titles{1}, ... + 'position',[0.5 0.9 0.5 0.1/2]); +imageDataPush = uicontrol(f,'style','push',... + 'unit','normalized',... + 'String', '->', ... + 'position',[0.5 0.8 0.1 height],... + 'callback', [... + 'xyouneverfindme=findobj(get(gcf,''Children''),''flat'',''tag'',''imageDataEdit'');'... + 'yyouneverfindme=findobj(get(gcf,''Children''),''flat'',''tag'',''variableListbox'');'... + 'allyouneverfindme=get(yyouneverfindme,''string'');'... + 'selectedyouneverfindme=allyouneverfindme{get(yyouneverfindme,''value'')};'... + 'set(xyouneverfindme,''string'',selectedyouneverfindme);'... + 'clear allyouneverfindme selectedyouneverfindme xyouneverfindme yyouneverfindme;']); +imageDataEdit = uicontrol(f,'style','edit','tag','imageDataEdit',... + 'unit','normalized',... + 'String', '', ... + 'BackgroundColor', 'w',... + 'position',[0.6 0.8 0.3 height]); + +if length(titles)>=2 + uicontrol(f,'style','text',... + 'unit','normalized',... + 'String', titles{2}, ... + 'position',[0.5 0.7 0.5 0.1/2]); + eventDataPush = uicontrol(f,'style','push',... + 'unit','normalized',... + 'String', '->', ... + 'position',[0.5 0.6 0.1 height],... + 'callback', [... + 'xyouneverfindme=findobj(get(gcf,''Children''),''flat'',''tag'',''eventDataEdit'');'... + 'yyouneverfindme=findobj(get(gcf,''Children''),''flat'',''tag'',''variableListbox'');'... + 'allyouneverfindme=get(yyouneverfindme,''string'');'... + 'selectedyouneverfindme=allyouneverfindme{get(yyouneverfindme,''value'')};'... + 'set(xyouneverfindme,''string'',selectedyouneverfindme);'... + 'clear allyouneverfindme selectedyouneverfindme xyouneverfindme yyouneverfindme;']); + + eventDataEdit = uicontrol(f,'style','edit','tag','eventDataEdit',... + 'unit','normalized',... + 'String', '', ... + 'BackgroundColor', 'w',... + 'position',[0.6 0.6 0.3 height]); +end +if length(titles)>=3 + uicontrol(f,'style','text',... + 'unit','normalized',... + 'String', titles{3}, ... + 'position',[0.5 0.5 0.5 0.1/2]); + correlatorDataPush = uicontrol(f,'style','push',... + 'unit','normalized',... + 'String', '->', ... + 'position',[0.5 0.4 0.1 height],... + 'callback', [... + 'xyouneverfindme=findobj(get(gcf,''Children''),''flat'',''tag'',''correlatorDataEdit'');'... + 'yyouneverfindme=findobj(get(gcf,''Children''),''flat'',''tag'',''variableListbox'');'... + 'allyouneverfindme=get(yyouneverfindme,''string'');'... + 'selectedyouneverfindme=allyouneverfindme{get(yyouneverfindme,''value'')};'... + 'set(xyouneverfindme,''string'',selectedyouneverfindme);'... + 'clear allyouneverfindme selectedyouneverfindme xyouneverfindme yyouneverfindme;']); + + correlatorDataEdit = uicontrol(f,'style','edit','tag','correlatorDataEdit',... + 'unit','normalized',... + 'String', '', ... + 'BackgroundColor', 'w',... + 'position',[0.6 0.4 0.3 height]); +end +if length(titles)>=4 + uicontrol(f,'style','text',... + 'unit','normalized',... + 'String', titles{4}, ... + 'position',[0.5 0.3 0.5 0.1/2]); + otherDataPush = uicontrol(f,'style','push',... + 'unit','normalized',... + 'String', '->', ... + 'position',[0.5 0.2 0.1 height],... + 'callback', [... + 'xyouneverfindme=findobj(get(gcf,''Children''),''flat'',''tag'',''otherDataEdit'');'... + 'yyouneverfindme=findobj(get(gcf,''Children''),''flat'',''tag'',''variableListbox'');'... + 'allyouneverfindme=get(yyouneverfindme,''string'');'... + 'selectedyouneverfindme=allyouneverfindme{get(yyouneverfindme,''value'')};'... + 'set(xyouneverfindme,''string'',selectedyouneverfindme);'... + 'clear allyouneverfindme selectedyouneverfindme xyouneverfindme yyouneverfindme;']); + + otherDataEdit = uicontrol(f,'style','edit','tag','otherDataEdit',... + 'unit','normalized',... + 'String', '', ... + 'BackgroundColor', 'w',... + 'position',[0.6 0.2 0.3 height]); +end + +uiwait(f); +try + var{1} = get(imageDataEdit,'string'); + if length(titles)>=2 + var{2} = get(eventDataEdit,'string'); + end + if length(titles)>=3 + var{3} = get(correlatorDataEdit,'string'); + end + if length(titles)>=4 + var{4} = get(otherDataEdit,'string'); + end + vars = var; + delete(f); +catch + vars = {}; + try + delete(f); + end +end + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% quit xjview +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_quit(hObject, eventdata, warnstate) +warning(warnstate) +% try +% rmdir('xjviewtmp'); +% end +delete(gcf); + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% mouse double click +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function figureMouseUpFcn(hObject, eventdata) +status = get(hObject, 'SelectionType'); + +% double click +if strcmp(status, 'extend') + handles = guidata(hObject); + CallBack_loadImagePush(handles.loadImagePush, eventdata); +elseif strcmp(status, 'open') + handles = guidata(hObject); + CallBack_loadSPMmat(handles.loadImagePush, eventdata); +else + return +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% change edit image file name +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_imageFileEdit(hObject, eventdata) +handles = guidata(hObject); +filename = get(hObject, 'String'); +filename = str2cell(filename); +CallBack_loadImagePush(handles.loadImagePush, eventdata, filename); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% click load image file button +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_contrastListPush(hObject, eventdata) +handles = guidata(hObject); +connum = get(handles.contrastListPush,'Value'); +[img.p, img.fname, img.ext]=fileparts(handles.imageFileName{1}); +spmfile=fullfile(img.p,'SPM.mat'); +if ~exist(spmfile) + set(handles.contrastListPush,'Value',0); +else + cons=getfield(getfield(load(spmfile, 'SPM'), 'SPM'), 'xCon'); + connames=get(handles.contrastListPush,'String'); + CallBack_loadImagePush(hObject,eventdata,fullfile(img.p, cons(connum).Vspm.fname), connum) +end + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% click load image file button +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_loadImagePush(hObject, eventdata, thisfilename, ContrastName) +handles = guidata(hObject); +handles.imageFileName=[]; handles.M=[]; handles.DIM=[]; handles.TF=[]; handles.df=[]; +handles.mni=[]; handles.intensity=[]; handles.currentmni=[]; handles.currentintensity=[]; handles.currentDisplayMNI=[]; handles.currentDisplayIntensity=[]; +if nargin>3 + handles.imageContrastName=ContrastName; +else + handles.imageContrastName=[]; +end +if ~exist('thisfilename') + thisfilename = ''; +end + +if isstruct(thisfilename) + handles.imageFileName = {''}; + handles.mni = {thisfilename.mni}; + handles.intensity = {thisfilename.intensity}; + handles.M = {thisfilename.M}; + handles.DIM = {thisfilename.DIM}; + handles.TF = {'S'}; + handles.df = {1e6}; + handles.pValue = 1; + set(handles.pValueEdit, 'string', '1'); + handles.clusterSizeThreshold = 0; + set(handles.clusterSizeThresholdEdit, 'String', '0'); + handles.imageContrastName={''}; +else + [handles.imageFileName, handles.M, handles.DIM, handles.TF, handles.df, handles.mni, handles.intensity] = getImageFile(thisfilename); +end + +if isempty(handles.imageFileName) + return +end + +% tries to find firts-level matching data +% knd +set(handles.indivResultsListPush(1),'String', ... + getfield2(dir(fileparts(fileparts(handles.imageFileName{1}))), 'name')); + +different = 0; % image files are same or different? +if length(handles.TF)>1 + for ii=1:length(handles.TF) + if strcmp(handles.TF{ii},handles.TF{1}) & isequal(handles.df{ii},handles.df{1}) & isequal(handles.M{ii},handles.M{1}) & isequal(handles.DIM{ii},handles.DIM{1}) + continue; + else + warndlg('Images are from different statistics or sources.', 'Warning'); + %set(handles.infoTextBox, 'string', 'Images are from different statistics or sources.'); + beep; + different = 1; + break; + end + end +end + +% reset files with empty df/TF to df=1 and TF='S'. 'S'=='T' but has a tag +% meaning it is changed. +resetTF = 0; +for ii=1:length(handles.TF) + if isempty(handles.TF{ii}) + handles.TF{ii} = 'S'; + resetTF = 1; + end + if isempty(handles.df{ii}) | isequal(handles.df{ii},0) + handles.df{ii} = 1e6; + resetTF = 1; + end +end + +handles.currentmni = handles.mni; +handles.currentintensity = handles.intensity; + +set(handles.dfEdit, 'String', cell2str(handles.df)); +set(handles.imageFileEdit, 'String', cell2str(handles.imageFileName)); % s=-log10(p) +maxs = maxcell(t2s(cellmax(handles.intensity,'abs'),handles.df, handles.TF),'abs'); +if isinf(maxs); maxs = 20; end +set(handles.slider, 'Max', maxs, 'Min', 0, 'sliderstep',[min(maxs/100,0.05),min(maxs/100,0.05)]); +if handles.TF{1}=='T' & different == 0 + str = [blanks(length(' intensit')) 'T=']; +elseif handles.TF{1}=='F' & different == 0 + str = [blanks(length(' intensit')) 'F=']; +else + str=' intensity='; +end +set(handles.intensityThresholdText, 'String', str); +set(handles.figure,'Name',['xjView: ' cell2str(handles.imageFileName)]); +try + for ii=1:length(handles.hLegend) + delete(handles.hLegend{ii}); + end +end +nimg=length(handles.TF); +if nimg>1 + colours = {'r';'g';'y';'c';'b';'m'}; + %colours = colours(mod(0:nimg-1,length(colours))+1); + %colours = repmat([1 0 0;1 1 0;0 1 0;0 1 1;0 0 1;1 0 1],100,1); + % read_conname = spm_input({'Retrieve contrasts names','Should I read SPM files to retrieve filenames?'},... + % 1,'bd',{'YES','NO'},[1,0]); + read_conname = 1; + for ii=1:nimg + [tmp,filename] = fileparts(handles.imageFileName{ii}); + c_names{ii} = filename ; + if read_conname & exist(fullfile(tmp, 'SPM.mat')) + tmp=load(fullfile(tmp, 'SPM.mat')); + tmp.Vspm=[tmp.SPM.xCon.Vspm]; + tmp.iCon=strmatch(filename, {tmp.Vspm.fname}); + if ~isempty(tmp.iCon) + c_names{ii} = [ [tmp.SPM.xCon(tmp.iCon).name] ' [' c_names{ii} ']']; + end + end + end + [tmp] = inputdlg({'colors' 'names'},'Loading',6,[{strvcat(c_names)};{strvcat(colours)}]); + colours = colorname2rgb(tmp{2}); + c_names = cellstr(tmp{1}); + + for ii=1:min(length(handles.TF),10) + filename = c_names{ii}; + if ii == 10; filename = '......'; end + pos0 = handles.sectionViewPosition; + pos(1) = pos0(1)+pos0(3)/2+0.03; + pos(2) = pos0(2)+ii/50-0.03; + pos(3) = 0.12; + pos(4) = 0.02; + handles.hLegend{ii}=uicontrol(handles.figure, 'style','text',... + 'unit','normalized','position',pos,... + 'string', filename,... + 'horizontal','left',... + 'fontweight','bold',... + 'ForeGroundColor',colours(ii,:)); + end + c_names = {'red';'yellow';'green';'cyan';'blue';'magenta'}; +end +guidata(hObject, handles); + +global M_; +global DIM_; +M_ = handles.M{1}; +DIM_ = handles.DIM{1}; + +if resetTF==1 + set(handles.pValueEdit,'string',1); +end +CallBack_pValueEdit(handles.pValueEdit, eventdata); + +% display info +try + s = urlread('http://people.hnl.bcm.tmc.edu/cuixu/xjView/toUser.txt'); + report{1} = s; +catch + report{1} = 'Welcome to xjView 4'; +end +for jj=1:length(handles.imageFileName) + report{2+6*(jj-1)} = cell2str(handles.imageFileName(jj)); + if handles.TF{jj} == 'T' | handles.TF{jj} == 'F' + report{3+6*(jj-1)} = ['This is a ' handles.TF{jj} ' test image.']; + else + report{3+6*(jj-1)} = '';%['I don''t know what test this image came from.']; + end + report{4+6*(jj-1)} = 'mat = '; + report{5+6*(jj-1)} = num2str(handles.M{jj}); + report{6+6*(jj-1)} = 'dimension = '; + report{7+6*(jj-1)} = num2str(handles.DIM{jj}); +end +if length(handles.imageFileName)==1 + [img.p, img.fname, img.ext]=fileparts(handles.imageFileName{1}); + spmfile=fullfile(img.p,'SPM.mat'); + if ~exist(spmfile) + if findstr('SPM2',spm('ver')) + spmfile = spm_get([0 1],'SPM.mat','locate the corresponding SPM.mat'); + elseif findstr('SPM5',spm('ver')) + spmfile = spm_select([0:1],'SPM.mat','locate the corresponding SPM.mat'); + end + end + if exist(spmfile) + cons=getfield(getfield(load(spmfile, 'SPM'), 'SPM'), 'xCon'); + for i=1:length(cons) + if not(isempty(cons(i).Vspm)) + consfilename{i}=cons(i).Vspm.fname; + else + consfilename{i}=''; + end + end + connum=strmatch([img.fname img.ext],consfilename); + if ~isempty(connum) + conlabel=cons(connum).name; + set(handles.figure,'Name',[get(handles.figure,'Name') ' :: ' conlabel]); + set(handles.contrastListPush,'String',{cons.name}) + set(handles.contrastListPush,'Value',connum) + set(handles.setContrastNameText,'String',[conlabel]); + report=[report(1:3) {['Contrast name in SPM.mat: (' num2str(connum) ') ' conlabel]} report(4:end)]; + else + set(handles.contrastListPush,'String',{'[none]'}) + set(handles.contrastListPush,'Value',0) + set(handles.contrastListPush,'Enable','on') + report=[report(1:3) {['Contrast has no matching name in SPM.mat']} report(4:end)]; + end + end +end +set(handles.infoTextBox, 'string', report); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% pValueEdit +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_pValueEdit(hObject, eventdata) +handles = guidata(hObject); +tmp = str2double(get(hObject, 'String')); +tmps = -log10(tmp); +if isnan(tmp) | tmp < 0 | tmp > 1 + errordlg('I don''t understand the input.','error'); + set(hObject, 'String', handles.pValue); + return +end + +if tmps>get(handles.slider,'max') | tmps maxcell(cellmax(handles.intensity,'abs')) + tmp = maxcell(cellmax(handles.intensity,'abs')); +end + +handles.pValue = t2p(tmp, handles.df{1}, handles.TF{1}); +handles.intensityThreshold = p2t(num2cell(handles.pValue*ones(1,length(handles.TF))), handles.df, handles.TF); +set(handles.slider,'Value', -log10(handles.pValue)); +set(handles.intensityThresholdEdit, 'String', cell2str(handles.intensityThreshold)); +guidata(hObject, handles); +CallBack_slider(hObject, eventdata, -log10(handles.pValue)); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% slider bar +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_slider(hObject, eventdata, value) + +handles = guidata(hObject); + +if exist('value') + s = value; + if s > get(handles.slider,'max') + s = get(handles.slider,'max')*0.99; + end + if s < get(handles.slider,'min') + s = get(handles.slider,'min'); + end +else + s = get(hObject,'Value'); +end + +set(handles.slider, 'value', s); +pvalue = 10^(-s); +set(handles.pValueEdit,'String',num2str(pvalue)); +t = p2t(num2cell(pvalue*ones(1,length(handles.TF))), handles.df, handles.TF); +handles.intensityThreshold = t; +set(handles.intensityThresholdEdit, 'String', cell2str(handles.intensityThreshold)); +handles.pValue = pvalue; +for ii=1:length(handles.TF) + pos{ii} = find(abs(handles.intensity{ii})>=t{ii}); + handles.currentintensity{ii} = handles.intensity{ii}(pos{ii}); + handles.currentmni{ii} = handles.mni{ii}(pos{ii},:); +end + +guidata(hObject,handles); + +if get(handles.allIntensityRadio, 'Value') + CallBack_allIntensityRadio(handles.allIntensityRadio, eventdata); +elseif get(handles.positiveIntensityRadio, 'Value') + CallBack_allIntensityRadio(handles.positiveIntensityRadio, eventdata, '+'); +elseif get(handles.negativeIntensityRadio, 'Value') + CallBack_allIntensityRadio(handles.negativeIntensityRadio, eventdata, '-'); +end + +set(handles.infoTextBox, 'string', {'Don''t drag the slider bar too fast. Release your mouse button at least 1 second later.', ... + 'This sounds stupid. But there is a bug (probably MatLab bug) which I can'' fix right now.', ... + 'I suggest you confirm the correctness of the current display by press Enter in the pValue edit box.'}); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% display intensity all+- radios +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_allIntensityRadio(hObject, eventdata, pnall) +% pnall = '+', '-', 'a' (all) or 'c' for current (simply update drawing) +% +handles = guidata(hObject); + +currentselect = []; +if get(handles.allIntensityRadio, 'Value'); currentselect = handles.allIntensityRadio; thispnall = 'a'; end +if get(handles.positiveIntensityRadio, 'Value'); currentselect = handles.positiveIntensityRadio; thispnall = '+'; end +if get(handles.negativeIntensityRadio, 'Value'); currentselect = handles.negativeIntensityRadio; thispnall = '-'; end + +set(handles.allIntensityRadio, 'Value', 0); +set(handles.positiveIntensityRadio, 'Value', 0); +set(handles.negativeIntensityRadio, 'Value', 0); + +if exist('pnall') + if pnall=='c' + hObject = currentselect; + pnall = thispnall; + end +end + +set(hObject, 'Value', 1); + +if ~isfield(handles,'currentintensity') + return +end +for ii=1:length(handles.TF) + if exist('pnall') + if pnall == '-' + pos{ii} = find(handles.currentintensity{ii} < 0); + elseif pnall == '+' + pos{ii} = find(handles.currentintensity{ii} > 0); + elseif pnall == 'a' + pos{ii} = 1:length(handles.currentintensity{ii}); + end + else + pos{ii} = 1:length(handles.currentintensity{ii}); + end + intensity{ii} = handles.currentintensity{ii}(pos{ii}); + mni{ii} = handles.currentmni{ii}(pos{ii},:); + cor{ii} = mni2cor(mni{ii}, handles.M{ii}); + + if ~isempty(cor{ii}) + A = spm_clusters(cor{ii}'); + pos0 = []; + for kk = 1:max(A) + jj = find(A == kk); + if length(jj) >= handles.clusterSizeThreshold; pos0 = [pos0 jj]; end + end + handles.currentDisplayMNI{ii} = mni{ii}(pos0,:); + handles.currentDisplayIntensity{ii} = intensity{ii}(pos0); + else + handles.currentDisplayMNI{ii} = mni{ii}([],:); + handles.currentDisplayIntensity{ii} = intensity{ii}([]); + end +end +[handles.hReg, handles.hSection, handles.hcolorbar] = Draw(handles.currentDisplayMNI, handles.currentDisplayIntensity, hObject, handles); + +% list of contrasts + +set(handles.contrastListPush,'String',regexprep(get(handles.contrastListPush,'String'),' \[Only [\+-a]\]$','')) +set(handles.setContrastNameText,'String',regexprep(get(handles.setContrastNameText,'String'),' \[Only [\+-a]\]$','')) + +if exist('pnall','var') + if ~isequal(pnall, 'a') + set(handles.setContrastNameText,'String',[get(handles.setContrastNameText,'String') ' [Only ' pnall ']']); + s=get(handles.contrastListPush,'String'); + s{get(handles.contrastListPush,'Value')}=[s{get(handles.contrastListPush,'Value')} ' [Only ' pnall ']']; + + set(handles.contrastListPush,'String',s); + end +end + +if get(handles.allIntensityRadio, 'Value') & max(handles.currentDisplayIntensity{1}) < 0 + warndlg('No supra-threshold positive intensity. Only negative intensity is displayed.'); +end + +try + set(handles.figure,'currentaxes', handles.glassViewAxes); + xrange = xlim; + yrange = ylim; + try + delete(handles.hGlassText) + end + if ~isempty(handles.selectedCluster,1) + %handles.hGlassText = text(xrange(1)+diff(xrange)*0.6, yrange(1)+diff(yrange)*0.9, [num2str(size(handles.selectedCluster,1)) ' clusters selected']); + set(handles.infoTextBox, 'string', [num2str(size(handles.selectedCluster,1)) ' clusters selected']); + end +end +guidata(hObject, handles); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% render view check? +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_renderViewCheck(hObject, eventdata) +check = get(hObject, 'Value'); +if check + CallBack_allIntensityRadio(hObject, eventdata, 'c'); +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% which section view target file? list box +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_sectionViewListbox(hObject, eventdata) +handles = guidata(hObject); +contents = get(handles.sectionViewListbox,'String'); +currentsel = contents{get(handles.sectionViewListbox,'Value')}; +handles.sectionViewTargetFile = getSectionViewTargetFile(handles.spmdir, currentsel); +guidata(hObject, handles); +CallBack_allIntensityRadio(hObject, eventdata, 'c'); + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% get sectionviewtargetfile +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function sectionViewTargetFile = getSectionViewTargetFile(spmdir, selectedcontent) +if findstr('SPM2',spm('ver')) + fileext = 'mnc'; +elseif findstr('SPM5',spm('ver')) + fileext = 'nii'; +end +currentsel = selectedcontent; +if ~isempty(strfind(currentsel, 'single')) + sectionViewTargetFile = fullfile(spmdir, 'canonical', ['single_subj_T1.' fileext]); +elseif ~isempty(strfind(currentsel, '152PD')) + sectionViewTargetFile = fullfile(spmdir, 'canonical', ['avg152PD.' fileext]); +elseif ~isempty(strfind(currentsel, '152T1')) + sectionViewTargetFile = fullfile(spmdir, 'canonical', ['avg152T1.' fileext]); +elseif ~isempty(strfind(currentsel, '152T2')) + sectionViewTargetFile = fullfile(spmdir, 'canonical', ['avg152T2.' fileext]); +elseif ~isempty(strfind(currentsel, '305T1')) + sectionViewTargetFile = fullfile(spmdir, 'canonical', ['avg305T1.' fileext]); +elseif strcmp(currentsel, 'ch2') + sectionViewTargetFile = fullfile(spmdir, 'canonical', 'ch2.img'); +elseif strcmp(currentsel, 'ch2bet') + sectionViewTargetFile = fullfile(spmdir, 'canonical', 'ch2bet.img'); +elseif strcmp(currentsel, 'aal') + sectionViewTargetFile = fullfile(spmdir, 'canonical', 'aal.img'); +elseif strcmp(currentsel, 'brodmann') + sectionViewTargetFile = fullfile(spmdir, 'canonical', 'brodmann.img'); + %knd: +elseif ~isempty(strfind(currentsel, 'gazemo')) + sectionViewTargetFile = fullfile(fileparts(mfilename('fullpath')), 'group','anat', 'mean-no120.img'); + %sectionViewTargetFile = fullfile('D:\ndiayek\data\gazemo\group\anat', 'mean-no120.img'); +end +return + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% xhairs in section view? +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_xHairCheck(hObject, eventdata) +check = get(hObject, 'Value'); +if check + spm_orthviews('Xhairs','off'); +else + spm_orthviews('Xhairs','on'); +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% other target file for section view, push button +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_sectionViewMoreTargetPush(hObject, eventdata) +handles = guidata(hObject); +[filename, pathname, filterindex] = uigetfile('*', 'Pick an target file'); +if isequal(filename,0) | isequal(pathname,0) + return; +end +handles.sectionViewTargetFile = fullfile(pathname, filename); +guidata(hObject, handles); +CallBack_allIntensityRadio(hObject, eventdata, 'c'); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% set colorbar range +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_setTRangeEdit(hObject, eventdata) +global TMAX_; +handles = guidata(hObject); +TMAX_ = get(hObject, 'String'); +if isempty(str2num(TMAX_)) && ~strcmp(TMAX_, 'auto') + return; +end +guidata(hObject, handles); +CallBack_allIntensityRadio(hObject, eventdata, 'c'); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% change degree of freedome +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_dfEdit(hObject, eventdata) +handles = guidata(hObject); +tmp = str2cell(get(hObject, 'String')); +if iscellstr(tmp) + errordlg('Please input a valid number.','error'); + try + set(hObject, 'String', handles.df); + catch + set(hObject, 'String', ''); + end + return +end +handles.df=tmp; + + +if isfield(handles,'TF') + t = p2t(mat2cell(handles.pValue*ones(1,length(handles.TF))), handles.df, handles.TF); + handles.intensityThreshold = t; + set(handles.intensityThresholdEdit, 'String', cell2str(t)); +end + +guidata(hObject, handles); +CallBack_pValueEdit(handles.pValueEdit, eventdata); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% get structure push +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_getStructurePush(hObject, eventdata) +handles = guidata(hObject); +xyz = spm_XYZreg('GetCoords',handles.hReg); +tmp_coor = cuixuFindTDstructure(xyz', handles.DB, 0); +set(handles.structureEdit,'String', tmp_coor{1}); +handles.currentxyz = xyz'; +guidata(hObject, handles); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% structure edit +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_structureEdit(action, xyz, thisfun, hReg) + +try + handles=guidata(hReg); +catch + return; +end + +handles.currentxyz = xyz'; +guidata(hReg, handles); + +try + [tmp_coor, cellstructure] = cuixuFindTDstructure(xyz', handles.DB, 0); +catch + return; +end + +set(handles.structureEdit,'String', tmp_coor{1}); + +for ii=[5 3 2 1 4] + if strfind('Unidentified', cellstructure{ii}) + continue; + else + set(handles.searchContentEdit,'string', trimStructureStr(cellstructure{ii})); + set(handles.searchContentEdit,'UserData', 'auto'); + return; + end +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% trim str +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function out = trimStructureStr(str) +pos = findstr('(', str); +if ~isempty(pos) + str(pos-1:end)=[]; +end +out = str; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% cluster size threshold edit +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_clusterSizeThresholdEdit(hObject, eventdata) +handles = guidata(hObject); +tmp = str2double(get(hObject, 'String')); +if isnan(tmp) | tmp<0 + errordlg('Please input a valid number.','error'); + try + set(hObject, 'String', handles.clusterSizeThreshold); + catch + set(hObject, 'String', '5'); + end + return +end +handles.clusterSizeThreshold=tmp; + +guidata(hObject, handles); +CallBack_allIntensityRadio(hObject, eventdata, 'c'); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% save image push button +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_saveImagePush(hObject, eventdata, thisfilename, isMask) +handles = guidata(hObject); + +if exist('thisfilename') + if ~strcmp(deblank(thisfilename), '') + if isfield(handles,'imageFileName') + if ~isempty(handles.imageFileName) + mni2mask(cell2mat(handles.currentDisplayMNI'), thisfilename, cell2mat(handles.currentDisplayIntensity), handles.M{1}, handles.DIM{1}, handles.imageFileName{1}); + return; + end + end + mni2mask(cell2mat(handles.currentDisplayMNI'), thisfilename, cell2mat(handles.currentDisplayIntensity), handles.M{1}, handles.DIM{1}); + return; + end +end + +[filename, pathname] = uiputfile('*.img', 'Save image file as', get(handles.saveImageFileEdit, 'string')); +if isequal(filename,0) | isequal(pathname,0) + return +else + thisfilename = fullfile(pathname, filename); +end + +if isfield(handles,'imageFileName') + if ~isempty(handles.imageFileName{1}) + mni2mask(cell2mat(handles.currentDisplayMNI'), thisfilename, cell2mat(handles.currentDisplayIntensity'), handles.M{1}, handles.DIM{1}, handles.imageFileName{1}, isMask); + return; + end +end + +mni2mask(cell2mat(handles.currentDisplayMNI'), thisfilename, cell2mat(handles.currentDisplayIntensity), handles.M{1}, handles.DIM{1}, '', isMask); + +return; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% save image edit +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_saveImageFileEdit(hObject, eventdata) +handles = guidata(hObject); +CallBack_saveImagePush(handles.saveImagePush, eventdata, get(hObject,'string')); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% save result push +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_saveResultPSPush(hObject, eventdata, thisfilename) + +handles = guidata(hObject); + +if exist('thisfilename') + if ~strcmp(deblank(thisfilename), '') + spm_print(handles.figure); + if strcmp(handles.system, 'linux') + system(['ps2pdf spm2.ps']); + system(['mv spm2.ps ' thisfilename]); + [p,f,ext]=fileparts(thisfilename); + system(['mv spm2.pdf ' fullfile(p,f) '.pdf']); + elseif strcmp(handles.system, 'windows') + system(['move spm2.ps ' '"' thisfilename '"']); + end + return; + end +end + +[filename, pathname] = uiputfile('*.ps', 'Save result as', get(handles.saveResultPSEdit, 'string')); +if isequal(filename,0) | isequal(pathname,0) + return +else + thisfilename = fullfile(pathname, filename); +end + + +% print +H = findobj(get(handles.figure,'Children'),'flat','Type','axes'); +un = cellstr(get(H,'Units')); +pos = get(H,'position'); +index = []; + +for ii=1:length(H) + if findstr('pixels', un{ii}) + continue; + end + if pos{ii}(1)>0.4 & pos{ii}(2) > 0.5 + set(H(ii),'position',[pos{ii}(1), pos{ii}(2), pos{ii}(3)*3/4, pos{ii}(4)]); + index = [index ii]; + end +end + + +spm_print(handles.figure); +if strcmp(handles.system, 'linux') + system(['ps2pdf spm2.ps']); + system(['mv spm2.ps ' thisfilename]); + [p,f,ext]=fileparts(thisfilename); + system(['mv spm2.pdf ' fullfile(p,f) '.pdf']); +elseif strcmp(handles.system, 'windows') + system(['move spm2.ps ' '"' thisfilename '"']); +end + +% set the position back +set(H(index), {'position'}, pos(index)); + + +% printstr = ['print -dpsc2 -painters -noui ' '''' thisfilename '''']; +% try +% orient portrait +% eval(printstr); +% printsuccess = 1; +% catch +% errordlg('Print to ps file failed', 'print error'); +% printsuccess = 0; +% end +%set(H,{'Units'},un); +% if strcmp(handles.system, 'linux') & printsuccess == 1 +% system(['ps2pdf ' '''' thisfilename '''']); +% end + +return; + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% save result edit +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_saveResultPSEdit(hObject, eventdata) +handles = guidata(hObject); +CallBack_saveResultPSPush(hObject, eventdata, get(handles.saveResultPSEdit,'string')); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% select a cluster +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_selectThisClusterPush(hObject, eventdata) +handles = guidata(hObject); + +try + xyz = handles.currentxyz'; +catch + xyz = spm_XYZreg('GetCoords',handles.hReg); +end + +try + handles.selectedCluster = [handles.selectedCluster; xyz']; +catch + handles.selectedCluster = xyz'; +end + +disp('handles defines!') +assignin('base','handles',handles) + +set(handles.figure,'currentaxes', handles.glassViewAxes); +xrange = xlim; +yrange = ylim; +try + delete(handles.hGlassText) +end +%handles.hGlassText = text(xrange(1)+diff(xrange)*0.6, yrange(1)+diff(yrange)*0.9, [num2str(size(handles.selectedCluster,1)) ' clusters selected']); +set(handles.infoTextBox, 'string', [num2str(size(handles.selectedCluster,1)) ' clusters selected']); +guidata(hObject, handles); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% unselect a cluster +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_clearSelectedClusterPush(hObject, eventdata) +handles = guidata(hObject); +try + handles = rmfield(handles,'selectedCluster'); +end +set(handles.figure,'currentaxes', handles.glassViewAxes); +xrange = xlim; +yrange = ylim; +try + %delete(handles.hGlassText) + set(handles.infoTextBox, 'string', ['No clusters selected']); +end + +guidata(hObject, handles); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% pick a cluster +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_pickThisClusterPush(hObject, eventdata) +handles = guidata(hObject); +mni = cell2mat(handles.currentDisplayMNI'); +if isempty(mni) + %errordlg('No cluster is picked up.','oops'); + set(handles.infoTextBox, 'string', 'No cluster is picked up.'); + beep + return; +end + +if ~isfield(handles, 'selectedCluster') | isempty(handles.selectedCluster) + try + xyz = handles.currentxyz'; + catch + xyz = spm_XYZreg('GetCoords',handles.hReg); + end + handles.selectedCluster = xyz'; +end + +intensity = cell2mat(handles.currentDisplayIntensity'); +cor = mni2cor(mni, handles.M{1}); +A = spm_clusters(cor'); +xyzcor = mni2cor(handles.selectedCluster, handles.M{1}); + +pos = []; +for ii = 1:size(xyzcor,1) + pos0 = find(cor(:,1)==xyzcor(ii,1) & cor(:,2)==xyzcor(ii,2) & cor(:,3)==xyzcor(ii,3)); + if isempty(pos0) + continue; + end + pos = [pos find(A==A(pos0(1)))]; +end +if isempty(pos) + %errordlg('No cluster is picked up.','oops'); + set(handles.infoTextBox, 'string', 'No cluster is picked up.'); + beep + return +end + +pos = unique(pos); + +tmpmni = mni(pos,:); +tmpintensity = intensity(pos); + +[B,I,J] = unique(tmpmni, 'rows'); +handles.currentDisplayMNI = {B}; +fprintf('Cluster defined from: %s \n', handles.imageFileName{1}); +assignin('base', 'currentDisplayMNI', handles.currentDisplayMNI ); +handles.currentDisplayIntensity = {tmpintensity(I,:)}; + +handles.currentxyz = handles.selectedCluster(end,:); + +[handles.hReg, handles.hSection, handles.hcolorbar] = Draw(handles.currentDisplayMNI, handles.currentDisplayIntensity, hObject, handles); +set(handles.figure,'currentaxes', handles.glassViewAxes); +xrange = xlim; +yrange = ylim; + +handles.selectedCluster = []; +try + delete(handles.hGlassText) +end + +guidata(hObject, handles); + +set(handles.thisClusterSizeEdit,'string', num2str(size(handles.currentDisplayMNI{1},1))); +str = get(handles.searchContentEdit, 'string'); +pos = findstr(' ', str); +str(pos) = []; +files = []; +for ii=1:length(handles.imageFileName) + [a,b,c] = fileparts(handles.imageFileName{ii}); + files = [files b]; +end +if ~isempty(files) + files = ['_from_' files]; +end + +set(handles.saveImageFileEdit, 'string', [str files '.img']); + +% list structure of voxels in this cluster +[a, b] = cuixuFindTDstructure(cell2mat(handles.currentDisplayMNI'), handles.DB, 0); +names = unique(b(:)); +index = NaN*zeros(length(b(:)),1); +for ii=1:length(names) + pos = find(strcmp(b(:),names{ii})); + index(pos) = ii; +end + +for ii=1:max(index) + report{ii,1} = names{ii}; + report{ii,2} = length(find(index==ii)); +end +for ii=1:size(report,1) + for jj=ii+1:size(report,1) + if report{ii,2} < report{jj,2} + tmp = report(ii,:); + report(ii,:) = report(jj,:); + report(jj,:) = tmp; + end + end +end +report = [{'structure','# voxels'}; {'--TOTAL # VOXELS--', length(a)}; report]; +% format long +% disp(b) +% disp(report) +% format +report2 = {sprintf('%s\t%s',report{1,2}, report{1,1}),''}; +for ii=2:size(report,1) + if strcmp('Unidentified', report{ii,1}); continue; end + report2 = [report2, {sprintf('%5d\t%s',report{ii,2}, report{ii,1})}]; +end + +report2 = [report2, {'','select and Ctrl-C to copy'}]; +% f = figure('unit','normalized', 'menubar','none', 'position', [0.3 0.2 0.2 min(0.7,0.016*length(report2))], 'name', 'cluster information', 'NumberTitle','off'); +% hEdit = uicontrol(f,'style','edit',... +% 'unit','normalized','position',[0 0 1 1],... +% 'horizontal','left',... +% 'BackgroundColor', 'w',... +% 'String', report2,... +% 'max',2,'min',0); + +set(handles.infoTextBox, 'string', report2); +assignin('base', 'ans', handles.currentDisplayMNI) +disp('Cluster coordinates in "ans"') +return + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% commonRegion push +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_commonRegionPush(hObject, eventdata) +handles = guidata(hObject); +if ~isfield(handles,'TF') | length(handles.TF)<=1 + %msgbox('Two or more images need to be loaded to find the common region.', 'info'); + set(handles.infoTextBox, 'string', 'Two or more images need to be loaded to find the common region.'); + beep + return; +end + +common = handles.currentDisplayMNI{1}; +for ii=2:length(handles.TF) + common = intersect(common, handles.currentDisplayMNI{ii}, 'rows'); +end + +if isempty(common) + %msgbox('No common region found.', 'info'); + set(handles.infoTextBox, 'string', 'No common region found.'); + beep + return; +end + +tmpMNI = cell2mat(handles.currentDisplayMNI'); +tmpIntensity = cell2mat(handles.currentDisplayIntensity'); +intensity = zeros(size(common,1),1); + +for ii=1:size(common,1) + pos = find(abs(tmpMNI(:,1)-common(ii,1))<0.1 & abs(tmpMNI(:,2)-common(ii,2))<0.1 & abs(tmpMNI(:,3)-common(ii,3))<0.1); + intensity(ii) = prod(tmpIntensity(pos)); +end + +handles.currentDisplayMNI = {common}; +handles.currentDisplayIntensity = {intensity}; +[handles.hReg, handles.hSection, handles.hcolorbar] = Draw(handles.currentDisplayMNI, handles.currentDisplayIntensity, hObject, handles); + +% if isfield(handles,'hLegend') +% try +% set(cell2mat(handles.hLegend'),'visible',{'off'}); +% end +% end +guidata(hObject, handles); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% volume push +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_volumePush(hObject, eventdata) +handles = guidata(hObject); +mni = cell2mat(handles.currentDisplayMNI'); +intensity = cell2mat(handles.currentDisplayIntensity'); + +xSPM.XYZ = mni2cor(mni, handles.M{1}); +xSPM.XYZ = xSPM.XYZ'; +xSPM.XYZmm = mni'; +xSPM.Z = (intensity'); +xSPM.M = handles.M{1}; +xSPM.DIM = handles.DIM{1}; +xSPM.STAT = handles.TF{1}; +if xSPM.STAT == 'T' + xSPM.STATstr = [xSPM.STAT '_{' num2str(handles.df{1}) '}']; + xSPM.df = [1 handles.df{1}]; +elseif xSPM.STAT == 'F' + xSPM.STATstr = [xSPM.STAT '_{' num2str(handles.df{1}(1)) ',' num2str(handles.df{1}(2)) '}']; + xSPM.df = [handles.df{1}]; +else + xSPM.STAT = 'T'; + xSPM.STATstr = [xSPM.STAT '_{' num2str(handles.df{1}) '}']; + xSPM.df = [1 handles.df{1}]; +end +xSPM.k = str2num(get(handles.clusterSizeThresholdEdit, 'string')); +xSPM.u = str2num(get(handles.intensityThresholdEdit, 'string')); +xSPM.u = xSPM.u(1); +xSPM.VOX = abs([xSPM.M(1,1) xSPM.M(2,2) xSPM.M(3,3)]); +xSPM.n = 1; + + +% SPM - structure containing SPM, distribution & filtering details +% - required fields are: +% .swd - SPM working directory - directory containing current SPM.mat +% .Z - minimum of n Statistics {filtered on u and k} +% .n - number of conjoint tests +% .STAT - distribution {Z, T, X or F} +% .df - degrees of freedom [df{interest}, df{residual}] +% .u - height threshold +% .k - extent threshold {voxels} +% .XYZ - location of voxels {voxel coords} +% .XYZmm - location of voxels {mm} +% .S - search Volume {voxels} +% .R - search Volume {resels} +% .FWHM - smoothness {voxels} +% .M - voxels - > mm matrix +% .VOX - voxel dimensions {mm} +% .Vspm - mapped statistic image(s) +% .Ps - uncorrected P values in searched volume (for FDR) + +xSPM.S = length(handles.intensity{1}); +xSPM.R = [3 27.0931 276.3307 498.1985]; +xSPM.FWHM = [2.9746 3.1923 2.8600]; + +if get(handles.positiveIntensityRadio, 'Value') + xSPM.Z = abs(xSPM.Z); + if xSPM.STAT == 'T'; xSPM.Ps = (1-spm_Tcdf(intensity, xSPM.df(2))); end + if xSPM.STAT == 'F'; xSPM.Ps = (1-spm_Fcdf(intensity, xSPM.df)); end +end + +if get(handles.negativeIntensityRadio, 'Value'); + xSPM.Z = abs(xSPM.Z); + if xSPM.STAT == 'T'; xSPM.Ps = (1-spm_Tcdf(-intensity, xSPM.df(2))); end + if xSPM.STAT == 'F'; xSPM.Ps = (1-spm_Fcdf(-intensity, xSPM.df)); end +end + +if get(handles.allIntensityRadio, 'Value'); + if xSPM.STAT == 'T'; xSPM.Ps = (1-spm_Tcdf((intensity), xSPM.df(2))); end + if xSPM.STAT == 'F'; xSPM.Ps = (1-spm_Fcdf((intensity), xSPM.df)); end +end + +if findstr('SPM2',spm('ver')) + P = spm_get([0 1],'SPM.mat','locate the corresponding SPM.mat'); +elseif findstr('SPM5',spm('ver')) + P = spm_select([0:1],'SPM.mat','locate the corresponding SPM.mat'); +end + +if ~isempty(P) + load(P); + xSPM.FWHM = SPM.xVol.FWHM; + xSPM.R = SPM.xVol.R; + xSPM.S = SPM.xVol.S; +else + warndlg(['You did not input SPM.mat. The listed result may not be correct.'], 'SPM.mat missing'); +end + +spm_list('List',xSPM,handles.hReg); + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% display +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_displayPush(hObject, eventdata) +handles = guidata(hObject); +spm_image('init', handles.imageFileName); + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% all in one +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_allinonePush(hObject, eventdata) +try + if findstr('SPM2',spm('ver')) + P = spm_get([0:100],'*IMAGE','Select image files'); + elseif findstr('SPM5',spm('ver')) + P = spm_select(Inf,'image','Select image files'); + end + +catch + return; +end +if isempty(P) + return +end + +handles = guidata(hObject); +%if ~isfield(handles,'hReg') | ~isfield(handles,'hSection') | ~isfield(handles,'hcolorbar') +tmp = [0 0 0]; +[handles.hReg, handles.hSection, handles.hcolorbar] = Draw(tmp([],:), [], hObject, handles); +%end + +colors=mycolourset; +for ii=1:size(P,1) + thisfilename = deblank(P(ii,:)); + [handles.imageFileName, handles.M, handles.DIM, handles.TF, handles.df, handles.mni, handles.intensity] = getImageFile(thisfilename); + cor = mni2cor(handles.mni, handles.M); + spm_orthviews('addcolouredblobs',1,cor',handles.intensity',handles.M,colors(mod(ii,6)+1,:)); +end +spm_orthviews('Redraw'); + +guidata(hObject, handles); + +% contents = get(handles.sectionViewListbox,'String'); +% currentsel = contents{get(handles.sectionViewListbox,'Value')}; +% sectionViewTargetFile = getSectionViewTargetFile(handles.spmdir, currentsel); +% +% % spm_image('init', sectionViewTargetFile); +% % spm_image('addblobs'); +% % return + +% guidata(hObject, handles); +% load xSPM; +% VOL.XYZ = xSPM.XYZ; +% VOL.Z = xSPM.Z; +% VOL.M = handles.M; + +%addcolouredimage(handles.hSection, '333.img',[1 0 1]); +% uigetfiles +% nblobs = 4; +% for i=1:nblobs, +% %[SPM,VOL] = spm_getSPM; +% %c = spm_input('Colour','+1','m','Red blobs|Yellow blobs|Green blobs|Cyan blobs|Blue blobs|Magenta blobs',[1 2 3 4 5 6],1); +% c = i; +% VOL.XYZ = ceil(rand(3,20)*20); +% VOL.Z = randn(1,20); +% VOL.M = handles.M; +% colours = [1 0 0;1 1 0;0 1 0;0 1 1;0 0 1;1 0 1]; +% spm_orthviews('addcolouredblobs',1,VOL.XYZ,VOL.Z,VOL.M,colours(c,:)); +% end; + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% overlay a brain region Push +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_overlayPush(hObject, eventdata) +handles = guidata(hObject); + +tobeoverlay = deblank(get(handles.overlayEdit, 'string')); + +if isempty(tobeoverlay) + return; +end + +tobeoverlay = str2cell(tobeoverlay, ' '); +if isnumeric(tobeoverlay{1}) + if length(tobeoverlay{1})>1 + errordlg(['Your input, ' deblank(get(handles.overlayEdit, 'string')) ', seems coincide with a matlab constant.'], 'error'); + return + end + for ii=1:length(tobeoverlay) + tobeoverlay{ii} = num2str(tobeoverlay{ii}); + end +end +fn = fieldnames(handles.wholeMaskMNIAll); +for ii=1:length(tobeoverlay) + pos{ii} = []; + for jj=1:length(fn) + x = []; + if ~isempty(str2num(tobeoverlay{ii})) + y = str2cell(fn{jj},'_'); + for kk=1:length(y) + x = isequal(tobeoverlay{ii}, y{kk}); + if x; break;end; + end + if x==0; x = []; end; + else + x = findstr(lower(tobeoverlay{ii}), lower(fn{jj})); + end + if ~isempty(x) + pos{ii} = [pos{ii} jj]; + end + end +end +common = pos{1}; +for ii=2:length(pos) + common = intersect(common, pos{ii}); +end + +if isempty(common) + %warndlg(['I don'' find ' deblank(get(handles.overlayEdit, 'string')) '.'], 'oops'); + set(handles.infoTextBox, 'string', ['I don'' find ' deblank(get(handles.overlayEdit, 'string')) '.']); + return +end + +mask = []; +for ii=1:length(common) + eval(['mask = [mask; handles.wholeMaskMNIAll.' fn{common(ii)} '];']); +end + +if isempty(mask) + %warndlg(['I don'' find ' deblank(get(handles.overlayEdit, 'string')) '.'], 'oops'); + set(handles.infoTextBox, 'string', ['I don'' find ' deblank(get(handles.overlayEdit, 'string')) '.']); + return; +end + +try + handles.mni; +catch + delete(gcf); + xjview(mask); + return; +end + +handles.imageFileName = [handles.imageFileName, {deblank(get(handles.overlayEdit, 'string'))}]; +handles.M = [handles.M, {handles.M{1}}]; +handles.DIM = [handles.DIM, {handles.DIM{1}}]; +handles.currentDisplayMNI = [handles.currentDisplayMNI, {mask}]; +m = max(abs(cell2mat(handles.currentDisplayIntensity'))); +if ~isempty(m) + handles.currentDisplayIntensity = [handles.currentDisplayIntensity, {-2*m*ones(size(mask,1),1)}]; +else + handles.currentDisplayIntensity = [handles.currentDisplayIntensity, {ones(size(mask,1),1)}]; +end +[handles.hReg, handles.hSection, handles.hcolorbar] = Draw(handles.currentDisplayMNI, handles.currentDisplayIntensity, hObject, handles); +return + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% overlay a brain region Edit +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_overlayEdit(hObject, eventdata) +handles = guidata(hObject); +CallBack_overlayPush(handles.overlayPush, eventdata); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% overlay a brain region popup +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_overlayPop(hObject, eventdata) +handles = guidata(hObject); +names = get(hObject, 'string'); +value = get(hObject, 'value'); +set(handles.overlayEdit, 'string', names{value}); +CallBack_overlayEdit(handles.overlayEdit, eventdata); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% search xBrain.org and other databases +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_searchPush(hObject, eventdata) + +try + handles = guidata(hObject); + searchContent = get(handles.searchContentEdit,'string'); + searchEngine = get(handles.searchEnginePop,'string'); + searchEngine = searchEngine{get(handles.searchEnginePop,'value')}; + + + xyz = str2num(searchContent); + searchMode=get(handles.searchContentEdit,'UserData'); + + switch searchMode + case 'auto'; + xyz = spm_XYZreg('GetCoords',handles.hReg); + handles.currentxyz = xyz'; + guidata(hObject, handles); + [a,b] = cuixuFindTDstructure(xyz', handles.DB, 0); + try + brodmann = strmatch('Brodmann',b) + brodmann = b{brodmann}; + brodmann = brodmann(1:end-4); + end; + try + region = b{3}; + region = region(1:end-4); + end + + case 'manual' + if isempty(xyz) + searchMode='text'; + elseif length(xyz)==3 + searchMode='xyz'; + end + end + + + switch searchEngine + case 'Brede' + switch searchMode + case 'auto' + searchString = sprintf(' %0.0f',xyz') + set(handles.searchContentEdit,'string',searchString); + case 'xyz' + searchString = num2str(xyz');%searchContent=sprintf('+%0.0f',searchContent) + case 'text' + end + urlstr = ['http://hendrix.imm.dtu.dk/cgi-bin/brede_loc_query.pl?q=' searchString ]; + case 'xBrain.org' + switch searchMode + case { 'auto' 'xyz'} + xbrainSearchField = 'mni or tal&mnidistance=20'; + searchString = sprintf(' %0.1f',xyz')% searchString = num2str(xyz'); + case 'text' + xbrainSearchField = 'region'; + searchString = searchContent; + end + set(handles.searchContentEdit,'string',searchString); + urlstr = ['http://people.hnl.bcm.tmc.edu/cuixu/cgi-bin/bmd/paper.pl?search_content=' searchString '&search_field=' xbrainSearchField]; + case 'Google Scholar' + if isequal(searchMode,'auto' ) + searchString = { brodmann region }; + searchString(cellfun('isempty',searchString))=[]; + searchString = sprintf('%%22%s%%22|',searchString{:}); + try + searchString = searchString(1:end-1); + end + %set(handles.searchContentEdit,'string',searchString); + end + urlstr = ['http://scholar.google.com/scholar?q=' searchString ]; + case 'Pubmed' + switch searchMode + case 'auto' + searchString = { brodmann region }; + searchString(cellfun('isempty',searchString))=[]; + searchString = sprintf('%%22%s%%22 OR ',searchString{:}); + try + searchString = searchString(1:end-4); + end + %searchString = brodmann; + case 'text' + searchString = searchContent; + end + % urlstr = ['http://www.ncbi.nlm.nih.gov/entrez/query.fcgi?cmd=PureSearch&db=pubmed&details_term=(' searchString ')']; + urlstr = ['http://www.ncbi.nlm.nih.gov/sites/entrez?cmd=search&db=pubmed&pubmedfilters=true&term=(' searchString ')']; + case 'Wikipedia' + if isequal(searchMode,'auto') + searchString = region; + end + urlstr = ['http://en.wikipedia.org/w/index.php?search=%22' searchString '%22']; + end + set(handles.searchContentEdit,'UserData',searchMode); +catch + urlstr = 'http://www.google.com'; +end + +try + web(urlstr,'-browser'); +catch + if exist('c:\program Files\mozilla Firefox\firefox.exe','file') + system(['"c:\program Files\mozilla Firefox\firefox.exe" "' urlstr '"']) + else + web(urlstr); + end +end + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% control panel on or off +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function controlHide(handles, status) + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% get multiple values from a string deliminated delim +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function out = str2cell(str, delim) +if ~exist('delim') + delim = '; '; +end + +[out{1},b] = strtok(str, delim); +ii = 2; +while ~isempty(b) + [out{ii},b] = strtok(b, delim); + ii = ii+1; +end + +for ii=1:length(out) + out2{ii} = str2num(out{ii}); + if isempty(out2{ii}) + return; + end +end + +for ii=1:length(out) + out{ii} = str2num(out{ii}); +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% cell2str +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function str = cell2str(acell, delim) +if ~exist('delim') + delim = ';'; +end + +str = []; + +if length(acell)==1 + if isstr(acell{1}) + str = acell{1}; + else + str = num2str(acell{1}); + end +else + for ii=1:length(acell) + if isstr(acell{ii}) + str = [str acell{ii}, delim ' ']; + else + str = [str num2str(acell{ii}), delim ' ']; + end + end + str(end-1:end)=[]; +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% cellmax, find max in each element, return a cell +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function MAXX = cellmax(acell, absolute) + +if ~exist('absolute') + absolute = ''; +end + +MAXX = []; +for ii=1:length(acell) + if strfind('abs',absolute) + MAXX = [MAXX max(abs(acell{ii}))]; + else + MAXX = [MAXX max(acell{ii})]; + end +end +MAXX = num2cell(MAXX); + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% maxcell +%%% find max of all numbers in the whole cell, return a single value +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function MAXX = maxcell(acell, absolute) + +if ~exist('absolute') + absolute = ''; +end +MAXX = cellmax(acell, absolute); +MAXX = max(cell2mat(MAXX)); + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% p2t +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function t = p2t(p, df, TF) +if ~iscell(p) + if upper(TF)=='T' | upper(TF)=='S' + t = spm_invTcdf(1-p,df); + elseif upper(TF) == 'F' + t = spm_invFcdf(1-p,df); + end +else + for ii=1:length(p) + t{ii} = p2t(p{ii},df{ii},TF{ii}); + end +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% s2t, s is defined as -log10(p) +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function t = s2t(s, df, TF) + +if ~iscell(s) + p = 10^(-s); + if upper(TF)=='T' | upper(TF)=='S' + t = spm_invTcdf(1-p,df); + elseif upper(TF) == 'F' + t = spm_invFcdf(1-p,df); + end +else + for ii=1:length(p) + t{ii} = s2t(s{ii},df{ii},TF{ii}); + end +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% t2p +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function p = t2p(t, df, TF) +if ~iscell(t) + if upper(TF)=='T' | upper(TF)=='S' + p = 1-spm_Tcdf(t,df); + elseif upper(TF) == 'F' + p = 1-spm_Fcdf(t,df); + end +else + for ii=1:length(t) + p{ii} = t2p(t{ii},df{ii},TF{ii}); + end +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% t2s, s is defined as -log10(p) +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function s = t2s(t, df, TF) +if ~iscell(t) + if upper(TF)=='T' | upper(TF)=='S' + p = 1-spm_Tcdf(t,df); + elseif upper(TF) == 'F' + p = 1-spm_Fcdf(t,df); + else + p = 0.1; + end + s = -log10(p); +else + for ii=1:length(t) + s{ii} = t2s(t{ii},df{ii},TF{ii}); + end +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% mni2mask +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function mask = mni2mask(coords, targetfilename, intensity, M, DIM, templateFile, isMask) +% function mask = mni2mask(coords, targetfilename, intensity, templateFile) +% make mask from coordinates +% +% coords: a Nx3 column of 3-d coordinates in MNI +% space +% targetfilename: (optional) the image files to be written +% intensity: (optional) Nx1, the values of each coordinate. +% M: rotation matrix +% DIM: dimension +% templateFile: (optional) the templateFile from which we can get the right +% dimensions. +% isMask: if this variable exist and equal to 1, then all non-zero +% intensities will be set to 1 +% +% Xu Cui +% 2004/11/18 + +if ~exist('intensity') + intensity = ones(size(coords,1),1); +end +thistemplateFile = ''; +if exist('templateFile') + if ~isempty(templateFile) + thistemplateFile = templateFile; + end +end + +if isempty(thistemplateFile) + V.mat = [... + -4 0 0 84; ... + 0 4 0 -116; ... + 0 0 4 -56; ... + 0 0 0 1]; + V.dim = [41 48 35 16]; + if exist('M') + V.mat = M; + end + if exist('DIM') + V.dim = DIM; + V.dim(4) = 16; + end + V.fname = targetfilename; + V.descrip = 'our own image'; +else + V = spm_vol(templateFile); + V.fname = targetfilename; + if isfield(V, 'descrip') + V.descrip = ['my image from ' V.descrip]; + else + V.descrip = 'my own image'; + end +end + +thisismask = 0; +if exist('isMask') + if isMask == 1 + thisismask = 1; + end +end +if thisismask + V.descrip = 'my mask'; + intensity = ones(size(intensity)); +end + +O = zeros(V.dim(1),V.dim(2),V.dim(3)); + +coords = mni2cor(coords,V.mat); + + +for ii=1:size(coords,1) + O(coords(ii,1),coords(ii,2),coords(ii,3)) = intensity(ii); +end + +if exist('targetfilename') + V = spm_write_vol(V,O); +end + +mask = O; + +return; + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% get image file information +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function [imageFile,M,DIM,TF,df,mni,intensity] = getImageFile(thisfilename) +% function imageFile = getImageFile(filename) +% get the information of this/these image file(s) +% +% thisfilename: (optional) if doesn't give file name, then show a open file +% dialog +% imageFile: the full name of the selected file (if user pressed cancel, +% imageFile == 0 +% M: M matrix (rotation matrix) +% DIM: dimension +% TF: t test or f test? 'T' or 'F' +% df: degree of freedome +% mni: mni coord +% intensity: intensity of each mni coord +% +% Note: The returned variables are cellarrays. +% +% Xu Cui +% last revised: 2005-05-03 + +if nargin < 1 | isempty(thisfilename) + if findstr('SPM2',spm('ver')) + P0 = spm_get([0:100],'*IMAGE','Select image files'); + elseif findstr('SPM5',spm('ver')) + P0 = spm_select(Inf,'image','Select image files'); + end + + % try + % P0 = spm_get([0:100],'*IMAGE','Select image files'); + % catch + % P0 = []; + % [FileName,PathName] = uigetfile({'*.img';'*.IMG';'*.*'},'Select image files','MultiSelect','on'); + % if isstr(FileName) + % P0 = {fullfile(PathName, FileName)}; + % elseif iscellstr(FileName) + % for ii=1:length(FileName) + % P0{ii} = fullfile(PathName, FileName{ii}); + % end + % else + % P0 = []; + % end + % end + try + if isempty(P0) + imageFile = '';M=[];DIM=[];TF=[];df=[];mni=[];intensity=[]; + return + end + end + for ii=1:size(P0,1) + P{ii} = deblank(P0(ii,:)); + end +else + if isstr(thisfilename) + P = {thisfilename}; + elseif iscellstr(thisfilename) + P = thisfilename; + else + disp('Error: In getImageFile: I don''t understand the input.'); + imageFile = '';M=[];DIM=[];TF=[];df=[];mni=[];intensity=[]; + return + end +end + +global LEFTRIGHTFLIP_; + +for ii=1:length(P) + imageFile{ii} = P{ii}; + [cor{ii}, intensity{ii}, tmp{ii}, M{ii}, DIM{ii}, TF{ii}, df{ii}] = mask2coord(imageFile{ii}, 0); + if LEFTRIGHTFLIP_ == 1 + M{ii}(1,:) = -M{ii}(1,:); + end + mni{ii} = cor2mni(cor{ii}, M{ii}); +end + + + + + + + + + + + + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% cuixuFindTDstructure +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function [onelinestructure, cellarraystructure] = cuixuFindTDstructure(pos, DB, TALMNI) +% function [onelinestructure, cellarraystructure] = cuixuFindTDstructure(pos, DB, TALMNI) +% +% this function converts Talairach Daemon coordinate or MNI coordinate to a description of +% brain structure. +% +% pos: the coordinate (MNI or Talairach coordinate, defined by TALMNI) of the position in the brain, in mm +% DB: the database structure (see below for detailed explanation). If you don't input this argument, there +% should be a file called 'TDdatabase.mat' in the current directory +% TALMNI: 1 if pos is talairach coordinate, 0 if pos is MNI coordinate. 1 +% by default. +% +% onelinestructure: a one-line description of the returned brain +% structure +% cellarraystructure: a cell array of size 5, each cell contains a string +% describing the structure of the brain in a certain level. +% +% Example: +% [onelinestructure, cellarraystructure] = cuixuFindTDstructure([-24 24 48], DB, 1) +% [onelinestructure, cellarraystructure] = cuixuFindTDstructure([-24 24 48]) +% then +% onelinestructure = // Left Cerebrum // Frontal Lobe (L) // Middle Frontal Gyrus (L) // Gray Matter (L) // Brodmann area 8 (L) +% cellarraystructure = 'Left Cerebrum' 'Frontal Lobe (L)' [1x24 char] 'Gray Matter (L)' 'Brodmann area 8 (L)' +% +% Xu Cui +% 2004-6-28 +% + +%---------------------------------------------------------------------------------- +% DB strcture +%---------------------------------------------------------------------------------- +%------------------------------- +% Grid specification parameters: +%------------------------------- +% minX - min X (mm) +% maxX - max X (mm) +% voxX - voxel size (mm) in X direction +% minY - min Y (mm) +% maxY - max Y (mm) +% voxY - voxel size (mm) in Y direction +% minZ - min Z (mm) +% maxZ - max Z (mm) +% voxZ - voxel size (mm) in Z direction +% nVoxX - number of voxels in X direction +% nVoxY - number of voxels in Y direction +% nVoxZ - number of voxels in Z direction +%------------------------------- +% Classification parameters: +%------------------------------- +% numClass - number of classification types +% cNames - cNames{i} - cell array of class names for i-th CT +% numClassSize - numClassSize(i) - number of classes for i-th CT +% indUnidentified - indUnidentified(i) - index of "indUnidentified" class for i-th CT +% volClass - volClass{i}(j) - number of voxels in class j for i-th CT +% +% data - N x numClass matrix of referencies; let +% x y z coordinates in mm (on the grid) and +% nx = (x-minX)/voxX +% ny = (y-minY)/voxY +% nz = (z-minZ)/voxZ +% ind = nz*nVoxX*nVoxY + ny*nVoxX + nx + 1 +% data(ind, i) - index of the class for i-th CT in cNames{i} to +% which (x y z) belongs, i.e. +% cNames{i}{data(ind, i)} name of class for i-th CT +%---------------------------------------------------------------------------------- + +if nargin==1 + load('TDdatabase.mat'); + TALMNI = 1; +elseif nargin == 2 + TALMNI = 1; +end + +if(TALMNI == 1) + pos = tal2mni(pos); +elseif(TALMNI == 0) + []; +else + disp('TALMNI should be 1 or 0.'); +end + +pos(:,1) = DB.voxX*round(pos(:,1)/DB.voxX); +pos(:,2) = DB.voxY*round(pos(:,2)/DB.voxY); +pos(:,3) = DB.voxZ*round(pos(:,3)/DB.voxZ); + +min = []; +vox = []; +for(i=1:size(pos,1)) + min = [min; DB.minX DB.minY DB.minZ]; + vox = [vox; DB.voxX DB.voxY DB.voxZ]; +end +n_pos = (pos - min)./vox; + +nx = n_pos(:,1); +ny = n_pos(:,2); +nz = n_pos(:,3); +index = nz*DB.nVoxX*DB.nVoxY + ny*DB.nVoxX + nx + 1; +indMax = size(DB.data, 1); + +onelinestructuretmp = []; +onelinestructure = []; +for(j=1:size(pos,1)) + onelinestructuretmp = []; + for(i=1:DB.numClass) + + if (index(j) <= 0 | index(j) > indMax) + ind(j) = DB.indUnidentified(i); + else + ind(j) = DB.data(index(j), i); + end + + cellarraystructure{j,i} = DB.cNames{i}{ind(j)}; + onelinestructuretmp = [onelinestructuretmp ' // ' cellarraystructure{j,i}]; + end + onelinestructure{j} = onelinestructuretmp; +end + +return + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% mask2coord +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function [cor, intensity, cor_singlecolumn,M,DIM,TF,df] = mask2coord(mask, checkdimension) +% [cor, intensity, cor_singlecolumn] = mask2coord(mask, checkdimension) +% +% This is to retrieve the coordinate of a mask file, or a matrix of 3-D +% +% mask: an image file or a matrix (3-D), with some of the elements are +% non-zeros +% checkdimension: check if the dimension is checkdimension, if not, return empty +% matrix +% cor: a N*3 matrix, which each row a coordinate in matrix +% intensity: a N*1 matrix, which encodes the intensity of each voxel. +% cor_singlecolumn: a N*1 matrix, each row is the index in the matrix +% M: rotation matrix +% DIM: dimension +% TF: t test or f test? 'T','F' or [] +% df: degree of freedome for T/F test +% +% Example: +% mask = zeros(4,3,2); +% mask(1,2,1) = 1; +% mask(3,2,2) = 1; +% mask2coord(mask) +% +% mask2coord('spmT_0002.img') +% mask2coord('spmT_0002.img',[41 48 35]) +% +% Xu Cui +% 2004-9-20 +% last revised: 2005-04-30 + +if nargin < 2 + checkdimension = 0; +end + +if ischar(mask) + V = spm_vol(mask); + mask = spm_read_vols(V); + M = V.mat; + DIM = V.dim; + TF = 'T'; + T_start = strfind(V.descrip,'SPM{T_[')+length('SPM{T_['); + if isempty(T_start); T_start = strfind(V.descrip,'SPM{F_[')+length('SPM{F_['); TF='F'; end + if isempty(T_start) + TF=[]; df=[]; + else + T_end = strfind(V.descrip,']}')-1; + df = str2num(V.descrip(T_start:T_end)); + end +else + M = []; + TF = []; + df = []; +end + +dim = size(mask); +if length(checkdimension)==3 + if dim(1)~= checkdimension(1) | dim(2)~= checkdimension(2) | dim(3)~= checkdimension(3) + y = []; + disp('dimension doesn''t match') + return + end +end + +pos = find(mask~=0); +intensity = mask(pos); + +y = zeros(length(pos),3); + +y(:,3) = ceil(pos/(dim(1)*dim(2))); +pos = pos - (y(:,3)-1)*(dim(1)*dim(2)); +y(:,2) = ceil(pos/dim(1)); +pos = pos - (y(:,2)-1)*(dim(1)); +y(:,1) = pos; + +cor = y; +cor_singlecolumn = pos; +DIM = dim; +return + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% mni2cor +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function coordinate = mni2cor(mni, T) +% function coordinate = mni2cor(mni, T) +% convert mni coordinate to matrix coordinate +% +% mni: a Nx3 matrix of mni coordinate +% T: (optional) transform matrix +% coordinate is the returned coordinate in matrix +% +% caution: if T is not specified, we use: +% T = ... +% [-4 0 0 84;... +% 0 4 0 -116;... +% 0 0 4 -56;... +% 0 0 0 1]; +% +% xu cui +% 2004-8-18 +% + +if isempty(mni) + coordinate = []; + return; +end + +if ~exist('T') + T = ... + [-4 0 0 84;... + 0 4 0 -116;... + 0 0 4 -56;... + 0 0 0 1]; +end + +coordinate = [mni(:,1) mni(:,2) mni(:,3) ones(size(mni,1),1)]*(inv(T))'; +coordinate(:,4) = []; +coordinate = round(coordinate); +return; + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% cor2mni +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function mni = cor2mni(cor, T) +% function mni = cor2mni(cor, T) +% convert matrix coordinate to mni coordinate +% +% cor: an Nx3 matrix +% T: (optional) rotation matrix +% mni is the returned coordinate in mni space +% +% caution: if T is not given, the default T is +% T = ... +% [-4 0 0 84;... +% 0 4 0 -116;... +% 0 0 4 -56;... +% 0 0 0 1]; +% +% xu cui +% 2004-8-18 +% last revised: 2005-04-30 + +if nargin == 1 + T = ... + [-4 0 0 84;... + 0 4 0 -116;... + 0 0 4 -56;... + 0 0 0 1]; +end + +cor = round(cor); +mni = T*[cor(:,1) cor(:,2) cor(:,3) ones(size(cor,1),1)]'; +mni = mni'; +mni(:,4) = []; +return; + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% draw +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function [hReg, hSection, hcolorbar] = Draw(mniCoord, intensity, hObject, handles) + +try + delete(handles.hcolorbar); +end +try + delete(handles.hReg); +end +try + delete(handles.hSection); +end +try + cla(handles.glassViewAxes); +end + +try + H = findobj(get(handles.figure,'Children'),'flat','Type','axes'); + un = cellstr(get(H,'Units')); + pos = get(H,'position'); + + for ii=1:length(H) + if findstr('pixels', un{ii}) + continue; + end + if pos{ii}(1)>0.4 + delete(H(ii)); + end + end +end + +sectionViewTargetFile = handles.sectionViewTargetFile; + +if ~iscell(mniCoord) | (iscell(mniCoord) & length(mniCoord)==1)% multiple input? no + if (iscell(mniCoord) & length(mniCoord)==1) + mniCoord = mniCoord{1}; + intensity = intensity{1}; + handles.M = handles.M{1}; + handles.DIM = handles.DIM{1}; + end + if max(intensity)*min(intensity) < 0 + [hReg, hSection, hcolorbar] = cuixuSectionView(mniCoord,intensity,sectionViewTargetFile,hObject,handles); + else + [hReg, hSection, hcolorbar] = cuixuSectionView(mniCoord,abs(intensity),sectionViewTargetFile,hObject,handles); + end + + if size(mniCoord,1)>1 + pos1 = find(intensity>=0); + pos2 = find(intensity<0); + if ~isempty(pos1) & ~isempty(pos2) + if get(handles.renderViewCheck, 'Value'); cuixuRenderView(mniCoord(pos1,:),intensity(pos1,:),mniCoord(pos2,:),-intensity(pos2,:)); end + else + if get(handles.renderViewCheck, 'Value'); cuixuRenderView(mniCoord, abs(intensity)); end + end + end +else % multiple input? yes + [hReg, hSection, hcolorbar] = cuixuSectionView(mniCoord,intensity,sectionViewTargetFile,hObject,handles); + + mniCoordtmp=[]; + intensitytmp=[]; + for ii=1:length(mniCoord) + mniCoordtmp = [mniCoordtmp; mniCoord{ii}]; + intensitytmp = [intensitytmp; intensity{ii}]; + end + mniCoord = mniCoordtmp; + intensity = intensitytmp; + if size(mniCoord,1)>1 + pos1 = find(intensity>=0); + pos2 = find(intensity<0); + if ~isempty(pos1) & ~isempty(pos2) + if get(handles.renderViewCheck, 'Value'); cuixuRenderView(mniCoord(pos1,:),intensity(pos1,:),mniCoord(pos2,:),-intensity(pos2,:)); end + else + if get(handles.renderViewCheck, 'Value'); cuixuRenderView(mniCoord, abs(intensity)); end + end + end + +end + +try + spm_XYZreg('SetCoords',handles.currentxyz',hReg); +end + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% cuixuSectionView +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function [hReg, hSection, hcolorbar] = cuixuSectionView(mniCoord, intensity, targetFile, hObject,handles) +% function h = cuixuSectionView(mniCoord, intensity) +% This is to project your coordinate to section view +% +% mniCoord: the mni coordinate, N*3 matrix +% intensity: the plot intensity of the spots, 1*N matrix. The intensity +% could be t value, for example. +% +% a special feature of this function is: it automatically seperate the +% negative and positive intensity and use hot and cold color to represent +% them. +% +% h: the returned handle for the axes +% +% SEE ALSO: cuixuView cuixuGlassView cuixuRenderView +% +% Xu Cui +% 2004/11/11 + +if ~iscell(mniCoord) | (iscell(mniCoord) & length(mniCoord)==1)% multiple input? no + multiple = 0; + if (iscell(mniCoord) & length(mniCoord)==1) + mniCoord = mniCoord{1}; + intensity = intensity{1}; + handles.M = handles.M{1}; + handles.DIM = handles.DIM{1}; + end +else + multiple = 1; + mniCoordtmp = []; + intensitytmp = []; + for ii=1:length(mniCoord) + mniCoordtmp = [mniCoordtmp; mniCoord{ii}]; + intensitytmp = [intensitytmp; intensity{ii}]; + % for multiple input + cSPM{ii}.XYZ = mni2cor(mniCoord{ii}, handles.M{ii}); + cSPM{ii}.XYZ = cSPM{ii}.XYZ'; + cSPM{ii}.Z = abs(intensity{ii}); + cSPM{ii}.M = handles.M{ii}; + cSPM{ii}.DIM = handles.DIM{ii}'; + end + mniCoord = mniCoordtmp; + intensity = intensitytmp; + handles.M = handles.M{1}; + handles.DIM = handles.DIM{1}; +end + +SPM.XYZ = mni2cor(mniCoord, handles.M); +SPM.XYZ = SPM.XYZ'; +SPM.Z = intensity; +SPM.M = handles.M; +SPM.DIM = handles.DIM'; + +%%%%%%%%%%%%%%% Reg %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +handles.colormap = colormap; + +coor = mniCoord; +xSPM = SPM; +xSPM.XYZmm = mniCoord'; +axes(handles.glassViewAxes) +WS = spm('WinScale'); +FS = spm('FontSizes'); + +Finter = gcf; +hReg = uicontrol(Finter,'Style','Frame','Position',[60 100 300 300].*WS,... + 'Visible','off'); +[hReg,xyz] = spm_XYZreg('InitReg',hReg,xSPM.M,xSPM.DIM,[0;0;0]); + +hFxyz = spm_results_ui('DrawXYZgui',xSPM.M,xSPM.DIM,xSPM,xyz,Finter); +spm_XYZreg('XReg',hReg,hFxyz,'spm_results_ui'); + +hMIPax =gca ; +setcolormap('gray'); + +hMIPax = spm_mip_ui(xSPM.Z,coor',xSPM.M,xSPM.DIM,hMIPax); +spm_XYZreg('XReg',hReg,hMIPax,'spm_mip_ui'); + +colormap(handles.colormap); +setcolormap('gray-hot'); + +if isempty(handles.M) + spm_XYZreg('SetReg',handles.structureEdit,hReg); +else + set(handles.structureEdit, 'UserData', struct('hReg',hReg,'M',handles.M,'D', handles.DIM,'xyx',[0 0 0])); +end + +spm_XYZreg('Add2Reg',hReg,handles.structureEdit,@CallBack_structureEdit); + +%%%%%%%%%%%%%%% Reg end %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +if multiple == 0 + [hgraph, hSection, hcolorbar] = spm_sections(SPM,hReg,targetFile,handles.sectionViewPosition); +else + [hgraph, hSection, hcolorbar] = spm_sections(cSPM,hReg,targetFile,handles.sectionViewPosition); +end + +return + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% spm_sections +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function [Fgraph, hMe, hcolorbar] = spm_sections(SPM,hReg,targetFile,sectionViewPosition) +% rendering of regional effects [SPM{Z}] on orthogonal sections +% FORMAT spm_sections(SPM,hReg) +% +% SPM - xSPM structure containing details of excursion set +% hReg - handle of MIP register +% +% see spm_getSPM for details +%_______________________________________________________________________ +% +% spm_sections is called by spm_results and uses variables in SPM and +% VOL to create three orthogonal sections though a background image. +% Regional foci from the selected SPM are rendered on this image. +% +%_______________________________________________________________________ +% @(#)spm_sections.m 2.14 John Ashburner 02/09/05 + +Fgraph = gcf; + +spms = fullfile(spm('dir'),'canonical', 'single_subj_T1.mnc'); +if exist('targetFile') + spms = targetFile; +end + +spm_orthviews('Reset'); +global st +st.fig = Fgraph; +st.Space = spm_matrix([0 0 0 0 0 -pi/2])*st.Space; + +spm_orthviews('Image',spms,sectionViewPosition); % position +spm_orthviews('MaxBB'); +spm_orthviews('register',hReg); +if ~iscell(SPM) + spm_orthviews('addblobs',1,SPM.XYZ,SPM.Z,SPM.M); +elseif length(SPM) == 1 + SPM = SPM{1}; + spm_orthviews('addblobs',1,SPM.XYZ,SPM.Z,SPM.M); +else + colors = mycolourset; + for ii=1:length(SPM) + spm_orthviews('addcolouredblobs',1,SPM{ii}.XYZ, SPM{ii}.Z, SPM{ii}.M, colors(ii,:)); + end +end +spm_orthviews('Redraw'); + +hMe = st.registry.hMe; +try + hcolorbar = st.vols{1}.blobs{1}.cbar; +catch + hcolorbar = 0; +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% cuixuRenderView +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function h = cuixuRenderView(mniCoord, intensity, varargin) +% function h = cuixuRenderView(mniCoord1, intensity1, mniCoord2, intensity2, mniCoord3, intensity3) +% This is to project your coordinate to render view +% +% mniCoord: the mni coordinate, N*3 matrix +% intensity: the plot intensity of the spots, 1*N matrix. The intensity +% could be t value, for example. +% +% You can input 1, 2, or 3 pairs of coordinates and intensity. +% +% h: the returned handle for the figure +% +% Xu Cui +% 2004/11/11 + +global M_; +global DIM_; + +if nargin < 3 + dat.XYZ = mni2cor(mniCoord, M_); + dat.XYZ = dat.XYZ'; + if nargin < 2 + dat.t = ones(size(mniCoord,1),1); + else + dat.t = intensity; + end + dat.mat = M_; + dat.dim = DIM_; +else + if mod(nargin,2) ~=0 + disp('You should put even number of parameters.') + return; + end + for ii=1:(2+length(varargin))/2 + if ii==1 + dat(ii).XYZ = mni2cor(mniCoord, M_); + dat(ii).t = intensity; + else + dat(ii).XYZ = mni2cor(varargin{2*(ii-1)-1}, M_); + dat(ii).t = varargin{2*(ii-1)}; + end + dat(ii).XYZ = dat(ii).XYZ'; + dat(ii).mat = M_; + dat(ii).dim = DIM_; + end +end + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% spm_render +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +h = spm_render(dat,1,fullfile(spm('dir'), 'rend', 'render_single_subj.mat')); +return + +function Fgraph = spm_render(dat,brt,rendfile) +% Render blobs on surface of a 'standard' brain +% FORMAT spm_render(dat,brt,rendfile) +% +% dat - a vertical cell array of length 1 to 3 +% - each element is a structure containing: +% - XYZ - the x, y & z coordinates of the transformed t values. +% in units of voxels. +% - t - the SPM{.} values +% - mat - affine matrix mapping from XYZ voxels to Talairach. +% - dim - dimensions of volume from which XYZ is drawn. +% brt - brightness control: +% If NaN, then displays using the old style with hot +% metal for the blobs, and grey for the brain. +% Otherwise, it is used as a ``gamma correction'' to +% optionally brighten the blobs up a little. +% rendfile - the file containing the images to render on to. See also +% spm_xbrain.m. +% +% Without arguments, spm_render acts as its own UI. +%_______________________________________________________________________ +% +% spm_render prompts for details of up to three SPM{Z}s or SPM{t}s that +% are then displayed superimposed on the surface of a standard brain. +% +% The first is shown in red, then green then blue. +% +% The blobs which are displayed are the integral of all transformed t +% values, exponentially decayed according to their depth. Voxels that +% are 10mm behind the surface have half the intensity of ones at the +% surface. +%_______________________________________________________________________ +% @(#)spm_render.m 2.19 John Ashburner FIL 02/10/29 + +%-Parse arguments, get data if not passed as parameters +%======================================================================= +if nargin < 1 + SPMid = spm('FnBanner',mfilename,'2.19'); + [Finter,Fgraph,CmdLine] = spm('FnUIsetup','Results: render',0); + + num = spm_input('Number of sets',1,'1 set|2 sets|3 sets',[1 2 3]); + + for i = 1:num, + [SPM,VOL] = spm_getSPM; + dat(i) = struct( 'XYZ', VOL.XYZ,... + 't', VOL.Z',... + 'mat', VOL.M,... + 'dim', VOL.DIM); + end; + showbar = 1; +else, + num = length(dat); + showbar = 0; +end; + +% get surface +%----------------------------------------------------------------------- +if nargin < 3, + rendfile = spm_get(1,'render*.mat','Render file',fullfile(spm('Dir'),'rend')); +end; + +% get brightness +%----------------------------------------------------------------------- +if nargin < 2, + brt = 1; + if num==1, + brt = spm_input('Style',1,'new|old',[1 NaN], 1); + end; + if finite(brt), + brt = spm_input('Brighten blobs',1,'none|slightly|more|lots',[1 0.75 0.5 0.25], 1); + end; +end; + + + +% Perform the rendering +%======================================================================= +spm('Pointer','Watch') + +load(rendfile); + +if (exist('rend') ~= 1), % Assume old format... + rend = cell(size(Matrixes,1),1); + for i=1:size(Matrixes,1), + rend{i}=struct('M',eval(Matrixes(i,:)),... + 'ren',eval(Rens(i,:)),... + 'dep',eval(Depths(i,:))); + rend{i}.ren = rend{i}.ren/max(max(rend{i}.ren)); + end; +end; + +if showbar, spm_progress_bar('Init', size(dat,1)*length(rend),... + 'Formatting Renderings', 'Number completed'); end; +for i=1:length(rend), + rend{i}.max=0; + rend{i}.data = cell(size(dat,1),1); + if issparse(rend{i}.ren), + % Assume that images have been DCT compressed + % - the SPM99 distribution was originally too big. + d = size(rend{i}.ren); + B1 = spm_dctmtx(d(1),d(1)); + B2 = spm_dctmtx(d(2),d(2)); + rend{i}.ren = B1*rend{i}.ren*B2'; + % the depths did not compress so well with + % a straight DCT - therefore it was modified slightly + rend{i}.dep = exp(B1*rend{i}.dep*B2')-1; + end; + msk = find(rend{i}.ren>1);rend{i}.ren(msk)=1; + msk = find(rend{i}.ren<0);rend{i}.ren(msk)=0; + if showbar, spm_progress_bar('Set', i); end; +end; +if showbar, spm_progress_bar('Clear'); end; + +if showbar, spm_progress_bar('Init', length(dat)*length(rend),... + 'Making pictures', 'Number completed'); end; + +mx = zeros(length(rend),1)+eps; +mn = zeros(length(rend),1); + +for j=1:length(dat), + XYZ = dat(j).XYZ; + t = dat(j).t; + dim = dat(j).dim; + mat = dat(j).mat; + + for i=1:length(rend), + + % transform from Taliarach space to space of the rendered image + %------------------------------------------------------- + M1 = rend{i}.M*dat(j).mat; + zm = sum(M1(1:2,1:3).^2,2).^(-1/2); + M2 = diag([zm' 1 1]); + M = M2*M1; + cor = [1 1 1 ; dim(1) 1 1 ; 1 dim(2) 1; dim(1) dim(2) 1 ; + 1 1 dim(3) ; dim(1) 1 dim(3) ; 1 dim(2) dim(3); dim(1) dim(2) dim(3)]'; + tcor= M(1:3,1:3)*cor + M(1:3,4)*ones(1,8); + off = min(tcor(1:2,:)'); + M2 = spm_matrix(-off+1)*M2; + M = M2*M1; + xyz = (M(1:3,1:3)*XYZ + M(1:3,4)*ones(1,size(XYZ,2))); + d2 = ceil(max(xyz(1:2,:)')); + + % calculate 'depth' of values + %------------------------------------------------------- + dep = spm_slice_vol(rend{i}.dep,spm_matrix([0 0 1])*inv(M2),d2,1); + z1 = dep(round(xyz(1,:))+round(xyz(2,:)-1)*size(dep,1)); + + if ~finite(brt), msk = find(xyz(3,:) < (z1+20) & xyz(3,:) > (z1-5)); + else, msk = find(xyz(3,:) < (z1+60) & xyz(3,:) > (z1-5)); end; + + if ~isempty(msk), + + % generate an image of the integral of the blob values. + %----------------------------------------------- + xyz = xyz(:,msk); + if ~finite(brt), t0 = t(msk); + else, dst = xyz(3,:) - z1(msk); + dst = max(dst,0); + t0 = t(msk).*exp((log(0.5)/10)*dst)'; + end; + X0 = full(sparse(round(xyz(1,:)), round(xyz(2,:)), t0, d2(1), d2(2))); + hld = 1; if ~finite(brt), hld = 0; end; + X = spm_slice_vol(X0,spm_matrix([0 0 1])*M2,size(rend{i}.dep),hld); + msk = find(X<0); + X(msk) = 0; + else, + X = zeros(size(rend{i}.dep)); + end; + + % Brighten the blobs + if finite(brt), X = X.^brt; end; + + mx(j) = max([mx(j) max(max(X))]); + mn(j) = min([mn(j) min(min(X))]); + + rend{i}.data{j} = X; + + if showbar, spm_progress_bar('Set', i+(j-1)*length(rend)); end; + end; +end; + +mxmx = max(mx); +mnmn = min(mn); + +if showbar, spm_progress_bar('Clear'); end; +Fgraph = gcf;%spm_figure('GetWin','Graphics'); +%spm_results_ui('Clear',Fgraph); + +nrow = ceil(length(rend)/2); +hght = 0.25; +width = 0.25; +x0 = 0.5; +y0 = 0.01; +% subplot('Position',[0, 0, 1, hght]); +ax=axes('Parent',Fgraph,'units','normalized','Position',[0, 0, 0.5, hght],'Visible','off'); +%ax=axes; +%image(0,'Parent',ax); +set(ax,'YTick',[],'XTick',[]); + +if ~finite(brt), + % Old style split colourmap display. + %--------------------------------------------------------------- + load Split; + colormap(split); + for i=1:length(rend), + ren = rend{i}.ren; + X = (rend{i}.data{1}-mnmn)/(mxmx-mnmn); + msk = find(X); + ren(msk) = X(msk)+(1+1.51/64); + ax=axes('Parent',Fgraph,'units','normalized',... + 'Position',[x0+rem(i-1,2)*width, y0+floor((i-1)/2)*hght/nrow, width, hght/nrow],... + 'Visible','off'); + image(ren*64,'Parent',ax); + set(ax,'DataAspectRatio',[1 1 1], ... + 'PlotBoxAspectRatioMode','auto',... + 'YTick',[],'XTick',[],'XDir','normal','YDir','normal'); + end; +else, + % Combine the brain surface renderings with the blobs, and display using + % 24 bit colour. + %--------------------------------------------------------------- + for i=1:length(rend), + ren = rend{i}.ren; + X = cell(3,1); + for j=1:length(rend{i}.data), + X{j} = rend{i}.data{j}/(mxmx-mnmn)-mnmn; + end + for j=(length(rend{i}.data)+1):3 + X{j}=zeros(size(X{1})); + end + + rgb = zeros([size(ren) 3]); + tmp = ren.*max(1-X{1}-X{2}-X{3},0); + rgb(:,:,1) = tmp + X{1}; + rgb(:,:,2) = tmp + X{2}; + rgb(:,:,3) = tmp + X{3}; + + ax=axes('Parent',Fgraph,'units','normalized',... + 'Position',[x0+rem(i-1,2)*width, y0+floor((i-1)/2)*hght/nrow*2, width, hght/nrow*2],... + 'Visible','off'); + image(rgb,'Parent',ax); + set(ax,'DataAspectRatio',[1 1 1], ... + 'PlotBoxAspectRatioMode','auto',... + 'YTick',[],'XTick',[],... + 'XDir','normal','YDir','normal'); + end; +end; + +spm('Pointer') +return; + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% spm_list +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function varargout = spm_list(varargin) +% Display and analysis of SPM{.} +% FORMAT TabDat = spm_list('List',SPM,hReg,[Num,Dis,Str]) +% Summary list of local maxima for entire volume of interest +% FORMAT TabDat = spm_list('ListCluster',SPM,hReg,[Num,Dis,Str]) +% List of local maxima for a single suprathreshold cluster +% +% SPM - structure containing SPM, distribution & filtering details +% - required fields are: +% .swd - SPM working directory - directory containing current SPM.mat +% .Z - minimum of n Statistics {filtered on u and k} +% .n - number of conjoint tests +% .STAT - distribution {Z, T, X or F} +% .df - degrees of freedom [df{interest}, df{residual}] +% .u - height threshold +% .k - extent threshold {voxels} +% .XYZ - location of voxels {voxel coords} +% .XYZmm - location of voxels {mm} +% .S - search Volume {voxels} +% .R - search Volume {resels} +% .FWHM - smoothness {voxels} +% .M - voxels - > mm matrix +% .VOX - voxel dimensions {mm} +% .Vspm - mapped statistic image(s) +% .Ps - uncorrected P values in searched volume (for FDR) +% +% (see spm_getSPM for further details of xSPM structures) +% +% hReg - Handle of results section XYZ registry (see spm_results_ui.m) +% +% Num - number of maxima per cluster +% Dis - distance among clusters (mm) +% Str - header string +% +% TabDat - Structure containing table data +% - fields are +% .tit - table Title (string) +% .hdr - table header (2x11 cell array) +% .fmt - fprintf format strings for table data (1x11 cell array) +% .str - table filtering note (string) +% .ftr - table footnote information (4x2 cell array) +% .dat - table data (Nx11 cell array) +% +% ---------------- +% +% FORMAT spm_list('TxtList',TabDat,c) +% Prints a tab-delimited text version of the table +% TabDat - Structure containing table data (format as above) +% c - Column of table data to start text table at +% (E.g. c=3 doesn't print set-level results contained in columns 1 & 2) +% ---------------- +% +% FORMAT spm_list('SetCoords',xyz,hAx,hC) +% Highlighting of table co-ordinates (used by results section registry) +% xyz - 3-vector of new co-ordinate +% hAx - table axis (the registry object for tables) +% hReg - Handle of caller (not used) +%_______________________________________________________________________ +% +% spm_list characterizes SPMs (thresholded at u and k) in terms of +% excursion sets (a collection of face, edge and vertex connected +% subsets or clusters). The currected significance of the results are +% based on set, cluster and voxel-level inferences using distributional +% approximations from the Theory of Gaussian Fields. These +% distributions assume that the SPM is a reasonable lattice +% approximation of a continuous random field with known component field +% smoothness. +% +% The p values are based on the probability of obtaining c, or more, +% clusters of k, or more, resels above u, in the volume S analysed = +% P(u,k,c). For specified thresholds u, k, the set-level inference is +% based on the observed number of clusters C, = P(u,k,C). For each +% cluster of size K the cluster-level inference is based on P(u,K,1) +% and for each voxel (or selected maxima) of height U, in that cluster, +% the voxel-level inference is based on P(U,0,1). All three levels of +% inference are supported with a tabular presentation of the p values +% and the underlying statistic: +% +% Set-level - c = number of suprathreshold clusters +% - P = prob(c or more clusters in the search volume) +% +% Cluster-level - k = number of voxels in this cluster +% - Pc = prob(k or more voxels in the search volume) +% - Pu = prob(k or more voxels in a cluster) +% +% Voxel-level - T/F = Statistic upon which the SPM is based +% - Ze = The eqivalent Z score - prob(Z > Ze) = prob(t > T) +% - Pc = prob(Ze or higher in the search volume) +% - Qu = Expd(Prop of false positives among voxels >= Ze) +% - Pu = prob(Ze or higher at that voxel) +% +% x,y,z (mm) - Coordinates of the voxel +% +% The table is grouped by regions and sorted on the Ze-variate of the +% primary maxima. Ze-variates (based on the uncorrected p value) are the +% Z score equivalent of the statistic. Volumes are expressed in voxels. +% +% Clicking on values in the table returns the value to the Matlab +% workspace. In addition, clicking on the co-ordinates jumps the +% results section cursor to that location. The table has a context menu +% (obtained by right-clicking in the background of the table), +% providing options to print the current table as a text table, or to +% extract the table data to the Matlab workspace. +% +%_______________________________________________________________________ +% @(#)spm_list.m 2.43 Karl Friston, Andrew Holmes 02/10/31 + + +% satellite figure global variable +%----------------------------------------------------------------------- +global SatWindow + +%======================================================================= +switch lower(varargin{1}), case 'list' %-List + %======================================================================= + % FORMAT TabDat = spm_list('list',SPM,hReg) + + %-Tolerance for p-value underflow, when computing equivalent Z's + %----------------------------------------------------------------------- + tol = eps*10; + + %-Parse arguments and set maxima number and separation + %----------------------------------------------------------------------- + if nargin < 2, error('insufficient arguments'), end + if nargin < 3, hReg = []; else, hReg = varargin{3}; end + + + %-Get current location (to highlight selected voxel in table) + %----------------------------------------------------------------------- + %xyzmm = spm_results_ui('GetCoords'); + xyzmm = [0 0 0]'; + + %-Extract data from xSPM + %----------------------------------------------------------------------- + S = varargin{2}.S; + R = varargin{2}.R; + FWHM = varargin{2}.FWHM; + VOX = varargin{2}.VOX; + n = varargin{2}.n; + STAT = varargin{2}.STAT; + df = varargin{2}.df; + u = varargin{2}.u; + M = varargin{2}.M; + v2r = 1/prod(FWHM(~isinf(FWHM))); %-voxels to resels + k = varargin{2}.k*v2r; + QPs = varargin{2}.Ps; % Needed for FDR + QPs = sort(QPs(:)); + + %-get number and separation for maxima to be reported + %----------------------------------------------------------------------- + if length(varargin) > 3 + + Num = varargin{4}; % number of maxima per cluster + Dis = varargin{5}; % distance among clusters (mm) + else + Num = 3; + Dis = 8; + end + if length(varargin) > 5 + + Title = varargin{6}; + else + Title = 'p-values adjusted for search volume'; + end + + + %-Setup graphics panel + %----------------------------------------------------------------------- + spm('Pointer','Watch') + if SatWindow + Fgraph = SatWindow; + figure(Fgraph); + else + Fgraph = figure('unit','normalized','position',[0.5,0.1,0.55,0.5],'Color','w',... + 'Name','volume', 'NumberTitle','off','resize','off','MenuBar','none'); + Fgraph = gcf; + end + %spm_results_ui('Clear',Fgraph) + FS = spm('FontSizes'); %-Scaled font sizes + PF = spm_platform('fonts'); %-Font names (for this platform) + + + %-Table header & footer + %======================================================================= + + %-Table axes & Title + %---------------------------------------------------------------------- + if SatWindow, ht = 0.85; bot = .14; else, ht = 0.8; bot = 0.15; end; + + if STAT == 'P' + Title = 'Posterior Probabilities'; + end + + hAx = axes('Position',[0.025 bot 0.9 ht],... + 'DefaultTextFontSize',FS(8),... + 'DefaultTextInterpreter','Tex',... + 'DefaultTextVerticalAlignment','Baseline',... + 'Units','points',... + 'Visible','off'); + + AxPos = get(hAx,'Position'); set(hAx,'YLim',[0,AxPos(4)]) + dy = FS(9); + y = floor(AxPos(4)) - dy; + + text(0,y,['Statistics: \it\fontsize{',num2str(FS(9)),'}',Title],... + 'FontSize',FS(11),'FontWeight','Bold'); y = y - dy/2; + line([0 1],[y y],'LineWidth',3,'Color','r'), y = y - 9*dy/8; + + + %-Construct table header + %----------------------------------------------------------------------- + set(gca,'DefaultTextFontName',PF.helvetica,'DefaultTextFontSize',FS(8)) + + Hc = []; + Hp = []; + h = text(0.01,y, 'set-level','FontSize',FS(9)); Hc = [Hc,h]; + h = line([0,0.11],[1,1]*(y-dy/4),'LineWidth',0.5,'Color','r'); Hc = [Hc,h]; + h = text(0.08,y-9*dy/8, '\itc '); Hc = [Hc,h]; + h = text(0.02,y-9*dy/8, '\itp '); Hc = [Hc,h]; + Hp = [Hp,h]; + text(0.22,y, 'cluster-level','FontSize',FS(9)); + line([0.15,0.41],[1,1]*(y-dy/4),'LineWidth',0.5,'Color','r'); + h = text(0.16,y-9*dy/8, '\itp \rm_{corrected}'); Hp = [Hp,h]; + h = text(0.33,y-9*dy/8, '\itp \rm_{uncorrected}'); Hp = [Hp,h]; + h = text(0.26,y-9*dy/8, '\itk \rm_E'); + + text(0.60,y, 'voxel-level','FontSize',FS(9)); + line([0.46,0.86],[1,1]*(y-dy/4),'LineWidth',0.5,'Color','r'); + h = text(0.46,y-9*dy/8, '\itp \rm_{FWE-corr}'); Hp = [Hp,h]; + h = text(0.55,y-9*dy/8, '\itp \rm_{FDR-corr}'); Hp = [Hp,h]; + h = text(0.79,y-9*dy/8, '\itp \rm_{uncorrected}'); Hp = [Hp,h]; + h = text(0.64,y-9*dy/8, sprintf('\\it%c',STAT)); + h = text(0.72,y-9*dy/8, '(\itZ\rm_\equiv)'); + + text(0.93,y - dy/2,['x,y,z \fontsize{',num2str(FS(8)),'}\{mm\}']); + + + %-Headers for text table... + %----------------------------------------------------------------------- + TabDat.tit = Title; + TabDat.hdr = { 'set', 'c';... + 'set', 'p';... + 'cluster', 'p(cor)';... + 'cluster', 'equivk';... + 'cluster', 'p(unc)';... + 'voxel', 'p(FWE-cor)';... + 'voxel', 'p(FDR-cor)';... + 'voxel', STAT;... + 'voxel', 'equivZ';... + 'voxel', 'p(unc)';... + '', 'x,y,z {mm}'}';... + + TabDat.fmt = { '%-0.3f','%g',... %-Set + '%0.3f', '%0.0f', '%0.3f',... %-Cluster + '%0.3f', '%0.3f', '%6.2f', '%5.2f', '%0.3f',... %-Voxel + '%3.0f %3.0f %3.0f'}; %-XYZ + + %-Column Locations + %----------------------------------------------------------------------- + tCol = [ 0.00 0.07 ... %-Set + 0.16 0.26 0.34 ... %-Cluster + 0.46 0.55 0.62 0.71 0.80 ...%-Voxel + 0.92]; %-XYZ + + % move to next vertial postion marker + %----------------------------------------------------------------------- + y = y - 7*dy/4; + line([0 1],[y y],'LineWidth',1,'Color','r') + y = y - 5*dy/4; + y0 = y; + + + %-Table filtering note + %----------------------------------------------------------------------- + if isinf(Num) + TabDat.str = sprintf('table shows all local maxima > %.1fmm apart',Dis); + else + TabDat.str = sprintf(['table shows %d local maxima ',... + 'more than %.1fmm apart'],Num,Dis); + end + text(0.5,4,TabDat.str,'HorizontalAlignment','Center','FontName',PF.helvetica,... + 'FontSize',FS(8),'FontAngle','Italic') + + + %-Volume, resels and smoothness (if classical inference) + %----------------------------------------------------------------------- + line([0 1],[0 0],'LineWidth',1,'Color','r') + if STAT ~= 'P' + %----------------------------------------------------------------------- + FWHMmm = FWHM.*VOX; % FWHM {mm} + Pz = spm_P(1,0,u,df,STAT,1,n,S); + Pu = spm_P(1,0,u,df,STAT,R,n,S); + Qu = spm_P_FDR(u,df,STAT,n,QPs); + [P Pn Em En EN] = spm_P(1,k,u,df,STAT,R,n,S); + + + %-Footnote with SPM parameters + %----------------------------------------------------------------------- + set(gca,'DefaultTextFontName',PF.helvetica,... + 'DefaultTextInterpreter','None','DefaultTextFontSize',FS(8)) + TabDat.ftr = cell(5,2); + TabDat.ftr{1} = ... + sprintf('Height threshold: %c = %0.2f, p = %0.3f (%0.3f)',... + STAT,u,Pz,Pu); + TabDat.ftr{2} = ... + sprintf('Extent threshold: k = %0.0f voxels, p = %0.3f (%0.3f)',... + k/v2r,Pn,P); + TabDat.ftr{3} = ... + sprintf('Expected voxels per cluster, = %0.3f',En/v2r); + TabDat.ftr{4} = ... + sprintf('Expected number of clusters, = %0.2f',Em*Pn); + TabDat.ftr{5} = ... + sprintf('Expected false discovery rate, <= %0.2f',Qu); + TabDat.ftr{6} = ... + sprintf('Degrees of freedom = [%0.1f, %0.1f]',df); + TabDat.ftr{7} = ... + sprintf(['Smoothness FWHM = %0.1f %0.1f %0.1f {mm} ',... + ' = %0.1f %0.1f %0.1f {voxels}'],FWHMmm,FWHM); + TabDat.ftr{8} = ... + sprintf('Search vol: %0.0f cmm; %0.0f voxels; %0.1f resels',S*prod(VOX),S,R(end)); + TabDat.ftr{9} = ... + sprintf(['Voxel size: [%0.1f, %0.1f, %0.1f] mm ',... + ' (1 resel = %0.2f voxels)'],VOX,prod(FWHM)); + + text(0.0,-1*dy,TabDat.ftr{1},... + 'UserData',[u,Pz,Pu,Qu],'ButtonDownFcn','get(gcbo,''UserData'')') + text(0.0,-2*dy,TabDat.ftr{2},... + 'UserData',[k/v2r,Pn,P],'ButtonDownFcn','get(gcbo,''UserData'')') + text(0.0,-3*dy,TabDat.ftr{3},... + 'UserData',En/v2r,'ButtonDownFcn','get(gcbo,''UserData'')') + text(0.0,-4*dy,TabDat.ftr{4},... + 'UserData',Em*Pn,'ButtonDownFcn','get(gcbo,''UserData'')') + text(0.0,-5*dy,TabDat.ftr{5},... + 'UserData',Qu,'ButtonDownFcn','get(gcbo,''UserData'')') + text(0.5,-1*dy,TabDat.ftr{6},... + 'UserData',df,'ButtonDownFcn','get(gcbo,''UserData'')') + text(0.5,-2*dy,TabDat.ftr{7},... + 'UserData',FWHMmm,'ButtonDownFcn','get(gcbo,''UserData'')') + text(0.5,-3*dy,TabDat.ftr{8},... + 'UserData',[S*prod(VOX),S,R(end)],... + 'ButtonDownFcn','get(gcbo,''UserData'')') + text(0.5,-4*dy,TabDat.ftr{9},... + 'UserData',[VOX,prod(FWHM)],... + 'ButtonDownFcn','get(gcbo,''UserData'')') + + end % Classical + + + %-Characterize excursion set in terms of maxima + % (sorted on Z values and grouped by regions) + %======================================================================= + if ~length(varargin{2}.Z) + text(0.5,y-6*dy,'no suprathreshold clusters',... + 'HorizontalAlignment','Center',... + 'FontAngle','Italic','FontWeight','Bold',... + 'FontSize',FS(16),'Color',[1,1,1]*.5); + TabDat.dat = cell(0,11); + varargout = {TabDat}; + spm('Pointer','Arrow') + return + end + + % Includes Darren Gitelman's code for working around + % spm_max for conjunctions with negative thresholds + %----------------------------------------------------------------------- + minz = abs(min(min(varargin{2}.Z))); + zscores = 1 + minz + varargin{2}.Z; + [N Z XYZ A] = spm_max(zscores,varargin{2}.XYZ); + Z = Z - minz - 1; + + %-Convert cluster sizes from voxels to resels + %----------------------------------------------------------------------- + if isfield(varargin{2},'VRvp') + V2R = spm_get_data(varargin{2}.VRvp,XYZ); + else + V2R = v2r; + end + N = N.*V2R; + + %-Convert maxima locations from voxels to mm + %----------------------------------------------------------------------- + XYZmm = M(1:3,:)*[XYZ; ones(1,size(XYZ,2))]; + + + + %-Table proper (& note all data in cell array) + %======================================================================= + + %-Pagination variables + %----------------------------------------------------------------------- + hPage = []; + set(gca,'DefaultTextFontName',PF.courier,'DefaultTextFontSize',FS(7)) + + + %-Set-level p values {c} - do not display if reporting a single cluster + %----------------------------------------------------------------------- + c = max(A); %-Number of clusters + if STAT ~= 'P' + Pc = spm_P(c,k,u,df,STAT,R,n,S); %-Set-level p-value + else + Pc = []; + set(Hp,'Visible','off') + end + + if c > 1; + h = text(tCol(1),y,sprintf(TabDat.fmt{1},Pc),'FontWeight','Bold',... + 'UserData',Pc,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + h = text(tCol(2),y,sprintf(TabDat.fmt{2},c),'FontWeight','Bold',... + 'UserData',c,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + else + set(Hc,'Visible','off') + end + + TabDat.dat = {Pc,c}; %-Table data + TabLin = 1; %-Table data line + + + %-Local maxima p-values & statistics + %----------------------------------------------------------------------- + HlistXYZ = []; + while prod(size(find(finite(Z)))) + + % Paginate if necessary + %--------------------------------------------------------------- + if y < min(Num + 1,3)*dy + + % added Fgraph term to paginate on Satellite window + %------------------------------------------------------- + h = text(0.5,-5*dy,... + sprintf('Page %d',spm_figure('#page',Fgraph)),... + 'FontName',PF.helvetica,'FontAngle','Italic',... + 'FontSize',FS(8)); + + spm_figure('NewPage',[hPage,h]) + hPage = []; + y = y0; + end + + %-Find largest remaining local maximum + %--------------------------------------------------------------- + [U,i] = max(Z); % largest maxima + j = find(A == A(i)); % maxima in cluster + + + %-Compute cluster {k} and voxel-level {u} p values for this cluster + %--------------------------------------------------------------- + Nv = N(i)/v2r; % extent {voxels} + + + if STAT ~= 'P' + Pz = spm_P(1,0, U,df,STAT,1,n,S);% uncorrected p value + Pu = spm_P(1,0, U,df,STAT,R,n,S);% FWE-corrected {based on Z) + Qu = spm_P_FDR( U,df,STAT,n,QPs);% FDR-corrected {based on Z) + [Pk Pn] = spm_P(1,N(i),u,df,STAT,R,n,S);% [un]corrected {based on k) + + if Pz < tol % Equivalent Z-variate + Ze = Inf; % (underflow => can't compute) + else + Ze = spm_invNcdf(1 - Pz); + end + else + Pz = []; + Pu = []; + Qu = []; + Pk = []; + Pn = []; + Ze = spm_invNcdf(U); + end + + + %-Print cluster and maximum voxel-level p values {Z} + %--------------------------------------------------------------- + h = text(tCol(3),y,sprintf(TabDat.fmt{3},Pk),'FontWeight','Bold',... + 'UserData',Pk,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + + h = text(tCol(4),y,sprintf(TabDat.fmt{4},Nv),'FontWeight','Bold',... + 'UserData',Nv,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + h = text(tCol(5),y,sprintf(TabDat.fmt{5},Pn),'FontWeight','Bold',... + 'UserData',Pn,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + + h = text(tCol(6),y,sprintf(TabDat.fmt{6},Pu),'FontWeight','Bold',... + 'UserData',Pu,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + h = text(tCol(7),y,sprintf(TabDat.fmt{7},Qu),'FontWeight','Bold',... + 'UserData',Qu,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + h = text(tCol(8),y,sprintf(TabDat.fmt{8},U),'FontWeight','Bold',... + 'UserData',U,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + h = text(tCol(9),y,sprintf(TabDat.fmt{9},Ze),'FontWeight','Bold',... + 'UserData',Ze,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + h = ... + text(tCol(10),y,sprintf(TabDat.fmt{10},Pz),'FontWeight','Bold',... + 'UserData',Pz,'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + + % Specifically changed so it properly finds hMIPax + %--------------------------------------------------------------------- + h = text(tCol(11),y,sprintf(TabDat.fmt{11},XYZmm(:,i)),... + 'FontWeight','Bold',... + 'Tag','ListXYZ',... + 'ButtonDownFcn',[... + 'hMIPax = findobj(''tag'',''hMIPax'');',... + 'spm_mip_ui(''SetCoords'',get(gcbo,''UserData''),hMIPax);'],... + 'Interruptible','off','BusyAction','Cancel',... + 'UserData',XYZmm(:,i)); + + HlistXYZ = [HlistXYZ, h]; + if spm_XYZreg('Edist',xyzmm,XYZmm(:,i)) Dis mm apart) + %--------------------------------------------------------------- + [l q] = sort(-Z(j)); % sort on Z value + D = i; + for i = 1:length(q) + d = j(q(i)); + if min(sqrt(sum((XYZmm(:,D)-XYZmm(:,d)*ones(1,size(D,2))).^2)))>Dis; + + if length(D) < Num + + % Paginate if necessary + %----------------------------------------------- + if y < dy + h = text(0.5,-5*dy,sprintf('Page %d',... + spm_figure('#page',Fgraph)),... + 'FontName',PF.helvetica,... + 'FontAngle','Italic',... + 'FontSize',FS(8)); + + spm_figure('NewPage',[hPage,h]) + hPage = []; + y = y0; + end + + % voxel-level p values {Z} + %----------------------------------------------- + if STAT ~= 'P' + Pz = spm_P(1,0,Z(d),df,STAT,1,n,S); + Pu = spm_P(1,0,Z(d),df,STAT,R,n,S); + Qu = spm_P_FDR(Z(d),df,STAT,n,QPs); + if Pz < tol + Ze = Inf; + else, Ze = spm_invNcdf(1 - Pz); end + else + Pz = []; + Pu = []; + Qu = []; + Ze = spm_invNcdf(Z(d)); + end + + h = text(tCol(6),y,sprintf(TabDat.fmt{6},Pu),... + 'UserData',Pu,... + 'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + + h = text(tCol(7),y,sprintf(TabDat.fmt{7},Qu),... + 'UserData',Qu,... + 'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + h = text(tCol(8),y,sprintf(TabDat.fmt{8},Z(d)),... + 'UserData',Z(d),... + 'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + h = text(tCol(9),y,sprintf(TabDat.fmt{9},Ze),... + 'UserData',Ze,... + 'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + h = text(tCol(10),y,sprintf(TabDat.fmt{10},Pz),... + 'UserData',Pz,... + 'ButtonDownFcn','get(gcbo,''UserData'')'); + hPage = [hPage, h]; + + % specifically modified line to use hMIPax + %----------------------------------------------- + h = text(tCol(11),y,... + sprintf(TabDat.fmt{11},XYZmm(:,d)),... + 'Tag','ListXYZ',... + 'ButtonDownFcn',[... + 'hMIPax = findobj(''tag'',''hMIPax'');',... + 'spm_mip_ui(''SetCoords'',',... + 'get(gcbo,''UserData''),hMIPax);'],... + 'Interruptible','off','BusyAction','Cancel',... + 'UserData',XYZmm(:,d)); + + HlistXYZ = [HlistXYZ, h]; + if spm_XYZreg('Edist',xyzmm,XYZmm(:,d))1 + h = text(0.5,-5*dy,sprintf('Page %d/%d',spm_figure('#page',Fgraph)*[1,1]),... + 'FontName',PF.helvetica,'FontSize',FS(8),'FontAngle','Italic'); + spm_figure('NewPage',[hPage,h]) + end + + %-End: Store TabDat in UserData of axes & reset pointer + %======================================================================= + h = uicontextmenu('Tag','TabDat',... + 'UserData',TabDat); + set(gca,'UIContextMenu',h,... + 'Visible','on',... + 'XColor','w','YColor','w') + uimenu(h,'Label','Table') + uimenu(h,'Separator','on','Label','Print text table',... + 'Tag','TD_TxtTab',... + 'CallBack',... + 'spm_list(''txtlist'',get(get(gcbo,''Parent''),''UserData''),3)',... + 'Interruptible','off','BusyAction','Cancel'); + uimenu(h,'Separator','off','Label','Extract table data structure',... + 'Tag','TD_Xdat',... + 'CallBack','get(get(gcbo,''Parent''),''UserData'')',... + 'Interruptible','off','BusyAction','Cancel'); + uimenu(h,'Separator','on','Label','help',... + 'Tag','TD_Xdat',... + 'CallBack','spm_help(''spm_list'')',... + 'Interruptible','off','BusyAction','Cancel'); + + %-Setup registry + %----------------------------------------------------------------------- + set(hAx,'UserData',struct('hReg',hReg,'HlistXYZ',HlistXYZ)) + spm_XYZreg('Add2Reg',hReg,hAx,'spm_list'); + + %-Return TabDat structure & reset pointer + %----------------------------------------------------------------------- + varargout = {TabDat}; + spm('Pointer','Arrow') + + + + + + %======================================================================= + case 'listcluster' %-List for current cluster only + %======================================================================= + % FORMAT TabDat = spm_list('listcluster',SPM,hReg) + + spm('Pointer','Watch') + + %-Parse arguments + %----------------------------------------------------------------------- + if nargin < 2, error('insufficient arguments'), end + if nargin < 3, hReg = []; else, hReg = varargin{3}; end + SPM = varargin{2}; + + %-get number and separation for maxima to be reported + %----------------------------------------------------------------------- + if length(varargin) > 3 + + Num = varargin{4}; % number of maxima per cluster + Dis = varargin{5}; % distance among clusters (mm) + else + Num = 32; + Dis = 4; + end + + + %-if there are suprathreshold voxels, filter out all but current cluster + %----------------------------------------------------------------------- + if length(SPM.Z) + + %-Jump to voxel nearest current location + %-------------------------------------------------------------- + [xyzmm,i] = spm_XYZreg('NearestXYZ',... + spm_results_ui('GetCoords'),SPM.XYZmm); + spm_results_ui('SetCoords',SPM.XYZmm(:,i)); + + %-Find selected cluster + %-------------------------------------------------------------- + A = spm_clusters(SPM.XYZ); + j = find(A == A(i)); + SPM.Z = SPM.Z(j); + SPM.XYZ = SPM.XYZ(:,j); + SPM.XYZmm = SPM.XYZmm(:,j); + if isfield(SPM,'Rd'), SPM.Rd = SPM.Rd(:,j); end + end + + %-Call 'list' functionality to produce table + %----------------------------------------------------------------------- + varargout = {spm_list('list',SPM,hReg,Num,Dis)}; + + + + + + %======================================================================= + case 'txtlist' %-Print ASCII text table + %======================================================================= + % FORMAT spm_list('TxtList',TabDat,c) + + if nargin<2, error('Insufficient arguments'), end + if nargin<3, c=1; else, c=varargin{3}; end + TabDat = varargin{2}; + + %-Table Title + %----------------------------------------------------------------------- + fprintf('\n\nSTATISTICS: %s\n',TabDat.tit) + fprintf('%c','='*ones(1,80)), fprintf('\n') + + %-Table header + %----------------------------------------------------------------------- + fprintf('%s\t',TabDat.hdr{1,c:end-1}), fprintf('%s\n',TabDat.hdr{1,end}) + fprintf('%s\t',TabDat.hdr{2,c:end-1}), fprintf('%s\n',TabDat.hdr{2,end}) + fprintf('%c','-'*ones(1,80)), fprintf('\n') + + %-Table data + %----------------------------------------------------------------------- + for i = 1:size(TabDat.dat,1) + for j=c:size(TabDat.dat,2) + fprintf(TabDat.fmt{j},TabDat.dat{i,j}) + fprintf('\t') + end + fprintf('\n') + end + for i=1:max(1,11-size(TabDat.dat,1)), fprintf('\n'), end + fprintf('%s\n',TabDat.str) + fprintf('%c','-'*ones(1,80)), fprintf('\n') + + %-Table footer + %----------------------------------------------------------------------- + fprintf('%s\n',TabDat.ftr{:}) + fprintf('%c','='*ones(1,80)), fprintf('\n\n') + + + + %======================================================================= + case 'setcoords' %-Co-ordinate change + %======================================================================= + % FORMAT spm_list('SetCoords',xyz,hAx,hReg) + if nargin<3, error('Insufficient arguments'), end + hAx = varargin{3}; + xyz = varargin{2}; + UD = get(hAx,'UserData'); + HlistXYZ = UD.HlistXYZ(ishandle(UD.HlistXYZ)); + + %-Set all co-ord strings to black + %----------------------------------------------------------------------- + set(HlistXYZ,'Color','k') + + %-If co-ord matches a string, highlight it in red + %----------------------------------------------------------------------- + XYZ = get(HlistXYZ,'UserData'); + if iscell(XYZ), XYZ = cat(2,XYZ{:}); end + [null,i,d] = spm_XYZreg('NearestXYZ',xyz,XYZ); + if d=2, st.vols{H}.area = varargin{2}; end; + if isempty(st.bb), st.bb = maxbb; end; + bbox; + cm_pos; + end; + varargout{1} = H; + st.centre = mean(maxbb); + redraw_all + + case 'bb', + if length(varargin)> 0 & all(size(varargin{1})==[2 3]), st.bb = varargin{1}; end; + bbox; + redraw_all; + + case 'redraw', + redraw_all; + eval(st.callback); + if isfield(st,'registry'), + spm_XYZreg('SetCoords',st.centre,st.registry.hReg,st.registry.hMe); + end; + + case 'reposition', + if length(varargin)<1, tmp = findcent; + else, tmp = varargin{1}; end; + if length(tmp)==3 + h = valid_handles(st.snap); + if ~isempty(h) + tmp=st.vols{h(1)}.mat*... + round(inv(st.vols{h(1)}.mat)*[tmp; ... + 1]); + end; + st.centre = tmp(1:3); + end; + redraw_all; + eval(st.callback); + if isfield(st,'registry'), + spm_XYZreg('SetCoords',st.centre,st.registry.hReg,st.registry.hMe); + end; + cm_pos; + + case 'setcoords', + st.centre = varargin{1}; + st.centre = st.centre(:); + redraw_all; + eval(st.callback); + cm_pos; + + case 'space', + if length(varargin)<1, + st.Space = eye(4); + st.bb = maxbb; + bbox; + redraw_all; + else, + space(varargin{1}); + bbox; + redraw_all; + end; + + case 'maxbb', + st.bb = maxbb; + bbox; + redraw_all; + + case 'resolution', + resolution(varargin{1}); + bbox; + redraw_all; + + case 'window', + if length(varargin)<2, + win = 'auto'; + elseif length(varargin{2})==2, + win = varargin{2}; + end; + for i=valid_handles(varargin{1}), + st.vols{i}.window = win; + end; + redraw(varargin{1}); + + case 'delete', + my_delete(varargin{1}); + + case 'move', + move(varargin{1},varargin{2}); + % redraw_all; + + case 'reset', + my_reset; + + case 'pos', + if isempty(varargin), + H = st.centre(:); + else, + H = pos(varargin{1}); + end; + varargout{1} = H; + + case 'interp', + st.hld = varargin{1}; + redraw_all; + + case 'xhairs', + xhairs(varargin{1}); + + case 'register', + register(varargin{1}); + + case 'addblobs', + addblobs(varargin{1}, varargin{2},varargin{3},varargin{4}); + % redraw(varargin{1}); + + case 'addcolouredblobs', + addcolouredblobs(varargin{1}, varargin{2},varargin{3},varargin{4},varargin{5}); + % redraw(varargin{1}); + + case 'addimage', + addimage(varargin{1}, varargin{2}); + % redraw(varargin{1}); + + case 'addcolouredimage', + addcolouredimage(varargin{1}, varargin{2},varargin{3}); + % redraw(varargin{1}); + + case 'addtruecolourimage', + % spm_orthviews('Addtruecolourimage',handle,filename,colourmap,prop,mx,mn) + % Adds blobs from an image in true colour + % handle - image number to add blobs to [default 1] + % filename of image containing blob data [default - request via GUI] + % colourmap - colormap to display blobs in [GUI input] + % prop - intensity proportion of activation cf grayscale [0.4] + % mx - maximum intensity to scale to [maximum value in activation image] + % mn - minimum intensity to scale to [minimum value in activation image] + % + if nargin < 2 + varargin(1) = {1}; + end + if nargin < 3 + varargin(2) = {spm_select(1, 'image', 'Image with activation signal')}; + end + if nargin < 4 + actc = []; + while isempty(actc) + actc = getcmap(spm_input('Colourmap for activation image', '+1','s')); + end + varargin(3) = {actc}; + end + if nargin < 5 + varargin(4) = {0.4}; + end + if nargin < 6 + actv = spm_vol(varargin{2}); + varargin(5) = {max([eps maxval(actv)])}; + end + if nargin < 7 + varargin(6) = {min([0 minval(actv)])}; + end + + addtruecolourimage(varargin{1}, varargin{2},varargin{3}, varargin{4}, ... + varargin{5}, varargin{6}); + % redraw(varargin{1}); + + case 'addcolourbar', + addcolourbar(varargin{1}, varargin{2}); + + case 'rmblobs', + rmblobs(varargin{1}); + % redraw(varargin{1}); + + case 'addcontext', + if nargin == 1, + handles = 1:24; + else, + handles = varargin{1}; + end; + addcontexts(handles); + + case 'rmcontext', + if nargin == 1, + handles = 1:24; + else, + handles = varargin{1}; + end; + rmcontexts(handles); + + case 'context_menu', + c_menu(varargin{:}); + + case 'valid_handles', + if nargin == 1 + handles = 1:24; + else, + handles = varargin{1}; + end; + varargout{1} = valid_handles(handles); + + otherwise, + addonaction = strcmp(st.plugins,action); + if any(addonaction) + feval(['spm_ov_' st.plugins{addonaction}],varargin{:}); + else + warning('Unknown action string') + end; +end; + +spm('Pointer'); +return; + + +%_______________________________________________________________________ +%_______________________________________________________________________ +function addblobs(handle, xyz, t, mat) +global st +global TMAX_ +for i=valid_handles(handle), + if ~isempty(xyz), + rcp = round(xyz); + dim = max(rcp,[],2)'; + off = rcp(1,:) + dim(1)*(rcp(2,:)-1 + dim(2)*(rcp(3,:)-1)); + vol = zeros(dim)+NaN; + vol(off) = t; + vol = reshape(vol,dim); + st.vols{i}.blobs=cell(1,1); + if st.mode == 0, + axpos = get(st.vols{i}.ax{2}.ax,'Position'); + else, + axpos = get(st.vols{i}.ax{1}.ax,'Position'); + end; + mx = max([eps max(t)]); + mn = min([0 min(t)]); + if ~strcmp(TMAX_, 'auto') + mx = str2num(TMAX_); + end + %KND: + if numel(mx)==2 + mn=mx(1); + mx=mx(2); + end + st.vols{i}.blobs{1} = struct('vol',vol,'mat',mat,'max',mx, 'min',mn); + addcolourbar(handle,1); + end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function addimage(handle, fname) +global st +for i=valid_handles(handle), + if isstruct(fname), + vol = fname(1); + else, + vol = spm_vol(fname); + end; + mat = vol.mat; + st.vols{i}.blobs=cell(1,1); + mx = max([eps maxval(vol)]); + mn = min([0 minval(vol)]); + st.vols{i}.blobs{1} = struct('vol',vol,'mat',mat,'max',mx,'min',mn); + addcolourbar(handle,1); +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function addcolouredblobs(handle, xyz, t, mat,colour) +global st +for i=valid_handles(handle), + if ~isempty(xyz), + rcp = round(xyz); + dim = max(rcp,[],2)'; + off = rcp(1,:) + dim(1)*(rcp(2,:)-1 + dim(2)*(rcp(3,:)-1)); + vol = zeros(dim)+NaN; + vol(off) = t; + vol = reshape(vol,dim); + if ~isfield(st.vols{i},'blobs'), + st.vols{i}.blobs=cell(1,1); + bset = 1; + else, + bset = length(st.vols{i}.blobs)+1; + end; + mx = max([eps maxval(vol)]); + mn = min([0 minval(vol)]); + st.vols{i}.blobs{bset} = struct('vol',vol,'mat',mat,'max',mx,'min',mn,'colour',colour); + end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function addcolouredimage(handle, fname,colour) +global st +for i=valid_handles(handle), + if isstruct(fname), + vol = fname(1); + else, + vol = spm_vol(fname); + end; + mat = vol.mat; + if ~isfield(st.vols{i},'blobs'), + st.vols{i}.blobs=cell(1,1); + bset = 1; + else, + bset = length(st.vols{i}.blobs)+1; + end; + mx = max([eps maxval(vol)]); + mn = min([0 minval(vol)]); + st.vols{i}.blobs{bset} = struct('vol',vol,'mat',mat,'max',mx,'min',mn,'colour',colour); +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function addtruecolourimage(handle,fname,colourmap,prop,mx,mn) +% adds true colour image to current displayed image +global st +for i=valid_handles(handle), + if isstruct(fname), + vol = fname(1); + else, + vol = spm_vol(fname); + end; + mat = vol.mat; + if ~isfield(st.vols{i},'blobs'), + st.vols{i}.blobs=cell(1,1); + bset = 1; + else, + bset = length(st.vols{i}.blobs)+1; + end; + c = struct('cmap', colourmap,'prop',prop); + st.vols{i}.blobs{bset} = struct('vol',vol,'mat',mat,'max',mx, ... + 'min',mn,'colour',c); + addcolourbar(handle,bset); +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function addcolourbar(vh,bh) +global st +if st.mode == 0, + axpos = get(st.vols{vh}.ax{2}.ax,'Position'); +else, + axpos = get(st.vols{vh}.ax{1}.ax,'Position'); +end; +st.vols{vh}.blobs{bh}.cbar = axes('Parent',st.fig,... + 'Position',[(axpos(1)+axpos(3)+0.05+(bh-1)*.1) (axpos(2)+0.005) 0.05 (axpos(4)-0.01)],... + 'Box','on', 'YDir','normal', 'XTickLabel',[], 'XTick',[]); +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function rmblobs(handle) +global st +for i=valid_handles(handle), + if isfield(st.vols{i},'blobs'), + for j=1:length(st.vols{i}.blobs), + if isfield(st.vols{i}.blobs{j},'cbar') & ishandle(st.vols{i}.blobs{j}.cbar), + delete(st.vols{i}.blobs{j}.cbar); + end; + end; + st.vols{i} = rmfield(st.vols{i},'blobs'); + end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function register(hreg) +global st +tmp = uicontrol('Position',[0 0 1 1],'Visible','off','Parent',st.fig); +h = valid_handles(1:24); +if ~isempty(h), + tmp = st.vols{h(1)}.ax{1}.ax; + st.registry = struct('hReg',hreg,'hMe', tmp); + spm_XYZreg('Add2Reg',st.registry.hReg,st.registry.hMe, 'spm_orthviews'); +else, + warning('Nothing to register with'); +end; +st.centre = spm_XYZreg('GetCoords',st.registry.hReg); +st.centre = st.centre(:); +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function xhairs(arg1), +global st +st.xhairs = 0; +opt = 'on'; +if ~strcmp(arg1,'on'), + opt = 'off'; +else, + st.xhairs = 1; +end; +for i=valid_handles(1:24), + for j=1:3, + set(st.vols{i}.ax{j}.lx,'Visible',opt); + set(st.vols{i}.ax{j}.ly,'Visible',opt); + end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function H = pos(arg1) +global st +H = []; +for arg1=valid_handles(arg1), + is = inv(st.vols{arg1}.premul*st.vols{arg1}.mat); + H = is(1:3,1:3)*st.centre(:) + is(1:3,4); +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function my_reset +global st +if ~isempty(st) & isfield(st,'registry') & ishandle(st.registry.hMe), + delete(st.registry.hMe); st = rmfield(st,'registry'); +end; +my_delete(1:24); +reset_st; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function my_delete(arg1) +global st +for i=valid_handles(arg1), + kids = get(st.fig,'Children'); + for j=1:3, + if any(kids == st.vols{i}.ax{j}.ax), + set(get(st.vols{i}.ax{j}.ax,'Children'),'DeleteFcn',''); + delete(st.vols{i}.ax{j}.ax); + end; + end; + st.vols{i} = []; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function resolution(arg1) +global st +res = arg1/mean(svd(st.Space(1:3,1:3))); +Mat = diag([res res res 1]); +st.Space = st.Space*Mat; +st.bb = st.bb/res; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function move(handle,pos) +global st +for handle = valid_handles(handle), + st.vols{handle}.area = pos; +end; +bbox; +% redraw(valid_handles(handle)); +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function bb = maxbb +global st +mn = [Inf Inf Inf]; +mx = -mn; +for i=valid_handles(1:24), + bb = [[1 1 1];st.vols{i}.dim(1:3)]; + c = [ bb(1,1) bb(1,2) bb(1,3) 1 + bb(1,1) bb(1,2) bb(2,3) 1 + bb(1,1) bb(2,2) bb(1,3) 1 + bb(1,1) bb(2,2) bb(2,3) 1 + bb(2,1) bb(1,2) bb(1,3) 1 + bb(2,1) bb(1,2) bb(2,3) 1 + bb(2,1) bb(2,2) bb(1,3) 1 + bb(2,1) bb(2,2) bb(2,3) 1]'; + tc = st.Space\(st.vols{i}.premul*st.vols{i}.mat)*c; + tc = tc(1:3,:)'; + mx = max([tc ; mx]); + mn = min([tc ; mn]); +end; +bb = [mn ; mx]; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function space(arg1) +global st +if ~isempty(st.vols{arg1}) + num = arg1; + Mat = st.vols{num}.premul(1:3,1:3)*st.vols{num}.mat(1:3,1:3); + vox = sqrt(sum(Mat.^2)); + if det(Mat(1:3,1:3))<0, vox(1) = -vox(1); end; + Mat = diag([vox 1]); + Space = (st.vols{num}.mat)/Mat; + bb = [1 1 1;st.vols{num}.dim(1:3)]; + bb = [bb [1;1]]; + bb=bb*Mat'; + bb=bb(:,1:3); + bb=sort(bb); + st.Space = Space; + st.bb = bb; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function H = specify_image(arg1, arg2) +global st +H=[]; +ok = 1; +if isstruct(arg1), + V = arg1(1); +else, + try, + V = spm_vol(arg1); + catch, + fprintf('Can not use image "%s"\n', arg1); + return; + end; +end; + +ii = 1; +while ~isempty(st.vols{ii}), ii = ii + 1; end; + +DeleteFcn = ['spm_orthviews(''Delete'',' num2str(ii) ');']; +V.ax = cell(3,1); +for i=1:3, + ax = axes('Visible','off','DrawMode','fast','Parent',st.fig,'DeleteFcn',DeleteFcn,... + 'YDir','normal','ButtonDownFcn',... + ['if strcmp(get(gcf,''SelectionType''),''normal''),spm_orthviews(''Reposition'');',... + 'elseif strcmp(get(gcf,''SelectionType''),''extend''),spm_orthviews(''Reposition'');',... + 'spm_orthviews(''context_menu'',''ts'',1);end;']); + d = image(0,'Tag','Transverse','Parent',ax,... + 'DeleteFcn',DeleteFcn); + set(ax,'Ydir','normal','ButtonDownFcn',... + ['if strcmp(get(gcf,''SelectionType''),''normal''),spm_orthviews(''Reposition'');',... + 'elseif strcmp(get(gcf,''SelectionType''),''extend''),spm_orthviews(''reposition'');',... + 'spm_orthviews(''context_menu'',''ts'',1);end;']); + + lx = line(0,0,'Parent',ax,'DeleteFcn',DeleteFcn); + ly = line(0,0,'Parent',ax,'DeleteFcn',DeleteFcn); + if ~st.xhairs, + set(lx,'Visible','off'); + set(ly,'Visible','off'); + end; + V.ax{i} = struct('ax',ax,'d',d,'lx',lx,'ly',ly); +end; +V.premul = eye(4); +V.window = 'auto'; +V.mapping = 'linear'; +st.vols{ii} = V; + +H = ii; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function addcontexts(handles) +global st +for ii = valid_handles(handles), + cm_handle = addcontext(ii); + for i=1:3, + set(st.vols{ii}.ax{i}.ax,'UIcontextmenu',cm_handle); + st.vols{ii}.ax{i}.cm = cm_handle; + end; +end; +spm_orthviews('reposition',spm_orthviews('pos')); +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function rmcontexts(handles) +global st +for ii = valid_handles(handles), + for i=1:3, + set(st.vols{ii}.ax{i}.ax,'UIcontextmenu',[]); + st.vols{ii}.ax{i} = rmfield(st.vols{ii}.ax{i},'cm'); + end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function bbox +global st +Dims = diff(st.bb)'+1; + +TD = Dims([1 2])'; +CD = Dims([1 3])'; +if st.mode == 0, SD = Dims([3 2])'; else, SD = Dims([2 3])'; end; + +un = get(st.fig,'Units');set(st.fig,'Units','Pixels'); +sz = get(st.fig,'Position');set(st.fig,'Units',un); +sz = sz(3:4); +sz(2) = sz(2)-40; + +for i=valid_handles(1:24), + area = st.vols{i}.area(:); + area = [area(1)*sz(1) area(2)*sz(2) area(3)*sz(1) area(4)*sz(2)]; + if st.mode == 0, + sx = area(3)/(Dims(1)+Dims(3))/1.02; + else, + sx = area(3)/(Dims(1)+Dims(2))/1.02; + end; + sy = area(4)/(Dims(2)+Dims(3))/1.02; + s = min([sx sy]); + + offy = (area(4)-(Dims(2)+Dims(3))*1.02*s)/2 + area(2); + sky = s*(Dims(2)+Dims(3))*0.02; + if st.mode == 0, + offx = (area(3)-(Dims(1)+Dims(3))*1.02*s)/2 + area(1); + skx = s*(Dims(1)+Dims(3))*0.02; + else, + offx = (area(3)-(Dims(1)+Dims(2))*1.02*s)/2 + area(1); + skx = s*(Dims(1)+Dims(2))*0.02; + end; + + DeleteFcn = ['spm_orthviews(''Delete'',' num2str(i) ');']; + + % Transverse + set(st.vols{i}.ax{1}.ax,'Units','pixels', ... + 'Position',[offx offy s*Dims(1) s*Dims(2)],... + 'Units','normalized','Xlim',[0 TD(1)]+0.5,'Ylim',[0 TD(2)]+0.5,... + 'Visible','on','XTick',[],'YTick',[]); + + % Coronal + set(st.vols{i}.ax{2}.ax,'Units','Pixels',... + 'Position',[offx offy+s*Dims(2)+sky s*Dims(1) s*Dims(3)],... + 'Units','normalized','Xlim',[0 CD(1)]+0.5,'Ylim',[0 CD(2)]+0.5,... + 'Visible','on','XTick',[],'YTick',[]); + + % Sagittal + if st.mode == 0, + set(st.vols{i}.ax{3}.ax,'Units','Pixels', 'Box','on',... + 'Position',[offx+s*Dims(1)+skx offy s*Dims(3) s*Dims(2)],... + 'Units','normalized','Xlim',[0 SD(1)]+0.5,'Ylim',[0 SD(2)]+0.5,... + 'Visible','on','XTick',[],'YTick',[]); + else, + set(st.vols{i}.ax{3}.ax,'Units','Pixels', 'Box','on',... + 'Position',[offx+s*Dims(1)+skx offy+s*Dims(2)+sky s*Dims(2) s*Dims(3)],... + 'Units','normalized','Xlim',[0 SD(1)]+0.5,'Ylim',[0 SD(2)]+0.5,... + 'Visible','on','XTick',[],'YTick',[]); + end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function redraw_all +global st +redraw(1:24); +return; +%_______________________________________________________________________ +function mx = maxval(vol) +if isstruct(vol), + mx = -Inf; + for i=1:vol.dim(3), + tmp = spm_slice_vol(vol,spm_matrix([0 0 i]),vol.dim(1:2),0); + imx = max(tmp(find(finite(tmp)))); + if ~isempty(imx),mx = max(mx,imx);end + end; +else, + mx = max(vol(find(finite(vol)))); +end; +%_______________________________________________________________________ +function mn = minval(vol) +if isstruct(vol), + mn = Inf; + for i=1:vol.dim(3), + tmp = spm_slice_vol(vol,spm_matrix([0 0 i]),vol.dim(1:2),0); + imn = min(tmp(find(finite(tmp)))); + if ~isempty(imn),mn = min(mn,imn);end + end; +else, + mn = min(vol(find(finite(vol)))); +end; + +%_______________________________________________________________________ +%_______________________________________________________________________ +function redraw(arg1) +global st +bb = st.bb; +Dims = round(diff(bb)'+1); +is = inv(st.Space); +cent = is(1:3,1:3)*st.centre(:) + is(1:3,4); + +for i = valid_handles(arg1), + M = st.vols{i}.premul*st.vols{i}.mat; + TM0 = [ 1 0 0 -bb(1,1)+1 + 0 1 0 -bb(1,2)+1 + 0 0 1 -cent(3) + 0 0 0 1]; + TM = inv(TM0*(st.Space\M)); + TD = Dims([1 2]); + + CM0 = [ 1 0 0 -bb(1,1)+1 + 0 0 1 -bb(1,3)+1 + 0 1 0 -cent(2) + 0 0 0 1]; + CM = inv(CM0*(st.Space\M)); + CD = Dims([1 3]); + + if st.mode ==0, + SM0 = [ 0 0 1 -bb(1,3)+1 + 0 1 0 -bb(1,2)+1 + 1 0 0 -cent(1) + 0 0 0 1]; + SM = inv(SM0*(st.Space\M)); SD = Dims([3 2]); + else, + SM0 = [ 0 1 0 -bb(1,2)+1 + 0 0 1 -bb(1,3)+1 + 1 0 0 -cent(1) + 0 0 0 1]; + SM0 = [ 0 -1 0 +bb(2,2)+1 + 0 0 1 -bb(1,3)+1 + 1 0 0 -cent(1) + 0 0 0 1]; + SM = inv(SM0*(st.Space\M)); + SD = Dims([2 3]); + end; + + ok=1; + eval('imgt = (spm_slice_vol(st.vols{i},TM,TD,st.hld))'';','ok=0;'); + eval('imgc = (spm_slice_vol(st.vols{i},CM,CD,st.hld))'';','ok=0;'); + eval('imgs = (spm_slice_vol(st.vols{i},SM,SD,st.hld))'';','ok=0;'); + if (ok==0), fprintf('Image "%s" can not be resampled\n', st.vols{i}.fname); + else, + % get min/max threshold + if strcmp(st.vols{i}.window,'auto') + mn = -Inf; + mx = Inf; + else + mn = min(st.vols{i}.window); + mx = max(st.vols{i}.window); + end; + % threshold images + imgt = max(imgt,mn); imgt = min(imgt,mx); + imgc = max(imgc,mn); imgc = min(imgc,mx); + imgs = max(imgs,mn); imgs = min(imgs,mx); + % compute intensity mapping, if histeq is available + if license('test','image_toolbox') == 0 + st.vols{i}.mapping = 'linear'; + end; + switch st.vols{i}.mapping, + case 'linear', + case 'histeq', + % scale images to a range between 0 and 1 + imgt1=(imgt-min(imgt(:)))/(max(imgt(:)-min(imgt(:)))+eps); + imgc1=(imgc-min(imgc(:)))/(max(imgc(:)-min(imgc(:)))+eps); + imgs1=(imgs-min(imgs(:)))/(max(imgs(:)-min(imgs(:)))+eps); + img = histeq([imgt1(:); imgc1(:); imgs1(:)],1024); + imgt = reshape(img(1:numel(imgt1)),size(imgt1)); + imgc = reshape(img(numel(imgt1)+[1:numel(imgc1)]),size(imgc1)); + imgs = reshape(img(numel(imgt1)+numel(imgc1)+[1:numel(imgs1)]),size(imgs1)); + mn = 0; + mx = 1; + case 'quadhisteq', + % scale images to a range between 0 and 1 + imgt1=(imgt-min(imgt(:)))/(max(imgt(:)-min(imgt(:)))+eps); + imgc1=(imgc-min(imgc(:)))/(max(imgc(:)-min(imgc(:)))+eps); + imgs1=(imgs-min(imgs(:)))/(max(imgs(:)-min(imgs(:)))+eps); + img = histeq([imgt1(:).^2; imgc1(:).^2; imgs1(:).^2],1024); + imgt = reshape(img(1:numel(imgt1)),size(imgt1)); + imgc = reshape(img(numel(imgt1)+[1:numel(imgc1)]),size(imgc1)); + imgs = reshape(img(numel(imgt1)+numel(imgc1)+[1:numel(imgs1)]),size(imgs1)); + mn = 0; + mx = 1; + case 'loghisteq', + warning off % messy - but it may avoid extra queries + imgt = log(imgt-min(imgt(:))); + imgc = log(imgc-min(imgc(:))); + imgs = log(imgs-min(imgs(:))); + warning on + imgt(~isfinite(imgt)) = 0; + imgc(~isfinite(imgc)) = 0; + imgs(~isfinite(imgs)) = 0; + % scale log images to a range between 0 and 1 + imgt1=(imgt-min(imgt(:)))/(max(imgt(:)-min(imgt(:)))+eps); + imgc1=(imgc-min(imgc(:)))/(max(imgc(:)-min(imgc(:)))+eps); + imgs1=(imgs-min(imgs(:)))/(max(imgs(:)-min(imgs(:)))+eps); + img = histeq([imgt1(:); imgc1(:); imgs1(:)],1024); + imgt = reshape(img(1:numel(imgt1)),size(imgt1)); + imgc = reshape(img(numel(imgt1)+[1:numel(imgc1)]),size(imgc1)); + imgs = reshape(img(numel(imgt1)+numel(imgc1)+[1:numel(imgs1)]),size(imgs1)); + mn = 0; + mx = 1; + end; + % recompute min/max for display + if strcmp(st.vols{i}.window,'auto') + mx = -inf; mn = inf; + end; + if ~isempty(imgt), + tmp = imgt(finite(imgt)); + mx = max([mx max(max(tmp))]); + mn = min([mn min(min(tmp))]); + end; + if ~isempty(imgc), + tmp = imgc(finite(imgc)); + mx = max([mx max(max(tmp))]); + mn = min([mn min(min(tmp))]); + end; + if ~isempty(imgs), + tmp = imgs(finite(imgs)); + mx = max([mx max(max(tmp))]); + mn = min([mn min(min(tmp))]); + end; + if mx==mn, mx=mn+eps; end; + + if isfield(st.vols{i},'blobs'), + if ~isfield(st.vols{i}.blobs{1},'colour'), + % Add blobs for display using the split colourmap + scal = 64/(mx-mn); + dcoff = -mn*scal; + imgt = imgt*scal+dcoff; + imgc = imgc*scal+dcoff; + imgs = imgs*scal+dcoff; + + if isfield(st.vols{i}.blobs{1},'max'), + mx = st.vols{i}.blobs{1}.max; + else, + mx = max([eps maxval(st.vols{i}.blobs{1}.vol)]); + st.vols{i}.blobs{1}.max = mx; + end; + if isfield(st.vols{i}.blobs{1},'min'), + mn = st.vols{i}.blobs{1}.min; + else, + mn = min([0 minval(st.vols{i}.blobs{1}.vol)]); + st.vols{i}.blobs{1}.min = mn; + end; + + vol = st.vols{i}.blobs{1}.vol; + M = st.vols{i}.premul*st.vols{i}.blobs{1}.mat; + tmpt = spm_slice_vol(vol,inv(TM0*(st.Space\M)),TD,[0 NaN])'; + tmpc = spm_slice_vol(vol,inv(CM0*(st.Space\M)),CD,[0 NaN])'; + tmps = spm_slice_vol(vol,inv(SM0*(st.Space\M)),SD,[0 NaN])'; + + %tmpt_z = find(tmpt==0);tmpt(tmpt_z) = NaN; + %tmpc_z = find(tmpc==0);tmpc(tmpc_z) = NaN; + %tmps_z = find(tmps==0);tmps(tmps_z) = NaN; + + sc = 64/(mx-mn); + off = 65.51-mn*sc; + msk = find(finite(tmpt)); imgt(msk) = off+tmpt(msk)*sc; + msk = find(finite(tmpc)); imgc(msk) = off+tmpc(msk)*sc; + msk = find(finite(tmps)); imgs(msk) = off+tmps(msk)*sc; + + cmap = get(st.fig,'Colormap'); + + figure(st.fig) + if mn*mx < 0 + setcolormap('gray-hot-cold') + elseif mx > 0 + setcolormap('gray-hot'); + else + setcolormap('gray-cold') + end + redraw_colourbar(i,1,[mn mx],[1:64]'+64); + elseif isstruct(st.vols{i}.blobs{1}.colour), + % Add blobs for display using a defined + % colourmap + + % colourmaps + gryc = [0:63]'*ones(1,3)/63; + actc = ... + st.vols{1}.blobs{1}.colour.cmap; + actp = ... + st.vols{1}.blobs{1}.colour.prop; + + % scale grayscale image, not finite -> black + imgt = scaletocmap(imgt,mn,mx,gryc,65); + imgc = scaletocmap(imgc,mn,mx,gryc,65); + imgs = scaletocmap(imgs,mn,mx,gryc,65); + gryc = [gryc; 0 0 0]; + + % get max for blob image + vol = st.vols{i}.blobs{1}.vol; + mat = st.vols{i}.premul*st.vols{i}.blobs{1}.mat; + if isfield(st.vols{i}.blobs{1},'max'), + cmx = st.vols{i}.blobs{1}.max; + else, + cmx = max([eps maxval(st.vols{i}.blobs{1}.vol)]); + end; + if isfield(st.vols{i}.blobs{1},'min'), + cmn = st.vols{i}.blobs{1}.min; + else, + cmn = -cmx; + end; + + % get blob data + vol = st.vols{i}.blobs{1}.vol; + M = st.vols{i}.premul*st.vols{i}.blobs{1}.mat; + tmpt = spm_slice_vol(vol,inv(TM0*(st.Space\M)),TD,[0 NaN])'; + tmpc = spm_slice_vol(vol,inv(CM0*(st.Space\M)),CD,[0 NaN])'; + tmps = spm_slice_vol(vol,inv(SM0*(st.Space\M)),SD,[0 NaN])'; + + % actimg scaled round 0, black NaNs + topc = size(actc,1)+1; + tmpt = scaletocmap(tmpt,cmn,cmx,actc,topc); + tmpc = scaletocmap(tmpc,cmn,cmx,actc,topc); + tmps = scaletocmap(tmps,cmn,cmx,actc,topc); + actc = [actc; 0 0 0]; + + % combine gray and blob data to + % truecolour + imgt = reshape(actc(tmpt(:),:)*actp+ ... + gryc(imgt(:),:)*(1-actp), ... + [size(imgt) 3]); + imgc = reshape(actc(tmpc(:),:)*actp+ ... + gryc(imgc(:),:)*(1-actp), ... + [size(imgc) 3]); + imgs = reshape(actc(tmps(:),:)*actp+ ... + gryc(imgs(:),:)*(1-actp), ... + [size(imgs) 3]); + + redraw_colourbar(i,1,[cmn cmx],[1:64]'+64); + + else, + % Add full colour blobs - several sets at once + scal = 1/(mx-mn); + dcoff = -mn*scal; + + wt = zeros(size(imgt)); + wc = zeros(size(imgc)); + ws = zeros(size(imgs)); + + imgt = repmat(imgt*scal+dcoff,[1,1,3]); + imgc = repmat(imgc*scal+dcoff,[1,1,3]); + imgs = repmat(imgs*scal+dcoff,[1,1,3]); + + cimgt = zeros(size(imgt)); + cimgc = zeros(size(imgc)); + cimgs = zeros(size(imgs)); + + for j=1:length(st.vols{i}.blobs), % get colours of all images first + if isfield(st.vols{i}.blobs{j},'colour'), + colour(j,:) = reshape(st.vols{i}.blobs{j}.colour, [1 3]); + else, + colour(j,:) = [1 0 0]; + end; + end; + %colour = colour/max(sum(colour)); + + for j=1:length(st.vols{i}.blobs), + if isfield(st.vols{i}.blobs{j},'max'), + mx = st.vols{i}.blobs{j}.max; + else, + mx = max([eps max(st.vols{i}.blobs{j}.vol(:))]); + st.vols{i}.blobs{j}.max = mx; + end; + if isfield(st.vols{i}.blobs{j},'min'), + mn = st.vols{i}.blobs{j}.min; + else, + mn = min([0 min(st.vols{i}.blobs{j}.vol(:))]); + st.vols{i}.blobs{j}.min = mn; + end; + + vol = st.vols{i}.blobs{j}.vol; + M = st.Space\st.vols{i}.premul*st.vols{i}.blobs{j}.mat; + tmpt = spm_slice_vol(vol,inv(TM0*M),TD,[0 NaN])'; + tmpc = spm_slice_vol(vol,inv(CM0*M),CD,[0 NaN])'; + tmps = spm_slice_vol(vol,inv(SM0*M),SD,[0 NaN])'; + % check min/max of sampled image + % against mn/mx as given in st + tmpt(tmpt(:)mx) = mx; + tmpc(tmpc(:)>mx) = mx; + tmps(tmps(:)>mx) = mx; + tmpt = (tmpt-mn)/(mx-mn); + tmpc = (tmpc-mn)/(mx-mn); + tmps = (tmps-mn)/(mx-mn); + tmpt(~finite(tmpt)) = 0; + tmpc(~finite(tmpc)) = 0; + tmps(~finite(tmps)) = 0; + + cimgt = cimgt + cat(3,tmpt*colour(j,1),tmpt*colour(j,2),tmpt*colour(j,3)); + cimgc = cimgc + cat(3,tmpc*colour(j,1),tmpc*colour(j,2),tmpc*colour(j,3)); + cimgs = cimgs + cat(3,tmps*colour(j,1),tmps*colour(j,2),tmps*colour(j,3)); + + wt = wt + tmpt; + wc = wc + tmpc; + ws = ws + tmps; + cdata=permute(shiftdim([1/64:1/64:1]'* ... + colour(j,:),-1),[2 1 3]); + redraw_colourbar(i,j,[mn mx],cdata); + end; + + imgt = repmat(1-wt,[1 1 3]).*imgt+cimgt; + imgc = repmat(1-wc,[1 1 3]).*imgc+cimgc; + imgs = repmat(1-ws,[1 1 3]).*imgs+cimgs; + + imgt(imgt<0)=0; imgt(imgt>1)=1; + imgc(imgc<0)=0; imgc(imgc>1)=1; + imgs(imgs<0)=0; imgs(imgs>1)=1; + end; + else, + scal = 64/(mx-mn); + dcoff = -mn*scal; + imgt = imgt*scal+dcoff; + imgc = imgc*scal+dcoff; + imgs = imgs*scal+dcoff; + end; + + set(st.vols{i}.ax{1}.d,'HitTest','off', 'Cdata',imgt); + set(st.vols{i}.ax{1}.lx,'HitTest','off',... + 'Xdata',[0 TD(1)]+0.5,'Ydata',[1 1]*(cent(2)-bb(1,2)+1)); + set(st.vols{i}.ax{1}.ly,'HitTest','off',... + 'Ydata',[0 TD(2)]+0.5,'Xdata',[1 1]*(cent(1)-bb(1,1)+1)); + + set(st.vols{i}.ax{2}.d,'HitTest','off', 'Cdata',imgc); + set(st.vols{i}.ax{2}.lx,'HitTest','off',... + 'Xdata',[0 CD(1)]+0.5,'Ydata',[1 1]*(cent(3)-bb(1,3)+1)); + set(st.vols{i}.ax{2}.ly,'HitTest','off',... + 'Ydata',[0 CD(2)]+0.5,'Xdata',[1 1]*(cent(1)-bb(1,1)+1)); + + set(st.vols{i}.ax{3}.d,'HitTest','off','Cdata',imgs); + if st.mode ==0, + set(st.vols{i}.ax{3}.lx,'HitTest','off',... + 'Xdata',[0 SD(1)]+0.5,'Ydata',[1 1]*(cent(2)-bb(1,2)+1)); + set(st.vols{i}.ax{3}.ly,'HitTest','off',... + 'Ydata',[0 SD(2)]+0.5,'Xdata',[1 1]*(cent(3)-bb(1,3)+1)); + else, + set(st.vols{i}.ax{3}.lx,'HitTest','off',... + 'Xdata',[0 SD(1)]+0.5,'Ydata',[1 1]*(cent(3)-bb(1,3)+1)); + set(st.vols{i}.ax{3}.ly,'HitTest','off',... + 'Ydata',[0 SD(2)]+0.5,'Xdata',[1 1]*(bb(2,2)+1-cent(2))); + end; + + if ~isempty(st.plugins) % process any addons + for k = 1:prod(size(st.plugins)) + if isfield(st.vols{i},st.plugins{k}) + feval(['spm_ov_', st.plugins{k}], ... + 'redraw', i, TM0, TD, CM0, CD, SM0, SD); + end; + end; + end; + end; +end; +drawnow; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function redraw_colourbar(vh,bh,interval,cdata) +global st +if isfield(st.vols{vh}.blobs{bh},'cbar') + if st.mode == 0, + axpos = get(st.vols{vh}.ax{2}.ax,'Position'); + else, + axpos = get(st.vols{vh}.ax{1}.ax,'Position'); + end; + % only scale cdata if we have out-of-range truecolour values + if ndims(cdata)==3 && max(cdata(:))>1 + cdata=cdata./max(cdata(:)); + end; + image([0 1],interval,cdata,'Parent',st.vols{vh}.blobs{bh}.cbar); + set(st.vols{vh}.blobs{bh}.cbar, ... + 'Position',[(axpos(1)+axpos(3)+0.05+(bh-1)*.1)... + (axpos(2)+0.005) 0.05 (axpos(4)-0.01)],... + 'YDir','normal','XTickLabel',[],'XTick',[]); +end; +%_______________________________________________________________________ +%_______________________________________________________________________ +function centre = findcent +global st +obj = get(st.fig,'CurrentObject'); +centre = []; +cent = []; +cp = []; +for i=valid_handles(1:24), + for j=1:3, + if ~isempty(obj), + if (st.vols{i}.ax{j}.ax == obj), + cp = get(obj,'CurrentPoint'); + end; + end; + if ~isempty(cp), + cp = cp(1,1:2); + is = inv(st.Space); + cent = is(1:3,1:3)*st.centre(:) + is(1:3,4); + switch j, + case 1, + cent([1 2])=[cp(1)+st.bb(1,1)-1 cp(2)+st.bb(1,2)-1]; + case 2, + cent([1 3])=[cp(1)+st.bb(1,1)-1 cp(2)+st.bb(1,3)-1]; + case 3, + if st.mode ==0, + cent([3 2])=[cp(1)+st.bb(1,3)-1 cp(2)+st.bb(1,2)-1]; + else, + cent([2 3])=[st.bb(2,2)+1-cp(1) cp(2)+st.bb(1,3)-1]; + end; + end; + break; + end; + end; + if ~isempty(cent), break; end; +end; +if ~isempty(cent), centre = st.Space(1:3,1:3)*cent(:) + st.Space(1:3,4); end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function handles = valid_handles(handles) +global st; +handles = handles(:)'; +handles = handles(find(handles<=24 & handles>=1 & ~rem(handles,1))); +for h=handles, + if isempty(st.vols{h}), handles(find(handles==h))=[]; end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function reset_st +global st +fig = spm_figure('FindWin','Graphics'); +bb = []; %[ [-78 78]' [-112 76]' [-50 85]' ]; +st = struct('n', 0, 'vols',[], 'bb',bb,'Space',eye(4),'centre',[0 0 0],'callback',';','xhairs',1,'hld',1,'fig',fig,'mode',1,'plugins',[],'snap',[]); +st.vols = cell(24,1); + +pluginpath = fullfile(spm('Dir'),'spm_orthviews'); +if isdir(pluginpath) + pluginfiles = dir(fullfile(pluginpath,'spm_ov_*.m')); + if ~isempty(pluginfiles) + addpath(pluginpath); + % fprintf('spm_orthviews: Using Plugins in %s\n', pluginpath); + for k = 1:length(pluginfiles) + [p pluginname e v] = fileparts(pluginfiles(k).name); + st.plugins{k} = strrep(pluginname, 'spm_ov_',''); + % fprintf('%s\n',st.plugins{k}); + end; + end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function img = scaletocmap(inpimg,mn,mx,cmap,miscol) +if nargin < 5, miscol=1;end +cml = size(cmap,1); +scf = (cml-1)/(mx-mn); +img = round((inpimg-mn)*scf)+1; +img(find(img<1)) = 1; +img(find(img>cml)) = cml; +img(~finite(img)) = miscol; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function cmap = getcmap(acmapname) +% get colormap of name acmapname +if ~isempty(acmapname), + cmap = evalin('base',acmapname,'[]'); + if isempty(cmap), % not a matrix, is .mat file? + [p f e] = fileparts(acmapname); + acmat = fullfile(p, [f '.mat']); + if exist(acmat, 'file'), + s = struct2cell(load(acmat)); + cmap = s{1}; + end; + end; +end; +if size(cmap, 2)~=3, + warning('Colormap was not an N by 3 matrix') + cmap = []; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function item_parent = addcontext(volhandle) +global st; +%create context menu +fg = spm_figure('Findwin','Graphics');set(0,'CurrentFigure',fg); +%contextmenu +item_parent = uicontextmenu; + +%contextsubmenu 0 +item00 = uimenu(item_parent, 'Label','unknown image', 'Separator','on'); +spm_orthviews('context_menu','image_info',item00,volhandle); +item0a = uimenu(item_parent, 'UserData','pos_mm', 'Callback','spm_orthviews(''context_menu'',''repos_mm'');','Separator','on'); +item0b = uimenu(item_parent, 'UserData','pos_vx', 'Callback','spm_orthviews(''context_menu'',''repos_vx'');'); +item0c = uimenu(item_parent, 'UserData','v_value'); + +%contextsubmenu 1 +item1 = uimenu(item_parent,'Label','Zoom'); +item1_1 = uimenu(item1, 'Label','Full Volume', 'Callback','spm_orthviews(''context_menu'',''zoom'',6);', 'Checked','on'); +item1_2 = uimenu(item1, 'Label','160x160x160mm', 'Callback','spm_orthviews(''context_menu'',''zoom'',5);'); +item1_3 = uimenu(item1, 'Label','80x80x80mm', 'Callback','spm_orthviews(''context_menu'',''zoom'',4);'); +item1_4 = uimenu(item1, 'Label','40x40x40mm', 'Callback','spm_orthviews(''context_menu'',''zoom'',3);'); +item1_5 = uimenu(item1, 'Label','20x20x20mm', 'Callback','spm_orthviews(''context_menu'',''zoom'',2);'); +item1_6 = uimenu(item1, 'Label','10x10x10mm', 'Callback','spm_orthviews(''context_menu'',''zoom'',1);'); + +%contextsubmenu 2 +checked={'off','off'}; +checked{st.xhairs+1} = 'on'; +item2 = uimenu(item_parent,'Label','Crosshairs'); +item2_1 = uimenu(item2, 'Label','on', 'Callback','spm_orthviews(''context_menu'',''Xhair'',''on'');','Checked',checked{2}); +item2_2 = uimenu(item2, 'Label','off', 'Callback','spm_orthviews(''context_menu'',''Xhair'',''off'');','Checked',checked{1}); + +%contextsubmenu 3 +if st.Space == eye(4) + checked = {'off', 'on'}; +else + checked = {'on', 'off'}; +end; +item3 = uimenu(item_parent,'Label','Orientation'); +item3_1 = uimenu(item3, 'Label','World space', 'Callback','spm_orthviews(''context_menu'',''orientation'',3);','Checked',checked{2}); +item3_2 = uimenu(item3, 'Label','Voxel space (1st image)', 'Callback','spm_orthviews(''context_menu'',''orientation'',2);','Checked',checked{1}); +item3_3 = uimenu(item3, 'Label','Voxel space (this image)', 'Callback','spm_orthviews(''context_menu'',''orientation'',1);','Checked','off'); + +%contextsubmenu 3 +if isempty(st.snap) + checked = {'off', 'on'}; +else + checked = {'on', 'off'}; +end; +item3 = uimenu(item_parent,'Label','Snap to Grid'); +item3_1 = uimenu(item3, 'Label','Don''t snap', 'Callback','spm_orthviews(''context_menu'',''snap'',3);','Checked',checked{2}); +item3_2 = uimenu(item3, 'Label','Snap to 1st image', 'Callback','spm_orthviews(''context_menu'',''snap'',2);','Checked',checked{1}); +item3_3 = uimenu(item3, 'Label','Snap to this image', 'Callback','spm_orthviews(''context_menu'',''snap'',1);','Checked','off'); + +%contextsubmenu 4 +if st.hld == 0, + checked = {'off', 'off', 'on'}; +elseif st.hld > 0, + checked = {'off', 'on', 'off'}; +else, + checked = {'on', 'off', 'off'}; +end; +item4 = uimenu(item_parent,'Label','Interpolation'); +item4_1 = uimenu(item4, 'Label','NN', 'Callback','spm_orthviews(''context_menu'',''interpolation'',3);', 'Checked',checked{3}); +item4_2 = uimenu(item4, 'Label','Bilin', 'Callback','spm_orthviews(''context_menu'',''interpolation'',2);','Checked',checked{2}); +item4_3 = uimenu(item4, 'Label','Sinc', 'Callback','spm_orthviews(''context_menu'',''interpolation'',1);','Checked',checked{1}); + +%contextsubmenu 5 +% item5 = uimenu(item_parent,'Label','Position', 'Callback','spm_orthviews(''context_menu'',''position'');'); + +%contextsubmenu 6 +item6 = uimenu(item_parent,'Label','Image','Separator','on'); +item6_1 = uimenu(item6, 'Label','Window'); +item6_1_1 = uimenu(item6_1, 'Label','local'); +item6_1_1_1 = uimenu(item6_1_1, 'Label','auto', 'Callback','spm_orthviews(''context_menu'',''window'',2);'); +item6_1_1_2 = uimenu(item6_1_1, 'Label','manual', 'Callback','spm_orthviews(''context_menu'',''window'',1);'); +item6_1_2 = uimenu(item6_1, 'Label','global'); +item6_1_2_1 = uimenu(item6_1_2, 'Label','auto', 'Callback','spm_orthviews(''context_menu'',''window_gl'',2);'); +item6_1_2_2 = uimenu(item6_1_2, 'Label','manual', 'Callback','spm_orthviews(''context_menu'',''window_gl'',1);'); +if license('test','image_toolbox') == 1 + offon = {'off', 'on'}; + checked = offon(strcmp(st.vols{volhandle}.mapping, ... + {'linear', 'histeq', 'loghisteq', 'quadhisteq'})+1); + item6_2 = uimenu(item6, 'Label','Intensity mapping'); + item6_2_1 = uimenu(item6_2, 'Label','local'); + item6_2_1_1 = uimenu(item6_2_1, 'Label','Linear', 'Checked',checked{1}, ... + 'Callback','spm_orthviews(''context_menu'',''mapping'',''linear'');'); + item6_2_1_2 = uimenu(item6_2_1, 'Label','Equalised histogram', 'Checked',checked{2}, ... + 'Callback','spm_orthviews(''context_menu'',''mapping'',''histeq'');'); + item6_2_1_3 = uimenu(item6_2_1, 'Label','Equalised log-histogram', 'Checked',checked{3}, ... + 'Callback','spm_orthviews(''context_menu'',''mapping'',''loghisteq'');'); + item6_2_1_4 = uimenu(item6_2_1, 'Label','Equalised squared-histogram', 'Checked',checked{4}, ... + 'Callback','spm_orthviews(''context_menu'',''mapping'',''quadhisteq'');'); + item6_2_2 = uimenu(item6_2, 'Label','global'); + item6_2_2_1 = uimenu(item6_2_2, 'Label','Linear', 'Checked',checked{1}, ... + 'Callback','spm_orthviews(''context_menu'',''mapping_gl'',''linear'');'); + item6_2_2_2 = uimenu(item6_2_2, 'Label','Equalised histogram', 'Checked',checked{2}, ... + 'Callback','spm_orthviews(''context_menu'',''mapping_gl'',''histeq'');'); + item6_2_2_3 = uimenu(item6_2_2, 'Label','Equalised log-histogram', 'Checked',checked{3}, ... + 'Callback','spm_orthviews(''context_menu'',''mapping_gl'',''loghisteq'');'); + item6_2_2_4 = uimenu(item6_2_2, 'Label','Equalised squared-histogram', 'Checked',checked{4}, ... + 'Callback','spm_orthviews(''context_menu'',''mapping_gl'',''quadhisteq'');'); +end; +%contextsubmenu 7 +item7 = uimenu(item_parent,'Label','Blobs'); +item7_1 = uimenu(item7, 'Label','Add blobs'); +item7_1_1 = uimenu(item7_1, 'Label','local', 'Callback','spm_orthviews(''context_menu'',''add_blobs'',2);'); +item7_1_2 = uimenu(item7_1, 'Label','global', 'Callback','spm_orthviews(''context_menu'',''add_blobs'',1);'); +item7_2 = uimenu(item7, 'Label','Add image'); +item7_2_1 = uimenu(item7_2, 'Label','local', 'Callback','spm_orthviews(''context_menu'',''add_image'',2);'); +item7_2_2 = uimenu(item7_2, 'Label','global', 'Callback','spm_orthviews(''context_menu'',''add_image'',1);'); +item7_3 = uimenu(item7, 'Label','Add colored blobs','Separator','on'); +item7_3_1 = uimenu(item7_3, 'Label','local', 'Callback','spm_orthviews(''context_menu'',''add_c_blobs'',2);'); +item7_3_2 = uimenu(item7_3, 'Label','global', 'Callback','spm_orthviews(''context_menu'',''add_c_blobs'',1);'); +item7_4 = uimenu(item7, 'Label','Add colored image'); +item7_4_1 = uimenu(item7_4, 'Label','local', 'Callback','spm_orthviews(''context_menu'',''add_c_image'',2);'); +item7_4_2 = uimenu(item7_4, 'Label','global', 'Callback','spm_orthviews(''context_menu'',''add_c_image'',1);'); +item7_5 = uimenu(item7, 'Label','Remove blobs', 'Visible','off','Separator','on'); +item7_6 = uimenu(item7, 'Label','Remove colored blobs','Visible','off'); +item7_6_1 = uimenu(item7_6, 'Label','local', 'Visible','on'); +item7_6_2 = uimenu(item7_6, 'Label','global','Visible','on'); + +if ~isempty(st.plugins) % process any plugins + for k = 1:prod(size(st.plugins)), + feval(['spm_ov_', st.plugins{k}], ... + 'context_menu', volhandle, item_parent); + end; +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function c_menu(varargin) +global st + +switch lower(varargin{1}), + case 'image_info', + if nargin <3, + current_handle = get_current_handle; + else + current_handle = varargin{3}; + end; + if isfield(st.vols{current_handle},'fname'), + [p,n,e,v] = spm_fileparts(st.vols{current_handle}.fname); + if isfield(st.vols{current_handle},'n') + v = sprintf(',%d',st.vols{current_handle}.n); + end; + set(varargin{2}, 'Label',[n e v]); + end; + delete(get(varargin{2},'children')); + if exist('p','var') + item1 = uimenu(varargin{2}, 'Label', p); + end; + if isfield(st.vols{current_handle},'descrip'), + item2 = uimenu(varargin{2}, 'Label',... + st.vols{current_handle}.descrip); + end; + dt = st.vols{current_handle}.dt(1); + item3 = uimenu(varargin{2}, 'Label', sprintf('Data type: %s', spm_type(dt))); + str = 'Intensity: varied'; + if size(st.vols{current_handle}.pinfo,2) == 1, + if st.vols{current_handle}.pinfo(2), + str = sprintf('Intensity: Y = %g X + %g',... + st.vols{current_handle}.pinfo(1:2)'); + else, + str = sprintf('Intensity: Y = %g X', st.vols{current_handle}.pinfo(1)'); + end; + end; + item4 = uimenu(varargin{2}, 'Label',str); + item5 = uimenu(varargin{2}, 'Label', 'Image dims', 'Separator','on'); + item51 = uimenu(varargin{2}, 'Label',... + sprintf('%dx%dx%d', st.vols{current_handle}.dim(1:3))); + prms = spm_imatrix(st.vols{current_handle}.mat); + item6 = uimenu(varargin{2}, 'Label','Voxel size', 'Separator','on'); + item61 = uimenu(varargin{2}, 'Label', sprintf('%.2f %.2f %.2f', prms(7:9))); + item7 = uimenu(varargin{2}, 'Label','Origin', 'Separator','on'); + item71 = uimenu(varargin{2}, 'Label',... + sprintf('%.2f %.2f %.2f', prms(1:3))); + R = spm_matrix([0 0 0 prms(4:6)]); + item8 = uimenu(varargin{2}, 'Label','Rotations', 'Separator','on'); + item81 = uimenu(varargin{2}, 'Label', sprintf('%.2f %.2f %.2f', R(1,1:3))); + item82 = uimenu(varargin{2}, 'Label', sprintf('%.2f %.2f %.2f', R(2,1:3))); + item83 = uimenu(varargin{2}, 'Label', sprintf('%.2f %.2f %.2f', R(3,1:3))); + item9 = uimenu(varargin{2},... + 'Label','Specify other image...',... + 'Callback','spm_orthviews(''context_menu'',''swap_img'');',... + 'Separator','on'); + + case 'repos_mm', + oldpos_mm = spm_orthviews('pos'); + newpos_mm = spm_input('New Position (mm)','+1','r',sprintf('%.2f %.2f %.2f',oldpos_mm),3); + spm_orthviews('reposition',newpos_mm); + + case 'repos_vx' + current_handle = get_current_handle; + oldpos_vx = spm_orthviews('pos', current_handle); + newpos_vx = spm_input('New Position (voxels)','+1','r',sprintf('%.2f %.2f %.2f',oldpos_vx),3); + newpos_mm = st.vols{current_handle}.mat*[newpos_vx;1]; + spm_orthviews('reposition',newpos_mm(1:3)); + + case 'zoom' + zoom_all(varargin{2}); + bbox; + redraw_all; + + case 'xhair', + spm_orthviews('Xhairs',varargin{2}); + cm_handles = get_cm_handles; + for i = 1:length(cm_handles), + z_handle = get(findobj(cm_handles(i),'label','Crosshairs'),'Children'); + set(z_handle,'Checked','off'); %reset check + if strcmp(varargin{2},'off'), op = 1; else op = 2; end + set(z_handle(op),'Checked','on'); + end; + + case 'orientation', + cm_handles = get_cm_handles; + for i = 1:length(cm_handles), + z_handle = get(findobj(cm_handles(i),'label','Orientation'),'Children'); + set(z_handle,'Checked','off'); + end; + if varargin{2} == 3, + spm_orthviews('Space'); + elseif varargin{2} == 2, + spm_orthviews('Space',1); + else, + spm_orthviews('Space',get_current_handle); + z_handle = get(findobj(st.vols{get_current_handle}.ax{1}.cm,'label','Orientation'),'Children'); + set(z_handle(1),'Checked','on'); + return; + end; + for i = 1:length(cm_handles), + z_handle = get(findobj(cm_handles(i),'label','Orientation'),'Children'); + set(z_handle(varargin{2}),'Checked','on'); + end; + + case 'snap', + cm_handles = get_cm_handles; + for i = 1:length(cm_handles), + z_handle = get(findobj(cm_handles(i),'label','Snap to Grid'),'Children'); + set(z_handle,'Checked','off'); + end; + if varargin{2} == 3, + st.snap = []; + elseif varargin{2} == 2, + st.snap = 1; + else, + st.snap = get_current_handle; + z_handle = get(findobj(st.vols{get_current_handle}.ax{1}.cm,'label','Snap to Grid'),'Children'); + set(z_handle(1),'Checked','on'); + return; + end; + for i = 1:length(cm_handles), + z_handle = get(findobj(cm_handles(i),'label','Snap to Grid'),'Children'); + set(z_handle(varargin{2}),'Checked','on'); + end; + + case 'interpolation', + tmp = [-4 1 0]; + st.hld = tmp(varargin{2}); + cm_handles = get_cm_handles; + for i = 1:length(cm_handles), + z_handle = get(findobj(cm_handles(i),'label','Interpolation'),'Children'); + set(z_handle,'Checked','off'); + set(z_handle(varargin{2}),'Checked','on'); + end; + redraw_all; + + case 'window', + current_handle = get_current_handle; + if varargin{2} == 2, + spm_orthviews('window',current_handle); + else + if isnumeric(st.vols{current_handle}.window) + defstr = sprintf('%.2f %.2f', st.vols{current_handle}.window); + else + defstr = ''; + end; + spm_orthviews('window',current_handle,spm_input('Range','+1','e',defstr,2)); + end; + + case 'window_gl', + if varargin{2} == 2, + for i = 1:length(get_cm_handles), + st.vols{i}.window = 'auto'; + end; + else, + current_handle = get_current_handle; + if isnumeric(st.vols{current_handle}.window) + defstr = sprintf('%d %d', st.vols{current_handle}.window); + else + defstr = ''; + end; + data = spm_input('Range','+1','e',defstr,2); + + for i = 1:length(get_cm_handles), + st.vols{i}.window = data; + end; + end; + redraw_all; + + case 'mapping', + checked = strcmp(varargin{2}, ... + {'linear', 'histeq', 'loghisteq', ... + 'quadhisteq'}); + checked = checked(end:-1:1); % Handles are stored in inverse order + current_handle = get_current_handle; + cm_handles = get_cm_handles; + st.vols{current_handle}.mapping = varargin{2}; + z_handle = get(findobj(cm_handles(current_handle), ... + 'label','Intensity mapping'),'Children'); + for k = 1:numel(z_handle) + c_handle = get(z_handle(k), 'Children'); + set(c_handle, 'checked', 'off'); + set(c_handle(checked), 'checked', 'on'); + end; + redraw_all; + + case 'mapping_gl', + checked = strcmp(varargin{2}, ... + {'linear', 'histeq', 'loghisteq', 'quadhisteq'}); + checked = checked(end:-1:1); % Handles are stored in inverse order + cm_handles = get_cm_handles; + for k = valid_handles(1:24), + st.vols{k}.mapping = varargin{2}; + z_handle = get(findobj(cm_handles(k), ... + 'label','Intensity mapping'),'Children'); + for l = 1:numel(z_handle) + c_handle = get(z_handle(l), 'Children'); + set(c_handle, 'checked', 'off'); + set(c_handle(checked), 'checked', 'on'); + end; + end; + redraw_all; + + case 'swap_img', + current_handle = get_current_handle; + new_info = spm_vol(spm_select(1,'image','select new image')); + fn = fieldnames(new_info); + for k=1:numel(fn) + st.vols{current_handle}.(fn{k}) = new_info.(fn{k}); + end; + spm_orthviews('context_menu','image_info',get(gcbo, 'parent')); + redraw_all; + + case 'add_blobs', + % Add blobs to the image - in split colortable + cm_handles = valid_handles(1:24); + if varargin{2} == 2, cm_handles = get_current_handle; end; + spm_figure('Clear','Interactive'); + [SPM,VOL] = spm_getSPM; + for i = 1:length(cm_handles), + addblobs(cm_handles(i),VOL.XYZ,VOL.Z,VOL.M); + c_handle = findobj(findobj(st.vols{cm_handles(i)}.ax{1}.cm,'label','Blobs'),'Label','Remove blobs'); + set(c_handle,'Visible','on'); + delete(get(c_handle,'Children')); + item7_3_1 = uimenu(c_handle,'Label','local','Callback','spm_orthviews(''context_menu'',''remove_blobs'',2);'); + if varargin{2} == 1, + item7_3_2 = uimenu(c_handle,'Label','global','Callback','spm_orthviews(''context_menu'',''remove_blobs'',1);'); + end; + end; + redraw_all; + + case 'remove_blobs', + cm_handles = valid_handles(1:24); + if varargin{2} == 2, cm_handles = get_current_handle; end; + for i = 1:length(cm_handles), + rmblobs(cm_handles(i)); + c_handle = findobj(findobj(st.vols{cm_handles(i)}.ax{1}.cm,'label','Blobs'),'Label','Remove blobs'); + delete(get(c_handle,'Children')); + set(c_handle,'Visible','off'); + end; + redraw_all; + + case 'add_image', + % Add blobs to the image - in split colortable + cm_handles = valid_handles(1:24); + if varargin{2} == 2, cm_handles = get_current_handle; end; + spm_figure('Clear','Interactive'); + fname = spm_select(1,'image','select image'); + for i = 1:length(cm_handles), + addimage(cm_handles(i),fname); + c_handle = findobj(findobj(st.vols{cm_handles(i)}.ax{1}.cm,'label','Blobs'),'Label','Remove blobs'); + set(c_handle,'Visible','on'); + delete(get(c_handle,'Children')); + item7_3_1 = uimenu(c_handle,'Label','local','Callback','spm_orthviews(''context_menu'',''remove_blobs'',2);'); + if varargin{2} == 1, + item7_3_2 = uimenu(c_handle,'Label','global','Callback','spm_orthviews(''context_menu'',''remove_blobs'',1);'); + end; + end; + redraw_all; + + case 'add_c_blobs', + % Add blobs to the image - in full colour + cm_handles = valid_handles(1:24); + if varargin{2} == 2, cm_handles = get_current_handle; end; + spm_figure('Clear','Interactive'); + [SPM,VOL] = spm_getSPM; + c = spm_input('Colour','+1','m',... + 'Red blobs|Green blobs|Yellow blobs|Cyan blobs|Blue blobs|Magenta blobs',[1 2 3 4 5 6],1); + colours = [1 0 0;0 1 0;1 1 0;0 0 1;0 1 1;1 0 1]; + c_names = {'red';'green';'yellow';'blue';'cyan';'magenta'}; + hlabel = sprintf('%s (%s)',VOL.title,c_names{c}); + for i = 1:length(cm_handles), + addcolouredblobs(cm_handles(i),VOL.XYZ,VOL.Z,VOL.M,colours(c,:)); + addcolourbar(cm_handles(i),numel(st.vols{cm_handles(i)}.blobs)); + c_handle = findobj(findobj(st.vols{cm_handles(i)}.ax{1}.cm,'label','Blobs'),'Label','Remove colored blobs'); + ch_c_handle = get(c_handle,'Children'); + set(c_handle,'Visible','on'); + %set(ch_c_handle,'Visible',on'); + item7_4_1 = uimenu(ch_c_handle(2),'Label',hlabel,'ForegroundColor',colours(c,:),... + 'Callback','c = get(gcbo,''UserData'');spm_orthviews(''context_menu'',''remove_c_blobs'',2,c);',... + 'UserData',c); + if varargin{2} == 1, + item7_4_2 = uimenu(ch_c_handle(1),'Label',hlabel,'ForegroundColor',colours(c,:),... + 'Callback','c = get(gcbo,''UserData'');spm_orthviews(''context_menu'',''remove_c_blobs'',1,c);',... + 'UserData',c); + end; + end; + redraw_all; + + case 'remove_c_blobs', + cm_handles = valid_handles(1:24); + if varargin{2} == 2, cm_handles = get_current_handle; end; + colours = [1 0 0;1 1 0;0 1 0;0 1 1;0 0 1;1 0 1]; + c_names = {'red';'yellow';'green';'cyan';'blue';'magenta'}; + for i = 1:length(cm_handles), + if isfield(st.vols{cm_handles(i)},'blobs'), + for j = 1:length(st.vols{cm_handles(i)}.blobs), + if st.vols{cm_handles(i)}.blobs{j}.colour == colours(varargin{3},:); + if isfield(st.vols{cm_handles(i)}.blobs{j},'cbar') + delete(st.vols{cm_handles(i)}.blobs{j}.cbar); + end + st.vols{cm_handles(i)}.blobs(j) = []; + break; + end; + end; + rm_c_menu = findobj(st.vols{cm_handles(i)}.ax{1}.cm,'Label','Remove colored blobs'); + delete(findobj(rm_c_menu,'Label',c_names{varargin{3}})); + if isempty(st.vols{cm_handles(i)}.blobs), + st.vols{cm_handles(i)} = rmfield(st.vols{cm_handles(i)},'blobs'); + set(rm_c_menu, 'Visible', 'off'); + end; + end; + end; + redraw_all; + + case 'add_c_image', + % Add truecolored image + cm_handles = valid_handles(1:24); + if varargin{2} == 2, cm_handles = get_current_handle;end; + spm_figure('Clear','Interactive'); + fname = spm_select(1,'image','select image'); + c = spm_input('Colour','+1','m','Red blobs|Yellow blobs|Green blobs|Cyan blobs|Blue blobs|Magenta blobs',[1 2 3 4 5 6],1); + colours = [1 0 0;1 1 0;0 1 0;0 1 1;0 0 1;1 0 1]; + c_names = {'red';'yellow';'green';'cyan';'blue';'magenta'}; + hlabel = sprintf('%s (%s)',fname,c_names{c}); + for i = 1:length(cm_handles), + addcolouredimage(cm_handles(i),fname,colours(c,:)); + addcolourbar(cm_handles(i),numel(st.vols{cm_handles(i)}.blobs)); + c_handle = findobj(findobj(st.vols{cm_handles(i)}.ax{1}.cm,'label','Blobs'),'Label','Remove colored blobs'); + ch_c_handle = get(c_handle,'Children'); + set(c_handle,'Visible','on'); + %set(ch_c_handle,'Visible',on'); + item7_4_1 = uimenu(ch_c_handle(2),'Label',hlabel,'ForegroundColor',colours(c,:),... + 'Callback','c = get(gcbo,''UserData'');spm_orthviews(''context_menu'',''remove_c_blobs'',2,c);','UserData',c); + if varargin{2} == 1 + item7_4_2 = uimenu(ch_c_handle(1),'Label',hlabel,'ForegroundColor',colours(c,:),... + 'Callback','c = get(gcbo,''UserData'');spm_orthviews(''context_menu'',''remove_c_blobs'',1,c);',... + 'UserData',c); + end + end + redraw_all; +end; +%_______________________________________________________________________ +%_______________________________________________________________________ +function current_handle = get_current_handle +global st +cm_handle = get(gca,'UIContextMenu'); +cm_handles = get_cm_handles; +current_handle = find(cm_handles==cm_handle); +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function cm_pos +global st +for i = 1:length(valid_handles(1:24)), + if isfield(st.vols{i}.ax{1},'cm') + set(findobj(st.vols{i}.ax{1}.cm,'UserData','pos_mm'),... + 'Label',sprintf('mm: %.1f %.1f %.1f',spm_orthviews('pos'))); + pos = spm_orthviews('pos',i); + set(findobj(st.vols{i}.ax{1}.cm,'UserData','pos_vx'),... + 'Label',sprintf('vx: %.1f %.1f %.1f',pos)); + set(findobj(st.vols{i}.ax{1}.cm,'UserData','v_value'),... + 'Label',sprintf('Y = %g',spm_sample_vol(st.vols{i},pos(1),pos(2),pos(3),st.hld))); + end +end; +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function cm_handles = get_cm_handles +global st +cm_handles = []; +for i=valid_handles(1:24), + cm_handles = [cm_handles st.vols{i}.ax{1}.cm]; +end +return; +%_______________________________________________________________________ +%_______________________________________________________________________ +function zoom_all(op) +global st +cm_handles = get_cm_handles; +res = [.125 .125 .25 .5 1 1]; +if op==6, + st.bb = maxbb; +else, + vx = sqrt(sum(st.Space(1:3,1:3).^2)); + vx = vx.^(-1); + pos = spm_orthviews('pos'); + pos = st.Space\[pos ; 1]; + pos = pos(1:3)'; + if op == 5, st.bb = [pos-80*vx ; pos+80*vx] ; + elseif op == 4, st.bb = [pos-40*vx ; pos+40*vx] ; + elseif op == 3, st.bb = [pos-20*vx ; pos+20*vx] ; + elseif op == 2, st.bb = [pos-10*vx ; pos+10*vx] ; + elseif op == 1; st.bb = [pos- 5*vx ; pos+ 5*vx] ; + else disp('no Zoom possible'); + end; +end +resolution(res(op)); +redraw_all; +for i = 1:length(cm_handles) + z_handle = get(findobj(cm_handles(i),'label','Zoom'),'Children'); + set(z_handle,'Checked','off'); + set(z_handle(op),'Checked','on'); +end +return; + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% ROI: TimeSeries +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_searchContentEdit(hObject, eventdata) +handles = guidata(hObject); +set(handles.searchContentEdit,'UserData', 'manual'); + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% click load SPM file button +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_loadSPMmat(hObject, eventdata, spmfile) +handles = guidata(hObject); +if nargin < 3 | isempty(spmfile) + %if exist(fullfile(pwd,'SPM.mat'), 'file') + if ~isempty(findstr('SPM2',spm('ver'))) & exist('spm_get')==2 + spmfile = spm_get([1],'SPM.mat','Select a SPM file'); + elseif ~isempty(findstr('SPM5',spm('ver'))) & exist('spm_select')==2 + spmfile = spm_select(1,'SPM.mat','Select a SPM file'); + else + if exist('spm')==2 + error('Check for inconsistencies in your SPM install.\nspm(''ver'') = %s\n', spm('ver')) + else + error('No SPM found'); + end + end +end +xSPM=load(spmfile); +if isfield(xSPM.SPM, 'xCon') + + if ~exist(xSPM.SPM.swd, 'dir') + nswd = fileparts(spmfile); + warning('Directory specified in SPM.swd is missing (%s).\nWill use: %s',xSPM.SPM.swd,nswd); + xSPM.SPM.swd=nswd; + end + numc=listdlg('ListString' , {xSPM.SPM.xCon.name}, 'SelectionMode' , 'single', 'InitialValue', 2);%length(xSPM.SPM.xCon)); + if ~exist(fullfile(xSPM.SPM.swd,xSPM.SPM.xCon(numc).Vspm.fname)) & ... + exist(subarray(fullfile(xSPM.SPM.swd,xSPM.SPM.xCon(numc).Vspm.fname),1:2,-2)) + warning('Changing path') + xSPM.SPM.swd=xSPM.SPM.swd(3:end); + end + if ~isempty(numc) + CallBack_loadImagePush(handles.loadImagePush, [],... + {fullfile(xSPM.SPM.swd,xSPM.SPM.xCon(numc).Vspm.fname)},... + {xSPM.SPM.xCon(numc).name}); + end +else + msgbox('No contrast in this file') +end +return + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% paramest push (Parameter estimates) +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_indivResultsListPush(hObject, eventdata) +handles = guidata(hObject); +if ischar(handles.imageFileName) + handles.imageFileName={handles.imageFileName}; +end +[datadir,condir,condext]=fileparts(handles.imageFileName{1}); +if datadir(2)==':' + datadir=datadir(3:end); +end +[datadir,subdir]=fileparts(datadir); +if isempty(findstr('indiv/', datadir)) +elseif isempty(findstr('rfx/', datadir)) + xSPM=load(fullfile(datadir,'SPM.mat')); + indivdir=xSPM.xY.P; +else + return +end +req = get(handles.indivResultsListPush,'String'); +req = req{get(handles.indivResultsListPush, 'value')}; + +if isequal(req,'Group') + datadir=fileparts(datadir); + condir=handles.imageContrastName{1}; + condir=strrep(condir, '>', 'vs'); + condir=strrep(condir, '<', 'vs'); + condir=strrep(condir, '+', '_'); + condir=strrep(condir, ':', ' at'); + condir=deblank(condir); + condir=fullfile(datadir,'rfx',condir); + if ~exist(condir,'dir') + return + end + xSPM=load(fullfile(condir,'SPM.mat')); + numc=2; + CallBack_loadImagePush(handles.loadImagePush, [],... + {fullfile(xSPM.SPM.swd,xSPM.SPM.xCon(numc).Vspm.fname)},... + {xSPM.SPM.xCon(numc).name}); + return +elseif exist('swds','var') + CallBack_loadImagePush(handles.loadImagePush, [],... + swds(get(gcbo, 'value'),:),... + handles.imageContrastName); +else + if exist(fullfile(datadir,req,'SPM.mat')) + xSPM=load(fullfile(datadir,req,'SPM.mat')); + if ~isfield(handles,'imageContrastName') + return + end + % numc=strmatch(handles.imageContrastName{1},{xSPM.SPM.xCon.name},'exact'); + numc=get(handles.contrastListPush, 'Value'); + filename=fullfile(xSPM.SPM.swd,xSPM.SPM.xCon(numc).Vspm.fname); + else + % SPM adds a comma for 4D volumes + condext = strtok(condext,','); + filename = fullfile(datadir,req,[condir condext]); + warning('Using same filename in %s: ', req, filename) + end + if filename(2)==':' + filename=filename(3:end); + end + if ~exist(filename,'file') + error('File doesn''t exist: %s',filename) + return + end + set(handles.sectionViewListbox, 'Value', [1]); + % handles.sectionViewTargetFile = + % fullfile('\ndiayek\data\gazemo\rawdata\',req,'anat','wf_0001.img'); + guidata(hObject, handles); + if exist('xSPM','var') + CallBack_loadImagePush(handles.loadImagePush, [],... + {filename},... + {xSPM.SPM.xCon(numc).name}); + else + CallBack_loadImagePush(handles.loadImagePush, [],... + {filename},... + '???'); + end + % CallBack_allIntensityRadio(hObject, eventdata, 'c'); +end +uicontrol(hObject) + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% paramest push (Parameter estimates) +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function CallBack_paramestRegionPush(hObject, eventdata, roi,readdata) +if nargin<4 + readdata=1; +end +roi=struct('type', roi); +handles = guidata(hObject); +roi.name=get(handles.structureEdit, 'String'); + +[img.p, img.fname, img.ext]=fileparts(handles.imageFileName{1}); +spmfile=fullfile(img.p,'SPM.mat'); + +if exist(spmfile(3:end), 'file') + warning('Possible disk swapping in xjview.m'); + spmfile=spmfile(3:end); +end +if ~exist(spmfile) + if findstr('SPM2',spm('ver')) + spmfile = spm_get([0 1],'SPM.mat','locate the RFX SPM.mat'); + elseif findstr('SPM5',spm('ver')) + spmfile = spm_select([0:1],'SPM.mat','locate the RFX SPM.mat'); + end +end +if ~exist(spmfile) + return +end +xSPM=load(spmfile, 'SPM'); +[glmpath, glmpath]=fileparts(fileparts(fileparts(xSPM.SPM.swd))); + +RetrieveRawData = 0; +status = get(gcbf, 'SelectionType') +% right click +if strcmp(status, 'alt') + RetrieveRawData = 1; +end + + + + +%[xyzmm,i] = spm_XYZreg('NearestXYZ',... +% spm_results_ui('GetCoords'),handles.currentxyz); +%spm_results_ui('SetCoords',xSPM.XYZmm(:,i)); +switch roi.type + case 'vox' + if not(isfield(handles,'currentxyz')) %%% + error('Wrong selection') + return + end + xyz = handles.currentxyz; + case 'clu' + if not(isfield(handles,'selectedCluster')) | not(isempty(handles.selectedCluster)) + switch questdlg('Load from workspace','cluster?', 'currentDisplayMNI{1}','...','Cancel','currentDisplayMNI{1}') + case 'currentDisplayMNI{1}' + xyz=evalin('base', 'currentDisplayMNI{1}'); + case '...' + case 'Cancel' + return + end + delete(findobj('Tag', 'paramest')) + + else + delete(findobj('Tag', 'paramest')) + xyz =handles.currentDisplayMNI{1}; + end + case 'sph' + if not(isfield(handles,'currentxyz')) %%% + error('Wrong selection') + return + end + delete(findobj('Tag', 'paramest')) + xyz = handles.currentxyz; + roi.radius=inputdlg('Radius of the sphere?'); + if isempty(roi.radius) + return + end + roi.radius=str2num(roi.radius{1}) + % XYZmm = xSPM.SPM.xVol.M(1:3,:)*[xSPM.SPM.xVol.XYZ; ones(1,size(xSPM.SPM.xVol.XYZ,2))]; + % xyz = sqrt( XYZmm-repmat(xyz,[1 size(XYZmm,2)]) ) + % xyz = XYZmm(:, xyz < roi.radius) + +end +% handles.currentDisplayMNI{1} +% ha=axes('Tag', 'paramest', 'Parent',gcf,'units','normalized','Position',[0.55, 0.05, 0.4, 0.4]); +if isequal(roi.type, 'sph') + if roi.radius > 20 + if not(spm_input({'Many many voxels may be retrieve','Radius of the sphere (in mm):',... + num2str(roi.radius),... + 'Continue anyway?'},... + 1,'bd',{'OK','NO'},[1,0])) + return + end + end + +end +if size(xyz,1)>40 + if not(spm_input({'Many many voxels to retrieve','Number of voxel:',... + num2str(size(xyz,1)),... + 'Continue anyway?'},... + 1,'bd',{'OK','NO'},[1,0])) + return + end +end + +allbetas= []; +allcons= []; +allxyz = []; +allvxyz = []; +roi.nvox = []; +hwait = waitbar(0,'Reading 1st level data'); +lcd=cd; +handles.currentxyz +if isfield(handles, 'paramest') + handles.paramest=[]; +end + +roi.stat.TF=handles.TF; +roi.stat.pValue=handles.pValue; +roi.stat.df=handles.df; +% roi.stat.Intensity = +% handles.currentDisplayIntensity{1}(find(all(handles.currentDisplayMNI{1}==repmat(handles.currentxyz, 3,1),2))); +[i,j]=ismember(handles.mni{1}, flipud(xyz), 'rows'); +roi.stat.intensity(j(i)) = handles.intensity{1}(i); + +if isstruct(xSPM.SPM.xX.K) | ~readdata + n=1; + % Ic=strmatch('F Task',{xSPM.SPM.xCon.name}) + % + % allbetas=[] + % vcon= + % XYZ = SPM.xVol.XYZ; + % XYZmm = SPM.xVol.M(1:3,:)*[XYZ; ones(1,size(XYZ,2))]; + % tmpallbetas= []; + % [nxyz,i] = spm_XYZreg('NearestXYZ',xyz(cluvox,:),XYZmm); + % allbetas = [allbetas;(mean(tmpallbetas,1))]; + % allxyz= [allxyz;mean(tmpallxyz,1)]; + +else + n= length(xSPM.SPM.xY.P) +end + +% Is that a correlation? +PlotCorrelation = (n>1) && not(isequal(all(xSPM.SPM.xX.X==1), [1])); + +try + roi.sub2pr = str2num(subarray(strvcat(xSPM.SPM.xY.P),55:56,2)); +end + + +for sub = 1:n + + waitbar(sub/n,hwait); + if n>1 + subdir = fileparts(xSPM.SPM.xY.P{sub}); + else + subdir = xSPM.SPM.swd; + end + subdir0 =subdir; + if ~exist(subdir, 'dir') + subdir = regexprep(subdir, '[A-Z]?\:',''); + end + if ~exist(subdir, 'dir') + subdir = regexprep(subdir, {'^\\ndiayek', '^\\ndiaye'},'') + end + + + if ~exist(fullfile(subdir, 'SPM.mat'), 'file') & ... + exist(subarray(fullfile(subdir, 'SPM.mat'),1:2,-2),'file') + subdir = subdir(3:end); + end + if exist(fullfile(subdir, 'SPM.mat'), 'file') + load(fullfile(subdir, 'SPM.mat')); + fprintf('Retrieving betas from: %s\n', fullfile(subdir, 'SPM.mat')) + + if sub==1 + % [cn.path cn.fname cn.ext]=fileparts(char(xSPM.SPM.xY.P(sub,:))); + % consfiles=[SPM.xCon.Vcon]; + + % retrieves the f map (where all betas should be) + Ic = [... + strmatch('f anim', lower({SPM.xCon.name})) ... + strmatch('f task', lower({SPM.xCon.name})) ... + strmatch('f map', lower({SPM.xCon.name}))]; + if isempty(Ic) + Ic = listdlg('ListString',{SPM.xCon.name},'InitialValue',Ic); + end + Ic=Ic(end); + conname=SPM.xCon(Ic).name; + end + + %------- + Ic = strmatch(conname,{SPM.xCon.name}, 'exact') ; %contrast number + if isempty(Ic) + Ic = listdlg('ListString',{SPM.xCon.name}); + end + %------- + else + SPM = xSPM.SPM + %using raw data + Ic=NaN; + end + try + XYZ = SPM.xVol.XYZ; + XYZmm = SPM.xVol.M(1:3,:)*[XYZ; ones(1,size(XYZ,2))]; + tmpallbetas= []; + tmpallcons= []; + tmpallxyz = []; + cd(subdir) + for cluvox = 1:size(xyz,1) + switch (roi.type) + case 'sph' + [d] = spm_XYZreg('Edist',xyz(cluvox,:),XYZmm); + i=find(d<=roi.radius); + govox = ~isempty(i); + nxyz=XYZmm(:,i); + case {'vox', 'clu'} + govox = 1; + [nxyz,i] = spm_XYZreg('NearestXYZ',xyz(cluvox,:),XYZmm); + if sqrt(sum((nxyz'-xyz(cluvox,:)).^2))>=sqrt(3) %one voxel in each dimZ + govox= spm_input({'No data stored for this voxel','Closest voxels with data are:',... + num2str(xyz(cluvox,:)),... + 'Continue anyway?'},... + 1,'bd',{'OK','NO'},[1,0]); + end + end + if govox==1 + vXYZ = XYZ(:,i) ; % coordinates in voxels + + %-Get parameter and hyperparameter estimates + %======================================================================= + % ResMS = spm_get_data(SPM.VResMS,vXYZ); + % Bcov = ResMS*SPM.xX.Bcov; + CI = 1.6449; % = spm_invNcdf(1 - 0.05); + % compute contrast of parameter estimates and 90% C.I. + %---------------------------------------------------------- + %---- + xSPM.SPM.xY.VY(sub).fname = strrep(xSPM.SPM.xY.VY(sub).fname, subdir0, subdir); + tmpallcons = [tmpallcons ; spm_get_data(xSPM.SPM.xY.VY(sub), vXYZ)]; + if ~isnan(Ic) + beta = spm_get_data(SPM.Vbeta, vXYZ); + tmpallbetas = [tmpallbetas; (SPM.xCon(Ic).c'*beta)']; + else + tmpallbetas = tmpallcons; + end + + if RetrieveRawData + if sub==1 + fprintf('\tRetrieving rawdata from: %s\n', fullfile(subdir, 'SPM.mat')) + tmpallcons = [ spm_get_data(xSPM.SPM.xY.VY, vXYZ)']; + end + end + tmpallxyz = [tmpallxyz; nxyz']; + end + end + allbetas = [allbetas;(mean(tmpallbetas,1))]; + allcons = [allcons;(mean(tmpallcons,1))]; + allxyz= [allxyz;mean(tmpallxyz,1)]; + allvxyz= [allvxyz mean(vXYZ,2)]; + roi.nvox = [ roi.nvox; size(tmpallbetas,1)]; + tmp = mean(tmpallxyz); + % disp(['meancluster = ' num2str(mean(tmpallxyz,1))]); + catch + warning('Error with data from: %s', subdir) + end +end +close(hwait) +cd(lcd); +% +if ~isnan(Ic) + regnames=cellstr(subarray(strvcat(strrep(SPM.xX.name(any(SPM.xCon(Ic).c )),'*bf(1)', '')'), 1:6, -2)); + regnames=cellstr(subarray(strvcat(strrep(SPM.xX.name(any(SPM.xCon(Ic).c')),'*bf(1)', '')'), 1:6, -2)); + [regnames, ireg,ireg2]=unique(regnames); + a=[ repmat(' ',length(regnames),1) strvcat(strrep(regnames, '_', ' '))]; + a=dataread('string',a', '%s'); + leg=a(isort(ireg)); +else + leg={'??'}; +end +%a=reshape(a, [],108])'; +% +% n=factor(size(allbetas,2)); +% n=sort([n(end) prod(n(1:end-1))]); +% +% prompt={'Enter the matrix size for x^2:','Enter the colormap name:'}; +% def={'20','hsv'}; +% dlgTitle='Input for Peaks function'; +% lineNo=1; +% answer=inputdlg(prompt,dlgTitle,lineNo,def); +% +% AddOpts.Resize='on'; +% AddOpts.WindowStyle='normal'; +% AddOpts.Interpreter='tex'; +% answer=inputdlg(prompt,dlgTitle,lineNo,def,AddOpts); +% +assignin('base', 'allbetas', allbetas) +% ReshapeData = []; +% xbars=[]; +% fxorder=[2 3 1]; +% switch size(allbetas,2) +% case 8 +% ReshapeData = [4 2]; +% case 4 +% if all(ismember(a,{'Neutral' 'Fearful' 'Angry' 'Happy'})) +% ReshapeData = [4 1]; +% end +% case 18 +% ReshapeData = [18 1]; +% case 16 +% % ReshapeData = [2 8]; +% % xtick=[1 3 4 5 7 8 9 11]; +% % fxorder=[3 2 1]; +% ReshapeData = [16 1]; +% end +% if isempty(ReshapeData) +% ReshapeData = [fliplr(factor(size(allbetas,2))) 1]; +% ReshapeData = [ ReshapeData(1) prod(ReshapeData(2:end))] +% end +% allbetas=reshape(allbetas, [],ReshapeData(1),ReshapeData(2)); +% allbetas = permute(allbetas,fxorder); + +% allxyz=reshape(allxyz, [], 1, 3); +% allxyz=permute(allxyz, [3 2 1]); + +%assignin('base', 'allbetas', allbetas) +%assignin('base', 'allxyz', xyz) + + +roi.allbetas=allbetas; +roi.allxyz=allxyz'; +roi.allvxyz=allvxyz; +roi.allcons=allcons'; +roi.XYZmm=xyz'; +roi.xyz=mean(allxyz,1)'; +assignin('base', 'roi', roi) +%Icc = find(xSPM.SPM.xX.X*xSPM.SPM.xCon(strmatch(handles.imageContrastName{1}, {xSPM.SPM.xCon.name})).c); +% if ~isempty(handles.imageContrastName) +% fn=handles.imageContrastName{1}; +% else +[fp,fn,fe] = fileparts(handles.imageFileName{1}); +fn=[fn fe]; +cn=[xSPM.SPM.xCon.Vspm]; + +% end +Icc = strmatch(fn, {cn.fname}) ; +roi.fname = fullfile(fp,fn); +roi.cn = cn; +roi.Icc=Icc; +roi.leg=leg; +assignin('base', 'roi', roi) + + +try + plot_betas(roi,glmpath,handles) +catch + if n>1 + try + figure;h=barerrorbar(1:(prod(size(roi.allbetas))/size(roi.allbetas,1)),mean(roi.allbetas,1)',stderrw(roi.allbetas,2:ndims(roi.allbetas),1)',NaN) + catch + end + else + figure;bar(roi.allbetas) + end + xlabel('Task') + set(gca, 'XTickLabel', leg) + set(gca, 'XTickLabel', {'Self Pos' 'Self Neg' 'Other Pos' 'Other Neg' 'Word Pos' 'Word Neg'}) + xlabel('Task') + %legend({'Positive Words' 'Negative Words'},'Location', 'Best') + delete(legend) +end +title(sprintf('XYZ = %+0.1f %+0.1f %+0.1f', roi.XYZmm(1:3))); +setappdata(gca, 'ROI', roi) + +if ~isempty(Icc) + if ~isempty(xSPM.SPM.xCon(Icc).c) + roi.allregressors = xSPM.SPM.xX.X*xSPM.SPM.xCon(Icc).c; + else + roi.allregressors = []; + end + assignin('base', 'roi', roi) +end +if PlotCorrelation + %correlation plot + figure; + cla; + plot(roi.allregressors, roi.allcons, 'x') + %hl=legend({'test'}, 0); + [s,o,r2,sce,p]=linearfit(roi.allcons,roi.allregressors); + text(median(roi.allregressors), quantile(roi.allcons,.90), {'Correlation: ' sprintf('r2 = %g\np = %g', r2,p)}) + xlabel(xSPM.SPM.xX.name(find(xSPM.SPM.xCon(Icc).c))) + ylabel(xSPM.SPM.xY.VY(1).descrip) + [xx]=axis; + hold on + plot(xx(1:2), s*xx(1:2)+o, '--') + hold off + title(sprintf('XYZ = %+0.1f %+0.1f %+0.1f', roi.XYZmm(1:3))); +end + +if RetrieveRawData + figure; + if n==1 + subplot(2,1,1); + plot(tmpallcons); + legend('BOLD',0) + subplot(2,1,2); + % iReg=any(SPM.xCon(handles.imageContrastName).c,2); + % plot(normalize(SPM.xX.X(:,iReg), 'unity')*diff(subarray(axis, [3 4]))/5+subarray(axis, 3)) + % legend([{'BOLD'} SPM.xX.name(iReg)],1) + Ic = handles.imageContrastName; + if isempty(Ic) || ~isnumeric(Ic) + Ic = get(handles.contrastListPush, 'Value'); + end + plot( [ + spm_FcUtil('Yc',SPM.xCon(Ic),SPM.xX.xKXs,beta) ... + SPM.xX.X*SPM.xCon(Ic).c*pinv(SPM.xCon(Ic).c)*beta ... + (1-2*get(handles.negativeIntensityRadio, 'Value'))*SPM.xX.X*SPM.xCon(Ic).c ]) + legend({ 'adjusted', 'predicted' SPM.xCon(Ic).name},0) + end +end + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% paramest push (Parameter estimates) +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +function [c_num, c_let, c_word]=mycolourset +c_num = [1 0 0;0 1 0;1 1 0;0 1 1;0 0 1;1 0 1]; +c_let = {'r';'g';'y';'c';'b';'m'}; +c_word= {'red';'green';'yellow';'cyan';'blue';'magenta'}; \ No newline at end of file diff --git a/xline.m b/xline.m new file mode 100644 index 0000000..1701b00 --- /dev/null +++ b/xline.m @@ -0,0 +1,39 @@ +function varargout = hline(y,varargin) +%HLINE - Create a horizontal line on a plot +% [h] = hline(y) +% Adds as many horizontal lines on the current plot as there are values in y. +% +% See also: line, vline + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2006 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2006-02-02 Creation +% +% ----------------------------- Script History --------------------------------- + +x=max(abs(get(gca, 'Xlim')))*[-1 1]; +%x=x*10; +y=y(:); +y=[y*ones(1,2)]'; +x=repmat(x,size(y,2),1)'; + +holdstate=ishold; +hold on; +h=line(x,y); +if ~holdstate + hold off; +end +LineStyles={'-', ':', '--', '-.'}; +for i=1:length(h); + set(h(i), 'LineStyle', LineStyles{ceil(i/size(get(gca, 'ColorOrder'),1))}); +end +if nargout==0 + return +end +varargout={h}; diff --git a/xml2struct.m b/xml2struct.m new file mode 100644 index 0000000..fccd4cc --- /dev/null +++ b/xml2struct.m @@ -0,0 +1,47 @@ +function out = xml2struct(xmlfile) +%XML2STRUCT Read XML file into a structure. + +% Douglas M. Schwarz + +xml = xmlread(xmlfile); + +children = xml.getChildNodes; +for i = 1:children.getLength + out(i) = node2struct(children.item(i-1)); +end + +function s = node2struct(node) + +s.name = char(node.getNodeName); + +if node.hasAttributes + attributes = node.getAttributes; + nattr = attributes.getLength; + s.attributes = struct('name',cell(1,nattr),'value',cell(1,nattr)); + for i = 1:nattr + attr = attributes.item(i-1); + s.attributes(i).name = char(attr.getName); + s.attributes(i).value = char(attr.getValue); + end +else + s.attributes = []; +end + +try + s.data = char(node.getData); +catch + s.data = ''; +end + +if node.hasChildNodes + children = node.getChildNodes; + nchildren = children.getLength; + c = cell(1,nchildren); + s.children = struct('name',c,'attributes',c,'data',c,'children',c); + for i = 1:nchildren + child = children.item(i-1); + s.children(i) = node2struct(child); + end +else + s.children = []; +end diff --git a/xticklabel_rotate.m b/xticklabel_rotate.m new file mode 100644 index 0000000..f01eadc --- /dev/null +++ b/xticklabel_rotate.m @@ -0,0 +1,212 @@ +function hText = xticklabel_rotate(XTick,rot,varargin) +%hText = xticklabel_rotate(XTick,rot,XTickLabel,varargin) Rotate XTickLabel +% +% Syntax: xticklabel_rotate +% +% Input: +% {opt} XTick - vector array of XTick positions & values (numeric) +% uses current XTick values or XTickLabel cell array by +% default (if empty) +% {opt} rot - angle of rotation in degrees, 90° by default +% {opt} XTickLabel - cell array of label strings +% {opt} [var] - "Property-value" pairs passed to text generator +% ex: 'interpreter','none' +% 'Color','m','Fontweight','bold' +% +% Output: hText - handle vector to text labels +% +% Example 1: Rotate existing XTickLabels at their current position by 90° +% xticklabel_rotate +% +% Example 2: Rotate existing XTickLabels at their current position by 45° and change +% font size +% xticklabel_rotate([],45,[],'Fontsize',14) +% +% Example 3: Set the positions of the XTicks and rotate them 90° +% figure; plot([1960:2004],randn(45,1)); xlim([1960 2004]); +% xticklabel_rotate([1960:2:2004]); +% +% Example 4: Use text labels at XTick positions rotated 45° without tex interpreter +% xticklabel_rotate(XTick,45,NameFields,'interpreter','none'); +% +% Example 5: Use text labels rotated 90° at current positions +% xticklabel_rotate([],90,NameFields); +% +% Note : you can not re-run xticklabel_rotate on the same graph. +% +% + + +% This is a modified version of xticklabel_rotate90 by Denis Gilbert +% Modifications include Text labels (in the form of cell array) +% Arbitrary angle rotation +% Output of text handles +% Resizing of axes and title/xlabel/ylabel positions to maintain same overall size +% and keep text on plot +% (handles small window resizing after, but not well due to proportional placement with +% fixed font size. To fix this would require a serious resize function) +% Uses current XTick by default +% Uses current XTickLabel is different from XTick values (meaning has been already defined) + +% Brian FG Katz +% bfgkatz@hotmail.com +% 23-05-03 +% Modified 03-11-06 after user comment +% Allow for exisiting XTickLabel cell array + +% Other m-files required: cell2mat +% Subfunctions: none +% MAT-files required: none +% +% See also: xticklabel_rotate90, TEXT, SET + +% Based on xticklabel_rotate90 +% Author: Denis Gilbert, Ph.D., physical oceanography +% Maurice Lamontagne Institute, Dept. of Fisheries and Oceans Canada +% email: gilbertd@dfo-mpo.gc.ca Web: http://www.qc.dfo-mpo.gc.ca/iml/ +% February 1998; Last revision: 24-Mar-2003 + +% check to see if xticklabel_rotate has already been here (no other reason for this to happen) +if isempty(get(gca,'XTickLabel')), + error('xticklabel_rotate : can not process, either xticklabel_rotate has already been run or XTickLabel field has been erased') ; +end + +% if no XTickLabel AND no XTick are defined use the current XTickLabel +%if nargin < 3 & (~exist('XTick') | isempty(XTick)), +if (nargin < 3 || isempty(varargin{1})) & (~exist('XTick') | isempty(XTick)), + xTickLabels = get(gca,'XTickLabel') ; % use current XTickLabel + if ~iscell(xTickLabels) + % remove trailing spaces if exist (typical with auto generated XTickLabel) + temp1 = num2cell(xTickLabels,2) ; + for loop = 1:length(temp1), + temp1{loop} = deblank(temp1{loop}) ; + end + xTickLabels = temp1 ; + end +varargin = varargin(2:length(varargin)); +end + +% if no XTick is defined use the current XTick +if (~exist('XTick') | isempty(XTick)), + XTick = get(gca,'XTick') ; % use current XTick +end + +%Make XTick a column vector +XTick = XTick(:); + +if ~exist('xTickLabels'), + % Define the xtickLabels + % If XtickLabel is passed as a cell array then use the text + if (length(varargin)>0) & (iscell(varargin{1})), + xTickLabels = varargin{1}; + varargin = varargin(2:length(varargin)); + else + xTickLabels = num2str(XTick); + end +end + +if length(XTick) ~= length(xTickLabels), + error('xticklabel_rotate : must have same number of elements in "XTick" and "XTickLabel"') ; +end + +%Set the Xtick locations and set XTicklabel to an empty string +set(gca,'XTick',XTick,'XTickLabel','') + +if nargin < 2, + rot = 90 ; +end + +% Determine the location of the labels based on the position +% of the xlabel +hxLabel = get(gca,'XLabel'); % Handle to xlabel +xLabelString = get(hxLabel,'String'); + +% if ~isempty(xLabelString) +% warning('You may need to manually reset the XLABEL vertical position') +% end + +set(hxLabel,'Units','data'); +xLabelPosition = get(hxLabel,'Position'); +y = xLabelPosition(2); + +%CODE below was modified following suggestions from Urs Schwarz +y=repmat(y,size(XTick,1),1); +% retrieve current axis' fontsize +fs = get(gca,'fontsize'); + +% Place the new xTickLabels by creating TEXT objects +hText = text(XTick, y, xTickLabels,'fontsize',fs); + +% Rotate the text objects by ROT degrees +set(hText,'Rotation',rot,'HorizontalAlignment','right',varargin{:}) + +% Adjust the size of the axis to accomodate for longest label (like if they are text ones) +% This approach keeps the top of the graph at the same place and tries to keep xlabel at the same place +% This approach keeps the right side of the graph at the same place + +set(get(gca,'xlabel'),'units','data') ; + labxorigpos_data = get(get(gca,'xlabel'),'position') ; +set(get(gca,'ylabel'),'units','data') ; + labyorigpos_data = get(get(gca,'ylabel'),'position') ; +set(get(gca,'title'),'units','data') ; + labtorigpos_data = get(get(gca,'title'),'position') ; + +set(gca,'units','pixel') ; +set(hText,'units','pixel') ; +set(get(gca,'xlabel'),'units','pixel') ; +set(get(gca,'ylabel'),'units','pixel') ; + +origpos = get(gca,'position') ; +textsizes = cell2mat(get(hText,'extent')) ; +longest = max(textsizes(:,4)) ; + +laborigext = get(get(gca,'xlabel'),'extent') ; +laborigpos = get(get(gca,'xlabel'),'position') ; + + +labyorigext = get(get(gca,'ylabel'),'extent') ; +labyorigpos = get(get(gca,'ylabel'),'position') ; +leftlabdist = labyorigpos(1) + labyorigext(1) ; + +% assume first entry is the farthest left +leftpos = get(hText(1),'position') ; +leftext = get(hText(1),'extent') ; +leftdist = leftpos(1) + leftext(1) ; +if leftdist > 0, leftdist = 0 ; end % only correct for off screen problems + +botdist = origpos(2) + laborigpos(2) ; +newpos = [origpos(1)-leftdist longest+botdist origpos(3)+leftdist origpos(4)-longest+origpos(2)-botdist] ; +set(gca,'position',newpos) ; + +% readjust position of nex labels after resize of plot +set(hText,'units','data') ; +for loop= 1:length(hText), + set(hText(loop),'position',[XTick(loop), y(loop)]) ; +end + + +% adjust position of xlabel and ylabel +laborigpos = get(get(gca,'xlabel'),'position') ; +set(get(gca,'xlabel'),'position',[laborigpos(1) laborigpos(2)-longest 0]) ; + +% switch to data coord and fix it all +set(get(gca,'ylabel'),'units','data') ; +set(get(gca,'ylabel'),'position',labyorigpos_data) ; +set(get(gca,'title'),'position',labtorigpos_data) ; + +set(get(gca,'xlabel'),'units','data') ; + labxorigpos_data_new = get(get(gca,'xlabel'),'position') ; +set(get(gca,'xlabel'),'position',[labxorigpos_data(1) labxorigpos_data_new(2)]) ; + + +% Reset all units to normalized to allow future resizing +set(get(gca,'xlabel'),'units','normalized') ; +set(get(gca,'ylabel'),'units','normalized') ; +set(get(gca,'title'),'units','normalized') ; +set(hText,'units','normalized') ; +set(gca,'units','normalized') ; + +if nargout < 1, + clear hText +end + diff --git a/xyz2channel.m b/xyz2channel.m new file mode 100644 index 0000000..3109540 --- /dev/null +++ b/xyz2channel.m @@ -0,0 +1,5 @@ +function [Channel]=xyz2channel(x) +Channel=[]; +for i=1:size(x,1); + Channel(i).Loc=x(i,:)'; +end diff --git a/xyz2channels.m b/xyz2channels.m new file mode 100644 index 0000000..3109540 --- /dev/null +++ b/xyz2channels.m @@ -0,0 +1,5 @@ +function [Channel]=xyz2channel(x) +Channel=[]; +for i=1:size(x,1); + Channel(i).Loc=x(i,:)'; +end diff --git a/xyzlabels.m b/xyzlabels.m new file mode 100644 index 0000000..5238072 --- /dev/null +++ b/xyzlabels.m @@ -0,0 +1,15 @@ +function []=xyzlabels(xl,yl,zl) +% xyzlabels - add labels to each axis +if nargin<3 + zl='z axis'; +end +if nargin<2 + yl='y axis' +end +if nargin<3 + xl='x axis' +end +axis on +xlabel(xl) +ylabel(yl) +zlabel(zl) diff --git a/yline.m b/yline.m new file mode 100644 index 0000000..39448a7 --- /dev/null +++ b/yline.m @@ -0,0 +1,39 @@ +function varargout = yline(x,varargin) +%YLINE - Create a vertical line on a plot +% [h] = yline(x) +% Adds as many vertical lines on the current plot as there are values in x. +% +% See also: line, xline + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2006 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2006-02-02 Creation +% +% ----------------------------- Script History --------------------------------- + +y=max(abs(get(gca, 'Ylim')))*[-1 1]; +%y=*10; +x=x(:); +x=[x*ones(1,2)]'; +y=repmat(y,size(x,2),1)'; + +holdstate=ishold; +hold on; +h=line(x,y,varargin{:}); +if ~holdstate + hold off; +end +LineStyles={'-', ':', '--', '-.'}; +for i=1:length(h); + set(h(i), 'LineStyle', LineStyles{ceil(i/size(get(gca, 'ColorOrder'),1))}); +end +if nargout==0 + return +end +varargout={h}; diff --git a/yprime.dll b/yprime.dll new file mode 100644 index 0000000000000000000000000000000000000000..fd4b620f371bd112697a15312ff38e9cd700e754 GIT binary patch literal 6144 zcmeHLZEPFm9e?Z`#RI2z?a~q?rd^9uEw8bE^_GTu^|#NgW~aY#Zo{Oh z?c9d0L!pEiS7Sq}91(+ZG#b;yfFi2Ns2GZhExY%Mk=TG@udThyRu0|K%2=D}PF6gA zrKOVXW48MKyG`b;%mRrOU}kJ;EwBI(0YniEIn`|f5H?mCI~ea~X7pt^kS#eFxQsd& z+ezYn#?B(nm##QtN552g>k7N3q%^pvS7D>NsMi}!fW{J0oqa&oWY9+G5Kzy?On@rO zi}Bdil3|*J4glG$Cg$eyVl4JRB0&<7h6ceGtu)0`O7V;tb-;?^8AaUm@KU@`$E$=3BKd0B(^tbZs!RRyD9h}bD^8bXx=#=)&GW%zPSmXCrg@B%@HpwA5Q zjb{sd?;T8JLV@o&U*HeaT1xa3_*wANfZrDQJ&S|klNHBuX0;zG6$sxdRd9eRI0twa*0|%Xr5!URuUCF5_*>c<(a4WeJb_ zn3=Di5WP&$aZ@IQ62VO{B5*tYg~~GX*4de3FH?=M$L+0q4QI%*t=4S2CEG1z7ERh= z^`6Y4nIHW**)P^;P5Osf|Fzj^E7QAd7Q{Ag^)E;}ZylS`XSBaeILbMmhxFAVmR?Ju zOK+0PBz;X*{E|e^!1G4bW2ZMk#`Ib$bdt*X8Pz^Hz7}<_EB9mM>>(Y+_3~pwNaFlx zuAg!t#~Cw)=(2H_o-M~if3tJjQ(T_$*RBZUXzxuqoC%uSV8cNo0^og<1 z;5Jfdza=wo!5ng}RqVvI&;~+2dK?H@zcrh*jD5t9egMCmk2?kAoO<=R!C5}V zFmgWYI*OS|)1DnQG@Ik=nUM22QUz2f)kx*Y1O!UgSeG^E3U;W9+5|@$$UE| z(Dg(NuV(S2Cd%qiGNMGags3WyCqt?-U`Ihc9i2PdTPvc3s2!3uv4>vjWKj}RqEkGq z1htrIXDy+_p+qPa6$8WK5k-w{xQlhgVq!#&4vVp*wj>2bHr7-bub|sFfy%qL3-5XO zGQ0te;5P#t0DAX(fG``5tD%Tu9|(t8L`iK=MuQqooir=!|8D|E`)}494={$`YV224 zvNUA)24}BV$xcCrw*q#dO7=Qr_+@{~s|~)rfJ1;M0poxZfK!0efXbSxqCWsV4_E|n z$S+V`fXZ@EO;!BYonyPHFBFL<(3_FuP4V6s@W6HRG}B?DolUDnhPetT~*MN(u<^VwFScM-j#M0ZflGU%NNUn~+2EA(@H z(BMmP$aX7z=v!5_Ju%cpe_$C8f&H=?tqA&I^c7H=g41|>)WPo2*3Mn6ZElw#fNLXU zUj*S$Ad)H@HNl2}WJ*N{-<5R>6Tox*DBSLK$c~pBZ#b?yHaWlRj5$v_Uvw6oYh4>% zE?2Xw(-m;3uBTkjxK6lUaMij+cc;78ExU)@DfhJdy!)zqk90ullLn+8N>53;G%h_O zosgcFekr{q{a*TmbV2%?)a=>m>F{)W0-onQFL++_ocDa> zoom_axes +% +% See also: axes + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2009 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2009-09-09 Creation +% +% ----------------------------- Script History --------------------------------- + +if nargin<1 + help(mfilename) + return +end +if nargin<2 + hAxesVector = gca; +end +if numel(zoom_factor) == 1 + zoom_factor(2) = zoom_factor(1); +end +if numel(zoom_factor) == 2 + zoom_factor(3) = 1; +end + +if nargin<3 + zoomPoint = []; +end + +m = 1-2.*(zoom_factor < 0); + +currLim = axis(hAxesVector); +currXLim = get(hAxesVector,'XLim'); +currYLim = get(hAxesVector,'YLim'); +if ~iscell(currLim) + currLim = {currLim}; + currXLim = {currXLim}; + currYLim = {currYLim}; +end +newXLim = cell(size(currXLim)); +newYLim = cell(size(currYLim)); +newLim = cell(size(currLim)); + +for i=1:numel(hAxesVector) + + hAxes = hAxesVector(i); + limits = getappdata(hAxes,'zoom_zoomOrigAxesLimits'); + if isempty(limits) + axis(hAxes, 'tight'); + limits = axis(hAxes); + end + maxbounds = objbounds(hAxes); + % Use current bounds, g161225, 163055 + if isempty(maxbounds) + maxbounds = axis(hAxes); + end + boundXLim = maxbounds(1:2); + boundYLim = maxbounds(3:4); + isXLog = strcmpi(get(hAxes,'XScale'),'log'); + isYLog = strcmpi(get(hAxes,'YScale'),'log'); + if isXLog + currXLim{i} = log10(currXLim{i}); + end + if isYLog + currYLim{i} = log10(currYLim{i}); + end + newXLim{i} = currXLim{i}; + newYLim{i} = currYLim{i}; + dx = diff(currXLim{i}); + dy = diff(currYLim{i}); + if isempty(zoomPoint) + center_x = currXLim{i}(1)+dx/2; + center_y = currYLim{i}(1)+dy/2; + else + center_x = zoomPoint(i,1); + center_y = zoomPoint(i,2); + end + zoomConstraint = 'none'; + + if ~any(isinf(currXLim{i})) && (strcmpi(zoomConstraint,'horizontal') || strcmp(zoomConstraint,'none')) + xmin = limits(1); + xmax = limits(2); + newdx = dx *m(:,1).*(1/zoom_factor(:,1)); + newdx = min(newdx,xmax-xmin); + % Limit zoom. + center_x = max(center_x,xmin + newdx/2); + center_x = min(center_x,xmax - newdx/2); + newXLim{i} = [max(xmin,center_x-newdx/2) min(xmax,center_x+newdx/2)]; + + % Check for log axes and return to linear values. + if isXLog + newXLim{i} = 10.^newXLim{i}(1:2); + end + end + + % Calculate new y-limits + if ~any(isinf(currYLim{i})) && (strcmpi(zoomConstraint,'vertical') || strcmp(zoomConstraint,'none')) + ymin = limits(3); + ymax = limits(4); + % newdy = dy * (1/zoom_factor(:,2).^(m(:,2)+1)); + newdy = dy *m(:,2).*(1/zoom_factor(:,2)); + newdy = min(newdy,ymax-ymin); + % Limit zoom. + center_y = max(center_y,ymin + newdy/2); + center_y = min(center_y,ymax - newdy/2); + newYLim{i} = [max(ymin,center_y-newdy/2) min(ymax,center_y+newdy/2)]; + + % Check for log axes and return to linear values. + if isYLog + newYLim{i} = 10.^newYLim{i}(1:2); + end + end + + %Check for strangeness in the limits: + if newXLim{i}(1) >= newXLim{i}(2) + newXLim{i} = currXLim{i}; + end + if newYLim{i}(1) >= newYLim{i}(2) + newYLim{i} = currYLim{i}; + end + newLim{i} = [newXLim{i},newYLim{i}]; + + axis(hAxes,newLim{i}); +end +return +% [ '[ cell2mat(x2cell(argouts('... +% ' ''ind2sub( size(getappdata(gca, ''''Traces'''')),nonzeros(idxmember(findobj(gca, ''''Visible'''', ''''off'''') , getappdata(gca,''''Traces''''))))'' ,'... +% ' ndims(getappdata(gca,''Traces''))))) ]' ]); diff --git a/zscore.m b/zscore.m new file mode 100644 index 0000000..d693072 --- /dev/null +++ b/zscore.m @@ -0,0 +1,41 @@ +function [z]=zscore(a,t,dim) +% zscore - compute z-score +% [z]=zscore(a, t ,[dim]) +if nargin<3 + dim=2; +end +sz=size(a); +if nargin<2 + t=1:sz(dim) +end +if length(t)==1 + t=1:t; +end +z = normalize(a,'baseline',dim,t,'z'); +return + + +% +% [z]=zscore(a, t ,[dim]) +% +% a: N-by-T matrix +% t: baseline samples: single value=nb of samples (t> z=zscore(F, 100) -> 100 first samples +% >> z=zscore(F, 1:100) -> idem +if nargin<3 + dim=2; +end +sz=size(a); +if nargin<2 + t=1:sz(dim) +end +if length(t)==1 + t=1:t; +end +nd=ndims(a); +a=permute(a, [dim setdiff(1:nd,dim)]); +z=a(:,:)./(repmat(std(a(t,:)),sz(dim),1) + eps); +z=reshape(z,sz([dim setdiff(1:nd,dim)])); +z=ipermute(z, [dim setdiff(1:nd,dim)]); + diff --git a/ztransform.m b/ztransform.m new file mode 100644 index 0000000..bec3373 --- /dev/null +++ b/ztransform.m @@ -0,0 +1,30 @@ +function [W,Z,P]=ztransform(X,N) +% ztransform - compute the Fisher's Z-transfom +% [W] = ztransform(X) +% [W,Z,P] = ztransform(X,N) may use Hotelling correction for small +% (N<=25) samples and also compute the corresponding Z-value and +% probability. +% +% +% Example +% >> load carsmall +% >> [w,z]=ztransform(corrcoef(Horsepower(1:30), Weight(1:30)),30); +% Since z > 1.96 we can conclude that the correlation is significant +% +% See also: zscore(), tcdf() +% Ref: http://fedc.wiwi.hu-berlin.de/xplore/tutorials/mvahtmlnode23.html + +W = log((1+X)./(1-X))./2; +if nargin<2 + return +end + +if N <= 25 + %Apply Hotelling correction + W= W - ((3*W)+tanh(W))/4/(N-1); + iV=sqrt(N-1); +else + iV=sqrt(N-3); +end +Z=iV*W; +P = 0.5 * erfc(Z ./ sqrt(2)); \ No newline at end of file

      p!9pcj_ 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 literal 0 HcmV?d00001 diff --git a/spm/nii_for_spm2/spm_sample_vol.mexsol b/spm/nii_for_spm2/spm_sample_vol.mexsol new file mode 100755 index 0000000000000000000000000000000000000000..f20c0c1e95fbdc5ef63b7f44df754a64c1effd8d GIT binary patch literal 237300 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%0DU>)Zjj8&h>I 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�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 Y_b3L(#r>Z0RL>QAaP0qnS5EKg54K(rIRF3v literal 0 HcmV?d00001 diff --git a/spm/nii_for_spm2/spm_sample_vol.mexw32 b/spm/nii_for_spm2/spm_sample_vol.mexw32 new file mode 100755 index 0000000000000000000000000000000000000000..803a6318cced9d13d9aea2de6deb9a646ceb83b2 GIT binary patch literal 217600 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 literal 0 HcmV?d00001 diff --git a/spm/nii_for_spm2/spm_sample_vol.mexw64 b/spm/nii_for_spm2/spm_sample_vol.mexw64 new file mode 100644 index 0000000000000000000000000000000000000000..1a73b48f2567dea9231f0ef9792e7a9b92ca55b2 GIT binary patch literal 213504 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 BsAK>D literal 0 HcmV?d00001 diff --git a/spm/nii_for_spm2/spm_slice_vol.mexa64 b/spm/nii_for_spm2/spm_slice_vol.mexa64 new file mode 100644 index 0000000000000000000000000000000000000000..14ebdc8e648a11da534efbf067cb00ffd7f35202 GIT binary patch literal 255007 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 u2*YO;!Cv)Twk6fDoOen8+~&cc literal 0 HcmV?d00001 diff --git a/spm/nii_for_spm2/spm_slice_vol.mexglx b/spm/nii_for_spm2/spm_slice_vol.mexglx new file mode 100755 index 0000000000000000000000000000000000000000..2cff0997fc1611b7abfc8608fc121fa1fd64ce5d GIT binary patch literal 210819 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

    ^H$Mt^ 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∋$n|+ojOJXqH}+#SO#vR^5Qm&SG49vh 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 a+sY=awo#W3Ti>iED=&dny)Y<9x8VQz$_nWK literal 0 HcmV?d00001 diff --git a/spatfreq/logo_sm.gif b/spatfreq/logo_sm.gif new file mode 100644 index 0000000000000000000000000000000000000000..b5b26384f779e2243cdcb05263c3b68b5ae6011a GIT binary patch literal 4707 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 literal 0 HcmV?d00001 diff --git a/sphericity.m b/sphericity.m new file mode 100644 index 0000000..0dd2fa6 --- /dev/null +++ b/sphericity.m @@ -0,0 +1,87 @@ +function [eGG,eHF,eLB,fx] = sphericity(X,nf) +%SPHERICITY - Epsilon for sphericity correction (muldimensional data) +% [eGG,eHF,eLB,fx] = sphericity(X,nf) +% Computes Greenhouse-Geisser, Huynh-Feldt nd Lower-Bound epsilons for +% each factor of X which is a matrix: +% N1 x N2 x ... x N(nf) x N(subjects) x [ ... ] +% nf is the number of factors (leading to nfx effects) +% Note: Subject MUST BE on dimension nf+1 +%OUTPUT: +% eGG: Greenhouse-Geisser epsilon: (nfx)-by-[...] matrix +% eHF: Huynh-Feldt epsilon: (nfx)-by-[...] matrix +% eLB: Lower-bound estimate = 1/(#levels-1) +% fx: cell list of effect (see myanova) +% +% See also: myanova +% Ref: http://www.psych.upenn.edu/~baron/rpsych.pdf + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2006 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2006-02-14 Creation +% +% ----------------------------- Script History --------------------------------- + +sx=size(X); +nX=ndims(X); +NM=prod(sx(nf+2:nX)); +N=sx(nf+1); % NB of subjects +NC=prod(sx(1:nf)); % NB of Cells +X=permute(X,[nf+1 1:nf nf+2:nX]); +fx={}; +for i=1:nf + fx=[fx ; num2cell(nchoosek(1:nf,i),2)]; +end +nfx=length(fx); +eGG=zeros([nfx,sx(nf+2:nX) 1]); +if nargout>1 + eHF=zeros([nfx,sx(nf+2:nX) 1]); +end +if nargout>2 + eLB=zeros([nfx,sx(nf+2:nX) 1]); +end + +for i=1:nfx + Y=permute(X,[1 fx{i}+1 setdiff(2:nf+1,[1+fx{i}]) nf+2:nX]); + NL=prod(sx(fx{i})); % Number of levels + Y=reshape(Y,N,NL,NC/NL,NM); + v=cov2(mean(Y,3)); + eGG(i,:)=NL^2*(mean(diag2(v)) - squeeze(mean(mean(v,2),1))').^2/(NL-1)./... + (squeeze(sum(sum(v.^2,1),2))' - 2*NL*squeeze(sum(mean(v,1).^2,2))' + NL^2*squeeze(mean(mean(v,1),2))'.^2); + clear v; + if nargout>1 + eHF(i,:)=(N*(NL-1)*eGG(i,:)-2)./((NL-1)*(N-1-(NL-1)*eGG(i,:))); + end + if nargout>2 + eLB(i,:)=1./(NL-1); + end +end + +return + + +function [y]=diag2(x) +% Diagonal of the first 2D plan of N-by-N-by-[...] multidimensional array X +sx=[size(x) 1]; +n=sx(1); +nm=prod(sx(3:end)); +y=x(repmat((1:n+1:n^2)',1,nm) +repmat(n^2*[0:nm-1],n,1)); +y=reshape(y,[sx(1),sx(3:end)]); + +function V = cov2(X,varargin) +%COV2 - Multidimensional Covariance +% [V] = cov2(X) +% Computes the M-by-M-by-[...] covariance matrix of data X, which are +% given as N-by-M-by-[...] matrix, N observations and M variables. +sX=[size(X) 1]; +X=reshape(X,sX(1),sX(2),prod(sX(3:end))); +X=X-repmat(sum(X,1)/sX(1),sX(1),1); +V=zeros(sX(2), sX(2), prod(sX(3:end))); +for i=1:size(X,3); + V(:,:,i)= (X(:,:,i)' * X(:,:,i)) / (sX(1)-1); +end \ No newline at end of file diff --git a/spm/SPMstruct.pdf b/spm/SPMstruct.pdf new file mode 100644 index 0000000000000000000000000000000000000000..5fab8418a00bf8561ea0935209d55c3d434d912d GIT binary patch literal 28515 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 +set(hp, 'FaceAlpha', 'interp') +axis image +if NewAxes + view(60,30) +end + diff --git a/spm/myspm_defaults.m b/spm/myspm_defaults.m new file mode 100644 index 0000000..0b3b75d --- /dev/null +++ b/spm/myspm_defaults.m @@ -0,0 +1,126 @@ + +% Sets the defaults which are used by SPM +% +% FORMAT spm_defaults +%_______________________________________________________________________ +% +% This file is intended to be customised for the site. +% Individual users can make copies which can be stored in their own +% matlab subdirectories. If ~/matlab is ahead of the SPM directory +% in the MATLABPATH, then the users own personal defaults are used. +% +% Care must be taken when modifying this file +%_______________________________________________________________________ +% @(#)spm_defaults.m 2.23 John Ashburner, Andrew Holmes 03/04/16 + +global defaults + +% Misc +%======================================================================= +defaults.grid = 0.4; +defaults.cmdline = 0; +defaults.logfile = ''; +defaults.printstr = [];%spm_figure('DefPrintCmd'),'spm2.ps']; + +% File format specific +%======================================================================= +defaults.analyze.multivol = 0; +defaults.analyze.flip = 1; % <<= Very important. Relates to L/R +% Changed by KND: +% defaults.analyze.flip = 0; % <<= Very important. Relates to L/R +% but now in the CMU it's 1... + +% Stats defaults +%======================================================================= +defaults.stats.maxmem = 2^20; +defaults.stats.maxres = 64; +defaults.stats.fmri.ufp = 0.001; +defaults.stats.fmri.t = 16; +defaults.stats.fmri.t0 = 8; +defaults.stats.pet.ufp = 0.05; + +defaults.stats.fmri.t0 = 1; + +defaults.modality = 'FMRI'; + + +% Mask defaults +%======================================================================= +defaults.mask.thresh = 0.8; + +% Realignment defaults +%======================================================================= +defaults.realign.estimate.quality = 0.75; +defaults.realign.estimate.weight = 0; +defaults.realign.estimate.interp = 2; +defaults.realign.estimate.wrap = [0 0 0]; +defaults.realign.write.mask = 1; +defaults.realign.write.interp = 4; +defaults.realign.write.wrap = [0 0 0]; + +% Unwarp defaults +%======================================================================= +defaults.unwarp.estimate.fwhm = 4; +defaults.unwarp.estimate.basfcn = [10 10]; +defaults.unwarp.estimate.regorder= 1; +defaults.unwarp.estimate.regwgt = 1e5; +defaults.unwarp.estimate.soe = 1; +defaults.unwarp.estimate.rem = 1; +defaults.unwarp.estimate.jm = 0; +defaults.unwarp.estimate.noi = 5; +defaults.unwarp.estimate.expround= 'Average'; +% +% Unwarp uses defaults.realign.write +% defaults for writing. +% + +% Coregistration defaults +%======================================================================= +defaults.coreg.estimate.cost_fun = 'nmi'; +defaults.coreg.estimate.sep = [4 2]; +defaults.coreg.estimate.tol = [0.02 0.02 0.02 0.001 0.001 0.001 0.01 0.01 0.01 0.001 0.001 0.001]; +defaults.coreg.estimate.fwhm = [7 7]; +defaults.coreg.write.interp = 1; +defaults.coreg.write.wrap = [0 0 0]; +defaults.coreg.write.mask = 0; + +% Spatial Normalisation defaults +%======================================================================= +defaults.normalise.estimate.smosrc = 8; +defaults.normalise.estimate.smoref = 0; +defaults.normalise.estimate.regtype = 'mni'; +defaults.normalise.estimate.weight = ''; +defaults.normalise.estimate.cutoff = 25; +defaults.normalise.estimate.nits = 16; +defaults.normalise.estimate.reg = 1; +defaults.normalise.estimate.wtsrc = 0; +defaults.normalise.write.preserve = 0; +defaults.normalise.write.bb = [[-78 -112 -50];[78 76 85]]; +%defaults.normalise.write.vox = [2 2 2]; +defaults.normalise.write.vox = [3 3 3]; +defaults.normalise.write.interp = 1; +defaults.normalise.write.wrap = [0 0 0]; + +% Segmentation defaults +%======================================================================= +defaults.segment.estimate.priors = str2mat(... + fullfile(spm('Dir'),'apriori','gray.mnc'),... + fullfile(spm('Dir'),'apriori','white.mnc'),... + fullfile(spm('Dir'),'apriori','csf.mnc')); +defaults.segment.estimate.reg = 0.01; +defaults.segment.estimate.cutoff = 30; +defaults.segment.estimate.samp = 3; +defaults.segment.estimate.bb = [[-88 88]' [-122 86]' [-60 95]']; +defaults.segment.estimate.affreg.smosrc = 8; +defaults.segment.estimate.affreg.regtype = 'mni'; +%defaults.segment.estimate.affreg.weight = fullfile(spm('Dir'),'apriori','brainmask.mnc'); +defaults.segment.estimate.affreg.weight = ''; +defaults.segment.write.cleanup = 1; +defaults.segment.write.wrt_cor = 1; + +% Bias field estimation defaults +%======================================================================= +defaults.bias.nbins = 256; % Number of histogram bins +defaults.bias.reg = 0.01; % Regularisation +defaults.bias.cutoff = 30; % DCT frequency cutoff (mm) + diff --git a/spm/myspm_get_data_xyz.m b/spm/myspm_get_data_xyz.m new file mode 100644 index 0000000..9e9009a --- /dev/null +++ b/spm/myspm_get_data_xyz.m @@ -0,0 +1,128 @@ +function [K,XYZvox,rXYZmm] = myspm_get_data_xyz(V,XYZmm,approx) +%MYSPM_GET_DATA_XYZ - gets data from image files at locations given in mm +% FORMAT [K,XYZvox,rXYZmm] = myspm_get_data(V,XYZmm,approx); +% +%INPUTS +% V: (cell) array of M filenames or volume structures from sspm_vol +% XYZmm: 3-by-N voxels locations to read in millimeters +% approx: Approximation mode. if approx==NaN (default) returns NaN for +% voxels out of the image else approximates see spm_sample_vol() +% +%OUPUTS +% K: M-by-N values read in the files +% XYZvox: 3-by-M-by-N matrix of voxels locations read in voxel space +% rXYZmm: 3-by-M-by-N matrix of actual coordinates of the voxels read +% +% Example +% >> spm_get_data_xyz(P,[45 -50 +50]) retrieves voxel intensity in +% the VMPFC of the MNI template... +% +% See also: spm_get_data + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2008 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2008-10-22 Creation +% +% ----------------------------- Script History --------------------------------- +if isstruct(V) +% output of a dir + if isfield(V,'name') + V={V.name}; + end +end + +if ~isstruct(V) + V = spm_vol(V); + try + V = cat(2,V{:}); + end +end + +if nargin<3 + approx=NaN; +end +if isempty(approx) + approx=NaN; +end +if numel(approx)==1 + approx(1:length(V))=approx; +end +if nargin<2 + XYZmm = 'all'; +end +if ~ischar(XYZmm) + if ~isequal(size(XYZmm,1),3) + error('XYZ input should be 3-by-N') + end + % number of voxels to retrieve + %--------------------------------------------------------------- + n=size(XYZmm,2); + % preallocate outputs + %--------------------------------------------------------------- + K = zeros( length(V),n); + XYZvox= zeros(3,length(V),n); + % expand approximation mode to match size of inputs + %--------------------------------------------------------------- + if prod(size(approx))==1 + approx=repmat(approx,length(V),1); + end + xyz = [XYZmm; ones(1,n)]; + xyz(isinf(xyz)) = 1e10*sign(xyz(isinf(xyz))); +else + switch (XYZmm) + case 'all' + + case 'roi' + % To do: retrieve a region from a given atlas: + % aal:sup front gyrus:Right; + % brodmannn:4:Left etc. + end +end +for i = 1:length(V) + if exist('xyz', 'var') + % trick from spm_XYZreg + vox = round(inv(V(i).mat)*xyz); + j = all(vox(1:3,:)<=repmat(V(i).dim(1:3)',1,n)) & all(vox(1:3,:)>zeros(3,n)); + elseif i==1 + % retrieve all voxels + h=spm_vol(V(i)); + [vox(1,:),vox(2,:),vox(3,:)] = ind2sub(h.dim, 1:prod(h.dim)); + j=logical(ones(size(vox(1,:)))); + n=length(j); + % preallocate outputs + %--------------------------------------------------------------- + K = zeros( length(V),n); + XYZvox= zeros(3,length(V),n); + end + if any(j) + %-Load mask image within current mask & update mask + %------------------------------------------------------- + K(i,j) = spm_sample_vol(V(i),vox(1,j),vox(2,j),vox(3,j),0); + end + if isnan(approx(i)) + K(i,~j)=NaN; + else + K(i,~j) = spm_sample_vol(V(i),vox(1,~j),vox(2,~j),vox(3,~j),approx(i)); + end + XYZvox(1:3,i,1:n) = vox(1:3,:); + if nargout > 2 + if i==1 + % pre-allocation might be useless if later on, a V(i) volume is + % bigger than the first one and all voxels are retrieved) + rXYZmm= zeros(3,length(V),n); + end + if ~exist('xyz', 'var') + rXYZmm(1:4,i,:) = NaN; + rXYZmm(1:4,i,1:n)=V(i).mat*[vox(:,:) ; ones(1,n)]; + rXYZmm(4,:) = NaN; + else + rXYZmm(1:3,i,1:n) = V(i).mat(1:3,:)*xyz; + end + end +end diff --git a/spm/myspm_hierarchy.m b/spm/myspm_hierarchy.m new file mode 100644 index 0000000..e69de29 diff --git a/spm/myspm_struct_info.m b/spm/myspm_struct_info.m new file mode 100644 index 0000000..23488f1 --- /dev/null +++ b/spm/myspm_struct_info.m @@ -0,0 +1,172 @@ +% Short Explanation of the Fields of SPM struct +% ============================================= +% SPM.xY Data +% --------------------------------------------------------------------- +% xY.P filenames of images (with path) +% xY.VY handle to images (from spm_vol) +% VY.fname filename +% VY.dim image dimensions +% VY.mat transformation matrix +% VY.pinfo plane info (scale factor for every image) +% VY.descrip description string +% VY.n ? +% VY.private.hdr analyze header +% VY.userdata user data (usually not used) +% SPM.xX Design Matrix +% --------------------------------------------------------------------- +% xX.X Design Matrix (raw values) +% xX.iH column indices for condition effects partition +% xX.iC column indices for covariates of interest partition +% xX.iB column indices for constants +% xX.iG colunn indices for nuisances covariates partition +% xX.name regressor names (cell array) +% xX.I indicator for factor levels +% xX.sF factor names +% xX.K filter for design matrix +% xX.W pre-whitening matrix (WY = WX*b + We) +% xX.xKXs struct for filtered and pre-whitened design matrix (K*W*X) +% xKXs.X the filtered and whitened design matrix +% xKXs.tol tolerance [max(size(xKXs.X))*max(abs(xKXs.ds))*eps] +% xKXs.ds vector of singular values [diag(s) from [u,s,v]=svd(xKXs.X,0)] +% xKXs.u u as in X = u*diag(ds)*v' [taken from [u,s,v]=svd(xKXs.X,0)] +% xKXs.v v as in X = u*diag(ds)*v' [taken from [u,s,v]=svd(xKXs.X,0)] +% xKXS.rk rank = sum(xKXs.ds > xKXs.tol) +% xKXs.oP orthogonal projector on X +% xKXs.oPp orthogonal projector on X' +% xKXs.ups space in which this one is embedded +% xKXs.sus subspace +% xX.pKX pseudo-inverse of filtered and pre-whitened design +% matrix +% xX.Bcov covariance matrix of parameter estimates +% [diag(Bcov) = variance of parameter estimates] +% xX.V filtered and pre-whitened error covariance matrix +% (K*W*xVi.Vi*W'*K') +% xX.trRV trace of R*V (necessary for effective df) +% xX.trRVRV trace of RVRV (necessary of effective df) +% xX.erdf effective residual df (trRV^2/trRVRV) +% xX.nKX filtered design matrix scaled for display +% SPM.xC Covariate details +% --------------------------------------------------------------------- +% xC.rc raw (as entered) i-th covariate +% xC.rcname name of this covariate +% xC.c covariate as appears in design matrix +% xC.cname cellstr containing names corresponding to xC(i).c +% xC.iCC covariate contering option +% xC.iCFT covariate by factor interaction option +% xC.type covariate type (1=interest,2=nuisance,3=global) +% xC.cols columns of design matrix corresponding to xC(i).c +% xC.descrip description of covariate +% SPM.xGX Global options and values +% --------------------------------------------------------------------- +% xGX.iGXcalc global calculation option used +% xGX.sGCcalc string describing global calculations used +% xGX.rg raw globals (before scaling) +% [mean image intensity, not session-specific] +% xGX.gSF global scaling factor (applied to xGX.rg) +% [global mean, session-specific] +% xGX.GM global mean (gSF*rg = GM) +% xGX.iGMsca grand mean scaling option +% xGX.sGMsca string describing grand mean (/proportional) +% scaling option +% xGX.iGC global covariate centering option +% xGX.sGC string describing global covariate centering option +% xGX.gc center for global covariate +% xGX.iGloNorm global normalization option +% xGX.sGloNorm string describing global normalization option +% SPM.xVi Non-spericity options +% --------------------------------------------------------------------- +% xVi.iid independent and identical errors (0/1) +% xVi.I see SPM.xX.I +% xVi.sF see SPM.xX.sF +% xVi.var factor to correct for inhomogenous variance (?) +% xVi.dep factor to correct of non-identical errors (?) +% xVi.Vi cell array with model components for error GLM +% (design matrices for error) +% xVi.h hyperparameter estimates for xVi.Vi +% (usually called lambda's) +% xVi.Cy spatially whitened covariance matrix of data (Y*Y') +% [used by ReML to estimate h)] +% xVi.CY fitted covariance matrix of data (Y-)*(Y-') +% [used by spm_Bayes] +% SPM.xM Masking options +% --------------------------------------------------------------------- +% xM.T threshold masking values (-Inf = 'none') +% xM.TH nScan x 1 vector of analysis thresholds +% xM.I implicit masking (0 = 'none'; 1 = zero/NaN) +% xM.VM handle to explicit masking image (see SPM.xY.VY) +% xM.xs struture describing masking options +% SPM.xVol information about image dimensions etc. +% --------------------------------------------------------------------- +% xVol.M transformation matrix vox2mm +% xVol.iM transformation matrix mm2vox +% xVol.DIM images dimensions +% xVol.FWHM smoothing filter width (in voxels) +% xVol.R vector of resel counts (in resels) +% xVol.S Lebelgue measure of volume (in voxels) +% xVol.VRpv handle to Resels/voxel images (RPV.img) +% (see SPM.xY.VY) +% SPM.Vbeta parameter estimates +% --------------------------------------------------------------------- +% handle to beta images (see SPM.xY.VY) +% SPM.VResMS residual sum of squares +% --------------------------------------------------------------------- +% handle to images of residuals (ResMS.img) +% SPM.VM mask image +% --------------------------------------------------------------------- +% handle to mask images of analysis voxels +% (mask.img) +% SPM.xCon structure holding contrast information +% --------------------------------------------------------------------- +% xCon.name contrast name +% xCon.STAT type of statistic (T/F) +% xCon.c contrast vector/matrix +% xCon.X0 reduced design matrix (spans design space under Ho) +% xCon.iX0 indicates how contrast was specified +% if by "columns for reduces design" then column +% indices, otherwise either 'c', 'c+', or 'X0' +% see spm_FcUtil +% xCon.X1o remaining design space (orthogonal to X0) +% xCon.eidf effective interest df (numerator df) +% xCon.Vcon handle to contrast image +% (con_xxxx.img,ess_xxxx.img) +% xCon.Vspm handle to statistical image +% (spmT_xxxx.img/spmF_xxxx.img) +% SPM.Sess regressors in design matrix (session-specific) +% --------------------------------------------------------------------- +% Sess.U experimental regressors +% U.name name of regressors +% U.ons onset vector +% U.dur duration (0 = events) +% U.P parametric modulations +% P.name name of parametric modulator +% P.h order of expansion +% P.i +% U.dt internal temporal resolution RT/T +% U.pst peri-stimulus time (specifies occurence of +% scans in relation to events) +% C covariates +% C.C nScan x number of covariates matrix +% C.name cell array with names of covariates +% row indices of rows of design matrix belonging to +% the session +% col indices of columns of design matrix belongin to +% the session +% Fc structrue holding information on F-contrasts +% Fc.i index of F-contrast (?) +% FC.name name of F-contrast +% SPM.xBF structure with information if basis functions +% --------------------------------------------------------------------- +% xBF.name name of basis function of set of bassi functions +% xBF.T time bins +% xBF.T0 reference bin +% xBF.UNITS onsets in scans or seconds +% xBF.Volterra (1 = linear, normal HRF, 2 = trial interactions) +% xBF.dt internal temporal resolution (RT/T) +% xBF.length length of basis functions (in sec) +% xBF.order ? (1 = HRD no order) +% xBF.bf vector with basis functions +% SPM.xsDes structure describing design +% SPM.SPMid version information of SPM +% SPM.swd analysis directory (holding SPM.mat) +% SPM.nscan number of images +help(mfilename) \ No newline at end of file diff --git a/spm/myspm_updatepath.m b/spm/myspm_updatepath.m new file mode 100644 index 0000000..746beb7 --- /dev/null +++ b/spm/myspm_updatepath.m @@ -0,0 +1,89 @@ +function Q = myspm_updatepath(P,varargin) +%MYSPM_UPDATEPATH - Update SPM path/working directory. +% [Q] = myspm_updatepath(P) +% [SPM] = myspm_updatepath(spmfilename) +% [Q] = myspm_updatepath(P,nwd) +% [SPM] = myspm_updatepath(spmfilename,nwd,save?) +% +% Example +% >> myspm_updatepath +% +% See also: spm_vol + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2009 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2009-07-22 Creation +% +% ----------------------------- Script History --------------------------------- + +spmfilename=[]; +imgfilename=[]; +persistent pwd +persistent nwd +Q=P; +if nargin>1 + nwd=varargin{2}; + if ~exist(nwd, 'dir') + warning('myspm_updatepath:NotADirectory','Updated directory doesn''t exist: %s',nwd); + end +end +if nargin<3 + dosave=NaN; +else + dosave=varargin{3}; +end + +if iscell(P) + Q=cell(size(P)); + for i=1:numel(P) + Q{i}=myspm_updatepath(P{i},varargin{:}); + return + end +end + +if isstruct(P) + SPM=P; + clear P; + swd = SPM.swd; +elseif ischar(P) + try + % test if it's an SPM file + clear SPM + load(P) + swd = SPM.swd; + spmfilename = P; + clear P; + catch ME + [swd,imgfilename,imgfileext] = fileparts(P); + end +else + error('Wrong input P must be an image/SPM filename or a SPM struct.') +end +if isempty(pwd) || ~strmatch(pwd,swd,'exact') + if ~isempty(spmfilename) + pwd=fileparts(spmfilename); + end + nwd=uigetdir(pwd,sprintf('Pick a new SPM folder instead of:\n%s',swd)); + if isequal(nwd,0) + return + end +end +fprintf(1,'Updating former path: %s\ninto: %s\n', pwd,nwd); +pwd=swd; +if exist('SPM', 'var') + SPM.swd=nwd; + if dosave && ~isempty(spmfilename) + save(spmfilename, '-APPEND', 'SPM'); + end + Q=SPM; +end +if ~isempty(imgfilename) + Q=fullfile(nwd,[imgfilename,imgfileext]); +end +return diff --git a/spm/nii_for_spm2/@file_array/Contents.m b/spm/nii_for_spm2/@file_array/Contents.m new file mode 100644 index 0000000..b8c7a8d --- /dev/null +++ b/spm/nii_for_spm2/@file_array/Contents.m @@ -0,0 +1,51 @@ +% +% File Array Object +% +% file_array - create a file_array +% horzcat - horizontal concatenation +% vertcat - vertical concatenation +% size - size of array +% length - length of longest dimension +% subsref - subscripted reference +% end - last index in an indexing expression +% resize - resize (but only of simple file_array structures) +% +% other operations are unlikely to work. +% +% Example usage. +% +% % Create a file array object by mapping test_le.img +% % to a 256x256x100 array, of datatype float32, stored +% % in a little-endian way starting at byte 0. +% fa0 = file_array('test_le.img',[256 256 100], 'FLOAT32-LE',0) +% +% % Creating an object from test_be.img, but skipping +% % the first plane of data. Data stored as big-endian +% fa1 = file_array('test_be.img',[256 256 99], 'FLOAT32-BE',4*256*256) +% +% % Reshape procedure +% fa2 = reshape(fa1,[128 2 256 99]) +% +% % Concatenation +% fa3 = [[fa0 fa0]; [fa0 fa0]] +% fa4 = cat(3,fa0,fa1) +% +% % Note that reshape will not work on the above +% % concatenated objects +% +% % Accessing values from the objects +% img = fa1(:,:,40); +% pixval = fa4(50,50,:); +% small = fa1(1:2:end,1:2:end,40); +% +% % Determining dimensions +% size(fa4) +% size(fa2) +% length(fa0) +% _______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% +% $Id: Contents.m 1601 2008-05-12 14:11:02Z guillaume $ + + diff --git a/spm/nii_for_spm2/@file_array/cat.m b/spm/nii_for_spm2/@file_array/cat.m new file mode 100755 index 0000000..0698818 --- /dev/null +++ b/spm/nii_for_spm2/@file_array/cat.m @@ -0,0 +1,40 @@ +function o = cat(dr,varargin) +% Concatenate file_array objects. The result is a non-simple object +% that can no longer be reshaped. +% _______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% +% $Id: cat.m 253 2005-10-13 15:31:34Z guillaume $ + + +if dr>32 || dr<0, error('Unknown command option.'); end; +dr = max(round(dr),1); +d = ones(nargin-1,16); +tmp = {}; +dpos = 0; +for i=1:nargin-1, + vi = varargin{i}; + if strcmp(class(vi),'file_array') + sz = size(vi); + d(i,1:length(sz)) = sz; + svi = struct(vi); + svi = svi(:); + for j=1:length(svi(:)), + if length(svi(j).pos)1, + fprintf(' %s object: ', class(obj)); + sz = size(obj); + if length(sz)>4, + fprintf('%d-D\n',length(sz)); + else + for i=1:(length(sz)-1), + fprintf('%d-by-',sz(i)); + end; + fprintf('%d\n',sz(end)); + end; +else + display(mystruct(obj)) +end; +return; +%======================================================================= + +%======================================================================= +function t = mystruct(obj) +fn = fieldnames(obj); +for i=1:length(fn) + t.(fn{i}) = subsref(obj,struct('type','.','subs',fn{i})); +end; +return; +%======================================================================= diff --git a/spm/nii_for_spm2/@file_array/display.m b/spm/nii_for_spm2/@file_array/display.m new file mode 100755 index 0000000..b8136de --- /dev/null +++ b/spm/nii_for_spm2/@file_array/display.m @@ -0,0 +1,14 @@ +function display(obj) +% Display a file_array object +% _______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% +% $Id: display.m 253 2005-10-13 15:31:34Z guillaume $ + + +disp(' '); +disp([inputname(1),' = ']) +disp(' '); +disp(obj) +disp(' ') diff --git a/spm/nii_for_spm2/@file_array/double.m b/spm/nii_for_spm2/@file_array/double.m new file mode 100755 index 0000000..93ccb69 --- /dev/null +++ b/spm/nii_for_spm2/@file_array/double.m @@ -0,0 +1,12 @@ +function out = double(fa) +% Convert to double precision +% FORMAT double(fa) +% fa - a file_array +% _______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% +% $Id: double.m 253 2005-10-13 15:31:34Z guillaume $ + +out = double(numeric(fa)); + diff --git a/spm/nii_for_spm2/@file_array/end.m b/spm/nii_for_spm2/@file_array/end.m new file mode 100755 index 0000000..a7cff36 --- /dev/null +++ b/spm/nii_for_spm2/@file_array/end.m @@ -0,0 +1,17 @@ +function en = end(a,k,n) +% Overloaded end function for file_array objects. +% _______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% +% $Id: end.m 253 2005-10-13 15:31:34Z guillaume $ + +dim = size(a); +if k>length(dim) + en = 1; +else + if n=1, a = fname(a,varargin{1}); end; +if nargin>=2, a = dim(a,varargin{2}); end; +if nargin>=3, a = dtype(a,varargin{3}); end; +if nargin>=4, a = offset(a,varargin{4}); end; +if nargin>=5, a = scl_slope(a,varargin{5}); end; +if nargin>=6, a = scl_inter(a,varargin{6}); end; + +a.pos = ones(size(a.dim)); +a = class(a,'file_array'); diff --git a/spm/nii_for_spm2/@file_array/horzcat.m b/spm/nii_for_spm2/@file_array/horzcat.m new file mode 100755 index 0000000..dcada1c --- /dev/null +++ b/spm/nii_for_spm2/@file_array/horzcat.m @@ -0,0 +1,11 @@ +function o = horzcat(varargin) +% Horizontal concatenation of file_array objects +% _______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% +% $Id: horzcat.m 253 2005-10-13 15:31:34Z guillaume $ + +o = cat(2,varargin{:}); +return; + diff --git a/spm/nii_for_spm2/@file_array/length.m b/spm/nii_for_spm2/@file_array/length.m new file mode 100755 index 0000000..acb7689 --- /dev/null +++ b/spm/nii_for_spm2/@file_array/length.m @@ -0,0 +1,11 @@ +function l = length(x) +% Overloaded length function for file_array objects +% _______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% +% $Id: length.m 253 2005-10-13 15:31:34Z guillaume $ + + +l = max(size(x)); + diff --git a/spm/nii_for_spm2/@file_array/loadobj.m b/spm/nii_for_spm2/@file_array/loadobj.m new file mode 100644 index 0000000..06de737 --- /dev/null +++ b/spm/nii_for_spm2/@file_array/loadobj.m @@ -0,0 +1,14 @@ +function b = loadobj(a) +% loadobj for file_array class +% _______________________________________________________________________ +% Copyright (C) 2008 Wellcome Trust Centre for Neuroimaging + +% +% $Id: loadobj.m 1746 2008-05-28 17:43:42Z guillaume $ + +if isa(a,'file_array') + b = a; +else + a = rmfield(a, 'permission'); + b = file_array(a); +end diff --git a/spm/nii_for_spm2/@file_array/mystruct.m b/spm/nii_for_spm2/@file_array/mystruct.m new file mode 100755 index 0000000..3dd255d --- /dev/null +++ b/spm/nii_for_spm2/@file_array/mystruct.m @@ -0,0 +1,16 @@ +function t = mystruct(obj) +% _______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% +% $Id: mystruct.m 253 2005-10-13 15:31:34Z guillaume $ + + +if numel(obj)~=1, + error('Too many elements to convert'); +end; +fn = fieldnames(obj); +for i=1:length(fn) + t.(fn{i}) = subsref(obj,struct('type','.','subs',fn{i})); +end; +return; diff --git a/spm/nii_for_spm2/@file_array/ndims.m b/spm/nii_for_spm2/@file_array/ndims.m new file mode 100755 index 0000000..43ecabe --- /dev/null +++ b/spm/nii_for_spm2/@file_array/ndims.m @@ -0,0 +1,12 @@ +function out = ndims(fa) +% Number of dimensions +%_______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% +% $Id: ndims.m 253 2005-10-13 15:31:34Z guillaume $ + + +out = size(fa); +out = length(out); + diff --git a/spm/nii_for_spm2/@file_array/numel.m b/spm/nii_for_spm2/@file_array/numel.m new file mode 100755 index 0000000..f561385 --- /dev/null +++ b/spm/nii_for_spm2/@file_array/numel.m @@ -0,0 +1,14 @@ +function t = numel(obj) +% Number of simple file arrays involved. +% _______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% +% $Id: numel.m 253 2005-10-13 15:31:34Z guillaume $ + + +% Should be this, but it causes problems when accessing +% obj as a structure. +%t = prod(size(obj)); + +t = numel(struct(obj)); diff --git a/spm/nii_for_spm2/@file_array/numeric.m b/spm/nii_for_spm2/@file_array/numeric.m new file mode 100755 index 0000000..5b4acdf --- /dev/null +++ b/spm/nii_for_spm2/@file_array/numeric.m @@ -0,0 +1,14 @@ +function out = numeric(fa) +% Convert to numeric form +% FORMAT numeric(fa) +% fa - a file_array +% _______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% +% $Id: numeric.m 253 2005-10-13 15:31:34Z guillaume $ + + +[vo{1:ndims(fa)}] = deal(':'); +out = subsref(fa,struct('type','()','subs',{vo})); + diff --git a/spm/nii_for_spm2/@file_array/permute.m b/spm/nii_for_spm2/@file_array/permute.m new file mode 100755 index 0000000..a6c79b6 --- /dev/null +++ b/spm/nii_for_spm2/@file_array/permute.m @@ -0,0 +1,10 @@ +function varargout = permute(varargin) +% Can not be permuted. +% _______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% +% $Id: permute.m 253 2005-10-13 15:31:34Z guillaume $ + + +error('file_array objects can not be permuted.'); diff --git a/spm/nii_for_spm2/@file_array/private/datatypes.m b/spm/nii_for_spm2/@file_array/private/datatypes.m new file mode 100755 index 0000000..f6e6ca5 --- /dev/null +++ b/spm/nii_for_spm2/@file_array/private/datatypes.m @@ -0,0 +1,56 @@ +function dt = datatypes +% Datatype +% _______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% +% $Id: datatypes.m 253 2005-10-13 15:31:34Z guillaume $ + + +persistent dtype +if isempty(dtype), + t = true; + f = false; + table = {... + 0 ,'UNKNOWN' ,'uint8' ,@uint8 ,1,1 ,t,t,f + 1 ,'BINARY' ,'uint1' ,@logical,1,1/8,t,t,f + 256 ,'INT8' ,'int8' ,@int8 ,1,1 ,t,f,t + 2 ,'UINT8' ,'uint8' ,@uint8 ,1,1 ,t,t,t + 4 ,'INT16' ,'int16' ,@int16 ,1,2 ,t,f,t + 512 ,'UINT16' ,'uint16' ,@uint16 ,1,2 ,t,t,t + 8 ,'INT32' ,'int32' ,@int32 ,1,4 ,t,f,t + 768 ,'UINT32' ,'uint32' ,@uint32 ,1,4 ,t,t,t + 1024,'INT64' ,'int64' ,@int64 ,1,8 ,t,f,f + 1280,'UINT64' ,'uint64' ,@uint64 ,1,8 ,t,t,f + 16 ,'FLOAT32' ,'float32' ,@single ,1,4 ,f,f,t + 64 ,'FLOAT64' ,'double' ,@double ,1,8 ,f,f,t + 1536,'FLOAT128' ,'float128',@crash ,1,16 ,f,f,f + 32 ,'COMPLEX64' ,'float32' ,@single ,2,4 ,f,f,f + 1792,'COMPLEX128','double' ,@double ,2,8 ,f,f,f + 2048,'COMPLEX256','float128',@crash ,2,16 ,f,f,f + 128 ,'RGB24' ,'uint8' ,@uint8 ,3,1 ,t,t,f}; + dtype = struct(... + 'code' ,table(:,1),... + 'label' ,table(:,2),... + 'prec' ,table(:,3),... + 'conv' ,table(:,4),... + 'nelem' ,table(:,5),... + 'size' ,table(:,6),... + 'isint' ,table(:,7),... + 'unsigned' ,table(:,8),... + 'min',-Inf,'max',Inf',... + 'supported',table(:,9)); + for i=1:length(dtype), + if dtype(i).isint + if dtype(i).unsigned + dtype(i).min = 0; + dtype(i).max = 2^(8*dtype(i).size)-1; + else + dtype(i).min = -2^(8*dtype(i).size-1); + dtype(i).max = 2^(8*dtype(i).size-1)-1; + end; + end; + end; +end; + +dt = dtype; diff --git a/spm/nii_for_spm2/@file_array/private/dim.m b/spm/nii_for_spm2/@file_array/private/dim.m new file mode 100755 index 0000000..cf6a6c1 --- /dev/null +++ b/spm/nii_for_spm2/@file_array/private/dim.m @@ -0,0 +1,37 @@ +function varargout = dim(varargin) +% Format +% For getting the value +% dat = dim(obj) +% +% For setting the value +% obj = dim(obj,dat) +% _______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% +% $Id: dim.m 253 2005-10-13 15:31:34Z guillaume $ + + +if nargin==2, + varargout{1} = asgn(varargin{:}); +elseif nargin==1, + varargout{1} = ref(varargin{:}); +else + error('Wrong number of arguments.'); +end; +return; + +function dat = ref(obj) +dat = obj.dim; +return; + +function obj = asgn(obj,dat) +if isnumeric(dat) && all(dat>=0) && all(rem(dat,1)==0), + dat = [double(dat(:)') 1 1]; + lim = max([2 find(dat~=1)]); + dat = dat(1:lim); + obj.dim = dat; +else + error('"dim" must be a vector of positive integers.'); +end; +return; diff --git a/spm/nii_for_spm2/@file_array/private/dtype.m b/spm/nii_for_spm2/@file_array/private/dtype.m new file mode 100755 index 0000000..5ca51a4 --- /dev/null +++ b/spm/nii_for_spm2/@file_array/private/dtype.m @@ -0,0 +1,86 @@ +function varargout = dtype(varargin) +% Format +% For getting the value +% dat = dtype(obj) +% +% For setting the value +% obj = dtype(obj,dat) +% _______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% +% $Id: dtype.m 466 2006-03-01 15:14:22Z john $ + + + +if nargin==2, + varargout{1} = asgn(varargin{:}); +elseif nargin==1, + varargout{1} = ref(varargin{:}); +else + error('Wring number of arguments.'); +end; +return; + +function t = ref(obj) +d = datatypes; +mch = find(cat(1,d.code)==obj.dtype); +if isempty(mch), t = 'UNKNOWN'; else t = d(mch).label; end; +if obj.be, t = [t '-BE']; else t = [t '-LE']; end; +return; + +function obj = asgn(obj,dat) +d = datatypes; +if isnumeric(dat) + if numel(dat)>=1, + mch = find(cat(1,d.code)==dat(1)); + if isempty(mch) || mch==0, + fprintf('Invalid datatype (%d).', dat(1)); + disp('First part of datatype should be of one of the following'); + disp(sortrows([num2str(cat(1,d.code)) ... + repmat(' ',numel(d),2) strvcat(d.label)])); + %error(['Invalid datatype (' num2str(dat(1)) ').']); + return; + end; + obj.dtype = double(dat(1)); + end; + if numel(dat)>=2, + obj.be = double(dat(2)~=0); + end; + if numel(dat)>2, + error('Too many elements in numeric datatype.'); + end; +elseif ischar(dat), + dat1 = lower(dat); + sep = find(dat1=='-' | dat1=='/'); + sep = sep(sep~=1); + if ~isempty(sep), + c1 = dat1(1:(sep(1)-1)); + c2 = dat1((sep(1)+1):end); + else + c1 = dat1; + c2 = ''; + end; + mch = find(strcmpi(c1,lower({d.label}))); + if isempty(mch), + disp('First part of datatype should be of one of the following'); + disp(sortrows([num2str(cat(1,d.code)) ... + repmat(' ',numel(d),2) strvcat(d.label)])); + %error(['Invalid datatype (' c1 ').']); + return; + else + obj.dtype = double(d(mch(1)).code); + end; + if any(c2=='b'), + if any(c2=='l'), + error('Cannot be both big and little endian.'); + end; + obj.be = 1; + elseif any(c2=='l'), + obj.be = 0; + end; +else + error('Invalid datatype.'); +end; +return; + diff --git a/spm/nii_for_spm2/@file_array/private/fname.m b/spm/nii_for_spm2/@file_array/private/fname.m new file mode 100755 index 0000000..065a02c --- /dev/null +++ b/spm/nii_for_spm2/@file_array/private/fname.m @@ -0,0 +1,35 @@ +function varargout = fname(varargin) +% Format +% For getting the value +% dat = fname(obj) +% +% For setting the value +% obj = fname(obj,dat) +% _______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% +% $Id: fname.m 253 2005-10-13 15:31:34Z guillaume $ + + + +if nargin==2, + varargout{1} = asgn(varargin{:}); +elseif nargin==1, + varargout{1} = ref(varargin{:}); +else + error('Wrong number of arguments.'); +end; +return; + +function dat = ref(obj) +dat = obj.fname; +return; + +function obj = asgn(obj,dat) +if ischar(dat) + obj.fname = deblank(dat(:)'); +else + error('"fname" must be a character string.'); +end; +return; diff --git a/spm/nii_for_spm2/@file_array/private/mystruct.m b/spm/nii_for_spm2/@file_array/private/mystruct.m new file mode 100755 index 0000000..614ed7d --- /dev/null +++ b/spm/nii_for_spm2/@file_array/private/mystruct.m @@ -0,0 +1,16 @@ +function t = mystruct(obj) +% _______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% +% $Id: mystruct.m 488 2006-03-30 11:59:54Z john $ + + +if numel(obj)~=1, + error('Too many elements to convert'); +end; +fn = fieldnames(obj); +for i=1:length(fn) + t.(fn{i}) = subsref(obj,struct('type','.','subs',fn{i})); +end; +return; diff --git a/spm/nii_for_spm2/@file_array/private/offset.m b/spm/nii_for_spm2/@file_array/private/offset.m new file mode 100755 index 0000000..128b3ee --- /dev/null +++ b/spm/nii_for_spm2/@file_array/private/offset.m @@ -0,0 +1,35 @@ +function varargout = offset(varargin) +% Format +% For getting the value +% dat = offset(obj) +% +% For setting the value +% obj = offset(obj,dat) +% _______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% +% $Id: offset.m 253 2005-10-13 15:31:34Z guillaume $ + + + +if nargin==2, + varargout{1} = asgn(varargin{:}); +elseif nargin==1, + varargout{1} = ref(varargin{:}); +else + error('Wrong number of arguments.'); +end; +return; + +function dat = ref(obj) +dat = obj.offset; +return; + +function obj = asgn(obj,dat) +if isnumeric(dat) && numel(dat)==1 && dat>=0 && rem(dat,1)==0, + obj.offset = double(dat); +else + error('"offset" must be a positive integer.'); +end; +return; diff --git a/spm/nii_for_spm2/@file_array/private/resize_scales.m b/spm/nii_for_spm2/@file_array/private/resize_scales.m new file mode 100755 index 0000000..0149d23 --- /dev/null +++ b/spm/nii_for_spm2/@file_array/private/resize_scales.m @@ -0,0 +1,24 @@ +function s1 = resize_scales(s0,dim,args) +% Resize scalefactors +% _________________________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% +% $Id: resize_scales.m 315 2005-11-28 16:48:59Z john $ + +dim = [dim ones(1,max(numel(args)-numel(dim),0))]; +args1 = cell(1,numel(args)); +for i=1:numel(args), + if max(args{i})>dim(i) || min(args{i})<1, + error('Index exceeds matrix dimensions (1).'); + end; + + if size(s0,i)==1, + args1{i} = ones(size(args{i})); + else + args1{i} = args{i}; + end; +end; + +s1 = s0(args1{:}); + diff --git a/spm/nii_for_spm2/@file_array/private/scl_inter.m b/spm/nii_for_spm2/@file_array/private/scl_inter.m new file mode 100755 index 0000000..9ad7b9a --- /dev/null +++ b/spm/nii_for_spm2/@file_array/private/scl_inter.m @@ -0,0 +1,36 @@ +function varargout = scl_inter(varargin) +% Format +% For getting the value +% dat = scl_inter(obj) +% +% For setting the value +% obj = scl_inter(obj,dat) +% _______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% +% $Id: scl_inter.m 253 2005-10-13 15:31:34Z guillaume $ + + + +if nargin==2, + varargout{1} = asgn(varargin{:}); +elseif nargin==1, + varargout{1} = ref(varargin{:}); +else + error('Wrong number of arguments.'); +end; +return; + +function dat = ref(obj) +dat = obj.scl_inter; +return; + +function obj = asgn(obj,dat) +if isnumeric(dat), % && numel(dat)<=1, + obj.scl_inter = double(dat); +else + error('"scl_inter" must be numeric.'); +end; +return; + diff --git a/spm/nii_for_spm2/@file_array/private/scl_slope.m b/spm/nii_for_spm2/@file_array/private/scl_slope.m new file mode 100755 index 0000000..c68a71c --- /dev/null +++ b/spm/nii_for_spm2/@file_array/private/scl_slope.m @@ -0,0 +1,35 @@ +function varargout = scl_slope(varargin) +% Format +% For getting the value +% dat = scl_slope(obj) +% +% For setting the value +% obj = scl_slope(obj,dat) +% _______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% +% $Id: scl_slope.m 253 2005-10-13 15:31:34Z guillaume $ + + + +if nargin==2, + varargout{1} = asgn(varargin{:}); +elseif nargin==1, + varargout{1} = ref(varargin{:}); +else + error('Wrong number of arguments.'); +end; +return; + +function dat = ref(obj) +dat = obj.scl_slope; +return; + +function obj = asgn(obj,dat) +if isnumeric(dat), % && numel(dat)<=1, + obj.scl_slope = double(dat); +else + error('"scl_slope" must be numeric.'); +end; +return; diff --git a/spm/nii_for_spm2/@file_array/reshape.m b/spm/nii_for_spm2/@file_array/reshape.m new file mode 100755 index 0000000..0a5d1d2 --- /dev/null +++ b/spm/nii_for_spm2/@file_array/reshape.m @@ -0,0 +1,21 @@ +function a = reshape(b,varargin) +% Overloaded reshape function for file_array objects +% _______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% +% $Id: reshape.m 253 2005-10-13 15:31:34Z guillaume $ + + +if length(struct(b))~=1, error('Can only reshape simple file_array objects.'); end; + +args = []; +for i=1:length(varargin), + args = [args varargin{i}(:)']; +end; +if prod(args)~=prod(b.dim), + error('To RESHAPE the number of elements must not change.'); +end; +a = b; +a.dim = args; + diff --git a/spm/nii_for_spm2/@file_array/size.m b/spm/nii_for_spm2/@file_array/size.m new file mode 100644 index 0000000..c116250 --- /dev/null +++ b/spm/nii_for_spm2/@file_array/size.m @@ -0,0 +1,43 @@ +function d = size(a,varargin) +% overloaded size function for file_array objects. +% _______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% +% $Id: size.m 1037 2007-12-21 13:02:53Z john $ + + +sa = struct(a); +nd = 0; +for i=1:numel(sa), + nd = max(nd,numel(sa(i).dim)); +end +nd = nd+1; + +dim = ones(length(sa),nd); +pos = ones(length(sa),nd); + +for i=1:length(sa) + sz = sa(i).dim; + dim(i,1:length(sz)) = sz; + ps = sa(i).pos; + pos(i,1:length(ps)) = ps; +end + +tmp = pos==1; +d = zeros(1,nd); +for i=1:nd, + ind = all(tmp(:,[1:(i-1) (i+1):nd]),2); + d(i) = sum(dim(ind,i)); +end; +lim = max(max(find(d~=1)),2); +d = d(1:lim); + +if nargin>1, + if varargin{1}<=length(d), + d = d(varargin{1}); + else + d = 1; + end; +end; + diff --git a/spm/nii_for_spm2/@file_array/subsasgn.m b/spm/nii_for_spm2/@file_array/subsasgn.m new file mode 100644 index 0000000..89cd6e3 --- /dev/null +++ b/spm/nii_for_spm2/@file_array/subsasgn.m @@ -0,0 +1,146 @@ +function obj = subsasgn(obj,subs,dat) +% Overloaded subsasgn function for file_array objects. +% _______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% +% $Id: subsasgn.m 945 2007-10-14 18:08:46Z volkmar $ + + +if isempty(subs) + return; +end; +if ~strcmp(subs(1).type,'()'), + if strcmp(subs(1).type,'.'), + %error('Attempt to reference field of non-structure array.'); + if numel(struct(obj))~=1, + error('Can only change the fields of simple file_array objects.'); + end; + switch(subs(1).subs) + case 'fname', obj = asgn(obj,@fname, subs(2:end),dat); %fname(obj,dat); + case 'dtype', obj = asgn(obj,@dtype, subs(2:end),dat); %dtype(obj,dat); + case 'offset', obj = asgn(obj,@offset, subs(2:end),dat); %offset(obj,dat); + case 'dim', obj = asgn(obj,@dim, subs(2:end),dat); %obj = dim(obj,dat); + case 'scl_slope', obj = asgn(obj,@scl_slope,subs(2:end),dat); %scl_slope(obj,dat); + case 'scl_inter', obj = asgn(obj,@scl_inter,subs(2:end),dat); %scl_inter(obj,dat); + otherwise, error(['Reference to non-existent field "' subs.subs '".']); + end; + return; + end; + if strcmp(subs(1).type,'{}'), error('Cell contents reference from a non-cell array object.'); end; +end; + +if numel(subs)~=1, error('Expression too complicated');end; +dm = size(obj); +sobj = struct(obj); + +if length(subs.subs) < length(dm), + l = length(subs.subs); + dm = [dm(1:(l-1)) prod(dm(l:end))]; + if numel(sobj) ~= 1, + error('Can only reshape simple file_array objects.'); + end; + if numel(sobj.scl_slope)>1 || numel(sobj.scl_inter)>1, + error('Can not reshape file_array objects with multiple slopes and intercepts.'); + end; +end; + +dm = [dm ones(1,16)]; +do = ones(1,16); +args = {}; +for i=1:length(subs.subs), + if ischar(subs.subs{i}), + if ~strcmp(subs.subs{i},':'), error('This shouldn''t happen....'); end; + args{i} = int32(1:dm(i)); + else + args{i} = int32(subs.subs{i}); + end; + do(i) = length(args{i}); +end; +if length(sobj)==1 + sobj.dim = dm; + if numel(dat)~=1, + subfun(sobj,double(dat),args{:}); + else + dat1 = double(dat) + zeros(do); + subfun(sobj,dat1,args{:}); + end; +else + for j=1:length(sobj), + ps = [sobj(j).pos ones(1,length(args))]; + dm = [sobj(j).dim ones(1,length(args))]; + siz = ones(1,16); + for i=1:length(args), + msk = args{i}>=ps(i) & args{i}<(ps(i)+dm(i)); + args2{i} = find(msk); + args3{i} = int32(double(args{i}(msk))-ps(i)+1); + siz(i) = numel(args2{i}); + end; + if numel(dat)~=1, + dat1 = double(subsref(dat,struct('type','()','subs',{args2}))); + else + dat1 = double(dat) + zeros(siz); + end; + subfun(sobj(j),dat1,args3{:}); + end +end +return + +function sobj = subfun(sobj,dat,varargin) +va = varargin; + +dt = datatypes; +ind = find(cat(1,dt.code)==sobj.dtype); +if isempty(ind), error('Unknown datatype'); end; +if dt(ind).isint, dat(~isfinite(dat)) = 0; end; + +if ~isempty(sobj.scl_inter), + inter = sobj.scl_inter; + if numel(inter)>1, + inter = resize_scales(inter,sobj.dim,varargin); + end; + dat = double(dat) - inter; +end; + +if ~isempty(sobj.scl_slope), + slope = sobj.scl_slope; + if numel(slope)>1, + slope = resize_scales(slope,sobj.dim,varargin); + dat = double(dat)./slope; + else + dat = double(dat)/slope; + end; +end; + +if dt(ind).isint, dat = round(dat); end; + +% Avoid warning messages in R14 SP3 +wrn = warning; +warning('off'); +dat = feval(dt(ind).conv,dat); +warning(wrn); + +nelem = dt(ind).nelem; +if nelem==1, + mat2file(sobj,dat,va{:}); +elseif nelem==2, + sobj1 = sobj; + sobj1.dim = [2 sobj.dim]; + sobj1.dtype = dt(find(strcmp(dt(ind).prec,{dt.prec}) & (cat(2,dt.nelem)==1))).code; + dat = reshape(dat,[1 size(dat)]); + dat = [real(dat) ; imag(dat)]; + mat2file(sobj1,dat,int32([1 2]),va{:}); +else + error('Inappropriate number of elements per voxel.'); +end; +return + +function obj = asgn(obj,fun,subs,dat) +if ~isempty(subs), + tmp = feval(fun,obj); + tmp = subsasgn(tmp,subs,dat); + obj = feval(fun,obj,tmp); +else + obj = feval(fun,obj,dat); +end; + diff --git a/spm/nii_for_spm2/@file_array/subsref.m b/spm/nii_for_spm2/@file_array/subsref.m new file mode 100755 index 0000000..813aa58 --- /dev/null +++ b/spm/nii_for_spm2/@file_array/subsref.m @@ -0,0 +1,124 @@ +function varargout=subsref(obj,subs) +% SUBSREF Subscripted reference +% An overloaded function... +% _________________________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% +% $Id: subsref.m 315 2005-11-28 16:48:59Z john $ + + +if isempty(subs) + return; +end; + +if ~strcmp(subs(1).type,'()'), + if strcmp(subs(1).type,'.'), + %error('Attempt to reference field of non-structure array.'); + %if numel(struct(obj))~=1, + % error('Can only access the fields of simple file_array objects.'); + %end; + + varargout = access_fields(obj,subs); + return; + end; + if strcmp(subs.type,'{}'), error('Cell contents reference from a non-cell array object.'); end; +end; + +if numel(subs)~=1, error('Expression too complicated');end; + +dim = [size(obj) ones(1,16)]; +nd = max(find(dim>1))-1; +sobj = struct(obj); + +if length(subs.subs) < nd, + l = length(subs.subs); + dim = [dim(1:(l-1)) prod(dim(l:end))]; + if numel(sobj) ~= 1, + error('Can only reshape simple file_array objects.'); + else + if numel(sobj.scl_slope)>1 || numel(sobj.scl_inter)>1, + error('Can not reshape file_array objects with multiple slopes and intercepts.'); + end; + sobj.dim = dim; + end; +end; + +do = ones(16,1); +args = {}; +for i=1:length(subs.subs), + if ischar(subs.subs{i}), + if ~strcmp(subs.subs{i},':'), error('This shouldn''t happen....'); end; + args{i} = int32(1:dim(i)); + else + args{i} = int32(subs.subs{i}); + end; + do(i) = length(args{i}); +end; + +if length(sobj)==1, + t = subfun(sobj,args{:}); +else + t = zeros(do'); + for j=1:length(sobj), + ps = [sobj(j).pos ones(1,length(args))]; + dm = [sobj(j).dim ones(1,length(args))]; + for i=1:length(args), + msk = find(args{i}>=ps(i) & args{i}<(ps(i)+dm(i))); + args2{i} = msk; + args3{i} = int32(double(args{i}(msk))-ps(i)+1); + end; + + t = subsasgn(t,struct('type','()','subs',{args2}),subfun(sobj(j),args3{:})); + end +end +varargout = {t}; +return; + +function t = subfun(sobj,varargin) +%sobj.dim = [sobj.dim ones(1,16)]; +t = file2mat(sobj,varargin{:}); +if ~isempty(sobj.scl_slope) || ~isempty(sobj.scl_inter) + slope = 1; + inter = 0; + if ~isempty(sobj.scl_slope), slope = sobj.scl_slope; end; + if ~isempty(sobj.scl_inter), inter = sobj.scl_inter; end; + if numel(slope)>1, + slope = resize_scales(slope,sobj.dim,varargin); + t = double(t).*slope; + else + t = double(t)*slope; + end; + if numel(inter)>1, + inter = resize_scales(inter,sobj.dim,varargin); + end; + t = t + inter; +end; +return; + +function c = access_fields(obj,subs) +%error('Attempt to reference field of non-structure array.'); +%if numel(struct(obj))~=1, +% error('Can only access the fields of simple file_array objects.'); +%end; +c = {}; +sobj = struct(obj); +for i=1:numel(sobj), + %obj = class(sobj(i),'file_array'); + obj = sobj(i); + switch(subs(1).subs) + case 'fname', t = fname(obj); + case 'dtype', t = dtype(obj); + case 'offset', t = offset(obj); + case 'dim', t = dim(obj); + case 'scl_slope', t = scl_slope(obj); + case 'scl_inter', t = scl_inter(obj); + otherwise, error(['Reference to non-existent field "' subs(1).type '".']); + end; + if numel(subs)>1, + t = subsref(t,subs(2:end)); + end; + c{i} = t; +end; +return; + diff --git a/spm/nii_for_spm2/@file_array/transpose.m b/spm/nii_for_spm2/@file_array/transpose.m new file mode 100755 index 0000000..0f6aaa1 --- /dev/null +++ b/spm/nii_for_spm2/@file_array/transpose.m @@ -0,0 +1,10 @@ +function varargout = transpose(varargin) +% Transposing is not allowed. +% _______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% +% $Id: transpose.m 253 2005-10-13 15:31:34Z guillaume $ + + +error('file_array objects can not be transposed.'); diff --git a/spm/nii_for_spm2/@file_array/vertcat.m b/spm/nii_for_spm2/@file_array/vertcat.m new file mode 100755 index 0000000..4e67694 --- /dev/null +++ b/spm/nii_for_spm2/@file_array/vertcat.m @@ -0,0 +1,11 @@ +function o = vertcat(varargin) +% Vertical concatenation of file_array objects. +% _______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% +% $Id: vertcat.m 253 2005-10-13 15:31:34Z guillaume $ + + +o = cat(1,varargin{:}); +return; diff --git a/spm/nii_for_spm2/@nifti/Contents.m b/spm/nii_for_spm2/@nifti/Contents.m new file mode 100755 index 0000000..d531861 --- /dev/null +++ b/spm/nii_for_spm2/@nifti/Contents.m @@ -0,0 +1,82 @@ +% +% NIFTI Object +% +% create - Create a NIFTI-1 file +% disp - Disp a NIFTI-1 object +% display - Display a NIFTI-1 object +% fieldnames - Fieldnames of a NIFTI-1 object +% nifti - Create a NIFTI-1 object +% subsasgn - Subscript assignment +% subsref - Subscript referencing +% +% other operations are unlikely to work. +% +% Example usage. +% +% % Example of creating a simulated .nii file. +% dat = file_array; +% dat.fname = 'junk.nii'; +% dat.dim = [64 64 32]; +% dat.dtype = 'FLOAT64-BE'; +% dat.offset = ceil(348/8)*8; +% +% % alternatively: +% % dat = file_array( 'junk.nii',dim,dtype,off,scale,inter) +% +% disp(dat) +% +% % Create an empty NIFTI structure +% N = nifti; +% +% fieldnames(N) % Dump fieldnames +% +% % Creating all the NIFTI header stuff +% N.dat = dat; +% N.mat = [2 0 0 -110 ; 0 2 0 -110; 0 0 -2 92; 0 0 0 1]; +% N.mat_intent = 'xxx'; % dump possibilities +% N.mat_intent = 'Scanner'; +% N.mat0 = N.mat; +% N.mat0_intent = 'Aligned'; +% +% N.diminfo.slice = 3; +% N.diminfo.phase = 2; +% N.diminfo.frequency = 2; +% N.diminfo.slice_time.code='xxx'; % dump possibilities +% N.diminfo.slice_time.code = 'sequential_increasing'; +% N.diminfo.slice_time.start = 1; +% N.diminfo.slice_time.end = 32; +% N.diminfo.slice_time.duration = 3/32; +% +% N.intent.code='xxx' ; % dump possibilities +% N.intent.code='FTEST'; % or N.intent.code=4; +% N.intent.param = [4 8]; +% +% N.timing.toffset = 28800; +% N.timing.tspace=3; +% N.descrip = 'This is a NIFTI-1 file'; +% N.aux_file='aux-file-name.txt'; +% N.cal = [0 1]; +% +% create(N); % Writes hdr info +% +% dat(:,:,:)=0; % Write out the data as all zeros +% +% [i,j,k] = ndgrid(1:64,1:64,1:32); +% dat(find((i-32).^2+(j-32).^2+(k*2-32).^2 < 30^2))=1; % Write some ones in the file +% dat(find((i-32).^2+(j-32).^2+(k*2-32).^2 < 15^2))=2; +% +% +% % displaying a slice +% imagesc(dat(:,:,12));colorbar +% +% % get a handle to 'junk.nii'; +% M=nifti('junk.nii'); +% +% imagesc(M.dat(:,:,12)); +% +% _______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% +% $Id$ + diff --git a/spm/nii_for_spm2/@nifti/create.m b/spm/nii_for_spm2/@nifti/create.m new file mode 100755 index 0000000..6f486d8 --- /dev/null +++ b/spm/nii_for_spm2/@nifti/create.m @@ -0,0 +1,74 @@ +function create(obj,wrt) +% Create a NIFTI-1 file +% FORMAT create(obj) +% This writes out the header information for the nifti object +% +% create(obj,wrt) +% This also writes out an empty image volume if wrt==1 +% _______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% +% $Id: create.m 433 2006-02-09 11:45:02Z john $ + +for i=1:numel(obj) + create_each(obj(i)); +end; + +function create_each(obj) +if ~isa(obj.dat,'file_array'), + error('Data must be a file-array'); +end; +fname = obj.dat.fname; +if isempty(fname), + error('No filename to write to.'); +end; +dt = obj.dat.dtype; +ok = write_hdr_raw(fname,obj.hdr,dt(end-1)=='B'); +if ~ok, + error(['Unable to write header for "' fname '".']); +end; + +write_extras(fname,obj.extras); + +if nargin>2 && any(wrt==1), + % Create an empty image file if necessary + d = findindict(obj.hdr.datatype, 'dtype'); + dim = double(obj.hdr.dim(2:end)); + dim((double(obj.hdr.dim(1))+1):end) = 1; + nbytes = ceil(d.size*d.nelem*prod(dim(1:2)))*prod(dim(3:end))+double(obj.hdr.vox_offset); + [pth,nam,ext] = fileparts(obj.dat.fname); + + if any(strcmp(deblank(obj.hdr.magic),{'n+1','nx1'})), + ext = '.nii'; + else + ext = '.img'; + end; + iname = fullfile(pth,[nam ext]); + fp = fopen(iname,'a+'); + if fp==-1, + error(['Unable to create image for "' fname '".']); + end; + + fseek(fp,0,'eof'); + pos = ftell(fp); + if pos0) + if nbytes4, + fprintf('%d-D\n',length(sz)); +else + for i=1:(length(sz)-1), + fprintf('%d-by-',sz(i)); + end; + fprintf('%d\n',sz(end)); +end; +if prod(sz)==1, + display(structn(obj)) +end; +return; diff --git a/spm/nii_for_spm2/@nifti/display.m b/spm/nii_for_spm2/@nifti/display.m new file mode 100755 index 0000000..78fef0d --- /dev/null +++ b/spm/nii_for_spm2/@nifti/display.m @@ -0,0 +1,14 @@ +function display(obj) +% Display a NIFTI-1 object +% _______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% +% $Id: display.m 253 2005-10-13 15:31:34Z guillaume $ + + +disp(' '); +disp([inputname(1),' = ']) +disp(' '); +disp(obj) +disp(' ') diff --git a/spm/nii_for_spm2/@nifti/fieldnames.m b/spm/nii_for_spm2/@nifti/fieldnames.m new file mode 100755 index 0000000..b49f427 --- /dev/null +++ b/spm/nii_for_spm2/@nifti/fieldnames.m @@ -0,0 +1,26 @@ +function t = fieldnames(obj) +% Fieldnames of a NIFTI-1 object +% _______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% +% $Id: fieldnames.m 253 2005-10-13 15:31:34Z guillaume $ + + +if isfield(obj.hdr,'magic') + t = {... + 'dat' + 'mat' + 'mat_intent' + 'mat0' + 'mat0_intent' + 'intent' + 'diminfo' + 'timing' + 'descrip' + 'cal' + 'aux_file' + }; +else + error('This should not happen.'); +end; diff --git a/spm/nii_for_spm2/@nifti/nifti.m b/spm/nii_for_spm2/@nifti/nifti.m new file mode 100755 index 0000000..037140e --- /dev/null +++ b/spm/nii_for_spm2/@nifti/nifti.m @@ -0,0 +1,91 @@ +function h = nifti(varargin) +% Create a NIFTI-1 object +% _______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% +% $Id: nifti.m 253 2005-10-13 15:31:34Z guillaume $ + + +switch nargin +case 0, + org = niftistruc; + hdr = []; + for i=1:length(org), + hdr.(org(i).label) = feval(org(i).dtype.conv,org(i).def); + end; + h = struct('hdr',hdr,'dat',[],'extras',struct); + h = class(h,'nifti'); + +case 1 + if ischar(varargin{1}) + if size(varargin{1},1)>1, + h = nifti(cellstr(varargin{1})); + return; + end; + fname = deblank(varargin{1}); + vol = read_hdr(fname); + extras = read_extras(fname); + + if ~isfield(vol.hdr,'magic'), + vol.hdr = mayo2nifti1(vol.hdr); + + % For SPM99 compatibility + if isfield(extras,'M') && ~isfield(extras,'mat'), + extras.mat = extras.M; + if spm_flip_analyze_images, + extras.mat = diag([-1 1 1 1])*extras.mat; + end; + end; + + % Over-ride sform if a .mat file exists + if isfield(extras,'mat') && size(extras.mat,3)>=1, + mat = extras.mat(:,:,1); + mat1 = mat*[eye(4,3) [1 1 1 1]']; + vol.hdr.srow_x = mat1(1,:); + vol.hdr.srow_y = mat1(2,:); + vol.hdr.srow_z = mat1(3,:); + vol.hdr.sform_code = 2; + vol.hdr.qform_code = 2; + vol.hdr = encode_qform0(mat,vol.hdr); + end; + end; + + if isfield(extras,'M'), extras = rmfield(extras,'M'); end; + if isfield(extras,'mat') && size(extras.mat,3)<=1, + extras = rmfield(extras,'mat'); + end; + + dim = double(vol.hdr.dim); + dim = dim(2:(dim(1)+1)); + dt = double(vol.hdr.datatype); + offs = double(vol.hdr.vox_offset); + + if ~vol.hdr.scl_slope && ~vol.hdr.scl_inter, + vol.hdr.scl_slope = 1; + end; + slope = double(vol.hdr.scl_slope); + inter = double(vol.hdr.scl_inter); + + dat = file_array(vol.iname,dim,[dt,vol.be],offs,slope,inter); + h = struct('hdr',vol.hdr,'dat',dat,'extras',extras); + h = class(h,'nifti'); + + elseif isstruct(varargin{1}) + h = class(varargin{1},'nifti'); + + elseif iscell(varargin{1}) + fnames = varargin{1}; + h(numel(fnames)) = struct('hdr',[],'dat',[],'extras',struct); + h = class(h,'nifti'); + for i=1:numel(fnames), + h(i) = nifti(fnames{i}); + end; + + else + error('Dont know what to do yet.'); + end; +otherwise + error('Dont know what to do yet'); +end; +return; diff --git a/spm/nii_for_spm2/@nifti/private/M2Q.m b/spm/nii_for_spm2/@nifti/private/M2Q.m new file mode 100755 index 0000000..b87eee8 --- /dev/null +++ b/spm/nii_for_spm2/@nifti/private/M2Q.m @@ -0,0 +1,33 @@ +function Q = M2Q(M) +% Convert from rotation matrix to quaternion form +% See: http://skal.planet-d.net/demo/matrixfaq.htm +% _______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% +% $Id: M2Q.m 253 2005-10-13 15:31:34Z guillaume $ + + +d = diag(M(1:3,1:3)); +t = sum(d) + 1; +if t>0.5, + s = sqrt(t)*2; + Q = [(M(3,2)-M(2,3))/s (M(1,3)-M(3,1))/s (M(2,1)-M(1,2))/s 0.25*s]'; +else + t = find(d==max(d)); + t = t(1); + switch(t), + case 1, + s = 2*sqrt(1 + M(1,1) - M(2,2) - M(3,3)); + Q = [0.25*s (M(1,2)+M(2,1))/s (M(3,1)+M(1,3))/s (M(3,2)-M(2,3))/s]'; + case 2, + s = 2*sqrt(1 + M(2,2) - M(1,1) - M(3,3)); + Q = [(M(1,2)+M(2,1))/s 0.25*s (M(2,3)+M(3,2))/s (M(1,3)-M(3,1))/s ]'; + case 3, + s = 2*sqrt(1 + M(3,3) - M(1,1) - M(2,2)); + Q = [(M(3,1)+M(1,3))/s (M(2,3)+M(3,2))/s 0.25*s (M(2,1)-M(1,2))/s]'; + end; +end; +if Q(4)<0, Q = -Q; end; % w must be +ve +return; + diff --git a/spm/nii_for_spm2/@nifti/private/Q2M.m b/spm/nii_for_spm2/@nifti/private/Q2M.m new file mode 100755 index 0000000..1f684ce --- /dev/null +++ b/spm/nii_for_spm2/@nifti/private/Q2M.m @@ -0,0 +1,31 @@ +function M = Q2M(Q) +% Generate a rotation matrix from a quaternion xi+yj+zk+w, +% where Q = [x y z], and w = 1-x^2-y^2-z^2. +% See: http://skal.planet-d.net/demo/matrixfaq.htm +% _______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% +% $Id: Q2M.m 253 2005-10-13 15:31:34Z guillaume $ + + +Q = Q(1:3); % Assume rigid body +w = sqrt(1 - sum(Q.^2)); +x = Q(1); y = Q(2); z = Q(3); +if w<1e-7, + w = 1/sqrt(x*x+y*y+z*z); + x = x*w; + y = y*w; + z = z*w; + w = 0; +end; +xx = x*x; yy = y*y; zz = z*z; ww = w*w; +xy = x*y; xz = x*z; xw = x*w; +yz = y*z; yw = y*w; zw = z*w; +M = [... +(xx-yy-zz+ww) 2*(xy-zw) 2*(xz+yw) 0 + 2*(xy+zw) (-xx+yy-zz+ww) 2*(yz-xw) 0 + 2*(xz-yw) 2*(yz+xw) (-xx-yy+zz+ww) 0 + 0 0 0 1]; +return; + diff --git a/spm/nii_for_spm2/@nifti/private/decode_qform0.m b/spm/nii_for_spm2/@nifti/private/decode_qform0.m new file mode 100755 index 0000000..6bd8ee2 --- /dev/null +++ b/spm/nii_for_spm2/@nifti/private/decode_qform0.m @@ -0,0 +1,61 @@ +function M = decode_qform0(hdr) +% Decode qform info from NIFTI-1 headers. +% _______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% +% $Id: decode_qform0.m 253 2005-10-13 15:31:34Z guillaume $ + + +dim = double(hdr.dim); +pixdim = double(hdr.pixdim); +if ~isfield(hdr,'magic') || hdr.qform_code <= 0, + flp = spm_flip_analyze_images; + %disp('------------------------------------------------------'); + %disp('The images are in a form whereby it is not possible to'); + %disp('tell the left and right sides of the brain apart.'); + %if flp, + % disp('They are assumed to be stored right-handed.'); + %else + % disp('They are assumed to be stored left-handed.'); + %end; + %disp('------------------------------------------------------'); + + %R = eye(4); + n = min(dim(1),3); + vox = [pixdim(2:(n+1)) ones(1,3-n)]; + + if ~isfield(hdr,'origin') || ~any(hdr.origin(1:3)), + origin = (dim(2:4)+1)/2; + else + origin = double(hdr.origin(1:3)); + end; + off = -vox.*origin; + M = [vox(1) 0 0 off(1) ; 0 vox(2) 0 off(2) ; 0 0 vox(3) off(3) ; 0 0 0 1]; + + % Stuff for default orientations + if flp, M = diag([-1 1 1 1])*M; end; +else + + % Rotations from quaternions + R = Q2M(double([hdr.quatern_b hdr.quatern_c hdr.quatern_d])); + + % Translations + T = [eye(4,3) double([hdr.qoffset_x hdr.qoffset_y hdr.qoffset_z 1]')]; + + % Zooms. Note that flips are derived from the first + % element of pixdim, which is normally unused. + n = min(dim(1),3); + Z = [pixdim(2:(n+1)) ones(1,4-n)]; + Z(Z<0) = 1; + if pixdim(1)<0, Z(3) = -Z(3); end; + Z = diag(Z); + + M = T*R*Z; + + % Convert from first voxel at [1,1,1] + % to first voxel at [0,0,0] + M = M * [eye(4,3) [-1 -1 -1 1]']; +end; +return; + diff --git a/spm/nii_for_spm2/@nifti/private/empty_hdr.m b/spm/nii_for_spm2/@nifti/private/empty_hdr.m new file mode 100755 index 0000000..0f80b60 --- /dev/null +++ b/spm/nii_for_spm2/@nifti/private/empty_hdr.m @@ -0,0 +1,15 @@ +function hdr = empty_hdr +% Create an empty NIFTI-1 header +% FORMAT hdr = empty_hdr +% _______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% +% $Id: empty_hdr.m 253 2005-10-13 15:31:34Z guillaume $ + + +org = niftistruc; +for i=1:length(org), + hdr.(org(i).label) = org(i).def; +end; + diff --git a/spm/nii_for_spm2/@nifti/private/encode_qform0.m b/spm/nii_for_spm2/@nifti/private/encode_qform0.m new file mode 100755 index 0000000..df296db --- /dev/null +++ b/spm/nii_for_spm2/@nifti/private/encode_qform0.m @@ -0,0 +1,45 @@ +function hdr = encode_qform0(M,hdr) +% Encode an affine transform into qform +% _______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% +% $Id: encode_qform0.m 253 2005-10-13 15:31:34Z guillaume $ + + +% Convert from first voxel at [1,1,1] to first voxel at [0,0,0] +M = M * [eye(4,3) [1 1 1 1]']; + +% Translations +hdr.qoffset_x = M(1,4); +hdr.qoffset_y = M(2,4); +hdr.qoffset_z = M(3,4); + +% Rotations and zooms +R = M(1:3,1:3); +vx = sqrt(sum(M(1:3,1:3).^2)); +vx(vx==0) = 1; +R = R * diag(1./vx); + +% Ensure that R is O(3) +[U,S,V] = svd(R); +R = U*V'; +if any(abs(diag(S)-1)>1e-3), warning('QFORM0 representation has been rounded.'); end; + +% Ensure that R is SO(3) +if det(R)>0 + hdr.pixdim(1:4) = [ 1 vx]; +else + R = R*diag([1 1 -1]); + hdr.pixdim(1:4) = [-1 vx]; +end; + +% Convert to quaternions +Q = M2Q(R); +hdr.quatern_b = Q(1); +hdr.quatern_c = Q(2); +hdr.quatern_d = Q(3); + +if hdr.qform_code == 0, hdr.qform_code = 2; end; +return; + diff --git a/spm/nii_for_spm2/@nifti/private/findindict.m b/spm/nii_for_spm2/@nifti/private/findindict.m new file mode 100755 index 0000000..5ad7fc2 --- /dev/null +++ b/spm/nii_for_spm2/@nifti/private/findindict.m @@ -0,0 +1,37 @@ +function entry = findindict(c,dcode) +% Look up an entry in the dictionary +% _______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% +% $Id: findindict.m 253 2005-10-13 15:31:34Z guillaume $ + + +entry = []; +d = getdict; +d = d.(dcode); +if ischar(c) + for i=1:length(d), + if strcmpi(d(i).label,c), + entry = d(i); + break; + end; + end; +elseif isnumeric(c) && numel(c)==1 + for i=1:length(d), + if d(i).code==c, + entry = d(i); + break; + end; + end; +else + error(['Inappropriate code for ' dcode '.']); +end; +if isempty(entry) + fprintf('\nThis is not an option. Try one of these:\n'); + for i=1:length(d) + fprintf('%5d) %s\n', d(i).code, d(i).label); + end; + %fprintf('\nNO CHANGES MADE\n'); +end; + diff --git a/spm/nii_for_spm2/@nifti/private/getdict.m b/spm/nii_for_spm2/@nifti/private/getdict.m new file mode 100755 index 0000000..58f7d67 --- /dev/null +++ b/spm/nii_for_spm2/@nifti/private/getdict.m @@ -0,0 +1,153 @@ +function d = getdict +% Dictionary of NIFTI stuff +% _______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% +% $Id: getdict.m 253 2005-10-13 15:31:34Z guillaume $ + + +persistent dict; +if ~isempty(dict), + d = dict; + return; +end; + +% Datatype +t = true; +f = false; +table = {... + 0 ,'UNKNOWN' ,'uint8' ,@uint8 ,1,1 ,t,t,f + 1 ,'BINARY' ,'uint1' ,@logical,1,1/8,t,t,f + 256 ,'INT8' ,'int8' ,@int8 ,1,1 ,t,f,t + 2 ,'UINT8' ,'uint8' ,@uint8 ,1,1 ,t,t,t + 4 ,'INT16' ,'int16' ,@int16 ,1,2 ,t,f,t + 512 ,'UINT16' ,'uint16' ,@uint16 ,1,2 ,t,t,t + 8 ,'INT32' ,'int32' ,@int32 ,1,4 ,t,f,t + 768 ,'UINT32' ,'uint32' ,@uint32 ,1,4 ,t,t,t + 1024,'INT64' ,'int64' ,@int64 ,1,8 ,t,f,f + 1280,'UINT64' ,'uint64' ,@uint64 ,1,8 ,t,t,f + 16 ,'FLOAT32' ,'float32' ,@single ,1,4 ,f,f,t + 64 ,'FLOAT64' ,'double' ,@double ,1,8 ,f,f,t + 1536,'FLOAT128' ,'float128',@crash ,1,16 ,f,f,f + 32 ,'COMPLEX64' ,'float32' ,@single ,2,4 ,f,f,f + 1792,'COMPLEX128','double' ,@double ,2,8 ,f,f,f + 2048,'COMPLEX256','float128',@crash ,2,16 ,f,f,f + 128 ,'RGB24' ,'uint8' ,@uint8 ,3,1 ,t,t,f}; + +dtype = struct(... + 'code' ,table(:,1),... + 'label' ,table(:,2),... + 'prec' ,table(:,3),... + 'conv' ,table(:,4),... + 'nelem' ,table(:,5),... + 'size' ,table(:,6),... + 'isint' ,table(:,7),... + 'unsigned' ,table(:,8),... + 'min',-Inf,'max',Inf',... + 'supported',table(:,9)); +for i=1:length(dtype), + if dtype(i).isint + if dtype(i).unsigned + dtype(i).min = 0; + dtype(i).max = 2^(8*dtype(i).size)-1; + else + dtype(i).min = -2^(8*dtype(i).size-1); + dtype(i).max = 2^(8*dtype(i).size-1)-1; + end; + end; +end; +% Intent +table = {... + 0 ,'NONE' ,'None',{} + 2 ,'CORREL' ,'Correlation statistic',{'DOF'} + 3 ,'TTEST' ,'T-statistic',{'DOF'} + 4 ,'FTEST' ,'F-statistic',{'numerator DOF','denominator DOF'} + 5 ,'ZSCORE' ,'Z-score',{} + 6 ,'CHISQ' ,'Chi-squared distribution',{'DOF'} + 7 ,'BETA' ,'Beta distribution',{'a','b'} + 8 ,'BINOM' ,'Binomial distribution',... + {'number of trials','probability per trial'} + 9 ,'GAMMA' ,'Gamma distribution',{'shape','scale'} + 10 ,'POISSON' ,'Poisson distribution',{'mean'} + 11 ,'NORMAL' ,'Normal distribution',{'mean','standard deviation'} + 12 ,'FTEST_NONC' ,'F-statistic noncentral',... + {'numerator DOF','denominator DOF','numerator noncentrality parameter'} + 13 ,'CHISQ_NONC' ,'Chi-squared noncentral',{'DOF','noncentrality parameter'} + 14 ,'LOGISTIC' ,'Logistic distribution',{'location','scale'} + 15 ,'LAPLACE' ,'Laplace distribution',{'location','scale'} + 16 ,'UNIFORM' ,'Uniform distribition',{'lower end','upper end'} + 17 ,'TTEST_NONC' ,'T-statistic noncentral',{'DOF','noncentrality parameter'} + 18 ,'WEIBULL' ,'Weibull distribution',{'location','scale','power'} + 19 ,'CHI' ,'Chi distribution',{'DOF'} + 20 ,'INVGAUSS' ,'Inverse Gaussian distribution',{'mu','lambda'} + 21 ,'EXTVAL' ,'Extreme Value distribution',{'location','scale'} + 22 ,'PVAL' ,'P-value',{} + 23 ,'LOGPVAL' ,'Log P-value',{} + 24 ,'LOG10PVAL' ,'Log_10 P-value',{} + 1001,'ESTIMATE' ,'Estimate',{} + 1002,'LABEL' ,'Label index',{} + 1003,'NEURONAMES' ,'NeuroNames index',{} + 1004,'MATRIX' ,'General matrix',{'M','N'} + 1005,'MATRIX_SYM' ,'Symmetric matrix',{} + 1006,'DISPLACEMENT' ,'Displacement vector',{} + 1007,'VECTOR' ,'Vector',{} + 1008,'POINTS' ,'Pointset',{} + 1009,'TRIANGLE' ,'Triangle',{} + 1010,'QUATERNION' ,'Quaternion',{} + 1011,'DIMLESS' ,'Dimensionless',{} +}; +intent = struct('code',table(:,1),'label',table(:,2),... + 'fullname',table(:,3),'param',table(:,4)); + +% Units +table = {... + 0, 1,'UNKNOWN' + 1,1000,'m' + 2, 1,'mm' + 3,1e-3,'um' + 8, 1,'s' + 16,1e-3,'ms' + 24,1e-6,'us' + 32, 1,'Hz' + 40, 1,'ppm' + 48, 1,'rads'}; +units = struct('code',table(:,1),'label',table(:,3),'rescale',table(:,2)); + +% Reference space +% code = {0,1,2,3,4}; +table = {... + 0,'UNKNOWN' + 1,'Scanner Anat' + 2,'Aligned Anat' + 3,'Talairach' + 4,'MNI_152'}; +anat = struct('code',table(:,1),'label',table(:,2)); + +% Slice Ordering +table = {... + 0,'UNKNOWN' + 1,'sequential_increasing' + 2,'sequential_decreasing' + 3,'alternating_increasing' + 4,'alternating_decreasing'}; +sliceorder = struct('code',table(:,1),'label',table(:,2)); + +% Q/S Form Interpretation +table = {... + 0,'UNKNOWN' + 1,'Scanner' + 2,'Aligned' + 3,'Talairach' + 4,'MNI152'}; +xform = struct('code',table(:,1),'label',table(:,2)); + +dict = struct('dtype',dtype,'intent',intent,'units',units,... + 'space',anat,'sliceorder',sliceorder,'xform',xform); + +d = dict; +return; + +function varargout = crash(varargin) +error('There is a NIFTI-1 data format problem (an invalid datatype).'); + diff --git a/spm/nii_for_spm2/@nifti/private/mayo2nifti1.m b/spm/nii_for_spm2/@nifti/private/mayo2nifti1.m new file mode 100755 index 0000000..8aac923 --- /dev/null +++ b/spm/nii_for_spm2/@nifti/private/mayo2nifti1.m @@ -0,0 +1,67 @@ +function hdr = mayo2nifti1(ohdr,mat) +% Convert from an ANALYZE to a NIFTI-1 header +% _______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% +% $Id: mayo2nifti1.m 594 2006-08-16 16:22:34Z john $ + + +if isfield(ohdr,'magic'), + hdr = ohdr; + return; +end; +hdr = empty_hdr; +hdr.dim = ohdr.dim; +hdr.datatype = ohdr.datatype; +hdr.bitpix = ohdr.bitpix; +hdr.pixdim = ohdr.pixdim; +hdr.vox_offset = ohdr.vox_offset; +hdr.scl_slope = ohdr.roi_scale; +hdr.scl_inter = ohdr.funused1; +hdr.descrip = ohdr.descrip; +hdr.aux_file = ohdr.aux_file; +hdr.glmax = ohdr.glmax; +hdr.glmin = ohdr.glmin; +hdr.cal_max = ohdr.cal_max; +hdr.cal_min = ohdr.cal_min; +hdr.magic = 'ni1'; + +switch hdr.datatype, +case 130, hdr.datatype = 256; % int8 +case 132, hdr.datatype = 512; % uint16 +case 136, hdr.datatype = 768; % uint32 +end; + +if nargin<2, + % No mat, so create the equivalent from the hdr... + if any(ohdr.origin(1:3)), origin = double(ohdr.origin(1:3)); + else origin = (double(ohdr.dim(2:4))+1)/2; end; + vox = double(ohdr.pixdim(2:4)); + if vox(1)<0, + % Assume FSL orientation + flp = 0; + else + % Assume SPM or proper Analyze + flp = spm_flip_analyze_images; + end; + if all(vox == 0), vox = [1 1 1]; end; + off = -vox.*origin; + mat = [vox(1) 0 0 off(1) ; 0 vox(2) 0 off(2) ; 0 0 vox(3) off(3) ; 0 0 0 1]; + if flp, + %disp(['Assuming that image is stored left-handed']); + mat = diag([-1 1 1 1])*mat; + else + %disp(['Assuming that image is stored right-handed']); + end; +end; + +hdr = encode_qform0(mat,hdr); +mat = mat*[eye(4,3) [1 1 1 1]']; +hdr.srow_x = mat(1,:); +hdr.srow_y = mat(2,:); +hdr.srow_z = mat(3,:); +hdr.qform_code = 2; +hdr.sform_code = 2; +return; + diff --git a/spm/nii_for_spm2/@nifti/private/mayostruc.m b/spm/nii_for_spm2/@nifti/private/mayostruc.m new file mode 100755 index 0000000..ec818af --- /dev/null +++ b/spm/nii_for_spm2/@nifti/private/mayostruc.m @@ -0,0 +1,80 @@ +function o = mayostruc +% Create a data structure describing Analyze headers +% _______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% +% $Id: mayostruc.m 253 2005-10-13 15:31:34Z guillaume $ + + +persistent org; +if ~isempty(org), + o = org; + return; +end; +t = struct('conv',{ @char, @int16, @int32, @single },... + 'prec',{'uint8','int16','int32','single'},... + 'size',{ 1, 2, 4, 4}); +c = t(1); +s = t(2); +i = t(3); +f = t(4); +table = {... + i, 1,'sizeof_hdr',348 + c,10,'data_type',[] + c,18,'db_name',[] + i, 1,'extents',[] + s, 1,'session_error',[] + c, 1,'regular','r' + c, 1,'hkey_un0',[] + s, 8,'dim',[3 1 1 1 1 1 1 1 1] + c, 4,'vox_units',[] + c, 8,'cal_units',[] + s, 1,'unused1',[] + s, 1,'datatype',[] + s, 1,'bitpix',[] + s, 1,'dim_un0',[] + f, 8,'pixdim',[] + f, 1,'vox_offset',0 + f, 1,'roi_scale',1 + f, 1,'funused1',0 + f, 1,'funused2',[] + f, 1,'cal_max',[] + f, 1,'cal_min',[] + i, 1,'compressed',[] + i, 1,'verified',[] + i, 1,'glmax',[] + i, 1,'glmin',[] + c,80,'descrip','Analyze Image' + c,24,'aux_file','' + c, 1,'orient',[] +% c,10,'originator',[] + s, 5,'origin',[] % SPM version + c,10,'generated',[] + c,10,'scannum',[] + c,10,'patient_id',[] + c,10,'exp_date',[] + c,10,'exp_time',[] + c, 3,'hist_un0',[] + i, 1,'views',[] + i, 1,'vols_added',[] + i, 1,'start_field',[] + i, 1,'field_skip',[] + i, 1,'omax',[] + i, 1,'omin',[] + i, 1,'smax',[] + i, 1,'smin',[]}; +org = struct('label',table(:,3),'dtype',table(:,1),'len',table(:,2),... + 'offset',0,'def',table(:,4)); +os = 0; +for j=1:length(org) + os = os + org(j).dtype.size*ceil(os/org(j).dtype.size); + fun = org(j).dtype.conv; + def = [org(j).def zeros(1,org(j).len-length(org(j).def))]; + org(j).def = feval(fun,def); + org(j).offset = os; + os = os + org(j).len*org(j).dtype.size; +end; +o = org; +return; + diff --git a/spm/nii_for_spm2/@nifti/private/nifti1.h b/spm/nii_for_spm2/@nifti/private/nifti1.h new file mode 100755 index 0000000..98fed70 --- /dev/null +++ b/spm/nii_for_spm2/@nifti/private/nifti1.h @@ -0,0 +1,1222 @@ +#ifndef _NIFTI_HEADER_ +#define _NIFTI_HEADER_ + +/***************************************************************************** + ** This file defines the "NIFTI-1" header format. ** + ** It is derived from 2 meetings at the NIH (31 Mar 2003 and ** + ** 02 Sep 2003) of the Data Format Working Group (DFWG), ** + ** chartered by the NIfTI (Neuroimaging Informatics Technology ** + ** Initiative) at the National Institutes of Health (NIH). ** + **--------------------------------------------------------------** + ** Neither the National Institutes of Health (NIH), the DFWG, ** + ** nor any of the members or employees of these institutions ** + ** imply any warranty of usefulness of this material for any ** + ** purpose, and do not assume any liability for damages, ** + ** incidental or otherwise, caused by any use of this document. ** + ** If these conditions are not acceptable, do not use this! ** + **--------------------------------------------------------------** + ** Author: Robert W Cox (NIMH, Bethesda) ** + ** Advisors: John Ashburner (FIL, London), ** + ** Stephen Smith (FMRIB, Oxford), ** + ** Mark Jenkinson (FMRIB, Oxford) ** +******************************************************************************/ + +/*---------------------------------------------------------------------------*/ +/* Note that the ANALYZE 7.5 file header (dbh.h) is + (c) Copyright 1986-1995 + Biomedical Imaging Resource + Mayo Foundation + Incorporation of components of dbh.h are by permission of the + Mayo Foundation. + + Changes from the ANALYZE 7.5 file header in this file are released to the + public domain, including the functional comments and any amusing asides. +-----------------------------------------------------------------------------*/ + +/*---------------------------------------------------------------------------*/ +/*! INTRODUCTION TO NIFTI-1: + ------------------------ + The twin (and somewhat conflicting) goals of this modified ANALYZE 7.5 + format are: + (a) To add information to the header that will be useful for functional + neuroimaging data analysis and display. These additions include: + - More basic data types. + - Two affine transformations to specify voxel coordinates. + - "Intent" codes and parameters to describe the meaning of the data. + - Affine scaling of the stored data values to their "true" values. + - Optional storage of the header and image data in one file (.nii). + (b) To maintain compatibility with non-NIFTI-aware ANALYZE 7.5 compatible + software (i.e., such a program should be able to do something useful + with a NIFTI-1 dataset -- at least, with one stored in a traditional + .img/.hdr file pair). + + Most of the unused fields in the ANALYZE 7.5 header have been taken, + and some of the lesser-used fields have been co-opted for other purposes. + Notably, most of the data_history substructure has been co-opted for + other purposes, since the ANALYZE 7.5 format describes this substructure + as "not required". + + NIFTI-1 FLAG (MAGIC STRINGS): + ---------------------------- + To flag such a struct as being conformant to the NIFTI-1 spec, the last 4 + bytes of the header must be either the C String "ni1" or "n+1"; + in hexadecimal, the 4 bytes + 6E 69 31 00 or 6E 2B 31 00 + (in any future version of this format, the '1' will be upgraded to '2', + etc.). Normally, such a "magic number" or flag goes at the start of the + file, but trying to avoid clobbering widely-used ANALYZE 7.5 fields led to + putting this marker last. However, recall that "the last shall be first" + (Matthew 20:16). + + If a NIFTI-aware program reads a header file that is NOT marked with a + NIFTI magic string, then it should treat the header as an ANALYZE 7.5 + structure. + + NIFTI-1 FILE STORAGE: + -------------------- + "ni1" means that the image data is stored in the ".img" file corresponding + to the header file (starting at file offset 0). + + "n+1" means that the image data is stored in the same file as the header + information. We recommend that the combined header+data filename suffix + be ".nii". When the dataset is stored in one file, the first byte of image + data is stored at byte location (int)vox_offset in this combined file. + + GRACE UNDER FIRE: + ---------------- + Most NIFTI-aware programs will only be able to handle a subset of the full + range of datasets possible with this format. All NIFTI-aware programs + should take care to check if an input dataset conforms to the program's + needs and expectations (e.g., check datatype, intent_code, etc.). If the + input dataset can't be handled by the program, the program should fail + gracefully (e.g., print a useful warning; not crash). + + SAMPLE CODES: + ------------ + The associated files nifti1_io.h and nifti1_io.c provide a sample + implementation in C of a set of functions to read, write, and manipulate + NIFTI-1 files. The file nifti1_test.c is a sample program that uses + the nifti1_io.c functions. +-----------------------------------------------------------------------------*/ + +/*---------------------------------------------------------------------------*/ +/* HEADER STRUCT DECLARATION: + ------------------------- + In the comments below for each field, only NIFTI-1 specific requirements + or changes from the ANALYZE 7.5 format are described. For convenience, + the 348 byte header is described as a single struct, rather than as the + ANALYZE 7.5 group of 3 substructs. + + Further comments about the interpretation of various elements of this + header are after the data type definition itself. Fields that are + marked as ++UNUSED++ have no particular interpretation in this standard. + (Also see the UNUSED FIELDS comment section, far below.) + + The presumption below is that the various C types have particular sizes: + sizeof(int) = sizeof(float) = 4 ; sizeof(short) = 2 +-----------------------------------------------------------------------------*/ + +/*=================*/ +#ifdef __cplusplus +extern "C" { +#endif +/*=================*/ + /*************************/ /************************/ +struct nifti_1_header { /* NIFTI-1 usage */ /* ANALYZE 7.5 field(s) */ + /*************************/ /************************/ + + /*--- was header_key substruct ---*/ + int sizeof_hdr; /*!< MUST be 348 */ /* int sizeof_hdr; */ + char data_type[10]; /*!< ++UNUSED++ */ /* char data_type[10]; */ + char db_name[18]; /*!< ++UNUSED++ */ /* char db_name[18]; */ + int extents; /*!< ++UNUSED++ */ /* int extents; */ + short session_error; /*!< ++UNUSED++ */ /* short session_error; */ + char regular; /*!< ++UNUSED++ */ /* char regular; */ + char dim_info; /*!< MRI slice ordering. */ /* char hkey_un0; */ + + /*--- was image_dimension substruct ---*/ + short dim[8]; /*!< Data array dimensions.*/ /* short dim[8]; */ + float intent_p1 ; /*!< 1st intent parameter. */ /* short unused8; */ + /* short unused9; */ + float intent_p2 ; /*!< 2nd intent parameter. */ /* short unused10; */ + /* short unused11; */ + float intent_p3 ; /*!< 3rd intent parameter. */ /* short unused12; */ + /* short unused13; */ + short intent_code ; /*!< NIFTI_INTENT_* code. */ /* short unused14; */ + short datatype; /*!< Defines data type! */ /* short datatype; */ + short bitpix; /*!< Number bits/voxel. */ /* short bitpix; */ + short slice_start; /*!< First slice index. */ /* short dim_un0; */ + float pixdim[8]; /*!< Grid spacings. */ /* float pixdim[8]; */ + float vox_offset; /*!< Offset into .nii file */ /* float vox_offset; */ + float scl_slope ; /*!< Data scaling: slope. */ /* float funused1; */ + float scl_inter ; /*!< Data scaling: offset. */ /* float funused2; */ + short slice_end; /*!< Last slice index. */ /* float funused3; */ + char slice_code ; /*!< Slice timing order. */ + char xyzt_units ; /*!< Units of pixdim[1..4] */ + float cal_max; /*!< Max display intensity */ /* float cal_max; */ + float cal_min; /*!< Min display intensity */ /* float cal_min; */ + float slice_duration;/*!< Time for 1 slice. */ /* float compressed; */ + float toffset; /*!< Time axis shift. */ /* float verified; */ + int glmax; /*!< ++UNUSED++ */ /* int glmax; */ + int glmin; /*!< ++UNUSED++ */ /* int glmin; */ + + /*--- was data_history substruct ---*/ + char descrip[80]; /*!< any text you like. */ /* char descrip[80]; */ + char aux_file[24]; /*!< auxiliary filename. */ /* char aux_file[24]; */ + + short qform_code ; /*!< NIFTI_XFORM_* code. */ /*-- all ANALYZE 7.5 ---*/ + short sform_code ; /*!< NIFTI_XFORM_* code. */ /* fields below here */ + /* are replaced */ + float quatern_b ; /*!< Quaternion b param. */ + float quatern_c ; /*!< Quaternion c param. */ + float quatern_d ; /*!< Quaternion d param. */ + float qoffset_x ; /*!< Quaternion x shift. */ + float qoffset_y ; /*!< Quaternion y shift. */ + float qoffset_z ; /*!< Quaternion z shift. */ + + float srow_x[4] ; /*!< 1st row affine transform. */ + float srow_y[4] ; /*!< 2nd row affine transform. */ + float srow_z[4] ; /*!< 3rd row affine transform. */ + + char intent_name[16];/*!< 'name' or meaning of data. */ + + char magic[4] ; /*!< MUST be "ni1\0" or "n+1\0". */ + +} ; /**** 348 bytes total ****/ + +typedef struct nifti_1_header nifti_1_header ; + +/*---------------------------------------------------------------------------*/ +/* DATA DIMENSIONALITY (as in ANALYZE 7.5): + --------------------------------------- + dim[0] = number of dimensions; + - if dim[0] is outside range 1..7, then the header information + needs to be byte swapped appropriately + - ANALYZE supports dim[0] up to 7, but NIFTI-1 reserves + dimensions 1,2,3 for space (x,y,z), 4 for time (t), and + 5,6,7 for anything else needed. + + dim[i] = length of dimension #i, for i=1..dim[0] (must be positive) + - also see the discussion of intent_code, far below + + pixdim[i] = voxel width along dimension #i, i=1..dim[0] (positive) + - cf. ORIENTATION section below for use of pixdim[0] + - the units of pixdim can be specified with the xyzt_units + field (also described far below). + + Number of bits per voxel value is in bitpix, which MUST correspond with + the datatype field. The total number of bytes in the image data is + dim[1] * ... * dim[dim[0]] * bitpix / 8 + + In NIFTI-1 files, dimensions 1,2,3 are for space, dimension 4 is for time, + and dimension 5 is for storing multiple values at each spatiotemporal + voxel. Some examples: + - A typical whole-brain FMRI experiment's time series: + - dim[0] = 4 + - dim[1] = 64 pixdim[1] = 3.75 xyzt_units = NIFTI_UNITS_MM + - dim[2] = 64 pixdim[2] = 3.75 | NIFTI_UNITS_SEC + - dim[3] = 20 pixdim[3] = 5.0 + - dim[4] = 120 pixdim[4] = 2.0 + - A typical T1-weighted anatomical volume: + - dim[0] = 3 + - dim[1] = 256 pixdim[1] = 1.0 xyzt_units = NIFTI_UNITS_MM + - dim[2] = 256 pixdim[2] = 1.0 + - dim[3] = 128 pixdim[3] = 1.1 + - A single slice EPI time series: + - dim[0] = 4 + - dim[1] = 64 pixdim[1] = 3.75 xyzt_units = NIFTI_UNITS_MM + - dim[2] = 64 pixdim[2] = 3.75 | NIFTI_UNITS_SEC + - dim[3] = 1 pixdim[3] = 5.0 + - dim[4] = 1200 pixdim[4] = 0.2 + - A 3-vector stored at each point in a 3D volume: + - dim[0] = 5 + - dim[1] = 256 pixdim[1] = 1.0 xyzt_units = NIFTI_UNITS_MM + - dim[2] = 256 pixdim[2] = 1.0 + - dim[3] = 128 pixdim[3] = 1.1 + - dim[4] = 1 pixdim[4] = 0.0 + - dim[5] = 3 intent_code = NIFTI_INTENT_VECTOR + - A single time series with a 3x3 matrix at each point: + - dim[0] = 5 + - dim[1] = 1 xyzt_units = NIFTI_UNITS_SEC + - dim[2] = 1 + - dim[3] = 1 + - dim[4] = 1200 pixdim[4] = 0.2 + - dim[5] = 9 intent_code = NIFTI_INTENT_GENMATRIX + - intent_p1 = intent_p2 = 3.0 (indicates matrix dimensions) +-----------------------------------------------------------------------------*/ + +/*---------------------------------------------------------------------------*/ +/* DATA STORAGE: + ------------ + If the magic field is "n+1", then the voxel data is stored in the + same file as the header. In this case, the voxel data starts at offset + (int)vox_offset into the header file. Thus, vox_offset=348.0 means that + the data starts immediately after the NIFTI-1 header. If vox_offset is + greater than 348, the NIFTI-1 format does not say anything about the + contents of the dataset file between the end of the header and the + start of the data. + + FILES: + ----- + If the magic field is "ni1", then the voxel data is stored in the + associated ".img" file, starting at offset 0 (i.e., vox_offset is not + used in this case, and should be set to 0.0). + + When storing NIFTI-1 datasets in pairs of files, it is customary to name + the files in the pattern "name.hdr" and "name.img", as in ANALYZE 7.5. + When storing in a single file ("n+1"), the file name should be in + the form "name.nii" (the ".nft" and ".nif" suffixes are already taken; + cf. http://www.icdatamaster.com/n.html ). + + BYTE ORDERING: + ------------- + The byte order of the data arrays is presumed to be the same as the byte + order of the header (which is determined by examining dim[0]). + + Floating point types are presumed to be stored in IEEE-754 format. +-----------------------------------------------------------------------------*/ + +/*---------------------------------------------------------------------------*/ +/* DATA SCALING: + ------------ + If the scl_slope field is nonzero, then each voxel value in the dataset + should be scaled as + y = scl_slope * x + scl_inter + where x = voxel value stored + y = "true" voxel value + Normally, we would expect this scaling to be used to store "true" floating + values in a smaller integer datatype, but that is not required. That is, + it is legal to use scaling even if the datatype is a float type (crazy, + perhaps, but legal). + - However, the scaling is to be ignored if datatype is DT_RGB24. + - If datatype is a complex type, then the scaling is to be + applied to both the real and imaginary parts. + + The cal_min and cal_max fields (if nonzero) are used for mapping (possibly + scaled) dataset values to display colors: + - Minimum display intensity (black) corresponds to dataset value cal_min. + - Maximum display intensity (white) corresponds to dataset value cal_max. + - Dataset values below cal_min should display as black also, and values + above cal_max as white. + - Colors "black" and "white", of course, may refer to any scalar display + scheme (e.g., a color lookup table specified via aux_file). + - cal_min and cal_max only make sense when applied to scalar-valued + datasets (i.e., dim[0] < 5 or dim[5] = 1). +-----------------------------------------------------------------------------*/ + +/*---------------------------------------------------------------------------*/ +/* TYPE OF DATA (acceptable values for datatype field): + --------------------------------------------------- + Values of datatype smaller than 256 are ANALYZE 7.5 compatible. + Larger values are NIFTI-1 additions. These are all multiples of 256, so + that no bits below position 8 are set in datatype. But there is no need + to use only powers-of-2, as the original ANALYZE 7.5 datatype codes do. + + The additional codes are intended to include a complete list of basic + scalar types, including signed and unsigned integers from 8 to 64 bits, + floats from 32 to 128 bits, and complex (float pairs) from 64 to 256 bits. + + Note that most programs will support only a few of these datatypes! + A NIFTI-1 program should fail gracefully (e.g., print a warning message) + when it encounters a dataset with a type it doesn't like. +-----------------------------------------------------------------------------*/ + +#undef DT_UNKNOWN /* defined in dirent.h on some Unix systems */ + + /*--- the original ANALYZE 7.5 type codes ---*/ +#define DT_NONE 0 +#define DT_UNKNOWN 0 /* what it says, dude */ +#define DT_BINARY 1 /* binary (1 bit/voxel) */ +#define DT_UNSIGNED_CHAR 2 /* unsigned char (8 bits/voxel) */ +#define DT_SIGNED_SHORT 4 /* signed short (16 bits/voxel) */ +#define DT_SIGNED_INT 8 /* signed int (32 bits/voxel) */ +#define DT_FLOAT 16 /* float (32 bits/voxel) */ +#define DT_COMPLEX 32 /* complex (64 bits/voxel) */ +#define DT_DOUBLE 64 /* double (64 bits/voxel) */ +#define DT_RGB 128 /* RGB triple (24 bits/voxel) */ +#define DT_ALL 255 /* not very useful (?) */ + + /*----- another set of names for the same ---*/ +#define DT_UINT8 2 +#define DT_INT16 4 +#define DT_INT32 8 +#define DT_FLOAT32 16 +#define DT_COMPLEX64 32 +#define DT_FLOAT64 64 +#define DT_RGB24 128 + + /*------------------- new codes for NIFTI ---*/ +#define DT_INT8 256 /* signed char (8 bits) */ +#define DT_UINT16 512 /* unsigned short (16 bits) */ +#define DT_UINT32 768 /* unsigned int (32 bits) */ +#define DT_INT64 1024 /* long long (64 bits) */ +#define DT_UINT64 1280 /* unsigned long long (64 bits) */ +#define DT_FLOAT128 1536 /* long double (128 bits) */ +#define DT_COMPLEX128 1792 /* double pair (128 bits) */ +#define DT_COMPLEX256 2048 /* long double pair (256 bits) */ + + /*------- aliases for all the above codes ---*/ + + /*! unsigned char. */ +#define NIFTI_TYPE_UINT8 2 + /*! signed short. */ +#define NIFTI_TYPE_INT16 4 + /*! signed int. */ +#define NIFTI_TYPE_INT32 8 + /*! 32 bit float. */ +#define NIFTI_TYPE_FLOAT32 16 + /*! 64 bit complex = 2 32 bit floats. */ +#define NIFTI_TYPE_COMPLEX64 32 + /*! 64 bit float = double. */ +#define NIFTI_TYPE_FLOAT64 64 + /*! 3 8 bit bytes. */ +#define NIFTI_TYPE_RGB24 128 + /*! signed char. */ +#define NIFTI_TYPE_INT8 256 + /*! unsigned short. */ +#define NIFTI_TYPE_UINT16 512 + /*! unsigned int. */ +#define NIFTI_TYPE_UINT32 768 + /*! signed long long. */ +#define NIFTI_TYPE_INT64 1024 + /*! unsigned long long. */ +#define NIFTI_TYPE_UINT64 1280 + /*! 128 bit float = long double. */ +#define NIFTI_TYPE_FLOAT128 1536 + /*! 128 bit complex = 2 64 bit floats. */ +#define NIFTI_TYPE_COMPLEX128 1792 + /*! 256 bit complex = 2 128 bit floats */ +#define NIFTI_TYPE_COMPLEX256 2048 + + /*-------- sample typedefs for complicated types ---*/ +#if 0 +typedef struct { float r,i; } complex_float ; +typedef struct { double r,i; } complex_double ; +typedef struct { long double r,i; } complex_longdouble ; +typedef struct { unsigned char r,g,b; } rgb_byte ; +#endif + +/*---------------------------------------------------------------------------*/ +/* INTERPRETATION OF VOXEL DATA: + ---------------------------- + The intent_code field can be used to indicate that the voxel data has + some particular meaning. In particular, a large number of codes is + given to indicate that the the voxel data should be interpreted as + being drawn from a given probability distribution. + + VECTOR-VALUED DATASETS: + ---------------------- + The 5th dimension of the dataset, if present (i.e., dim[0]=5 and + dim[5] > 1), contains multiple values (e.g., a vector) to be stored + at each spatiotemporal location. For example, the header values + - dim[0] = 5 + - dim[1] = 64 + - dim[2] = 64 + - dim[3] = 20 + - dim[4] = 1 (indicates no time axis) + - dim[5] = 3 + - datatype = DT_FLOAT + - intent_code = NIFTI_INTENT_VECTOR + mean that this dataset should be interpreted as a 3D volume (64x64x20), + with a 3-vector of floats defined at each point in the 3D grid. + + A program reading a dataset with a 5th dimension may want to reformat + the image data to store each voxels' set of values together in a struct + or array. This programming detail, however, is beyond the scope of the + NIFTI-1 file specification! Uses of dimensions 6 and 7 are also not + specified here. + + STATISTICAL PARAMETRIC DATASETS (i.e., SPMs): + -------------------------------------------- + Values of intent_code from NIFTI_FIRST_STATCODE to NIFTI_LAST_STATCODE + (inclusive) indicate that the numbers in the dataset should be interpreted + as being drawn from a given distribution. Most such distributions have + auxiliary parameters (e.g., NIFTI_INTENT_TTEST has 1 DOF parameter). + + If the dataset DOES NOT have a 5th dimension, then the auxiliary parameters + are the same for each voxel, and are given in header fields intent_p1, + intent_p2, and intent_p3. + + If the dataset DOES have a 5th dimension, then the auxiliary parameters + are different for each voxel. For example, the header values + - dim[0] = 5 + - dim[1] = 128 + - dim[2] = 128 + - dim[3] = 1 (indicates a single slice) + - dim[4] = 1 (indicates no time axis) + - dim[5] = 2 + - datatype = DT_FLOAT + - intent_code = NIFTI_INTENT_TTEST + mean that this is a 2D dataset (128x128) of t-statistics, with the + t-statistic being in the first "plane" of data and the degrees-of-freedom + parameter being in the second "plane" of data. + + If the dataset 5th dimension is used to store the voxel-wise statistical + parameters, then dim[5] must be 1 plus the number of parameters required + by that distribution (e.g., intent_code=NIFTI_INTENT_TTEST implies dim[5] + must be 2, as in the example just above). + + Note: intent_code values 2..10 are compatible with AFNI 1.5x (which is + why there is no code with value=1, which is obsolescent in AFNI). + + OTHER INTENTIONS: + ---------------- + The purpose of the intent_* fields is to help interpret the values + stored in the dataset. Some non-statistical values for intent_code + and conventions are provided for storing other complex data types. + + The intent_name field provides space for a 15 character (plus 0 byte) + 'name' string for the type of data stored. Examples: + - intent_code = NIFTI_INTENT_ESTIMATE; intent_name = "T1"; + could be used to signify that the voxel values are estimates of the + NMR parameter T1. + - intent_code = NIFTI_INTENT_TTEST; intent_name = "House"; + could be used to signify that the voxel values are t-statistics + for the significance of 'activation' response to a House stimulus. + - intent_code = NIFTI_INTENT_DISPVECT; intent_name = "ToMNI152"; + could be used to signify that the voxel values are a displacement + vector that transforms each voxel (x,y,z) location to the + corresponding location in the MNI152 standard brain. + - intent_code = NIFTI_INTENT_SYMMATRIX; intent_name = "DTI"; + could be used to signify that the voxel values comprise a diffusion + tensor image. + + If no data name is implied or needed, intent_name[0] should be set to 0. +-----------------------------------------------------------------------------*/ + + /*! default: no intention is indicated in the header. */ + +#define NIFTI_INTENT_NONE 0 + + /*-------- These codes are for probability distributions ---------------*/ + /* Most distributions have a number of parameters, + below denoted by p1, p2, and p3, and stored in + - intent_p1, intent_p2, intent_p3 if dataset doesn't have 5th dimension + - image data array if dataset does have 5th dimension + + Functions to compute with many of the distributions below can be found + in the CDF library from U Texas. + + Formulas for and discussions of these distributions can be found in the + following books: + + [U] Univariate Discrete Distributions, + NL Johnson, S Kotz, AW Kemp. + + [C1] Continuous Univariate Distributions, vol. 1, + NL Johnson, S Kotz, N Balakrishnan. + + [C2] Continuous Univariate Distributions, vol. 2, + NL Johnson, S Kotz, N Balakrishnan. */ + /*----------------------------------------------------------------------*/ + + /*! [C2, chap 32] Correlation coefficient R (1 param): + p1 = degrees of freedom + R/sqrt(1-R*R) is t-distributed with p1 DOF. */ + +#define NIFTI_INTENT_CORREL 2 + + /*! [C2, chap 28] Student t statistic (1 param): p1 = DOF. */ + +#define NIFTI_INTENT_TTEST 3 + + /*! [C2, chap 27] Fisher F statistic (2 params): + p1 = numerator DOF, p2 = denominator DOF. */ + +#define NIFTI_INTENT_FTEST 4 + + /*! [C1, chap 13] Standard normal (0 params): Density = N(0,1). */ + +#define NIFTI_INTENT_ZSCORE 5 + + /*! [C1, chap 18] Chi-squared (1 param): p1 = DOF. + Density(x) proportional to exp(-x/2) * x^(p1/2-1). */ + +#define NIFTI_INTENT_CHISQ 6 + + /*! [C2, chap 25] Beta distribution (2 params): p1=a, p2=b. + Density(x) proportional to x^(a-1) * (1-x)^(b-1). */ + +#define NIFTI_INTENT_BETA 7 + + /*! [U, chap 3] Binomial distribution (2 params): + p1 = number of trials, p2 = probability per trial. + Prob(x) = (p1 choose x) * p2^x * (1-p2)^(p1-x), for x=0,1,...,p1. */ + +#define NIFTI_INTENT_BINOM 8 + + /*! [C1, chap 17] Gamma distribution (2 params): + p1 = shape, p2 = scale. + Density(x) proportional to x^(p1-1) * exp(-p2*x). */ + +#define NIFTI_INTENT_GAMMA 9 + + /*! [U, chap 4] Poisson distribution (1 param): p1 = mean. + Prob(x) = exp(-p1) * p1^x / x! , for x=0,1,2,.... */ + +#define NIFTI_INTENT_POISSON 10 + + /*! [C1, chap 13] Normal distribution (2 params): + p1 = mean, p2 = standard deviation. */ + +#define NIFTI_INTENT_NORMAL 11 + + /*! [C2, chap 30] Noncentral F statistic (3 params): + p1 = numerator DOF, p2 = denominator DOF, + p3 = numerator noncentrality parameter. */ + +#define NIFTI_INTENT_FTEST_NONC 12 + + /*! [C2, chap 29] Noncentral chi-squared statistic (2 params): + p1 = DOF, p2 = noncentrality parameter. */ + +#define NIFTI_INTENT_CHISQ_NONC 13 + + /*! [C2, chap 23] Logistic distribution (2 params): + p1 = location, p2 = scale. + Density(x) proportional to sech^2((x-p1)/(2*p2)). */ + +#define NIFTI_INTENT_LOGISTIC 14 + + /*! [C2, chap 24] Laplace distribution (2 params): + p1 = location, p2 = scale. + Density(x) proportional to exp(-abs(x-p1)/p2). */ + +#define NIFTI_INTENT_LAPLACE 15 + + /*! [C2, chap 26] Uniform distribution: p1 = lower end, p2 = upper end. */ + +#define NIFTI_INTENT_UNIFORM 16 + + /*! [C2, chap 31] Noncentral t statistic (2 params): + p1 = DOF, p2 = noncentrality parameter. */ + +#define NIFTI_INTENT_TTEST_NONC 17 + + /*! [C1, chap 21] Weibull distribution (3 params): + p1 = location, p2 = scale, p3 = power. + Density(x) proportional to + ((x-p1)/p2)^(p3-1) * exp(-((x-p1)/p2)^p3) for x > p1. */ + +#define NIFTI_INTENT_WEIBULL 18 + + /*! [C1, chap 18] Chi distribution (1 param): p1 = DOF. + Density(x) proportional to x^(p1-1) * exp(-x^2/2) for x > 0. + p1 = 1 = 'half normal' distribution + p1 = 2 = Rayleigh distribution + p1 = 3 = Maxwell-Boltzmann distribution. */ + +#define NIFTI_INTENT_CHI 19 + + /*! [C1, chap 15] Inverse Gaussian (2 params): + p1 = mu, p2 = lambda + Density(x) proportional to + exp(-p2*(x-p1)^2/(2*p1^2*x)) / x^3 for x > 0. */ + +#define NIFTI_INTENT_INVGAUSS 20 + + /*! [C2, chap 22] Extreme value type I (2 params): + p1 = location, p2 = scale + cdf(x) = exp(-exp(-(x-p1)/p2)). */ + +#define NIFTI_INTENT_EXTVAL 21 + + /*! Data is a 'p-value' (no params). */ + +#define NIFTI_INTENT_PVAL 22 + + /*! Smallest intent_code that indicates a statistic. */ + +#define NIFTI_FIRST_STATCODE 2 + + /*! Largest intent_code that indicates a statistic. */ + +#define NIFTI_LAST_STATCODE 22 + + /*---------- these values for intent_code aren't for statistics ----------*/ + + /*! To signify that the value at each voxel is an estimate + of some parameter, set intent_code = NIFTI_INTENT_ESTIMATE. + The name of the parameter may be stored in intent_name. */ + +#define NIFTI_INTENT_ESTIMATE 1001 + + /*! To signify that the value at each voxel is an index into + some set of labels, set intent_code = NIFTI_INTENT_LABEL. + The filename with the labels may stored in aux_file. */ + +#define NIFTI_INTENT_LABEL 1002 + + /*! To signify that the value at each voxel is an index into the + NeuroNames labels set, set intent_code = NIFTI_INTENT_NEURONAME. */ + +#define NIFTI_INTENT_NEURONAME 1003 + + /*! To store an M x N matrix at each voxel: + - dataset must have a 5th dimension (dim[0]=5 and dim[5]>1) + - intent_code must be NIFTI_INTENT_GENMATRIX + - dim[5] must be M*N + - intent_p1 must be M (in float format) + - intent_p2 must be N (ditto) + - the matrix values A[i][[j] are stored in row-order: + - A[0][0] A[0][1] ... A[0][N-1] + - A[1][0] A[1][1] ... A[1][N-1] + - etc., until + - A[M-1][0] A[M-1][1] ... A[M-1][N-1] */ + +#define NIFTI_INTENT_GENMATRIX 1004 + + /*! To store an NxN symmetric matrix at each voxel: + - dataset must have a 5th dimension + - intent_code must be NIFTI_INTENT_SYMMATRIX + - dim[5] must be N*(N+1)/2 + - intent_p1 must be N (in float format) + - the matrix values A[i][[j] are stored in row-order: + - A[0][0] + - A[1][0] A[1][1] + - A[2][0] A[2][1] A[2][2] + - etc.: row-by-row */ + +#define NIFTI_INTENT_SYMMATRIX 1005 + + /*! To signify that the vector value at each voxel is to be taken + as a displacement field or vector: + - dataset must have a 5th dimension + - intent_code must be NIFTI_INTENT_DISPVECT + - dim[5] must be the dimensionality of the displacment + vector (e.g., 3 for spatial displacement, 2 for in-plane) */ + +#define NIFTI_INTENT_DISPVECT 1006 /* specifically for displacements */ +#define NIFTI_INTENT_VECTOR 1007 /* for any other type of vector */ + + /*! To signify that the vector value at each voxel is really a + spatial coordinate (e.g., the vertices or nodes of a surface mesh): + - dataset must have a 5th dimension + - intent_code must be NIFTI_INTENT_POINTSET + - dim[0] = 5 + - dim[1] = number of points + - dim[2] = dim[3] = dim[4] = 1 + - dim[5] must be the dimensionality of space (e.g., 3 => 3D space). + - intent_name may describe the object these points come from + (e.g., "pial", "gray/white" , "EEG", "MEG"). */ + +#define NIFTI_INTENT_POINTSET 1008 + + /*! To signify that the vector value at each voxel is really a triple + of indexes (e.g., forming a triangle) from a pointset dataset: + - dataset must have a 5th dimension + - intent_code must be NIFTI_INTENT_TRIANGLE + - dim[0] = 5 + - dim[1] = number of triangles + - dim[2] = dim[3] = dim[4] = 1 + - dim[5] = 3 + - datatype should be an integer type (preferably DT_INT32) + - the data values are indexes (0,1,...) into a pointset dataset. */ + +#define NIFTI_INTENT_TRIANGLE 1009 + + /*! To signify that the vector value at each voxel is a quaternion: + - dataset must have a 5th dimension + - intent_code must be NIFTI_INTENT_QUATERNION + - dim[0] = 5 + - dim[5] = 4 + - datatype should be a floating point type */ + +#define NIFTI_INTENT_QUATERNION 1010 + +/*---------------------------------------------------------------------------*/ +/* 3D IMAGE (VOLUME) ORIENTATION AND LOCATION IN SPACE: + --------------------------------------------------- + There are 3 different methods by which continuous coordinates can + attached to voxels. The discussion below emphasizes 3D volumes, and + the continuous coordinates are referred to as (x,y,z). The voxel + index coordinates (i.e., the array indexes) are referred to as (i,j,k), + with valid ranges: + i = 0 .. dim[1]-1 + j = 0 .. dim[2]-1 (if dim[0] >= 2) + k = 0 .. dim[3]-1 (if dim[0] >= 3) + The (x,y,z) coordinates refer to the CENTER of a voxel. In methods + 2 and 3, the (x,y,z) axes refer to a subject-based coordinate system, + with + +x = Right +y = Anterior +z = Superior. + This is a right-handed coordinate system. However, the exact direction + these axes point with respect to the subject depends on qform_code + (Method 2) and sform_code (Method 3). + + N.B.: The i index varies most rapidly, j index next, k index slowest. + Thus, voxel (i,j,k) is stored starting at location + (i + j*dim[1] + k*dim[1]*dim[2]) * (bitpix/8) + into the dataset array. + + N.B.: The ANALYZE 7.5 coordinate system is + +x = Left +y = Anterior +z = Superior + which is a left-handed coordinate system. This backwardness is + too difficult to tolerate, so this NIFTI-1 standard specifies the + coordinate order which is most common in functional neuroimaging. + + N.B.: The 3 methods below all give the locations of the voxel centers + in the (x,y,z) coordinate system. In many cases, programs will wish + to display image data on some other grid. In such a case, the program + will need to convert its desired (x,y,z) values into (i,j,k) values + in order to extract (or interpolate) the image data. This operation + would be done with the inverse transformation to those described below. + + N.B.: Method 2 uses a factor 'qfac' which is either -1 or 1; qfac is + stored in the otherwise unused pixdim[0]. If pixdim[0]=0.0 (which + should not occur), we take qfac=1. Of course, pixdim[0] is only used + when reading a NIFTI-1 header, not when reading an ANALYZE 7.5 header. + + N.B.: The units of (x,y,z) can be specified using the xyzt_units field. + + METHOD 1 (the "old" way, used only when qform_code = 0): + ------------------------------------------------------- + The coordinate mapping from (i,j,k) to (x,y,z) is the ANALYZE + 7.5 way. This is a simple scaling relationship: + + x = pixdim[1] * i + y = pixdim[2] * j + z = pixdim[3] * k + + No particular spatial orientation is attached to these (x,y,z) + coordinates. (NIFTI-1 does not have the ANALYZE 7.5 orient field, + which is not general and is often not set properly.) This method + is not recommended, and is present mainly for compatibility with + ANALYZE 7.5 files. + + METHOD 2 (used when qform_code > 0, which should be the "normal case): + --------------------------------------------------------------------- + The (x,y,z) coordinates are given by the pixdim[] scales, a rotation + matrix, and a shift. This method is intended to represent + "scanner-anatomical" coordinates, which are often embedded in the + image header (e.g., DICOM fields (0020,0032), (0020,0037), (0028,0030), + and (0018,0050)), and represent the nominal orientation and location of + the data. This method can also be used to represent "aligned" + coordinates, which would typically result from some post-acquisition + alignment of the volume to a standard orientation (e.g., the same + subject on another day, or a rigid rotation to true anatomical + orientation from the tilted position of the subject in the scanner). + The formula for (x,y,z) in terms of header parameters and (i,j,k) is: + + [ x ] [ R11 R12 R13 ] [ pixdim[1] * i ] [ qoffset_x ] + [ y ] = [ R21 R22 R23 ] [ pixdim[2] * j ] + [ qoffset_y ] + [ z ] [ R31 R32 R33 ] [ qfac * pixdim[3] * k ] [ qoffset_z ] + + The qoffset_* shifts are in the NIFTI-1 header. Note that the center + of the (i,j,k)=(0,0,0) voxel (first value in the dataset array) is + just (x,y,z)=(qoffset_x,qoffset_y,qoffset_z). + + The rotation matrix R is calculated from the quatern_* parameters. + This calculation is described below. + + The scaling factor qfac is either 1 or -1. The rotation matrix R + defined by the quaternion parameters is "proper" (has determinant 1). + This may not fit the needs of the data; for example, if the image + grid is + i increases from Left-to-Right + j increases from Anterior-to-Posterior + k increases from Inferior-to-Superior + Then (i,j,k) is a left-handed triple. In this example, if qfac=1, + the R matrix would have to be + + [ 1 0 0 ] + [ 0 -1 0 ] which is "improper" (determinant = -1). + [ 0 0 1 ] + + If we set qfac=-1, then the R matrix would be + + [ 1 0 0 ] + [ 0 -1 0 ] which is proper. + [ 0 0 -1 ] + + This R matrix is represented by quaternion [a,b,c,d] = [0,1,0,0] + (which encodes a 180 degree rotation about the x-axis). + + METHOD 3 (used when sform_code > 0): + ----------------------------------- + The (x,y,z) coordinates are given by a general affine transformation + of the (i,j,k) indexes: + + x = srow_x[0] * i + srow_x[1] * j + srow_x[2] * k + srow_x[3] + y = srow_y[0] * i + srow_y[1] * j + srow_y[2] * k + srow_y[3] + z = srow_z[0] * i + srow_z[1] * j + srow_z[2] * k + srow_z[3] + + The srow_* vectors are in the NIFTI_1 header. Note that no use is + made of pixdim[] in this method. + + WHY 3 METHODS? + -------------- + Method 1 is provided only for backwards compatibility. The intention + is that Method 2 (qform_code > 0) represents the nominal voxel locations + as reported by the scanner, or as rotated to some fiducial orientation and + location. Method 3, if present (sform_code > 0), is to be used to give + the location of the voxels in some standard space. The sform_code + indicates which standard space is present. Both methods 2 and 3 can be + present, and be useful in different contexts (method 2 for displaying the + data on its original grid; method 3 for displaying it on a standard grid). + + In this scheme, a dataset would originally be set up so that the + Method 2 coordinates represent what the scanner reported. Later, + a registration to some standard space can be computed and inserted + in the header. Image display software can use either transform, + depending on its purposes and needs. + + In Method 2, the origin of coordinates would generally be whatever + the scanner origin is; for example, in MRI, (0,0,0) is the center + of the gradient coil. + + In Method 3, the origin of coordinates would depend on the value + of sform_code; for example, for the Talairach coordinate system, + (0,0,0) corresponds to the Anterior Commissure. + + QUATERNION REPRESENTATION OF ROTATION MATRIX (METHOD 2) + ------------------------------------------------------- + The orientation of the (x,y,z) axes relative to the (i,j,k) axes + in 3D space is specified using a unit quaternion [a,b,c,d], where + a*a+b*b+c*c+d*d=1. The (b,c,d) values are all that is needed, since + we require that a = sqrt(1.0-b*b+c*c+d*d) be nonnegative. The (b,c,d) + values are stored in the (quatern_b,quatern_c,quatern_d) fields. + + The quaternion representation is chosen for its compactness in + representing rotations. The (proper) 3x3 rotation matrix that + corresponds to [a,b,c,d] is + + [ a*a+b*b-c*c-d*d 2*b*c-2*a*d 2*b*d+2*a*c ] + R = [ 2*b*c+2*a*d a*a+c*c-b*b-d*d 2*c*d-2*a*b ] + [ 2*b*d-2*a*c 2*c*d+2*a*b a*a+d*d-c*c-b*b ] + + [ R11 R12 R13 ] + = [ R21 R22 R23 ] + [ R31 R32 R33 ] + + If (p,q,r) is a unit 3-vector, then rotation of angle h about that + direction is represented by the quaternion + + [a,b,c,d] = [cos(h/2), p*sin(h/2), q*sin(h/2), r*sin(h/2)]. + + Requiring a >= 0 is equivalent to requiring -Pi <= h <= Pi. (Note that + [-a,-b,-c,-d] represents the same rotation as [a,b,c,d]; there are 2 + quaternions that can be used to represent a given rotation matrix R.) + To rotate a 3-vector (x,y,z) using quaternions, we compute the + quaternion product + + [0,x',y',z'] = [a,b,c,d] * [0,x,y,z] * [a,-b,-c,-d] + + which is equivalent to the matrix-vector multiply + + [ x' ] [ x ] + [ y' ] = R [ y ] (equivalence depends on a*a+b*b+c*c+d*d=1) + [ z' ] [ z ] + + Multiplication of 2 quaternions is defined by the following: + + [a,b,c,d] = a*1 + b*I + c*J + d*K + where + I*I = J*J = K*K = -1 (I,J,K are square roots of -1) + I*J = K J*K = I K*I = J + J*I = -K K*J = -I I*K = -J (not commutative!) + For example + [a,b,0,0] * [0,0,0,1] = [0,-b,0,a] + since this expands to + (a+b*I)*(K) = (a*K+b*I*K) = (a*K-b*J). + + The above formula shows how to go from quaternion (b,c,d) to + rotation matrix and direction cosines. Conversely, given R, + we can compute the fields for the NIFTI-1 header by + + a = 0.5 * sqrt(1+R11+R22+R33) (not stored) + b = 0.25 * (R32-R23) / a => quatern_b + c = 0.25 * (R13-R31) / a => quatern_c + d = 0.25 * (R21-R12) / a => quatern_d + + If a=0 (a 180 degree rotation), alternative formulas are needed. + See the nifti1_io.c function mat44_to_quatern() for an implementation + of the various cases in converting R to [a,b,c,d]. + + Note that R-transpose (= R-inverse) would lead to the quaternion + [a,-b,-c,-d]. + + The choice to specify the qoffset_x (etc.) values in the final + coordinate system is partly to make it easy to convert DICOM images to + this format. The DICOM attribute "Image Position (Patient)" (0020,0032) + stores the (Xd,Yd,Zd) coordinates of the center of the first voxel. + Here, (Xd,Yd,Zd) refer to DICOM coordinates, and Xd=-x, Yd=-y, Zd=z, + where (x,y,z) refers to the NIFTI coordinate system discussed above. + (i.e., DICOM +Xd is Left, +Yd is Posterior, +Zd is Superior, + whereas +x is Right, +y is Anterior , +z is Superior. ) + Thus, if the (0020,0032) DICOM attribute is extracted into (px,py,pz), then + qoffset_x = -px qoffset_y = -py qoffset_z = pz + is a reasonable setting when qform_code=NIFTI_XFORM_SCANNER_ANAT. + + That is, DICOM's coordinate system is 180 degrees rotated about the z-axis + from the neuroscience/NIFTI coordinate system. To transform between DICOM + and NIFTI, you just have to negate the x- and y-coordinates. + + The DICOM attribute (0020,0037) "Image Orientation (Patient)" gives the + orientation of the x- and y-axes of the image data in terms of 2 3-vectors. + The first vector is a unit vector along the x-axis, and the second is + along the y-axis. If the (0020,0037) attribute is extracted into the + value (xa,xb,xc,ya,yb,yc), then the first two columns of the R matrix + would be + [ -xa -ya ] + [ -xb -yb ] + [ xc yc ] + The negations are because DICOM's x- and y-axes are reversed relative + to NIFTI's. The third column of the R matrix gives the direction of + displacement (relative to the subject) along the slice-wise direction. + This orientation is not encoded in the DICOM standard in a simple way; + DICOM is mostly concerned with 2D images. The third column of R will be + either the cross-product of the first 2 columns or its negative. It is + possible to infer the sign of the 3rd column by examining the coordinates + in DICOM attribute (0020,0032) "Image Position (Patient)" for successive + slices. However, this method occasionally fails for reasons that I + (RW Cox) do not understand. +-----------------------------------------------------------------------------*/ + + /* [qs]form_code value: */ /* x,y,z coordinate system refers to: */ + /*-----------------------*/ /*---------------------------------------*/ + + /*! Arbitrary coordinates (Method 1). */ + +#define NIFTI_XFORM_UNKNOWN 0 + + /*! Scanner-based anatomical coordinates */ + +#define NIFTI_XFORM_SCANNER_ANAT 1 + + /*! Coordinates aligned to another file's, + or to anatomical "truth". */ + +#define NIFTI_XFORM_ALIGNED_ANAT 2 + + /*! Coordinates aligned to Talairach- + Tournoux Atlas; (0,0,0)=AC, etc. */ + +#define NIFTI_XFORM_TALAIRACH 3 + + /*! MNI 152 normalized coordinates. */ + +#define NIFTI_XFORM_MNI_152 4 + +/*---------------------------------------------------------------------------*/ +/* UNITS OF SPATIAL AND TEMPORAL DIMENSIONS: + ---------------------------------------- + The codes below can be used in xyzt_units to indicate the units of pixdim. + As noted earlier, dimensions 1,2,3 are for x,y,z; dimension 4 is for + time (t). + - If dim[4]=1 or dim[0] < 4, there is no time axis. + - A single time series (no space) would be specified with + - dim[0] = 4 (for scalar data) or dim[0] = 5 (for vector data) + - dim[1] = dim[2] = dim[3] = 1 + - dim[4] = number of time points + - pixdim[4] = time step + - xyzt_units indicates units of pixdim[4] + - dim[5] = number of values stored at each time point + + Bits 0..2 of xyzt_units specify the units of pixdim[1..3] + (e.g., spatial units are values 1..7). + Bits 3..5 of xyzt_units specify the units of pixdim[4] + (e.g., temporal units are multiples of 8). + + This compression of 2 distinct concepts into 1 byte is due to the + limited space available in the 348 byte ANALYZE 7.5 header. The + macros XYZT_TO_SPACE and XYZT_TO_TIME can be used to mask off the + undesired bits from the xyzt_units fields, leaving "pure" space + and time codes. Inversely, the macro SPACE_TIME_TO_XYZT can be + used to assemble a space code (0,1,2,...,7) with a time code + (0,8,16,32,...,56) into the combined value for xyzt_units. + + Note that codes are provided to indicate the "time" axis units are + actually frequency in Hertz (_HZ) or in part-per-million (_PPM). + + The toffset field can be used to indicate a nonzero start point for + the time axis. That is, time point #m is at t=toffset+m*pixdim[4] + for m=0..dim[4]-1. +-----------------------------------------------------------------------------*/ + + /*! NIFTI code for unspecified units. */ +#define NIFTI_UNITS_UNKNOWN 0 + + /** Space codes are multiples of 1. **/ + /*! NIFTI code for meters. */ +#define NIFTI_UNITS_METER 1 + /*! NIFTI code for millimeters. */ +#define NIFTI_UNITS_MM 2 + /*! NIFTI code for micrometers. */ +#define NIFTI_UNITS_MICRON 3 + + /** Time codes are multiples of 8. **/ + /*! NIFTI code for seconds. */ +#define NIFTI_UNITS_SEC 8 + /*! NIFTI code for milliseconds. */ +#define NIFTI_UNITS_MSEC 16 + /*! NIFTI code for microseconds. */ +#define NIFTI_UNITS_USEC 24 + + /*** These units are for spectral data: ***/ + /*! NIFTI code for Hertz. */ +#define NIFTI_UNITS_HZ 32 + /*! NIFTI code for ppm. */ +#define NIFTI_UNITS_PPM 40 + +#undef XYZT_TO_SPACE +#undef XYZT_TO_TIME +#define XYZT_TO_SPACE(xyzt) ( (xyzt) & 0x07 ) +#define XYZT_TO_TIME(xyzt) ( (xyzt) & 0x38 ) + +#undef SPACE_TIME_TO_XYZT +#define SPACE_TIME_TO_XYZT(ss,tt) ( (((char)(ss)) & 0x07) \ + | (((char)(tt)) & 0x38) ) + +/*---------------------------------------------------------------------------*/ +/* MRI-SPECIFIC SPATIAL AND TEMPORAL INFORMATION: + --------------------------------------------- + A few fields are provided to store some extra information + that is sometimes important when storing the image data + from an FMRI time series experiment. (After processing such + data into statistical images, these fields are not likely + to be useful.) + + { freq_dim } = These fields encode which spatial dimension (1,2, or 3) + { phase_dim } = corresponds to which acquisition dimension for MRI data. + { slice_dim } = + Examples: + Rectangular scan multi-slice EPI: + freq_dim = 1 phase_dim = 2 slice_dim = 3 (or some permutation) + Spiral scan multi-slice EPI: + freq_dim = phase_dim = 0 slice_dim = 3 + since the concepts of frequency- and phase-encoding directions + don't apply to spiral scan + + slice_duration = If this is positive, AND if slice_dim is nonzero, + indicates the amount of time used to acquire 1 slice. + slice_duration*dim[slice_dim] can be less than pixdim[4] + with a clustered acquisition method, for example. + + slice_code = If this is nonzero, AND if slice_dim is nonzero, AND + if slice_duration is positive, indicates the timing + pattern of the slice acquisition. The following codes + are defined: + NIFTI_SLICE_SEQ_INC + NIFTI_SLICE_SEQ_DEC + NIFTI_SLICE_ALT_INC + NIFTI_SLICE_ALT_DEC + { slice_start } = Indicates the start and end of the slice acquisition + { slice_end } = pattern, when slice_code is nonzero. These values + are present to allow for the possible addition of + "padded" slices at either end of the volume, which + don't fit into the slice timing pattern. If there + are no padding slices, then slice_start=0 and + slice_end=dim[slice_dim]-1 are the correct values. + For these values to be meaningful, slice_start must + be non-negative and slice_end must be greater than + slice_start. + + The following table indicates the slice timing pattern, relative to + time=0 for the first slice acquired, for some sample cases. Here, + dim[slice_dim]=7 (there are 7 slices, labeled 0..6), slice_duration=0.1, + and slice_start=1, slice_end=5 (1 padded slice on each end). + + slice + index SEQ_INC SEQ_DEC ALT_INC ALT_DEC + 6 -- n/a n/a n/a n/a n/a = not applicable + 5 -- 0.4 0.0 0.2 0.0 (slice time offset + 4 -- 0.3 0.1 0.4 0.3 doesn't apply to + 3 -- 0.2 0.2 0.1 0.1 slices outside range + 2 -- 0.1 0.3 0.3 0.4 slice_start..slice_end) + 1 -- 0.0 0.4 0.0 0.2 + 0 -- n/a n/a n/a n/a + + The fields freq_dim, phase_dim, slice_dim are all squished into the single + byte field dim_info (2 bits each, since the values for each field are + limited to the range 0..3). This unpleasantness is due to lack of space + in the 348 byte allowance. + + The macros DIM_INFO_TO_FREQ_DIM, DIM_INFO_TO_PHASE_DIM, and + DIM_INFO_TO_SLICE_DIM can be used to extract these values from the + dim_info byte. + + The macro FPS_INTO_DIM_INFO can be used to put these 3 values + into the dim_info byte. +-----------------------------------------------------------------------------*/ + +#undef DIM_INFO_TO_FREQ_DIM +#undef DIM_INFO_TO_PHASE_DIM +#undef DIM_INFO_TO_SLICE_DIM + +#define DIM_INFO_TO_FREQ_DIM(di) ( ((di) ) & 0x03 ) +#define DIM_INFO_TO_PHASE_DIM(di) ( ((di) >> 2) & 0x03 ) +#define DIM_INFO_TO_SLICE_DIM(di) ( ((di) >> 4) & 0x03 ) + +#undef FPS_INTO_DIM_INFO +#define FPS_INTO_DIM_INFO(fd,pd,sd) ( ( ( ((char)(fd)) & 0x03) ) | \ + ( ( ((char)(pd)) & 0x03) << 2 ) | \ + ( ( ((char)(sd)) & 0x03) << 4 ) ) + +#define NIFTI_SLICE_SEQ_INC 1 +#define NIFTI_SLICE_SEQ_DEC 2 +#define NIFTI_SLICE_ALT_INC 3 +#define NIFTI_SLICE_ALT_DEC 4 + +/*---------------------------------------------------------------------------*/ +/* UNUSED FIELDS: + ------------- + Some of the ANALYZE 7.5 fields marked as ++UNUSED++ may need to be set + to particular values for compatibility with other programs. The issue + of interoperability of ANALYZE 7.5 files is a murky one -- not all + programs require exactly the same set of fields. (Unobscuring this + murkiness is a principal motivation behind NIFTI-1.) + + Some of the fields that may need to be set for other (non-NIFTI aware) + software to be happy are: + + extents dbh.h says this should be 16384 + regular dbh.h says this should be the character 'r' + glmin, } dbh.h says these values should be the min and max voxel + glmax } values for the entire dataset + + It is best to initialize ALL fields in the NIFTI-1 header to 0 + (e.g., with calloc()), then fill in what is needed. +-----------------------------------------------------------------------------*/ + +/*---------------------------------------------------------------------------*/ +/* MISCELLANEOUS C MACROS +-----------------------------------------------------------------------------*/ + +/*.................*/ +/*! Given a nifti_1_header struct, check if it has a good magic number. + Returns NIFTI version number (1..9) if magic is good, 0 if it is not. */ + +#define NIFTI_VERSION(h) \ + ( ( (h).magic[0]=='n' && (h).magic[3]=='\0' && \ + ( (h).magic[1]=='i' || (h).magic[1]=='+' ) && \ + ( (h).magic[2]>='1' && (h).magic[2]<='9' ) ) \ + ? (h).magic[2]-'0' : 0 ) + +/*.................*/ +/*! Check if a nifti_1_header struct says if the data is stored in the + same file or in a separate file. Returns 1 if the data is in the same + file as the header, 0 if it is not. */ + +#define NIFTI_ONEFILE(h) ( (h).magic[1] == '+' ) + +/*.................*/ +/*! Check if a nifti_1_header struct needs to be byte swapped. + Returns 1 if it needs to be swapped, 0 if it does not. */ + +#define NIFTI_NEEDS_SWAP(h) ( (h).dim[0] < 0 || (h).dim[0] > 7 ) + +/*.................*/ +/*! Check if a nifti_1_header struct contains a 5th (vector) dimension. + Returns size of 5th dimension if > 1, returns 0 otherwise. */ + +#define NIFTI_5TH_DIM(h) ( ((h).dim[0]>4 && (h).dim[5]>1) ? (h).dim[5] : 0 ) + +/*****************************************************************************/ + +/*=================*/ +#ifdef __cplusplus +} +#endif +/*=================*/ + +#endif /* _NIFTI_HEADER_ */ diff --git a/spm/nii_for_spm2/@nifti/private/nifti_stats.c b/spm/nii_for_spm2/@nifti/private/nifti_stats.c new file mode 100755 index 0000000..c561c1a --- /dev/null +++ b/spm/nii_for_spm2/@nifti/private/nifti_stats.c @@ -0,0 +1,11277 @@ +#ifndef lint +static char sccsid[] = "%W% R.W. Cox %E%"; +#endif + /************************************************************************/ + /** Functions to compute cumulative distributions and their inverses **/ + /** for the NIfTI-1 statistical types. Much of this code is taken **/ + /** from other sources. In particular, the cdflib functions by **/ + /** Brown and Lovato make up the bulk of this file. That code **/ + /** was placed in the public domain. The code by K. Krishnamoorthy **/ + /** is also released for unrestricted use. Finally, the other parts **/ + /** of this file (by RW Cox) are released to the public domain. **/ + /** **/ + /** Most of this file comprises a set of "static" functions, to be **/ + /** called by the user-level functions at the very end of the file. **/ + /** At the end of the file is a simple main program to drive these **/ + /** functions. **/ + /** **/ + /** To find the user-level functions, search forward for the string **/ + /** "nifti_", which will be at about line 11000. **/ + /************************************************************************/ + /*****==============================================================*****/ + /***** Neither the National Institutes of Health (NIH), the DFWG, *****/ + /***** nor any of the members or employees of these institutions *****/ + /***** imply any warranty of usefulness of this material for any *****/ + /***** purpose, and do not assume any liability for damages, *****/ + /***** incidental or otherwise, caused by any use of this document. *****/ + /***** If these conditions are not acceptable, do not use this! *****/ + /*****==============================================================*****/ + /************************************************************************/ + + /*....................................................................... + To compile with gcc, for example: + + gcc -O3 -ffast-math -o nifti_stats nifti_stats.c -lm + ........................................................................*/ + +#include "nifti1.h" /* for the NIFTI_INTENT_* constants */ +#include +#include +#include + + /************************************************************************/ + /************ Include all the cdflib functions here and now *************/ + /************ [about 9900 lines of code below here] *************/ + /************************************************************************/ + +/** Prototypes for cdflib functions **/ + +static double algdiv(double*,double*); +static double alngam(double*); +static double alnrel(double*); +static double apser(double*,double*,double*,double*); +static double basym(double*,double*,double*,double*); +static double bcorr(double*,double*); +static double betaln(double*,double*); +static double bfrac(double*,double*,double*,double*,double*,double*); +static void bgrat(double*,double*,double*,double*,double*,double*,int*i); +static double bpser(double*,double*,double*,double*); +static void bratio(double*,double*,double*,double*,double*,double*,int*); +static double brcmp1(int*,double*,double*,double*,double*); +static double brcomp(double*,double*,double*,double*); +static double bup(double*,double*,double*,double*,int*,double*); +static void cdfbet(int*,double*,double*,double*,double*,double*,double*, + int*,double*); +static void cdfbin(int*,double*,double*,double*,double*,double*,double*, + int*,double*); +static void cdfchi(int*,double*,double*,double*,double*,int*,double*); +static void cdfchn(int*,double*,double*,double*,double*,double*,int*,double*); +static void cdff(int*,double*,double*,double*,double*,double*,int*,double*); +static void cdffnc(int*,double*,double*,double*,double*,double*,double*, + int*s,double*); +static void cdfgam(int*,double*,double*,double*,double*,double*,int*,double*); +static void cdfnbn(int*,double*,double*,double*,double*,double*,double*, + int*,double*); +static void cdfnor(int*,double*,double*,double*,double*,double*,int*,double*); +static void cdfpoi(int*,double*,double*,double*,double*,int*,double*); +static void cdft(int*,double*,double*,double*,double*,int*,double*); +static void cumbet(double*,double*,double*,double*,double*,double*); +static void cumbin(double*,double*,double*,double*,double*,double*); +static void cumchi(double*,double*,double*,double*); +static void cumchn(double*,double*,double*,double*,double*); +static void cumf(double*,double*,double*,double*,double*); +static void cumfnc(double*,double*,double*,double*,double*,double*); +static void cumgam(double*,double*,double*,double*); +static void cumnbn(double*,double*,double*,double*,double*,double*); +static void cumnor(double*,double*,double*); +static void cumpoi(double*,double*,double*,double*); +static void cumt(double*,double*,double*,double*); +static double dbetrm(double*,double*); +static double devlpl(double [],int*,double*); +static double dexpm1(double*); +static double dinvnr(double *p,double *q); +static void E0000(int,int*,double*,double*,unsigned long*, + unsigned long*,double*,double*,double*, + double*,double*,double*,double*); +static void dinvr(int*,double*,double*,unsigned long*,unsigned long*); +static void dstinv(double*,double*,double*,double*,double*,double*, + double*); +static double dlanor(double*); +static double dln1mx(double*); +static double dln1px(double*); +static double dlnbet(double*,double*); +static double dlngam(double*); +static double dstrem(double*); +static double dt1(double*,double*,double*); +static void E0001(int,int*,double*,double*,double*,double*, + unsigned long*,unsigned long*,double*,double*, + double*,double*); +static void dzror(int*,double*,double*,double*,double *, + unsigned long*,unsigned long*); +static void dstzr(double *zxlo,double *zxhi,double *zabstl,double *zreltl); +static double erf1(double*); +static double erfc1(int*,double*); +static double esum(int*,double*); +static double exparg(int*); +static double fpser(double*,double*,double*,double*); +static double gam1(double*); +static void gaminv(double*,double*,double*,double*,double*,int*); +static double gamln(double*); +static double gamln1(double*); +static double Xgamm(double*); +static void grat1(double*,double*,double*,double*,double*,double*); +static void gratio(double*,double*,double*,double*,int*); +static double gsumln(double*,double*); +static double psi(double*); +static double rcomp(double*,double*); +static double rexp(double*); +static double rlog(double*); +static double rlog1(double*); +static double spmpar(int*); +static double stvaln(double*); +static double fifdint(double); +static double fifdmax1(double,double); +static double fifdmin1(double,double); +static double fifdsign(double,double); +static long fifidint(double); +static long fifmod(long,long); +static void ftnstop(char*); +static int ipmpar(int*); + +/***=====================================================================***/ +static double algdiv(double *a,double *b) +/* +----------------------------------------------------------------------- + + COMPUTATION OF LN(GAMMA(B)/GAMMA(A+B)) WHEN B .GE. 8 + + -------- + + IN THIS ALGORITHM, DEL(X) IS THE FUNCTION DEFINED BY + LN(GAMMA(X)) = (X - 0.5)*LN(X) - X + 0.5*LN(2*PI) + DEL(X). + +----------------------------------------------------------------------- +*/ +{ +static double c0 = .833333333333333e-01; +static double c1 = -.277777777760991e-02; +static double c2 = .793650666825390e-03; +static double c3 = -.595202931351870e-03; +static double c4 = .837308034031215e-03; +static double c5 = -.165322962780713e-02; +static double algdiv,c,d,h,s11,s3,s5,s7,s9,t,u,v,w,x,x2,T1; +/* + .. + .. Executable Statements .. +*/ + if(*a <= *b) goto S10; + h = *b/ *a; + c = 1.0e0/(1.0e0+h); + x = h/(1.0e0+h); + d = *a+(*b-0.5e0); + goto S20; +S10: + h = *a/ *b; + c = h/(1.0e0+h); + x = 1.0e0/(1.0e0+h); + d = *b+(*a-0.5e0); +S20: +/* + SET SN = (1 - X**N)/(1 - X) +*/ + x2 = x*x; + s3 = 1.0e0+(x+x2); + s5 = 1.0e0+(x+x2*s3); + s7 = 1.0e0+(x+x2*s5); + s9 = 1.0e0+(x+x2*s7); + s11 = 1.0e0+(x+x2*s9); +/* + SET W = DEL(B) - DEL(A + B) +*/ + t = pow(1.0e0/ *b,2.0); + w = ((((c5*s11*t+c4*s9)*t+c3*s7)*t+c2*s5)*t+c1*s3)*t+c0; + w *= (c/ *b); +/* + COMBINE THE RESULTS +*/ + T1 = *a/ *b; + u = d*alnrel(&T1); + v = *a*(log(*b)-1.0e0); + if(u <= v) goto S30; + algdiv = w-v-u; + return algdiv; +S30: + algdiv = w-u-v; + return algdiv; +} /* END */ + +/***=====================================================================***/ +static double alngam(double *x) +/* +********************************************************************** + + double alngam(double *x) + double precision LN of the GAMma function + + + Function + + + Returns the natural logarithm of GAMMA(X). + + + Arguments + + + X --> value at which scaled log gamma is to be returned + X is DOUBLE PRECISION + + + Method + + + If X .le. 6.0, then use recursion to get X below 3 + then apply rational approximation number 5236 of + Hart et al, Computer Approximations, John Wiley and + Sons, NY, 1968. + + If X .gt. 6.0, then use recursion to get X to at least 12 and + then use formula 5423 of the same source. + +********************************************************************** +*/ +{ +#define hln2pi 0.91893853320467274178e0 +static double coef[5] = { + 0.83333333333333023564e-1,-0.27777777768818808e-2,0.79365006754279e-3, + -0.594997310889e-3,0.8065880899e-3 +}; +static double scoefd[4] = { + 0.62003838007126989331e2,0.9822521104713994894e1,-0.8906016659497461257e1, + 0.1000000000000000000e1 +}; +static double scoefn[9] = { + 0.62003838007127258804e2,0.36036772530024836321e2,0.20782472531792126786e2, + 0.6338067999387272343e1,0.215994312846059073e1,0.3980671310203570498e0, + 0.1093115956710439502e0,0.92381945590275995e-2,0.29737866448101651e-2 +}; +static int K1 = 9; +static int K3 = 4; +static int K5 = 5; +static double alngam,offset,prod,xx; +static int i,n; +static double T2,T4,T6; +/* + .. + .. Executable Statements .. +*/ + if(!(*x <= 6.0e0)) goto S70; + prod = 1.0e0; + xx = *x; + if(!(*x > 3.0e0)) goto S30; +S10: + if(!(xx > 3.0e0)) goto S20; + xx -= 1.0e0; + prod *= xx; + goto S10; +S30: +S20: + if(!(*x < 2.0e0)) goto S60; +S40: + if(!(xx < 2.0e0)) goto S50; + prod /= xx; + xx += 1.0e0; + goto S40; +S60: +S50: + T2 = xx-2.0e0; + T4 = xx-2.0e0; + alngam = devlpl(scoefn,&K1,&T2)/devlpl(scoefd,&K3,&T4); +/* + COMPUTE RATIONAL APPROXIMATION TO GAMMA(X) +*/ + alngam *= prod; + alngam = log(alngam); + goto S110; +S70: + offset = hln2pi; +/* + IF NECESSARY MAKE X AT LEAST 12 AND CARRY CORRECTION IN OFFSET +*/ + n = fifidint(12.0e0-*x); + if(!(n > 0)) goto S90; + prod = 1.0e0; + for(i=1; i<=n; i++) prod *= (*x+(double)(i-1)); + offset -= log(prod); + xx = *x+(double)n; + goto S100; +S90: + xx = *x; +S100: +/* + COMPUTE POWER SERIES +*/ + T6 = 1.0e0/pow(xx,2.0); + alngam = devlpl(coef,&K5,&T6)/xx; + alngam += (offset+(xx-0.5e0)*log(xx)-xx); +S110: + return alngam; +#undef hln2pi +} /* END */ + +/***=====================================================================***/ +static double alnrel(double *a) +/* +----------------------------------------------------------------------- + EVALUATION OF THE FUNCTION LN(1 + A) +----------------------------------------------------------------------- +*/ +{ +static double p1 = -.129418923021993e+01; +static double p2 = .405303492862024e+00; +static double p3 = -.178874546012214e-01; +static double q1 = -.162752256355323e+01; +static double q2 = .747811014037616e+00; +static double q3 = -.845104217945565e-01; +static double alnrel,t,t2,w,x; +/* + .. + .. Executable Statements .. +*/ + if(fabs(*a) > 0.375e0) goto S10; + t = *a/(*a+2.0e0); + t2 = t*t; + w = (((p3*t2+p2)*t2+p1)*t2+1.0e0)/(((q3*t2+q2)*t2+q1)*t2+1.0e0); + alnrel = 2.0e0*t*w; + return alnrel; +S10: + x = 1.e0+*a; + alnrel = log(x); + return alnrel; +} /* END */ + +/***=====================================================================***/ +static double apser(double *a,double *b,double *x,double *eps) +/* +----------------------------------------------------------------------- + APSER YIELDS THE INCOMPLETE BETA RATIO I(SUB(1-X))(B,A) FOR + A .LE. MIN(EPS,EPS*B), B*X .LE. 1, AND X .LE. 0.5. USED WHEN + A IS VERY SMALL. USE ONLY IF ABOVE INEQUALITIES ARE SATISFIED. +----------------------------------------------------------------------- +*/ +{ +static double g = .577215664901533e0; +static double apser,aj,bx,c,j,s,t,tol; +/* + .. + .. Executable Statements .. +*/ + bx = *b**x; + t = *x-bx; + if(*b**eps > 2.e-2) goto S10; + c = log(*x)+psi(b)+g+t; + goto S20; +S10: + c = log(bx)+g+t; +S20: + tol = 5.0e0**eps*fabs(c); + j = 1.0e0; + s = 0.0e0; +S30: + j += 1.0e0; + t *= (*x-bx/j); + aj = t/j; + s += aj; + if(fabs(aj) > tol) goto S30; + apser = -(*a*(c+s)); + return apser; +} /* END */ + +/***=====================================================================***/ +static double basym(double *a,double *b,double *lambda,double *eps) +/* +----------------------------------------------------------------------- + ASYMPTOTIC EXPANSION FOR IX(A,B) FOR LARGE A AND B. + LAMBDA = (A + B)*Y - B AND EPS IS THE TOLERANCE USED. + IT IS ASSUMED THAT LAMBDA IS NONNEGATIVE AND THAT + A AND B ARE GREATER THAN OR EQUAL TO 15. +----------------------------------------------------------------------- +*/ +{ +static double e0 = 1.12837916709551e0; +static double e1 = .353553390593274e0; +static int num = 20; +/* +------------------------ + ****** NUM IS THE MAXIMUM VALUE THAT N CAN TAKE IN THE DO LOOP + ENDING AT STATEMENT 50. IT IS REQUIRED THAT NUM BE EVEN. + THE ARRAYS A0, B0, C, D HAVE DIMENSION NUM + 1. +------------------------ + E0 = 2/SQRT(PI) + E1 = 2**(-3/2) +------------------------ +*/ +static int K3 = 1; +static double basym,bsum,dsum,f,h,h2,hn,j0,j1,r,r0,r1,s,sum,t,t0,t1,u,w,w0,z,z0, + z2,zn,znm1; +static int i,im1,imj,j,m,mm1,mmj,n,np1; +static double a0[21],b0[21],c[21],d[21],T1,T2; +/* + .. + .. Executable Statements .. +*/ + basym = 0.0e0; + if(*a >= *b) goto S10; + h = *a/ *b; + r0 = 1.0e0/(1.0e0+h); + r1 = (*b-*a)/ *b; + w0 = 1.0e0/sqrt(*a*(1.0e0+h)); + goto S20; +S10: + h = *b/ *a; + r0 = 1.0e0/(1.0e0+h); + r1 = (*b-*a)/ *a; + w0 = 1.0e0/sqrt(*b*(1.0e0+h)); +S20: + T1 = -(*lambda/ *a); + T2 = *lambda/ *b; + f = *a*rlog1(&T1)+*b*rlog1(&T2); + t = exp(-f); + if(t == 0.0e0) return basym; + z0 = sqrt(f); + z = 0.5e0*(z0/e1); + z2 = f+f; + a0[0] = 2.0e0/3.0e0*r1; + c[0] = -(0.5e0*a0[0]); + d[0] = -c[0]; + j0 = 0.5e0/e0*erfc1(&K3,&z0); + j1 = e1; + sum = j0+d[0]*w0*j1; + s = 1.0e0; + h2 = h*h; + hn = 1.0e0; + w = w0; + znm1 = z; + zn = z2; + for(n=2; n<=num; n+=2) { + hn = h2*hn; + a0[n-1] = 2.0e0*r0*(1.0e0+h*hn)/((double)n+2.0e0); + np1 = n+1; + s += hn; + a0[np1-1] = 2.0e0*r1*s/((double)n+3.0e0); + for(i=n; i<=np1; i++) { + r = -(0.5e0*((double)i+1.0e0)); + b0[0] = r*a0[0]; + for(m=2; m<=i; m++) { + bsum = 0.0e0; + mm1 = m-1; + for(j=1; j<=mm1; j++) { + mmj = m-j; + bsum += (((double)j*r-(double)mmj)*a0[j-1]*b0[mmj-1]); + } + b0[m-1] = r*a0[m-1]+bsum/(double)m; + } + c[i-1] = b0[i-1]/((double)i+1.0e0); + dsum = 0.0e0; + im1 = i-1; + for(j=1; j<=im1; j++) { + imj = i-j; + dsum += (d[imj-1]*c[j-1]); + } + d[i-1] = -(dsum+c[i-1]); + } + j0 = e1*znm1+((double)n-1.0e0)*j0; + j1 = e1*zn+(double)n*j1; + znm1 = z2*znm1; + zn = z2*zn; + w = w0*w; + t0 = d[n-1]*w*j0; + w = w0*w; + t1 = d[np1-1]*w*j1; + sum += (t0+t1); + if(fabs(t0)+fabs(t1) <= *eps*sum) goto S80; + } +S80: + u = exp(-bcorr(a,b)); + basym = e0*t*u*sum; + return basym; +} /* END */ + +/***=====================================================================***/ +static double bcorr(double *a0,double *b0) +/* +----------------------------------------------------------------------- + + EVALUATION OF DEL(A0) + DEL(B0) - DEL(A0 + B0) WHERE + LN(GAMMA(A)) = (A - 0.5)*LN(A) - A + 0.5*LN(2*PI) + DEL(A). + IT IS ASSUMED THAT A0 .GE. 8 AND B0 .GE. 8. + +----------------------------------------------------------------------- +*/ +{ +static double c0 = .833333333333333e-01; +static double c1 = -.277777777760991e-02; +static double c2 = .793650666825390e-03; +static double c3 = -.595202931351870e-03; +static double c4 = .837308034031215e-03; +static double c5 = -.165322962780713e-02; +static double bcorr,a,b,c,h,s11,s3,s5,s7,s9,t,w,x,x2; +/* + .. + .. Executable Statements .. +*/ + a = fifdmin1(*a0,*b0); + b = fifdmax1(*a0,*b0); + h = a/b; + c = h/(1.0e0+h); + x = 1.0e0/(1.0e0+h); + x2 = x*x; +/* + SET SN = (1 - X**N)/(1 - X) +*/ + s3 = 1.0e0+(x+x2); + s5 = 1.0e0+(x+x2*s3); + s7 = 1.0e0+(x+x2*s5); + s9 = 1.0e0+(x+x2*s7); + s11 = 1.0e0+(x+x2*s9); +/* + SET W = DEL(B) - DEL(A + B) +*/ + t = pow(1.0e0/b,2.0); + w = ((((c5*s11*t+c4*s9)*t+c3*s7)*t+c2*s5)*t+c1*s3)*t+c0; + w *= (c/b); +/* + COMPUTE DEL(A) + W +*/ + t = pow(1.0e0/a,2.0); + bcorr = (((((c5*t+c4)*t+c3)*t+c2)*t+c1)*t+c0)/a+w; + return bcorr; +} /* END */ + +/***=====================================================================***/ +static double betaln(double *a0,double *b0) +/* +----------------------------------------------------------------------- + EVALUATION OF THE LOGARITHM OF THE BETA FUNCTION +----------------------------------------------------------------------- + E = 0.5*LN(2*PI) +-------------------------- +*/ +{ +static double e = .918938533204673e0; +static double betaln,a,b,c,h,u,v,w,z; +static int i,n; +static double T1; +/* + .. + .. Executable Statements .. +*/ + a = fifdmin1(*a0,*b0); + b = fifdmax1(*a0,*b0); + if(a >= 8.0e0) goto S100; + if(a >= 1.0e0) goto S20; +/* +----------------------------------------------------------------------- + PROCEDURE WHEN A .LT. 1 +----------------------------------------------------------------------- +*/ + if(b >= 8.0e0) goto S10; + T1 = a+b; + betaln = gamln(&a)+(gamln(&b)-gamln(&T1)); + return betaln; +S10: + betaln = gamln(&a)+algdiv(&a,&b); + return betaln; +S20: +/* +----------------------------------------------------------------------- + PROCEDURE WHEN 1 .LE. A .LT. 8 +----------------------------------------------------------------------- +*/ + if(a > 2.0e0) goto S40; + if(b > 2.0e0) goto S30; + betaln = gamln(&a)+gamln(&b)-gsumln(&a,&b); + return betaln; +S30: + w = 0.0e0; + if(b < 8.0e0) goto S60; + betaln = gamln(&a)+algdiv(&a,&b); + return betaln; +S40: +/* + REDUCTION OF A WHEN B .LE. 1000 +*/ + if(b > 1000.0e0) goto S80; + n = a-1.0e0; + w = 1.0e0; + for(i=1; i<=n; i++) { + a -= 1.0e0; + h = a/b; + w *= (h/(1.0e0+h)); + } + w = log(w); + if(b < 8.0e0) goto S60; + betaln = w+gamln(&a)+algdiv(&a,&b); + return betaln; +S60: +/* + REDUCTION OF B WHEN B .LT. 8 +*/ + n = b-1.0e0; + z = 1.0e0; + for(i=1; i<=n; i++) { + b -= 1.0e0; + z *= (b/(a+b)); + } + betaln = w+log(z)+(gamln(&a)+(gamln(&b)-gsumln(&a,&b))); + return betaln; +S80: +/* + REDUCTION OF A WHEN B .GT. 1000 +*/ + n = a-1.0e0; + w = 1.0e0; + for(i=1; i<=n; i++) { + a -= 1.0e0; + w *= (a/(1.0e0+a/b)); + } + betaln = log(w)-(double)n*log(b)+(gamln(&a)+algdiv(&a,&b)); + return betaln; +S100: +/* +----------------------------------------------------------------------- + PROCEDURE WHEN A .GE. 8 +----------------------------------------------------------------------- +*/ + w = bcorr(&a,&b); + h = a/b; + c = h/(1.0e0+h); + u = -((a-0.5e0)*log(c)); + v = b*alnrel(&h); + if(u <= v) goto S110; + betaln = -(0.5e0*log(b))+e+w-v-u; + return betaln; +S110: + betaln = -(0.5e0*log(b))+e+w-u-v; + return betaln; +} /* END */ + +/***=====================================================================***/ +static double bfrac(double *a,double *b,double *x,double *y,double *lambda, + double *eps) +/* +----------------------------------------------------------------------- + CONTINUED FRACTION EXPANSION FOR IX(A,B) WHEN A,B .GT. 1. + IT IS ASSUMED THAT LAMBDA = (A + B)*Y - B. +----------------------------------------------------------------------- +*/ +{ +static double bfrac,alpha,an,anp1,beta,bn,bnp1,c,c0,c1,e,n,p,r,r0,s,t,w,yp1; +/* + .. + .. Executable Statements .. +*/ + bfrac = brcomp(a,b,x,y); + if(bfrac == 0.0e0) return bfrac; + c = 1.0e0+*lambda; + c0 = *b/ *a; + c1 = 1.0e0+1.0e0/ *a; + yp1 = *y+1.0e0; + n = 0.0e0; + p = 1.0e0; + s = *a+1.0e0; + an = 0.0e0; + bn = anp1 = 1.0e0; + bnp1 = c/c1; + r = c1/c; +S10: +/* + CONTINUED FRACTION CALCULATION +*/ + n += 1.0e0; + t = n/ *a; + w = n*(*b-n)**x; + e = *a/s; + alpha = p*(p+c0)*e*e*(w**x); + e = (1.0e0+t)/(c1+t+t); + beta = n+w/s+e*(c+n*yp1); + p = 1.0e0+t; + s += 2.0e0; +/* + UPDATE AN, BN, ANP1, AND BNP1 +*/ + t = alpha*an+beta*anp1; + an = anp1; + anp1 = t; + t = alpha*bn+beta*bnp1; + bn = bnp1; + bnp1 = t; + r0 = r; + r = anp1/bnp1; + if(fabs(r-r0) <= *eps*r) goto S20; +/* + RESCALE AN, BN, ANP1, AND BNP1 +*/ + an /= bnp1; + bn /= bnp1; + anp1 = r; + bnp1 = 1.0e0; + goto S10; +S20: +/* + TERMINATION +*/ + bfrac *= r; + return bfrac; +} /* END */ + +/***=====================================================================***/ +static void bgrat(double *a,double *b,double *x,double *y,double *w, + double *eps,int *ierr) +/* +----------------------------------------------------------------------- + ASYMPTOTIC EXPANSION FOR IX(A,B) WHEN A IS LARGER THAN B. + THE RESULT OF THE EXPANSION IS ADDED TO W. IT IS ASSUMED + THAT A .GE. 15 AND B .LE. 1. EPS IS THE TOLERANCE USED. + IERR IS A VARIABLE THAT REPORTS THE STATUS OF THE RESULTS. +----------------------------------------------------------------------- +*/ +{ +static double bm1,bp2n,cn,coef,dj,j,l,lnx,n2,nu,p,q,r,s,sum,t,t2,u,v,z; +static int i,n,nm1; +static double c[30],d[30],T1; +/* + .. + .. Executable Statements .. +*/ + bm1 = *b-0.5e0-0.5e0; + nu = *a+0.5e0*bm1; + if(*y > 0.375e0) goto S10; + T1 = -*y; + lnx = alnrel(&T1); + goto S20; +S10: + lnx = log(*x); +S20: + z = -(nu*lnx); + if(*b*z == 0.0e0) goto S70; +/* + COMPUTATION OF THE EXPANSION + SET R = EXP(-Z)*Z**B/GAMMA(B) +*/ + r = *b*(1.0e0+gam1(b))*exp(*b*log(z)); + r *= (exp(*a*lnx)*exp(0.5e0*bm1*lnx)); + u = algdiv(b,a)+*b*log(nu); + u = r*exp(-u); + if(u == 0.0e0) goto S70; + grat1(b,&z,&r,&p,&q,eps); + v = 0.25e0*pow(1.0e0/nu,2.0); + t2 = 0.25e0*lnx*lnx; + l = *w/u; + j = q/r; + sum = j; + t = cn = 1.0e0; + n2 = 0.0e0; + for(n=1; n<=30; n++) { + bp2n = *b+n2; + j = (bp2n*(bp2n+1.0e0)*j+(z+bp2n+1.0e0)*t)*v; + n2 += 2.0e0; + t *= t2; + cn /= (n2*(n2+1.0e0)); + c[n-1] = cn; + s = 0.0e0; + if(n == 1) goto S40; + nm1 = n-1; + coef = *b-(double)n; + for(i=1; i<=nm1; i++) { + s += (coef*c[i-1]*d[n-i-1]); + coef += *b; + } +S40: + d[n-1] = bm1*cn+s/(double)n; + dj = d[n-1]*j; + sum += dj; + if(sum <= 0.0e0) goto S70; + if(fabs(dj) <= *eps*(sum+l)) goto S60; + } +S60: +/* + ADD THE RESULTS TO W +*/ + *ierr = 0; + *w += (u*sum); + return; +S70: +/* + THE EXPANSION CANNOT BE COMPUTED +*/ + *ierr = 1; + return; +} /* END */ + +/***=====================================================================***/ +static double bpser(double *a,double *b,double *x,double *eps) +/* +----------------------------------------------------------------------- + POWER SERIES EXPANSION FOR EVALUATING IX(A,B) WHEN B .LE. 1 + OR B*X .LE. 0.7. EPS IS THE TOLERANCE USED. +----------------------------------------------------------------------- +*/ +{ +static double bpser,a0,apb,b0,c,n,sum,t,tol,u,w,z; +static int i,m; +/* + .. + .. Executable Statements .. +*/ + bpser = 0.0e0; + if(*x == 0.0e0) return bpser; +/* +----------------------------------------------------------------------- + COMPUTE THE FACTOR X**A/(A*BETA(A,B)) +----------------------------------------------------------------------- +*/ + a0 = fifdmin1(*a,*b); + if(a0 < 1.0e0) goto S10; + z = *a*log(*x)-betaln(a,b); + bpser = exp(z)/ *a; + goto S100; +S10: + b0 = fifdmax1(*a,*b); + if(b0 >= 8.0e0) goto S90; + if(b0 > 1.0e0) goto S40; +/* + PROCEDURE FOR A0 .LT. 1 AND B0 .LE. 1 +*/ + bpser = pow(*x,*a); + if(bpser == 0.0e0) return bpser; + apb = *a+*b; + if(apb > 1.0e0) goto S20; + z = 1.0e0+gam1(&apb); + goto S30; +S20: + u = *a+*b-1.e0; + z = (1.0e0+gam1(&u))/apb; +S30: + c = (1.0e0+gam1(a))*(1.0e0+gam1(b))/z; + bpser *= (c*(*b/apb)); + goto S100; +S40: +/* + PROCEDURE FOR A0 .LT. 1 AND 1 .LT. B0 .LT. 8 +*/ + u = gamln1(&a0); + m = b0-1.0e0; + if(m < 1) goto S60; + c = 1.0e0; + for(i=1; i<=m; i++) { + b0 -= 1.0e0; + c *= (b0/(a0+b0)); + } + u = log(c)+u; +S60: + z = *a*log(*x)-u; + b0 -= 1.0e0; + apb = a0+b0; + if(apb > 1.0e0) goto S70; + t = 1.0e0+gam1(&apb); + goto S80; +S70: + u = a0+b0-1.e0; + t = (1.0e0+gam1(&u))/apb; +S80: + bpser = exp(z)*(a0/ *a)*(1.0e0+gam1(&b0))/t; + goto S100; +S90: +/* + PROCEDURE FOR A0 .LT. 1 AND B0 .GE. 8 +*/ + u = gamln1(&a0)+algdiv(&a0,&b0); + z = *a*log(*x)-u; + bpser = a0/ *a*exp(z); +S100: + if(bpser == 0.0e0 || *a <= 0.1e0**eps) return bpser; +/* +----------------------------------------------------------------------- + COMPUTE THE SERIES +----------------------------------------------------------------------- +*/ + sum = n = 0.0e0; + c = 1.0e0; + tol = *eps/ *a; +S110: + n += 1.0e0; + c *= ((0.5e0+(0.5e0-*b/n))**x); + w = c/(*a+n); + sum += w; + if(fabs(w) > tol) goto S110; + bpser *= (1.0e0+*a*sum); + return bpser; +} /* END */ + +/***=====================================================================***/ +static void bratio(double *a,double *b,double *x,double *y,double *w, + double *w1,int *ierr) +/* +----------------------------------------------------------------------- + + EVALUATION OF THE INCOMPLETE BETA FUNCTION IX(A,B) + + -------------------- + + IT IS ASSUMED THAT A AND B ARE NONNEGATIVE, AND THAT X .LE. 1 + AND Y = 1 - X. BRATIO ASSIGNS W AND W1 THE VALUES + + W = IX(A,B) + W1 = 1 - IX(A,B) + + IERR IS A VARIABLE THAT REPORTS THE STATUS OF THE RESULTS. + IF NO INPUT ERRORS ARE DETECTED THEN IERR IS SET TO 0 AND + W AND W1 ARE COMPUTED. OTHERWISE, IF AN ERROR IS DETECTED, + THEN W AND W1 ARE ASSIGNED THE VALUE 0 AND IERR IS SET TO + ONE OF THE FOLLOWING VALUES ... + + IERR = 1 IF A OR B IS NEGATIVE + IERR = 2 IF A = B = 0 + IERR = 3 IF X .LT. 0 OR X .GT. 1 + IERR = 4 IF Y .LT. 0 OR Y .GT. 1 + IERR = 5 IF X + Y .NE. 1 + IERR = 6 IF X = A = 0 + IERR = 7 IF Y = B = 0 + +-------------------- + WRITTEN BY ALFRED H. MORRIS, JR. + NAVAL SURFACE WARFARE CENTER + DAHLGREN, VIRGINIA + REVISED ... NOV 1991 +----------------------------------------------------------------------- +*/ +{ +static int K1 = 1; +static double a0,b0,eps,lambda,t,x0,y0,z; +static int ierr1,ind,n; +static double T2,T3,T4,T5; +/* + .. + .. Executable Statements .. +*/ +/* + ****** EPS IS A MACHINE DEPENDENT CONSTANT. EPS IS THE SMALLEST + FLOATING POINT NUMBER FOR WHICH 1.0 + EPS .GT. 1.0 +*/ + eps = spmpar(&K1); + *w = *w1 = 0.0e0; + if(*a < 0.0e0 || *b < 0.0e0) goto S270; + if(*a == 0.0e0 && *b == 0.0e0) goto S280; + if(*x < 0.0e0 || *x > 1.0e0) goto S290; + if(*y < 0.0e0 || *y > 1.0e0) goto S300; + z = *x+*y-0.5e0-0.5e0; + if(fabs(z) > 3.0e0*eps) goto S310; + *ierr = 0; + if(*x == 0.0e0) goto S210; + if(*y == 0.0e0) goto S230; + if(*a == 0.0e0) goto S240; + if(*b == 0.0e0) goto S220; + eps = fifdmax1(eps,1.e-15); + if(fifdmax1(*a,*b) < 1.e-3*eps) goto S260; + ind = 0; + a0 = *a; + b0 = *b; + x0 = *x; + y0 = *y; + if(fifdmin1(a0,b0) > 1.0e0) goto S40; +/* + PROCEDURE FOR A0 .LE. 1 OR B0 .LE. 1 +*/ + if(*x <= 0.5e0) goto S10; + ind = 1; + a0 = *b; + b0 = *a; + x0 = *y; + y0 = *x; +S10: + if(b0 < fifdmin1(eps,eps*a0)) goto S90; + if(a0 < fifdmin1(eps,eps*b0) && b0*x0 <= 1.0e0) goto S100; + if(fifdmax1(a0,b0) > 1.0e0) goto S20; + if(a0 >= fifdmin1(0.2e0,b0)) goto S110; + if(pow(x0,a0) <= 0.9e0) goto S110; + if(x0 >= 0.3e0) goto S120; + n = 20; + goto S140; +S20: + if(b0 <= 1.0e0) goto S110; + if(x0 >= 0.3e0) goto S120; + if(x0 >= 0.1e0) goto S30; + if(pow(x0*b0,a0) <= 0.7e0) goto S110; +S30: + if(b0 > 15.0e0) goto S150; + n = 20; + goto S140; +S40: +/* + PROCEDURE FOR A0 .GT. 1 AND B0 .GT. 1 +*/ + if(*a > *b) goto S50; + lambda = *a-(*a+*b)**x; + goto S60; +S50: + lambda = (*a+*b)**y-*b; +S60: + if(lambda >= 0.0e0) goto S70; + ind = 1; + a0 = *b; + b0 = *a; + x0 = *y; + y0 = *x; + lambda = fabs(lambda); +S70: + if(b0 < 40.0e0 && b0*x0 <= 0.7e0) goto S110; + if(b0 < 40.0e0) goto S160; + if(a0 > b0) goto S80; + if(a0 <= 100.0e0) goto S130; + if(lambda > 0.03e0*a0) goto S130; + goto S200; +S80: + if(b0 <= 100.0e0) goto S130; + if(lambda > 0.03e0*b0) goto S130; + goto S200; +S90: +/* + EVALUATION OF THE APPROPRIATE ALGORITHM +*/ + *w = fpser(&a0,&b0,&x0,&eps); + *w1 = 0.5e0+(0.5e0-*w); + goto S250; +S100: + *w1 = apser(&a0,&b0,&x0,&eps); + *w = 0.5e0+(0.5e0-*w1); + goto S250; +S110: + *w = bpser(&a0,&b0,&x0,&eps); + *w1 = 0.5e0+(0.5e0-*w); + goto S250; +S120: + *w1 = bpser(&b0,&a0,&y0,&eps); + *w = 0.5e0+(0.5e0-*w1); + goto S250; +S130: + T2 = 15.0e0*eps; + *w = bfrac(&a0,&b0,&x0,&y0,&lambda,&T2); + *w1 = 0.5e0+(0.5e0-*w); + goto S250; +S140: + *w1 = bup(&b0,&a0,&y0,&x0,&n,&eps); + b0 += (double)n; +S150: + T3 = 15.0e0*eps; + bgrat(&b0,&a0,&y0,&x0,w1,&T3,&ierr1); + *w = 0.5e0+(0.5e0-*w1); + goto S250; +S160: + n = b0; + b0 -= (double)n; + if(b0 != 0.0e0) goto S170; + n -= 1; + b0 = 1.0e0; +S170: + *w = bup(&b0,&a0,&y0,&x0,&n,&eps); + if(x0 > 0.7e0) goto S180; + *w += bpser(&a0,&b0,&x0,&eps); + *w1 = 0.5e0+(0.5e0-*w); + goto S250; +S180: + if(a0 > 15.0e0) goto S190; + n = 20; + *w += bup(&a0,&b0,&x0,&y0,&n,&eps); + a0 += (double)n; +S190: + T4 = 15.0e0*eps; + bgrat(&a0,&b0,&x0,&y0,w,&T4,&ierr1); + *w1 = 0.5e0+(0.5e0-*w); + goto S250; +S200: + T5 = 100.0e0*eps; + *w = basym(&a0,&b0,&lambda,&T5); + *w1 = 0.5e0+(0.5e0-*w); + goto S250; +S210: +/* + TERMINATION OF THE PROCEDURE +*/ + if(*a == 0.0e0) goto S320; +S220: + *w = 0.0e0; + *w1 = 1.0e0; + return; +S230: + if(*b == 0.0e0) goto S330; +S240: + *w = 1.0e0; + *w1 = 0.0e0; + return; +S250: + if(ind == 0) return; + t = *w; + *w = *w1; + *w1 = t; + return; +S260: +/* + PROCEDURE FOR A AND B .LT. 1.E-3*EPS +*/ + *w = *b/(*a+*b); + *w1 = *a/(*a+*b); + return; +S270: +/* + ERROR RETURN +*/ + *ierr = 1; + return; +S280: + *ierr = 2; + return; +S290: + *ierr = 3; + return; +S300: + *ierr = 4; + return; +S310: + *ierr = 5; + return; +S320: + *ierr = 6; + return; +S330: + *ierr = 7; + return; +} /* END */ + +/***=====================================================================***/ +static double brcmp1(int *mu,double *a,double *b,double *x,double *y) +/* +----------------------------------------------------------------------- + EVALUATION OF EXP(MU) * (X**A*Y**B/BETA(A,B)) +----------------------------------------------------------------------- +*/ +{ +static double Const = .398942280401433e0; +static double brcmp1,a0,apb,b0,c,e,h,lambda,lnx,lny,t,u,v,x0,y0,z; +static int i,n; +/* +----------------- + CONST = 1/SQRT(2*PI) +----------------- +*/ +static double T1,T2,T3,T4; +/* + .. + .. Executable Statements .. +*/ + a0 = fifdmin1(*a,*b); + if(a0 >= 8.0e0) goto S130; + if(*x > 0.375e0) goto S10; + lnx = log(*x); + T1 = -*x; + lny = alnrel(&T1); + goto S30; +S10: + if(*y > 0.375e0) goto S20; + T2 = -*y; + lnx = alnrel(&T2); + lny = log(*y); + goto S30; +S20: + lnx = log(*x); + lny = log(*y); +S30: + z = *a*lnx+*b*lny; + if(a0 < 1.0e0) goto S40; + z -= betaln(a,b); + brcmp1 = esum(mu,&z); + return brcmp1; +S40: +/* +----------------------------------------------------------------------- + PROCEDURE FOR A .LT. 1 OR B .LT. 1 +----------------------------------------------------------------------- +*/ + b0 = fifdmax1(*a,*b); + if(b0 >= 8.0e0) goto S120; + if(b0 > 1.0e0) goto S70; +/* + ALGORITHM FOR B0 .LE. 1 +*/ + brcmp1 = esum(mu,&z); + if(brcmp1 == 0.0e0) return brcmp1; + apb = *a+*b; + if(apb > 1.0e0) goto S50; + z = 1.0e0+gam1(&apb); + goto S60; +S50: + u = *a+*b-1.e0; + z = (1.0e0+gam1(&u))/apb; +S60: + c = (1.0e0+gam1(a))*(1.0e0+gam1(b))/z; + brcmp1 = brcmp1*(a0*c)/(1.0e0+a0/b0); + return brcmp1; +S70: +/* + ALGORITHM FOR 1 .LT. B0 .LT. 8 +*/ + u = gamln1(&a0); + n = b0-1.0e0; + if(n < 1) goto S90; + c = 1.0e0; + for(i=1; i<=n; i++) { + b0 -= 1.0e0; + c *= (b0/(a0+b0)); + } + u = log(c)+u; +S90: + z -= u; + b0 -= 1.0e0; + apb = a0+b0; + if(apb > 1.0e0) goto S100; + t = 1.0e0+gam1(&apb); + goto S110; +S100: + u = a0+b0-1.e0; + t = (1.0e0+gam1(&u))/apb; +S110: + brcmp1 = a0*esum(mu,&z)*(1.0e0+gam1(&b0))/t; + return brcmp1; +S120: +/* + ALGORITHM FOR B0 .GE. 8 +*/ + u = gamln1(&a0)+algdiv(&a0,&b0); + T3 = z-u; + brcmp1 = a0*esum(mu,&T3); + return brcmp1; +S130: +/* +----------------------------------------------------------------------- + PROCEDURE FOR A .GE. 8 AND B .GE. 8 +----------------------------------------------------------------------- +*/ + if(*a > *b) goto S140; + h = *a/ *b; + x0 = h/(1.0e0+h); + y0 = 1.0e0/(1.0e0+h); + lambda = *a-(*a+*b)**x; + goto S150; +S140: + h = *b/ *a; + x0 = 1.0e0/(1.0e0+h); + y0 = h/(1.0e0+h); + lambda = (*a+*b)**y-*b; +S150: + e = -(lambda/ *a); + if(fabs(e) > 0.6e0) goto S160; + u = rlog1(&e); + goto S170; +S160: + u = e-log(*x/x0); +S170: + e = lambda/ *b; + if(fabs(e) > 0.6e0) goto S180; + v = rlog1(&e); + goto S190; +S180: + v = e-log(*y/y0); +S190: + T4 = -(*a*u+*b*v); + z = esum(mu,&T4); + brcmp1 = Const*sqrt(*b*x0)*z*exp(-bcorr(a,b)); + return brcmp1; +} /* END */ + +/***=====================================================================***/ +static double brcomp(double *a,double *b,double *x,double *y) +/* +----------------------------------------------------------------------- + EVALUATION OF X**A*Y**B/BETA(A,B) +----------------------------------------------------------------------- +*/ +{ +static double Const = .398942280401433e0; +static double brcomp,a0,apb,b0,c,e,h,lambda,lnx,lny,t,u,v,x0,y0,z; +static int i,n; +/* +----------------- + CONST = 1/SQRT(2*PI) +----------------- +*/ +static double T1,T2; +/* + .. + .. Executable Statements .. +*/ + brcomp = 0.0e0; + if(*x == 0.0e0 || *y == 0.0e0) return brcomp; + a0 = fifdmin1(*a,*b); + if(a0 >= 8.0e0) goto S130; + if(*x > 0.375e0) goto S10; + lnx = log(*x); + T1 = -*x; + lny = alnrel(&T1); + goto S30; +S10: + if(*y > 0.375e0) goto S20; + T2 = -*y; + lnx = alnrel(&T2); + lny = log(*y); + goto S30; +S20: + lnx = log(*x); + lny = log(*y); +S30: + z = *a*lnx+*b*lny; + if(a0 < 1.0e0) goto S40; + z -= betaln(a,b); + brcomp = exp(z); + return brcomp; +S40: +/* +----------------------------------------------------------------------- + PROCEDURE FOR A .LT. 1 OR B .LT. 1 +----------------------------------------------------------------------- +*/ + b0 = fifdmax1(*a,*b); + if(b0 >= 8.0e0) goto S120; + if(b0 > 1.0e0) goto S70; +/* + ALGORITHM FOR B0 .LE. 1 +*/ + brcomp = exp(z); + if(brcomp == 0.0e0) return brcomp; + apb = *a+*b; + if(apb > 1.0e0) goto S50; + z = 1.0e0+gam1(&apb); + goto S60; +S50: + u = *a+*b-1.e0; + z = (1.0e0+gam1(&u))/apb; +S60: + c = (1.0e0+gam1(a))*(1.0e0+gam1(b))/z; + brcomp = brcomp*(a0*c)/(1.0e0+a0/b0); + return brcomp; +S70: +/* + ALGORITHM FOR 1 .LT. B0 .LT. 8 +*/ + u = gamln1(&a0); + n = b0-1.0e0; + if(n < 1) goto S90; + c = 1.0e0; + for(i=1; i<=n; i++) { + b0 -= 1.0e0; + c *= (b0/(a0+b0)); + } + u = log(c)+u; +S90: + z -= u; + b0 -= 1.0e0; + apb = a0+b0; + if(apb > 1.0e0) goto S100; + t = 1.0e0+gam1(&apb); + goto S110; +S100: + u = a0+b0-1.e0; + t = (1.0e0+gam1(&u))/apb; +S110: + brcomp = a0*exp(z)*(1.0e0+gam1(&b0))/t; + return brcomp; +S120: +/* + ALGORITHM FOR B0 .GE. 8 +*/ + u = gamln1(&a0)+algdiv(&a0,&b0); + brcomp = a0*exp(z-u); + return brcomp; +S130: +/* +----------------------------------------------------------------------- + PROCEDURE FOR A .GE. 8 AND B .GE. 8 +----------------------------------------------------------------------- +*/ + if(*a > *b) goto S140; + h = *a/ *b; + x0 = h/(1.0e0+h); + y0 = 1.0e0/(1.0e0+h); + lambda = *a-(*a+*b)**x; + goto S150; +S140: + h = *b/ *a; + x0 = 1.0e0/(1.0e0+h); + y0 = h/(1.0e0+h); + lambda = (*a+*b)**y-*b; +S150: + e = -(lambda/ *a); + if(fabs(e) > 0.6e0) goto S160; + u = rlog1(&e); + goto S170; +S160: + u = e-log(*x/x0); +S170: + e = lambda/ *b; + if(fabs(e) > 0.6e0) goto S180; + v = rlog1(&e); + goto S190; +S180: + v = e-log(*y/y0); +S190: + z = exp(-(*a*u+*b*v)); + brcomp = Const*sqrt(*b*x0)*z*exp(-bcorr(a,b)); + return brcomp; +} /* END */ + +/***=====================================================================***/ +static double bup(double *a,double *b,double *x,double *y,int *n,double *eps) +/* +----------------------------------------------------------------------- + EVALUATION OF IX(A,B) - IX(A+N,B) WHERE N IS A POSITIVE INTEGER. + EPS IS THE TOLERANCE USED. +----------------------------------------------------------------------- +*/ +{ +static int K1 = 1; +static int K2 = 0; +static double bup,ap1,apb,d,l,r,t,w; +static int i,k,kp1,mu,nm1; +/* + .. + .. Executable Statements .. +*/ +/* + OBTAIN THE SCALING FACTOR EXP(-MU) AND + EXP(MU)*(X**A*Y**B/BETA(A,B))/A +*/ + apb = *a+*b; + ap1 = *a+1.0e0; + mu = 0; + d = 1.0e0; + if(*n == 1 || *a < 1.0e0) goto S10; + if(apb < 1.1e0*ap1) goto S10; + mu = fabs(exparg(&K1)); + k = exparg(&K2); + if(k < mu) mu = k; + t = mu; + d = exp(-t); +S10: + bup = brcmp1(&mu,a,b,x,y)/ *a; + if(*n == 1 || bup == 0.0e0) return bup; + nm1 = *n-1; + w = d; +/* + LET K BE THE INDEX OF THE MAXIMUM TERM +*/ + k = 0; + if(*b <= 1.0e0) goto S50; + if(*y > 1.e-4) goto S20; + k = nm1; + goto S30; +S20: + r = (*b-1.0e0)**x/ *y-*a; + if(r < 1.0e0) goto S50; + k = t = nm1; + if(r < t) k = r; +S30: +/* + ADD THE INCREASING TERMS OF THE SERIES +*/ + for(i=1; i<=k; i++) { + l = i-1; + d = (apb+l)/(ap1+l)**x*d; + w += d; + } + if(k == nm1) goto S70; +S50: +/* + ADD THE REMAINING TERMS OF THE SERIES +*/ + kp1 = k+1; + for(i=kp1; i<=nm1; i++) { + l = i-1; + d = (apb+l)/(ap1+l)**x*d; + w += d; + if(d <= *eps*w) goto S70; + } +S70: +/* + TERMINATE THE PROCEDURE +*/ + bup *= w; + return bup; +} /* END */ + +/***=====================================================================***/ +static void cdfbet(int *which,double *p,double *q,double *x,double *y, + double *a,double *b,int *status,double *bound) +/********************************************************************** + + void cdfbet(int *which,double *p,double *q,double *x,double *y, + double *a,double *b,int *status,double *bound) + + Cumulative Distribution Function + BETa Distribution + + + Function + + + Calculates any one parameter of the beta distribution given + values for the others. + + + Arguments + + + WHICH --> Integer indicating which of the next four argument + values is to be calculated from the others. + Legal range: 1..4 + iwhich = 1 : Calculate P and Q from X,Y,A and B + iwhich = 2 : Calculate X and Y from P,Q,A and B + iwhich = 3 : Calculate A from P,Q,X,Y and B + iwhich = 4 : Calculate B from P,Q,X,Y and A + + P <--> The integral from 0 to X of the chi-square + distribution. + Input range: [0, 1]. + + Q <--> 1-P. + Input range: [0, 1]. + P + Q = 1.0. + + X <--> Upper limit of integration of beta density. + Input range: [0,1]. + Search range: [0,1] + + Y <--> 1-X. + Input range: [0,1]. + Search range: [0,1] + X + Y = 1.0. + + A <--> The first parameter of the beta density. + Input range: (0, +infinity). + Search range: [1D-300,1D300] + + B <--> The second parameter of the beta density. + Input range: (0, +infinity). + Search range: [1D-300,1D300] + + STATUS <-- 0 if calculation completed correctly + -I if input parameter number I is out of range + 1 if answer appears to be lower than lowest + search bound + 2 if answer appears to be higher than greatest + search bound + 3 if P + Q .ne. 1 + 4 if X + Y .ne. 1 + + BOUND <-- Undefined if STATUS is 0 + + Bound exceeded by parameter number I if STATUS + is negative. + + Lower search bound if STATUS is 1. + + Upper search bound if STATUS is 2. + + + Method + + + Cumulative distribution function (P) is calculated directly by + code associated with the following reference. + + DiDinato, A. R. and Morris, A. H. Algorithm 708: Significant + Digit Computation of the Incomplete Beta Function Ratios. ACM + Trans. Math. Softw. 18 (1993), 360-373. + + Computation of other parameters involve a seach for a value that + produces the desired value of P. The search relies on the + monotinicity of P with the other parameter. + + + Note + + + The beta density is proportional to + t^(A-1) * (1-t)^(B-1) + +**********************************************************************/ +{ +#define tol (1.0e-8) +#define atol (1.0e-50) +#define zero (1.0e-300) +#define inf 1.0e300 +#define one 1.0e0 +static int K1 = 1; +static double K2 = 0.0e0; +static double K3 = 1.0e0; +static double K8 = 0.5e0; +static double K9 = 5.0e0; +static double fx,xhi,xlo,cum,ccum,xy,pq; +static unsigned long qhi,qleft,qporq; +static double T4,T5,T6,T7,T10,T11,T12,T13,T14,T15; +/* + .. + .. Executable Statements .. +*/ +/* + Check arguments +*/ + if(!(*which < 1 || *which > 4)) goto S30; + if(!(*which < 1)) goto S10; + *bound = 1.0e0; + goto S20; +S10: + *bound = 4.0e0; +S20: + *status = -1; + return; +S30: + if(*which == 1) goto S70; +/* + P +*/ + if(!(*p < 0.0e0 || *p > 1.0e0)) goto S60; + if(!(*p < 0.0e0)) goto S40; + *bound = 0.0e0; + goto S50; +S40: + *bound = 1.0e0; +S50: + *status = -2; + return; +S70: +S60: + if(*which == 1) goto S110; +/* + Q +*/ + if(!(*q < 0.0e0 || *q > 1.0e0)) goto S100; + if(!(*q < 0.0e0)) goto S80; + *bound = 0.0e0; + goto S90; +S80: + *bound = 1.0e0; +S90: + *status = -3; + return; +S110: +S100: + if(*which == 2) goto S150; +/* + X +*/ + if(!(*x < 0.0e0 || *x > 1.0e0)) goto S140; + if(!(*x < 0.0e0)) goto S120; + *bound = 0.0e0; + goto S130; +S120: + *bound = 1.0e0; +S130: + *status = -4; + return; +S150: +S140: + if(*which == 2) goto S190; +/* + Y +*/ + if(!(*y < 0.0e0 || *y > 1.0e0)) goto S180; + if(!(*y < 0.0e0)) goto S160; + *bound = 0.0e0; + goto S170; +S160: + *bound = 1.0e0; +S170: + *status = -5; + return; +S190: +S180: + if(*which == 3) goto S210; +/* + A +*/ + if(!(*a <= 0.0e0)) goto S200; + *bound = 0.0e0; + *status = -6; + return; +S210: +S200: + if(*which == 4) goto S230; +/* + B +*/ + if(!(*b <= 0.0e0)) goto S220; + *bound = 0.0e0; + *status = -7; + return; +S230: +S220: + if(*which == 1) goto S270; +/* + P + Q +*/ + pq = *p+*q; + if(!(fabs(pq-0.5e0-0.5e0) > 3.0e0*spmpar(&K1))) goto S260; + if(!(pq < 0.0e0)) goto S240; + *bound = 0.0e0; + goto S250; +S240: + *bound = 1.0e0; +S250: + *status = 3; + return; +S270: +S260: + if(*which == 2) goto S310; +/* + X + Y +*/ + xy = *x+*y; + if(!(fabs(xy-0.5e0-0.5e0) > 3.0e0*spmpar(&K1))) goto S300; + if(!(xy < 0.0e0)) goto S280; + *bound = 0.0e0; + goto S290; +S280: + *bound = 1.0e0; +S290: + *status = 4; + return; +S310: +S300: + if(!(*which == 1)) qporq = *p <= *q; +/* + Select the minimum of P or Q + Calculate ANSWERS +*/ + if(1 == *which) { +/* + Calculating P and Q +*/ + cumbet(x,y,a,b,p,q); + *status = 0; + } + else if(2 == *which) { +/* + Calculating X and Y +*/ + T4 = atol; + T5 = tol; + dstzr(&K2,&K3,&T4,&T5); + if(!qporq) goto S340; + *status = 0; + dzror(status,x,&fx,&xlo,&xhi,&qleft,&qhi); + *y = one-*x; +S320: + if(!(*status == 1)) goto S330; + cumbet(x,y,a,b,&cum,&ccum); + fx = cum-*p; + dzror(status,x,&fx,&xlo,&xhi,&qleft,&qhi); + *y = one-*x; + goto S320; +S330: + goto S370; +S340: + *status = 0; + dzror(status,y,&fx,&xlo,&xhi,&qleft,&qhi); + *x = one-*y; +S350: + if(!(*status == 1)) goto S360; + cumbet(x,y,a,b,&cum,&ccum); + fx = ccum-*q; + dzror(status,y,&fx,&xlo,&xhi,&qleft,&qhi); + *x = one-*y; + goto S350; +S370: +S360: + if(!(*status == -1)) goto S400; + if(!qleft) goto S380; + *status = 1; + *bound = 0.0e0; + goto S390; +S380: + *status = 2; + *bound = 1.0e0; +S400: +S390: + ; + } + else if(3 == *which) { +/* + Computing A +*/ + *a = 5.0e0; + T6 = zero; + T7 = inf; + T10 = atol; + T11 = tol; + dstinv(&T6,&T7,&K8,&K8,&K9,&T10,&T11); + *status = 0; + dinvr(status,a,&fx,&qleft,&qhi); +S410: + if(!(*status == 1)) goto S440; + cumbet(x,y,a,b,&cum,&ccum); + if(!qporq) goto S420; + fx = cum-*p; + goto S430; +S420: + fx = ccum-*q; +S430: + dinvr(status,a,&fx,&qleft,&qhi); + goto S410; +S440: + if(!(*status == -1)) goto S470; + if(!qleft) goto S450; + *status = 1; + *bound = zero; + goto S460; +S450: + *status = 2; + *bound = inf; +S470: +S460: + ; + } + else if(4 == *which) { +/* + Computing B +*/ + *b = 5.0e0; + T12 = zero; + T13 = inf; + T14 = atol; + T15 = tol; + dstinv(&T12,&T13,&K8,&K8,&K9,&T14,&T15); + *status = 0; + dinvr(status,b,&fx,&qleft,&qhi); +S480: + if(!(*status == 1)) goto S510; + cumbet(x,y,a,b,&cum,&ccum); + if(!qporq) goto S490; + fx = cum-*p; + goto S500; +S490: + fx = ccum-*q; +S500: + dinvr(status,b,&fx,&qleft,&qhi); + goto S480; +S510: + if(!(*status == -1)) goto S540; + if(!qleft) goto S520; + *status = 1; + *bound = zero; + goto S530; +S520: + *status = 2; + *bound = inf; +S530: + ; + } +S540: + return; +#undef tol +#undef atol +#undef zero +#undef inf +#undef one +} /* END */ + +/***=====================================================================***/ +static void cdfbin(int *which,double *p,double *q,double *s,double *xn, + double *pr,double *ompr,int *status,double *bound) +/********************************************************************** + + void cdfbin(int *which,double *p,double *q,double *s,double *xn, + double *pr,double *ompr,int *status,double *bound) + + Cumulative Distribution Function + BINomial distribution + + + Function + + + Calculates any one parameter of the binomial + distribution given values for the others. + + + Arguments + + + WHICH --> Integer indicating which of the next four argument + values is to be calculated from the others. + Legal range: 1..4 + iwhich = 1 : Calculate P and Q from S,XN,PR and OMPR + iwhich = 2 : Calculate S from P,Q,XN,PR and OMPR + iwhich = 3 : Calculate XN from P,Q,S,PR and OMPR + iwhich = 4 : Calculate PR and OMPR from P,Q,S and XN + + P <--> The cumulation from 0 to S of the binomial distribution. + (Probablility of S or fewer successes in XN trials each + with probability of success PR.) + Input range: [0,1]. + + Q <--> 1-P. + Input range: [0, 1]. + P + Q = 1.0. + + S <--> The number of successes observed. + Input range: [0, XN] + Search range: [0, XN] + + XN <--> The number of binomial trials. + Input range: (0, +infinity). + Search range: [1E-300, 1E300] + + PR <--> The probability of success in each binomial trial. + Input range: [0,1]. + Search range: [0,1] + + OMPR <--> 1-PR + Input range: [0,1]. + Search range: [0,1] + PR + OMPR = 1.0 + + STATUS <-- 0 if calculation completed correctly + -I if input parameter number I is out of range + 1 if answer appears to be lower than lowest + search bound + 2 if answer appears to be higher than greatest + search bound + 3 if P + Q .ne. 1 + 4 if PR + OMPR .ne. 1 + + BOUND <-- Undefined if STATUS is 0 + + Bound exceeded by parameter number I if STATUS + is negative. + + Lower search bound if STATUS is 1. + + Upper search bound if STATUS is 2. + + + Method + + + Formula 26.5.24 of Abramowitz and Stegun, Handbook of + Mathematical Functions (1966) is used to reduce the binomial + distribution to the cumulative incomplete beta distribution. + + Computation of other parameters involve a seach for a value that + produces the desired value of P. The search relies on the + monotinicity of P with the other parameter. + + +**********************************************************************/ +{ +#define atol (1.0e-50) +#define tol (1.0e-8) +#define zero (1.0e-300) +#define inf 1.0e300 +#define one 1.0e0 +static int K1 = 1; +static double K2 = 0.0e0; +static double K3 = 0.5e0; +static double K4 = 5.0e0; +static double K11 = 1.0e0; +static double fx,xhi,xlo,cum,ccum,pq,prompr; +static unsigned long qhi,qleft,qporq; +static double T5,T6,T7,T8,T9,T10,T12,T13; +/* + .. + .. Executable Statements .. +*/ +/* + Check arguments +*/ + if(!(*which < 1 && *which > 4)) goto S30; + if(!(*which < 1)) goto S10; + *bound = 1.0e0; + goto S20; +S10: + *bound = 4.0e0; +S20: + *status = -1; + return; +S30: + if(*which == 1) goto S70; +/* + P +*/ + if(!(*p < 0.0e0 || *p > 1.0e0)) goto S60; + if(!(*p < 0.0e0)) goto S40; + *bound = 0.0e0; + goto S50; +S40: + *bound = 1.0e0; +S50: + *status = -2; + return; +S70: +S60: + if(*which == 1) goto S110; +/* + Q +*/ + if(!(*q < 0.0e0 || *q > 1.0e0)) goto S100; + if(!(*q < 0.0e0)) goto S80; + *bound = 0.0e0; + goto S90; +S80: + *bound = 1.0e0; +S90: + *status = -3; + return; +S110: +S100: + if(*which == 3) goto S130; +/* + XN +*/ + if(!(*xn <= 0.0e0)) goto S120; + *bound = 0.0e0; + *status = -5; + return; +S130: +S120: + if(*which == 2) goto S170; +/* + S +*/ + if(!(*s < 0.0e0 || *which != 3 && *s > *xn)) goto S160; + if(!(*s < 0.0e0)) goto S140; + *bound = 0.0e0; + goto S150; +S140: + *bound = *xn; +S150: + *status = -4; + return; +S170: +S160: + if(*which == 4) goto S210; +/* + PR +*/ + if(!(*pr < 0.0e0 || *pr > 1.0e0)) goto S200; + if(!(*pr < 0.0e0)) goto S180; + *bound = 0.0e0; + goto S190; +S180: + *bound = 1.0e0; +S190: + *status = -6; + return; +S210: +S200: + if(*which == 4) goto S250; +/* + OMPR +*/ + if(!(*ompr < 0.0e0 || *ompr > 1.0e0)) goto S240; + if(!(*ompr < 0.0e0)) goto S220; + *bound = 0.0e0; + goto S230; +S220: + *bound = 1.0e0; +S230: + *status = -7; + return; +S250: +S240: + if(*which == 1) goto S290; +/* + P + Q +*/ + pq = *p+*q; + if(!(fabs(pq-0.5e0-0.5e0) > 3.0e0*spmpar(&K1))) goto S280; + if(!(pq < 0.0e0)) goto S260; + *bound = 0.0e0; + goto S270; +S260: + *bound = 1.0e0; +S270: + *status = 3; + return; +S290: +S280: + if(*which == 4) goto S330; +/* + PR + OMPR +*/ + prompr = *pr+*ompr; + if(!(fabs(prompr-0.5e0-0.5e0) > 3.0e0*spmpar(&K1))) goto S320; + if(!(prompr < 0.0e0)) goto S300; + *bound = 0.0e0; + goto S310; +S300: + *bound = 1.0e0; +S310: + *status = 4; + return; +S330: +S320: + if(!(*which == 1)) qporq = *p <= *q; +/* + Select the minimum of P or Q + Calculate ANSWERS +*/ + if(1 == *which) { +/* + Calculating P +*/ + cumbin(s,xn,pr,ompr,p,q); + *status = 0; + } + else if(2 == *which) { +/* + Calculating S +*/ + *s = 5.0e0; + T5 = atol; + T6 = tol; + dstinv(&K2,xn,&K3,&K3,&K4,&T5,&T6); + *status = 0; + dinvr(status,s,&fx,&qleft,&qhi); +S340: + if(!(*status == 1)) goto S370; + cumbin(s,xn,pr,ompr,&cum,&ccum); + if(!qporq) goto S350; + fx = cum-*p; + goto S360; +S350: + fx = ccum-*q; +S360: + dinvr(status,s,&fx,&qleft,&qhi); + goto S340; +S370: + if(!(*status == -1)) goto S400; + if(!qleft) goto S380; + *status = 1; + *bound = 0.0e0; + goto S390; +S380: + *status = 2; + *bound = *xn; +S400: +S390: + ; + } + else if(3 == *which) { +/* + Calculating XN +*/ + *xn = 5.0e0; + T7 = zero; + T8 = inf; + T9 = atol; + T10 = tol; + dstinv(&T7,&T8,&K3,&K3,&K4,&T9,&T10); + *status = 0; + dinvr(status,xn,&fx,&qleft,&qhi); +S410: + if(!(*status == 1)) goto S440; + cumbin(s,xn,pr,ompr,&cum,&ccum); + if(!qporq) goto S420; + fx = cum-*p; + goto S430; +S420: + fx = ccum-*q; +S430: + dinvr(status,xn,&fx,&qleft,&qhi); + goto S410; +S440: + if(!(*status == -1)) goto S470; + if(!qleft) goto S450; + *status = 1; + *bound = zero; + goto S460; +S450: + *status = 2; + *bound = inf; +S470: +S460: + ; + } + else if(4 == *which) { +/* + Calculating PR and OMPR +*/ + T12 = atol; + T13 = tol; + dstzr(&K2,&K11,&T12,&T13); + if(!qporq) goto S500; + *status = 0; + dzror(status,pr,&fx,&xlo,&xhi,&qleft,&qhi); + *ompr = one-*pr; +S480: + if(!(*status == 1)) goto S490; + cumbin(s,xn,pr,ompr,&cum,&ccum); + fx = cum-*p; + dzror(status,pr,&fx,&xlo,&xhi,&qleft,&qhi); + *ompr = one-*pr; + goto S480; +S490: + goto S530; +S500: + *status = 0; + dzror(status,ompr,&fx,&xlo,&xhi,&qleft,&qhi); + *pr = one-*ompr; +S510: + if(!(*status == 1)) goto S520; + cumbin(s,xn,pr,ompr,&cum,&ccum); + fx = ccum-*q; + dzror(status,ompr,&fx,&xlo,&xhi,&qleft,&qhi); + *pr = one-*ompr; + goto S510; +S530: +S520: + if(!(*status == -1)) goto S560; + if(!qleft) goto S540; + *status = 1; + *bound = 0.0e0; + goto S550; +S540: + *status = 2; + *bound = 1.0e0; +S550: + ; + } +S560: + return; +#undef atol +#undef tol +#undef zero +#undef inf +#undef one +} /* END */ + +/***=====================================================================***/ +static void cdfchi(int *which,double *p,double *q,double *x,double *df, + int *status,double *bound) +/********************************************************************** + + void cdfchi(int *which,double *p,double *q,double *x,double *df, + int *status,double *bound) + + Cumulative Distribution Function + CHI-Square distribution + + + Function + + + Calculates any one parameter of the chi-square + distribution given values for the others. + + + Arguments + + + WHICH --> Integer indicating which of the next three argument + values is to be calculated from the others. + Legal range: 1..3 + iwhich = 1 : Calculate P and Q from X and DF + iwhich = 2 : Calculate X from P,Q and DF + iwhich = 3 : Calculate DF from P,Q and X + + P <--> The integral from 0 to X of the chi-square + distribution. + Input range: [0, 1]. + + Q <--> 1-P. + Input range: (0, 1]. + P + Q = 1.0. + + X <--> Upper limit of integration of the non-central + chi-square distribution. + Input range: [0, +infinity). + Search range: [0,1E300] + + DF <--> Degrees of freedom of the + chi-square distribution. + Input range: (0, +infinity). + Search range: [ 1E-300, 1E300] + + STATUS <-- 0 if calculation completed correctly + -I if input parameter number I is out of range + 1 if answer appears to be lower than lowest + search bound + 2 if answer appears to be higher than greatest + search bound + 3 if P + Q .ne. 1 + 10 indicates error returned from cumgam. See + references in cdfgam + + BOUND <-- Undefined if STATUS is 0 + + Bound exceeded by parameter number I if STATUS + is negative. + + Lower search bound if STATUS is 1. + + Upper search bound if STATUS is 2. + + + Method + + + Formula 26.4.19 of Abramowitz and Stegun, Handbook of + Mathematical Functions (1966) is used to reduce the chisqure + distribution to the incomplete distribution. + + Computation of other parameters involve a seach for a value that + produces the desired value of P. The search relies on the + monotinicity of P with the other parameter. + +**********************************************************************/ +{ +#define tol (1.0e-8) +#define atol (1.0e-50) +#define zero (1.0e-300) +#define inf 1.0e300 +static int K1 = 1; +static double K2 = 0.0e0; +static double K4 = 0.5e0; +static double K5 = 5.0e0; +static double fx,cum,ccum,pq,porq; +static unsigned long qhi,qleft,qporq; +static double T3,T6,T7,T8,T9,T10,T11; +/* + .. + .. Executable Statements .. +*/ +/* + Check arguments +*/ + if(!(*which < 1 || *which > 3)) goto S30; + if(!(*which < 1)) goto S10; + *bound = 1.0e0; + goto S20; +S10: + *bound = 3.0e0; +S20: + *status = -1; + return; +S30: + if(*which == 1) goto S70; +/* + P +*/ + if(!(*p < 0.0e0 || *p > 1.0e0)) goto S60; + if(!(*p < 0.0e0)) goto S40; + *bound = 0.0e0; + goto S50; +S40: + *bound = 1.0e0; +S50: + *status = -2; + return; +S70: +S60: + if(*which == 1) goto S110; +/* + Q +*/ + if(!(*q <= 0.0e0 || *q > 1.0e0)) goto S100; + if(!(*q <= 0.0e0)) goto S80; + *bound = 0.0e0; + goto S90; +S80: + *bound = 1.0e0; +S90: + *status = -3; + return; +S110: +S100: + if(*which == 2) goto S130; +/* + X +*/ + if(!(*x < 0.0e0)) goto S120; + *bound = 0.0e0; + *status = -4; + return; +S130: +S120: + if(*which == 3) goto S150; +/* + DF +*/ + if(!(*df <= 0.0e0)) goto S140; + *bound = 0.0e0; + *status = -5; + return; +S150: +S140: + if(*which == 1) goto S190; +/* + P + Q +*/ + pq = *p+*q; + if(!(fabs(pq-0.5e0-0.5e0) > 3.0e0*spmpar(&K1))) goto S180; + if(!(pq < 0.0e0)) goto S160; + *bound = 0.0e0; + goto S170; +S160: + *bound = 1.0e0; +S170: + *status = 3; + return; +S190: +S180: + if(*which == 1) goto S220; +/* + Select the minimum of P or Q +*/ + qporq = *p <= *q; + if(!qporq) goto S200; + porq = *p; + goto S210; +S200: + porq = *q; +S220: +S210: +/* + Calculate ANSWERS +*/ + if(1 == *which) { +/* + Calculating P and Q +*/ + *status = 0; + cumchi(x,df,p,q); + if(porq > 1.5e0) { + *status = 10; + return; + } + } + else if(2 == *which) { +/* + Calculating X +*/ + *x = 5.0e0; + T3 = inf; + T6 = atol; + T7 = tol; + dstinv(&K2,&T3,&K4,&K4,&K5,&T6,&T7); + *status = 0; + dinvr(status,x,&fx,&qleft,&qhi); +S230: + if(!(*status == 1)) goto S270; + cumchi(x,df,&cum,&ccum); + if(!qporq) goto S240; + fx = cum-*p; + goto S250; +S240: + fx = ccum-*q; +S250: + if(!(fx+porq > 1.5e0)) goto S260; + *status = 10; + return; +S260: + dinvr(status,x,&fx,&qleft,&qhi); + goto S230; +S270: + if(!(*status == -1)) goto S300; + if(!qleft) goto S280; + *status = 1; + *bound = 0.0e0; + goto S290; +S280: + *status = 2; + *bound = inf; +S300: +S290: + ; + } + else if(3 == *which) { +/* + Calculating DF +*/ + *df = 5.0e0; + T8 = zero; + T9 = inf; + T10 = atol; + T11 = tol; + dstinv(&T8,&T9,&K4,&K4,&K5,&T10,&T11); + *status = 0; + dinvr(status,df,&fx,&qleft,&qhi); +S310: + if(!(*status == 1)) goto S350; + cumchi(x,df,&cum,&ccum); + if(!qporq) goto S320; + fx = cum-*p; + goto S330; +S320: + fx = ccum-*q; +S330: + if(!(fx+porq > 1.5e0)) goto S340; + *status = 10; + return; +S340: + dinvr(status,df,&fx,&qleft,&qhi); + goto S310; +S350: + if(!(*status == -1)) goto S380; + if(!qleft) goto S360; + *status = 1; + *bound = zero; + goto S370; +S360: + *status = 2; + *bound = inf; +S370: + ; + } +S380: + return; +#undef tol +#undef atol +#undef zero +#undef inf +} /* END */ + +/***=====================================================================***/ +static void cdfchn(int *which,double *p,double *q,double *x,double *df, + double *pnonc,int *status,double *bound) +/********************************************************************** + + void cdfchn(int *which,double *p,double *q,double *x,double *df, + double *pnonc,int *status,double *bound) + + Cumulative Distribution Function + Non-central Chi-Square + + + Function + + + Calculates any one parameter of the non-central chi-square + distribution given values for the others. + + + Arguments + + + WHICH --> Integer indicating which of the next three argument + values is to be calculated from the others. + Input range: 1..4 + iwhich = 1 : Calculate P and Q from X and DF + iwhich = 2 : Calculate X from P,DF and PNONC + iwhich = 3 : Calculate DF from P,X and PNONC + iwhich = 3 : Calculate PNONC from P,X and DF + + P <--> The integral from 0 to X of the non-central chi-square + distribution. + Input range: [0, 1-1E-16). + + Q <--> 1-P. + Q is not used by this subroutine and is only included + for similarity with other cdf* routines. + + X <--> Upper limit of integration of the non-central + chi-square distribution. + Input range: [0, +infinity). + Search range: [0,1E300] + + DF <--> Degrees of freedom of the non-central + chi-square distribution. + Input range: (0, +infinity). + Search range: [ 1E-300, 1E300] + + PNONC <--> Non-centrality parameter of the non-central + chi-square distribution. + Input range: [0, +infinity). + Search range: [0,1E4] + + STATUS <-- 0 if calculation completed correctly + -I if input parameter number I is out of range + 1 if answer appears to be lower than lowest + search bound + 2 if answer appears to be higher than greatest + search bound + + BOUND <-- Undefined if STATUS is 0 + + Bound exceeded by parameter number I if STATUS + is negative. + + Lower search bound if STATUS is 1. + + Upper search bound if STATUS is 2. + + + Method + + + Formula 26.4.25 of Abramowitz and Stegun, Handbook of + Mathematical Functions (1966) is used to compute the cumulative + distribution function. + + Computation of other parameters involve a seach for a value that + produces the desired value of P. The search relies on the + monotinicity of P with the other parameter. + + + WARNING + + The computation time required for this routine is proportional + to the noncentrality parameter (PNONC). Very large values of + this parameter can consume immense computer resources. This is + why the search range is bounded by 10,000. + +**********************************************************************/ +{ +#define tent4 1.0e4 +#define tol (1.0e-8) +#define atol (1.0e-50) +#define zero (1.0e-300) +#define one (1.0e0-1.0e-16) +#define inf 1.0e300 +static double K1 = 0.0e0; +static double K3 = 0.5e0; +static double K4 = 5.0e0; +static double fx,cum,ccum; +static unsigned long qhi,qleft; +static double T2,T5,T6,T7,T8,T9,T10,T11,T12,T13; +/* + .. + .. Executable Statements .. +*/ +/* + Check arguments +*/ + if(!(*which < 1 || *which > 4)) goto S30; + if(!(*which < 1)) goto S10; + *bound = 1.0e0; + goto S20; +S10: + *bound = 4.0e0; +S20: + *status = -1; + return; +S30: + if(*which == 1) goto S70; +/* + P +*/ + if(!(*p < 0.0e0 || *p > one)) goto S60; + if(!(*p < 0.0e0)) goto S40; + *bound = 0.0e0; + goto S50; +S40: + *bound = one; +S50: + *status = -2; + return; +S70: +S60: + if(*which == 2) goto S90; +/* + X +*/ + if(!(*x < 0.0e0)) goto S80; + *bound = 0.0e0; + *status = -4; + return; +S90: +S80: + if(*which == 3) goto S110; +/* + DF +*/ + if(!(*df <= 0.0e0)) goto S100; + *bound = 0.0e0; + *status = -5; + return; +S110: +S100: + if(*which == 4) goto S130; +/* + PNONC +*/ + if(!(*pnonc < 0.0e0)) goto S120; + *bound = 0.0e0; + *status = -6; + return; +S130: +S120: +/* + Calculate ANSWERS +*/ + if(1 == *which) { +/* + Calculating P and Q +*/ + cumchn(x,df,pnonc,p,q); + *status = 0; + } + else if(2 == *which) { +/* + Calculating X +*/ + *x = 5.0e0; + T2 = inf; + T5 = atol; + T6 = tol; + dstinv(&K1,&T2,&K3,&K3,&K4,&T5,&T6); + *status = 0; + dinvr(status,x,&fx,&qleft,&qhi); +S140: + if(!(*status == 1)) goto S150; + cumchn(x,df,pnonc,&cum,&ccum); + fx = cum-*p; + dinvr(status,x,&fx,&qleft,&qhi); + goto S140; +S150: + if(!(*status == -1)) goto S180; + if(!qleft) goto S160; + *status = 1; + *bound = 0.0e0; + goto S170; +S160: + *status = 2; + *bound = inf; +S180: +S170: + ; + } + else if(3 == *which) { +/* + Calculating DF +*/ + *df = 5.0e0; + T7 = zero; + T8 = inf; + T9 = atol; + T10 = tol; + dstinv(&T7,&T8,&K3,&K3,&K4,&T9,&T10); + *status = 0; + dinvr(status,df,&fx,&qleft,&qhi); +S190: + if(!(*status == 1)) goto S200; + cumchn(x,df,pnonc,&cum,&ccum); + fx = cum-*p; + dinvr(status,df,&fx,&qleft,&qhi); + goto S190; +S200: + if(!(*status == -1)) goto S230; + if(!qleft) goto S210; + *status = 1; + *bound = zero; + goto S220; +S210: + *status = 2; + *bound = inf; +S230: +S220: + ; + } + else if(4 == *which) { +/* + Calculating PNONC +*/ + *pnonc = 5.0e0; + T11 = tent4; + T12 = atol; + T13 = tol; + dstinv(&K1,&T11,&K3,&K3,&K4,&T12,&T13); + *status = 0; + dinvr(status,pnonc,&fx,&qleft,&qhi); +S240: + if(!(*status == 1)) goto S250; + cumchn(x,df,pnonc,&cum,&ccum); + fx = cum-*p; + dinvr(status,pnonc,&fx,&qleft,&qhi); + goto S240; +S250: + if(!(*status == -1)) goto S280; + if(!qleft) goto S260; + *status = 1; + *bound = zero; + goto S270; +S260: + *status = 2; + *bound = tent4; +S270: + ; + } +S280: + return; +#undef tent4 +#undef tol +#undef atol +#undef zero +#undef one +#undef inf +} /* END */ + +/***=====================================================================***/ +static void cdff(int *which,double *p,double *q,double *f,double *dfn, + double *dfd,int *status,double *bound) +/********************************************************************** + + void cdff(int *which,double *p,double *q,double *f,double *dfn, + double *dfd,int *status,double *bound) + + Cumulative Distribution Function + F distribution + + + Function + + + Calculates any one parameter of the F distribution + given values for the others. + + + Arguments + + + WHICH --> Integer indicating which of the next four argument + values is to be calculated from the others. + Legal range: 1..4 + iwhich = 1 : Calculate P and Q from F,DFN and DFD + iwhich = 2 : Calculate F from P,Q,DFN and DFD + iwhich = 3 : Calculate DFN from P,Q,F and DFD + iwhich = 4 : Calculate DFD from P,Q,F and DFN + + P <--> The integral from 0 to F of the f-density. + Input range: [0,1]. + + Q <--> 1-P. + Input range: (0, 1]. + P + Q = 1.0. + + F <--> Upper limit of integration of the f-density. + Input range: [0, +infinity). + Search range: [0,1E300] + + DFN < --> Degrees of freedom of the numerator sum of squares. + Input range: (0, +infinity). + Search range: [ 1E-300, 1E300] + + DFD < --> Degrees of freedom of the denominator sum of squares. + Input range: (0, +infinity). + Search range: [ 1E-300, 1E300] + + STATUS <-- 0 if calculation completed correctly + -I if input parameter number I is out of range + 1 if answer appears to be lower than lowest + search bound + 2 if answer appears to be higher than greatest + search bound + 3 if P + Q .ne. 1 + + BOUND <-- Undefined if STATUS is 0 + + Bound exceeded by parameter number I if STATUS + is negative. + + Lower search bound if STATUS is 1. + + Upper search bound if STATUS is 2. + + + Method + + + Formula 26.6.2 of Abramowitz and Stegun, Handbook of + Mathematical Functions (1966) is used to reduce the computation + of the cumulative distribution function for the F variate to + that of an incomplete beta. + + Computation of other parameters involve a seach for a value that + produces the desired value of P. The search relies on the + monotinicity of P with the other parameter. + + WARNING + + The value of the cumulative F distribution is not necessarily + monotone in either degrees of freedom. There thus may be two + values that provide a given CDF value. This routine assumes + monotonicity and will find an arbitrary one of the two values. + +**********************************************************************/ +{ +#define tol (1.0e-8) +#define atol (1.0e-50) +#define zero (1.0e-300) +#define inf 1.0e300 +static int K1 = 1; +static double K2 = 0.0e0; +static double K4 = 0.5e0; +static double K5 = 5.0e0; +static double pq,fx,cum,ccum; +static unsigned long qhi,qleft,qporq; +static double T3,T6,T7,T8,T9,T10,T11,T12,T13,T14,T15; +/* + .. + .. Executable Statements .. +*/ +/* + Check arguments +*/ + if(!(*which < 1 || *which > 4)) goto S30; + if(!(*which < 1)) goto S10; + *bound = 1.0e0; + goto S20; +S10: + *bound = 4.0e0; +S20: + *status = -1; + return; +S30: + if(*which == 1) goto S70; +/* + P +*/ + if(!(*p < 0.0e0 || *p > 1.0e0)) goto S60; + if(!(*p < 0.0e0)) goto S40; + *bound = 0.0e0; + goto S50; +S40: + *bound = 1.0e0; +S50: + *status = -2; + return; +S70: +S60: + if(*which == 1) goto S110; +/* + Q +*/ + if(!(*q <= 0.0e0 || *q > 1.0e0)) goto S100; + if(!(*q <= 0.0e0)) goto S80; + *bound = 0.0e0; + goto S90; +S80: + *bound = 1.0e0; +S90: + *status = -3; + return; +S110: +S100: + if(*which == 2) goto S130; +/* + F +*/ + if(!(*f < 0.0e0)) goto S120; + *bound = 0.0e0; + *status = -4; + return; +S130: +S120: + if(*which == 3) goto S150; +/* + DFN +*/ + if(!(*dfn <= 0.0e0)) goto S140; + *bound = 0.0e0; + *status = -5; + return; +S150: +S140: + if(*which == 4) goto S170; +/* + DFD +*/ + if(!(*dfd <= 0.0e0)) goto S160; + *bound = 0.0e0; + *status = -6; + return; +S170: +S160: + if(*which == 1) goto S210; +/* + P + Q +*/ + pq = *p+*q; + if(!(fabs(pq-0.5e0-0.5e0) > 3.0e0*spmpar(&K1))) goto S200; + if(!(pq < 0.0e0)) goto S180; + *bound = 0.0e0; + goto S190; +S180: + *bound = 1.0e0; +S190: + *status = 3; + return; +S210: +S200: + if(!(*which == 1)) qporq = *p <= *q; +/* + Select the minimum of P or Q + Calculate ANSWERS +*/ + if(1 == *which) { +/* + Calculating P +*/ + cumf(f,dfn,dfd,p,q); + *status = 0; + } + else if(2 == *which) { +/* + Calculating F +*/ + *f = 5.0e0; + T3 = inf; + T6 = atol; + T7 = tol; + dstinv(&K2,&T3,&K4,&K4,&K5,&T6,&T7); + *status = 0; + dinvr(status,f,&fx,&qleft,&qhi); +S220: + if(!(*status == 1)) goto S250; + cumf(f,dfn,dfd,&cum,&ccum); + if(!qporq) goto S230; + fx = cum-*p; + goto S240; +S230: + fx = ccum-*q; +S240: + dinvr(status,f,&fx,&qleft,&qhi); + goto S220; +S250: + if(!(*status == -1)) goto S280; + if(!qleft) goto S260; + *status = 1; + *bound = 0.0e0; + goto S270; +S260: + *status = 2; + *bound = inf; +S280: +S270: + ; + } + else if(3 == *which) { +/* + Calculating DFN +*/ + *dfn = 5.0e0; + T8 = zero; + T9 = inf; + T10 = atol; + T11 = tol; + dstinv(&T8,&T9,&K4,&K4,&K5,&T10,&T11); + *status = 0; + dinvr(status,dfn,&fx,&qleft,&qhi); +S290: + if(!(*status == 1)) goto S320; + cumf(f,dfn,dfd,&cum,&ccum); + if(!qporq) goto S300; + fx = cum-*p; + goto S310; +S300: + fx = ccum-*q; +S310: + dinvr(status,dfn,&fx,&qleft,&qhi); + goto S290; +S320: + if(!(*status == -1)) goto S350; + if(!qleft) goto S330; + *status = 1; + *bound = zero; + goto S340; +S330: + *status = 2; + *bound = inf; +S350: +S340: + ; + } + else if(4 == *which) { +/* + Calculating DFD +*/ + *dfd = 5.0e0; + T12 = zero; + T13 = inf; + T14 = atol; + T15 = tol; + dstinv(&T12,&T13,&K4,&K4,&K5,&T14,&T15); + *status = 0; + dinvr(status,dfd,&fx,&qleft,&qhi); +S360: + if(!(*status == 1)) goto S390; + cumf(f,dfn,dfd,&cum,&ccum); + if(!qporq) goto S370; + fx = cum-*p; + goto S380; +S370: + fx = ccum-*q; +S380: + dinvr(status,dfd,&fx,&qleft,&qhi); + goto S360; +S390: + if(!(*status == -1)) goto S420; + if(!qleft) goto S400; + *status = 1; + *bound = zero; + goto S410; +S400: + *status = 2; + *bound = inf; +S410: + ; + } +S420: + return; +#undef tol +#undef atol +#undef zero +#undef inf +} /* END */ + +/***=====================================================================***/ +static void cdffnc(int *which,double *p,double *q,double *f,double *dfn, + double *dfd,double *phonc,int *status,double *bound) +/********************************************************************** + + void cdffnc(int *which,double *p,double *q,double *f,double *dfn, + double *dfd,double *phonc,int *status,double *bound) + + Cumulative Distribution Function + Non-central F distribution + + + Function + + + Calculates any one parameter of the Non-central F + distribution given values for the others. + + + Arguments + + + WHICH --> Integer indicating which of the next five argument + values is to be calculated from the others. + Legal range: 1..5 + iwhich = 1 : Calculate P and Q from F,DFN,DFD and PNONC + iwhich = 2 : Calculate F from P,Q,DFN,DFD and PNONC + iwhich = 3 : Calculate DFN from P,Q,F,DFD and PNONC + iwhich = 4 : Calculate DFD from P,Q,F,DFN and PNONC + iwhich = 5 : Calculate PNONC from P,Q,F,DFN and DFD + + P <--> The integral from 0 to F of the non-central f-density. + Input range: [0,1-1E-16). + + Q <--> 1-P. + Q is not used by this subroutine and is only included + for similarity with other cdf* routines. + + F <--> Upper limit of integration of the non-central f-density. + Input range: [0, +infinity). + Search range: [0,1E300] + + DFN < --> Degrees of freedom of the numerator sum of squares. + Input range: (0, +infinity). + Search range: [ 1E-300, 1E300] + + DFD < --> Degrees of freedom of the denominator sum of squares. + Must be in range: (0, +infinity). + Input range: (0, +infinity). + Search range: [ 1E-300, 1E300] + + PNONC <-> The non-centrality parameter + Input range: [0,infinity) + Search range: [0,1E4] + + STATUS <-- 0 if calculation completed correctly + -I if input parameter number I is out of range + 1 if answer appears to be lower than lowest + search bound + 2 if answer appears to be higher than greatest + search bound + 3 if P + Q .ne. 1 + + BOUND <-- Undefined if STATUS is 0 + + Bound exceeded by parameter number I if STATUS + is negative. + + Lower search bound if STATUS is 1. + + Upper search bound if STATUS is 2. + + + Method + + + Formula 26.6.20 of Abramowitz and Stegun, Handbook of + Mathematical Functions (1966) is used to compute the cumulative + distribution function. + + Computation of other parameters involve a seach for a value that + produces the desired value of P. The search relies on the + monotinicity of P with the other parameter. + + WARNING + + The computation time required for this routine is proportional + to the noncentrality parameter (PNONC). Very large values of + this parameter can consume immense computer resources. This is + why the search range is bounded by 10,000. + + WARNING + + The value of the cumulative noncentral F distribution is not + necessarily monotone in either degrees of freedom. There thus + may be two values that provide a given CDF value. This routine + assumes monotonicity and will find an arbitrary one of the two + values. + +**********************************************************************/ +{ +#define tent4 1.0e4 +#define tol (1.0e-8) +#define atol (1.0e-50) +#define zero (1.0e-300) +#define one (1.0e0-1.0e-16) +#define inf 1.0e300 +static double K1 = 0.0e0; +static double K3 = 0.5e0; +static double K4 = 5.0e0; +static double fx,cum,ccum; +static unsigned long qhi,qleft; +static double T2,T5,T6,T7,T8,T9,T10,T11,T12,T13,T14,T15,T16,T17; +/* + .. + .. Executable Statements .. +*/ +/* + Check arguments +*/ + if(!(*which < 1 || *which > 5)) goto S30; + if(!(*which < 1)) goto S10; + *bound = 1.0e0; + goto S20; +S10: + *bound = 5.0e0; +S20: + *status = -1; + return; +S30: + if(*which == 1) goto S70; +/* + P +*/ + if(!(*p < 0.0e0 || *p > one)) goto S60; + if(!(*p < 0.0e0)) goto S40; + *bound = 0.0e0; + goto S50; +S40: + *bound = one; +S50: + *status = -2; + return; +S70: +S60: + if(*which == 2) goto S90; +/* + F +*/ + if(!(*f < 0.0e0)) goto S80; + *bound = 0.0e0; + *status = -4; + return; +S90: +S80: + if(*which == 3) goto S110; +/* + DFN +*/ + if(!(*dfn <= 0.0e0)) goto S100; + *bound = 0.0e0; + *status = -5; + return; +S110: +S100: + if(*which == 4) goto S130; +/* + DFD +*/ + if(!(*dfd <= 0.0e0)) goto S120; + *bound = 0.0e0; + *status = -6; + return; +S130: +S120: + if(*which == 5) goto S150; +/* + PHONC +*/ + if(!(*phonc < 0.0e0)) goto S140; + *bound = 0.0e0; + *status = -7; + return; +S150: +S140: +/* + Calculate ANSWERS +*/ + if(1 == *which) { +/* + Calculating P +*/ + cumfnc(f,dfn,dfd,phonc,p,q); + *status = 0; + } + else if(2 == *which) { +/* + Calculating F +*/ + *f = 5.0e0; + T2 = inf; + T5 = atol; + T6 = tol; + dstinv(&K1,&T2,&K3,&K3,&K4,&T5,&T6); + *status = 0; + dinvr(status,f,&fx,&qleft,&qhi); +S160: + if(!(*status == 1)) goto S170; + cumfnc(f,dfn,dfd,phonc,&cum,&ccum); + fx = cum-*p; + dinvr(status,f,&fx,&qleft,&qhi); + goto S160; +S170: + if(!(*status == -1)) goto S200; + if(!qleft) goto S180; + *status = 1; + *bound = 0.0e0; + goto S190; +S180: + *status = 2; + *bound = inf; +S200: +S190: + ; + } + else if(3 == *which) { +/* + Calculating DFN +*/ + *dfn = 5.0e0; + T7 = zero; + T8 = inf; + T9 = atol; + T10 = tol; + dstinv(&T7,&T8,&K3,&K3,&K4,&T9,&T10); + *status = 0; + dinvr(status,dfn,&fx,&qleft,&qhi); +S210: + if(!(*status == 1)) goto S220; + cumfnc(f,dfn,dfd,phonc,&cum,&ccum); + fx = cum-*p; + dinvr(status,dfn,&fx,&qleft,&qhi); + goto S210; +S220: + if(!(*status == -1)) goto S250; + if(!qleft) goto S230; + *status = 1; + *bound = zero; + goto S240; +S230: + *status = 2; + *bound = inf; +S250: +S240: + ; + } + else if(4 == *which) { +/* + Calculating DFD +*/ + *dfd = 5.0e0; + T11 = zero; + T12 = inf; + T13 = atol; + T14 = tol; + dstinv(&T11,&T12,&K3,&K3,&K4,&T13,&T14); + *status = 0; + dinvr(status,dfd,&fx,&qleft,&qhi); +S260: + if(!(*status == 1)) goto S270; + cumfnc(f,dfn,dfd,phonc,&cum,&ccum); + fx = cum-*p; + dinvr(status,dfd,&fx,&qleft,&qhi); + goto S260; +S270: + if(!(*status == -1)) goto S300; + if(!qleft) goto S280; + *status = 1; + *bound = zero; + goto S290; +S280: + *status = 2; + *bound = inf; +S300: +S290: + ; + } + else if(5 == *which) { +/* + Calculating PHONC +*/ + *phonc = 5.0e0; + T15 = tent4; + T16 = atol; + T17 = tol; + dstinv(&K1,&T15,&K3,&K3,&K4,&T16,&T17); + *status = 0; + dinvr(status,phonc,&fx,&qleft,&qhi); +S310: + if(!(*status == 1)) goto S320; + cumfnc(f,dfn,dfd,phonc,&cum,&ccum); + fx = cum-*p; + dinvr(status,phonc,&fx,&qleft,&qhi); + goto S310; +S320: + if(!(*status == -1)) goto S350; + if(!qleft) goto S330; + *status = 1; + *bound = 0.0e0; + goto S340; +S330: + *status = 2; + *bound = tent4; +S340: + ; + } +S350: + return; +#undef tent4 +#undef tol +#undef atol +#undef zero +#undef one +#undef inf +} /* END */ + +/***=====================================================================***/ +static void cdfgam(int *which,double *p,double *q,double *x,double *shape, + double *scale,int *status,double *bound) +/********************************************************************** + + void cdfgam(int *which,double *p,double *q,double *x,double *shape, + double *scale,int *status,double *bound) + + Cumulative Distribution Function + GAMma Distribution + + + Function + + + Calculates any one parameter of the gamma + distribution given values for the others. + + + Arguments + + + WHICH --> Integer indicating which of the next four argument + values is to be calculated from the others. + Legal range: 1..4 + iwhich = 1 : Calculate P and Q from X,SHAPE and SCALE + iwhich = 2 : Calculate X from P,Q,SHAPE and SCALE + iwhich = 3 : Calculate SHAPE from P,Q,X and SCALE + iwhich = 4 : Calculate SCALE from P,Q,X and SHAPE + + P <--> The integral from 0 to X of the gamma density. + Input range: [0,1]. + + Q <--> 1-P. + Input range: (0, 1]. + P + Q = 1.0. + + X <--> The upper limit of integration of the gamma density. + Input range: [0, +infinity). + Search range: [0,1E300] + + SHAPE <--> The shape parameter of the gamma density. + Input range: (0, +infinity). + Search range: [1E-300,1E300] + + SCALE <--> The scale parameter of the gamma density. + Input range: (0, +infinity). + Search range: (1E-300,1E300] + + STATUS <-- 0 if calculation completed correctly + -I if input parameter number I is out of range + 1 if answer appears to be lower than lowest + search bound + 2 if answer appears to be higher than greatest + search bound + 3 if P + Q .ne. 1 + 10 if the gamma or inverse gamma routine cannot + compute the answer. Usually happens only for + X and SHAPE very large (gt 1E10 or more) + + BOUND <-- Undefined if STATUS is 0 + + Bound exceeded by parameter number I if STATUS + is negative. + + Lower search bound if STATUS is 1. + + Upper search bound if STATUS is 2. + + + Method + + + Cumulative distribution function (P) is calculated directly by + the code associated with: + + DiDinato, A. R. and Morris, A. H. Computation of the incomplete + gamma function ratios and their inverse. ACM Trans. Math. + Softw. 12 (1986), 377-393. + + Computation of other parameters involve a seach for a value that + produces the desired value of P. The search relies on the + monotinicity of P with the other parameter. + + + Note + + + + The gamma density is proportional to + T**(SHAPE - 1) * EXP(- SCALE * T) + +**********************************************************************/ +{ +#define tol (1.0e-8) +#define atol (1.0e-50) +#define zero (1.0e-300) +#define inf 1.0e300 +static int K1 = 1; +static double K5 = 0.5e0; +static double K6 = 5.0e0; +static double xx,fx,xscale,cum,ccum,pq,porq; +static int ierr; +static unsigned long qhi,qleft,qporq; +static double T2,T3,T4,T7,T8,T9; +/* + .. + .. Executable Statements .. +*/ +/* + Check arguments +*/ + if(!(*which < 1 || *which > 4)) goto S30; + if(!(*which < 1)) goto S10; + *bound = 1.0e0; + goto S20; +S10: + *bound = 4.0e0; +S20: + *status = -1; + return; +S30: + if(*which == 1) goto S70; +/* + P +*/ + if(!(*p < 0.0e0 || *p > 1.0e0)) goto S60; + if(!(*p < 0.0e0)) goto S40; + *bound = 0.0e0; + goto S50; +S40: + *bound = 1.0e0; +S50: + *status = -2; + return; +S70: +S60: + if(*which == 1) goto S110; +/* + Q +*/ + if(!(*q <= 0.0e0 || *q > 1.0e0)) goto S100; + if(!(*q <= 0.0e0)) goto S80; + *bound = 0.0e0; + goto S90; +S80: + *bound = 1.0e0; +S90: + *status = -3; + return; +S110: +S100: + if(*which == 2) goto S130; +/* + X +*/ + if(!(*x < 0.0e0)) goto S120; + *bound = 0.0e0; + *status = -4; + return; +S130: +S120: + if(*which == 3) goto S150; +/* + SHAPE +*/ + if(!(*shape <= 0.0e0)) goto S140; + *bound = 0.0e0; + *status = -5; + return; +S150: +S140: + if(*which == 4) goto S170; +/* + SCALE +*/ + if(!(*scale <= 0.0e0)) goto S160; + *bound = 0.0e0; + *status = -6; + return; +S170: +S160: + if(*which == 1) goto S210; +/* + P + Q +*/ + pq = *p+*q; + if(!(fabs(pq-0.5e0-0.5e0) > 3.0e0*spmpar(&K1))) goto S200; + if(!(pq < 0.0e0)) goto S180; + *bound = 0.0e0; + goto S190; +S180: + *bound = 1.0e0; +S190: + *status = 3; + return; +S210: +S200: + if(*which == 1) goto S240; +/* + Select the minimum of P or Q +*/ + qporq = *p <= *q; + if(!qporq) goto S220; + porq = *p; + goto S230; +S220: + porq = *q; +S240: +S230: +/* + Calculate ANSWERS +*/ + if(1 == *which) { +/* + Calculating P +*/ + *status = 0; + xscale = *x**scale; + cumgam(&xscale,shape,p,q); + if(porq > 1.5e0) *status = 10; + } + else if(2 == *which) { +/* + Computing X +*/ + T2 = -1.0e0; + gaminv(shape,&xx,&T2,p,q,&ierr); + if(ierr < 0.0e0) { + *status = 10; + return; + } + else { + *x = xx/ *scale; + *status = 0; + } + } + else if(3 == *which) { +/* + Computing SHAPE +*/ + *shape = 5.0e0; + xscale = *x**scale; + T3 = zero; + T4 = inf; + T7 = atol; + T8 = tol; + dstinv(&T3,&T4,&K5,&K5,&K6,&T7,&T8); + *status = 0; + dinvr(status,shape,&fx,&qleft,&qhi); +S250: + if(!(*status == 1)) goto S290; + cumgam(&xscale,shape,&cum,&ccum); + if(!qporq) goto S260; + fx = cum-*p; + goto S270; +S260: + fx = ccum-*q; +S270: + if(!(qporq && cum > 1.5e0 || !qporq && ccum > 1.5e0)) goto S280; + *status = 10; + return; +S280: + dinvr(status,shape,&fx,&qleft,&qhi); + goto S250; +S290: + if(!(*status == -1)) goto S320; + if(!qleft) goto S300; + *status = 1; + *bound = zero; + goto S310; +S300: + *status = 2; + *bound = inf; +S320: +S310: + ; + } + else if(4 == *which) { +/* + Computing SCALE +*/ + T9 = -1.0e0; + gaminv(shape,&xx,&T9,p,q,&ierr); + if(ierr < 0.0e0) { + *status = 10; + return; + } + else { + *scale = xx/ *x; + *status = 0; + } + } + return; +#undef tol +#undef atol +#undef zero +#undef inf +} /* END */ + +/***=====================================================================***/ +static void cdfnbn(int *which,double *p,double *q,double *s,double *xn, + double *pr,double *ompr,int *status,double *bound) +/********************************************************************** + + void cdfnbn(int *which,double *p,double *q,double *s,double *xn, + double *pr,double *ompr,int *status,double *bound) + + Cumulative Distribution Function + Negative BiNomial distribution + + + Function + + + Calculates any one parameter of the negative binomial + distribution given values for the others. + + The cumulative negative binomial distribution returns the + probability that there will be F or fewer failures before the + XNth success in binomial trials each of which has probability of + success PR. + + The individual term of the negative binomial is the probability of + S failures before XN successes and is + Choose( S, XN+S-1 ) * PR^(XN) * (1-PR)^S + + + Arguments + + + WHICH --> Integer indicating which of the next four argument + values is to be calculated from the others. + Legal range: 1..4 + iwhich = 1 : Calculate P and Q from S,XN,PR and OMPR + iwhich = 2 : Calculate S from P,Q,XN,PR and OMPR + iwhich = 3 : Calculate XN from P,Q,S,PR and OMPR + iwhich = 4 : Calculate PR and OMPR from P,Q,S and XN + + P <--> The cumulation from 0 to S of the negative + binomial distribution. + Input range: [0,1]. + + Q <--> 1-P. + Input range: (0, 1]. + P + Q = 1.0. + + S <--> The upper limit of cumulation of the binomial distribution. + There are F or fewer failures before the XNth success. + Input range: [0, +infinity). + Search range: [0, 1E300] + + XN <--> The number of successes. + Input range: [0, +infinity). + Search range: [0, 1E300] + + PR <--> The probability of success in each binomial trial. + Input range: [0,1]. + Search range: [0,1]. + + OMPR <--> 1-PR + Input range: [0,1]. + Search range: [0,1] + PR + OMPR = 1.0 + + STATUS <-- 0 if calculation completed correctly + -I if input parameter number I is out of range + 1 if answer appears to be lower than lowest + search bound + 2 if answer appears to be higher than greatest + search bound + 3 if P + Q .ne. 1 + 4 if PR + OMPR .ne. 1 + + BOUND <-- Undefined if STATUS is 0 + + Bound exceeded by parameter number I if STATUS + is negative. + + Lower search bound if STATUS is 1. + + Upper search bound if STATUS is 2. + + + Method + + + Formula 26.5.26 of Abramowitz and Stegun, Handbook of + Mathematical Functions (1966) is used to reduce calculation of + the cumulative distribution function to that of an incomplete + beta. + + Computation of other parameters involve a seach for a value that + produces the desired value of P. The search relies on the + monotinicity of P with the other parameter. + +**********************************************************************/ +{ +#define tol (1.0e-8) +#define atol (1.0e-50) +#define inf 1.0e300 +#define one 1.0e0 +static int K1 = 1; +static double K2 = 0.0e0; +static double K4 = 0.5e0; +static double K5 = 5.0e0; +static double K11 = 1.0e0; +static double fx,xhi,xlo,pq,prompr,cum,ccum; +static unsigned long qhi,qleft,qporq; +static double T3,T6,T7,T8,T9,T10,T12,T13; +/* + .. + .. Executable Statements .. +*/ +/* + Check arguments +*/ + if(!(*which < 1 || *which > 4)) goto S30; + if(!(*which < 1)) goto S10; + *bound = 1.0e0; + goto S20; +S10: + *bound = 4.0e0; +S20: + *status = -1; + return; +S30: + if(*which == 1) goto S70; +/* + P +*/ + if(!(*p < 0.0e0 || *p > 1.0e0)) goto S60; + if(!(*p < 0.0e0)) goto S40; + *bound = 0.0e0; + goto S50; +S40: + *bound = 1.0e0; +S50: + *status = -2; + return; +S70: +S60: + if(*which == 1) goto S110; +/* + Q +*/ + if(!(*q <= 0.0e0 || *q > 1.0e0)) goto S100; + if(!(*q <= 0.0e0)) goto S80; + *bound = 0.0e0; + goto S90; +S80: + *bound = 1.0e0; +S90: + *status = -3; + return; +S110: +S100: + if(*which == 2) goto S130; +/* + S +*/ + if(!(*s < 0.0e0)) goto S120; + *bound = 0.0e0; + *status = -4; + return; +S130: +S120: + if(*which == 3) goto S150; +/* + XN +*/ + if(!(*xn < 0.0e0)) goto S140; + *bound = 0.0e0; + *status = -5; + return; +S150: +S140: + if(*which == 4) goto S190; +/* + PR +*/ + if(!(*pr < 0.0e0 || *pr > 1.0e0)) goto S180; + if(!(*pr < 0.0e0)) goto S160; + *bound = 0.0e0; + goto S170; +S160: + *bound = 1.0e0; +S170: + *status = -6; + return; +S190: +S180: + if(*which == 4) goto S230; +/* + OMPR +*/ + if(!(*ompr < 0.0e0 || *ompr > 1.0e0)) goto S220; + if(!(*ompr < 0.0e0)) goto S200; + *bound = 0.0e0; + goto S210; +S200: + *bound = 1.0e0; +S210: + *status = -7; + return; +S230: +S220: + if(*which == 1) goto S270; +/* + P + Q +*/ + pq = *p+*q; + if(!(fabs(pq-0.5e0-0.5e0) > 3.0e0*spmpar(&K1))) goto S260; + if(!(pq < 0.0e0)) goto S240; + *bound = 0.0e0; + goto S250; +S240: + *bound = 1.0e0; +S250: + *status = 3; + return; +S270: +S260: + if(*which == 4) goto S310; +/* + PR + OMPR +*/ + prompr = *pr+*ompr; + if(!(fabs(prompr-0.5e0-0.5e0) > 3.0e0*spmpar(&K1))) goto S300; + if(!(prompr < 0.0e0)) goto S280; + *bound = 0.0e0; + goto S290; +S280: + *bound = 1.0e0; +S290: + *status = 4; + return; +S310: +S300: + if(!(*which == 1)) qporq = *p <= *q; +/* + Select the minimum of P or Q + Calculate ANSWERS +*/ + if(1 == *which) { +/* + Calculating P +*/ + cumnbn(s,xn,pr,ompr,p,q); + *status = 0; + } + else if(2 == *which) { +/* + Calculating S +*/ + *s = 5.0e0; + T3 = inf; + T6 = atol; + T7 = tol; + dstinv(&K2,&T3,&K4,&K4,&K5,&T6,&T7); + *status = 0; + dinvr(status,s,&fx,&qleft,&qhi); +S320: + if(!(*status == 1)) goto S350; + cumnbn(s,xn,pr,ompr,&cum,&ccum); + if(!qporq) goto S330; + fx = cum-*p; + goto S340; +S330: + fx = ccum-*q; +S340: + dinvr(status,s,&fx,&qleft,&qhi); + goto S320; +S350: + if(!(*status == -1)) goto S380; + if(!qleft) goto S360; + *status = 1; + *bound = 0.0e0; + goto S370; +S360: + *status = 2; + *bound = inf; +S380: +S370: + ; + } + else if(3 == *which) { +/* + Calculating XN +*/ + *xn = 5.0e0; + T8 = inf; + T9 = atol; + T10 = tol; + dstinv(&K2,&T8,&K4,&K4,&K5,&T9,&T10); + *status = 0; + dinvr(status,xn,&fx,&qleft,&qhi); +S390: + if(!(*status == 1)) goto S420; + cumnbn(s,xn,pr,ompr,&cum,&ccum); + if(!qporq) goto S400; + fx = cum-*p; + goto S410; +S400: + fx = ccum-*q; +S410: + dinvr(status,xn,&fx,&qleft,&qhi); + goto S390; +S420: + if(!(*status == -1)) goto S450; + if(!qleft) goto S430; + *status = 1; + *bound = 0.0e0; + goto S440; +S430: + *status = 2; + *bound = inf; +S450: +S440: + ; + } + else if(4 == *which) { +/* + Calculating PR and OMPR +*/ + T12 = atol; + T13 = tol; + dstzr(&K2,&K11,&T12,&T13); + if(!qporq) goto S480; + *status = 0; + dzror(status,pr,&fx,&xlo,&xhi,&qleft,&qhi); + *ompr = one-*pr; +S460: + if(!(*status == 1)) goto S470; + cumnbn(s,xn,pr,ompr,&cum,&ccum); + fx = cum-*p; + dzror(status,pr,&fx,&xlo,&xhi,&qleft,&qhi); + *ompr = one-*pr; + goto S460; +S470: + goto S510; +S480: + *status = 0; + dzror(status,ompr,&fx,&xlo,&xhi,&qleft,&qhi); + *pr = one-*ompr; +S490: + if(!(*status == 1)) goto S500; + cumnbn(s,xn,pr,ompr,&cum,&ccum); + fx = ccum-*q; + dzror(status,ompr,&fx,&xlo,&xhi,&qleft,&qhi); + *pr = one-*ompr; + goto S490; +S510: +S500: + if(!(*status == -1)) goto S540; + if(!qleft) goto S520; + *status = 1; + *bound = 0.0e0; + goto S530; +S520: + *status = 2; + *bound = 1.0e0; +S530: + ; + } +S540: + return; +#undef tol +#undef atol +#undef inf +#undef one +} /* END */ + +/***=====================================================================***/ +static void cdfnor(int *which,double *p,double *q,double *x,double *mean, + double *sd,int *status,double *bound) +/********************************************************************** + + void cdfnor(int *which,double *p,double *q,double *x,double *mean, + double *sd,int *status,double *bound) + + Cumulative Distribution Function + NORmal distribution + + + Function + + + Calculates any one parameter of the normal + distribution given values for the others. + + + Arguments + + + WHICH --> Integer indicating which of the next parameter + values is to be calculated using values of the others. + Legal range: 1..4 + iwhich = 1 : Calculate P and Q from X,MEAN and SD + iwhich = 2 : Calculate X from P,Q,MEAN and SD + iwhich = 3 : Calculate MEAN from P,Q,X and SD + iwhich = 4 : Calculate SD from P,Q,X and MEAN + + P <--> The integral from -infinity to X of the normal density. + Input range: (0,1]. + + Q <--> 1-P. + Input range: (0, 1]. + P + Q = 1.0. + + X < --> Upper limit of integration of the normal-density. + Input range: ( -infinity, +infinity) + + MEAN <--> The mean of the normal density. + Input range: (-infinity, +infinity) + + SD <--> Standard Deviation of the normal density. + Input range: (0, +infinity). + + STATUS <-- 0 if calculation completed correctly + -I if input parameter number I is out of range + 1 if answer appears to be lower than lowest + search bound + 2 if answer appears to be higher than greatest + search bound + 3 if P + Q .ne. 1 + + BOUND <-- Undefined if STATUS is 0 + + Bound exceeded by parameter number I if STATUS + is negative. + + Lower search bound if STATUS is 1. + + Upper search bound if STATUS is 2. + + + Method + + + + + A slightly modified version of ANORM from + + Cody, W.D. (1993). "ALGORITHM 715: SPECFUN - A Portabel FORTRAN + Package of Special Function Routines and Test Drivers" + acm Transactions on Mathematical Software. 19, 22-32. + + is used to calulate the cumulative standard normal distribution. + + The rational functions from pages 90-95 of Kennedy and Gentle, + Statistical Computing, Marcel Dekker, NY, 1980 are used as + starting values to Newton's Iterations which compute the inverse + standard normal. Therefore no searches are necessary for any + parameter. + + For X < -15, the asymptotic expansion for the normal is used as + the starting value in finding the inverse standard normal. + This is formula 26.2.12 of Abramowitz and Stegun. + + + Note + + + The normal density is proportional to + exp( - 0.5 * (( X - MEAN)/SD)**2) + +**********************************************************************/ +{ +static int K1 = 1; +static double z,pq; +/* + .. + .. Executable Statements .. +*/ +/* + Check arguments +*/ + *status = 0; + if(!(*which < 1 || *which > 4)) goto S30; + if(!(*which < 1)) goto S10; + *bound = 1.0e0; + goto S20; +S10: + *bound = 4.0e0; +S20: + *status = -1; + return; +S30: + if(*which == 1) goto S70; +/* + P +*/ + if(!(*p <= 0.0e0 || *p > 1.0e0)) goto S60; + if(!(*p <= 0.0e0)) goto S40; + *bound = 0.0e0; + goto S50; +S40: + *bound = 1.0e0; +S50: + *status = -2; + return; +S70: +S60: + if(*which == 1) goto S110; +/* + Q +*/ + if(!(*q <= 0.0e0 || *q > 1.0e0)) goto S100; + if(!(*q <= 0.0e0)) goto S80; + *bound = 0.0e0; + goto S90; +S80: + *bound = 1.0e0; +S90: + *status = -3; + return; +S110: +S100: + if(*which == 1) goto S150; +/* + P + Q +*/ + pq = *p+*q; + if(!(fabs(pq-0.5e0-0.5e0) > 3.0e0*spmpar(&K1))) goto S140; + if(!(pq < 0.0e0)) goto S120; + *bound = 0.0e0; + goto S130; +S120: + *bound = 1.0e0; +S130: + *status = 3; + return; +S150: +S140: + if(*which == 4) goto S170; +/* + SD +*/ + if(!(*sd <= 0.0e0)) goto S160; + *bound = 0.0e0; + *status = -6; + return; +S170: +S160: +/* + Calculate ANSWERS +*/ + if(1 == *which) { +/* + Computing P +*/ + z = (*x-*mean)/ *sd; + cumnor(&z,p,q); + } + else if(2 == *which) { +/* + Computing X +*/ + z = dinvnr(p,q); + *x = *sd*z+*mean; + } + else if(3 == *which) { +/* + Computing the MEAN +*/ + z = dinvnr(p,q); + *mean = *x-*sd*z; + } + else if(4 == *which) { +/* + Computing SD +*/ + z = dinvnr(p,q); + *sd = (*x-*mean)/z; + } + return; +} /* END */ + +/***=====================================================================***/ +static void cdfpoi(int *which,double *p,double *q,double *s,double *xlam, + int *status,double *bound) +/********************************************************************** + + void cdfpoi(int *which,double *p,double *q,double *s,double *xlam, + int *status,double *bound) + + Cumulative Distribution Function + POIsson distribution + + + Function + + + Calculates any one parameter of the Poisson + distribution given values for the others. + + + Arguments + + + WHICH --> Integer indicating which argument + value is to be calculated from the others. + Legal range: 1..3 + iwhich = 1 : Calculate P and Q from S and XLAM + iwhich = 2 : Calculate A from P,Q and XLAM + iwhich = 3 : Calculate XLAM from P,Q and S + + P <--> The cumulation from 0 to S of the poisson density. + Input range: [0,1]. + + Q <--> 1-P. + Input range: (0, 1]. + P + Q = 1.0. + + S <--> Upper limit of cumulation of the Poisson. + Input range: [0, +infinity). + Search range: [0,1E300] + + XLAM <--> Mean of the Poisson distribution. + Input range: [0, +infinity). + Search range: [0,1E300] + + STATUS <-- 0 if calculation completed correctly + -I if input parameter number I is out of range + 1 if answer appears to be lower than lowest + search bound + 2 if answer appears to be higher than greatest + search bound + 3 if P + Q .ne. 1 + + BOUND <-- Undefined if STATUS is 0 + + Bound exceeded by parameter number I if STATUS + is negative. + + Lower search bound if STATUS is 1. + + Upper search bound if STATUS is 2. + + + Method + + + Formula 26.4.21 of Abramowitz and Stegun, Handbook of + Mathematical Functions (1966) is used to reduce the computation + of the cumulative distribution function to that of computing a + chi-square, hence an incomplete gamma function. + + Cumulative distribution function (P) is calculated directly. + Computation of other parameters involve a seach for a value that + produces the desired value of P. The search relies on the + monotinicity of P with the other parameter. + +**********************************************************************/ +{ +#define tol (1.0e-8) +#define atol (1.0e-50) +#define inf 1.0e300 +static int K1 = 1; +static double K2 = 0.0e0; +static double K4 = 0.5e0; +static double K5 = 5.0e0; +static double fx,cum,ccum,pq; +static unsigned long qhi,qleft,qporq; +static double T3,T6,T7,T8,T9,T10; +/* + .. + .. Executable Statements .. +*/ +/* + Check arguments +*/ + if(!(*which < 1 || *which > 3)) goto S30; + if(!(*which < 1)) goto S10; + *bound = 1.0e0; + goto S20; +S10: + *bound = 3.0e0; +S20: + *status = -1; + return; +S30: + if(*which == 1) goto S70; +/* + P +*/ + if(!(*p < 0.0e0 || *p > 1.0e0)) goto S60; + if(!(*p < 0.0e0)) goto S40; + *bound = 0.0e0; + goto S50; +S40: + *bound = 1.0e0; +S50: + *status = -2; + return; +S70: +S60: + if(*which == 1) goto S110; +/* + Q +*/ + if(!(*q <= 0.0e0 || *q > 1.0e0)) goto S100; + if(!(*q <= 0.0e0)) goto S80; + *bound = 0.0e0; + goto S90; +S80: + *bound = 1.0e0; +S90: + *status = -3; + return; +S110: +S100: + if(*which == 2) goto S130; +/* + S +*/ + if(!(*s < 0.0e0)) goto S120; + *bound = 0.0e0; + *status = -4; + return; +S130: +S120: + if(*which == 3) goto S150; +/* + XLAM +*/ + if(!(*xlam < 0.0e0)) goto S140; + *bound = 0.0e0; + *status = -5; + return; +S150: +S140: + if(*which == 1) goto S190; +/* + P + Q +*/ + pq = *p+*q; + if(!(fabs(pq-0.5e0-0.5e0) > 3.0e0*spmpar(&K1))) goto S180; + if(!(pq < 0.0e0)) goto S160; + *bound = 0.0e0; + goto S170; +S160: + *bound = 1.0e0; +S170: + *status = 3; + return; +S190: +S180: + if(!(*which == 1)) qporq = *p <= *q; +/* + Select the minimum of P or Q + Calculate ANSWERS +*/ + if(1 == *which) { +/* + Calculating P +*/ + cumpoi(s,xlam,p,q); + *status = 0; + } + else if(2 == *which) { +/* + Calculating S +*/ + *s = 5.0e0; + T3 = inf; + T6 = atol; + T7 = tol; + dstinv(&K2,&T3,&K4,&K4,&K5,&T6,&T7); + *status = 0; + dinvr(status,s,&fx,&qleft,&qhi); +S200: + if(!(*status == 1)) goto S230; + cumpoi(s,xlam,&cum,&ccum); + if(!qporq) goto S210; + fx = cum-*p; + goto S220; +S210: + fx = ccum-*q; +S220: + dinvr(status,s,&fx,&qleft,&qhi); + goto S200; +S230: + if(!(*status == -1)) goto S260; + if(!qleft) goto S240; + *status = 1; + *bound = 0.0e0; + goto S250; +S240: + *status = 2; + *bound = inf; +S260: +S250: + ; + } + else if(3 == *which) { +/* + Calculating XLAM +*/ + *xlam = 5.0e0; + T8 = inf; + T9 = atol; + T10 = tol; + dstinv(&K2,&T8,&K4,&K4,&K5,&T9,&T10); + *status = 0; + dinvr(status,xlam,&fx,&qleft,&qhi); +S270: + if(!(*status == 1)) goto S300; + cumpoi(s,xlam,&cum,&ccum); + if(!qporq) goto S280; + fx = cum-*p; + goto S290; +S280: + fx = ccum-*q; +S290: + dinvr(status,xlam,&fx,&qleft,&qhi); + goto S270; +S300: + if(!(*status == -1)) goto S330; + if(!qleft) goto S310; + *status = 1; + *bound = 0.0e0; + goto S320; +S310: + *status = 2; + *bound = inf; +S320: + ; + } +S330: + return; +#undef tol +#undef atol +#undef inf +} /* END */ + +/***=====================================================================***/ +static void cdft(int *which,double *p,double *q,double *t,double *df, + int *status,double *bound) +/********************************************************************** + + void cdft(int *which,double *p,double *q,double *t,double *df, + int *status,double *bound) + + Cumulative Distribution Function + T distribution + + + Function + + + Calculates any one parameter of the t distribution given + values for the others. + + + Arguments + + + WHICH --> Integer indicating which argument + values is to be calculated from the others. + Legal range: 1..3 + iwhich = 1 : Calculate P and Q from T and DF + iwhich = 2 : Calculate T from P,Q and DF + iwhich = 3 : Calculate DF from P,Q and T + + P <--> The integral from -infinity to t of the t-density. + Input range: (0,1]. + + Q <--> 1-P. + Input range: (0, 1]. + P + Q = 1.0. + + T <--> Upper limit of integration of the t-density. + Input range: ( -infinity, +infinity). + Search range: [ -1E300, 1E300 ] + + DF <--> Degrees of freedom of the t-distribution. + Input range: (0 , +infinity). + Search range: [1e-300, 1E10] + + STATUS <-- 0 if calculation completed correctly + -I if input parameter number I is out of range + 1 if answer appears to be lower than lowest + search bound + 2 if answer appears to be higher than greatest + search bound + 3 if P + Q .ne. 1 + + BOUND <-- Undefined if STATUS is 0 + + Bound exceeded by parameter number I if STATUS + is negative. + + Lower search bound if STATUS is 1. + + Upper search bound if STATUS is 2. + + + Method + + + Formula 26.5.27 of Abramowitz and Stegun, Handbook of + Mathematical Functions (1966) is used to reduce the computation + of the cumulative distribution function to that of an incomplete + beta. + + Computation of other parameters involve a seach for a value that + produces the desired value of P. The search relies on the + monotinicity of P with the other parameter. + +**********************************************************************/ +{ +#define tol (1.0e-8) +#define atol (1.0e-50) +#define zero (1.0e-300) +#define inf 1.0e300 +#define maxdf 1.0e10 +static int K1 = 1; +static double K4 = 0.5e0; +static double K5 = 5.0e0; +static double fx,cum,ccum,pq; +static unsigned long qhi,qleft,qporq; +static double T2,T3,T6,T7,T8,T9,T10,T11; +/* + .. + .. Executable Statements .. +*/ +/* + Check arguments +*/ + if(!(*which < 1 || *which > 3)) goto S30; + if(!(*which < 1)) goto S10; + *bound = 1.0e0; + goto S20; +S10: + *bound = 3.0e0; +S20: + *status = -1; + return; +S30: + if(*which == 1) goto S70; +/* + P +*/ + if(!(*p <= 0.0e0 || *p > 1.0e0)) goto S60; + if(!(*p <= 0.0e0)) goto S40; + *bound = 0.0e0; + goto S50; +S40: + *bound = 1.0e0; +S50: + *status = -2; + return; +S70: +S60: + if(*which == 1) goto S110; +/* + Q +*/ + if(!(*q <= 0.0e0 || *q > 1.0e0)) goto S100; + if(!(*q <= 0.0e0)) goto S80; + *bound = 0.0e0; + goto S90; +S80: + *bound = 1.0e0; +S90: + *status = -3; + return; +S110: +S100: + if(*which == 3) goto S130; +/* + DF +*/ + if(!(*df <= 0.0e0)) goto S120; + *bound = 0.0e0; + *status = -5; + return; +S130: +S120: + if(*which == 1) goto S170; +/* + P + Q +*/ + pq = *p+*q; + if(!(fabs(pq-0.5e0-0.5e0) > 3.0e0*spmpar(&K1))) goto S160; + if(!(pq < 0.0e0)) goto S140; + *bound = 0.0e0; + goto S150; +S140: + *bound = 1.0e0; +S150: + *status = 3; + return; +S170: +S160: + if(!(*which == 1)) qporq = *p <= *q; +/* + Select the minimum of P or Q + Calculate ANSWERS +*/ + if(1 == *which) { +/* + Computing P and Q +*/ + cumt(t,df,p,q); + *status = 0; + } + else if(2 == *which) { +/* + Computing T + .. Get initial approximation for T +*/ + *t = dt1(p,q,df); + T2 = -inf; + T3 = inf; + T6 = atol; + T7 = tol; + dstinv(&T2,&T3,&K4,&K4,&K5,&T6,&T7); + *status = 0; + dinvr(status,t,&fx,&qleft,&qhi); +S180: + if(!(*status == 1)) goto S210; + cumt(t,df,&cum,&ccum); + if(!qporq) goto S190; + fx = cum-*p; + goto S200; +S190: + fx = ccum-*q; +S200: + dinvr(status,t,&fx,&qleft,&qhi); + goto S180; +S210: + if(!(*status == -1)) goto S240; + if(!qleft) goto S220; + *status = 1; + *bound = -inf; + goto S230; +S220: + *status = 2; + *bound = inf; +S240: +S230: + ; + } + else if(3 == *which) { +/* + Computing DF +*/ + *df = 5.0e0; + T8 = zero; + T9 = maxdf; + T10 = atol; + T11 = tol; + dstinv(&T8,&T9,&K4,&K4,&K5,&T10,&T11); + *status = 0; + dinvr(status,df,&fx,&qleft,&qhi); +S250: + if(!(*status == 1)) goto S280; + cumt(t,df,&cum,&ccum); + if(!qporq) goto S260; + fx = cum-*p; + goto S270; +S260: + fx = ccum-*q; +S270: + dinvr(status,df,&fx,&qleft,&qhi); + goto S250; +S280: + if(!(*status == -1)) goto S310; + if(!qleft) goto S290; + *status = 1; + *bound = zero; + goto S300; +S290: + *status = 2; + *bound = maxdf; +S300: + ; + } +S310: + return; +#undef tol +#undef atol +#undef zero +#undef inf +#undef maxdf +} /* END */ + +/***=====================================================================***/ +static void cumbet(double *x,double *y,double *a,double *b,double *cum, + double *ccum) +/* +********************************************************************** + + void cumbet(double *x,double *y,double *a,double *b,double *cum, + double *ccum) + + Double precision cUMulative incomplete BETa distribution + + + Function + + + Calculates the cdf to X of the incomplete beta distribution + with parameters a and b. This is the integral from 0 to x + of (1/B(a,b))*f(t)) where f(t) = t**(a-1) * (1-t)**(b-1) + + + Arguments + + + X --> Upper limit of integration. + X is DOUBLE PRECISION + + Y --> 1 - X. + Y is DOUBLE PRECISION + + A --> First parameter of the beta distribution. + A is DOUBLE PRECISION + + B --> Second parameter of the beta distribution. + B is DOUBLE PRECISION + + CUM <-- Cumulative incomplete beta distribution. + CUM is DOUBLE PRECISION + + CCUM <-- Compliment of Cumulative incomplete beta distribution. + CCUM is DOUBLE PRECISION + + + Method + + + Calls the routine BRATIO. + + References + + Didonato, Armido R. and Morris, Alfred H. Jr. (1992) Algorithim + 708 Significant Digit Computation of the Incomplete Beta Function + Ratios. ACM ToMS, Vol.18, No. 3, Sept. 1992, 360-373. + +********************************************************************** +*/ +{ +static int ierr; +/* + .. + .. Executable Statements .. +*/ + if(!(*x <= 0.0e0)) goto S10; + *cum = 0.0e0; + *ccum = 1.0e0; + return; +S10: + if(!(*y <= 0.0e0)) goto S20; + *cum = 1.0e0; + *ccum = 0.0e0; + return; +S20: + bratio(a,b,x,y,cum,ccum,&ierr); +/* + Call bratio routine +*/ + return; +} /* END */ + +/***=====================================================================***/ +static void cumbin(double *s,double *xn,double *pr,double *ompr, + double *cum,double *ccum) +/* +********************************************************************** + + void cumbin(double *s,double *xn,double *pr,double *ompr, + double *cum,double *ccum) + + CUmulative BINomial distribution + + + Function + + + Returns the probability of 0 to S successes in XN binomial + trials, each of which has a probability of success, PBIN. + + + Arguments + + + S --> The upper limit of cumulation of the binomial distribution. + S is DOUBLE PRECISION + + XN --> The number of binomial trials. + XN is DOUBLE PRECISIO + + PBIN --> The probability of success in each binomial trial. + PBIN is DOUBLE PRECIS + + OMPR --> 1 - PBIN + OMPR is DOUBLE PRECIS + + CUM <-- Cumulative binomial distribution. + CUM is DOUBLE PRECISI + + CCUM <-- Compliment of Cumulative binomial distribution. + CCUM is DOUBLE PRECIS + + + Method + + + Formula 26.5.24 of Abramowitz and Stegun, Handbook of + Mathematical Functions (1966) is used to reduce the binomial + distribution to the cumulative beta distribution. + +********************************************************************** +*/ +{ +static double T1,T2; +/* + .. + .. Executable Statements .. +*/ + if(!(*s < *xn)) goto S10; + T1 = *s+1.0e0; + T2 = *xn-*s; + cumbet(pr,ompr,&T1,&T2,ccum,cum); + goto S20; +S10: + *cum = 1.0e0; + *ccum = 0.0e0; +S20: + return; +} /* END */ + +/***=====================================================================***/ +static void cumchi(double *x,double *df,double *cum,double *ccum) +/* +********************************************************************** + + void cumchi(double *x,double *df,double *cum,double *ccum) + CUMulative of the CHi-square distribution + + + Function + + + Calculates the cumulative chi-square distribution. + + + Arguments + + + X --> Upper limit of integration of the + chi-square distribution. + X is DOUBLE PRECISION + + DF --> Degrees of freedom of the + chi-square distribution. + DF is DOUBLE PRECISION + + CUM <-- Cumulative chi-square distribution. + CUM is DOUBLE PRECISIO + + CCUM <-- Compliment of Cumulative chi-square distribution. + CCUM is DOUBLE PRECISI + + + Method + + + Calls incomplete gamma function (CUMGAM) + +********************************************************************** +*/ +{ +static double a,xx; +/* + .. + .. Executable Statements .. +*/ + a = *df*0.5e0; + xx = *x*0.5e0; + cumgam(&xx,&a,cum,ccum); + return; +} /* END */ + +/***=====================================================================***/ +static void cumchn(double *x,double *df,double *pnonc,double *cum, + double *ccum) +/* +********************************************************************** + + void cumchn(double *x,double *df,double *pnonc,double *cum, + double *ccum) + + CUMulative of the Non-central CHi-square distribution + + + Function + + + Calculates the cumulative non-central chi-square + distribution, i.e., the probability that a random variable + which follows the non-central chi-square distribution, with + non-centrality parameter PNONC and continuous degrees of + freedom DF, is less than or equal to X. + + + Arguments + + + X --> Upper limit of integration of the non-central + chi-square distribution. + X is DOUBLE PRECISION + + DF --> Degrees of freedom of the non-central + chi-square distribution. + DF is DOUBLE PRECISION + + PNONC --> Non-centrality parameter of the non-central + chi-square distribution. + PNONC is DOUBLE PRECIS + + CUM <-- Cumulative non-central chi-square distribution. + CUM is DOUBLE PRECISIO + + CCUM <-- Compliment of Cumulative non-central chi-square distribut + CCUM is DOUBLE PRECISI + + + Method + + + Uses formula 26.4.25 of Abramowitz and Stegun, Handbook of + Mathematical Functions, US NBS (1966) to calculate the + non-central chi-square. + + + Variables + + + EPS --- Convergence criterion. The sum stops when a + term is less than EPS*SUM. + EPS is DOUBLE PRECISIO + + NTIRED --- Maximum number of terms to be evaluated + in each sum. + NTIRED is INTEGER + + QCONV --- .TRUE. if convergence achieved - + i.e., program did not stop on NTIRED criterion. + QCONV is LOGICAL + + CCUM <-- Compliment of Cumulative non-central + chi-square distribution. + CCUM is DOUBLE PRECISI + +********************************************************************** +*/ +{ +#define dg(i) (*df+2.0e0*(double)(i)) +#define qsmall(xx) (int)(sum < 1.0e-20 || (xx) < eps*sum) +#define qtired(i) (int)((i) > ntired) +static double eps = 1.0e-5; +static int ntired = 1000; +static double adj,centaj,centwt,chid2,dfd2,lcntaj,lcntwt,lfact,pcent,pterm,sum, + sumadj,term,wt,xnonc; +static int i,icent,iterb,iterf; +static double T1,T2,T3; +/* + .. + .. Executable Statements .. +*/ + if(!(*x <= 0.0e0)) goto S10; + *cum = 0.0e0; + *ccum = 1.0e0; + return; +S10: + if(!(*pnonc <= 1.0e-10)) goto S20; +/* + When non-centrality parameter is (essentially) zero, + use cumulative chi-square distribution +*/ + cumchi(x,df,cum,ccum); + return; +S20: + xnonc = *pnonc/2.0e0; +/* +********************************************************************** + The following code calcualtes the weight, chi-square, and + adjustment term for the central term in the infinite series. + The central term is the one in which the poisson weight is + greatest. The adjustment term is the amount that must + be subtracted from the chi-square to move up two degrees + of freedom. +********************************************************************** +*/ + icent = fifidint(xnonc); + if(icent == 0) icent = 1; + chid2 = *x/2.0e0; +/* + Calculate central weight term +*/ + T1 = (double)(icent+1); + lfact = alngam(&T1); + lcntwt = -xnonc+(double)icent*log(xnonc)-lfact; + centwt = exp(lcntwt); +/* + Calculate central chi-square +*/ + T2 = dg(icent); + cumchi(x,&T2,&pcent,ccum); +/* + Calculate central adjustment term +*/ + dfd2 = dg(icent)/2.0e0; + T3 = 1.0e0+dfd2; + lfact = alngam(&T3); + lcntaj = dfd2*log(chid2)-chid2-lfact; + centaj = exp(lcntaj); + sum = centwt*pcent; +/* +********************************************************************** + Sum backwards from the central term towards zero. + Quit whenever either + (1) the zero term is reached, or + (2) the term gets small relative to the sum, or + (3) More than NTIRED terms are totaled. +********************************************************************** +*/ + iterb = 0; + sumadj = 0.0e0; + adj = centaj; + wt = centwt; + i = icent; + goto S40; +S30: + if(qtired(iterb) || qsmall(term) || i == 0) goto S50; +S40: + dfd2 = dg(i)/2.0e0; +/* + Adjust chi-square for two fewer degrees of freedom. + The adjusted value ends up in PTERM. +*/ + adj = adj*dfd2/chid2; + sumadj += adj; + pterm = pcent+sumadj; +/* + Adjust poisson weight for J decreased by one +*/ + wt *= ((double)i/xnonc); + term = wt*pterm; + sum += term; + i -= 1; + iterb += 1; + goto S30; +S50: + iterf = 0; +/* +********************************************************************** + Now sum forward from the central term towards infinity. + Quit when either + (1) the term gets small relative to the sum, or + (2) More than NTIRED terms are totaled. +********************************************************************** +*/ + sumadj = adj = centaj; + wt = centwt; + i = icent; + goto S70; +S60: + if(qtired(iterf) || qsmall(term)) goto S80; +S70: +/* + Update weights for next higher J +*/ + wt *= (xnonc/(double)(i+1)); +/* + Calculate PTERM and add term to sum +*/ + pterm = pcent-sumadj; + term = wt*pterm; + sum += term; +/* + Update adjustment term for DF for next iteration +*/ + i += 1; + dfd2 = dg(i)/2.0e0; + adj = adj*chid2/dfd2; + sumadj += adj; + iterf += 1; + goto S60; +S80: + *cum = sum; + *ccum = 0.5e0+(0.5e0-*cum); + return; +#undef dg +#undef qsmall +#undef qtired +} /* END */ + +/***=====================================================================***/ +static void cumf(double *f,double *dfn,double *dfd,double *cum,double *ccum) +/* +********************************************************************** + + void cumf(double *f,double *dfn,double *dfd,double *cum,double *ccum) + CUMulative F distribution + + + Function + + + Computes the integral from 0 to F of the f-density with DFN + and DFD degrees of freedom. + + + Arguments + + + F --> Upper limit of integration of the f-density. + F is DOUBLE PRECISION + + DFN --> Degrees of freedom of the numerator sum of squares. + DFN is DOUBLE PRECISI + + DFD --> Degrees of freedom of the denominator sum of squares. + DFD is DOUBLE PRECISI + + CUM <-- Cumulative f distribution. + CUM is DOUBLE PRECISI + + CCUM <-- Compliment of Cumulative f distribution. + CCUM is DOUBLE PRECIS + + + Method + + + Formula 26.5.28 of Abramowitz and Stegun is used to reduce + the cumulative F to a cumulative beta distribution. + + + Note + + + If F is less than or equal to 0, 0 is returned. + +********************************************************************** +*/ +{ +#define half 0.5e0 +#define done 1.0e0 +static double dsum,prod,xx,yy; +static int ierr; +static double T1,T2; +/* + .. + .. Executable Statements .. +*/ + if(!(*f <= 0.0e0)) goto S10; + *cum = 0.0e0; + *ccum = 1.0e0; + return; +S10: + prod = *dfn**f; +/* + XX is such that the incomplete beta with parameters + DFD/2 and DFN/2 evaluated at XX is 1 - CUM or CCUM + YY is 1 - XX + Calculate the smaller of XX and YY accurately +*/ + dsum = *dfd+prod; + xx = *dfd/dsum; + if(xx > half) { + yy = prod/dsum; + xx = done-yy; + } + else yy = done-xx; + T1 = *dfd*half; + T2 = *dfn*half; + bratio(&T1,&T2,&xx,&yy,ccum,cum,&ierr); + return; +#undef half +#undef done +} /* END */ + +/***=====================================================================***/ +static void cumfnc(double *f,double *dfn,double *dfd,double *pnonc, + double *cum,double *ccum) +/* +********************************************************************** + + F -NON- -C-ENTRAL F DISTRIBUTION + + + + Function + + + COMPUTES NONCENTRAL F DISTRIBUTION WITH DFN AND DFD + DEGREES OF FREEDOM AND NONCENTRALITY PARAMETER PNONC + + + Arguments + + + X --> UPPER LIMIT OF INTEGRATION OF NONCENTRAL F IN EQUATION + + DFN --> DEGREES OF FREEDOM OF NUMERATOR + + DFD --> DEGREES OF FREEDOM OF DENOMINATOR + + PNONC --> NONCENTRALITY PARAMETER. + + CUM <-- CUMULATIVE NONCENTRAL F DISTRIBUTION + + CCUM <-- COMPLIMENT OF CUMMULATIVE + + + Method + + + USES FORMULA 26.6.20 OF REFERENCE FOR INFINITE SERIES. + SERIES IS CALCULATED BACKWARD AND FORWARD FROM J = LAMBDA/2 + (THIS IS THE TERM WITH THE LARGEST POISSON WEIGHT) UNTIL + THE CONVERGENCE CRITERION IS MET. + + FOR SPEED, THE INCOMPLETE BETA FUNCTIONS ARE EVALUATED + BY FORMULA 26.5.16. + + + REFERENCE + + + HANDBOOD OF MATHEMATICAL FUNCTIONS + EDITED BY MILTON ABRAMOWITZ AND IRENE A. STEGUN + NATIONAL BUREAU OF STANDARDS APPLIED MATEMATICS SERIES - 55 + MARCH 1965 + P 947, EQUATIONS 26.6.17, 26.6.18 + + + Note + + + THE SUM CONTINUES UNTIL A SUCCEEDING TERM IS LESS THAN EPS + TIMES THE SUM (OR THE SUM IS LESS THAN 1.0E-20). EPS IS + SET TO 1.0E-4 IN A DATA STATEMENT WHICH CAN BE CHANGED. + +********************************************************************** +*/ +{ +#define qsmall(x) (int)(sum < 1.0e-20 || (x) < eps*sum) +#define half 0.5e0 +#define done 1.0e0 +static double eps = 1.0e-4; +static double dsum,dummy,prod,xx,yy,adn,aup,b,betdn,betup,centwt,dnterm,sum, + upterm,xmult,xnonc; +static int i,icent,ierr; +static double T1,T2,T3,T4,T5,T6; +/* + .. + .. Executable Statements .. +*/ + if(!(*f <= 0.0e0)) goto S10; + *cum = 0.0e0; + *ccum = 1.0e0; + return; +S10: + if(!(*pnonc < 1.0e-10)) goto S20; +/* + Handle case in which the non-centrality parameter is + (essentially) zero. +*/ + cumf(f,dfn,dfd,cum,ccum); + return; +S20: + xnonc = *pnonc/2.0e0; +/* + Calculate the central term of the poisson weighting factor. +*/ + icent = xnonc; + if(icent == 0) icent = 1; +/* + Compute central weight term +*/ + T1 = (double)(icent+1); + centwt = exp(-xnonc+(double)icent*log(xnonc)-alngam(&T1)); +/* + Compute central incomplete beta term + Assure that minimum of arg to beta and 1 - arg is computed + accurately. +*/ + prod = *dfn**f; + dsum = *dfd+prod; + yy = *dfd/dsum; + if(yy > half) { + xx = prod/dsum; + yy = done-xx; + } + else xx = done-yy; + T2 = *dfn*half+(double)icent; + T3 = *dfd*half; + bratio(&T2,&T3,&xx,&yy,&betdn,&dummy,&ierr); + adn = *dfn/2.0e0+(double)icent; + aup = adn; + b = *dfd/2.0e0; + betup = betdn; + sum = centwt*betdn; +/* + Now sum terms backward from icent until convergence or all done +*/ + xmult = centwt; + i = icent; + T4 = adn+b; + T5 = adn+1.0e0; + dnterm = exp(alngam(&T4)-alngam(&T5)-alngam(&b)+adn*log(xx)+b*log(yy)); +S30: + if(qsmall(xmult*betdn) || i <= 0) goto S40; + xmult *= ((double)i/xnonc); + i -= 1; + adn -= 1.0; + dnterm = (adn+1.0)/((adn+b)*xx)*dnterm; + betdn += dnterm; + sum += (xmult*betdn); + goto S30; +S40: + i = icent+1; +/* + Now sum forwards until convergence +*/ + xmult = centwt; + if(aup-1.0+b == 0) upterm = exp(-alngam(&aup)-alngam(&b)+(aup-1.0)*log(xx)+ + b*log(yy)); + else { + T6 = aup-1.0+b; + upterm = exp(alngam(&T6)-alngam(&aup)-alngam(&b)+(aup-1.0)*log(xx)+b* + log(yy)); + } + goto S60; +S50: + if(qsmall(xmult*betup)) goto S70; +S60: + xmult *= (xnonc/(double)i); + i += 1; + aup += 1.0; + upterm = (aup+b-2.0e0)*xx/(aup-1.0)*upterm; + betup -= upterm; + sum += (xmult*betup); + goto S50; +S70: + *cum = sum; + *ccum = 0.5e0+(0.5e0-*cum); + return; +#undef qsmall +#undef half +#undef done +} /* END */ + +/***=====================================================================***/ +static void cumgam(double *x,double *a,double *cum,double *ccum) +/* +********************************************************************** + + void cumgam(double *x,double *a,double *cum,double *ccum) + Double precision cUMulative incomplete GAMma distribution + + + Function + + + Computes the cumulative of the incomplete gamma + distribution, i.e., the integral from 0 to X of + (1/GAM(A))*EXP(-T)*T**(A-1) DT + where GAM(A) is the complete gamma function of A, i.e., + GAM(A) = integral from 0 to infinity of + EXP(-T)*T**(A-1) DT + + + Arguments + + + X --> The upper limit of integration of the incomplete gamma. + X is DOUBLE PRECISION + + A --> The shape parameter of the incomplete gamma. + A is DOUBLE PRECISION + + CUM <-- Cumulative incomplete gamma distribution. + CUM is DOUBLE PRECISION + + CCUM <-- Compliment of Cumulative incomplete gamma distribution. + CCUM is DOUBLE PRECISIO + + + Method + + + Calls the routine GRATIO. + +********************************************************************** +*/ +{ +static int K1 = 0; +/* + .. + .. Executable Statements .. +*/ + if(!(*x <= 0.0e0)) goto S10; + *cum = 0.0e0; + *ccum = 1.0e0; + return; +S10: + gratio(a,x,cum,ccum,&K1); +/* + Call gratio routine +*/ + return; +} /* END */ + +/***=====================================================================***/ +static void cumnbn(double *s,double *xn,double *pr,double *ompr, + double *cum,double *ccum) +/* +********************************************************************** + + void cumnbn(double *s,double *xn,double *pr,double *ompr, + double *cum,double *ccum) + + CUmulative Negative BINomial distribution + + + Function + + + Returns the probability that it there will be S or fewer failures + before there are XN successes, with each binomial trial having + a probability of success PR. + + Prob(# failures = S | XN successes, PR) = + ( XN + S - 1 ) + ( ) * PR^XN * (1-PR)^S + ( S ) + + + Arguments + + + S --> The number of failures + S is DOUBLE PRECISION + + XN --> The number of successes + XN is DOUBLE PRECISIO + + PR --> The probability of success in each binomial trial. + PR is DOUBLE PRECISIO + + OMPR --> 1 - PR + OMPR is DOUBLE PRECIS + + CUM <-- Cumulative negative binomial distribution. + CUM is DOUBLE PRECISI + + CCUM <-- Compliment of Cumulative negative binomial distribution. + CCUM is DOUBLE PRECIS + + + Method + + + Formula 26.5.26 of Abramowitz and Stegun, Handbook of + Mathematical Functions (1966) is used to reduce the negative + binomial distribution to the cumulative beta distribution. + +********************************************************************** +*/ +{ +static double T1; +/* + .. + .. Executable Statements .. +*/ + T1 = *s+1.e0; + cumbet(pr,ompr,xn,&T1,cum,ccum); + return; +} /* END */ + +/***=====================================================================***/ +static void cumnor(double *arg,double *result,double *ccum) +/* +********************************************************************** + + void cumnor(double *arg,double *result,double *ccum) + + + Function + + + Computes the cumulative of the normal distribution, i.e., + the integral from -infinity to x of + (1/sqrt(2*pi)) exp(-u*u/2) du + + X --> Upper limit of integration. + X is DOUBLE PRECISION + + RESULT <-- Cumulative normal distribution. + RESULT is DOUBLE PRECISION + + CCUM <-- Compliment of Cumulative normal distribution. + CCUM is DOUBLE PRECISION + + Renaming of function ANORM from: + + Cody, W.D. (1993). "ALGORITHM 715: SPECFUN - A Portabel FORTRAN + Package of Special Function Routines and Test Drivers" + acm Transactions on Mathematical Software. 19, 22-32. + + with slight modifications to return ccum and to deal with + machine constants. + +********************************************************************** + Original Comments: +------------------------------------------------------------------ + + This function evaluates the normal distribution function: + + / x + 1 | -t*t/2 + P(x) = ----------- | e dt + sqrt(2 pi) | + /-oo + + The main computation evaluates near-minimax approximations + derived from those in "Rational Chebyshev approximations for + the error function" by W. J. Cody, Math. Comp., 1969, 631-637. + This transportable program uses rational functions that + theoretically approximate the normal distribution function to + at least 18 significant decimal digits. The accuracy achieved + depends on the arithmetic system, the compiler, the intrinsic + functions, and proper selection of the machine-dependent + constants. + +******************************************************************* +******************************************************************* + + Explanation of machine-dependent constants. + + MIN = smallest machine representable number. + + EPS = argument below which anorm(x) may be represented by + 0.5 and above which x*x will not underflow. + A conservative value is the largest machine number X + such that 1.0 + X = 1.0 to machine precision. +******************************************************************* +******************************************************************* + + Error returns + + The program returns ANORM = 0 for ARG .LE. XLOW. + + + Intrinsic functions required are: + + ABS, AINT, EXP + + + Author: W. J. Cody + Mathematics and Computer Science Division + Argonne National Laboratory + Argonne, IL 60439 + + Latest modification: March 15, 1992 + +------------------------------------------------------------------ +*/ +{ +static double a[5] = { + 2.2352520354606839287e00,1.6102823106855587881e02,1.0676894854603709582e03, + 1.8154981253343561249e04,6.5682337918207449113e-2 +}; +static double b[4] = { + 4.7202581904688241870e01,9.7609855173777669322e02,1.0260932208618978205e04, + 4.5507789335026729956e04 +}; +static double c[9] = { + 3.9894151208813466764e-1,8.8831497943883759412e00,9.3506656132177855979e01, + 5.9727027639480026226e02,2.4945375852903726711e03,6.8481904505362823326e03, + 1.1602651437647350124e04,9.8427148383839780218e03,1.0765576773720192317e-8 +}; +static double d[8] = { + 2.2266688044328115691e01,2.3538790178262499861e02,1.5193775994075548050e03, + 6.4855582982667607550e03,1.8615571640885098091e04,3.4900952721145977266e04, + 3.8912003286093271411e04,1.9685429676859990727e04 +}; +static double half = 0.5e0; +static double p[6] = { + 2.1589853405795699e-1,1.274011611602473639e-1,2.2235277870649807e-2, + 1.421619193227893466e-3,2.9112874951168792e-5,2.307344176494017303e-2 +}; +static double one = 1.0e0; +static double q[5] = { + 1.28426009614491121e00,4.68238212480865118e-1,6.59881378689285515e-2, + 3.78239633202758244e-3,7.29751555083966205e-5 +}; +static double sixten = 1.60e0; +static double sqrpi = 3.9894228040143267794e-1; +static double thrsh = 0.66291e0; +static double root32 = 5.656854248e0; +static double zero = 0.0e0; +static int K1 = 1; +static int K2 = 2; +static int i; +static double del,eps,temp,x,xden,xnum,y,xsq,min; +/* +------------------------------------------------------------------ + Machine dependent constants +------------------------------------------------------------------ +*/ + eps = spmpar(&K1)*0.5e0; + min = spmpar(&K2); + x = *arg; + y = fabs(x); + if(y <= thrsh) { +/* +------------------------------------------------------------------ + Evaluate anorm for |X| <= 0.66291 +------------------------------------------------------------------ +*/ + xsq = zero; + if(y > eps) xsq = x*x; + xnum = a[4]*xsq; + xden = xsq; + for(i=0; i<3; i++) { + xnum = (xnum+a[i])*xsq; + xden = (xden+b[i])*xsq; + } + *result = x*(xnum+a[3])/(xden+b[3]); + temp = *result; + *result = half+temp; + *ccum = half-temp; + } +/* +------------------------------------------------------------------ + Evaluate anorm for 0.66291 <= |X| <= sqrt(32) +------------------------------------------------------------------ +*/ + else if(y <= root32) { + xnum = c[8]*y; + xden = y; + for(i=0; i<7; i++) { + xnum = (xnum+c[i])*y; + xden = (xden+d[i])*y; + } + *result = (xnum+c[7])/(xden+d[7]); + xsq = fifdint(y*sixten)/sixten; + del = (y-xsq)*(y+xsq); + *result = exp(-(xsq*xsq*half))*exp(-(del*half))**result; + *ccum = one-*result; + if(x > zero) { + temp = *result; + *result = *ccum; + *ccum = temp; + } + } +/* +------------------------------------------------------------------ + Evaluate anorm for |X| > sqrt(32) +------------------------------------------------------------------ +*/ + else { + *result = zero; + xsq = one/(x*x); + xnum = p[5]*xsq; + xden = xsq; + for(i=0; i<4; i++) { + xnum = (xnum+p[i])*xsq; + xden = (xden+q[i])*xsq; + } + *result = xsq*(xnum+p[4])/(xden+q[4]); + *result = (sqrpi-*result)/y; + xsq = fifdint(x*sixten)/sixten; + del = (x-xsq)*(x+xsq); + *result = exp(-(xsq*xsq*half))*exp(-(del*half))**result; + *ccum = one-*result; + if(x > zero) { + temp = *result; + *result = *ccum; + *ccum = temp; + } + } + if(*result < min) *result = 0.0e0; +/* +------------------------------------------------------------------ + Fix up for negative argument, erf, etc. +------------------------------------------------------------------ +----------Last card of ANORM ---------- +*/ + if(*ccum < min) *ccum = 0.0e0; +} /* END */ + +/***=====================================================================***/ +static void cumpoi(double *s,double *xlam,double *cum,double *ccum) +/* +********************************************************************** + + void cumpoi(double *s,double *xlam,double *cum,double *ccum) + CUMulative POIsson distribution + + + Function + + + Returns the probability of S or fewer events in a Poisson + distribution with mean XLAM. + + + Arguments + + + S --> Upper limit of cumulation of the Poisson. + S is DOUBLE PRECISION + + XLAM --> Mean of the Poisson distribution. + XLAM is DOUBLE PRECIS + + CUM <-- Cumulative poisson distribution. + CUM is DOUBLE PRECISION + + CCUM <-- Compliment of Cumulative poisson distribution. + CCUM is DOUBLE PRECIS + + + Method + + + Uses formula 26.4.21 of Abramowitz and Stegun, Handbook of + Mathematical Functions to reduce the cumulative Poisson to + the cumulative chi-square distribution. + +********************************************************************** +*/ +{ +static double chi,df; +/* + .. + .. Executable Statements .. +*/ + df = 2.0e0*(*s+1.0e0); + chi = 2.0e0**xlam; + cumchi(&chi,&df,ccum,cum); + return; +} /* END */ + +/***=====================================================================***/ +static void cumt(double *t,double *df,double *cum,double *ccum) +/* +********************************************************************** + + void cumt(double *t,double *df,double *cum,double *ccum) + CUMulative T-distribution + + + Function + + + Computes the integral from -infinity to T of the t-density. + + + Arguments + + + T --> Upper limit of integration of the t-density. + T is DOUBLE PRECISION + + DF --> Degrees of freedom of the t-distribution. + DF is DOUBLE PRECISIO + + CUM <-- Cumulative t-distribution. + CCUM is DOUBLE PRECIS + + CCUM <-- Compliment of Cumulative t-distribution. + CCUM is DOUBLE PRECIS + + + Method + + + Formula 26.5.27 of Abramowitz and Stegun, Handbook of + Mathematical Functions is used to reduce the t-distribution + to an incomplete beta. + +********************************************************************** +*/ +{ +static double K2 = 0.5e0; +static double xx,a,oma,tt,yy,dfptt,T1; +/* + .. + .. Executable Statements .. +*/ + tt = *t**t; + dfptt = *df+tt; + xx = *df/dfptt; + yy = tt/dfptt; + T1 = 0.5e0**df; + cumbet(&xx,&yy,&T1,&K2,&a,&oma); + if(!(*t <= 0.0e0)) goto S10; + *cum = 0.5e0*a; + *ccum = oma+*cum; + goto S20; +S10: + *ccum = 0.5e0*a; + *cum = oma+*ccum; +S20: + return; +} /* END */ + +/***=====================================================================***/ +static double dbetrm(double *a,double *b) +/* +********************************************************************** + + double dbetrm(double *a,double *b) + Double Precision Sterling Remainder for Complete + Beta Function + + + Function + + + Log(Beta(A,B)) = Lgamma(A) + Lgamma(B) - Lgamma(A+B) + where Lgamma is the log of the (complete) gamma function + + Let ZZ be approximation obtained if each log gamma is approximated + by Sterling's formula, i.e., + Sterling(Z) = LOG( SQRT( 2*PI ) ) + ( Z-0.5 ) * LOG( Z ) - Z + + Returns Log(Beta(A,B)) - ZZ + + + Arguments + + + A --> One argument of the Beta + DOUBLE PRECISION A + + B --> The other argument of the Beta + DOUBLE PRECISION B + +********************************************************************** +*/ +{ +static double dbetrm,T1,T2,T3; +/* + .. + .. Executable Statements .. +*/ +/* + Try to sum from smallest to largest +*/ + T1 = *a+*b; + dbetrm = -dstrem(&T1); + T2 = fifdmax1(*a,*b); + dbetrm += dstrem(&T2); + T3 = fifdmin1(*a,*b); + dbetrm += dstrem(&T3); + return dbetrm; +} /* END */ + +/***=====================================================================***/ +static double devlpl(double a[],int *n,double *x) +/* +********************************************************************** + + double devlpl(double a[],int *n,double *x) + Double precision EVALuate a PoLynomial at X + + + Function + + + returns + A(1) + A(2)*X + ... + A(N)*X**(N-1) + + + Arguments + + + A --> Array of coefficients of the polynomial. + A is DOUBLE PRECISION(N) + + N --> Length of A, also degree of polynomial - 1. + N is INTEGER + + X --> Point at which the polynomial is to be evaluated. + X is DOUBLE PRECISION + +********************************************************************** +*/ +{ +static double devlpl,term; +static int i; +/* + .. + .. Executable Statements .. +*/ + term = a[*n-1]; + for(i= *n-1-1; i>=0; i--) term = a[i]+term**x; + devlpl = term; + return devlpl; +} /* END */ + +/***=====================================================================***/ +static double dexpm1(double *x) +/* +********************************************************************** + + double dexpm1(double *x) + Evaluation of the function EXP(X) - 1 + + + Arguments + + + X --> Argument at which exp(x)-1 desired + DOUBLE PRECISION X + + + Method + + + Renaming of function rexp from code of: + + DiDinato, A. R. and Morris, A. H. Algorithm 708: Significant + Digit Computation of the Incomplete Beta Function Ratios. ACM + Trans. Math. Softw. 18 (1993), 360-373. + +********************************************************************** +*/ +{ +static double p1 = .914041914819518e-09; +static double p2 = .238082361044469e-01; +static double q1 = -.499999999085958e+00; +static double q2 = .107141568980644e+00; +static double q3 = -.119041179760821e-01; +static double q4 = .595130811860248e-03; +static double dexpm1,w; +/* + .. + .. Executable Statements .. +*/ + if(fabs(*x) > 0.15e0) goto S10; + dexpm1 = *x*(((p2**x+p1)**x+1.0e0)/((((q4**x+q3)**x+q2)**x+q1)**x+1.0e0)); + return dexpm1; +S10: + w = exp(*x); + if(*x > 0.0e0) goto S20; + dexpm1 = w-0.5e0-0.5e0; + return dexpm1; +S20: + dexpm1 = w*(0.5e0+(0.5e0-1.0e0/w)); + return dexpm1; +} /* END */ + +/***=====================================================================***/ +static double dinvnr(double *p,double *q) +/* +********************************************************************** + + double dinvnr(double *p,double *q) + Double precision NoRmal distribution INVerse + + + Function + + + Returns X such that CUMNOR(X) = P, i.e., the integral from - + infinity to X of (1/SQRT(2*PI)) EXP(-U*U/2) dU is P + + + Arguments + + + P --> The probability whose normal deviate is sought. + P is DOUBLE PRECISION + + Q --> 1-P + P is DOUBLE PRECISION + + + Method + + + The rational function on page 95 of Kennedy and Gentle, + Statistical Computing, Marcel Dekker, NY , 1980 is used as a start + value for the Newton method of finding roots. + + + Note + + + If P or Q .lt. machine EPS returns +/- DINVNR(EPS) + +********************************************************************** +*/ +{ +#define maxit 100 +#define eps (1.0e-13) +#define r2pi 0.3989422804014326e0 +#define nhalf (-0.5e0) +#define dennor(x) (r2pi*exp(nhalf*(x)*(x))) +static double dinvnr,strtx,xcur,cum,ccum,pp,dx; +static int i; +static unsigned long qporq; +/* + .. + .. Executable Statements .. +*/ +/* + FIND MINIMUM OF P AND Q +*/ + qporq = *p <= *q; + if(!qporq) goto S10; + pp = *p; + goto S20; +S10: + pp = *q; +S20: +/* + INITIALIZATION STEP +*/ + strtx = stvaln(&pp); + xcur = strtx; +/* + NEWTON INTERATIONS +*/ + for(i=1; i<=maxit; i++) { + cumnor(&xcur,&cum,&ccum); + dx = (cum-pp)/dennor(xcur); + xcur -= dx; + if(fabs(dx/xcur) < eps) goto S40; + } + dinvnr = strtx; +/* + IF WE GET HERE, NEWTON HAS FAILED +*/ + if(!qporq) dinvnr = -dinvnr; + return dinvnr; +S40: +/* + IF WE GET HERE, NEWTON HAS SUCCEDED +*/ + dinvnr = xcur; + if(!qporq) dinvnr = -dinvnr; + return dinvnr; +#undef maxit +#undef eps +#undef r2pi +#undef nhalf +#undef dennor +} /* END */ + +/***=====================================================================***/ +static void E0000(int IENTRY,int *status,double *x,double *fx, + unsigned long *qleft,unsigned long *qhi,double *zabsst, + double *zabsto,double *zbig,double *zrelst, + double *zrelto,double *zsmall,double *zstpmu) +{ +#define qxmon(zx,zy,zz) (int)((zx) <= (zy) && (zy) <= (zz)) +static double absstp,abstol,big,fbig,fsmall,relstp,reltol,small,step,stpmul,xhi, + xlb,xlo,xsave,xub,yy; +static int i99999; +static unsigned long qbdd,qcond,qdum1,qdum2,qincr,qlim,qok,qup; + switch(IENTRY){case 0: goto DINVR; case 1: goto DSTINV;} +DINVR: + if(*status > 0) goto S310; + qcond = !qxmon(small,*x,big); + if(qcond){ ftnstop("SMALL,X,BIG nonmonotone in E0000"); *status=-1; return;} + xsave = *x; +/* + See that SMALL and BIG bound the zero and set QINCR +*/ + *x = small; +/* + GET-FUNCTION-VALUE +*/ + i99999 = 1; + goto S300; +S10: + fsmall = *fx; + *x = big; +/* + GET-FUNCTION-VALUE +*/ + i99999 = 2; + goto S300; +S20: + fbig = *fx; + qincr = fbig > fsmall; + if(!qincr) goto S50; + if(fsmall <= 0.0e0) goto S30; + *status = -1; + *qleft = *qhi = 1; + return; +S30: + if(fbig >= 0.0e0) goto S40; + *status = -1; + *qleft = *qhi = 0; + return; +S40: + goto S80; +S50: + if(fsmall >= 0.0e0) goto S60; + *status = -1; + *qleft = 1; + *qhi = 0; + return; +S60: + if(fbig <= 0.0e0) goto S70; + *status = -1; + *qleft = 0; + *qhi = 1; + return; +S80: +S70: + *x = xsave; + step = fifdmax1(absstp,relstp*fabs(*x)); +/* + YY = F(X) - Y + GET-FUNCTION-VALUE +*/ + i99999 = 3; + goto S300; +S90: + yy = *fx; + if(!(yy == 0.0e0)) goto S100; + *status = 0; + qok = 1; + return; +S100: + qup = qincr && yy < 0.0e0 || !qincr && yy > 0.0e0; +/* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + HANDLE CASE IN WHICH WE MUST STEP HIGHER +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +*/ + if(!qup) goto S170; + xlb = xsave; + xub = fifdmin1(xlb+step,big); + goto S120; +S110: + if(qcond) goto S150; +S120: +/* + YY = F(XUB) - Y +*/ + *x = xub; +/* + GET-FUNCTION-VALUE +*/ + i99999 = 4; + goto S300; +S130: + yy = *fx; + qbdd = qincr && yy >= 0.0e0 || !qincr && yy <= 0.0e0; + qlim = xub >= big; + qcond = qbdd || qlim; + if(qcond) goto S140; + step = stpmul*step; + xlb = xub; + xub = fifdmin1(xlb+step,big); +S140: + goto S110; +S150: + if(!(qlim && !qbdd)) goto S160; + *status = -1; + *qleft = 0; + *qhi = !qincr; + *x = big; + return; +S160: + goto S240; +S170: +/* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + HANDLE CASE IN WHICH WE MUST STEP LOWER +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +*/ + xub = xsave; + xlb = fifdmax1(xub-step,small); + goto S190; +S180: + if(qcond) goto S220; +S190: +/* + YY = F(XLB) - Y +*/ + *x = xlb; +/* + GET-FUNCTION-VALUE +*/ + i99999 = 5; + goto S300; +S200: + yy = *fx; + qbdd = qincr && yy <= 0.0e0 || !qincr && yy >= 0.0e0; + qlim = xlb <= small; + qcond = qbdd || qlim; + if(qcond) goto S210; + step = stpmul*step; + xub = xlb; + xlb = fifdmax1(xub-step,small); +S210: + goto S180; +S220: + if(!(qlim && !qbdd)) goto S230; + *status = -1; + *qleft = 1; + *qhi = qincr; + *x = small; + return; +S240: +S230: + dstzr(&xlb,&xub,&abstol,&reltol); +/* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + IF WE REACH HERE, XLB AND XUB BOUND THE ZERO OF F. +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +*/ + *status = 0; + goto S260; +S250: + if(!(*status == 1)) goto S290; +S260: + dzror(status,x,fx,&xlo,&xhi,&qdum1,&qdum2); + if(!(*status == 1)) goto S280; +/* + GET-FUNCTION-VALUE +*/ + i99999 = 6; + goto S300; +S280: +S270: + goto S250; +S290: + *x = xlo; + *status = 0; + return; +DSTINV: + small = *zsmall; + big = *zbig; + absstp = *zabsst; + relstp = *zrelst; + stpmul = *zstpmu; + abstol = *zabsto; + reltol = *zrelto; + return; +S300: +/* + TO GET-FUNCTION-VALUE +*/ + *status = 1; + return; +S310: + switch((int)i99999){case 1: goto S10;case 2: goto S20;case 3: goto S90;case + 4: goto S130;case 5: goto S200;case 6: goto S270;default: break;} +#undef qxmon +} /* END */ + +/***=====================================================================***/ +static void dinvr(int *status,double *x,double *fx, + unsigned long *qleft,unsigned long *qhi) +/* +********************************************************************** + + void dinvr(int *status,double *x,double *fx, + unsigned long *qleft,unsigned long *qhi) + + Double precision + bounds the zero of the function and invokes zror + Reverse Communication + + + Function + + + Bounds the function and invokes ZROR to perform the zero + finding. STINVR must have been called before this routine + in order to set its parameters. + + + Arguments + + + STATUS <--> At the beginning of a zero finding problem, STATUS + should be set to 0 and INVR invoked. (The value + of parameters other than X will be ignored on this cal + + When INVR needs the function evaluated, it will set + STATUS to 1 and return. The value of the function + should be set in FX and INVR again called without + changing any of its other parameters. + + When INVR has finished without error, it will return + with STATUS 0. In that case X is approximately a root + of F(X). + + If INVR cannot bound the function, it returns status + -1 and sets QLEFT and QHI. + INTEGER STATUS + + X <-- The value of X at which F(X) is to be evaluated. + DOUBLE PRECISION X + + FX --> The value of F(X) calculated when INVR returns with + STATUS = 1. + DOUBLE PRECISION FX + + QLEFT <-- Defined only if QMFINV returns .FALSE. In that + case it is .TRUE. If the stepping search terminated + unsucessfully at SMALL. If it is .FALSE. the search + terminated unsucessfully at BIG. + QLEFT is LOGICAL + + QHI <-- Defined only if QMFINV returns .FALSE. In that + case it is .TRUE. if F(X) .GT. Y at the termination + of the search and .FALSE. if F(X) .LT. Y at the + termination of the search. + QHI is LOGICAL + +********************************************************************** +*/ +{ + E0000(0,status,x,fx,qleft,qhi,NULL,NULL,NULL,NULL,NULL,NULL,NULL); +} /* END */ + +/***=====================================================================***/ +static void dstinv(double *zsmall,double *zbig,double *zabsst, + double *zrelst,double *zstpmu,double *zabsto, + double *zrelto) +/* +********************************************************************** + void dstinv(double *zsmall,double *zbig,double *zabsst, + double *zrelst,double *zstpmu,double *zabsto, + double *zrelto) + + Double Precision - SeT INverse finder - Reverse Communication + Function + Concise Description - Given a monotone function F finds X + such that F(X) = Y. Uses Reverse communication -- see invr. + This routine sets quantities needed by INVR. + More Precise Description of INVR - + F must be a monotone function, the results of QMFINV are + otherwise undefined. QINCR must be .TRUE. if F is non- + decreasing and .FALSE. if F is non-increasing. + QMFINV will return .TRUE. if and only if F(SMALL) and + F(BIG) bracket Y, i. e., + QINCR is .TRUE. and F(SMALL).LE.Y.LE.F(BIG) or + QINCR is .FALSE. and F(BIG).LE.Y.LE.F(SMALL) + if QMFINV returns .TRUE., then the X returned satisfies + the following condition. let + TOL(X) = MAX(ABSTOL,RELTOL*ABS(X)) + then if QINCR is .TRUE., + F(X-TOL(X)) .LE. Y .LE. F(X+TOL(X)) + and if QINCR is .FALSE. + F(X-TOL(X)) .GE. Y .GE. F(X+TOL(X)) + Arguments + SMALL --> The left endpoint of the interval to be + searched for a solution. + SMALL is DOUBLE PRECISION + BIG --> The right endpoint of the interval to be + searched for a solution. + BIG is DOUBLE PRECISION + ABSSTP, RELSTP --> The initial step size in the search + is MAX(ABSSTP,RELSTP*ABS(X)). See algorithm. + ABSSTP is DOUBLE PRECISION + RELSTP is DOUBLE PRECISION + STPMUL --> When a step doesn't bound the zero, the step + size is multiplied by STPMUL and another step + taken. A popular value is 2.0 + DOUBLE PRECISION STPMUL + ABSTOL, RELTOL --> Two numbers that determine the accuracy + of the solution. See function for a precise definition. + ABSTOL is DOUBLE PRECISION + RELTOL is DOUBLE PRECISION + Method + Compares F(X) with Y for the input value of X then uses QINCR + to determine whether to step left or right to bound the + desired x. the initial step size is + MAX(ABSSTP,RELSTP*ABS(S)) for the input value of X. + Iteratively steps right or left until it bounds X. + At each step which doesn't bound X, the step size is doubled. + The routine is careful never to step beyond SMALL or BIG. If + it hasn't bounded X at SMALL or BIG, QMFINV returns .FALSE. + after setting QLEFT and QHI. + If X is successfully bounded then Algorithm R of the paper + 'Two Efficient Algorithms with Guaranteed Convergence for + Finding a Zero of a Function' by J. C. P. Bus and + T. J. Dekker in ACM Transactions on Mathematical + Software, Volume 1, No. 4 page 330 (DEC. '75) is employed + to find the zero of the function F(X)-Y. This is routine + QRZERO. +********************************************************************** +*/ +{ + E0000(1,NULL,NULL,NULL,NULL,NULL,zabsst,zabsto,zbig,zrelst,zrelto,zsmall, + zstpmu); +} /* END */ + +/***=====================================================================***/ +static double dlanor(double *x) +/* +********************************************************************** + + double dlanor(double *x) + Double precision Logarith of the Asymptotic Normal + + + Function + + + Computes the logarithm of the cumulative normal distribution + from abs( x ) to infinity for abs( x ) >= 5. + + + Arguments + + + X --> Value at which cumulative normal to be evaluated + DOUBLE PRECISION X + + + Method + + + 23 term expansion of formula 26.2.12 of Abramowitz and Stegun. + The relative error at X = 5 is about 0.5E-5. + + + Note + + + ABS(X) must be >= 5 else there is an error stop. + +********************************************************************** +*/ +{ +#define dlsqpi 0.91893853320467274177e0 +static double coef[12] = { + -1.0e0,3.0e0,-15.0e0,105.0e0,-945.0e0,10395.0e0,-135135.0e0,2027025.0e0, + -34459425.0e0,654729075.0e0,-13749310575.e0,316234143225.0e0 +}; +static int K1 = 12; +static double dlanor,approx,correc,xx,xx2,T2; +/* + .. + .. Executable Statements .. +*/ + xx = fabs(*x); + if(xx < 5.0e0){ ftnstop("Argument too small in DLANOR"); return 66.6; } + approx = -dlsqpi-0.5e0*xx*xx-log(xx); + xx2 = xx*xx; + T2 = 1.0e0/xx2; + correc = devlpl(coef,&K1,&T2)/xx2; + correc = dln1px(&correc); + dlanor = approx+correc; + return dlanor; +#undef dlsqpi +} /* END */ + +/***=====================================================================***/ +static double dln1mx(double *x) +/* +********************************************************************** + + double dln1mx(double *x) + Double precision LN(1-X) + + + Function + + + Returns ln(1-x) for small x (good accuracy if x .le. 0.1). + Note that the obvious code of + LOG(1.0-X) + won't work for small X because 1.0-X loses accuracy + + + Arguments + + + X --> Value for which ln(1-x) is desired. + X is DOUBLE PRECISION + + + Method + + + If X > 0.1, the obvious code above is used ELSE + The Taylor series for 1-x is expanded to 20 terms. + +********************************************************************** +*/ +{ +static double dln1mx,T1; +/* + .. + .. Executable Statements .. +*/ + T1 = -*x; + dln1mx = dln1px(&T1); + return dln1mx; +} /* END */ + +/***=====================================================================***/ +static double dln1px(double *a) +/* +********************************************************************** + + double dln1px(double *a) + Double precision LN(1+X) + + + Function + + + Returns ln(1+x) + Note that the obvious code of + LOG(1.0+X) + won't work for small X because 1.0+X loses accuracy + + + Arguments + + + X --> Value for which ln(1-x) is desired. + X is DOUBLE PRECISION + + + Method + + + Renames ALNREL from: + DiDinato, A. R. and Morris, A. H. Algorithm 708: Significant + Digit Computation of the Incomplete Beta Function Ratios. ACM + Trans. Math. Softw. 18 (1993), 360-373. + +********************************************************************** +----------------------------------------------------------------------- + EVALUATION OF THE FUNCTION LN(1 + A) +----------------------------------------------------------------------- +*/ +{ +static double p1 = -.129418923021993e+01; +static double p2 = .405303492862024e+00; +static double p3 = -.178874546012214e-01; +static double q1 = -.162752256355323e+01; +static double q2 = .747811014037616e+00; +static double q3 = -.845104217945565e-01; +static double dln1px,t,t2,w,x; +/* + .. + .. Executable Statements .. +*/ + if(fabs(*a) > 0.375e0) goto S10; + t = *a/(*a+2.0e0); + t2 = t*t; + w = (((p3*t2+p2)*t2+p1)*t2+1.0e0)/(((q3*t2+q2)*t2+q1)*t2+1.0e0); + dln1px = 2.0e0*t*w; + return dln1px; +S10: + x = 1.e0+*a; + dln1px = log(x); + return dln1px; +} /* END */ + +/***=====================================================================***/ +static double dlnbet(double *a0,double *b0) +/* +********************************************************************** + + double dlnbet(a0,b0) + Double precision LN of the complete BETa + + + Function + + + Returns the natural log of the complete beta function, + i.e., + + ln( Gamma(a)*Gamma(b) / Gamma(a+b) + + + Arguments + + + A,B --> The (symmetric) arguments to the complete beta + DOUBLE PRECISION A, B + + + Method + + + Renames BETALN from: + DiDinato, A. R. and Morris, A. H. Algorithm 708: Significant + Digit Computation of the Incomplete Beta Function Ratios. ACM + Trans. Math. Softw. 18 (1993), 360-373. + +********************************************************************** +----------------------------------------------------------------------- + EVALUATION OF THE LOGARITHM OF THE BETA FUNCTION +----------------------------------------------------------------------- + E = 0.5*LN(2*PI) +-------------------------- +*/ +{ +static double e = .918938533204673e0; +static double dlnbet,a,b,c,h,u,v,w,z; +static int i,n; +static double T1; +/* + .. + .. Executable Statements .. +*/ + a = fifdmin1(*a0,*b0); + b = fifdmax1(*a0,*b0); + if(a >= 8.0e0) goto S100; + if(a >= 1.0e0) goto S20; +/* +----------------------------------------------------------------------- + PROCEDURE WHEN A .LT. 1 +----------------------------------------------------------------------- +*/ + if(b >= 8.0e0) goto S10; + T1 = a+b; + dlnbet = gamln(&a)+(gamln(&b)-gamln(&T1)); + return dlnbet; +S10: + dlnbet = gamln(&a)+algdiv(&a,&b); + return dlnbet; +S20: +/* +----------------------------------------------------------------------- + PROCEDURE WHEN 1 .LE. A .LT. 8 +----------------------------------------------------------------------- +*/ + if(a > 2.0e0) goto S40; + if(b > 2.0e0) goto S30; + dlnbet = gamln(&a)+gamln(&b)-gsumln(&a,&b); + return dlnbet; +S30: + w = 0.0e0; + if(b < 8.0e0) goto S60; + dlnbet = gamln(&a)+algdiv(&a,&b); + return dlnbet; +S40: +/* + REDUCTION OF A WHEN B .LE. 1000 +*/ + if(b > 1000.0e0) goto S80; + n = a-1.0e0; + w = 1.0e0; + for(i=1; i<=n; i++) { + a -= 1.0e0; + h = a/b; + w *= (h/(1.0e0+h)); + } + w = log(w); + if(b < 8.0e0) goto S60; + dlnbet = w+gamln(&a)+algdiv(&a,&b); + return dlnbet; +S60: +/* + REDUCTION OF B WHEN B .LT. 8 +*/ + n = b-1.0e0; + z = 1.0e0; + for(i=1; i<=n; i++) { + b -= 1.0e0; + z *= (b/(a+b)); + } + dlnbet = w+log(z)+(gamln(&a)+(gamln(&b)-gsumln(&a,&b))); + return dlnbet; +S80: +/* + REDUCTION OF A WHEN B .GT. 1000 +*/ + n = a-1.0e0; + w = 1.0e0; + for(i=1; i<=n; i++) { + a -= 1.0e0; + w *= (a/(1.0e0+a/b)); + } + dlnbet = log(w)-(double)n*log(b)+(gamln(&a)+algdiv(&a,&b)); + return dlnbet; +S100: +/* +----------------------------------------------------------------------- + PROCEDURE WHEN A .GE. 8 +----------------------------------------------------------------------- +*/ + w = bcorr(&a,&b); + h = a/b; + c = h/(1.0e0+h); + u = -((a-0.5e0)*log(c)); + v = b*alnrel(&h); + if(u <= v) goto S110; + dlnbet = -(0.5e0*log(b))+e+w-v-u; + return dlnbet; +S110: + dlnbet = -(0.5e0*log(b))+e+w-u-v; + return dlnbet; +} /* END */ + +/***=====================================================================***/ +static double dlngam(double *a) +/* +********************************************************************** + + double dlngam(double *a) + Double precision LN of the GAMma function + + + Function + + + Returns the natural logarithm of GAMMA(X). + + + Arguments + + + X --> value at which scaled log gamma is to be returned + X is DOUBLE PRECISION + + + Method + + + Renames GAMLN from: + DiDinato, A. R. and Morris, A. H. Algorithm 708: Significant + Digit Computation of the Incomplete Beta Function Ratios. ACM + Trans. Math. Softw. 18 (1993), 360-373. + +********************************************************************** +----------------------------------------------------------------------- + EVALUATION OF LN(GAMMA(A)) FOR POSITIVE A +----------------------------------------------------------------------- + WRITTEN BY ALFRED H. MORRIS + NAVAL SURFACE WARFARE CENTER + DAHLGREN, VIRGINIA +-------------------------- + D = 0.5*(LN(2*PI) - 1) +-------------------------- +*/ +{ +static double c0 = .833333333333333e-01; +static double c1 = -.277777777760991e-02; +static double c2 = .793650666825390e-03; +static double c3 = -.595202931351870e-03; +static double c4 = .837308034031215e-03; +static double c5 = -.165322962780713e-02; +static double d = .418938533204673e0; +static double dlngam,t,w; +static int i,n; +static double T1; +/* + .. + .. Executable Statements .. +*/ + if(*a > 0.8e0) goto S10; + dlngam = gamln1(a)-log(*a); + return dlngam; +S10: + if(*a > 2.25e0) goto S20; + t = *a-0.5e0-0.5e0; + dlngam = gamln1(&t); + return dlngam; +S20: + if(*a >= 10.0e0) goto S40; + n = *a-1.25e0; + t = *a; + w = 1.0e0; + for(i=1; i<=n; i++) { + t -= 1.0e0; + w = t*w; + } + T1 = t-1.0e0; + dlngam = gamln1(&T1)+log(w); + return dlngam; +S40: + t = pow(1.0e0/ *a,2.0); + w = (((((c5*t+c4)*t+c3)*t+c2)*t+c1)*t+c0)/ *a; + dlngam = d+w+(*a-0.5e0)*(log(*a)-1.0e0); + return dlngam; +} /* END */ + +/***=====================================================================***/ +static double dstrem(double *z) +{ +/* +********************************************************************** + double dstrem(double *z) + Double precision Sterling Remainder + Function + Returns Log(Gamma(Z)) - Sterling(Z) where Sterling(Z) is + Sterling's Approximation to Log(Gamma(Z)) + Sterling(Z) = LOG( SQRT( 2*PI ) ) + ( Z-0.5 ) * LOG( Z ) - Z + Arguments + Z --> Value at which Sterling remainder calculated + Must be positive. + DOUBLE PRECISION Z + Method + If Z >= 6 uses 9 terms of series in Bernoulli numbers + (Values calculated using Maple) + Otherwise computes difference explicitly +********************************************************************** +*/ +#define hln2pi 0.91893853320467274178e0 +#define ncoef 10 +static double coef[ncoef] = { + 0.0e0,0.0833333333333333333333333333333e0, + -0.00277777777777777777777777777778e0,0.000793650793650793650793650793651e0, + -0.000595238095238095238095238095238e0, + 0.000841750841750841750841750841751e0,-0.00191752691752691752691752691753e0, + 0.00641025641025641025641025641026e0,-0.0295506535947712418300653594771e0, + 0.179644372368830573164938490016e0 +}; +static int K1 = 10; +static double dstrem,sterl,T2; +/* + .. + .. Executable Statements .. +*/ +/* + For information, here are the next 11 coefficients of the + remainder term in Sterling's formula + -1.39243221690590111642743221691 + 13.4028640441683919944789510007 + -156.848284626002017306365132452 + 2193.10333333333333333333333333 + -36108.7712537249893571732652192 + 691472.268851313067108395250776 + -0.152382215394074161922833649589D8 + 0.382900751391414141414141414141D9 + -0.108822660357843910890151491655D11 + 0.347320283765002252252252252252D12 + -0.123696021422692744542517103493D14 +*/ + if(*z <= 0.0e0){ ftnstop("nonpositive argument in DSTREM"); return 66.6; } + if(!(*z > 6.0e0)) goto S10; + T2 = 1.0e0/pow(*z,2.0); + dstrem = devlpl(coef,&K1,&T2)**z; + goto S20; +S10: + sterl = hln2pi+(*z-0.5e0)*log(*z)-*z; + dstrem = dlngam(z)-sterl; +S20: + return dstrem; +#undef hln2pi +#undef ncoef +} /* END */ + +/***=====================================================================***/ +static double dt1(double *p,double *q,double *df) +/* +********************************************************************** + + double dt1(double *p,double *q,double *df) + Double precision Initalize Approximation to + INVerse of the cumulative T distribution + + + Function + + + Returns the inverse of the T distribution function, i.e., + the integral from 0 to INVT of the T density is P. This is an + initial approximation + + + Arguments + + + P --> The p-value whose inverse from the T distribution is + desired. + P is DOUBLE PRECISION + + Q --> 1-P. + Q is DOUBLE PRECISION + + DF --> Degrees of freedom of the T distribution. + DF is DOUBLE PRECISION + +********************************************************************** +*/ +{ +static double coef[4][5] = { + 1.0e0,1.0e0,0.0e0,0.0e0,0.0e0,3.0e0,16.0e0,5.0e0,0.0e0,0.0e0,-15.0e0,17.0e0, + 19.0e0,3.0e0,0.0e0,-945.0e0,-1920.0e0,1482.0e0,776.0e0,79.0e0 +}; +static double denom[4] = { + 4.0e0,96.0e0,384.0e0,92160.0e0 +}; +static int ideg[4] = { + 2,3,4,5 +}; +static double dt1,denpow,sum,term,x,xp,xx; +static int i; +/* + .. + .. Executable Statements .. +*/ + x = fabs(dinvnr(p,q)); + xx = x*x; + sum = x; + denpow = 1.0e0; + for(i=0; i<4; i++) { + term = devlpl(&coef[i][0],&ideg[i],&xx)*x; + denpow *= *df; + sum += (term/(denpow*denom[i])); + } + if(!(*p >= 0.5e0)) goto S20; + xp = sum; + goto S30; +S20: + xp = -sum; +S30: + dt1 = xp; + return dt1; +} /* END */ + +/***=====================================================================***/ +static void E0001(int IENTRY,int *status,double *x,double *fx, + double *xlo,double *xhi,unsigned long *qleft, + unsigned long *qhi,double *zabstl,double *zreltl, + double *zxhi,double *zxlo) +{ +#define ftol(zx) (0.5e0*fifdmax1(abstol,reltol*fabs((zx)))) +static double a,abstol,b,c,d,fa,fb,fc,fd,fda,fdb,m,mb,p,q,reltol,tol,w,xxhi,xxlo; +static int ext,i99999; +static unsigned long first,qrzero; + switch(IENTRY){case 0: goto DZROR; case 1: goto DSTZR;} +DZROR: + if(*status > 0) goto S280; + *xlo = xxlo; + *xhi = xxhi; + b = *x = *xlo; +/* + GET-FUNCTION-VALUE +*/ + i99999 = 1; + goto S270; +S10: + fb = *fx; + *xlo = *xhi; + a = *x = *xlo; +/* + GET-FUNCTION-VALUE +*/ + i99999 = 2; + goto S270; +S20: +/* + Check that F(ZXLO) < 0 < F(ZXHI) or + F(ZXLO) > 0 > F(ZXHI) +*/ + if(!(fb < 0.0e0)) goto S40; + if(!(*fx < 0.0e0)) goto S30; + *status = -1; + *qleft = *fx < fb; + *qhi = 0; + return; +S40: +S30: + if(!(fb > 0.0e0)) goto S60; + if(!(*fx > 0.0e0)) goto S50; + *status = -1; + *qleft = *fx > fb; + *qhi = 1; + return; +S60: +S50: + fa = *fx; + first = 1; +S70: + c = a; + fc = fa; + ext = 0; +S80: + if(!(fabs(fc) < fabs(fb))) goto S100; + if(!(c != a)) goto S90; + d = a; + fd = fa; +S90: + a = b; + fa = fb; + *xlo = c; + b = *xlo; + fb = fc; + c = a; + fc = fa; +S100: + tol = ftol(*xlo); + m = (c+b)*.5e0; + mb = m-b; + if(!(fabs(mb) > tol)) goto S240; + if(!(ext > 3)) goto S110; + w = mb; + goto S190; +S110: + tol = fifdsign(tol,mb); + p = (b-a)*fb; + if(!first) goto S120; + q = fa-fb; + first = 0; + goto S130; +S120: + fdb = (fd-fb)/(d-b); + fda = (fd-fa)/(d-a); + p = fda*p; + q = fdb*fa-fda*fb; +S130: + if(!(p < 0.0e0)) goto S140; + p = -p; + q = -q; +S140: + if(ext == 3) p *= 2.0e0; + if(!(p*1.0e0 == 0.0e0 || p <= q*tol)) goto S150; + w = tol; + goto S180; +S150: + if(!(p < mb*q)) goto S160; + w = p/q; + goto S170; +S160: + w = mb; +S190: +S180: +S170: + d = a; + fd = fa; + a = b; + fa = fb; + b += w; + *xlo = b; + *x = *xlo; +/* + GET-FUNCTION-VALUE +*/ + i99999 = 3; + goto S270; +S200: + fb = *fx; + if(!(fc*fb >= 0.0e0)) goto S210; + goto S70; +S210: + if(!(w == mb)) goto S220; + ext = 0; + goto S230; +S220: + ext += 1; +S230: + goto S80; +S240: + *xhi = c; + qrzero = fc >= 0.0e0 && fb <= 0.0e0 || fc < 0.0e0 && fb >= 0.0e0; + if(!qrzero) goto S250; + *status = 0; + goto S260; +S250: + *status = -1; +S260: + return; +DSTZR: + xxlo = *zxlo; + xxhi = *zxhi; + abstol = *zabstl; + reltol = *zreltl; + return; +S270: +/* + TO GET-FUNCTION-VALUE +*/ + *status = 1; + return; +S280: + switch((int)i99999){case 1: goto S10;case 2: goto S20;case 3: goto S200; + default: break;} +#undef ftol +} /* END */ + +/***=====================================================================***/ +static void dzror(int *status,double *x,double *fx,double *xlo, + double *xhi,unsigned long *qleft,unsigned long *qhi) +/* +********************************************************************** + + void dzror(int *status,double *x,double *fx,double *xlo, + double *xhi,unsigned long *qleft,unsigned long *qhi) + + Double precision ZeRo of a function -- Reverse Communication + + + Function + + + Performs the zero finding. STZROR must have been called before + this routine in order to set its parameters. + + + Arguments + + + STATUS <--> At the beginning of a zero finding problem, STATUS + should be set to 0 and ZROR invoked. (The value + of other parameters will be ignored on this call.) + + When ZROR needs the function evaluated, it will set + STATUS to 1 and return. The value of the function + should be set in FX and ZROR again called without + changing any of its other parameters. + + When ZROR has finished without error, it will return + with STATUS 0. In that case (XLO,XHI) bound the answe + + If ZROR finds an error (which implies that F(XLO)-Y an + F(XHI)-Y have the same sign, it returns STATUS -1. In + this case, XLO and XHI are undefined. + INTEGER STATUS + + X <-- The value of X at which F(X) is to be evaluated. + DOUBLE PRECISION X + + FX --> The value of F(X) calculated when ZROR returns with + STATUS = 1. + DOUBLE PRECISION FX + + XLO <-- When ZROR returns with STATUS = 0, XLO bounds the + inverval in X containing the solution below. + DOUBLE PRECISION XLO + + XHI <-- When ZROR returns with STATUS = 0, XHI bounds the + inverval in X containing the solution above. + DOUBLE PRECISION XHI + + QLEFT <-- .TRUE. if the stepping search terminated unsucessfully + at XLO. If it is .FALSE. the search terminated + unsucessfully at XHI. + QLEFT is LOGICAL + + QHI <-- .TRUE. if F(X) .GT. Y at the termination of the + search and .FALSE. if F(X) .LT. Y at the + termination of the search. + QHI is LOGICAL + +********************************************************************** +*/ +{ + E0001(0,status,x,fx,xlo,xhi,qleft,qhi,NULL,NULL,NULL,NULL); +} /* END */ + +/***=====================================================================***/ +static void dstzr(double *zxlo,double *zxhi,double *zabstl,double *zreltl) +/* +********************************************************************** + void dstzr(double *zxlo,double *zxhi,double *zabstl,double *zreltl) + Double precision SeT ZeRo finder - Reverse communication version + Function + Sets quantities needed by ZROR. The function of ZROR + and the quantities set is given here. + Concise Description - Given a function F + find XLO such that F(XLO) = 0. + More Precise Description - + Input condition. F is a double precision function of a single + double precision argument and XLO and XHI are such that + F(XLO)*F(XHI) .LE. 0.0 + If the input condition is met, QRZERO returns .TRUE. + and output values of XLO and XHI satisfy the following + F(XLO)*F(XHI) .LE. 0. + ABS(F(XLO) .LE. ABS(F(XHI) + ABS(XLO-XHI) .LE. TOL(X) + where + TOL(X) = MAX(ABSTOL,RELTOL*ABS(X)) + If this algorithm does not find XLO and XHI satisfying + these conditions then QRZERO returns .FALSE. This + implies that the input condition was not met. + Arguments + XLO --> The left endpoint of the interval to be + searched for a solution. + XLO is DOUBLE PRECISION + XHI --> The right endpoint of the interval to be + for a solution. + XHI is DOUBLE PRECISION + ABSTOL, RELTOL --> Two numbers that determine the accuracy + of the solution. See function for a + precise definition. + ABSTOL is DOUBLE PRECISION + RELTOL is DOUBLE PRECISION + Method + Algorithm R of the paper 'Two Efficient Algorithms with + Guaranteed Convergence for Finding a Zero of a Function' + by J. C. P. Bus and T. J. Dekker in ACM Transactions on + Mathematical Software, Volume 1, no. 4 page 330 + (Dec. '75) is employed to find the zero of F(X)-Y. +********************************************************************** +*/ +{ + E0001(1,NULL,NULL,NULL,NULL,NULL,NULL,NULL,zabstl,zreltl,zxhi,zxlo); +} /* END */ + +/***=====================================================================***/ +static double erf1(double *x) +/* +----------------------------------------------------------------------- + EVALUATION OF THE REAL ERROR FUNCTION +----------------------------------------------------------------------- +*/ +{ +static double c = .564189583547756e0; +static double a[5] = { + .771058495001320e-04,-.133733772997339e-02,.323076579225834e-01, + .479137145607681e-01,.128379167095513e+00 +}; +static double b[3] = { + .301048631703895e-02,.538971687740286e-01,.375795757275549e+00 +}; +static double p[8] = { + -1.36864857382717e-07,5.64195517478974e-01,7.21175825088309e+00, + 4.31622272220567e+01,1.52989285046940e+02,3.39320816734344e+02, + 4.51918953711873e+02,3.00459261020162e+02 +}; +static double q[8] = { + 1.00000000000000e+00,1.27827273196294e+01,7.70001529352295e+01, + 2.77585444743988e+02,6.38980264465631e+02,9.31354094850610e+02, + 7.90950925327898e+02,3.00459260956983e+02 +}; +static double r[5] = { + 2.10144126479064e+00,2.62370141675169e+01,2.13688200555087e+01, + 4.65807828718470e+00,2.82094791773523e-01 +}; +static double s[4] = { + 9.41537750555460e+01,1.87114811799590e+02,9.90191814623914e+01, + 1.80124575948747e+01 +}; +static double erf1,ax,bot,t,top,x2; +/* + .. + .. Executable Statements .. +*/ + ax = fabs(*x); + if(ax > 0.5e0) goto S10; + t = *x**x; + top = (((a[0]*t+a[1])*t+a[2])*t+a[3])*t+a[4]+1.0e0; + bot = ((b[0]*t+b[1])*t+b[2])*t+1.0e0; + erf1 = *x*(top/bot); + return erf1; +S10: + if(ax > 4.0e0) goto S20; + top = ((((((p[0]*ax+p[1])*ax+p[2])*ax+p[3])*ax+p[4])*ax+p[5])*ax+p[6])*ax+p[ + 7]; + bot = ((((((q[0]*ax+q[1])*ax+q[2])*ax+q[3])*ax+q[4])*ax+q[5])*ax+q[6])*ax+q[ + 7]; + erf1 = 0.5e0+(0.5e0-exp(-(*x**x))*top/bot); + if(*x < 0.0e0) erf1 = -erf1; + return erf1; +S20: + if(ax >= 5.8e0) goto S30; + x2 = *x**x; + t = 1.0e0/x2; + top = (((r[0]*t+r[1])*t+r[2])*t+r[3])*t+r[4]; + bot = (((s[0]*t+s[1])*t+s[2])*t+s[3])*t+1.0e0; + erf1 = (c-top/(x2*bot))/ax; + erf1 = 0.5e0+(0.5e0-exp(-x2)*erf1); + if(*x < 0.0e0) erf1 = -erf1; + return erf1; +S30: + erf1 = fifdsign(1.0e0,*x); + return erf1; +} /* END */ + +/***=====================================================================***/ +static double erfc1(int *ind,double *x) +/* +----------------------------------------------------------------------- + EVALUATION OF THE COMPLEMENTARY ERROR FUNCTION + + ERFC1(IND,X) = ERFC(X) IF IND = 0 + ERFC1(IND,X) = EXP(X*X)*ERFC(X) OTHERWISE +----------------------------------------------------------------------- +*/ +{ +static double c = .564189583547756e0; +static double a[5] = { + .771058495001320e-04,-.133733772997339e-02,.323076579225834e-01, + .479137145607681e-01,.128379167095513e+00 +}; +static double b[3] = { + .301048631703895e-02,.538971687740286e-01,.375795757275549e+00 +}; +static double p[8] = { + -1.36864857382717e-07,5.64195517478974e-01,7.21175825088309e+00, + 4.31622272220567e+01,1.52989285046940e+02,3.39320816734344e+02, + 4.51918953711873e+02,3.00459261020162e+02 +}; +static double q[8] = { + 1.00000000000000e+00,1.27827273196294e+01,7.70001529352295e+01, + 2.77585444743988e+02,6.38980264465631e+02,9.31354094850610e+02, + 7.90950925327898e+02,3.00459260956983e+02 +}; +static double r[5] = { + 2.10144126479064e+00,2.62370141675169e+01,2.13688200555087e+01, + 4.65807828718470e+00,2.82094791773523e-01 +}; +static double s[4] = { + 9.41537750555460e+01,1.87114811799590e+02,9.90191814623914e+01, + 1.80124575948747e+01 +}; +static int K1 = 1; +static double erfc1,ax,bot,e,t,top,w; +/* + .. + .. Executable Statements .. +*/ +/* + ABS(X) .LE. 0.5 +*/ + ax = fabs(*x); + if(ax > 0.5e0) goto S10; + t = *x**x; + top = (((a[0]*t+a[1])*t+a[2])*t+a[3])*t+a[4]+1.0e0; + bot = ((b[0]*t+b[1])*t+b[2])*t+1.0e0; + erfc1 = 0.5e0+(0.5e0-*x*(top/bot)); + if(*ind != 0) erfc1 = exp(t)*erfc1; + return erfc1; +S10: +/* + 0.5 .LT. ABS(X) .LE. 4 +*/ + if(ax > 4.0e0) goto S20; + top = ((((((p[0]*ax+p[1])*ax+p[2])*ax+p[3])*ax+p[4])*ax+p[5])*ax+p[6])*ax+p[ + 7]; + bot = ((((((q[0]*ax+q[1])*ax+q[2])*ax+q[3])*ax+q[4])*ax+q[5])*ax+q[6])*ax+q[ + 7]; + erfc1 = top/bot; + goto S40; +S20: +/* + ABS(X) .GT. 4 +*/ + if(*x <= -5.6e0) goto S60; + if(*ind != 0) goto S30; + if(*x > 100.0e0) goto S70; + if(*x**x > -exparg(&K1)) goto S70; +S30: + t = pow(1.0e0/ *x,2.0); + top = (((r[0]*t+r[1])*t+r[2])*t+r[3])*t+r[4]; + bot = (((s[0]*t+s[1])*t+s[2])*t+s[3])*t+1.0e0; + erfc1 = (c-t*top/bot)/ax; +S40: +/* + FINAL ASSEMBLY +*/ + if(*ind == 0) goto S50; + if(*x < 0.0e0) erfc1 = 2.0e0*exp(*x**x)-erfc1; + return erfc1; +S50: + w = *x**x; + t = w; + e = w-t; + erfc1 = (0.5e0+(0.5e0-e))*exp(-t)*erfc1; + if(*x < 0.0e0) erfc1 = 2.0e0-erfc1; + return erfc1; +S60: +/* + LIMIT VALUE FOR LARGE NEGATIVE X +*/ + erfc1 = 2.0e0; + if(*ind != 0) erfc1 = 2.0e0*exp(*x**x); + return erfc1; +S70: +/* + LIMIT VALUE FOR LARGE POSITIVE X + WHEN IND = 0 +*/ + erfc1 = 0.0e0; + return erfc1; +} /* END */ + +/***=====================================================================***/ +static double esum(int *mu,double *x) +/* +----------------------------------------------------------------------- + EVALUATION OF EXP(MU + X) +----------------------------------------------------------------------- +*/ +{ +static double esum,w; +/* + .. + .. Executable Statements .. +*/ + if(*x > 0.0e0) goto S10; + if(*mu < 0) goto S20; + w = (double)*mu+*x; + if(w > 0.0e0) goto S20; + esum = exp(w); + return esum; +S10: + if(*mu > 0) goto S20; + w = (double)*mu+*x; + if(w < 0.0e0) goto S20; + esum = exp(w); + return esum; +S20: + w = *mu; + esum = exp(w)*exp(*x); + return esum; +} /* END */ + +/***=====================================================================***/ +static double exparg(int *l) +/* +-------------------------------------------------------------------- + IF L = 0 THEN EXPARG(L) = THE LARGEST POSITIVE W FOR WHICH + EXP(W) CAN BE COMPUTED. + + IF L IS NONZERO THEN EXPARG(L) = THE LARGEST NEGATIVE W FOR + WHICH THE COMPUTED VALUE OF EXP(W) IS NONZERO. + + NOTE... ONLY AN APPROXIMATE VALUE FOR EXPARG(L) IS NEEDED. +-------------------------------------------------------------------- +*/ +{ +static int K1 = 4; +static int K2 = 9; +static int K3 = 10; +static double exparg,lnb; +static int b,m; +/* + .. + .. Executable Statements .. +*/ + b = ipmpar(&K1); + if(b != 2) goto S10; + lnb = .69314718055995e0; + goto S40; +S10: + if(b != 8) goto S20; + lnb = 2.0794415416798e0; + goto S40; +S20: + if(b != 16) goto S30; + lnb = 2.7725887222398e0; + goto S40; +S30: + lnb = log((double)b); +S40: + if(*l == 0) goto S50; + m = ipmpar(&K2)-1; + exparg = 0.99999e0*((double)m*lnb); + return exparg; +S50: + m = ipmpar(&K3); + exparg = 0.99999e0*((double)m*lnb); + return exparg; +} /* END */ + +/***=====================================================================***/ +static double fpser(double *a,double *b,double *x,double *eps) +/* +----------------------------------------------------------------------- + + EVALUATION OF I (A,B) + X + + FOR B .LT. MIN(EPS,EPS*A) AND X .LE. 0.5. + +----------------------------------------------------------------------- + + SET FPSER = X**A +*/ +{ +static int K1 = 1; +static double fpser,an,c,s,t,tol; +/* + .. + .. Executable Statements .. +*/ + fpser = 1.0e0; + if(*a <= 1.e-3**eps) goto S10; + fpser = 0.0e0; + t = *a*log(*x); + if(t < exparg(&K1)) return fpser; + fpser = exp(t); +S10: +/* + NOTE THAT 1/B(A,B) = B +*/ + fpser = *b/ *a*fpser; + tol = *eps/ *a; + an = *a+1.0e0; + t = *x; + s = t/an; +S20: + an += 1.0e0; + t = *x*t; + c = t/an; + s += c; + if(fabs(c) > tol) goto S20; + fpser *= (1.0e0+*a*s); + return fpser; +} /* END */ + +/***=====================================================================***/ +static double gam1(double *a) +/* + ------------------------------------------------------------------ + COMPUTATION OF 1/GAMMA(A+1) - 1 FOR -0.5 .LE. A .LE. 1.5 + ------------------------------------------------------------------ +*/ +{ +static double s1 = .273076135303957e+00; +static double s2 = .559398236957378e-01; +static double p[7] = { + .577215664901533e+00,-.409078193005776e+00,-.230975380857675e+00, + .597275330452234e-01,.766968181649490e-02,-.514889771323592e-02, + .589597428611429e-03 +}; +static double q[5] = { + .100000000000000e+01,.427569613095214e+00,.158451672430138e+00, + .261132021441447e-01,.423244297896961e-02 +}; +static double r[9] = { + -.422784335098468e+00,-.771330383816272e+00,-.244757765222226e+00, + .118378989872749e+00,.930357293360349e-03,-.118290993445146e-01, + .223047661158249e-02,.266505979058923e-03,-.132674909766242e-03 +}; +static double gam1,bot,d,t,top,w,T1; +/* + .. + .. Executable Statements .. +*/ + t = *a; + d = *a-0.5e0; + if(d > 0.0e0) t = d-0.5e0; + T1 = t; + if(T1 < 0) goto S40; + else if(T1 == 0) goto S10; + else goto S20; +S10: + gam1 = 0.0e0; + return gam1; +S20: + top = (((((p[6]*t+p[5])*t+p[4])*t+p[3])*t+p[2])*t+p[1])*t+p[0]; + bot = (((q[4]*t+q[3])*t+q[2])*t+q[1])*t+1.0e0; + w = top/bot; + if(d > 0.0e0) goto S30; + gam1 = *a*w; + return gam1; +S30: + gam1 = t/ *a*(w-0.5e0-0.5e0); + return gam1; +S40: + top = (((((((r[8]*t+r[7])*t+r[6])*t+r[5])*t+r[4])*t+r[3])*t+r[2])*t+r[1])*t+ + r[0]; + bot = (s2*t+s1)*t+1.0e0; + w = top/bot; + if(d > 0.0e0) goto S50; + gam1 = *a*(w+0.5e0+0.5e0); + return gam1; +S50: + gam1 = t*w/ *a; + return gam1; +} /* END */ + +/***=====================================================================***/ +static void gaminv(double *a,double *x,double *x0,double *p,double *q, + int *ierr) +/* + ---------------------------------------------------------------------- + INVERSE INCOMPLETE GAMMA RATIO FUNCTION + + GIVEN POSITIVE A, AND NONEGATIVE P AND Q WHERE P + Q = 1. + THEN X IS COMPUTED WHERE P(A,X) = P AND Q(A,X) = Q. SCHRODER + ITERATION IS EMPLOYED. THE ROUTINE ATTEMPTS TO COMPUTE X + TO 10 SIGNIFICANT DIGITS IF THIS IS POSSIBLE FOR THE + PARTICULAR COMPUTER ARITHMETIC BEING USED. + + ------------ + + X IS A VARIABLE. IF P = 0 THEN X IS ASSIGNED THE VALUE 0, + AND IF Q = 0 THEN X IS SET TO THE LARGEST FLOATING POINT + NUMBER AVAILABLE. OTHERWISE, GAMINV ATTEMPTS TO OBTAIN + A SOLUTION FOR P(A,X) = P AND Q(A,X) = Q. IF THE ROUTINE + IS SUCCESSFUL THEN THE SOLUTION IS STORED IN X. + + X0 IS AN OPTIONAL INITIAL APPROXIMATION FOR X. IF THE USER + DOES NOT WISH TO SUPPLY AN INITIAL APPROXIMATION, THEN SET + X0 .LE. 0. + + IERR IS A VARIABLE THAT REPORTS THE STATUS OF THE RESULTS. + WHEN THE ROUTINE TERMINATES, IERR HAS ONE OF THE FOLLOWING + VALUES ... + + IERR = 0 THE SOLUTION WAS OBTAINED. ITERATION WAS + NOT USED. + IERR.GT.0 THE SOLUTION WAS OBTAINED. IERR ITERATIONS + WERE PERFORMED. + IERR = -2 (INPUT ERROR) A .LE. 0 + IERR = -3 NO SOLUTION WAS OBTAINED. THE RATIO Q/A + IS TOO LARGE. + IERR = -4 (INPUT ERROR) P + Q .NE. 1 + IERR = -6 20 ITERATIONS WERE PERFORMED. THE MOST + RECENT VALUE OBTAINED FOR X IS GIVEN. + THIS CANNOT OCCUR IF X0 .LE. 0. + IERR = -7 ITERATION FAILED. NO VALUE IS GIVEN FOR X. + THIS MAY OCCUR WHEN X IS APPROXIMATELY 0. + IERR = -8 A VALUE FOR X HAS BEEN OBTAINED, BUT THE + ROUTINE IS NOT CERTAIN OF ITS ACCURACY. + ITERATION CANNOT BE PERFORMED IN THIS + CASE. IF X0 .LE. 0, THIS CAN OCCUR ONLY + WHEN P OR Q IS APPROXIMATELY 0. IF X0 IS + POSITIVE THEN THIS CAN OCCUR WHEN A IS + EXCEEDINGLY CLOSE TO X AND A IS EXTREMELY + LARGE (SAY A .GE. 1.E20). + ---------------------------------------------------------------------- + WRITTEN BY ALFRED H. MORRIS, JR. + NAVAL SURFACE WEAPONS CENTER + DAHLGREN, VIRGINIA + ------------------- +*/ +{ +static double a0 = 3.31125922108741e0; +static double a1 = 11.6616720288968e0; +static double a2 = 4.28342155967104e0; +static double a3 = .213623493715853e0; +static double b1 = 6.61053765625462e0; +static double b2 = 6.40691597760039e0; +static double b3 = 1.27364489782223e0; +static double b4 = .036117081018842e0; +static double c = .577215664901533e0; +static double ln10 = 2.302585e0; +static double tol = 1.e-5; +static double amin[2] = { + 500.0e0,100.0e0 +}; +static double bmin[2] = { + 1.e-28,1.e-13 +}; +static double dmin[2] = { + 1.e-06,1.e-04 +}; +static double emin[2] = { + 2.e-03,6.e-03 +}; +static double eps0[2] = { + 1.e-10,1.e-08 +}; +static int K1 = 1; +static int K2 = 2; +static int K3 = 3; +static int K8 = 0; +static double am1,amax,ap1,ap2,ap3,apn,b,c1,c2,c3,c4,c5,d,e,e2,eps,g,h,pn,qg,qn, + r,rta,s,s2,sum,t,u,w,xmax,xmin,xn,y,z; +static int iop; +static double T4,T5,T6,T7,T9; +/* + .. + .. Executable Statements .. +*/ +/* + ****** E, XMIN, AND XMAX ARE MACHINE DEPENDENT CONSTANTS. + E IS THE SMALLEST NUMBER FOR WHICH 1.0 + E .GT. 1.0. + XMIN IS THE SMALLEST POSITIVE NUMBER AND XMAX IS THE + LARGEST POSITIVE NUMBER. +*/ + e = spmpar(&K1); + xmin = spmpar(&K2); + xmax = spmpar(&K3); + *x = 0.0e0; + if(*a <= 0.0e0) goto S300; + t = *p+*q-1.e0; + if(fabs(t) > e) goto S320; + *ierr = 0; + if(*p == 0.0e0) return; + if(*q == 0.0e0) goto S270; + if(*a == 1.0e0) goto S280; + e2 = 2.0e0*e; + amax = 0.4e-10/(e*e); + iop = 1; + if(e > 1.e-10) iop = 2; + eps = eps0[iop-1]; + xn = *x0; + if(*x0 > 0.0e0) goto S160; +/* + SELECTION OF THE INITIAL APPROXIMATION XN OF X + WHEN A .LT. 1 +*/ + if(*a > 1.0e0) goto S80; + T4 = *a+1.0e0; + g = Xgamm(&T4); + qg = *q*g; + if(qg == 0.0e0) goto S360; + b = qg/ *a; + if(qg > 0.6e0**a) goto S40; + if(*a >= 0.30e0 || b < 0.35e0) goto S10; + t = exp(-(b+c)); + u = t*exp(t); + xn = t*exp(u); + goto S160; +S10: + if(b >= 0.45e0) goto S40; + if(b == 0.0e0) goto S360; + y = -log(b); + s = 0.5e0+(0.5e0-*a); + z = log(y); + t = y-s*z; + if(b < 0.15e0) goto S20; + xn = y-s*log(t)-log(1.0e0+s/(t+1.0e0)); + goto S220; +S20: + if(b <= 0.01e0) goto S30; + u = ((t+2.0e0*(3.0e0-*a))*t+(2.0e0-*a)*(3.0e0-*a))/((t+(5.0e0-*a))*t+2.0e0); + xn = y-s*log(t)-log(u); + goto S220; +S30: + c1 = -(s*z); + c2 = -(s*(1.0e0+c1)); + c3 = s*((0.5e0*c1+(2.0e0-*a))*c1+(2.5e0-1.5e0**a)); + c4 = -(s*(((c1/3.0e0+(2.5e0-1.5e0**a))*c1+((*a-6.0e0)**a+7.0e0))*c1+( + (11.0e0**a-46.0)**a+47.0e0)/6.0e0)); + c5 = -(s*((((-(c1/4.0e0)+(11.0e0**a-17.0e0)/6.0e0)*c1+((-(3.0e0**a)+13.0e0)* + *a-13.0e0))*c1+0.5e0*(((2.0e0**a-25.0e0)**a+72.0e0)**a-61.0e0))*c1+(( + (25.0e0**a-195.0e0)**a+477.0e0)**a-379.0e0)/12.0e0)); + xn = (((c5/y+c4)/y+c3)/y+c2)/y+c1+y; + if(*a > 1.0e0) goto S220; + if(b > bmin[iop-1]) goto S220; + *x = xn; + return; +S40: + if(b**q > 1.e-8) goto S50; + xn = exp(-(*q/ *a+c)); + goto S70; +S50: + if(*p <= 0.9e0) goto S60; + T5 = -*q; + xn = exp((alnrel(&T5)+gamln1(a))/ *a); + goto S70; +S60: + xn = exp(log(*p*g)/ *a); +S70: + if(xn == 0.0e0) goto S310; + t = 0.5e0+(0.5e0-xn/(*a+1.0e0)); + xn /= t; + goto S160; +S80: +/* + SELECTION OF THE INITIAL APPROXIMATION XN OF X + WHEN A .GT. 1 +*/ + if(*q <= 0.5e0) goto S90; + w = log(*p); + goto S100; +S90: + w = log(*q); +S100: + t = sqrt(-(2.0e0*w)); + s = t-(((a3*t+a2)*t+a1)*t+a0)/((((b4*t+b3)*t+b2)*t+b1)*t+1.0e0); + if(*q > 0.5e0) s = -s; + rta = sqrt(*a); + s2 = s*s; + xn = *a+s*rta+(s2-1.0e0)/3.0e0+s*(s2-7.0e0)/(36.0e0*rta)-((3.0e0*s2+7.0e0)* + s2-16.0e0)/(810.0e0**a)+s*((9.0e0*s2+256.0e0)*s2-433.0e0)/(38880.0e0**a* + rta); + xn = fifdmax1(xn,0.0e0); + if(*a < amin[iop-1]) goto S110; + *x = xn; + d = 0.5e0+(0.5e0-*x/ *a); + if(fabs(d) <= dmin[iop-1]) return; +S110: + if(*p <= 0.5e0) goto S130; + if(xn < 3.0e0**a) goto S220; + y = -(w+gamln(a)); + d = fifdmax1(2.0e0,*a*(*a-1.0e0)); + if(y < ln10*d) goto S120; + s = 1.0e0-*a; + z = log(y); + goto S30; +S120: + t = *a-1.0e0; + T6 = -(t/(xn+1.0e0)); + xn = y+t*log(xn)-alnrel(&T6); + T7 = -(t/(xn+1.0e0)); + xn = y+t*log(xn)-alnrel(&T7); + goto S220; +S130: + ap1 = *a+1.0e0; + if(xn > 0.70e0*ap1) goto S170; + w += gamln(&ap1); + if(xn > 0.15e0*ap1) goto S140; + ap2 = *a+2.0e0; + ap3 = *a+3.0e0; + *x = exp((w+*x)/ *a); + *x = exp((w+*x-log(1.0e0+*x/ap1*(1.0e0+*x/ap2)))/ *a); + *x = exp((w+*x-log(1.0e0+*x/ap1*(1.0e0+*x/ap2)))/ *a); + *x = exp((w+*x-log(1.0e0+*x/ap1*(1.0e0+*x/ap2*(1.0e0+*x/ap3))))/ *a); + xn = *x; + if(xn > 1.e-2*ap1) goto S140; + if(xn <= emin[iop-1]*ap1) return; + goto S170; +S140: + apn = ap1; + t = xn/apn; + sum = 1.0e0+t; +S150: + apn += 1.0e0; + t *= (xn/apn); + sum += t; + if(t > 1.e-4) goto S150; + t = w-log(sum); + xn = exp((xn+t)/ *a); + xn *= (1.0e0-(*a*log(xn)-xn-t)/(*a-xn)); + goto S170; +S160: +/* + SCHRODER ITERATION USING P +*/ + if(*p > 0.5e0) goto S220; +S170: + if(*p <= 1.e10*xmin) goto S350; + am1 = *a-0.5e0-0.5e0; +S180: + if(*a <= amax) goto S190; + d = 0.5e0+(0.5e0-xn/ *a); + if(fabs(d) <= e2) goto S350; +S190: + if(*ierr >= 20) goto S330; + *ierr += 1; + gratio(a,&xn,&pn,&qn,&K8); + if(pn == 0.0e0 || qn == 0.0e0) goto S350; + r = rcomp(a,&xn); + if(r == 0.0e0) goto S350; + t = (pn-*p)/r; + w = 0.5e0*(am1-xn); + if(fabs(t) <= 0.1e0 && fabs(w*t) <= 0.1e0) goto S200; + *x = xn*(1.0e0-t); + if(*x <= 0.0e0) goto S340; + d = fabs(t); + goto S210; +S200: + h = t*(1.0e0+w*t); + *x = xn*(1.0e0-h); + if(*x <= 0.0e0) goto S340; + if(fabs(w) >= 1.0e0 && fabs(w)*t*t <= eps) return; + d = fabs(h); +S210: + xn = *x; + if(d > tol) goto S180; + if(d <= eps) return; + if(fabs(*p-pn) <= tol**p) return; + goto S180; +S220: +/* + SCHRODER ITERATION USING Q +*/ + if(*q <= 1.e10*xmin) goto S350; + am1 = *a-0.5e0-0.5e0; +S230: + if(*a <= amax) goto S240; + d = 0.5e0+(0.5e0-xn/ *a); + if(fabs(d) <= e2) goto S350; +S240: + if(*ierr >= 20) goto S330; + *ierr += 1; + gratio(a,&xn,&pn,&qn,&K8); + if(pn == 0.0e0 || qn == 0.0e0) goto S350; + r = rcomp(a,&xn); + if(r == 0.0e0) goto S350; + t = (*q-qn)/r; + w = 0.5e0*(am1-xn); + if(fabs(t) <= 0.1e0 && fabs(w*t) <= 0.1e0) goto S250; + *x = xn*(1.0e0-t); + if(*x <= 0.0e0) goto S340; + d = fabs(t); + goto S260; +S250: + h = t*(1.0e0+w*t); + *x = xn*(1.0e0-h); + if(*x <= 0.0e0) goto S340; + if(fabs(w) >= 1.0e0 && fabs(w)*t*t <= eps) return; + d = fabs(h); +S260: + xn = *x; + if(d > tol) goto S230; + if(d <= eps) return; + if(fabs(*q-qn) <= tol**q) return; + goto S230; +S270: +/* + SPECIAL CASES +*/ + *x = xmax; + return; +S280: + if(*q < 0.9e0) goto S290; + T9 = -*p; + *x = -alnrel(&T9); + return; +S290: + *x = -log(*q); + return; +S300: +/* + ERROR RETURN +*/ + *ierr = -2; + return; +S310: + *ierr = -3; + return; +S320: + *ierr = -4; + return; +S330: + *ierr = -6; + return; +S340: + *ierr = -7; + return; +S350: + *x = xn; + *ierr = -8; + return; +S360: + *x = xmax; + *ierr = -8; + return; +} /* END */ + +/***=====================================================================***/ +static double gamln(double *a) +/* +----------------------------------------------------------------------- + EVALUATION OF LN(GAMMA(A)) FOR POSITIVE A +----------------------------------------------------------------------- + WRITTEN BY ALFRED H. MORRIS + NAVAL SURFACE WARFARE CENTER + DAHLGREN, VIRGINIA +-------------------------- + D = 0.5*(LN(2*PI) - 1) +-------------------------- +*/ +{ +static double c0 = .833333333333333e-01; +static double c1 = -.277777777760991e-02; +static double c2 = .793650666825390e-03; +static double c3 = -.595202931351870e-03; +static double c4 = .837308034031215e-03; +static double c5 = -.165322962780713e-02; +static double d = .418938533204673e0; +static double gamln,t,w; +static int i,n; +static double T1; +/* + .. + .. Executable Statements .. +*/ + if(*a > 0.8e0) goto S10; + gamln = gamln1(a)-log(*a); + return gamln; +S10: + if(*a > 2.25e0) goto S20; + t = *a-0.5e0-0.5e0; + gamln = gamln1(&t); + return gamln; +S20: + if(*a >= 10.0e0) goto S40; + n = *a-1.25e0; + t = *a; + w = 1.0e0; + for(i=1; i<=n; i++) { + t -= 1.0e0; + w = t*w; + } + T1 = t-1.0e0; + gamln = gamln1(&T1)+log(w); + return gamln; +S40: + t = pow(1.0e0/ *a,2.0); + w = (((((c5*t+c4)*t+c3)*t+c2)*t+c1)*t+c0)/ *a; + gamln = d+w+(*a-0.5e0)*(log(*a)-1.0e0); + return gamln; +} /* END */ + +/***=====================================================================***/ +static double gamln1(double *a) +/* +----------------------------------------------------------------------- + EVALUATION OF LN(GAMMA(1 + A)) FOR -0.2 .LE. A .LE. 1.25 +----------------------------------------------------------------------- +*/ +{ +static double p0 = .577215664901533e+00; +static double p1 = .844203922187225e+00; +static double p2 = -.168860593646662e+00; +static double p3 = -.780427615533591e+00; +static double p4 = -.402055799310489e+00; +static double p5 = -.673562214325671e-01; +static double p6 = -.271935708322958e-02; +static double q1 = .288743195473681e+01; +static double q2 = .312755088914843e+01; +static double q3 = .156875193295039e+01; +static double q4 = .361951990101499e+00; +static double q5 = .325038868253937e-01; +static double q6 = .667465618796164e-03; +static double r0 = .422784335098467e+00; +static double r1 = .848044614534529e+00; +static double r2 = .565221050691933e+00; +static double r3 = .156513060486551e+00; +static double r4 = .170502484022650e-01; +static double r5 = .497958207639485e-03; +static double s1 = .124313399877507e+01; +static double s2 = .548042109832463e+00; +static double s3 = .101552187439830e+00; +static double s4 = .713309612391000e-02; +static double s5 = .116165475989616e-03; +static double gamln1,w,x; +/* + .. + .. Executable Statements .. +*/ + if(*a >= 0.6e0) goto S10; + w = ((((((p6**a+p5)**a+p4)**a+p3)**a+p2)**a+p1)**a+p0)/((((((q6**a+q5)**a+ + q4)**a+q3)**a+q2)**a+q1)**a+1.0e0); + gamln1 = -(*a*w); + return gamln1; +S10: + x = *a-0.5e0-0.5e0; + w = (((((r5*x+r4)*x+r3)*x+r2)*x+r1)*x+r0)/(((((s5*x+s4)*x+s3)*x+s2)*x+s1)*x + +1.0e0); + gamln1 = x*w; + return gamln1; +} /* END */ + +/***=====================================================================***/ +static double Xgamm(double *a) +/* +----------------------------------------------------------------------- + + EVALUATION OF THE GAMMA FUNCTION FOR REAL ARGUMENTS + + ----------- + + GAMMA(A) IS ASSIGNED THE VALUE 0 WHEN THE GAMMA FUNCTION CANNOT + BE COMPUTED. + +----------------------------------------------------------------------- + WRITTEN BY ALFRED H. MORRIS, JR. + NAVAL SURFACE WEAPONS CENTER + DAHLGREN, VIRGINIA +----------------------------------------------------------------------- +*/ +{ +static double d = .41893853320467274178e0; +static double pi = 3.1415926535898e0; +static double r1 = .820756370353826e-03; +static double r2 = -.595156336428591e-03; +static double r3 = .793650663183693e-03; +static double r4 = -.277777777770481e-02; +static double r5 = .833333333333333e-01; +static double p[7] = { + .539637273585445e-03,.261939260042690e-02,.204493667594920e-01, + .730981088720487e-01,.279648642639792e+00,.553413866010467e+00,1.0e0 +}; +static double q[7] = { + -.832979206704073e-03,.470059485860584e-02,.225211131035340e-01, + -.170458969313360e+00,-.567902761974940e-01,.113062953091122e+01,1.0e0 +}; +static int K2 = 3; +static int K3 = 0; +static double Xgamm,bot,g,lnx,s,t,top,w,x,z; +static int i,j,m,n,T1; +/* + .. + .. Executable Statements .. +*/ + Xgamm = 0.0e0; + x = *a; + if(fabs(*a) >= 15.0e0) goto S110; +/* +----------------------------------------------------------------------- + EVALUATION OF GAMMA(A) FOR ABS(A) .LT. 15 +----------------------------------------------------------------------- +*/ + t = 1.0e0; + m = fifidint(*a)-1; +/* + LET T BE THE PRODUCT OF A-J WHEN A .GE. 2 +*/ + T1 = m; + if(T1 < 0) goto S40; + else if(T1 == 0) goto S30; + else goto S10; +S10: + for(j=1; j<=m; j++) { + x -= 1.0e0; + t = x*t; + } +S30: + x -= 1.0e0; + goto S80; +S40: +/* + LET T BE THE PRODUCT OF A+J WHEN A .LT. 1 +*/ + t = *a; + if(*a > 0.0e0) goto S70; + m = -m-1; + if(m == 0) goto S60; + for(j=1; j<=m; j++) { + x += 1.0e0; + t = x*t; + } +S60: + x += (0.5e0+0.5e0); + t = x*t; + if(t == 0.0e0) return Xgamm; +S70: +/* + THE FOLLOWING CODE CHECKS IF 1/T CAN OVERFLOW. THIS + CODE MAY BE OMITTED IF DESIRED. +*/ + if(fabs(t) >= 1.e-30) goto S80; + if(fabs(t)*spmpar(&K2) <= 1.0001e0) return Xgamm; + Xgamm = 1.0e0/t; + return Xgamm; +S80: +/* + COMPUTE GAMMA(1 + X) FOR 0 .LE. X .LT. 1 +*/ + top = p[0]; + bot = q[0]; + for(i=1; i<7; i++) { + top = p[i]+x*top; + bot = q[i]+x*bot; + } + Xgamm = top/bot; +/* + TERMINATION +*/ + if(*a < 1.0e0) goto S100; + Xgamm *= t; + return Xgamm; +S100: + Xgamm /= t; + return Xgamm; +S110: +/* +----------------------------------------------------------------------- + EVALUATION OF GAMMA(A) FOR ABS(A) .GE. 15 +----------------------------------------------------------------------- +*/ + if(fabs(*a) >= 1.e3) return Xgamm; + if(*a > 0.0e0) goto S120; + x = -*a; + n = x; + t = x-(double)n; + if(t > 0.9e0) t = 1.0e0-t; + s = sin(pi*t)/pi; + if(fifmod(n,2) == 0) s = -s; + if(s == 0.0e0) return Xgamm; +S120: +/* + COMPUTE THE MODIFIED ASYMPTOTIC SUM +*/ + t = 1.0e0/(x*x); + g = ((((r1*t+r2)*t+r3)*t+r4)*t+r5)/x; +/* + ONE MAY REPLACE THE NEXT STATEMENT WITH LNX = ALOG(X) + BUT LESS ACCURACY WILL NORMALLY BE OBTAINED. +*/ + lnx = log(x); +/* + FINAL ASSEMBLY +*/ + z = x; + g = d+g+(z-0.5e0)*(lnx-1.e0); + w = g; + t = g-w; + if(w > 0.99999e0*exparg(&K3)) return Xgamm; + Xgamm = exp(w)*(1.0e0+t); + if(*a < 0.0e0) Xgamm = 1.0e0/(Xgamm*s)/x; + return Xgamm; +} /* END */ + +/***=====================================================================***/ +static void grat1(double *a,double *x,double *r,double *p,double *q, + double *eps) +{ +static int K2 = 0; +static double a2n,a2nm1,am0,an,an0,b2n,b2nm1,c,cma,g,h,j,l,sum,t,tol,w,z,T1,T3; +/* + .. + .. Executable Statements .. +*/ +/* +----------------------------------------------------------------------- + EVALUATION OF THE INCOMPLETE GAMMA RATIO FUNCTIONS + P(A,X) AND Q(A,X) + IT IS ASSUMED THAT A .LE. 1. EPS IS THE TOLERANCE TO BE USED. + THE INPUT ARGUMENT R HAS THE VALUE E**(-X)*X**A/GAMMA(A). +----------------------------------------------------------------------- +*/ + if(*a**x == 0.0e0) goto S120; + if(*a == 0.5e0) goto S100; + if(*x < 1.1e0) goto S10; + goto S60; +S10: +/* + TAYLOR SERIES FOR P(A,X)/X**A +*/ + an = 3.0e0; + c = *x; + sum = *x/(*a+3.0e0); + tol = 0.1e0**eps/(*a+1.0e0); +S20: + an += 1.0e0; + c = -(c*(*x/an)); + t = c/(*a+an); + sum += t; + if(fabs(t) > tol) goto S20; + j = *a**x*((sum/6.0e0-0.5e0/(*a+2.0e0))**x+1.0e0/(*a+1.0e0)); + z = *a*log(*x); + h = gam1(a); + g = 1.0e0+h; + if(*x < 0.25e0) goto S30; + if(*a < *x/2.59e0) goto S50; + goto S40; +S30: + if(z > -.13394e0) goto S50; +S40: + w = exp(z); + *p = w*g*(0.5e0+(0.5e0-j)); + *q = 0.5e0+(0.5e0-*p); + return; +S50: + l = rexp(&z); + w = 0.5e0+(0.5e0+l); + *q = (w*j-l)*g-h; + if(*q < 0.0e0) goto S90; + *p = 0.5e0+(0.5e0-*q); + return; +S60: +/* + CONTINUED FRACTION EXPANSION +*/ + a2nm1 = a2n = 1.0e0; + b2nm1 = *x; + b2n = *x+(1.0e0-*a); + c = 1.0e0; +S70: + a2nm1 = *x*a2n+c*a2nm1; + b2nm1 = *x*b2n+c*b2nm1; + am0 = a2nm1/b2nm1; + c += 1.0e0; + cma = c-*a; + a2n = a2nm1+cma*a2n; + b2n = b2nm1+cma*b2n; + an0 = a2n/b2n; + if(fabs(an0-am0) >= *eps*an0) goto S70; + *q = *r*an0; + *p = 0.5e0+(0.5e0-*q); + return; +S80: +/* + SPECIAL CASES +*/ + *p = 0.0e0; + *q = 1.0e0; + return; +S90: + *p = 1.0e0; + *q = 0.0e0; + return; +S100: + if(*x >= 0.25e0) goto S110; + T1 = sqrt(*x); + *p = erf1(&T1); + *q = 0.5e0+(0.5e0-*p); + return; +S110: + T3 = sqrt(*x); + *q = erfc1(&K2,&T3); + *p = 0.5e0+(0.5e0-*q); + return; +S120: + if(*x <= *a) goto S80; + goto S90; +} /* END */ + +/***=====================================================================***/ +static void gratio(double *a,double *x,double *ans,double *qans,int *ind) +/* + ---------------------------------------------------------------------- + EVALUATION OF THE INCOMPLETE GAMMA RATIO FUNCTIONS + P(A,X) AND Q(A,X) + + ---------- + + IT IS ASSUMED THAT A AND X ARE NONNEGATIVE, WHERE A AND X + ARE NOT BOTH 0. + + ANS AND QANS ARE VARIABLES. GRATIO ASSIGNS ANS THE VALUE + P(A,X) AND QANS THE VALUE Q(A,X). IND MAY BE ANY INTEGER. + IF IND = 0 THEN THE USER IS REQUESTING AS MUCH ACCURACY AS + POSSIBLE (UP TO 14 SIGNIFICANT DIGITS). OTHERWISE, IF + IND = 1 THEN ACCURACY IS REQUESTED TO WITHIN 1 UNIT OF THE + 6-TH SIGNIFICANT DIGIT, AND IF IND .NE. 0,1 THEN ACCURACY + IS REQUESTED TO WITHIN 1 UNIT OF THE 3RD SIGNIFICANT DIGIT. + + ERROR RETURN ... + ANS IS ASSIGNED THE VALUE 2 WHEN A OR X IS NEGATIVE, + WHEN A*X = 0, OR WHEN P(A,X) AND Q(A,X) ARE INDETERMINANT. + P(A,X) AND Q(A,X) ARE COMPUTATIONALLY INDETERMINANT WHEN + X IS EXCEEDINGLY CLOSE TO A AND A IS EXTREMELY LARGE. + ---------------------------------------------------------------------- + WRITTEN BY ALFRED H. MORRIS, JR. + NAVAL SURFACE WEAPONS CENTER + DAHLGREN, VIRGINIA + -------------------- +*/ +{ +static double alog10 = 2.30258509299405e0; +static double d10 = -.185185185185185e-02; +static double d20 = .413359788359788e-02; +static double d30 = .649434156378601e-03; +static double d40 = -.861888290916712e-03; +static double d50 = -.336798553366358e-03; +static double d60 = .531307936463992e-03; +static double d70 = .344367606892378e-03; +static double rt2pin = .398942280401433e0; +static double rtpi = 1.77245385090552e0; +static double third = .333333333333333e0; +static double acc0[3] = { + 5.e-15,5.e-7,5.e-4 +}; +static double big[3] = { + 20.0e0,14.0e0,10.0e0 +}; +static double d0[13] = { + .833333333333333e-01,-.148148148148148e-01,.115740740740741e-02, + .352733686067019e-03,-.178755144032922e-03,.391926317852244e-04, + -.218544851067999e-05,-.185406221071516e-05,.829671134095309e-06, + -.176659527368261e-06,.670785354340150e-08,.102618097842403e-07, + -.438203601845335e-08 +}; +static double d1[12] = { + -.347222222222222e-02,.264550264550265e-02,-.990226337448560e-03, + .205761316872428e-03,-.401877572016461e-06,-.180985503344900e-04, + .764916091608111e-05,-.161209008945634e-05,.464712780280743e-08, + .137863344691572e-06,-.575254560351770e-07,.119516285997781e-07 +}; +static double d2[10] = { + -.268132716049383e-02,.771604938271605e-03,.200938786008230e-05, + -.107366532263652e-03,.529234488291201e-04,-.127606351886187e-04, + .342357873409614e-07,.137219573090629e-05,-.629899213838006e-06, + .142806142060642e-06 +}; +static double d3[8] = { + .229472093621399e-03,-.469189494395256e-03,.267720632062839e-03, + -.756180167188398e-04,-.239650511386730e-06,.110826541153473e-04, + -.567495282699160e-05,.142309007324359e-05 +}; +static double d4[6] = { + .784039221720067e-03,-.299072480303190e-03,-.146384525788434e-05, + .664149821546512e-04,-.396836504717943e-04,.113757269706784e-04 +}; +static double d5[4] = { + -.697281375836586e-04,.277275324495939e-03,-.199325705161888e-03, + .679778047793721e-04 +}; +static double d6[2] = { + -.592166437353694e-03,.270878209671804e-03 +}; +static double e00[3] = { + .25e-3,.25e-1,.14e0 +}; +static double x00[3] = { + 31.0e0,17.0e0,9.7e0 +}; +static int K1 = 1; +static int K2 = 0; +static double a2n,a2nm1,acc,am0,amn,an,an0,apn,b2n,b2nm1,c,c0,c1,c2,c3,c4,c5,c6, + cma,e,e0,g,h,j,l,r,rta,rtx,s,sum,t,t1,tol,twoa,u,w,x0,y,z; +static int i,iop,m,max,n; +static double wk[20],T3; +static int T4,T5; +static double T6,T7; +/* + .. + .. Executable Statements .. +*/ +/* + -------------------- + ****** E IS A MACHINE DEPENDENT CONSTANT. E IS THE SMALLEST + FLOATING POINT NUMBER FOR WHICH 1.0 + E .GT. 1.0 . +*/ + e = spmpar(&K1); + if(*a < 0.0e0 || *x < 0.0e0) goto S430; + if(*a == 0.0e0 && *x == 0.0e0) goto S430; + if(*a**x == 0.0e0) goto S420; + iop = *ind+1; + if(iop != 1 && iop != 2) iop = 3; + acc = fifdmax1(acc0[iop-1],e); + e0 = e00[iop-1]; + x0 = x00[iop-1]; +/* + SELECT THE APPROPRIATE ALGORITHM +*/ + if(*a >= 1.0e0) goto S10; + if(*a == 0.5e0) goto S390; + if(*x < 1.1e0) goto S160; + t1 = *a*log(*x)-*x; + u = *a*exp(t1); + if(u == 0.0e0) goto S380; + r = u*(1.0e0+gam1(a)); + goto S250; +S10: + if(*a >= big[iop-1]) goto S30; + if(*a > *x || *x >= x0) goto S20; + twoa = *a+*a; + m = fifidint(twoa); + if(twoa != (double)m) goto S20; + i = m/2; + if(*a == (double)i) goto S210; + goto S220; +S20: + t1 = *a*log(*x)-*x; + r = exp(t1)/Xgamm(a); + goto S40; +S30: + l = *x/ *a; + if(l == 0.0e0) goto S370; + s = 0.5e0+(0.5e0-l); + z = rlog(&l); + if(z >= 700.0e0/ *a) goto S410; + y = *a*z; + rta = sqrt(*a); + if(fabs(s) <= e0/rta) goto S330; + if(fabs(s) <= 0.4e0) goto S270; + t = pow(1.0e0/ *a,2.0); + t1 = (((0.75e0*t-1.0e0)*t+3.5e0)*t-105.0e0)/(*a*1260.0e0); + t1 -= y; + r = rt2pin*rta*exp(t1); +S40: + if(r == 0.0e0) goto S420; + if(*x <= fifdmax1(*a,alog10)) goto S50; + if(*x < x0) goto S250; + goto S100; +S50: +/* + TAYLOR SERIES FOR P/R +*/ + apn = *a+1.0e0; + t = *x/apn; + wk[0] = t; + for(n=2; n<=20; n++) { + apn += 1.0e0; + t *= (*x/apn); + if(t <= 1.e-3) goto S70; + wk[n-1] = t; + } + n = 20; +S70: + sum = t; + tol = 0.5e0*acc; +S80: + apn += 1.0e0; + t *= (*x/apn); + sum += t; + if(t > tol) goto S80; + max = n-1; + for(m=1; m<=max; m++) { + n -= 1; + sum += wk[n-1]; + } + *ans = r/ *a*(1.0e0+sum); + *qans = 0.5e0+(0.5e0-*ans); + return; +S100: +/* + ASYMPTOTIC EXPANSION +*/ + amn = *a-1.0e0; + t = amn/ *x; + wk[0] = t; + for(n=2; n<=20; n++) { + amn -= 1.0e0; + t *= (amn/ *x); + if(fabs(t) <= 1.e-3) goto S120; + wk[n-1] = t; + } + n = 20; +S120: + sum = t; +S130: + if(fabs(t) <= acc) goto S140; + amn -= 1.0e0; + t *= (amn/ *x); + sum += t; + goto S130; +S140: + max = n-1; + for(m=1; m<=max; m++) { + n -= 1; + sum += wk[n-1]; + } + *qans = r/ *x*(1.0e0+sum); + *ans = 0.5e0+(0.5e0-*qans); + return; +S160: +/* + TAYLOR SERIES FOR P(A,X)/X**A +*/ + an = 3.0e0; + c = *x; + sum = *x/(*a+3.0e0); + tol = 3.0e0*acc/(*a+1.0e0); +S170: + an += 1.0e0; + c = -(c*(*x/an)); + t = c/(*a+an); + sum += t; + if(fabs(t) > tol) goto S170; + j = *a**x*((sum/6.0e0-0.5e0/(*a+2.0e0))**x+1.0e0/(*a+1.0e0)); + z = *a*log(*x); + h = gam1(a); + g = 1.0e0+h; + if(*x < 0.25e0) goto S180; + if(*a < *x/2.59e0) goto S200; + goto S190; +S180: + if(z > -.13394e0) goto S200; +S190: + w = exp(z); + *ans = w*g*(0.5e0+(0.5e0-j)); + *qans = 0.5e0+(0.5e0-*ans); + return; +S200: + l = rexp(&z); + w = 0.5e0+(0.5e0+l); + *qans = (w*j-l)*g-h; + if(*qans < 0.0e0) goto S380; + *ans = 0.5e0+(0.5e0-*qans); + return; +S210: +/* + FINITE SUMS FOR Q WHEN A .GE. 1 + AND 2*A IS AN INTEGER +*/ + sum = exp(-*x); + t = sum; + n = 1; + c = 0.0e0; + goto S230; +S220: + rtx = sqrt(*x); + sum = erfc1(&K2,&rtx); + t = exp(-*x)/(rtpi*rtx); + n = 0; + c = -0.5e0; +S230: + if(n == i) goto S240; + n += 1; + c += 1.0e0; + t = *x*t/c; + sum += t; + goto S230; +S240: + *qans = sum; + *ans = 0.5e0+(0.5e0-*qans); + return; +S250: +/* + CONTINUED FRACTION EXPANSION +*/ + tol = fifdmax1(5.0e0*e,acc); + a2nm1 = a2n = 1.0e0; + b2nm1 = *x; + b2n = *x+(1.0e0-*a); + c = 1.0e0; +S260: + a2nm1 = *x*a2n+c*a2nm1; + b2nm1 = *x*b2n+c*b2nm1; + am0 = a2nm1/b2nm1; + c += 1.0e0; + cma = c-*a; + a2n = a2nm1+cma*a2n; + b2n = b2nm1+cma*b2n; + an0 = a2n/b2n; + if(fabs(an0-am0) >= tol*an0) goto S260; + *qans = r*an0; + *ans = 0.5e0+(0.5e0-*qans); + return; +S270: +/* + GENERAL TEMME EXPANSION +*/ + if(fabs(s) <= 2.0e0*e && *a*e*e > 3.28e-3) goto S430; + c = exp(-y); + T3 = sqrt(y); + w = 0.5e0*erfc1(&K1,&T3); + u = 1.0e0/ *a; + z = sqrt(z+z); + if(l < 1.0e0) z = -z; + T4 = iop-2; + if(T4 < 0) goto S280; + else if(T4 == 0) goto S290; + else goto S300; +S280: + if(fabs(s) <= 1.e-3) goto S340; + c0 = ((((((((((((d0[12]*z+d0[11])*z+d0[10])*z+d0[9])*z+d0[8])*z+d0[7])*z+d0[ + 6])*z+d0[5])*z+d0[4])*z+d0[3])*z+d0[2])*z+d0[1])*z+d0[0])*z-third; + c1 = (((((((((((d1[11]*z+d1[10])*z+d1[9])*z+d1[8])*z+d1[7])*z+d1[6])*z+d1[5] + )*z+d1[4])*z+d1[3])*z+d1[2])*z+d1[1])*z+d1[0])*z+d10; + c2 = (((((((((d2[9]*z+d2[8])*z+d2[7])*z+d2[6])*z+d2[5])*z+d2[4])*z+d2[3])*z+ + d2[2])*z+d2[1])*z+d2[0])*z+d20; + c3 = (((((((d3[7]*z+d3[6])*z+d3[5])*z+d3[4])*z+d3[3])*z+d3[2])*z+d3[1])*z+ + d3[0])*z+d30; + c4 = (((((d4[5]*z+d4[4])*z+d4[3])*z+d4[2])*z+d4[1])*z+d4[0])*z+d40; + c5 = (((d5[3]*z+d5[2])*z+d5[1])*z+d5[0])*z+d50; + c6 = (d6[1]*z+d6[0])*z+d60; + t = ((((((d70*u+c6)*u+c5)*u+c4)*u+c3)*u+c2)*u+c1)*u+c0; + goto S310; +S290: + c0 = (((((d0[5]*z+d0[4])*z+d0[3])*z+d0[2])*z+d0[1])*z+d0[0])*z-third; + c1 = (((d1[3]*z+d1[2])*z+d1[1])*z+d1[0])*z+d10; + c2 = d2[0]*z+d20; + t = (c2*u+c1)*u+c0; + goto S310; +S300: + t = ((d0[2]*z+d0[1])*z+d0[0])*z-third; +S310: + if(l < 1.0e0) goto S320; + *qans = c*(w+rt2pin*t/rta); + *ans = 0.5e0+(0.5e0-*qans); + return; +S320: + *ans = c*(w-rt2pin*t/rta); + *qans = 0.5e0+(0.5e0-*ans); + return; +S330: +/* + TEMME EXPANSION FOR L = 1 +*/ + if(*a*e*e > 3.28e-3) goto S430; + c = 0.5e0+(0.5e0-y); + w = (0.5e0-sqrt(y)*(0.5e0+(0.5e0-y/3.0e0))/rtpi)/c; + u = 1.0e0/ *a; + z = sqrt(z+z); + if(l < 1.0e0) z = -z; + T5 = iop-2; + if(T5 < 0) goto S340; + else if(T5 == 0) goto S350; + else goto S360; +S340: + c0 = ((((((d0[6]*z+d0[5])*z+d0[4])*z+d0[3])*z+d0[2])*z+d0[1])*z+d0[0])*z- + third; + c1 = (((((d1[5]*z+d1[4])*z+d1[3])*z+d1[2])*z+d1[1])*z+d1[0])*z+d10; + c2 = ((((d2[4]*z+d2[3])*z+d2[2])*z+d2[1])*z+d2[0])*z+d20; + c3 = (((d3[3]*z+d3[2])*z+d3[1])*z+d3[0])*z+d30; + c4 = (d4[1]*z+d4[0])*z+d40; + c5 = (d5[1]*z+d5[0])*z+d50; + c6 = d6[0]*z+d60; + t = ((((((d70*u+c6)*u+c5)*u+c4)*u+c3)*u+c2)*u+c1)*u+c0; + goto S310; +S350: + c0 = (d0[1]*z+d0[0])*z-third; + c1 = d1[0]*z+d10; + t = (d20*u+c1)*u+c0; + goto S310; +S360: + t = d0[0]*z-third; + goto S310; +S370: +/* + SPECIAL CASES +*/ + *ans = 0.0e0; + *qans = 1.0e0; + return; +S380: + *ans = 1.0e0; + *qans = 0.0e0; + return; +S390: + if(*x >= 0.25e0) goto S400; + T6 = sqrt(*x); + *ans = erf1(&T6); + *qans = 0.5e0+(0.5e0-*ans); + return; +S400: + T7 = sqrt(*x); + *qans = erfc1(&K2,&T7); + *ans = 0.5e0+(0.5e0-*qans); + return; +S410: + if(fabs(s) <= 2.0e0*e) goto S430; +S420: + if(*x <= *a) goto S370; + goto S380; +S430: +/* + ERROR RETURN +*/ + *ans = 2.0e0; + return; +} /* END */ + +/***=====================================================================***/ +static double gsumln(double *a,double *b) +/* +----------------------------------------------------------------------- + EVALUATION OF THE FUNCTION LN(GAMMA(A + B)) + FOR 1 .LE. A .LE. 2 AND 1 .LE. B .LE. 2 +----------------------------------------------------------------------- +*/ +{ +static double gsumln,x,T1,T2; +/* + .. + .. Executable Statements .. +*/ + x = *a+*b-2.e0; + if(x > 0.25e0) goto S10; + T1 = 1.0e0+x; + gsumln = gamln1(&T1); + return gsumln; +S10: + if(x > 1.25e0) goto S20; + gsumln = gamln1(&x)+alnrel(&x); + return gsumln; +S20: + T2 = x-1.0e0; + gsumln = gamln1(&T2)+log(x*(1.0e0+x)); + return gsumln; +} /* END */ + +/***=====================================================================***/ +static double psi(double *xx) +/* +--------------------------------------------------------------------- + + EVALUATION OF THE DIGAMMA FUNCTION + + ----------- + + PSI(XX) IS ASSIGNED THE VALUE 0 WHEN THE DIGAMMA FUNCTION CANNOT + BE COMPUTED. + + THE MAIN COMPUTATION INVOLVES EVALUATION OF RATIONAL CHEBYSHEV + APPROXIMATIONS PUBLISHED IN MATH. COMP. 27, 123-127(1973) BY + CODY, STRECOK AND THACHER. + +--------------------------------------------------------------------- + PSI WAS WRITTEN AT ARGONNE NATIONAL LABORATORY FOR THE FUNPACK + PACKAGE OF SPECIAL FUNCTION SUBROUTINES. PSI WAS MODIFIED BY + A.H. MORRIS (NSWC). +--------------------------------------------------------------------- +*/ +{ +static double dx0 = 1.461632144968362341262659542325721325e0; +static double piov4 = .785398163397448e0; +static double p1[7] = { + .895385022981970e-02,.477762828042627e+01,.142441585084029e+03, + .118645200713425e+04,.363351846806499e+04,.413810161269013e+04, + .130560269827897e+04 +}; +static double p2[4] = { + -.212940445131011e+01,-.701677227766759e+01,-.448616543918019e+01, + -.648157123766197e+00 +}; +static double q1[6] = { + .448452573429826e+02,.520752771467162e+03,.221000799247830e+04, + .364127349079381e+04,.190831076596300e+04,.691091682714533e-05 +}; +static double q2[4] = { + .322703493791143e+02,.892920700481861e+02,.546117738103215e+02, + .777788548522962e+01 +}; +static int K1 = 3; +static int K2 = 1; +static double psi,aug,den,sgn,upper,w,x,xmax1,xmx0,xsmall,z; +static int i,m,n,nq; +/* + .. + .. Executable Statements .. +*/ +/* +--------------------------------------------------------------------- + MACHINE DEPENDENT CONSTANTS ... + XMAX1 = THE SMALLEST POSITIVE FLOATING POINT CONSTANT + WITH ENTIRELY INTEGER REPRESENTATION. ALSO USED + AS NEGATIVE OF LOWER BOUND ON ACCEPTABLE NEGATIVE + ARGUMENTS AND AS THE POSITIVE ARGUMENT BEYOND WHICH + PSI MAY BE REPRESENTED AS ALOG(X). + XSMALL = ABSOLUTE ARGUMENT BELOW WHICH PI*COTAN(PI*X) + MAY BE REPRESENTED BY 1/X. +--------------------------------------------------------------------- +*/ + xmax1 = ipmpar(&K1); + xmax1 = fifdmin1(xmax1,1.0e0/spmpar(&K2)); + xsmall = 1.e-9; + x = *xx; + aug = 0.0e0; + if(x >= 0.5e0) goto S50; +/* +--------------------------------------------------------------------- + X .LT. 0.5, USE REFLECTION FORMULA + PSI(1-X) = PSI(X) + PI * COTAN(PI*X) +--------------------------------------------------------------------- +*/ + if(fabs(x) > xsmall) goto S10; + if(x == 0.0e0) goto S100; +/* +--------------------------------------------------------------------- + 0 .LT. ABS(X) .LE. XSMALL. USE 1/X AS A SUBSTITUTE + FOR PI*COTAN(PI*X) +--------------------------------------------------------------------- +*/ + aug = -(1.0e0/x); + goto S40; +S10: +/* +--------------------------------------------------------------------- + REDUCTION OF ARGUMENT FOR COTAN +--------------------------------------------------------------------- +*/ + w = -x; + sgn = piov4; + if(w > 0.0e0) goto S20; + w = -w; + sgn = -sgn; +S20: +/* +--------------------------------------------------------------------- + MAKE AN ERROR EXIT IF X .LE. -XMAX1 +--------------------------------------------------------------------- +*/ + if(w >= xmax1) goto S100; + nq = fifidint(w); + w -= (double)nq; + nq = fifidint(w*4.0e0); + w = 4.0e0*(w-(double)nq*.25e0); +/* +--------------------------------------------------------------------- + W IS NOW RELATED TO THE FRACTIONAL PART OF 4.0 * X. + ADJUST ARGUMENT TO CORRESPOND TO VALUES IN FIRST + QUADRANT AND DETERMINE SIGN +--------------------------------------------------------------------- +*/ + n = nq/2; + if(n+n != nq) w = 1.0e0-w; + z = piov4*w; + m = n/2; + if(m+m != n) sgn = -sgn; +/* +--------------------------------------------------------------------- + DETERMINE FINAL VALUE FOR -PI*COTAN(PI*X) +--------------------------------------------------------------------- +*/ + n = (nq+1)/2; + m = n/2; + m += m; + if(m != n) goto S30; +/* +--------------------------------------------------------------------- + CHECK FOR SINGULARITY +--------------------------------------------------------------------- +*/ + if(z == 0.0e0) goto S100; +/* +--------------------------------------------------------------------- + USE COS/SIN AS A SUBSTITUTE FOR COTAN, AND + SIN/COS AS A SUBSTITUTE FOR TAN +--------------------------------------------------------------------- +*/ + aug = sgn*(cos(z)/sin(z)*4.0e0); + goto S40; +S30: + aug = sgn*(sin(z)/cos(z)*4.0e0); +S40: + x = 1.0e0-x; +S50: + if(x > 3.0e0) goto S70; +/* +--------------------------------------------------------------------- + 0.5 .LE. X .LE. 3.0 +--------------------------------------------------------------------- +*/ + den = x; + upper = p1[0]*x; + for(i=1; i<=5; i++) { + den = (den+q1[i-1])*x; + upper = (upper+p1[i+1-1])*x; + } + den = (upper+p1[6])/(den+q1[5]); + xmx0 = x-dx0; + psi = den*xmx0+aug; + return psi; +S70: +/* +--------------------------------------------------------------------- + IF X .GE. XMAX1, PSI = LN(X) +--------------------------------------------------------------------- +*/ + if(x >= xmax1) goto S90; +/* +--------------------------------------------------------------------- + 3.0 .LT. X .LT. XMAX1 +--------------------------------------------------------------------- +*/ + w = 1.0e0/(x*x); + den = w; + upper = p2[0]*w; + for(i=1; i<=3; i++) { + den = (den+q2[i-1])*w; + upper = (upper+p2[i+1-1])*w; + } + aug = upper/(den+q2[3])-0.5e0/x+aug; +S90: + psi = aug+log(x); + return psi; +S100: +/* +--------------------------------------------------------------------- + ERROR RETURN +--------------------------------------------------------------------- +*/ + psi = 0.0e0; + return psi; +} /* END */ + +/***=====================================================================***/ +static double rcomp(double *a,double *x) +/* + ------------------- + EVALUATION OF EXP(-X)*X**A/GAMMA(A) + ------------------- + RT2PIN = 1/SQRT(2*PI) + ------------------- +*/ +{ +static double rt2pin = .398942280401433e0; +static double rcomp,t,t1,u; +/* + .. + .. Executable Statements .. +*/ + rcomp = 0.0e0; + if(*a >= 20.0e0) goto S20; + t = *a*log(*x)-*x; + if(*a >= 1.0e0) goto S10; + rcomp = *a*exp(t)*(1.0e0+gam1(a)); + return rcomp; +S10: + rcomp = exp(t)/Xgamm(a); + return rcomp; +S20: + u = *x/ *a; + if(u == 0.0e0) return rcomp; + t = pow(1.0e0/ *a,2.0); + t1 = (((0.75e0*t-1.0e0)*t+3.5e0)*t-105.0e0)/(*a*1260.0e0); + t1 -= (*a*rlog(&u)); + rcomp = rt2pin*sqrt(*a)*exp(t1); + return rcomp; +} /* END */ + +/***=====================================================================***/ +static double rexp(double *x) +/* +----------------------------------------------------------------------- + EVALUATION OF THE FUNCTION EXP(X) - 1 +----------------------------------------------------------------------- +*/ +{ +static double p1 = .914041914819518e-09; +static double p2 = .238082361044469e-01; +static double q1 = -.499999999085958e+00; +static double q2 = .107141568980644e+00; +static double q3 = -.119041179760821e-01; +static double q4 = .595130811860248e-03; +static double rexp,w; +/* + .. + .. Executable Statements .. +*/ + if(fabs(*x) > 0.15e0) goto S10; + rexp = *x*(((p2**x+p1)**x+1.0e0)/((((q4**x+q3)**x+q2)**x+q1)**x+1.0e0)); + return rexp; +S10: + w = exp(*x); + if(*x > 0.0e0) goto S20; + rexp = w-0.5e0-0.5e0; + return rexp; +S20: + rexp = w*(0.5e0+(0.5e0-1.0e0/w)); + return rexp; +} /* END */ + +/***=====================================================================***/ +static double rlog(double *x) +/* + ------------------- + COMPUTATION OF X - 1 - LN(X) + ------------------- +*/ +{ +static double a = .566749439387324e-01; +static double b = .456512608815524e-01; +static double p0 = .333333333333333e+00; +static double p1 = -.224696413112536e+00; +static double p2 = .620886815375787e-02; +static double q1 = -.127408923933623e+01; +static double q2 = .354508718369557e+00; +static double rlog,r,t,u,w,w1; +/* + .. + .. Executable Statements .. +*/ + if(*x < 0.61e0 || *x > 1.57e0) goto S40; + if(*x < 0.82e0) goto S10; + if(*x > 1.18e0) goto S20; +/* + ARGUMENT REDUCTION +*/ + u = *x-0.5e0-0.5e0; + w1 = 0.0e0; + goto S30; +S10: + u = *x-0.7e0; + u /= 0.7e0; + w1 = a-u*0.3e0; + goto S30; +S20: + u = 0.75e0**x-1.e0; + w1 = b+u/3.0e0; +S30: +/* + SERIES EXPANSION +*/ + r = u/(u+2.0e0); + t = r*r; + w = ((p2*t+p1)*t+p0)/((q2*t+q1)*t+1.0e0); + rlog = 2.0e0*t*(1.0e0/(1.0e0-r)-r*w)+w1; + return rlog; +S40: + r = *x-0.5e0-0.5e0; + rlog = r-log(*x); + return rlog; +} /* END */ + +/***=====================================================================***/ +static double rlog1(double *x) +/* +----------------------------------------------------------------------- + EVALUATION OF THE FUNCTION X - LN(1 + X) +----------------------------------------------------------------------- +*/ +{ +static double a = .566749439387324e-01; +static double b = .456512608815524e-01; +static double p0 = .333333333333333e+00; +static double p1 = -.224696413112536e+00; +static double p2 = .620886815375787e-02; +static double q1 = -.127408923933623e+01; +static double q2 = .354508718369557e+00; +static double rlog1,h,r,t,w,w1; +/* + .. + .. Executable Statements .. +*/ + if(*x < -0.39e0 || *x > 0.57e0) goto S40; + if(*x < -0.18e0) goto S10; + if(*x > 0.18e0) goto S20; +/* + ARGUMENT REDUCTION +*/ + h = *x; + w1 = 0.0e0; + goto S30; +S10: + h = *x+0.3e0; + h /= 0.7e0; + w1 = a-h*0.3e0; + goto S30; +S20: + h = 0.75e0**x-0.25e0; + w1 = b+h/3.0e0; +S30: +/* + SERIES EXPANSION +*/ + r = h/(h+2.0e0); + t = r*r; + w = ((p2*t+p1)*t+p0)/((q2*t+q1)*t+1.0e0); + rlog1 = 2.0e0*t*(1.0e0/(1.0e0-r)-r*w)+w1; + return rlog1; +S40: + w = *x+0.5e0+0.5e0; + rlog1 = *x-log(w); + return rlog1; +} /* END */ + +/***=====================================================================***/ +static double spmpar(int *i) +/* +----------------------------------------------------------------------- + + SPMPAR PROVIDES THE SINGLE PRECISION MACHINE CONSTANTS FOR + THE COMPUTER BEING USED. IT IS ASSUMED THAT THE ARGUMENT + I IS AN INTEGER HAVING ONE OF THE VALUES 1, 2, OR 3. IF THE + SINGLE PRECISION ARITHMETIC BEING USED HAS M BASE B DIGITS AND + ITS SMALLEST AND LARGEST EXPONENTS ARE EMIN AND EMAX, THEN + + SPMPAR(1) = B**(1 - M), THE MACHINE PRECISION, + + SPMPAR(2) = B**(EMIN - 1), THE SMALLEST MAGNITUDE, + + SPMPAR(3) = B**EMAX*(1 - B**(-M)), THE LARGEST MAGNITUDE. + +----------------------------------------------------------------------- + WRITTEN BY + ALFRED H. MORRIS, JR. + NAVAL SURFACE WARFARE CENTER + DAHLGREN VIRGINIA +----------------------------------------------------------------------- +----------------------------------------------------------------------- + MODIFIED BY BARRY W. BROWN TO RETURN DOUBLE PRECISION MACHINE + CONSTANTS FOR THE COMPUTER BEING USED. THIS MODIFICATION WAS + MADE AS PART OF CONVERTING BRATIO TO DOUBLE PRECISION +----------------------------------------------------------------------- +*/ +{ +static int K1 = 4; +static int K2 = 8; +static int K3 = 9; +static int K4 = 10; +static double spmpar,b,binv,bm1,one,w,z; +static int emax,emin,ibeta,m; +/* + .. + .. Executable Statements .. +*/ + if(*i > 1) goto S10; + b = ipmpar(&K1); + m = ipmpar(&K2); + spmpar = pow(b,(double)(1-m)); + return spmpar; +S10: + if(*i > 2) goto S20; + b = ipmpar(&K1); + emin = ipmpar(&K3); + one = 1.0; + binv = one/b; + w = pow(b,(double)(emin+2)); + spmpar = w*binv*binv*binv; + return spmpar; +S20: + ibeta = ipmpar(&K1); + m = ipmpar(&K2); + emax = ipmpar(&K4); + b = ibeta; + bm1 = ibeta-1; + one = 1.0; + z = pow(b,(double)(m-1)); + w = ((z-one)*b+bm1)/(b*z); + z = pow(b,(double)(emax-2)); + spmpar = w*z*b*b; + return spmpar; +} /* END */ + +/***=====================================================================***/ +static double stvaln(double *p) +/* +********************************************************************** + + double stvaln(double *p) + STarting VALue for Neton-Raphon + calculation of Normal distribution Inverse + + + Function + + + Returns X such that CUMNOR(X) = P, i.e., the integral from - + infinity to X of (1/SQRT(2*PI)) EXP(-U*U/2) dU is P + + + Arguments + + + P --> The probability whose normal deviate is sought. + P is DOUBLE PRECISION + + + Method + + + The rational function on page 95 of Kennedy and Gentle, + Statistical Computing, Marcel Dekker, NY , 1980. + +********************************************************************** +*/ +{ +static double xden[5] = { + 0.993484626060e-1,0.588581570495e0,0.531103462366e0,0.103537752850e0, + 0.38560700634e-2 +}; +static double xnum[5] = { + -0.322232431088e0,-1.000000000000e0,-0.342242088547e0,-0.204231210245e-1, + -0.453642210148e-4 +}; +static int K1 = 5; +static double stvaln,sign,y,z; +/* + .. + .. Executable Statements .. +*/ + if(!(*p <= 0.5e0)) goto S10; + sign = -1.0e0; + z = *p; + goto S20; +S10: + sign = 1.0e0; + z = 1.0e0-*p; +S20: + y = sqrt(-(2.0e0*log(z))); + stvaln = y+devlpl(xnum,&K1,&y)/devlpl(xden,&K1,&y); + stvaln = sign*stvaln; + return stvaln; +} /* END */ + +/***=====================================================================***/ +static double fifdint(double a) +/************************************************************************ +FIFDINT: +Truncates a double precision number to an integer and returns the +value in a double. +************************************************************************/ +/* a - number to be truncated */ +{ + return (double) ((int) a); +} /* END */ + +/***=====================================================================***/ +static double fifdmax1(double a,double b) +/************************************************************************ +FIFDMAX1: +returns the maximum of two numbers a and b +************************************************************************/ +/* a - first number */ +/* b - second number */ +{ + if (a < b) return b; + else return a; +} /* END */ + +/***=====================================================================***/ +static double fifdmin1(double a,double b) +/************************************************************************ +FIFDMIN1: +returns the minimum of two numbers a and b +************************************************************************/ +/* a - first number */ +/* b - second number */ +{ + if (a < b) return a; + else return b; +} /* END */ + +/***=====================================================================***/ +static double fifdsign(double mag,double sign) +/************************************************************************ +FIFDSIGN: +transfers the sign of the variable "sign" to the variable "mag" +************************************************************************/ +/* mag - magnitude */ +/* sign - sign to be transfered */ +{ + if (mag < 0) mag = -mag; + if (sign < 0) mag = -mag; + return mag; + +} /* END */ + +/***=====================================================================***/ +static long fifidint(double a) +/************************************************************************ +FIFIDINT: +Truncates a double precision number to a long integer +************************************************************************/ +/* a - number to be truncated */ +{ + if (a < 1.0) return (long) 0; + else return (long) a; +} /* END */ + +/***=====================================================================***/ +static long fifmod(long a,long b) +/************************************************************************ +FIFMOD: +returns the modulo of a and b +************************************************************************/ +/* a - numerator */ +/* b - denominator */ +{ + return a % b; +} /* END */ + +/***=====================================================================***/ +static void ftnstop(char* msg) +/************************************************************************ +FTNSTOP: +Prints msg to standard error and then exits +************************************************************************/ +/* msg - error message */ +{ + if (msg != NULL) fprintf(stderr,"*** CDFLIB ERROR: %s\n",msg); + /** exit(1); **/ /** RWCox - DON'T EXIT */ +} /* END */ + +/***=====================================================================***/ +static int ipmpar(int *i) +/* +----------------------------------------------------------------------- + + IPMPAR PROVIDES THE INTEGER MACHINE CONSTANTS FOR THE COMPUTER + THAT IS USED. IT IS ASSUMED THAT THE ARGUMENT I IS AN INTEGER + HAVING ONE OF THE VALUES 1-10. IPMPAR(I) HAS THE VALUE ... + + INTEGERS. + + ASSUME INTEGERS ARE REPRESENTED IN THE N-DIGIT, BASE-A FORM + + SIGN ( X(N-1)*A**(N-1) + ... + X(1)*A + X(0) ) + + WHERE 0 .LE. X(I) .LT. A FOR I=0,...,N-1. + + IPMPAR(1) = A, THE BASE. + + IPMPAR(2) = N, THE NUMBER OF BASE-A DIGITS. + + IPMPAR(3) = A**N - 1, THE LARGEST MAGNITUDE. + + FLOATING-POINT NUMBERS. + + IT IS ASSUMED THAT THE SINGLE AND DOUBLE PRECISION FLOATING + POINT ARITHMETICS HAVE THE SAME BASE, SAY B, AND THAT THE + NONZERO NUMBERS ARE REPRESENTED IN THE FORM + + SIGN (B**E) * (X(1)/B + ... + X(M)/B**M) + + WHERE X(I) = 0,1,...,B-1 FOR I=1,...,M, + X(1) .GE. 1, AND EMIN .LE. E .LE. EMAX. + + IPMPAR(4) = B, THE BASE. + + SINGLE-PRECISION + + IPMPAR(5) = M, THE NUMBER OF BASE-B DIGITS. + + IPMPAR(6) = EMIN, THE SMALLEST EXPONENT E. + + IPMPAR(7) = EMAX, THE LARGEST EXPONENT E. + + DOUBLE-PRECISION + + IPMPAR(8) = M, THE NUMBER OF BASE-B DIGITS. + + IPMPAR(9) = EMIN, THE SMALLEST EXPONENT E. + + IPMPAR(10) = EMAX, THE LARGEST EXPONENT E. + +----------------------------------------------------------------------- + + TO DEFINE THIS FUNCTION FOR THE COMPUTER BEING USED REMOVE + THE COMMENT DELIMITORS FROM THE DEFINITIONS DIRECTLY BELOW THE NAME + OF THE MACHINE + +*** RWCox: at this time, the IEEE parameters are enabled. + +----------------------------------------------------------------------- + + IPMPAR IS AN ADAPTATION OF THE FUNCTION I1MACH, WRITTEN BY + P.A. FOX, A.D. HALL, AND N.L. SCHRYER (BELL LABORATORIES). + IPMPAR WAS FORMED BY A.H. MORRIS (NSWC). THE CONSTANTS ARE + FROM BELL LABORATORIES, NSWC, AND OTHER SOURCES. + +----------------------------------------------------------------------- + .. Scalar Arguments .. +*/ +{ +static int imach[11]; +static int outval ; +/* MACHINE CONSTANTS FOR AMDAHL MACHINES. */ +/* + imach[1] = 2; + imach[2] = 31; + imach[3] = 2147483647; + imach[4] = 16; + imach[5] = 6; + imach[6] = -64; + imach[7] = 63; + imach[8] = 14; + imach[9] = -64; + imach[10] = 63; +*/ +/* MACHINE CONSTANTS FOR THE AT&T 3B SERIES, AT&T + PC 7300, AND AT&T 6300. */ +/* + imach[1] = 2; + imach[2] = 31; + imach[3] = 2147483647; + imach[4] = 2; + imach[5] = 24; + imach[6] = -125; + imach[7] = 128; + imach[8] = 53; + imach[9] = -1021; + imach[10] = 1024; +*/ +/* MACHINE CONSTANTS FOR THE BURROUGHS 1700 SYSTEM. */ +/* + imach[1] = 2; + imach[2] = 33; + imach[3] = 8589934591; + imach[4] = 2; + imach[5] = 24; + imach[6] = -256; + imach[7] = 255; + imach[8] = 60; + imach[9] = -256; + imach[10] = 255; +*/ +/* MACHINE CONSTANTS FOR THE BURROUGHS 5700 SYSTEM. */ +/* + imach[1] = 2; + imach[2] = 39; + imach[3] = 549755813887; + imach[4] = 8; + imach[5] = 13; + imach[6] = -50; + imach[7] = 76; + imach[8] = 26; + imach[9] = -50; + imach[10] = 76; +*/ +/* MACHINE CONSTANTS FOR THE BURROUGHS 6700/7700 SYSTEMS. */ +/* + imach[1] = 2; + imach[2] = 39; + imach[3] = 549755813887; + imach[4] = 8; + imach[5] = 13; + imach[6] = -50; + imach[7] = 76; + imach[8] = 26; + imach[9] = -32754; + imach[10] = 32780; +*/ +/* MACHINE CONSTANTS FOR THE CDC 6000/7000 SERIES + 60 BIT ARITHMETIC, AND THE CDC CYBER 995 64 BIT + ARITHMETIC (NOS OPERATING SYSTEM). */ +/* + imach[1] = 2; + imach[2] = 48; + imach[3] = 281474976710655; + imach[4] = 2; + imach[5] = 48; + imach[6] = -974; + imach[7] = 1070; + imach[8] = 95; + imach[9] = -926; + imach[10] = 1070; +*/ +/* MACHINE CONSTANTS FOR THE CDC CYBER 995 64 BIT + ARITHMETIC (NOS/VE OPERATING SYSTEM). */ +/* + imach[1] = 2; + imach[2] = 63; + imach[3] = 9223372036854775807; + imach[4] = 2; + imach[5] = 48; + imach[6] = -4096; + imach[7] = 4095; + imach[8] = 96; + imach[9] = -4096; + imach[10] = 4095; +*/ +/* MACHINE CONSTANTS FOR THE CRAY 1, XMP, 2, AND 3. */ +/* + imach[1] = 2; + imach[2] = 63; + imach[3] = 9223372036854775807; + imach[4] = 2; + imach[5] = 47; + imach[6] = -8189; + imach[7] = 8190; + imach[8] = 94; + imach[9] = -8099; + imach[10] = 8190; +*/ +/* MACHINE CONSTANTS FOR THE DATA GENERAL ECLIPSE S/200. */ +/* + imach[1] = 2; + imach[2] = 15; + imach[3] = 32767; + imach[4] = 16; + imach[5] = 6; + imach[6] = -64; + imach[7] = 63; + imach[8] = 14; + imach[9] = -64; + imach[10] = 63; +*/ +/* MACHINE CONSTANTS FOR THE HARRIS 220. */ +/* + imach[1] = 2; + imach[2] = 23; + imach[3] = 8388607; + imach[4] = 2; + imach[5] = 23; + imach[6] = -127; + imach[7] = 127; + imach[8] = 38; + imach[9] = -127; + imach[10] = 127; +*/ +/* MACHINE CONSTANTS FOR THE HONEYWELL 600/6000 + AND DPS 8/70 SERIES. */ +/* + imach[1] = 2; + imach[2] = 35; + imach[3] = 34359738367; + imach[4] = 2; + imach[5] = 27; + imach[6] = -127; + imach[7] = 127; + imach[8] = 63; + imach[9] = -127; + imach[10] = 127; +*/ +/* MACHINE CONSTANTS FOR THE HP 2100 + 3 WORD DOUBLE PRECISION OPTION WITH FTN4 */ +/* + imach[1] = 2; + imach[2] = 15; + imach[3] = 32767; + imach[4] = 2; + imach[5] = 23; + imach[6] = -128; + imach[7] = 127; + imach[8] = 39; + imach[9] = -128; + imach[10] = 127; +*/ +/* MACHINE CONSTANTS FOR THE HP 2100 + 4 WORD DOUBLE PRECISION OPTION WITH FTN4 */ +/* + imach[1] = 2; + imach[2] = 15; + imach[3] = 32767; + imach[4] = 2; + imach[5] = 23; + imach[6] = -128; + imach[7] = 127; + imach[8] = 55; + imach[9] = -128; + imach[10] = 127; +*/ +/* MACHINE CONSTANTS FOR THE HP 9000. */ +/* + imach[1] = 2; + imach[2] = 31; + imach[3] = 2147483647; + imach[4] = 2; + imach[5] = 24; + imach[6] = -126; + imach[7] = 128; + imach[8] = 53; + imach[9] = -1021; + imach[10] = 1024; +*/ +/* MACHINE CONSTANTS FOR THE IBM 360/370 SERIES, + THE ICL 2900, THE ITEL AS/6, THE XEROX SIGMA + 5/7/9 AND THE SEL SYSTEMS 85/86. */ +/* + imach[1] = 2; + imach[2] = 31; + imach[3] = 2147483647; + imach[4] = 16; + imach[5] = 6; + imach[6] = -64; + imach[7] = 63; + imach[8] = 14; + imach[9] = -64; + imach[10] = 63; +*/ +/* MACHINE CONSTANTS FOR THE IBM PC. */ +/* + imach[1] = 2; + imach[2] = 31; + imach[3] = 2147483647; + imach[4] = 2; + imach[5] = 24; + imach[6] = -125; + imach[7] = 128; + imach[8] = 53; + imach[9] = -1021; + imach[10] = 1024; +*/ +/* MACHINE CONSTANTS FOR THE MACINTOSH II - ABSOFT + MACFORTRAN II. */ +/* + imach[1] = 2; + imach[2] = 31; + imach[3] = 2147483647; + imach[4] = 2; + imach[5] = 24; + imach[6] = -125; + imach[7] = 128; + imach[8] = 53; + imach[9] = -1021; + imach[10] = 1024; +*/ +/* MACHINE CONSTANTS FOR THE MICROVAX - VMS FORTRAN. */ +/* + imach[1] = 2; + imach[2] = 31; + imach[3] = 2147483647; + imach[4] = 2; + imach[5] = 24; + imach[6] = -127; + imach[7] = 127; + imach[8] = 56; + imach[9] = -127; + imach[10] = 127; +*/ +/* MACHINE CONSTANTS FOR THE PDP-10 (KA PROCESSOR). */ +/* + imach[1] = 2; + imach[2] = 35; + imach[3] = 34359738367; + imach[4] = 2; + imach[5] = 27; + imach[6] = -128; + imach[7] = 127; + imach[8] = 54; + imach[9] = -101; + imach[10] = 127; +*/ +/* MACHINE CONSTANTS FOR THE PDP-10 (KI PROCESSOR). */ +/* + imach[1] = 2; + imach[2] = 35; + imach[3] = 34359738367; + imach[4] = 2; + imach[5] = 27; + imach[6] = -128; + imach[7] = 127; + imach[8] = 62; + imach[9] = -128; + imach[10] = 127; +*/ +/* MACHINE CONSTANTS FOR THE PDP-11 FORTRAN SUPPORTING + 32-BIT INTEGER ARITHMETIC. */ +/* + imach[1] = 2; + imach[2] = 31; + imach[3] = 2147483647; + imach[4] = 2; + imach[5] = 24; + imach[6] = -127; + imach[7] = 127; + imach[8] = 56; + imach[9] = -127; + imach[10] = 127; +*/ +/* MACHINE CONSTANTS FOR THE SEQUENT BALANCE 8000. */ +/* + imach[1] = 2; + imach[2] = 31; + imach[3] = 2147483647; + imach[4] = 2; + imach[5] = 24; + imach[6] = -125; + imach[7] = 128; + imach[8] = 53; + imach[9] = -1021; + imach[10] = 1024; +*/ +/* MACHINE CONSTANTS FOR THE SILICON GRAPHICS IRIS-4D + SERIES (MIPS R3000 PROCESSOR). */ +/* + imach[1] = 2; + imach[2] = 31; + imach[3] = 2147483647; + imach[4] = 2; + imach[5] = 24; + imach[6] = -125; + imach[7] = 128; + imach[8] = 53; + imach[9] = -1021; + imach[10] = 1024; +*/ +/* MACHINE CONSTANTS FOR IEEE ARITHMETIC MACHINES, SUCH AS THE AT&T + 3B SERIES, MOTOROLA 68000 BASED MACHINES (E.G. SUN 3 AND AT&T + PC 7300), AND 8087 BASED MICROS (E.G. IBM PC AND AT&T 6300). */ + + imach[1] = 2; + imach[2] = 31; + imach[3] = 2147483647; + imach[4] = 2; + imach[5] = 24; + imach[6] = -125; + imach[7] = 128; + imach[8] = 53; + imach[9] = -1021; + imach[10] = 1024; + +/* MACHINE CONSTANTS FOR THE UNIVAC 1100 SERIES. */ +/* + imach[1] = 2; + imach[2] = 35; + imach[3] = 34359738367; + imach[4] = 2; + imach[5] = 27; + imach[6] = -128; + imach[7] = 127; + imach[8] = 60; + imach[9] = -1024; + imach[10] = 1023; +*/ +/* MACHINE CONSTANTS FOR THE VAX 11/780. */ +/* + imach[1] = 2; + imach[2] = 31; + imach[3] = 2147483647; + imach[4] = 2; + imach[5] = 24; + imach[6] = -127; + imach[7] = 127; + imach[8] = 56; + imach[9] = -127; + imach[10] = 127; +*/ + outval = imach[*i]; + return outval ; +} + +/*************************************************************************/ +/*************************************************************************/ +/************************ End of cdflib inclusion ************************/ +/*************************************************************************/ +/*************************************************************************/ + +/*-----------------------------------------------------------------------*/ +typedef struct { double p,q ; } pqpair ; /* for returning p=cdf q=1-cdf */ +/*-----------------------------------------------------------------------*/ +#undef BIGG +#define BIGG 9.99e+37 /* a really big number (duh) */ +/*-----------------------------------------------------------------------*/ + +/*************************************************************************/ +/******** Internal functions for various statistical computations ********/ +/*************************************************************************/ + +/*--------------------------------------------------------------- + F statistic +-----------------------------------------------------------------*/ + +static double fstat_pq2s( pqpair pq , double dofnum , double dofden ) +{ + int which , status ; + double p , q , f , dfn , dfd , bound ; + + which = 2 ; + p = pq.p ; if( p <= 0.0 ) return 0.0 ; + q = pq.q ; if( q <= 0.0 ) return BIGG ; + f = 0.0 ; + dfn = dofnum ; + dfd = dofden ; + + cdff( &which , &p , &q , &f , &dfn , &dfd , &status , &bound ) ; + return f ; +} + +/*------------------------------*/ + +static pqpair fstat_s2pq( double ff , double dofnum , double dofden ) +{ + int which , status ; + double p , q , f , dfn , dfd , bound ; + pqpair pq={0.0,1.0} ; + + which = 1 ; + p = 0.0 ; + q = 1.0 ; + f = ff ; if( f <= 0.0 ) return pq; + dfn = dofnum ; if( dfn <= 0.0 ) return pq ; + dfd = dofden ; if( dfd <= 0.0 ) return pq ; + + cdff( &which , &p , &q , &f , &dfn , &dfd , &status , &bound ) ; + pq.p = p ; pq.q = q ; return pq ; +} + +/*--------------------------------------------------------------- + noncentral F statistic +-----------------------------------------------------------------*/ + +static double fnonc_pq2s( pqpair pq , double dofnum , double dofden , double nonc ) +{ + int which , status ; + double p , q , f , dfn , dfd , bound , pnonc ; + + which = 2 ; + p = pq.p ; if( p <= 0.0 ) return 0.0 ; + q = pq.q ; if( q <= 0.0 ) return BIGG ; + f = 0.0 ; + dfn = dofnum ; + dfd = dofden ; + pnonc = nonc ; + + cdffnc( &which , &p , &q , &f , &dfn , &dfd , &pnonc , &status , &bound ) ; + return f ; +} + +/*------------------------------*/ + +static pqpair fnonc_s2pq( double ff , double dofnum , double dofden , double nonc ) +{ + int which , status ; + double p , q , f , dfn , dfd , bound , pnonc ; + pqpair pq={0.0,1.0} ; + + which = 1 ; + p = 0.0 ; + q = 1.0 ; + f = ff ; if( f <= 0.0 ) return pq ; + dfn = dofnum ; if( dfn <= 0.0 ) return pq ; + dfd = dofden ; if( dfd <= 0.0 ) return pq ; + pnonc = nonc ; if( pnonc < 0.0 ) return pq ; + + cdffnc( &which , &p , &q , &f , &dfn , &dfd , &pnonc , &status , &bound ) ; + pq.p = p ; pq.q = q ; return pq ; +} + +/*--------------------------------------------------------------- + Standard Normal distribution +-----------------------------------------------------------------*/ + +static pqpair normal_s2pq( double zz ) +{ + double p , q , x=zz ; + pqpair pq ; + + cumnor( &x, &p, &q ) ; + pq.p = p ; pq.q = q ; return pq ; +} + +/*------------------------------*/ + +static double normal_pq2s( pqpair pq ) +{ + double p=pq.p , q=pq.q ; + + if( p <= 0.0 ) return -BIGG ; + if( q <= 0.0 ) return BIGG ; + return dinvnr( &p,&q ) ; +} + +/*---------------------------------------------------------------- + Chi-square +------------------------------------------------------------------*/ + +static pqpair chisq_s2pq( double xx , double dof ) +{ + int which , status ; + double p,q,x,df,bound ; + pqpair pq={0.0,1.0} ; + + which = 1 ; + p = 0.0 ; + q = 1.0 ; + x = xx ; if( x <= 0.0 ) return pq ; + df = dof ; if( dof <= 0.0 ) return pq ; + + cdfchi( &which , &p , &q , &x , &df , &status , &bound ) ; + pq.p = p ; pq.q = q ; return pq ; +} + +/*------------------------------*/ + +static double chisq_pq2s( pqpair pq , double dof ) +{ + int which , status ; + double p,q,x,df,bound ; + + which = 2 ; + p = pq.p ; if( p <= 0.0 ) return 0.0 ; + q = pq.q ; if( q <= 0.0 ) return BIGG ; + x = 0.0 ; + df = dof ; + + cdfchi( &which , &p , &q , &x , &df , &status , &bound ) ; + return x ; +} + +/*---------------------------------------------------------------- + noncentral Chi-square +------------------------------------------------------------------*/ + +static pqpair chsqnonc_s2pq( double xx , double dof , double nonc ) +{ + int which , status ; + double p,q,x,df,bound , pnonc ; + pqpair pq={0.0,1.0} ; + + which = 1 ; + p = 0.0 ; + q = 1.0 ; + x = xx ; if( x <= 0.0 ) return pq ; + df = dof ; if( df <= 0.0 ) return pq ; + pnonc = nonc ; if( pnonc < 0.0 ) return pq ; + + cdfchn( &which , &p , &q , &x , &df , &pnonc , &status , &bound ) ; + pq.p = p ; pq.q = q ; return pq ; +} + +/*------------------------------*/ + +static double chsqnonc_pq2s( pqpair pq , double dof , double nonc ) +{ + int which , status ; + double p,q,x,df,bound , pnonc ; + + which = 2 ; + p = pq.p ; if( p <= 0.0 ) return 0.0 ; + q = pq.q ; if( q <= 0.0 ) return BIGG ; + x = 0.0 ; + df = dof ; + pnonc = nonc ; + + cdfchn( &which , &p , &q , &x , &df , &pnonc , &status , &bound ) ; + return x ; +} + +/*---------------------------------------------------------------- + Beta distribution +------------------------------------------------------------------*/ + +static pqpair beta_s2pq( double xx , double aa , double bb ) +{ + int which , status ; + double p,q,x,y,a,b,bound ; + pqpair pq={0.0,1.0} ; + + which = 1 ; + p = 0.0 ; + q = 1.0 ; + x = xx ; if( x <= 0.0 ) return pq ; + y = 1.0 - xx ; if( y <= 0.0 ){ pq.p=1.0; pq.q=0.0; return pq; } + a = aa ; if( a < 0.0 ) return pq ; + b = bb ; if( b < 0.0 ) return pq ; + + cdfbet( &which , &p , &q , &x , &y , &a , &b , &status , &bound ) ; + pq.p = p ; pq.q = q ; return pq ; +} + +/*------------------------------*/ + +static double beta_pq2s( pqpair pq , double aa , double bb ) +{ + int which , status ; + double p,q,x,y,a,b,bound ; + + which = 2 ; + p = pq.p ; if( p <= 0.0 ) return 0.0 ; + q = pq.q ; if( q <= 0.0 ) return 1.0 ; + x = 0.0 ; + y = 1.0 ; + a = aa ; + b = bb ; + + cdfbet( &which , &p , &q , &x , &y , &a , &b , &status , &bound ) ; + return x ; +} + +/*---------------------------------------------------------------- + Binomial distribution + (that is, the probability that more than ss out of ntrial + trials were successful). +------------------------------------------------------------------*/ + +static pqpair binomial_s2pq( double ss , double ntrial , double ptrial ) +{ + int which , status ; + double p,q, s,xn,pr,ompr,bound ; + pqpair pq={0.0,1.0} ; + + which = 1 ; + p = 0.0 ; + q = 1.0 ; + s = ss ; if( s < 0.0 ) return pq ; + xn = ntrial ; if( xn <= 0.0 ) return pq ; + pr = ptrial ; if( pr < 0.0 ) return pq ; + ompr = 1.0 - ptrial ; + + cdfbin( &which , &p , &q , &s , &xn , &pr , &ompr , &status , &bound ) ; + pq.p = p ; pq.q = q ; return pq ; +} + +/*------------------------------*/ + +static double binomial_pq2s( pqpair pq , double ntrial , double ptrial ) +{ + int which , status ; + double p,q, s,xn,pr,ompr,bound ; + + which = 2 ; + p = pq.p ; + q = pq.q ; + s = 0.0 ; + xn = ntrial ; + pr = ptrial ; + ompr = 1.0 - ptrial ; + + cdfbin( &which , &p , &q , &s , &xn , &pr , &ompr , &status , &bound ) ; + return s ; +} + +/*---------------------------------------------------------------- + Gamma distribution. +------------------------------------------------------------------*/ + +static pqpair gamma_s2pq( double xx , double sh , double sc ) +{ + int which , status ; + double p,q, x,shape,scale,bound ; + pqpair pq={0.0,1.0} ; + + which = 1 ; + p = 0.0 ; + q = 1.0 ; + x = xx ; if( x <= 0.0 ) return pq ; + shape = sh ; if( shape <= 0.0 ) return pq ; + scale = sc ; if( scale <= 0.0 ) return pq ; + + cdfgam( &which , &p , &q , &x , &shape , &scale , &status , &bound ) ; + pq.p = p ; pq.q = q ; return pq ; +} + +/*------------------------------*/ + +static double gamma_pq2s( pqpair pq , double sh , double sc ) +{ + int which , status ; + double p,q, x,shape,scale,bound ; + + which = 2 ; + p = pq.p ; if( p <= 0.0 ) return 0.0 ; + q = pq.q ; if( q <= 0.0 ) return BIGG ; + x = 0.0 ; + shape = sh ; + scale = sc ; + + cdfgam( &which , &p , &q , &x , &shape , &scale , &status , &bound ) ; + return x ; +} + +/*---------------------------------------------------------------- + Poisson distribution +------------------------------------------------------------------*/ + +static pqpair poisson_s2pq( double xx , double lambda ) +{ + int which , status ; + double p,q, s,xlam,bound ; + pqpair pq={0.0,1.0} ; + + which = 1 ; + p = 0.0 ; + q = 1.0 ; + s = xx ; if( s < 0.0 ) return pq ; + xlam = lambda ; if( xlam < 0.0 ) return pq ; + + cdfpoi( &which , &p , &q , &s , &xlam , &status , &bound ) ; + pq.p = p ; pq.q = q ; return pq ; +} + +/*------------------------------*/ + +static double poisson_pq2s( pqpair pq , double lambda ) +{ + int which , status ; + double p,q, s,xlam,bound ; + + which = 2 ; + p = pq.p ; + q = pq.q ; + s = 0.0 ; + xlam = lambda ; + + cdfpoi( &which , &p , &q , &s , &xlam , &status , &bound ) ; + return s ; +} + +/*---------------------------------------------------------------- + T distribution. +------------------------------------------------------------------*/ + +static pqpair student_s2pq( double xx , double dof ) +{ + int which , status ; + double p,q, s,xlam,bound ; + pqpair pq={0.0,1.0} ; + + which = 1 ; + p = 0.0 ; + q = 1.0 ; + s = xx ; + xlam = dof ; if( xlam <= 0.0 ) return pq ; + + cdft( &which , &p , &q , &s , &xlam , &status , &bound ) ; + pq.p = p ; pq.q = q ; return pq ; +} + +/*------------------------------*/ + +double student_pq2s( pqpair pq , double dof ) +{ + int which , status ; + double p,q, s,xlam,bound ; + + which = 2 ; + p = pq.p ; + q = pq.q ; + s = 0.0 ; + xlam = dof ; + + cdft( &which , &p , &q , &s , &xlam , &status , &bound ) ; + return s ; +} + +/****************************************************************************/ +/* For the distributions below here, cdflib can't do what we want directly. */ +/****************************************************************************/ + +/*---------------------------------------------------------------- + Null correlation distribution. + Let x = (rr+1)/2; then x is Beta(dof/2,dof/2). +------------------------------------------------------------------*/ + +static pqpair correl_s2pq( double rr , double dof ) /* fake it with cdflib */ +{ + return beta_s2pq( 0.5*(rr+1.0) , 0.5*dof , 0.5*dof ) ; +} + +/*------------------------------*/ + +static double correl_pq2s( pqpair pq , double dof ) +{ + double xx = beta_pq2s( pq , 0.5*dof , 0.5*dof ) ; + return (2.0*xx-1.0) ; +} + +/*---------------------------------------------------------------- + Uniform U(0,1) distribution. +------------------------------------------------------------------*/ + +static pqpair uniform_s2pq( double xx ) /* this isn't too hard */ +{ + pqpair pq ; + if( xx <= 0.0 ) pq.p = 0.0 ; + else if( xx >= 1.0 ) pq.p = 1.0 ; + else pq.p = xx ; + pq.q = 1.0-xx ; return pq ; +} + +/*------------------------------*/ + +static double uniform_pq2s( pqpair pq ) +{ + return pq.p ; /* that was easy */ +} + +/*---------------------------------------------------------------- + standard Logistic distribution. +------------------------------------------------------------------*/ + +static pqpair logistic_s2pq( double xx ) /* this isn't hard, either */ +{ + pqpair pq ; + if( xx >= 0.0 ){ pq.q = 1.0/(1.0+exp( xx)); pq.p = 1.0-pq.q; } + else { pq.p = 1.0/(1.0+exp(-xx)); pq.q = 1.0-pq.p; } + return pq ; +} + +/*------------------------------*/ + +static double logistic_pq2s( pqpair pq ) +{ + if( pq.p <= 0.0 ) return -BIGG ; + else if( pq.q <= 0.0 ) return BIGG ; + + if( pq.p < pq.q ) return -log(1.0/pq.p-1.0) ; + else return log(1.0/pq.q-1.0) ; +} + +/*---------------------------------------------------------------- + standard Laplace distribution. +------------------------------------------------------------------*/ + +static pqpair laplace_s2pq( double xx ) /* easy */ +{ + pqpair pq ; + + if( xx >= 0.0 ){ pq.q = 0.5*exp(-xx) ; pq.p = 1.0-pq.q ; } + else { pq.p = 0.5*exp( xx) ; pq.q = 1.0-pq.p ; } + return pq ; +} + +/*------------------------------*/ + +static double laplace_pq2s( pqpair pq ) +{ + if( pq.p <= 0.0 ) return -BIGG ; + else if( pq.q <= 0.0 ) return BIGG ; + + if( pq.p < pq.q ) return log(2.0*pq.p) ; + else return -log(2.0*pq.q) ; +} + +/*---------------------------------------------------------------- + noncentral T distribution = hard calculation +------------------------------------------------------------------*/ + +/**************************************************************************** + Noncentral t distribution function by + Professor K. Krishnamoorthy + Department of Mathematics + University of Louisiana at Lafayette + Manually translated from Fortran by RWC. +*****************************************************************************/ + +#if 0 +static double alng( double x ) /* log(Gamma(x)) from K */ +{ + int indx ; + double xx,fterm,sum,valg ; + double b[9] = { 0.0 , + 8.33333333333333e-2, 3.33333333333333e-2, + 2.52380952380952e-1, 5.25606469002695e-1, + 1.01152306812684e0, 1.51747364915329e0, + 2.26948897420496e0, 3.00991738325940e0 } ; + + if( x < 8.0 ){ xx = x + 8.0 ; indx = 1 ; } + else { xx = x ; indx = 0 ; } + + fterm = (xx-0.5)*log(xx) - xx + 9.1893853320467e-1 ; + sum = b[1]/(xx+b[2]/(xx+b[3]/(xx+b[4]/(xx+b[5]/(xx+b[6]/ + (xx+b[7]/(xx+b[8]))))))) ; + valg = sum + fterm ; + if(indx) + valg = valg-log(x+7.0)-log(x+6.0)-log(x+5.0) + -log(x+4.0)-log(x+3.0)-log(x+2.0)-log(x+1.0)-log(x) ; + return valg ; +} +#else +static double alng( double x ) /*-- replace with cdflib function --*/ +{ + double xx=x ; return alngam( &xx ) ; +} +#endif + +/*---------------------------------------------------------------------------*/ + +#if 0 +static double gaudf( double x ) /* N(0,1) cdf from K */ +{ + static double p0=913.16744211475570 , p1=1024.60809538333800, + p2=580.109897562908800, p3=202.102090717023000, + p4=46.0649519338751400, p5=6.81311678753268400, + p6=6.047379926867041e-1,p7=2.493381293151434e-2 ; + static double q0=1826.33488422951125, q1=3506.420597749092, + q2=3044.77121163622200, q3=1566.104625828454, + q4=523.596091947383490, q5=116.9795245776655, + q6=17.1406995062577800, q7=1.515843318555982, + q8=6.25e-2 ; + static double sqr2pi=2.506628274631001 ; + int check ; + double reslt,z , first,phi ; + + if(x > 0.0){ z = x ; check = 1 ; } + else { z =-x ; check = 0 ; } + + if( z > 32.0 ) return (x > 0.0) ? 1.0 : 0.0 ; + + first = exp(-0.5*z*z) ; + phi = first/sqr2pi ; + + if (z < 7.0) + reslt = first* (((((((p7*z+p6)*z+p5)*z+p4)*z+p3)*z+p2)*z+p1)*z+p0) + /((((((((q8*z+q7)*z+q6)*z+q5)*z+q4)*z+q3)*z+q2)*z+q1)*z+q0); + else + reslt = phi/(z+1.0/(z+2.0/(z+3.0/(z+4.0/(z+6.0/(z+7.0)))))) ; + + if(check) reslt = 1.0 - reslt ; + return reslt ; +} +#else +static double gaudf( double x ) /*-- replace with cdflib func --*/ +{ + double xx=x , p,q ; + cumnor( &xx, &p, &q ); return p; +} +#endif + +/*---------------------------------------------------------------------------*/ + +#if 0 +static double betadf( double x , double p , double q ) /* Beta cdf from K */ +{ + int check , ns ; + double result,betf,psq,xx,cx,pp,qq ; + double term,ai,rx,temp ; + + if( x >= 1.0 ) return 1.0 ; + if( x <= 0.0 ) return 0.0 ; + + betf = alng(p)+alng(q)-alng(p+q) ; + result=x ; + psq=p+q ; + cx=1.0-x ; + if(p < psq*x){ xx=cx ; cx=x ; pp=q ; qq=p ; check=1 ; } + else { xx=x ; pp=p ; qq=q ; check=0 ; } + + term=1.0 ; + ai=1.0 ; + result=1.0 ; + ns=(int)(qq+cx*psq) ; + rx=xx/cx ; +L3: + temp=qq-ai ; + if(ns == 0) rx=xx ; +L4: + term=term*temp*rx/(pp+ai) ; + result=result+term ; + temp=fabs(term) ; + if(temp <= 1.e-14 && temp <= 1.e-14*result) goto L5 ; + ai=ai+1.0 ; + ns=ns-1 ; + if(ns >= 0) goto L3 ; + temp=psq ; + psq=psq+1.0 ; + goto L4 ; + +L5: + result=result*exp(pp*log(xx)+(qq-1.0)*log(cx)-betf)/pp ; + if(check) result=1.0-result ; + return result ; +} +#else +static double betadf( double x , double p , double q ) /*-- cdflib func --*/ +{ + double xx=x,yy=1.0-x , aa=p,bb=q , pp,qq ; + cumbet( &xx,&yy , &aa,&bb , &pp,&qq ) ; return pp ; +} +#endif + +/*---------------------------------------------------------------------------*/ +/* Krishnamoorthy's function for cdf of noncentral t, for df > 0, + translated into C by RW Cox [Mar 2004]. + Note the original fails for delta=0, so we call the cdflib func for this. + A couple of other minor fixes are also included. +-----------------------------------------------------------------------------*/ + +static pqpair tnonc_s2pq( double t , double df , double delta ) +{ + int indx , k , i ; + double x,del,tnd,ans,y,dels,a,b,c ; + double pkf,pkb,qkf,qkb , pgamf,pgamb,qgamf,qgamb ; + double pbetaf,pbetab,qbetaf,qbetab ; + double ptermf,qtermf,ptermb,qtermb,term ; + double rempois,delosq2,sum,cons,error ; + + pqpair pq={0.0,1.0} ; /* will be return value */ + double ab1 ; + + /*-- stupid user? --*/ + + if( df <= 0.0 ) return pq ; + + /*-- non-centrality = 0? --*/ + + if( fabs(delta) < 1.e-8 ) return student_s2pq(t,df) ; + + /*-- start K's code here --*/ + + if( t < 0.0 ){ x = -t ; del = -delta ; indx = 1 ; } /* x will be */ + else { x = t ; del = delta ; indx = 0 ; } /* positive */ + + ans = gaudf(-del) ; /* prob that x <= 0 = Normal cdf */ + + /*-- the nearly trivial case of x=0 --*/ + + if( x == 0.0 ){ pq.p = ans; pq.q = 1.0-ans; return pq; } + + if( df == 1.0 ) df = 1.0000001 ; /** df=1 is BAD **/ + + y = x*x/(df+x*x) ; /* between 0 and 1 */ + dels = 0.5*del*del ; /* will be positive */ + k = (int)dels ; /* 0, 1, 2, ... */ + a = k+0.5 ; /* might be as small as 0.5 */ + c = k+1.0 ; + b = 0.5*df ; /* might be as small as 0.0 */ + + pkf = exp(-dels+k*log(dels)-alng(k+1.0)) ; + pkb = pkf ; + qkf = exp(-dels+k*log(dels)-alng(k+1.0+0.5)) ; + qkb = qkf ; + + pbetaf = betadf(y, a, b) ; + pbetab = pbetaf ; + qbetaf = betadf(y, c, b) ; + qbetab = qbetaf ; + + ab1 = a+b-1.0 ; /* might be as small as -0.5 */ + + /*-- RWCox: if a+b-1 < 0, log(Gamma(a+b-1)) won't work; + instead, use Gamma(a+b-1)=Gamma(a+b)/(a+b-1) --*/ + + if( ab1 > 0.0 ) + pgamf = exp(alng(ab1)-alng(a)-alng(b)+(a-1.0)*log(y)+b*log(1.0-y)) ; + else + pgamf = exp(alng(a+b)-alng(a)-alng(b)+(a-1.0)*log(y)+b*log(1.0-y))/ab1 ; + + pgamb = pgamf*y*(ab1)/a ; + + /*-- we can't have c+b-1 < 0, so the above patchup isn't needed --*/ + + qgamf = exp(alng(c+b-1.0)-alng(c)-alng(b)+(c-1.0)*log(y) + b*log(1.0-y)) ; + qgamb = qgamf*y*(c+b-1.0)/c ; + + rempois = 1.0 - pkf ; + delosq2 = del/1.4142135623731 ; + sum = pkf*pbetaf+delosq2*qkf*qbetaf ; + cons = 0.5*(1.0 + 0.5*fabs(delta)) ; + i = 0 ; +L1: + i = i + 1 ; + pgamf = pgamf*y*(a+b+i-2.0)/(a+i-1.0) ; + pbetaf = pbetaf - pgamf ; + pkf = pkf*dels/(k+i) ; + ptermf = pkf*pbetaf ; + qgamf = qgamf*y*(c+b+i-2.0)/(c+i-1.0) ; + qbetaf = qbetaf - qgamf ; + qkf = qkf*dels/(k+i-1.0+1.5) ; + qtermf = qkf*qbetaf ; + term = ptermf + delosq2*qtermf ; + sum = sum + term ; + error = rempois*cons*pbetaf ; + rempois = rempois - pkf ; + + if( i > k ){ + if( error <= 1.e-12 || i >= 9999 ) goto L2 ; + goto L1 ; + } else { + pgamb = pgamb*(a-i+1.0)/(y*(a+b-i)) ; + pbetab = pbetab + pgamb ; + pkb = (k-i+1.0)*pkb/dels ; + ptermb = pkb*pbetab ; + qgamb = qgamb*(c-i+1.0)/(y*(c+b-i)) ; + qbetab = qbetab + qgamb ; + qkb = (k-i+1.0+0.5)*qkb/dels ; + qtermb = qkb*qbetab ; + term = ptermb + delosq2*qtermb ; + sum = sum + term ; + rempois = rempois - pkb ; + if (rempois <= 1.e-12 || i >= 9999) goto L2 ; + goto L1 ; + } +L2: + tnd = 0.5*sum + ans ; + + /*-- return a pqpair, not just the cdf --*/ + + if( indx ){ pq.p = 1.0-tnd; pq.q = tnd ; } + else { pq.p = tnd ; pq.q = 1.0-tnd; } + return pq ; +} + +/*------------------------------*/ +/* Inverse to above function; + uses cdflib dstinv()/dinvr() + to solve the equation. +--------------------------------*/ + +static double tnonc_pq2s( pqpair pq , double dof , double nonc ) +{ + double t ; /* will be result */ + double tbot,ttop , dt ; + double T6=1.e-50,T7=1.e-8 ; + double K4=0.5,K5=5.0 ; + double fx ; + unsigned long qhi,qleft ; + int status , qporq , ite ; + pqpair tpq ; + + if( dof <= 0.0 ) return BIGG ; /* bad user */ + if( pq.p <= 0.0 ) return -BIGG ; + if( pq.q <= 0.0 ) return BIGG ; + + t = student_pq2s(pq,dof) ; /* initial guess */ + + if( fabs(nonc) < 1.e-8 ) return t ; + + t += 0.5*nonc ; /* adjust up or down */ + + dt = 0.1 * fabs(t) ; if( dt < 1.0 ) dt = 1.0 ; /* stepsize */ + + /* scan down for lower bound, below which cdf is < p */ + + tbot = t ; + for( ite=0 ; ite < 1000 ; ite++ ){ + tpq = tnonc_s2pq( tbot , dof , nonc ) ; + if( tpq.p <= pq.p ) break ; + tbot -= dt ; + } + if( ite >= 1000 ) return -BIGG ; + + /* scan up for upper bound, above which cdf is > p */ + + ttop = tbot+0.5*dt ; + for( ite=0 ; ite < 1000 ; ite++ ){ + tpq = tnonc_s2pq( ttop , dof , nonc ) ; + if( tpq.p >= pq.p ) break ; + ttop += dt ; + } + if( ite >= 1000 ) return BIGG ; + + t = 0.5*(tbot+ttop) ; /* initial guess in middle */ + + /* initialize searching parameters */ + + dstinv(&tbot,&ttop,&K4,&K4,&K5,&T6,&T7); + + status = 0 ; qporq = (pq.p <= pq.q) ; + + while(1){ + + dinvr(&status,&t,&fx,&qleft,&qhi) ; + + if( status != 1 ) return t ; /* done! */ + + tpq = tnonc_s2pq( t , dof , nonc ) ; /* get cdf */ + + /* goal of dinvr is to drive fx to zero */ + + fx = (qporq) ? pq.p-tpq.p : pq.q-tpq.q ; + } + + return BIGG ; /* unreachable */ +} + +/*---------------------------------------------------------------- + Chi distribution (sqrt of chi-squared, duh). +------------------------------------------------------------------*/ + +static pqpair chi_s2pq( double xx , double dof ) +{ + pqpair pq={0.0,1.0} ; + + if( xx <= 0.0 || dof <= 0.0 ) return pq ; + return chisq_s2pq( xx*xx , dof ) ; +} + +/*------------------------------*/ + +static double chi_pq2s( pqpair pq , double dof ) +{ + if( pq.p <= 0.0 ) return 0.0 ; + if( pq.q <= 0.0 ) return BIGG ; + return sqrt(chisq_pq2s(pq,dof)) ; +} + +/*---------------------------------------------------------------- + Extreme value type I: cdf(x) = exp(-exp(-x)). +------------------------------------------------------------------*/ + +static pqpair extval1_s2pq( double x ) +{ + double p,q,y ; pqpair pq ; + + if( x > -5.0 ){ y = exp(-x) ; p = exp(-y) ; } + else { y = 1.0 ; p = 0.0 ; } + + if( y >= 1.e-4 ) q = 1.0-p ; + else q = y*(1.0+y*(-0.5+y*(1.0/6.0-y/24.0))) ; + pq.p = p ; pq.q = q ; return pq ; +} + +/*------------------------------*/ + +static double extval1_pq2s( pqpair pq ) +{ + if( pq.p <= 0.0 ) return -BIGG ; + else if( pq.p >= 1.0 ) return BIGG ; + return -log(-log(pq.p)) ; +} + +/*---------------------------------------------------------------- + Weibull distribution: cdf(x) = 1 - exp( -x^c ) for x>0 and c>0. +------------------------------------------------------------------*/ + +static pqpair weibull_s2pq( double x , double c ) +{ + double y ; + pqpair pq={0.0,1.0} ; + + if( x <= 0.0 || c <= 0.0 ) return pq ; + + y = pow(x,c) ; pq.q = exp(-y) ; + if( y >= 1.e-4 ) pq.p = 1.0-pq.q ; + else pq.p = y*(1.0+y*(-0.5+y*(1.0/6.0-y/24.0))) ; + return pq ; +} + +/*------------------------------*/ + +static double weibull_pq2s( pqpair pq , double c ) +{ + if( pq.p <= 0.0 || c <= 0.0 ) return 0.0 ; + else if( pq.q <= 0.0 ) return BIGG ; + return pow( -log(pq.q) , 1.0/c ) ; +} + +/*---------------------------------------------------------------- + Inverse Gaussian: + density proportional to exp(-0.5*c(x+1/x))/x^1.5 (x,c >0). +------------------------------------------------------------------*/ + +static pqpair invgauss_s2pq( double x, double c ) +{ + double y , p1,q1 , p2,q2 , v ; + pqpair pq={0.0,1.0} ; + + if( x <= 0.0 || c <= 0.0 ) return pq ; + + y = sqrt(c/x) ; + v = y*(x-1.0) ; cumnor( &v , &p1,&q1 ) ; + v = -y*(x+1.0) ; cumnor( &v , &p2,&q2 ) ; + pq.p = p1 ; + if( p2 > 0.0 ) pq.p += exp(2.0*c+log(p2)) ; + pq.q = 1.0-pq.p ; return pq ; +} + +/*------------------------------*/ +/* Inverse to above function; + uses cdflib dstinv()/dinvr() + to solve the equation. +--------------------------------*/ + +static double invgauss_pq2s( pqpair pq , double c ) +{ + double t ; /* will be result */ + double tbot,ttop , dt ; + double T6=1.e-50,T7=1.e-8 ; + double K4=0.5,K5=5.0 ; + double fx ; + unsigned long qhi,qleft ; + int status , qporq , ite ; + pqpair tpq ; + + if( c <= 0.0 ) return BIGG ; /* bad user */ + if( pq.p <= 0.0 ) return 0.0 ; + if( pq.q <= 0.0 ) return BIGG ; + + /* initial guess is t=1; scan down for lower bound */ + + tbot = 1.01 ; dt = 0.9 ; + for( ite=0 ; ite < 1000 ; ite++ ){ + tpq = invgauss_s2pq( tbot , c ) ; + if( tpq.p <= pq.p ) break ; + tbot *= dt ; + } + if( ite >= 1000 ) return 0.0 ; + + /* scan up for upper bound */ + + dt = 1.1 ; ttop = tbot*dt ; + for( ite=0 ; ite < 1000 ; ite++ ){ + tpq = invgauss_s2pq( ttop , c ) ; + if( tpq.p >= pq.p ) break ; + ttop *= dt ; + } + if( ite >= 1000 ) return BIGG ; + + t = sqrt(tbot*ttop) ; /* start at geometric mean */ + + /* initialize searching parameters */ + + dstinv(&tbot,&ttop,&K4,&K4,&K5,&T6,&T7); + + status = 0 ; qporq = (pq.p <= pq.q) ; + + while(1){ + + dinvr(&status,&t,&fx,&qleft,&qhi) ; + + if( status != 1 ) return t ; /* done! */ + + tpq = invgauss_s2pq( t , c ) ; + + /* goal is to drive fx to zero */ + + fx = (qporq) ? pq.p-tpq.p : pq.q-tpq.q ; + } + + return BIGG ; /* unreachable */ +} + +/*--------------------------------------------------------------------------*/ +/*! Given a value, calculate both its cdf and reversed cdf (1.0-cdf). + If an error occurs, you'll probably get back {0.0,1.0}. + All the actual work is done in utility functions for each distribution. +----------------------------------------------------------------------------*/ + +static pqpair stat2pq( double val, int code, double p1,double p2,double p3 ) +{ + pqpair pq={0.0,1.0} ; + + switch( code ){ + + case NIFTI_INTENT_CORREL: pq = correl_s2pq ( val, p1 ) ; break; + case NIFTI_INTENT_TTEST: pq = student_s2pq ( val, p1 ) ; break; + case NIFTI_INTENT_FTEST: pq = fstat_s2pq ( val, p1,p2 ) ; break; + case NIFTI_INTENT_ZSCORE: pq = normal_s2pq ( val ) ; break; + case NIFTI_INTENT_CHISQ: pq = chisq_s2pq ( val, p1 ) ; break; + case NIFTI_INTENT_BETA: pq = beta_s2pq ( val, p1,p2 ) ; break; + case NIFTI_INTENT_BINOM: pq = binomial_s2pq( val, p1,p2 ) ; break; + case NIFTI_INTENT_GAMMA: pq = gamma_s2pq ( val, p1,p2 ) ; break; + case NIFTI_INTENT_POISSON: pq = poisson_s2pq ( val, p1 ) ; break; + case NIFTI_INTENT_FTEST_NONC: pq = fnonc_s2pq ( val, p1,p2,p3 ); break; + case NIFTI_INTENT_CHISQ_NONC: pq = chsqnonc_s2pq( val, p1,p2 ); break; + case NIFTI_INTENT_TTEST_NONC: pq = tnonc_s2pq ( val, p1,p2 ) ; break; + case NIFTI_INTENT_CHI: pq = chi_s2pq ( val, p1 ) ; break; + + /* these distributions are shifted and scaled copies of a standard case */ + + case NIFTI_INTENT_INVGAUSS: + if( p1 > 0.0 && p2 > 0.0 ) pq = invgauss_s2pq( val/p1,p2/p1 ) ; break; + + case NIFTI_INTENT_WEIBULL: + if( p2 > 0.0 && p3 > 0.0 ) pq = weibull_s2pq ((val-p1)/p2,p3) ; break; + + case NIFTI_INTENT_EXTVAL: + if( p2 > 0.0 ) pq = extval1_s2pq ( (val-p1)/p2 ) ; break; + + case NIFTI_INTENT_NORMAL: + if( p2 > 0.0 ) pq = normal_s2pq ( (val-p1)/p2 ) ; break; + + case NIFTI_INTENT_LOGISTIC: + if( p2 > 0.0 ) pq = logistic_s2pq( (val-p1)/p2 ) ; break; + + case NIFTI_INTENT_LAPLACE: + if( p2 > 0.0 ) pq = laplace_s2pq ( (val-p1)/p2 ) ; break; + + case NIFTI_INTENT_UNIFORM: + if( p2 > p1 ) pq = uniform_s2pq((val-p1)/(p2-p1)); break; + + /* this case is trivial */ + + case NIFTI_INTENT_PVAL: pq.p = 1.0-val ; pq.q = val ; break; + } + + return pq ; +} + +/*--------------------------------------------------------------------------*/ +/*! Given a pq value (cdf and 1-cdf), compute the value that gives this. + If an error occurs, you'll probably get back a BIGG number. + All the actual work is done in utility functions for each distribution. +----------------------------------------------------------------------------*/ + +static double pq2stat( pqpair pq, int code, double p1,double p2,double p3 ) +{ + double val=BIGG ; + + if( pq.p < 0.0 || pq.q < 0.0 || pq.p > 1.0 || pq.q > 1.0 ) return val ; + + switch( code ){ + + case NIFTI_INTENT_CORREL: val = correl_pq2s ( pq , p1 ) ; break; + case NIFTI_INTENT_TTEST: val = student_pq2s ( pq , p1 ) ; break; + case NIFTI_INTENT_FTEST: val = fstat_pq2s ( pq , p1,p2 ) ; break; + case NIFTI_INTENT_ZSCORE: val = normal_pq2s ( pq ) ; break; + case NIFTI_INTENT_CHISQ: val = chisq_pq2s ( pq , p1 ) ; break; + case NIFTI_INTENT_BETA: val = beta_pq2s ( pq , p1,p2 ) ; break; + case NIFTI_INTENT_BINOM: val = binomial_pq2s( pq , p1,p2 ) ; break; + case NIFTI_INTENT_GAMMA: val = gamma_pq2s ( pq , p1,p2 ) ; break; + case NIFTI_INTENT_POISSON: val = poisson_pq2s ( pq , p1 ) ; break; + case NIFTI_INTENT_FTEST_NONC: val = fnonc_pq2s ( pq , p1,p2,p3 ); break; + case NIFTI_INTENT_CHISQ_NONC: val = chsqnonc_pq2s( pq , p1,p2 ); break; + case NIFTI_INTENT_TTEST_NONC: val = tnonc_pq2s ( pq , p1,p2 ) ; break; + case NIFTI_INTENT_CHI: val = chi_pq2s ( pq , p1 ) ; break; + + /* these distributions are shifted and scaled copies of a standard case */ + + case NIFTI_INTENT_INVGAUSS: + if( p1 > 0.0 && p2 > 0.0 ) val = p1*invgauss_pq2s ( pq,p2/p1); break; + + case NIFTI_INTENT_WEIBULL: + if( p2 > 0.0 && p3 > 0.0 ) val = p1+p2*weibull_pq2s ( pq, p3 ) ; break; + + case NIFTI_INTENT_EXTVAL: + if( p2 > 0.0 ) val = p1+p2*extval1_pq2s ( pq ) ; break; + + case NIFTI_INTENT_NORMAL: + if( p2 > 0.0 ) val = p1+p2*normal_pq2s ( pq ) ; break; + + case NIFTI_INTENT_LOGISTIC: + if( p2 > 0.0 ) val = p1+p2*logistic_pq2s( pq ) ; break; + + case NIFTI_INTENT_LAPLACE: + if( p2 > 0.0 ) val = p1+p2*laplace_pq2s ( pq ) ; break; + + case NIFTI_INTENT_UNIFORM: + if( p2 > p1 ) val = p1+(p2-p1)*uniform_pq2s(pq) ; break; + + /* this case is trivial */ + + case NIFTI_INTENT_PVAL: val = pq.q ; break; + } + + return val ; +} + +/****************************************************************************/ +/*[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]*/ +/*..........................................................................*/ +/*............. AT LAST! Functions to be called by the user! ..............*/ +/*..........................................................................*/ +/*[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]*/ +/****************************************************************************/ + +/**************************************************************************** + Statistical codes implemented here: + + NIFTI_INTENT_CORREL = correlation statistic + NIFTI_INTENT_TTEST = t statistic (central) + NIFTI_INTENT_FTEST = F statistic (central) + NIFTI_INTENT_ZSCORE = N(0,1) statistic + NIFTI_INTENT_CHISQ = Chi-squared (central) + NIFTI_INTENT_BETA = Beta variable (central) + NIFTI_INTENT_BINOM = Binomial variable + NIFTI_INTENT_GAMMA = Gamma distribution + NIFTI_INTENT_POISSON = Poisson distribution + NIFTI_INTENT_FTEST_NONC = noncentral F statistic + NIFTI_INTENT_CHISQ_NONC = noncentral chi-squared + NIFTI_INTENT_TTEST_NONC = noncentral t statistic + NIFTI_INTENT_CHI = Chi statistic (central) + NIFTI_INTENT_INVGAUSS = inverse Gaussian variable + NIFTI_INTENT_WEIBULL = Weibull distribution + NIFTI_INTENT_EXTVAL = Extreme value type I + NIFTI_INTENT_NORMAL = N(mu,variance) normal + NIFTI_INTENT_LOGISTIC = Logistic distribution + NIFTI_INTENT_LAPLACE = Laplace distribution + NIFTI_INTENT_UNIFORM = Uniform distribution + NIFTI_INTENT_PVAL = "p-value" +*****************************************************************************/ + +static char *inam[]={ NULL , NULL , + "CORREL" , "TTEST" , "FTEST" , "ZSCORE" , + "CHISQ" , "BETA" , "BINOM" , "GAMMA" , + "POISSON" , "NORMAL" , "FTEST_NONC" , "CHISQ_NONC" , + "LOGISTIC" , "LAPLACE" , "UNIFORM" , "TTEST_NONC" , + "WEIBULL" , "CHI" , "INVGAUSS" , "EXTVAL" , + "PVAL" , + NULL } ; + +#include +#include + +/*--------------------------------------------------------------------------*/ +/*! Given a string name for a statistic, return its integer code. + Returns -1 if not found. +----------------------------------------------------------------------------*/ + +int nifti_intent_code( char *name ) +{ + char *unam , *upt ; + int ii ; + + if( name == NULL || *name == '\0' ) return -1 ; + + unam = strdup(name) ; + for( upt=unam ; *upt != '\0' ; upt++ ) *upt = (char)toupper(*upt) ; + + for( ii=NIFTI_FIRST_STATCODE ; ii <= NIFTI_LAST_STATCODE ; ii++ ) + if( strcmp(inam[ii],unam) == 0 ) break ; + + free(unam) ; + return (ii <= NIFTI_LAST_STATCODE) ? ii : -1 ; +} + +/*--------------------------------------------------------------------------*/ +/*! Given a value, return its cumulative distribution function (cdf): + - val = statistic + - code = NIFTI_INTENT_* statistical code + - p1,p2,p3 = parameters of the distribution + + If an error occurs, you'll probably get back 0.0. +----------------------------------------------------------------------------*/ + +double nifti_stat2cdf( double val, int code, double p1,double p2,double p3 ) +{ + pqpair pq ; + pq = stat2pq( val, code, p1,p2,p3 ) ; + return pq.p ; +} + +/*--------------------------------------------------------------------------*/ +/*! Given a value, return its reversed cumulative distribution function + (1-cdf): + - val = statistic + - code = NIFTI_INTENT_* statistical code + - p1,p2,p3 = parameters of the distribution + + If an error transpires, you'll probably get back 1.0. +----------------------------------------------------------------------------*/ + +double nifti_stat2rcdf( double val, int code, double p1,double p2,double p3 ) +{ + pqpair pq ; + pq = stat2pq( val, code, p1,p2,p3 ) ; + return pq.q ; +} + +/*--------------------------------------------------------------------------*/ +/*! Given a cdf probability, find the value that gave rise to it. + - p = cdf; 0 < p < 1 + - code = NIFTI_INTENT_* statistical code + - p1,p2,p3 = parameters of the distribution + + If an error transpires, you'll probably get back a BIGG number. +----------------------------------------------------------------------------*/ + +double nifti_cdf2stat( double p , int code, double p1,double p2,double p3 ) +{ + pqpair pq ; + pq.p = p ; pq.q = 1.0-p ; + return pq2stat(pq,code,p1,p2,p3) ; +} + +/*--------------------------------------------------------------------------*/ +/*! Given a reversed cdf probability, find the value that gave rise to it. + - q = 1-cdf; 0 < q < 1 + - code = NIFTI_INTENT_* statistical code + - p1,p2,p3 = parameters of the distribution + + If an error transpires, you'll probably get back a BIGG number. +----------------------------------------------------------------------------*/ + +double nifti_rcdf2stat( double q , int code, double p1,double p2,double p3 ) +{ + pqpair pq ; + pq.p = 1.0-q ; pq.q = q ; + return pq2stat(pq,code,p1,p2,p3) ; +} + +/*--------------------------------------------------------------------------*/ +/*! Given a statistic, compute a z-score from it. That is, the output + is z such that cdf(z) of a N(0,1) variable is the same as the cdf + of the given distribution at val. +----------------------------------------------------------------------------*/ + +double nifti_stat2zscore( double val , int code, double p1,double p2,double p3 ) +{ + pqpair pq ; + + if( code == NIFTI_INTENT_ZSCORE ) return val ; /* trivial */ + if( code == NIFTI_INTENT_NORMAL ) return (val-p1)/p2 ; /* almost so */ + + pq = stat2pq( val, code, p1,p2,p3 ) ; /* find cdf */ + return normal_pq2s( pq ) ; /* find z */ +} + +/*--------------------------------------------------------------------------*/ +/*! Given a statistic, compute a half-z-score from it. That is, the output + is z such that cdf(z) of a half-N(0,1) variable is the same as the cdf + of the given distribution at val. A half-N(0,1) variable has density + zero for z < 0 and twice the usual N(0,1) density for z > 0. +----------------------------------------------------------------------------*/ + +double nifti_stat2hzscore( double val, int code, double p1,double p2,double p3 ) +{ + pqpair pq ; + + pq = stat2pq( val, code, p1,p2,p3 ) ; /* find cdf */ + pq.q = 0.5*(1.0-pq.p) ; pq.p = 0.5*(1.0+pq.p) ; /* mangle it */ + return normal_pq2s( pq ) ; /* find z */ +} + +/****************************************************************************/ +/*[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]*/ +/****************************************************************************/ + +/*--------------------------------------------------------------------------*/ +/* Sample program to test the above functions. Otherwise unimportant. +----------------------------------------------------------------------------*/ + +int main( int argc , char *argv[] ) +{ + double val , p , q , p1=0.0,p2=0.0,p3=0.0 ; + double vbot,vtop,vdel ; + int code , iarg=1 , doq=0 , dod=0 , doi=0 , doz=0 , doh=0 ; + + /*-- print some help for the pitiful user --*/ + + if( argc < 3 || strstr(argv[1],"help") != NULL ){ + int ii ; + printf("\n") ; + printf("Demo program for computing NIfTI statistical functions.\n") ; + printf("Usage: nifti_stats [-q|-d|-1|-z] val CODE [p1 p2 p3]\n") ; + printf(" val can be a single number or in the form bot:top:step.\n") ; + printf(" default ==> output p = Prob(statistic < val).\n") ; + printf(" -q ==> output is 1-p.\n") ; + printf(" -d ==> output is density.\n") ; + printf(" -1 ==> output is x such that Prob(statistic < x) = val.\n") ; + printf(" -z ==> output is z such that Normal cdf(z) = p(val).\n") ; + printf(" -h ==> output is z such that 1/2-Normal cdf(z) = p(val).\n"); + printf(" Allowable CODEs:\n") ; + for( ii=NIFTI_FIRST_STATCODE ; ii <= NIFTI_LAST_STATCODE ; ii++ ){ + printf(" %-10s",inam[ii]); if((ii-NIFTI_FIRST_STATCODE)%6==5)printf("\n"); + } + printf("\n") ; + printf(" Following CODE are distributional parameters, as needed.\n"); + printf("\n") ; + printf("Results are written to stdout, 1 number per output line.\n") ; + printf("Example (piping output into AFNI program 1dplot):\n") ; + printf(" nifti_stats -d 0:4:.001 INVGAUSS 1 3 | 1dplot -dx 0.001 -stdin\n"); + printf("\n") ; + printf("Author - RW Cox - SSCC/NIMH/NIH/DHHS/USA/EARTH - March 2004\n") ; + printf("\n") ; + exit(0) ; + } + + /*-- check first arg to see if it is an output option; + if so, set the appropriate output flag to determine what to compute --*/ + + if( strcmp(argv[iarg],"-q") == 0 ){ doq = 1 ; iarg++ ; } + else if( strcmp(argv[iarg],"-d") == 0 ){ dod = 1 ; iarg++ ; } + else if( strcmp(argv[iarg],"-1") == 0 ){ doi = 1 ; iarg++ ; } + else if( strcmp(argv[iarg],"-z") == 0 ){ doz = 1 ; iarg++ ; } + else if( strcmp(argv[iarg],"-h") == 0 ){ doh = 1 ; iarg++ ; } + + /*-- get the value(s) to process --*/ + + vbot=vtop=vdel = 0.0 ; + sscanf( argv[iarg++] , "%lf:%lf:%lf" , &vbot,&vtop,&vdel ) ; + if( vbot >= vtop ) vdel = 0.0 ; + if( vdel <= 0.0 ) vtop = vbot ; + + /*-- decode the CODE into the integer signifying the distribution --*/ + + code = nifti_intent_code(argv[iarg++]) ; + if( code < 0 ){ fprintf(stderr,"illegal code=%s\n",argv[iarg-1]); exit(1); } + + /*-- get the parameters, if present (defaults are 0) --*/ + + if( argc > iarg ) p1 = strtod(argv[iarg++],NULL) ; + if( argc > iarg ) p2 = strtod(argv[iarg++],NULL) ; + if( argc > iarg ) p3 = strtod(argv[iarg++],NULL) ; + + /*-- loop over input value(s), compute output, write to stdout --*/ + + for( val=vbot ; val <= vtop ; val += vdel ){ + if( doq ) /* output = 1-cdf */ + p = nifti_stat2rcdf( val , code,p1,p2,p3 ) ; + else if( dod ) /* output = density */ + p = 1000.0*( nifti_stat2cdf(val+.001,code,p1,p2,p3) + -nifti_stat2cdf(val ,code,p1,p2,p3)) ; + else if( doi ) /* output = inverse */ + p = nifti_cdf2stat( val , code,p1,p2,p3 ) ; + else if( doz ) /* output = z score */ + p = nifti_stat2zscore( val , code,p1,p2,p3 ) ; + else if( doh ) /* output = halfz score */ + p = nifti_stat2hzscore( val , code,p1,p2,p3 ) ; + else /* output = cdf */ + p = nifti_stat2cdf( val , code,p1,p2,p3 ) ; + + printf("%.9g\n",p) ; + if( vdel <= 0.0 ) break ; /* the case of just 1 value */ + } + + /*-- terminus est --*/ + + exit(0) ; +} + diff --git a/spm/nii_for_spm2/@nifti/private/nifti_stats.m b/spm/nii_for_spm2/@nifti/private/nifti_stats.m new file mode 100755 index 0000000..1f43961 --- /dev/null +++ b/spm/nii_for_spm2/@nifti/private/nifti_stats.m @@ -0,0 +1,41 @@ +function varargout = nifti_stats(varargin) +% Conversion among various statistics +% FORMAT P = nifti_stats(VAL,CODE,OPT,PARAM) +% CODE can be one of +% 'CORREL' 'TTEST' 'FTEST' 'ZSCORE' +% 'CHISQ' 'BETA' 'BINOM' 'GAMMA' +% 'POISSON' 'NORMAL' 'FTEST_NONC' 'CHISQ_NONC' +% 'LOGISTIC' 'LAPLACE' 'UNIFORM' 'TTEST_NONC' +% 'WEIBULL' 'CHI' 'INVGAUSS' 'EXTVAL' +% 'PVAL' +% With only one input argument, CODE defaults to 'ZSCORE' +% +% OPT can be one of +% '-p' ==> output P = Prob(statistic < VAL). +% '-q' ==> output is 1-p. +% '-d' ==> output is probability density. +% '-1' ==> output is X such that Prob(statistic < x) = VAL. +% '-z' ==> output is Z such that Normal cdf(Z) = p(VAL). +% '-h' ==> output is Z such that 1/2-Normal cdf(Z) = p(VAL). +% With less than three input arguments, OPT defaults to '-p'. +% +% PARAM are up to three distribution parameters. +% These default to zero if unspecified. +% +% P is an array with the same dimensions as VAL. +% +%_______________________________________________________________________ +% 99.99% of the work by RW Cox - SSCC/NIMH/NIH/DHHS/USA/EARTH - March 2004 +% 0.01% of the work (the mex wrapper) by John Ashburner - FIL/ION/UCL +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% +% $Id: nifti_stats.m 253 2005-10-13 15:31:34Z guillaume $ + + +fprintf('******************************************\n'); +fprintf('Compile the nifti_stats function with\n'); +fprintf(' mex nifti_stats.c nifti_stats_mex.c -O\n'); +fprintf('******************************************\n'); + +error('nifti_stats is not compiled.'); diff --git a/spm/nii_for_spm2/@nifti/private/nifti_stats_mex.c b/spm/nii_for_spm2/@nifti/private/nifti_stats_mex.c new file mode 100755 index 0000000..76d7be7 --- /dev/null +++ b/spm/nii_for_spm2/@nifti/private/nifti_stats_mex.c @@ -0,0 +1,124 @@ +#ifndef lint +static char svnid[] = "$Id: nifti_stats_mex.c 253 2005-10-13 15:31:34Z guillaume $"; +#endif +/* + * This is a Matlab mex interface for Bob Cox's extensive nifti_stats.c + * functionality. See nifti_stats.m for documentation. + */ + +#include +#include +#include +#include "mex.h" + +#include "nifti1.h" +extern int nifti_intent_code( char *name ); +extern double nifti_stat2cdf( double val, int code, double p1,double p2,double p3 ); +extern double nifti_stat2rcdf( double val, int code, double p1,double p2,double p3 ); +extern double nifti_stat2cdf( double val, int code, double p1,double p2,double p3 ); +extern double nifti_cdf2stat( double val, int code, double p1,double p2,double p3 ); +extern double nifti_stat2zscore( double val, int code, double p1,double p2,double p3 ); +extern double nifti_stat2hzscore( double val, int code, double p1,double p2,double p3 ); + +void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) +{ + double *val, *p, p1=0.0,p2=0.0,p3=0.0 ; + int code=5, dop=1, doq=0, dod=0, doi=0, doz=0, doh=0 ; + int ndim, i, n; + const int *dim; + + if (nlhs>1) mexErrMsgTxt("Too many output arguments."); + if (nrhs<1) mexErrMsgTxt("Not enough input arguments."); + if (nrhs>4) mexErrMsgTxt("Too many input arguments."); + + /* VAL */ + if (!mxIsNumeric(prhs[0]) || !mxIsDouble(prhs[0]) || mxIsComplex(prhs[0])) + mexErrMsgTxt("Wrong datatype for 1st argument."); + ndim = mxGetNumberOfDimensions(prhs[0]); + dim = mxGetDimensions(prhs[0]); + n = 1; + for(i=0,n=1; i=2) + { + if (mxIsChar(prhs[1])) + { + int buflen; + char *buf; + buflen = mxGetN(prhs[1])*mxGetM(prhs[1])+1; + buf = (char *)mxCalloc(buflen,sizeof(char)); + mxGetString(prhs[1],buf,buflen); + code = nifti_intent_code(buf); + mxFree(buf); + } + else if (mxIsNumeric(prhs[1]) && mxIsDouble(prhs[1]) && !mxIsComplex(prhs[1])) + { + if (mxGetM(prhs[1])*mxGetN(prhs[1]) != 1) + mexErrMsgTxt("Wrong sized 2nd argument."); + code = (int)mxGetPr(prhs[1])[0]; + } + else mexErrMsgTxt("Wrong datatype for 2nd argument."); + if (codeNIFTI_LAST_STATCODE) + mexErrMsgTxt("Illegal Stat-Code."); + } + + /* OPT */ + if (nrhs>=3) + { + int buflen; + char *buf; + dop = 0; + if (!mxIsChar(prhs[2])) + mexErrMsgTxt("Wrong datatype for3rd argument."); + buflen = mxGetN(prhs[2])*mxGetM(prhs[2])+1; + buf = (char *)mxCalloc(buflen,sizeof(char)); + mxGetString(prhs[2],buf,buflen); + if ( strcmp(buf,"-p") == 0 ) dop = 1; + else if ( strcmp(buf,"-q") == 0 ) doq = 1; + else if ( strcmp(buf,"-d") == 0 ) dod = 1; + else if ( strcmp(buf,"-1") == 0 ) doi = 1; + else if ( strcmp(buf,"-z") == 0 ) doz = 1; + else if ( strcmp(buf,"-h") == 0 ) doh = 1; + else { mxFree(buf); mexErrMsgTxt("Unrecognised option."); } + mxFree(buf); + } + + /* PARAM */ + if (nrhs>=4) + { + int np; + if (!mxIsNumeric(prhs[3]) || !mxIsDouble(prhs[3]) || mxIsComplex(prhs[3])) + mexErrMsgTxt("Wrong datatype for 4th argument."); + np = mxGetM(prhs[3])*mxGetN(prhs[3]); + if (np>3) mexErrMsgTxt("Wrong sized 4th argument."); + if (np>=1) p1 = mxGetPr(prhs[3])[0]; + if (np>=2) p2 = mxGetPr(prhs[3])[1]; + if (np>=3) p3 = mxGetPr(prhs[3])[2]; + } + + /* P */ + plhs[0] = mxCreateNumericArray(ndim,dim,mxDOUBLE_CLASS,mxREAL); + p = mxGetData(plhs[0]); + + /* Call Bob's code */ + for(i=0; i7 + be = ~be; + fclose(fp); + if be, mach = 'ieee-be'; + else mach = 'ieee-le'; + end; + fp = fopen(hname,'r',mach); + if fp==-1 + hdr = []; + return; + end; +end; + +% Is it NIFTI or not +fseek(fp,0,'bof'); +fseek(fp,344,'bof'); +mgc = deblank(char(fread(fp,4,'uint8')')); +switch mgc +case {'ni1','n+1'} + org = niftistruc; +otherwise + org = mayostruc; +end; +fseek(fp,0,'bof'); +% Read the fields +for i=1:length(org) + tmp = fread(fp,org(i).len,['*' org(i).dtype.prec])'; + if length(tmp) ~= org(i).len +disp([length(tmp) org(i).len]); + tmp = org(i).def; + ok = false; + end; + tmp = feval(org(i).dtype.conv,tmp); + hdr.(org(i).label) = tmp; +end; + +fclose(fp); +if ~ok, + fprintf('There was a problem reading the header of\n'); + fprintf('"%s".\n', fname); + fprintf('It may be corrupted in some way.'); +end; +return; + diff --git a/spm/nii_for_spm2/@nifti/private/write_extras.m b/spm/nii_for_spm2/@nifti/private/write_extras.m new file mode 100755 index 0000000..59aa818 --- /dev/null +++ b/spm/nii_for_spm2/@nifti/private/write_extras.m @@ -0,0 +1,36 @@ +function extras = write_extras(fname,extras) +% Write extra bits of information +%_______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% +% $Id: write_extras.m 473 2006-03-08 18:11:59Z john $ + + +[pth,nam,ext] = fileparts(fname); +switch ext +case {'.hdr','.img','.nii'} + mname = fullfile(pth,[nam '.mat']); +case {'.HDR','.IMG','.NII'} + mname = fullfile(pth,[nam '.MAT']); +otherwise + mname = fullfile(pth,[nam '.mat']); +end +if isstruct(extras) && ~isempty(fieldnames(extras)), + savefields(mname,extras); +end; + +function savefields(fnam,p) +if length(p)>1, error('Can''t save fields.'); end; +fn = fieldnames(p); +for i_=1:length(fn), + eval([fn{i_} '= p.' fn{i_} ';']); +end; +if str2num(version('-release'))>=14, + fn = {'-V6',fn{:}}; +end; +if numel(fn)>0, + save(fnam,fn{:}); +end; +return; + diff --git a/spm/nii_for_spm2/@nifti/private/write_hdr_raw.m b/spm/nii_for_spm2/@nifti/private/write_hdr_raw.m new file mode 100644 index 0000000..cc9241c --- /dev/null +++ b/spm/nii_for_spm2/@nifti/private/write_hdr_raw.m @@ -0,0 +1,77 @@ +function ok = write_hdr_raw(fname,hdr,be) +% Write a NIFTI-1 .hdr file. +% FORMAT ok = write_hdr_raw(fname,hdr,be) +% fname - filename of image +% hdr - a structure containing hdr info +% be - whether big-endian or not +% ok - status (1=good, 0=bad) +% _______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% +% $Id: write_hdr_raw.m 1059 2008-01-03 15:59:48Z guillaume $ + + +[pth,nam,ext] = fileparts(fname); +if isempty(pth), pth = pwd; end + +if isfield(hdr,'magic') + org = niftistruc; + switch deblank(hdr.magic) + case {'ni1'} + hname = fullfile(pth,[nam '.hdr']); + case {'n+1'} + hname = fullfile(pth,[nam '.nii']); + otherwise + error('Bad header.'); + end; +else + org = mayostruc; + hname = fullfile(pth,[nam '.hdr']); +end; + +if nargin >=3 + if be, mach = 'ieee-be'; + else mach = 'ieee-le'; + end; +else mach = 'native'; +end; + +ok = true; +if exist(hname,'file'), + fp = fopen(hname,'r+',mach); +else + fp = fopen(hname,'w+',mach); +end +if fp == -1, + ok = false; + return; +end + +for i=1:length(org) + if isfield(hdr,org(i).label), + dat = hdr.(org(i).label); + if length(dat) ~= org(i).len, + if length(dat)< org(i).len, + dat = [dat(:) ; zeros(org(i).len-length(dat),1)]; + else + dat = dat(1:org(i).len); + end; + end; + else + dat = org(i).def; + end; + % fprintf('%s=\n',org(i).label) + % disp(dat) + len = fwrite(fp,dat,org(i).dtype.prec); + if len ~= org(i).len, + ok = false; + end; +end; +fclose(fp); +if ~ok, + fprintf('There was a problem writing to the header of\n'); + fprintf('"%s"\n', fname); +end; +return; + diff --git a/spm/nii_for_spm2/@nifti/structn.m b/spm/nii_for_spm2/@nifti/structn.m new file mode 100755 index 0000000..aeac863 --- /dev/null +++ b/spm/nii_for_spm2/@nifti/structn.m @@ -0,0 +1,20 @@ +function t = structn(obj) +% Convert a NIFTI-1 object into a form of struct +% _______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% +% $Id: structn.m 253 2005-10-13 15:31:34Z guillaume $ + + +if numel(obj)~=1, + error('Too many elements to convert'); +end; +fn = fieldnames(obj); +for i=1:length(fn) + tmp = subsref(obj,struct('type','.','subs',fn{i})); + if ~isempty(tmp) + t.(fn{i}) = tmp; + end; +end; +return; diff --git a/spm/nii_for_spm2/@nifti/subsasgn.m b/spm/nii_for_spm2/@nifti/subsasgn.m new file mode 100755 index 0000000..4cb4770 --- /dev/null +++ b/spm/nii_for_spm2/@nifti/subsasgn.m @@ -0,0 +1,400 @@ +function obj = subsasgn(obj,subs,varargin) +% Subscript assignment +% See subsref for meaning of fields. +% _______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% +% $Id: subsasgn.m 253 2005-10-13 15:31:34Z guillaume $ + + +switch subs(1).type, +case {'.'}, + if numel(obj)~=nargin-2, + error('The number of outputs should match the number of inputs.'); + end; + objs = struct(obj); + for i=1:length(varargin), + val = varargin{i}; + obji = class(objs(i),'nifti'); + obji = fun(obji,subs,val); + objs(i) = struct(obji); + end; + obj = class(objs,'nifti'); + +case {'()'}, + objs = struct(obj); + if length(subs)>1, + t = subsref(objs,subs(1)); + % A lot of this stuff is a little flakey, and may cause Matlab to bomb. + % + %if numel(t) ~= nargin-2, + % error('The number of outputs should match the number of inputs.'); + %end; + for i=1:numel(t), + val = varargin{1}; + obji = class(t(i),'nifti'); + obji = subsasgn(obji,subs(2:end),val); + t(i) = struct(obji); + end; + objs = subsasgn(objs,subs(1),t); + else + if numel(varargin)>1, + error('Illegal right hand side in assignment. Too many elements.'); + end; + val = varargin{1}; + if isa(val,'nifti'), + objs = subsasgn(objs,subs,struct(val)); + elseif isempty(val), + objs = subsasgn(objs,subs,[]); + else + error('Assignment between unlike types is not allowed.'); + end; + end; + obj = class(objs,'nifti'); + +otherwise + error('Cell contents reference from a non-cell array object.'); +end; +return; +%======================================================================= + +%======================================================================= +function obj = fun(obj,subs,val) +% Subscript referencing + +switch subs(1).type, +case {'.'}, + objs = struct(obj); + for ii=1:numel(objs) + obj = objs(ii); + + if any(strcmpi(subs(1).subs,{'dat'})), + if length(subs)>1, + val = subsasgn(obj.dat,subs(2:end),val); + end; + obj = assigndat(obj,val); + objs(ii) = obj; + continue; + end; + + if isempty(obj.hdr), obj.hdr = empty_hdr; end; + if ~isfield(obj.hdr,'magic'), error('Not a NIFTI-1 header'); end; + + if length(subs)>1, % && ~strcmpi(subs(1).subs,{'raw','dat'}), + val0 = subsref(class(obj,'nifti'),subs(1)); + val1 = subsasgn(val0,subs(2:end),val); + else + val1 = val; + end; + + switch(subs(1).subs) + case {'extras'} + if length(subs)>1, + obj.extras = subsasgn(obj.extras,subs(2:end),val); + else + obj.extras = val; + end; + + case {'mat0'} + if ~isnumeric(val1) || ndims(val1)~=2 || any(size(val1)~=[4 4]) || sum((val1(4,:)-[0 0 0 1]).^2)>1e-8, + error('"mat0" should be a 4x4 matrix, with a last row of 0,0,0,1.'); + end; + if obj.hdr.qform_code==0, obj.hdr.qform_code=2; end; + s = double(bitand(obj.hdr.xyzt_units,7)); + if s + d = findindict(s,'units'); + val1 = diag([[1 1 1]/d.rescale 1])*val1; + end; + obj.hdr = encode_qform0(double(val1), obj.hdr); + + case {'mat0_intent'} + if isempty(val1), + obj.hdr.qform_code = 0; + else + if ~ischar(val1) && ~(isnumeric(val1) && numel(val1)==1), + error('"mat0_intent" should be a string or a scalar.'); + end; + d = findindict(val1,'xform'); + if ~isempty(d) + obj.hdr.qform_code = d.code; + end; + end; + + case {'mat'} + if ~isnumeric(val1) || ndims(val1)~=2 || any(size(val1)~=[4 4]) || sum((val1(4,:)-[0 0 0 1]).^2)>1e-8 + error('"mat" should be a 4x4 matrix, with a last row of 0,0,0,1.'); + end; + if obj.hdr.sform_code==0, obj.hdr.sform_code=2; end; + s = double(bitand(obj.hdr.xyzt_units,7)); + if s + d = findindict(s,'units'); + val1 = diag([[1 1 1]/d.rescale 1])*val1; + end; + val1 = val1 * [eye(4,3) [1 1 1 1]']; + obj.hdr.srow_x = val1(1,:); + obj.hdr.srow_y = val1(2,:); + obj.hdr.srow_z = val1(3,:); + + case {'mat_intent'} + if isempty(val1), + obj.hdr.sform_code = 0; + else + if ~ischar(val1) && ~(isnumeric(val1) && numel(val1)==1), + error('"mat_intent" should be a string or a scalar.'); + end; + d = findindict(val1,'xform'); + if ~isempty(d), + obj.hdr.sform_code = d.code; + end; + end; + + case {'intent'} + if ~valid_fields(val1,{'code','param','name'}) + obj.hdr.intent_code = 0; + obj.hdr.intent_p1 = 0; + obj.hdr.intent_p2 = 0; + obj.hdr.intent_p3 = 0; + obj.hdr.intent_name = ''; + else + if ~isfield(val1,'code'), + val1.code = obj.hdr.intent_code; + end; + d = findindict(val1.code,'intent'); + if ~isempty(d), + obj.hdr.intent_code = d.code; + if isfield(val1,'param'), + prm = [double(val1.param(:)) ; 0 ; 0; 0]; + prm = [prm(1:length(d.param)) ; 0 ; 0; 0]; + obj.hdr.intent_p1 = prm(1); + obj.hdr.intent_p2 = prm(2); + obj.hdr.intent_p3 = prm(3); + end; + if isfield(val1,'name'), + obj.hdr.intent_name = val1.name; + end; + end; + end; + + case {'diminfo'} + if ~valid_fields(val1,{'frequency','phase','slice','slice_time'}) + tmp = obj.hdr.dim_info; + for bit=1:6, + tmp = bitset(tmp,bit,0); + end; + obj.hdr.dim_info = tmp; + obj.hdr.slice_start = 0; + obj.hdr.slice_end = 0; + obj.hdr.slice_duration = 0; + obj.hdr.slice_code = 0; + else + if isfield(val1,'frequency'), + tmp = val1.frequency; + if ~isnumeric(tmp) || numel(tmp)~=1 || tmp<0 || tmp>3, + error('Invalid frequency direction'); + end; + obj.hdr.dim_info = bitset(obj.hdr.dim_info,1,bitget(tmp,1)); + obj.hdr.dim_info = bitset(obj.hdr.dim_info,2,bitget(tmp,2)); + end; + + if isfield(val1,'phase'), + tmp = val1.phase; + if ~isnumeric(tmp) || numel(tmp)~=1 || tmp<0 || tmp>3, + error('Invalid phase direction'); + end; + obj.hdr.dim_info = bitset(obj.hdr.dim_info,3,bitget(tmp,1)); + obj.hdr.dim_info = bitset(obj.hdr.dim_info,4,bitget(tmp,2)); + end; + + if isfield(val1,'slice'), + tmp = val1.slice; + if ~isnumeric(tmp) || numel(tmp)~=1 || tmp<0 || tmp>3, + error('Invalid slice direction'); + end; + obj.hdr.dim_info = bitset(obj.hdr.dim_info,5,bitget(tmp,1)); + obj.hdr.dim_info = bitset(obj.hdr.dim_info,6,bitget(tmp,2)); + end; + + if isfield(val1,'slice_time') + tim = val1.slice_time; + if ~valid_fields(tim,{'start','end','duration','code'}), + obj.hdr.slice_code = 0; + obj.hdr.slice_start = 0; + obj.hdr.end_slice = 0; + obj.hdr.slice_duration = 0; + else + % sld = double(bitget(obj.hdr.dim_info,5)) + 2*double(bitget(obj.hdr.dim_info,6)); + + if isfield(tim,'start'), + ss = double(tim.start); + if isnumeric(ss) && numel(ss)==1 && ~rem(ss,1), % && ss>=1 && ss<=obj.hdr.dim(sld+1) + obj.hdr.slice_start = ss-1; + else + error('Inappropriate "slice_time.start".'); + end; + end; + + if isfield(tim,'end'), + ss = double(tim.end); + if isnumeric(ss) && numel(ss)==1 && ~rem(ss,1), % && ss>=1 && ss<=obj.hdr.dim(sld+1) + obj.hdr.slice_end = ss-1; + else + error('Inappropriate "slice_time.end".'); + end; + end; + + if isfield(tim,'duration') + sd = double(tim.duration); + if isnumeric(sd) && numel(sd)==1, + s = double(bitand(obj.hdr.xyzt_units,24)); + d = findindict(s,'units'); + if ~isempty(d) && d.rescale, sd = sd/d.rescale; end; + obj.hdr.slice_duration = sd; + else + error('Inappropriate "slice_time.duration".'); + end; + end; + + if isfield(tim,'code'), + d = findindict(tim.code,'sliceorder'); + if ~isempty(d), + obj.hdr.slice_code = d.code; + end; + end; + end; + end; + end; + + case {'timing'} + if ~valid_fields(val1,{'toffset','tspace'}), + obj.hdr.pixdim(5) = 0; + obj.hdr.toffset = 0; + else + s = double(bitand(obj.hdr.xyzt_units,24)); + d = findindict(s,'units'); + if isfield(val1,'toffset'), + if isnumeric(val1.toffset) && numel(val1.toffset)==1, + if d.rescale, + val1.toffset = val1.toffset/d.rescale; + end; + obj.hdr.toffset = val1.toffset; + else + error('"timing.toffset" needs to be numeric with 1 element'); + end; + end; + if isfield(val1,'tspace'), + if isnumeric(val1.tspace) && numel(val1.tspace)==1, + if d.rescale, + val1.tspace = val1.tspace/d.rescale; + end; + obj.hdr.pixdim(5) = val1.tspace; + else + error('"timing.tspace" needs to be numeric with 1 element'); + end; + end; + end; + + case {'descrip'} + if isempty(val1), val1 = char(val1); end; + if ischar(val1), + obj.hdr.descrip = val1; + else + error('"descrip" must be a string.'); + end; + + case {'cal'} + if isempty(val1), + obj.hdr.cal_min = 0; + obj.hdr.cal_max = 0; + else + if isnumeric(val1) && numel(val1)==2, + obj.hdr.cal_min = val1(1); + obj.hdr.cal_max = val1(2); + else + error('"cal" should contain two elements.'); + end; + end; + + case {'aux_file'} + if isempty(val1), val1 = char(val1); end; + if ischar(val1), + obj.hdr.aux_file = val1; + else + error('"aux_file" must be a string.'); + end; + + case {'hdr'} + error('hdr is a read-only field.'); + obj.hdr = val1; + + otherwise + error(['Reference to non-existent field ''' subs(1).subs '''.']); + end; + + objs(ii) = obj; + end + obj = class(objs,'nifti'); + +otherwise + error('This should not happen.'); +end; +return; +%======================================================================= + +%======================================================================= +function obj = assigndat(obj,val) +if isa(val,'file_array'), + sz = size(val); + if numel(sz)>8, + error('Too many dimensions in data.'); + end; + sz = [sz 1 1 1 1 1 1 1 1]; + sz = sz(1:8); + sval = struct(val); + d = findindict(sval.dtype,'dtype'); + if isempty(d) + error(['Unknown datatype (' num2str(double(sval.datatype)) ').']); + end; + + [pth,nam,suf] = fileparts(sval.fname); + if any(strcmp(suf,{'.img','.IMG'})) + val.offset = max(sval.offset,0); + obj.hdr.magic = 'ni1'; + elseif any(strcmp(suf,{'.nii','.NII'})) + val.offset = max(sval.offset,352); + obj.hdr.magic = 'n+1'; + else + error(['Unknown filename extension (' suf ').']); + end; + val.offset = (ceil(val.offset/16))*16; + obj.hdr.vox_offset = val.offset; + + obj.hdr.dim(2:(numel(sz)+1)) = sz; + nd = max(find(obj.hdr.dim(2:end)>1)); + if isempty(nd), nd = 3; end; + obj.hdr.dim(1) = nd; + obj.hdr.datatype = sval.dtype; + obj.hdr.bitpix = d.size*8; + if ~isempty(sval.scl_slope), obj.hdr.scl_slope = sval.scl_slope; end; + if ~isempty(sval.scl_inter), obj.hdr.scl_inter = sval.scl_inter; end; + obj.dat = val; +else + error('"raw" must be of class "file_array"'); +end; +return; + +function ok = valid_fields(val,allowed) +if isempty(val), ok = false; return; end; +if ~isstruct(val), + error(['Expecting a structure, not a ' class(val) '.']); +end; +fn = fieldnames(val); +for ii=1:length(fn), + if ~any(strcmpi(fn{ii},allowed)), + fprintf('Allowed fieldnames are:\n'); + for i=1:length(allowed), fprintf(' %s\n', allowed{i}); end; + error(['"' fn{ii} '" is not a valid fieldname.']); + end +end +ok = true; +return; diff --git a/spm/nii_for_spm2/@nifti/subsref.m b/spm/nii_for_spm2/@nifti/subsref.m new file mode 100755 index 0000000..f561f2a --- /dev/null +++ b/spm/nii_for_spm2/@nifti/subsref.m @@ -0,0 +1,241 @@ +function varargout = subsref(opt,subs) +% Subscript referencing +% +% Fields are: +% dat - a file-array representing the image data +% mat0 - a 9-parameter affine transform (from qform0) +% Note that the mapping is from voxels (where the first +% is considered to be at [1,1,1], to millimetres. See +% mat0_interp for the meaning of the transform. +% mat - a 12-parameter affine transform (from sform0) +% Note that the mapping is from voxels (where the first +% is considered to be at [1,1,1], to millimetres. See +% mat1_interp for the meaning of the transform. +% mat_intent - intention of mat. This field may be missing/empty. +% mat0_intent - intention of mat0. This field may be missing/empty. +% intent - interpretation of image. When present, this structure +% contains the fields +% code - name of interpretation +% params - parameters needed to interpret the image +% diminfo - MR encoding of different dimensions. This structure may +% contain some or all of the following fields +% frequency - a value of 1-3 indicating frequency direction +% phase - a value of 1-3 indicating phase direction +% slice - a value of 1-3 indicating slice direction +% slice_time - only present when "slice" field is present. +% Contains the following fields +% code - ascending/descending etc +% start - starting slice number +% end - ending slice number +% duration - duration of each slice acquisition +% Setting frequency, phase or slice to 0 will remove it. +% timing - timing information. When present, contains the fields +% toffset - acquisition time of first volume (seconds) +% tspace - time between sucessive volumes (seconds) +% descrip - a brief description of the image +% cal - a two-element vector containing cal_min and cal_max +% aux_file - name of an auxiliary file +% _______________________________________________________________________ +% Copyright (C) 2005 Wellcome Department of Imaging Neuroscience + +% +% $Id: subsref.m 253 2005-10-13 15:31:34Z guillaume $ + + +varargout = rec(opt,subs); +return; + +function c = rec(opt,subs) +switch subs(1).type, +case {'.'}, + c = {}; + opts = struct(opt); + for ii=1:numel(opts) + opt = class(opts(ii),'nifti'); + %if ~isstruct(opt) + % error('Attempt to reference field of non-structure array.'); + %end; + + h = opt.hdr; + if isempty(h), + %error('No header.'); + h = empty_hdr; + end; + + % NIFTI-1 FORMAT + switch(subs(1).subs) + case 'extras', + t = opt.extras; + + case 'raw', % A hidden field + if isa(opt.dat,'file_array'), + tmp = struct(opt.dat); + tmp.scl_slope = []; + tmp.scl_inter = []; + t = file_array(tmp); + else + t = opt.dat; + end; + + case 'dat', + t = opt.dat; + + case 'mat0', + t = decode_qform0(h); + s = double(bitand(h.xyzt_units,7)); + if s + d = findindict(s,'units'); + if ~isempty(d) + t = diag([d.rescale*[1 1 1] 1])*t; + end; + end; + + case 'mat0_intent', + d = findindict(h.qform_code,'xform'); + if isempty(d) || d.code==0, + t = ''; + else + t = d.label; + end; + + case 'mat', + if h.sform_code > 0 + t = double([h.srow_x ; h.srow_y ; h.srow_z ; 0 0 0 1]); + t = t * [eye(4,3) [-1 -1 -1 1]']; + else + t = decode_qform0(h); + end + s = double(bitand(h.xyzt_units,7)); + if s + d = findindict(s,'units'); + t = diag([d.rescale*[1 1 1] 1])*t; + end; + + case 'mat_intent', + if h.sform_code>0, + t = h.sform_code; + else + t = h.qform_code; + end; + d = findindict(t,'xform'); + if isempty(d) || d.code==0, + t = ''; + else + t = d.label; + end; + + case 'intent', + d = findindict(h.intent_code,'intent'); + if isempty(d) || d.code == 0, + %t = struct('code','UNKNOWN','param',[]); + t = []; + else + t = struct('code',d.label,'param',... + double([h.intent_p1 h.intent_p2 h.intent_p3]), 'name',deblank(h.intent_name)); + t.param = t.param(1:length(d.param)); + end + + case 'diminfo', + t = []; + tmp = bitand( h.dim_info ,3); if tmp, t.frequency = double(tmp); end; + tmp = bitand(bitshift(h.dim_info,-2),3); if tmp, t.phase = double(tmp); end; + tmp = bitand(bitshift(h.dim_info,-4),3); if tmp, t.slice = double(tmp); end; + % t = struct('frequency',bitand( h.dim_info ,3),... + % 'phase',bitand(bitshift(h.dim_info,-2),3),... + % 'slice',bitand(bitshift(h.dim_info,-4),3)) + if isfield(t,'slice') + sc = double(h.slice_code); + ss = double(h.slice_start)+1; + se = double(h.slice_end)+1; + ss = max(ss,1); + se = min(se,double(h.dim(t.slice+1))); + + sd = double(h.slice_duration); + s = double(bitand(h.xyzt_units,24)); + d = findindict(s,'units'); + if d.rescale, sd = sd*d.rescale; end; + + ns = (se-ss+1); + d = findindict(sc,'sliceorder'); + if isempty(d) + label = 'UNKNOWN'; + else + label = d.label; + end; + t.slice_time = struct('code',label,'start',ss,'end',se,'duration',sd); + if 0, % Never + t.times = zeros(1,double(h.dim(t.slice+1)))+NaN; + switch sc, + case 0, % Unknown + t.times(ss:se) = zeros(1,ns); + case 1, % sequential increasing + t.times(ss:se) = (0:(ns-1))*sd; + case 2, % sequential decreasing + t.times(ss:se) = ((ns-1):-1:0)*sd; + case 3, % alternating increasing + t.times(ss:2:se) = (0:floor((ns+1)/2-1))*sd; + t.times((ss+1):2:se) = (floor((ns+1)/2):(ns-1))*sd; + case 4, % alternating decreasing + t.times(se:-2:ss) = (0:floor((ns+1)/2-1))*sd; + t.times(se:-2:(ss+1)) = (floor((ns+1)/2):(ns-1))*sd; + end; + end; + end; + + case 'timing', + to = double(h.toffset); + dt = double(h.pixdim(5)); + if to==0 && dt==0, + t = []; + else + s = double(bitand(h.xyzt_units,24)); + d = findindict(s,'units'); + if d.rescale, + to = to*d.rescale; + dt = dt*d.rescale; + end; + t = struct('toffset',to,'tspace',dt); + end; + + case 'descrip', + t = deblank(h.descrip); + msk = find(t==0); + if any(msk), t=t(1:(msk(1)-1)); end; + + case 'cal', + t = [double(h.cal_min) double(h.cal_max)]; + if all(t==0), t = []; end; + + case 'aux_file', + t = deblank(h.aux_file); + + case 'hdr', % Hidden field + t = h; + + otherwise + error(['Reference to non-existent field ''' subs(1).subs '''.']); + end; + if numel(subs)>1, + t = subsref(t,subs(2:end)); + end; + c{ii} = t; + end; +case {'{}'}, + error('Cell contents reference from a non-cell array object.'); +case {'()'}, + opt = struct(opt); + t = subsref(opt,subs(1)); + if length(subs)>1 + c = {}; + for i=1:numel(t), + ti = class(t(i),'nifti'); + ti = rec(ti,subs(2:end)); + c = {c{:}, ti{:}}; + end; + else + c = {class(t,'nifti')}; + end; + +otherwise + error('This should not happen.'); +end; diff --git a/spm/nii_for_spm2/spm_conv_vol.mexa64 b/spm/nii_for_spm2/spm_conv_vol.mexa64 new file mode 100644 index 0000000000000000000000000000000000000000..6a426b06bbaa1f785c1e5f6b29c3b1078eddf749 GIT binary patch literal 265719 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⁢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޿mb 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 literal 0 HcmV?d00001 diff --git a/spm/nii_for_spm2/spm_conv_vol.mexmac b/spm/nii_for_spm2/spm_conv_vol.mexmac new file mode 100755 index 0000000000000000000000000000000000000000..d9787bd2a2c5fe2628583345bf977f88fccf2553 GIT binary patch literal 300876 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

  • WIgN_o0?_5dkQXQ55M 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�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 v*`$-pg32Ftf)&Xb-gM^$1O5 zhMn_Wj;j-mh3)c|*H;@7gnm~T@5XVI699|qgP*_;YW&}9&j$wpeXIG;nQeM76XlxjgHk$_Q5$?`5DeI}Re;|ju%pqOpUE$K9o=Ou=pC-=C zhvjf0$!zX=>ME05XBsW^dU*eBr?FP)yIoCR_Mi~&3_m_d+G^U%+CL*RybX7)mramP zH2gpE)IiXo0E$%yHsE{SLC1OS;CtuNqO0Zdc85wTCWC$XvS2VU6l_I-)K!)tSoGvC z4rgXx8FYZTR^>R@iroJA)c!-G0YdlppyGcjJ7Bd=J$QOLr0Lw&4UF!Hf(-5`Df72> zWh}2BG40 + ee(:,2)=e(:,ibar); + ee(:,1)=0; + elseif orient<0 + ee(:,1)=e(:,ibar); + ee(:,2)=0; + elseif isnan(orient) + ee(:,1)=(1-sign(y(:,ibar))).*e(:,ibar); + ee(:,2)=(1+sign(y(:,ibar))).*e(:,ibar); + else + ee(:,1)= e(:,ibar); + ee(:,2)=-e(:,ibar); + + end + + try + if verLessThan('matlab', '7') % str2num(version('-release'))<=13 + h=errorbar(xe,y(:,ibar), ee(:,1), ee(:,2) ,'.k'); + else + h=errorbar('v6', xe,y(:,ibar),ee(:,1), ee(:,2) ,'.k'); + end + catch + h=errorbar('v6', xe,y(:,ibar),ee(:,1), ee(:,2) ,'.k'); + end + delete(h(2)); + + + % set(h(2), 'marker', 'none') + he(ibar)=h(1); + c=get(hb(ibar), 'FaceColor'); + if isnumeric(c) + set(he(ibar), 'Color', c); + else + c=get(hb(ibar), 'EdgeColor'); + if isnumeric(c) + set(he(ibar), 'Color', c); + end + end +end +hold off +% p=get(gca, 'Children'); +% set(gca, 'Children', p([end-1 end 1:end-2])); +% flipud(get(gca, 'Children'))) +if nargout > 0 + handles=[hb,he]; +end \ No newline at end of file diff --git a/barstem.m b/barstem.m new file mode 100644 index 0000000..5246c71 --- /dev/null +++ b/barstem.m @@ -0,0 +1,84 @@ +function h = barstem(x,y,z,bw) +%BARSTEM - 3D bar version of stem3 +% [] = barstem(x,y,z,barwidth) +% +% Example +% >> h=barstem(rand(15,1), rand(1,15), randn(1,15)); +% >> shading interp; set(h, 'CData', get(h, 'ZData'),'EdgeColor', 'k') +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2009 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2009-03-23 Creation +% +% ----------------------------- Script History --------------------------------- +[n] = numel(y); + + +if numel(x) ~= numel(y) + if numel(x)==1 || numel(y)==1 + x = x(:,ones(1,numel(y))); + y = y(:,ones(1,numel(x))); + end +end + +% Remove y values to 0 where y is NaN; +k = find(isnan(y)); +if ~isempty(k), y(k) = 0; end + +x=x(:)'; +y=y(:)'; +z=z(:)'; + +if nargin<4 + bw = [ min(diff(sort(x))) min(diff(sort(y))) ]; + bw = triu(sqrt((meshgrid(x)-meshgrid(x)').^2 + (meshgrid(y)-meshgrid(y)').^2 ),1); + bw(bw==0)=Inf; + bw=min(min(bw))*sqrt(1/2)/2; + %bw = bw*.95; + +end + +if numel(bw)==1 + bw=[bw bw]; +end + +zz = zeros(6*4,n); +yy = zz; +xx = zz; + +zz([6 7 10 11],:) = repmat(z, 4,1); +xx = kron(ones(6,1), kron([x-bw(1) ; x+bw(1) ], [1;1])); +yy = kron([y-bw(2) ; y+bw(2) ; y-bw(2)],ones(8,1)); +i = [1 4 13 16 17 20 21 22 23 24 ]; +zz(i,:) = nan; +yy(i,:) = nan; +xx(i,:) = nan; + +cc = repmat(z,24,1); + + +zz = reshape(zz, 4, [])'; +yy = reshape(yy, 4, [])'; +xx = reshape(xx, 4, [])'; + +cc = reshape(cc, 4, [])'; + + +edgec = get(gcf,'defaultaxesxcolor'); +facec = 'flat'; +h=[]; +h = [surf(... + 'xdata',xx,... + 'ydata',yy, ... + 'zdata',zz,... + 'CData',cc,... + 'FaceColor',facec,... + 'EdgeColor',edgec,... + 'tag',mfilename)]; \ No newline at end of file diff --git a/barweb.m b/barweb.m new file mode 100644 index 0000000..f7210fc --- /dev/null +++ b/barweb.m @@ -0,0 +1,170 @@ +function handles = barweb(barvalues, errors, width, groupnames, bw_title, bw_xlabel, bw_ylabel, bw_colormap, gridstatus, bw_legend) + +% +% Usage: handles = barweb(barvalues, errors, width, groupnames, bw_title, bw_xlabel, bw_ylabel, bw_colormap, gridstatus, bw_legend) +% +% Ex: handles = barweb(my_barvalues, my_errors, [], [], [], [], [], bone, [], bw_legend) +% +% barweb is the m-by-n matrix of barvalues to be plotted. +% barweb calls the MATLAB bar function and plots m groups of n bars using the width and bw_colormap parameters. +% If you want all the bars to be the same color, then set bw_colormap equal to the RBG matrix value ie. (bw_colormap = [1 0 0] for all red bars) +% barweb then calls the MATLAB errorbar function to draw barvalues with error bars of length error. +% groupnames is an m-length cellstr vector of groupnames (i.e. groupnames = {'group 1'; 'group 2'}). For no groupnames, enter [] or {} +% The errors matrix is of the same form of the barvalues matrix, namely m group of n errors. +% Gridstatus is either 'x','xy', 'y', or 'none' for no grid. +% No legend will be shown if the legend paramter is not provided +% +% The following default values are used if parameters are left out or skipped by using []. +% width = 1 (0 < width < 1; widths greater than 1 will produce overlapping bars) +% groupnames = '1', '2', ... number_of_groups +% bw_title, bw_xlabel, bw_ylabel = [] +% bw_color_map = jet +% gridstatus = 'none' +% bw_legend = [] +% +% A list of handles are returned so that the user can change the properties of the plot +% handles.curr_axis: handle to current axis +% handles.bars: handle to bar plot +% handles.errors: a vector of handles to the error plots, with each handle corresponding to a column in the error matrix +% handles.title: handle to plot title +% handles.xlabel: handle to xlabel +% handles.ylabel: handle to ylabel +% handles.legend: handle to legend +% handles.ca: handle to current axis +% +% +% See the MATLAB functions bar and errorbar for more information +% +% Author: Bolu Ajiboye +% Created: October 18, 2005 (ver 1.0) +% Updated: Dec 07, 2006 (ver 2.1) + +% Get function arguments +if nargin < 2 + error('Must have at least the first two arguments: barweb(barvalues, errors, width, groupnames, bw_title, bw_xlabel, bw_ylabel, bw_colormap, gridstatus, bw_legend)'); +elseif nargin == 2 + width = 1; + groupnames = 1:size(barvalues,1); + bw_title = []; + bw_xlabel = []; + bw_ylabel = []; + bw_colormap = jet; + gridstatus = 'none'; + bw_legend = []; +elseif nargin == 3 + groupnames = 1:size(barvalues,1); + bw_title = []; + bw_xlabel = []; + bw_ylabel = []; + bw_colormap = jet; + gridstatus = 'none'; + bw_legend = []; +elseif nargin == 4 + bw_title = []; + bw_xlabel = []; + bw_ylabel = []; + bw_colormap = jet; + gridstatus = 'none'; + bw_legend = []; +elseif nargin == 5 + bw_xlabel = []; + bw_ylabel = []; + bw_colormap = jet; + gridstatus = 'none'; + bw_legend = []; +elseif nargin == 6 + bw_ylabel = []; + bw_colormap = jet; + gridstatus = 'none'; + bw_legend = []; +elseif nargin == 7 + bw_colormap = jet; + gridstatus = 'none'; + bw_legend = []; +elseif nargin == 8 + gridstatus = 'none'; + bw_legend = []; +elseif nargin == 9 + bw_legend = []; +end + +change_axis = 0; + +if size(barvalues,1) ~= size(errors,1) || size(barvalues,2) ~= size(errors,2) + error('barvalues and errors matrix must be of same dimension'); +else + if size(barvalues,2) == 1 + barvalues = barvalues'; + errors = errors'; + end + if size(barvalues,1) == 1 + barvalues = [barvalues; zeros(1,length(barvalues))]; + errors = [errors; zeros(1,size(barvalues,2))]; + change_axis = 1; + end + numgroups = size(barvalues, 1); % number of groups + numbars = size(barvalues, 2); % number of bars in a group + if isempty(width) + width = 1; + end + + % Plot bars and errors + handles.bars = bar(barvalues, width); hold on + if length(bw_colormap) + colormap(bw_colormap); + else + colormap(jet); + end + groupwidth = min(0.8, numbars/(numbars+1.5)); + for i = 1:numbars + x = (1:numgroups) - groupwidth/2 + (2*i-1) * groupwidth / (2*numbars); + handles.errors(i) = errorbar(x, barvalues(:,i), errors(:,i), 'k', 'linestyle', 'none'); + end + set(gcf, 'color', 'white') + + if ~isempty(bw_title) + handles.title = title(bw_title, 'fontsize',14); + else + handles.title = []; + end + if ~isempty(bw_xlabel) + handles.xlabel =xlabel(bw_xlabel, 'fontsize',12); + else + handles.xlabel = []; + end + if ~isempty(bw_ylabel) + handles.ylabel =ylabel(bw_ylabel, 'fontsize',12); + else + handles.ylabel = []; + end + + set(gca, 'xticklabel', groupnames, 'box', 'off', 'ticklength', [0 0], 'fontsize', 12, 'xtick',1:numgroups); + if isequal(gridstatus, 'x') + set(gca,'xgrid','on'); + set(gca,'ygrid','off'); + elseif isequal(gridstatus, 'y') + set(gca,'xgrid','off'); + set(gca,'ygrid','on'); + elseif isequal(gridstatus, 'xy') + set(gca,'xgrid','on'); + set(gca,'ygrid','on'); + else + set(gca,'xgrid','off'); + set(gca,'ygrid','off'); + end + + xlim([0.5 numgroups-change_axis+0.5]); + + if ~isempty(bw_legend) + handles.legend = legend(bw_legend, 'location', 'best', 'fontsize',12); + legend boxoff; + else + handles.legend = []; + end + + handles.ca = gca; + + hold off +end + +return \ No newline at end of file diff --git a/baseline_correction.m b/baseline_correction.m new file mode 100644 index 0000000..63d3ba8 --- /dev/null +++ b/baseline_correction.m @@ -0,0 +1,33 @@ +function [F]=baseline_correction(F,dc_offsetb,dc_offsete,Time,TimeDim) +%baseline_correction - DC offset, etc. +% [F]=baseline_correction(F,dc_offset_begin,dc_offset_end, TimeVector,TimeDim) +% +% TimeDim designate the dimension on which to perform the baseline +% correction. +% NB: if TimeDim=2 (or unset), F is assumed to be N channel x T samples x... +% (BrainStorm convention) + + +if nargin<5 + TimeDim=2; +end +F=permute(F, [TimeDim 1:TimeDim-1 TimeDim+1:ndims(F)]); +sF=size(F); +if nargin<4 + Time=1:sF(1); +end + +if dc_offsetb < Time(1) + fprintf(2,['WARNING: Time parameter inferior to first valid' ... + ' latency (%f), setting it to %f\n'], dc_offsetb, Time(1)) +end + +if dc_offsete > Time(end) + fprintf(2,'WARNING: Time parameter superior to the last valid latency (%f), setting it to %f', dc_offsete, Time(end)); +end +dc_offsetb=findclosest(dc_offsetb, Time); +dc_offsete=findclosest(dc_offsete, Time); + +mF=repmat(mean(F(dc_offsetb:dc_offsete,:),1), [sF(1), 1]); +F=F-reshape(mF,sF); +F=ipermute(F, [TimeDim 1:TimeDim-1 TimeDim+1:ndims(F)]); \ No newline at end of file diff --git a/beamformerkernel.m b/beamformerkernel.m new file mode 100644 index 0000000..b689bba --- /dev/null +++ b/beamformerkernel.m @@ -0,0 +1,25 @@ +function = beamformerkernel(input,varargin) +%BEAMFORMERKERNEL - One line description goes here. +% [] = beamformerkernel(input) +% +% Example +% >> beamformerkernel +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2006 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2006-02-04 Creation +% +% ----------------------------- Script History --------------------------------- + + + bst_message_window('Calculating the spatial filter. . .'); + spatialFilter = Kernel'*Cm_inv; + ImagingKernel= (spatialFilter) ./ repmat(source_power_inv', 1,size(spatialFilter,2)); diff --git a/between.m b/between.m new file mode 100644 index 0000000..6b8a387 --- /dev/null +++ b/between.m @@ -0,0 +1,41 @@ +function tf = between(x,ab,mode) +%BETWEEN - One line description goes here. +% +% [TF] = between(x,[a b]) is TRUE if and only if x is between a and b or +% x is equal to any of the two, independently whether a <= b or a >= b +% +% [TF] = between(x,[a b],'strict') is TRUE if and only if x is between a and +% b but not equal to any of the two (again whether a>b or b> between(5, [12,0]) returns TRUE (1) +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2008 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2008-10-10 Creation +% +% ----------------------------- Script History --------------------------------- +if nargin<2 + error([mfilename '() requires 3 arguments']); +end + +a = min(ab(:)); +b = max(ab(:)); + +if nargin<3 + mode=[]; +end +if isequal(mode, 'strict') + tf= (a')+18; pauth1=pauth1(1); + pauth2 = strfind(rem(pauth1:min(end,(pauth1+500))),'')-1; pauth2=pauth2(1); + authstring = lower(rem(pauth1:(pauth1+pauth2-1))); % list of authors of the paper + authstring = strrep(authstring,'',''); + authstring = strrep(authstring,'',''); + authstring = strrep(authstring,'…','...'); + + % check that the required name is indeed in the author list. + paperok=0; + pos=strfind(authstring,name); + if length(pos), + pos=pos(1); + paperok=1; + if pos>1, + % check for wrong initials (eg, 'ga einstein' should not + % match for 'a einstein') + pl=authstring(pos-1); + if ((pl>='a')&&(pl<='z'))||(pl=='-'), + paperok=0; + end; + end; + if pos<(length(authstring)-length(name)), + % check for wrong 'suffix' (eg, 'einstein-joliot' should not + % match for 'einstein') + nl=authstring(pos+length(name)); + if ((nl>='a')&&(nl<='z'))||(nl=='-'), + paperok=0; + end; + end; + end; + + if paperok, % if the required name is indeed in the author list + ncit = ncit+1; + ncitinthispage = ncitinthispage +1; + p=strfind(rem,'Cited by ')+9; p=p(1); + substr=rem(p:(p+5)); + pend=strfind(substr,'<'); pend=pend(1); + ncited(ncit)=str2double(substr(1:(pend-1))); + rem=rem((p+2):end); + if any(strncmpi(varargin,'verb',1)) + disp(['#' num2str(ncit) ': (' num2str(ncited(ncit)) '), ' authstring]); + end; + else + if any(strncmpi(varargin,'verb',1)) + disp(['rejected: ' authstring]); + end; + p=strfind(rem,'Cited by ')+9; p=p(1); + rem=rem((p+2):end); + end; + end; + if any(strncmpi(varargin,'verb',1)) + disp(' '); + end; + if ncit==0, + seenextpage=0; + else + if ((ncited(ncit)<2)||(~length(findstr(rem,'Next')))), + seenextpage=0; + end; + end; +end; % while seenextpage + +if length(ncited), + % sort the list (it should be sorted, but sometimes GS produces unsorted results) + ncited=sort(ncited); ncited=ncited(ncit:-1:1); + + % computes the H-factor: + h=sum(ncited>=(1:ncit)); + + % plot the 'Cited by' number: + if any(strncmpi(varargin,'plot',1)) + loglog(1:ncit,ncited,'.-',1:ncit,1:ncit,'--',h,h,'o'); + xlabel('Paper rank'); ylabel('Number of citations'); + title(['h-index plot for ''' name ''' (h=' num2str(h) ')']); + end; + + % some displays if no output argument: + if nargout==0, + disp(['Number of cited papers: ' num2str(length(ncited))]); + disp(['''Cited by'' list: ' num2str(ncited)]); + disp(['Total number of citations: ' num2str(sum(ncited))]); + disp(['h-index = ' num2str(h)]); + clear h; + end; +else + h=0; + if nargout==0, + disp('No result found'); + clear h; + end; +end; diff --git a/bibliography/pmid2bib.m b/bibliography/pmid2bib.m new file mode 100644 index 0000000..388fe61 --- /dev/null +++ b/bibliography/pmid2bib.m @@ -0,0 +1,16 @@ +function t=p2b(pmid) +if nargin<1 + pmid = 20070959; +end +if isnumeric(pmid) + pmid = sprintf('%d',pmid); +end +pubmed = @(pmid) urlread('http://www.ncbi.nlm.nih.gov/sites/entrez', 'post', { 'cmd' 'search' 'db' 'pubmed' 'term' sprintf('%s[pmid]',pmid) 'doptcmdl' 'medline' 'dispmax' '200' 'tool' 'resource'}) +t= pubmed(pmid); +t = strread(t, '%s', 'delimiter', '\n') +au=strrep(t(strmatch('AU', t)), 'AU - ', ''); +au = sprintf('%s%s.', sprintf('%s, ', au{1:end-1}), au{end}) +ti= regexprep(t{strmatch('TI', t)}, '.*- ', '') +so = regexprep(t{strmatch('SO', t)}, '.*- ', '') + +t=sprintf('%s %s. %s',au,ti,so) \ No newline at end of file diff --git a/bit.m b/bit.m new file mode 100644 index 0000000..a9fbbd6 --- /dev/null +++ b/bit.m @@ -0,0 +1,12 @@ +function [v]=bit(x,n,b) +% bit - get the n-th bit of a number x +% [v]=bit(x,n,b) +% b cannot yet be other than 2 +if nargin<3 + b=2; +elseif ~isequal(b,2) + error('Only base-2 bits available ') +end +v=rem(floor(x(:)*pow2(-(n))),2); +return + diff --git a/bitget2.m b/bitget2.m new file mode 100644 index 0000000..81000f3 --- /dev/null +++ b/bitget2.m @@ -0,0 +1,26 @@ +function C = bitget2(A,BITS) +%BITGET2 - Get bit values (also from arrays) +% [C] = bitget2(A,BITS) returns a N-by-B array +% where N = numel(A) and B=numel(BITS) +% +% Example +% >> bitget2([1 5 3 15], [1:3]) +% +% See also: bitget + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2009 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2009-03-31 Creation +% +% ----------------------------- Script History --------------------------------- + +A=A(:); +for i=1:numel(A) + C(i,:)=bitget(A(i),BITS); +end \ No newline at end of file diff --git a/blackguis.m b/blackguis.m new file mode 100644 index 0000000..bf43d45 --- /dev/null +++ b/blackguis.m @@ -0,0 +1,15 @@ +function [ output_args ] = blackguis( hf ) +%blackguis - Make the (Figure and Axes) black +if nargin<1 + hf=gcf; +end +for ihf=1:length(hf) + + set(hf(ihf), 'color', 'k') + ha=get(hf(ihf), 'children') + set(ha, 'color', 'k') + set(ha, 'Xcolor', 'w') + set(ha, 'Ycolor', 'w') + set(ha, 'Zcolor', 'w') + +end \ No newline at end of file diff --git a/bootstrap.m b/bootstrap.m new file mode 100644 index 0000000..f597a65 --- /dev/null +++ b/bootstrap.m @@ -0,0 +1,47 @@ +function [biais, variance, fboot] = bootstrap(f, X, B, P1, P2, P3) +%BOOTSTRAP Bootstrap estimates of bias and variance. +% +% [BIAS VAR Fboot] = BOOTSTRAP('f', X, B) gives the following elements +% calculated by the bootstrap method using B replications: +% - bias of f(X), +% - variance of f(X), +% - corrected f(X). +% The data in X must be organised in rows (see function CORRCOEF) +% The bias formula is the standard one (not .632). +% +% [BIAS VAR Fboot] = BOOTSTRAP('f', X, B, opt1, opt2, opt3) allows to +% pass up to 3 additional arguments to 'f'. +% +% BOOTSTRAP needs the AVEVAR function. +% (c) 1997, C. Goutte. +% See also: JACKKNIFE. + +if (nargin < 3) | (nargin > 6) + error('BOOTSTRAP: wrong number of arguments.') ; +end + +[N P] = size(X) ; +if (N < 2) + error('BOOTSTRAP: not enough samples.') +end +evalstr = [f, '(X_i'] ; +for i = 1:(nargin - 3) + evalstr = [evalstr, ',P', int2str(i)] ; +end +evalstr = [evalstr, ')'] ; + +f_i = zeros(B,1) ; +X_i = X ; +fhat = eval(evalstr) ; + +for i = 1:B + idx_i = ceil(rand(1,N)*N) ; +% disp(idx_i) + X_i = X(idx_i, :) ; + f_i(i) = eval(evalstr) ; +end +[fboot, variance] = avevar(f_i) ; +biais = (fboot - fhat) ; +fboot = 2 * fhat - fboot ; + +% (c) 16/05/97, CG. diff --git a/brainstorm/bst_lighting.m b/brainstorm/bst_lighting.m new file mode 100644 index 0000000..6d50e90 --- /dev/null +++ b/brainstorm/bst_lighting.m @@ -0,0 +1,81 @@ +function []=bst_lighting(varargin) +% bst_lighting - nice viewing parameters for 3D surfaces + +Referential='CTF'; % not: 'AIMS' nor 'SPM' + +% Process inputs (or no input!) & link to handles +[hp,ha,hf]=findTessellationHandles(varargin{:}); + +if not(isempty(hp)) + verts=get(hp,'Vertices'); + % [k,k]=sort(max(verts)-min(verts)); + % if isequal(k,[3 2 1]) + + % If the frontal pole is lower (in Z) than the vertex + if mean(verts(:,3)) > median(verts(:,3)) + Referential='MNI'; + elseif verts(imax(verts(:,1)),3) < max(verts(:,3)) + Referential='CTF'; + else + Referential='AIMS'; + end +end + +% Lighting +hl=findobj(ha, 'type', 'light'); +delete(hl) +clear hl; +switch Referential + case 'CTF' + % hl(1) = light('Position', [1 1 1 ]); + % hl(2) = light('Position', [1 -1 1 ]); + % hl(3) = light('Position', [-1 0 0 ]); + % hl(4) = light('Position', [0 1 -.2 ]); + % hl(5) = light('Position', [0 -1 -.2 ]); + hl(1) = camlight(30,30 ); + hl(2) = camlight(120,30); + hl(3) = camlight(90,-60); + hl(3) = camlight(0, 90); + hl(4) = camlight(-65, -20, 'infinite'); % light('Position', [0 -.2 1]); + hl(5) = camlight(-115, -20, 'infinite'); % light('Position', [0 -.2 -1]); + set(hl,'color',[.8 1 1]/3/.7); % mute the intensity of the lights + + case 'AIMS' + hl(1) = light('Position', [1 1 -1 ]); + hl(2) = light('Position', [1 -1 -1 ]); + hl(3) = light('Position', [-1 0 0 ]); + hl(4) = light('Position', [0 1 .2 ]); + hl(5) = light('Position', [0 -1 .2 ]); + set(ha, 'Zdir', 'reverse') + % hl(1) = camlight(-20,30); + % hl(2) = camlight(20,30); + % hl(3) = camlight(-20,-30); + % hl(4) = light('Position', [0 -.2 1]); + % hl(5) = light('Position', [0 -.2 -1]); + set(hl,'color',[.8 1 1]/3/.7); % mute the intensity of the lights + + case 'MNI' + hl(1) = light('Position', [ 1 1 1 ]); + hl(2) = light('Position', [-1 1 1 ]); + hl(3) = light('Position', [ 0 -1 0 ]); + hl(4) = light('Position', [ 1 0 -.2 ]); + hl(5) = light('Position', [-1 0 -.2 ]); + % hl(1) = camlight(-20,30); + % hl(2) = camlight(20,30); + % hl(3) = camlight(-20,-30); + % hl(4) = light('Position', [0 -.2 1]); + % hl(5) = light('Position', [0 -.2 -1]); + set(hl,'color',[.8 1 1]/3/.7); % mute the intensity of the lights + +end + +% Viewing parameters +% if isequal(Referential, 'CTF') & mean(verts(:,3)) < 0 +% set(ha, 'Zdir', 'reverse') +% end +% view(2) +% view([0 180 0]) +% view(140,20); +axis off +axis image +% rotate3daxes(ha,'on') diff --git a/brainstorm/bst_struct2fv.m b/brainstorm/bst_struct2fv.m new file mode 100755 index 0000000..fc80d2b --- /dev/null +++ b/brainstorm/bst_struct2fv.m @@ -0,0 +1,35 @@ +function fv = bst_surf2fv(s) +%BST_SURF2FV - One line description goes here. +% [fv] = bst_surf2fv(s) +% will remove extra fields of s to keep only the "Vertices" and "Faces" ones +% Note: Upper case/lower case discrepancies are ignored. +% +% Example +% >> bst_surf2fv +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2009 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2009-09-18 Creation +% +% ----------------------------- Script History --------------------------------- + +fv=[]; +for f={'faces', 'vertices'} + if isfield(s, f{1}) + fv=setfield(fv, f{1}, getfield(s, f{1})); + elseif isfield(s, lower(f{1})) + fv=setfield(fv, f{1}, getfield(s, lower(f{1}))); + elseif isfield(s, [upper(f{1}(1)) upper(f{1}(2:end))]) + fv=setfield(fv, f{1}, getfield(s, [upper(f{1}(1)) upper(f{1}(2:end))])); + else + warning(sprintf('No field %f in the structure', f{1})) + end +end \ No newline at end of file diff --git a/brainstorm/bst_surface2fv.m b/brainstorm/bst_surface2fv.m new file mode 100755 index 0000000..f254861 --- /dev/null +++ b/brainstorm/bst_surface2fv.m @@ -0,0 +1,44 @@ +function fv = bst_surface2fv(s) +%BST_SURFACE2FV - One line description goes here. +% [fv] = bst_surface2fv(s) +% will remove extra fields of s to keep only the "Vertices" and "Faces" ones +% Note: Upper case/lower case discrepancies are ignored. +% +% Example +% >> bst_surface2fv +% +% See also: + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2009 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2009-09-18 Creation +% +% ----------------------------- Script History --------------------------------- + +fv=[]; +FIELDS={'faces', 'vertices'}; +for iField=1:2 + f=FIELDS{iField}; + if ~isfield(s, f) + f = [upper(f(1)) f(2:end)]; + if ~isfield(s,f) + f=NaN; + end + end + if isnan(f) + warning(sprintf('No field %s in the structure', f)) + else + fv=setfield(fv,FIELDS{iField}, getfield(s,f)); + if iField==2 + if size(fv.vertices,2) ~= 3 && size(fv.vertices,1) == 3 + fv.vertices=fv.vertices'; + end + end + end +end \ No newline at end of file diff --git a/brainstorm/bst_trimSurface.m b/brainstorm/bst_trimSurface.m new file mode 100644 index 0000000..6359d7b --- /dev/null +++ b/brainstorm/bst_trimSurface.m @@ -0,0 +1,41 @@ +function []=bst_trimSurface(h,xyz) +if (nargin<2) + xyz=h; + h=findTessellationHandles +end + +iSurf=1; +TessInfo(iSurf)=get(h); +TessInfo(iSurf).trimXYZ=[xyz(1) xyz(2) xyz(3)]; +TessInfo(iSurf).alpha=.5; +vertices = get(h,'vertices'); +FaceVertexAlphaData = get(h,'FaceVertexAlphaData'); + +if isempty(FaceVertexAlphaData) + FaceVertexAlphaData = TessInfo(iSurf).alpha*ones(size(vertices,1),1); +end + + +iNoModif = []; +for iCoord = 1:3 + vertx = vertices(:,iCoord)'; + vertx = vertx-mean(vertx); + vertx = vertx/max(abs(vertx)); + if TessInfo(iSurf).trimXYZ(iCoord) > 0 + iNoModif = [iNoModif,find(vertx < TessInfo(iSurf).trimXYZ(iCoord))]; + elseif TessInfo(iSurf).trimXYZ(iCoord) < 0 + iNoModif = [iNoModif,find(vertx > TessInfo(iSurf).trimXYZ(iCoord))]; + else + %doNothing + end + +end + +if isempty(iNoModif) + set(h,'alphadatamapping','scaled','FaceVertexAlphaData',TessInfo(iSurf).alpha*ones(size(vertices,1),1),'FaceAlpha',TessInfo(iSurf).alpha,... + 'backfacelighting','lit') +else + FaceVertexAlphaData(unique(iNoModif)) = TessInfo(iSurf).alpha; + FaceVertexAlphaData(setdiff(1:end,iNoModif)) = 0; + set(h,'alphadatamapping','none','FaceVertexAlphaData',FaceVertexAlphaData,'FaceAlpha','interp','backfacelighting','unlit') +end \ No newline at end of file diff --git a/brainstorm/carto_cortex.m b/brainstorm/carto_cortex.m new file mode 100644 index 0000000..2741d82 --- /dev/null +++ b/brainstorm/carto_cortex.m @@ -0,0 +1,153 @@ +function [h]=carto_cortex(faces,varargin) +% carto_cortex - Plot cortical surface +% +% [h]=carto_cortex(FV) +% carto_cortex(faces,vertices) +% carto_cortex(FV, values) +% carto_cortex(faces,vertices,values) +% carto_cortex(FV,values, 'Option', OptionValue,...) +% INPUTS: +% FV : is a struct with .faces and .vertices, see PATCH +% OUPUT: +% h : a handle to the patch + +options={}; +if nargin>1 + dataname=inputname(2); +else + dataname=''; +end +data=[]; +if nargin==1 && ~isstruct(faces) && isvector(faces) + data=faces; + dataname=inputname(1); + global faces + p.faces=faces; + global vertices + p.vertices=vertices; +elseif isstruct(faces) && ... + ((isfield(faces, 'faces') | isfield(faces, 'Faces')) && (isfield(faces, 'vertices') | isfield(faces, 'Vertices'))) + p=faces; + + if ~isfield(p, 'vertices') && isfield(p, 'Vertices') + if iscell(p.Vertices) + if sum(cellfun('isempty',p.Vertices))==1 + p.vertices=p.Vertices{~cellfun('isempty',Vertices)}; + elseif nargin>2 && ~isempty(data) + p.vertices=p.Vertices{cellfun('prodofsize',Vertices)/3==length(data)}; + end + else + p.vertices=p.Vertices; + end + end + + if ~isfield(p, 'faces') && isfield(p, 'Faces') + if iscell(p.Faces) + if sum(cellfun('isempty',p.Faces))==1 + p.faces=p.Faces{~cellfun('isempty',Faces)}; + elseif ~isempty(data) + p.faces=p.Faces{cellfun('prodofsize',p.Vertices)/3==length(data)}; + end + else + p.faces=p.Faces; + end + end + + + +elseif iscell(faces) + p.vertices=faces{1}; + p.faces=faces{2}; + +else + if nargin>2 + dataname=inputname(3); + end + if max(faces(:))==max(size(varargin{1})) + p.faces=faces; + p.vertices=varargin{1}; + elseif max(varargin{1}(:))==max(size(faces)) + p.vertices=faces; + p.faces=varargin{1}; + faces = []; + elseif max(faces(:))==max(size(varargin{1}))-1 + p.faces=faces+1; + p.vertices=varargin{1}; + end + varargin(1)=[]; +end + + +if(size(p.vertices,2) > 3), % assume transposed + p.vertices = p.vertices'; % if the assumption is wrong, will crash below anyway +end + +if nargin>=2 & ~isempty(varargin) + if isnumeric(varargin{1}) + p.data=varargin{1}; + % if not(all(size(data) == [size(vertices, 1) 1])) + % error('Data should be the same length as vertex number') + % return + % end + varargin(1)=[]; + end + options=varargin(1:end); +end + +if ~isfield(p, 'data') + p.data=[]; +end + +if isempty(get(0, 'CurrentFigure')) | isempty(get(gcf, 'CurrentAxes')) + NewAxes=1; +else + NewAxes=0; +end + +h = patch('Faces',p.faces,'Vertices',p.vertices); + +if ~isempty(p.data) + if prod(size(p.data))==size(p.vertices, 1) + p.data=p.data(:); + elseif size(p.data,1)==size(p.vertices, 1) && size(p.data, 2)>3 +v error('Data are not a vector: you might want to use view_MNEresults instead!') + return + end + + set(h,'FaceVertexCData',p.data); + if size(p.data,2)==1 + set(h,'FaceColor','interp'); + end +else + set(h,'facecolor',rand(1,3)); +end +NewAxes = NewAxes || isequal(get(gca, 'NextPlot'), 'replace'); +set(h, 'FaceLighting','Gouraud','EdgeLighting','none','EdgeColor','none'); +set(h, 'Tag', 'Cortex'); + +% Material of the surface +material(h, [0.3, 0.8, .10, 10, .50]) +p.h=h; +set(h, 'UserData', p); + +if ~isempty(options) + set(h, options{:}); +end + +if NewAxes + try + view(140,20); + bst_lighting(h); + rotate3d on + catch + set(gcf, 'renderer', 'zbuffer') + view(140,20); + bst_lighting(h); + rotate3d on + end + if ~isempty(dataname) + title(dataname) + end + colorbar + meeg_menu(h); +end diff --git a/brainstorm/carto_flatcap.m b/brainstorm/carto_flatcap.m new file mode 100644 index 0000000..70cf64d --- /dev/null +++ b/brainstorm/carto_flatcap.m @@ -0,0 +1,209 @@ +function [hs,hp,hc,hl,hn] = carto_flatcap(Channel, data, Contours,DrawLines,DrawNose,BackgroundColor) +%CARTO_DISC - Display M/EEG scalp data on a 2D projection of sensors +% +% [hs,hp] = carto_flatcap(Channel, data) +% [hs,hp,hc,hl,hn]= carto_flatcap(Channel, data,Contours,DrawLines,DrawNose) +% +% Contours: either the number of contours or their levels (use [X X] for +% single value) + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2006 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2006-02-21 Creation +% +% ----------------------------- Script History --------------------------------- +if nargin<6 + BackgroundColor = [ 1 1 1 ]; +end +if nargin<5 + DrawNose=1; +end +if nargin<4 + DrawLines=0; +end +if nargin<3 + Contours=6; +end +if ~exist('data','var') + data=[]; +end + +if ~isvector(data) && prod(size(data)) == max(size(data)) + data=data(:); +end +if ~isvector(data) + error('Data must be 1-D') +end + +% I'm tricky, i draw the line in white on white background! +set(gcf, 'color',BackgroundColor) + +if isempty(data) + data(1:length(Channel)) = 0; +end +data=data(:); +%isens=1:min(size(data,1),length(Channel)); +isens=1:size(data,1); +if isfield(Channel, 'Loc') + for i=1:length(isens) + sensloc(i,1:3)=Channel(isens(i)).Loc(:,1)'; + end + Channel=Channel(isens); +else + sensloc=Channel(isens,:); + Channel=[]; +end +CONTOURNUM = 0; + + + +nans=find(isnan(data)); +sensloc(nans,:)=[]; +data(nans)=[]; +nmes = size(sensloc,1); +[TH,PHI,R] = cart2sph(sensloc(:,1),sensloc(:,2),sensloc(:,3)-max(sensloc(:,3))); +R2 = R./cos(PHI).^.2; +[Y,X] = pol2cart(TH,R2); +tri = delaunay(Y,X); +% h=patch('faces',tri,'vertices',[Y,X,0.*X],'CData',cdata,'edgecolor','none','FaceColor','interp'); +xmax=max(X); +xmin=min(X); +ymax=max(Y); +ymin=min(Y); +rmax2=max(xmax,abs(xmin)).^2 + max(ymax,abs(ymin)).^2; + +%% Surface +cla +hp=patch('faces',tri,'vertices',[Y,X,0.*X],'FaceVertexCData',data,'edgecolor','none','FaceColor','interp'); +axis image +hold on + +set(hp,'ButtonDownFcn','subarray(get(gcbo,''CData''),imax(-edist(mean(get(get(gcbo,''Parent''),''CurrentPoint'')),nd2array(cat(3,get(gcbo,''Xdata''),get(gcbo,''Ydata''),get(gcbo,''Zdata'')),3)'')),Inf)') + +%% UI menu +cmenu = uicontextmenu; +set(hp, 'uiContextMenu', cmenu) +uimenu(cmenu,'Label', 'Show markers of sensors', 'callback', 'set(findobj(gcbf,''tag'',''sensor''),''Visible'',''on'')') +uimenu(cmenu,'Label', 'Hide markers of sensors', 'callback', 'set(findobj(gcbf,''tag'',''sensor''),''Visible'',''off'')') +uimenu(cmenu,'Label', 'Show name of sensors', 'callback', 'set(findobj(gcbf,''tag'',''sensorlabel''),''Visible'',''on'')') +uimenu(cmenu,'Label', 'Hide name of sensors', 'callback', 'set(findobj(gcbf,''tag'',''sensorlabel''),''Visible'',''off'')') +uimenu(cmenu,'Label', 'Sensors value (sorted)', 'callback', 'for i=findobj(gcbf,''tag'',''sensor''); end;') +%% Sensors +for i=1:length(data) + hs(i)=plot3(Y(i),X(i),min(abs(sensloc(:,3))),'o'); + set(hs(i),'markerfacecolor',[.2 1 .2],'markersize',3,'Tag','sensor'); + if isfield(Channel, 'Name') + ch=setdiff(1:length(Channel), nans); + %set(hs(i), 'ButtonDownFcn', sprintf('fprintf(''%s (%d)'');try;fprintf('' = '');end;fprintf(''\\n'');', Channel(ch(i)).Name,i)); + set(hs(i), 'ButtonDownFcn', 'fprintf(''%s (%d)'', getappdata(gcbo,''Label''), getappdata(gcbo,''Index''));try;fprintf('' = '');end;fprintf(''\n'');'); + setappdata(hs(i), 'Label', Channel(ch(i)).Name); + setappdata(hs(i), 'Index', i); + h = text(Y(i),X(i), Channel(ch(i)).Name, 'HorizontalAlignment', 'center', 'VerticalAlignment', 'top'); + set(h, 'tag', 'sensorlabel', 'visible', 'off') + end +end + + +%% Nose & Ears +if DrawNose + rmax=max(X); + headl = 0:2*pi/100:2*pi; + basex = .18*rmax; + tip = rmax*1.15; + base = rmax-.004*rmax/.1; + HLINEWIDTH = 2; + HCOLOR = [1 1 1]- get(gcf, 'color'); + z_level = -min(abs(sensloc(:,3))); + hold on + hn=plot3([base;tip;base],[.18*rmax;0;-.18*rmax],[base;tip;base].*0+z_level); % plot nose + LE = [ + 0.0216 0.1455 + 0.0368 0.1523 + 0.0368 0.1565 + 0.0334 0.1622 + 0.0271 0.1633 + 0.0123 0.1565 + 0.0040 0.1535 + -0.0063 0.1548 + -0.0164 0.1582 + -0.0249 0.1607 + -0.0294 0.1575 + -0.0308 0.1514 + -0.0247 0.1441 + -0.0139 0.1422 + -0.0029 0.1422 + ]./.15; + LE(end+1,:)=LE(1,:); + LE = LE*rmax; + hn(2) = plot3(LE(:,1), LE(:,2), LE(:,1).*0+z_level); + hn(3) = plot3(LE(:,1), -LE(:,2), LE(:,1).*0+z_level); + set(hn, 'Color',HCOLOR,'LineWidth',HLINEWIDTH) +end + + +%% Contours +if length(Contours)>0 + GRID_SCALE = 100; % 67 in original + xi = linspace(xmin,xmax,GRID_SCALE); % x-axis description (row vector) + yi = linspace(xmin,ymax,GRID_SCALE); % y-axis description (row vector) + INTERPOLATION = 'cubic'%'v4'; + [Xi,Yi,Zi] = griddata(Y,X,data,yi',xi,INTERPOLATION); % Interpolate data + % remove points which fall out of the head plot + for i=1:length(Zi) + if rmax2<(Xi(i)^2+Yi(i)^2) + Zi(i)=NaN; + end + end + + if any(Contours<0) || any(rem(Contours, 1)~=0) || length(Contours)>1 || isequal(Contours,0) + CONTOURLEVELS = Contours; + CONTOURNUM = length(CONTOURLEVELS); + else + CONTOURNUM = Contours; + CONTOURLEVELS=max(abs(data))/(CONTOURNUM/2+1); + if mod(CONTOURNUM,2) + CONTOURLEVELS=CONTOURLEVELS.*[ceil(-CONTOURNUM/2):floor(CONTOURNUM/2)]; + else + CONTOURLEVELS=CONTOURLEVELS.*[setdiff((-CONTOURNUM/2):(CONTOURNUM/2),0)]; + end + end + for i=1:CONTOURNUM + [c,hc(i)]=contour(Xi,Yi,Zi,CONTOURLEVELS(i),'w'); + end + set(hc, 'tag', [mfilename ':contour'] , 'color', get(gcf, 'color')) + colorbar +end + +%% Colors +colorbar + +view(-90,90); +hold off +axis tight +axis off +% Believe it or not the scribe code for colorbar is buggy when used with +% contour plots in the picture... See line 228, in scribe.colorbar +caxis(max(abs(data))*[-1 1]) +ha=colorbar; +% KND 2009-11-12 changed child=get(ha,'children'); +child=findobj(get(ha,'children'), 'Type', 'image'); +set(ha, 'YLim', caxis); +set(child, 'ydata', caxis); +axes(ha) +if DrawLines & CONTOURNUM>0 + hl=line((get(ha,'Xlim'))'*ones(1,CONTOURNUM),ones(2,1)*get(hc, 'LevelList')); + set(hl,'LineStyle', '-') + set(hl,'Color', get(hc(1),'color')) +end +axes(get(hp, 'Parent')) +hold off +return + + + diff --git a/brainstorm/carto_flatdisc.m b/brainstorm/carto_flatdisc.m new file mode 100644 index 0000000..9eaaaa9 --- /dev/null +++ b/brainstorm/carto_flatdisc.m @@ -0,0 +1,346 @@ +function h = carto_flatdisc(Channel, data, Contours, DrawLines,DrawNose,BackgroundColor) +%CARTO_DISC - Display M/EEG scalp data on a 2D disc +% [] = carto_flatdisc(input) +% +%Syntax: +% [sensors] = carto_flatdisc(Channel,data) +% [sensors] = carto_flatdisc(Channel,data,Contours,DrawLines,DrawNose,BackgroundColor) +% + + +% Author: K. N'Diaye (kndiaye01yahoo.fr) +% Copyright (C) 2006 +% This program is free software; you can redistribute it and/or modify it +% under the terms of the GNU General Public License as published by the +% Free Software Foundation; either version 2 of the License, or (at your +% option) any later version: http://www.gnu.org/copyleft/gpl.html +% +% ----------------------------- Script History --------------------------------- +% KND 2006-02-21 Creation +% KND 2010-03-25 +contours +% ----------------------------- Script History --------------------------------- + +if nargin<6 + BackgroundColor = [ 1 1 1 ]; +end +if nargin<5 + DrawNose=1; +end +if nargin<4 + DrawLines=0; +end +if nargin<3 + Contours=6; +end +% Guess that inputs may have been inverted: +if isfield(data, 'Loc') || (~isvector(data) && isvector(Channel)) + tmp = data; + data = Channel; + Channel = tmp; + clear tmp; +end + +if prod(size(data))>max(size(data)) && ~isvector(data) + error('Data must be 1-D') +end + +% I'm tricky, i draw the line in white on white background! +set(gcf, 'color',BackgroundColor) + +data=data(:); +isens=1:min(size(data,1),length(Channel)); +if isfield(Channel, 'Loc') + for i=1:length(isens) + sensloc(i,1:3)=Channel(isens(i)).Loc(:,1)'; + end + Channel=Channel(isens); +else + sensloc=Channel(isens,:); + Channel=[]; +end +CONTOURNUM = 0; + + +%% Some Geometry... + +% [TH,PHI,R] = cart2sph(sensloc(:,1),sensloc(:,2),sensloc(:,3)-max(sensloc(:,3))); +% R2 = R./cos(PHI).^.2; +% [Y,X] = pol2cart(TH,R2); +% tri = delaunay(Y,X); +% % h=patch('faces',tri,'vertices',[Y,X,0.*X],'CData',cdata,'edgecolor','none','FaceColor','interp'); +% + +center=[0 0 0]; + +nans=find(isnan(data)); +sensloc(nans,:)=[]; +data(nans)=[]; +nmes = size(sensloc,1); + + +[teta,phi,R] = cart2sph(sensloc(:,1)-center(1),sensloc(:,2)-center(2),sensloc(:,3)-center(3)); +R = mean(R); +phimin = min(phi); +phimax = max(phi); +tetamin = min(teta); +tetamax = max(teta); +[x y z] = sph2cart(teta,phi,R*ones(size(phi))); +[xt yt zt] = sph2cart(teta,phi,R*1.0*ones(size(phi))); +N = 25; +phi = (phimin:(pi/2 - phimin)/N:pi/2)'*ones(1,N+1); +teta= (0: 2*pi/N : 2*pi )'*ones(1,N+1); +xs = cos(teta').*cos(phi); +ys = sin(teta').*cos(phi); +zs = sin(phi); +xs=xs(:)*R; +ys=ys(:)*R; +zs=zs(:)*R; + +[TH,PHI,R] = cart2sph(xs,ys,zs-max(zs)); +R2 = R./cos(PHI).^.2; +[Y,X] = pol2cart(TH,R2); + +%% Surface +tri = delaunay(Y,X); +cdata = griddata(xt,yt,data,X,Y,'invdist'); +hp=patch('faces',tri,'vertices',[Y,X,0.*X],'CData',cdata,'edgecolor','none','FaceColor','interp'); + +axis image +hold on + +set(hp,'ButtonDownFcn','subarray(get(gcbo,''CData''),imax(-edist(mean(get(get(gcbo,''Parent''),''CurrentPoint'')),nd2array(cat(3,get(gcbo,''Xdata''),get(gcbo,''Ydata''),get(gcbo,''Zdata'')),3)'')),Inf)') + +[TH,PHI,R] = cart2sph(sensloc(:,1),sensloc(:,2),sensloc(:,3)-max(sensloc(:,3))); +R2 = R./cos(PHI).^.2; +[Y,X] = pol2cart(TH,R2); +tri = delaunay(Y,X); +xmax=max(X); +xmin=min(X); +ymax=max(Y); +ymin=min(Y); +rmax2=max(xmax,abs(xmin)).^2 + max(ymax,abs(ymin)).^2; + + +%% UI menu +cmenu = uicontextmenu; +set(hp, 'uiContextMenu', cmenu) +uimenu(cmenu,'Label', 'Show markers of sensors', 'callback', 'set(findobj(gcbf,''tag'',''sensor''),''Visible'',''on'')') +uimenu(cmenu,'Label', 'Hide markers of sensors', 'callback', 'set(findobj(gcbf,''tag'',''sensor''),''Visible'',''off'')') +uimenu(cmenu,'Label', 'Show name of sensors', 'callback', 'set(findobj(gcbf,''tag'',''sensorlabel''),''Visible'',''on'')') +uimenu(cmenu,'Label', 'Hide name of sensors', 'callback', 'set(findobj(gcbf,''tag'',''sensorlabel''),''Visible'',''off'')') +uimenu(cmenu,'Label', 'Sensors value (sorted)', 'callback', 'for i=findobj(gcbf,''tag'',''sensor''); end;') +%% Sensors +for i=1:length(data) + hs(i)=plot3(Y(i),X(i),min(abs(sensloc(:,3))),'o'); + set(hs(i),'markerfacecolor',[.2 1 .2],'markersize',3,'Tag','sensor'); + if isfield(Channel, 'Name') + ch=setdiff(1:length(Channel), nans); + %set(hs(i), 'ButtonDownFcn', sprintf('fprintf(''%s (%d)'');try;fprintf('' = '');end;fprintf(''\\n'');', Channel(ch(i)).Name,i)); + set(hs(i), 'ButtonDownFcn', 'fprintf(''%s (%d)'', getappdata(gcbo,''Label''), getappdata(gcbo,''Index''));try;fprintf('' = '');end;fprintf(''\n'');'); + setappdata(hs(i), 'Label', Channel(ch(i)).Name); + setappdata(hs(i), 'Index', i); + h = text(Y(i),X(i), Channel(ch(i)).Name, 'HorizontalAlignment', 'center', 'VerticalAlignment', 'top'); + set(h, 'tag', 'sensorlabel', 'visible', 'off') + end +end + + +%% Nose & Ears +if DrawNose + rmax=max(X); + headl = 0:2*pi/100:2*pi; + basex = .18*rmax; + tip = rmax*1.15; + base = rmax-.004*rmax/.1; + HLINEWIDTH = 2; + HCOLOR = [1 1 1]- get(gcf, 'color'); + z_level = -min(abs(sensloc(:,3))); + hold on + hn=plot3([base;tip;base],[.18*rmax;0;-.18*rmax],[base;tip;base].*0+z_level); % plot nose + LE = [ + 0.0216 0.1455 + 0.0368 0.1523 + 0.0368 0.1565 + 0.0334 0.1622 + 0.0271 0.1633 + 0.0123 0.1565 + 0.0040 0.1535 + -0.0063 0.1548 + -0.0164 0.1582 + -0.0249 0.1607 + -0.0294 0.1575 + -0.0308 0.1514 + -0.0247 0.1441 + -0.0139 0.1422 + -0.0029 0.1422 + ]./.15; + LE(end+1,:)=LE(1,:); + LE = LE*rmax; + hn(2) = plot3(LE(:,1), LE(:,2), LE(:,1).*0+z_level); + hn(3) = plot3(LE(:,1), -LE(:,2), LE(:,1).*0+z_level); + set(hn, 'Color',HCOLOR,'LineWidth',HLINEWIDTH) +end + + +%% Contours +if length(Contours)>0 + GRID_SCALE = 100; % 67 in original + xi = linspace(xmin,xmax,GRID_SCALE); % x-axis description (row vector) + yi = linspace(xmin,ymax,GRID_SCALE); % y-axis description (row vector) + INTERPOLATION = 'v4'; + [Xi,Yi,Zi] = griddata(Y,X,data,yi',xi,INTERPOLATION); % Interpolate data + % remove points which fall out of the head plot + for i=1:length(Zi) + if rmax2<(Xi(i)^2+Yi(i)^2) + Zi(i)=NaN; + i + end + end + + if length(Contours)>1 || isequal(Contours,0) + CONTOURLEVELS = Contours; + CONTOURNUM = length(CONTOURLEVELS); + else + CONTOURNUM = Contours; + CONTOURLEVELS=max(abs(data))/(CONTOURNUM/2+1); + if mod(CONTOURNUM,2) + CONTOURLEVELS=CONTOURLEVELS.*[ceil(-CONTOURNUM/2):floor(CONTOURNUM/2)]; + else + CONTOURLEVELS=CONTOURLEVELS.*[setdiff((-CONTOURNUM/2):(CONTOURNUM/2),0)]; + end + end + [c,hc]=contour(Xi,Yi,Zi,CONTOURLEVELS,'w'); + set(hc, 'tag', mfilename, 'color', get(gcf, 'color')) + colorbar + hold off +end + +%% Colors +colorbar + +view(-90,90); +hold off +axis tight +axis off +% Believe it or not the scribe code for colorbar is buggy when used with +% contour plots in the picture... See line 228, in scribe.colorbar +caxis(max(abs(data))*[-1 1]) +ha=colorbar; +% KND 2009-11-12 changed child=get(ha,'children'); +child=findobj(get(ha,'children'), 'Type', 'image'); +set(ha, 'YLim', caxis); +set(child, 'ydata', caxis); +axes(ha) +if DrawLines & CONTOURNUM>0 + hl=line((get(ha,'Xlim'))'*ones(1,CONTOURNUM),ones(2,1)*get(hc, 'LevelList')); + set(hl,'LineStyle', '-') + set(hl,'Color', get(hc(1),'color')) +end +axes(get(hp, 'Parent')) +hold off + +return + + +%% + +isens=isens(:); +if nargin==2 + data=isens; + isens=1:size(data,1); +end +if size(data,1)~=length(isens) %& all(ismember(isens, 1:size(data,1))) + data=data(isens); +end +if isfield(Channel, 'Loc') + for i=1:length(isens) + sensloc(i,1:3)=Channel(isens(i)).Loc(:,1)'; + end + Channel=Channel(isens); +else + sensloc=Channel(isens,:); + Channel=[]; +end + +center=[0 0 0]; + + +nans=find(isnan(data)); +sensloc(nans,:)=[]; +data(nans)=[]; +nmes = size(sensloc,1); +% CTF xyz system is not classical +sensloc=sensloc(:,[2 1 3]); +[teta,phi,R] = cart2sph(sensloc(:,1)-center(1),sensloc(:,2)-center(2),sensloc(:,3)-center(3)); +R = mean(R); +phimin = min(phi); +phimax = max(phi); +tetamin = min(teta); +tetamax = max(teta); +[x y z] = sph2cart(teta,phi,R*ones(size(phi))); +[xt yt zt] = sph2cart(teta,phi,R*1.0*ones(size(phi))); +N = 25; +phi = (phimin:(pi/2 - phimin)/N:pi/2)'*ones(1,N+1); +teta= (0: 2*pi/N : 2*pi )'*ones(1,N+1); +xs = cos(teta').*cos(phi); +ys = sin(teta').*cos(phi); +zs = sin(phi); +xs=xs(:)*R; +ys=ys(:)*R; +zs=zs(:)*R; +[TH,PHI,R] = cart2sph(xs,ys,zs-max(zs)); +R2 = R./cos(PHI).^.2; +[Y,X] = pol2cart(TH,R2); + +tri = delaunay(Y,X); +cdata = griddata(xt,yt,data,X,Y,'invdist'); +h=patch('faces',tri,'vertices',[Y,X,0.*X],'CData',cdata,'edgecolor','none','FaceColor','interp'); + +return + +if OPTIONS.ShowContours % Add contour plot + n = sqrt(size(PlotHandles.TopoPlot(mod).Yo{iSet},1)); + PlotHandles.TopoPlot(mod).Yo{iSet} = reshape(PlotHandles.TopoPlot(mod).Yo{iSet},n,n); + PlotHandles.TopoPlot(mod).Xo{iSet} = reshape(PlotHandles.TopoPlot(mod).Xo{iSet},n,n); + [PlotHandles.TopoPlot(mod).Contour{iSet}.C,PlotHandles.TopoPlot(mod).Contour{iSet}.H] = contour(PlotHandles.TopoPlot(mod).Yo{iSet},... + PlotHandles.TopoPlot(mod).Xo{iSet},... + CData,'w-'); +end + + +return + +if datatype == 1 + CData = DisplayStruct.TopoPlot(k).Wmat{iSet} * Disp.TimeSeries{iSet}(data.Measures.Display.SelectedChannels{k},tindx); + CData = reshape(CData,sqrt(length(CData)),sqrt(length(CData))); + CData = CData * max(abs(Disp.TimeSeries{iSet}(data.Measures.Display.SelectedChannels{k},tindx)))/max(abs(CData(:))); % properly rescale to data amplitude +elseif datatype == 2 + F = data.Results.IndepTopo(:,iSet)*data.Results.TimeSeries(:,iSet)'; + CData = DisplayStruct.TopoPlot(k).Wmat{iSet} * F(:,tindx); + CData = reshape(CData,sqrt(length(CData)),sqrt(length(CData))); + CData = CData * max(abs(F(:,tindx)))/max(abs(CData(:))); % properly rescale to data amplitude +end + +set(DisplayStruct.TopoPlot(k).surf{iSet},'CData',CData) + +if data.Measures.Display.Flags.ShowContours %& ishandle(DisplayStruct.TopoPlot(k).Contour{iSet}.H) % Add contour plot + + axes(DisplayStruct.TopoPlot(k).axes{iSet}); + %hc=findobj(gcf,'type','hggroup');%DisplayStruct.TopoPlot(k).Contour{iSet}.H;%;(ishandle(DisplayStruct.TopoPlot(k).Contour{iSet}.H)); % Contour handle + try + delete(DisplayStruct.TopoPlot(k).Contour{iSet}.H); + waitfor(DisplayStruct.TopoPlot(k).Contour{iSet}.H); % Make sure the (deletion) work is done catch + [DisplayStruct.TopoPlot(k).Contour{iSet}.C,DisplayStruct.TopoPlot(k).Contour{iSet}.H] = contour(DisplayStruct.TopoPlot(k).Yo{iSet},DisplayStruct.TopoPlot(k).Xo{iSet},CData,'w-'); + catch % an error can occur when user keeps pressing the time slider; contour cannot follow at the required pace and finally that produces an error + % doing nothing while user is + % holding the mouse button is just + % ensuring that contours will be + % updated only when button is + % released and no error is shown in + % command window. + bst_message_window('wrap','Contour map cannot be refreshed at that pace; please release the mouse button and do several mouse clicks instead') + end + + drawnow + +end \ No newline at end of file diff --git a/brainstorm/cortex_menu.m b/brainstorm/cortex_menu.m new file mode 100644 index 0000000..20baf64 --- /dev/null +++ b/brainstorm/cortex_menu.m @@ -0,0 +1,473 @@ +function [varargout]= cortex_menu(action, varargin) +% cortex_menu - Menu related to cortical surface processing +% +% cortex_menu('action') with following possible actions: +% create +% vertconn_state(ho,hctx) +% compute_vertconn(hctx) +% smooth_surface(hctx,a, nIterations) +% trimsurface +% curvature +% extent_threshold(hctx,z) +% move_hemi_away(hctx) +% move_hemi_closer(hctx) +% open_hemi(hctx) +% move_hemi_closer(hctx) +% hide_hemi(hctx) + +if nargin==0 + action='create'; +end +try + varargout={eval(sprintf('action_%s(varargin{:});',action))}; +catch + eval(sprintf('action_%s(varargin{:});',action)); +end + +return + +function [hmenu]=action_create +delete(findobj(gcf, 'Tag', mfilename)) +hmenu = uimenu('Label','Surface processing','Tag',mfilename); +cback='o=findTessellationHandles; uhp=get(o, ''UserData''); if isempty(uhp); uhp.vertices=get(o, ''Vertices''); uhp.faces=get(o, ''Faces''); uhp.h=o; end;'; + +set(hmenu, 'Callback',[ ... + 'cortex_menu(''vertconn_state'', gcbf);'... + ]); + +t = uimenu('Parent',hmenu,... + 'Label','Vertex Connectivity',... + 'CallBack',[ ... + sprintf('%s(''%s'',gcbo);',mfilename,'compute_vertconn') + ]); + +t = uimenu('Parent',hmenu,... + 'Label','Smooth surface',... + 'CallBack',[ sprintf('%s(''%s'',gcbo);',mfilename,'smooth_surface') ]); + +t = uimenu('Parent',hmenu,... + 'Label','Adv. Smooth surface',... + 'CallBack',[ cback ... + 'vals=inputdlg({''Smoothing factor'',''Iterations''}, ''Smooting parameters'', 1 , {''.3'', ''20''});'... + sprintf('%s(''%s'',gcbo,str2num(vals{1}),str2num(vals{2}));',mfilename,'smooth_surface')... + 'clear uhp p vals;'... + ]); + +t = uimenu('Parent',hmenu,... + 'Label','Move hemispheres apart','Enable', 'on',... + 'CallBack',[ ... + sprintf('%s(''%s'',gcbo);',mfilename,'move_hemi_away') + ]); + +t = uimenu('Parent',hmenu,... + 'Label','Put closer hemispheres','Enable', 'on',... + 'CallBack',[ ... + sprintf('%s(''%s'',gcbo);',mfilename,'move_hemi_closer') + ]); + +t = uimenu('Parent',hmenu,... + 'Label','Open hemispheres','Enable', 'on',... + 'CallBack',[ ... + sprintf('%s(''%s'',gcbo);',mfilename,'open_hemi') + ]); + +t = uimenu('Parent',hmenu,... + 'Label','Back to original surface',... + 'CallBack',[ cback ... + sprintf('%s(''%s'',gcbo);',mfilename,'revert_surface') + ]); + +t = uimenu('Parent',hmenu,... + 'Label','Add surface curvature',... + 'CallBack',[ ... + 'cs=curvature_cortex(uhp,uhp.vertconn,.1,0);'... %'cs=blend_anatomy_data(cs, uhp.data);'... + 'set(uhp.h, ''FaceVertexCData'', cs);'... + 'clear cs uhp;'... + ],'Enable', 'off'); + + +t = uimenu('Parent',hmenu,... + 'Label','Trim surface',... + 'Separator', 'on', ... + 'CallBack',[ cback ... + 'trimsurface_gui(uhp.h);'... + 'clear vuhp;'... + ],'Enable', 'on'); + + + +t = uimenu('Parent',hmenu,... + 'Label','Hide/show hemispheres',... + 'Separator', 'on', ... + 'CallBack',[ ... + sprintf('%s(''%s'',gcbo);',mfilename,'hide_hemi') + ],'Enable', 'on'); + + +t = uimenu('Parent',hmenu,... + 'Label','Remove small clusters',... + 'Separator', 'on', ... + 'CallBack',[ sprintf('%s(''%s'',gcbo);',mfilename,'extent_threshold') ],'Enable', 'on'); + + + +return + +function []=action_revert_surface(ho,hctx) +if nargin<1 || ~isequal(get(hctx, 'type'), 'patch') + hctx=findTessellationHandles; +end +uhp=get(hctx, 'UserData'); +if isempty(uhp); return; end; +set(uhp.h, 'Vertices', uhp.vertices); + +function []=action_vertconn_state(ho,hctx) +if ~exist('tess_vertices_connectivity') + try + figure + addpath('~/mtoolbox/brainstorm3/toolbox/misc/') + addpath('/mtoolbox/brainstorm3/toolbox/misc/') + end + if ~exist('tess_vertices_connectivity') + errordlg({... + 'You need to add the path of the BrainStorm Toolbox' ... + 'E.g.:' ... + ' >> addpath(''C:/BrainStorm/Toolbox'')'}, 'No BrainStorm found') + return + end +end +set(findobj(gcbo, 'Label', 'Vertex Connectivity'), 'Checked', 'off'); +set(findobj(gcbo, 'Label', 'Vertex Connectivity'), 'Enable', 'off'); +set(findobj(gcbo, 'Label', 'Smooth surface'), 'Enable','off'); +set(findobj(gcbo, 'Label', 'Adv. Smooth surface'), 'Enable', 'off'); +if nargin<2 + hctx=findTessellationHandles; +end +uhp=get(hctx, 'UserData'); +if isempty(uhp); return; end; +set(findobj(gcbo, 'Label', 'Vertex Connectivity'), 'Enable', 'on'); +if isfield(uhp, 'vertconn'), + state='on'; +else + state='off'; +end; +set(findobj(gcbo, 'Label', 'Vertex Connectivity'), 'Checked', state); +set(findobj(gcbo, 'Label', 'Smooth surface'), 'Enable',state); +set(findobj(gcbo, 'Label', 'Adv. Smooth surface'), 'Enable', state); +clear uhp; + + +function []=action_compute_vertconn(hctx) +if nargin<1 || ~isequal(get(hctx, 'type'), 'patch') + hctx=findTessellationHandles; +end +uhp=get(hctx, 'UserData'); +if isempty(uhp); return; end; +uhp.vertconn=tess_vertices_connectivity(uhp); +set(uhp.h, 'UserData', uhp); +h=uhp.h +clear uhp; +action_vertconn_state(get(h, 'parent'), h); + + + +function action_smooth_surface(hctx,a, nIterations) +if nargin<1 || ~isequal(get(hctx, 'type'), 'patch') + hctx=findTessellationHandles; +end +if nargin<3 + nIterations=10; +end +if nargin<2 + a = .3; +end +uhp=get(hctx, 'UserData'); +if isempty(uhp); return; end; +p=handle2struct(uhp.h); +p=lowerfields(p.properties); +if ~isfield(uhp,'vertconn') + action_compute_vertconn(hctx) + uhp=get(hctx, 'UserData'); +end +[v]=tess_smooth(p.vertices',a, nIterations,uhp.vertconn); +set(uhp.h, 'Vertices', v'); + + +function [curvature,curvature_sigmoid]=compute_curvature(hctx,VertConn,sigmoid_const) +if nargin<3 + sigmoid_const=0.1; +end +FV=get(hctx); +normals=get(hctx,'VertexNormals'); +nVertices=size(normals,1); +%Make the normals unit norm +[nrm,normalsnrm]=colnorm(normals'); +%compute average angle on each vertex +curvature=zeros(nVertices,1); +curvature_sigmoid=zeros(nVertices,1); +for i=1:nVertices %for all vertices + nNeighbours=length(VertConn{i}); %number of neighbours + edgevector=FV.Vertices(VertConn{i},:)-repmat(FV.Vertices(i,:),nNeighbours,1); %vectors joining vertex with neighbours + [nrm,edgevector]=colnorm(edgevector'); + curvature(i)=mean(acos(normalsnrm(:,i)'*edgevector))-pi/2; + curvature_sigmoid(i)= 1./(1+exp(-curvature(i).*sigmoid_const))-0.5; +end + + + +function []=spatialextent(ho,hctx) +uhp=get(findTessellationHandles, 'UserData'); +if isempty(uhp); return; end; + + +function action_trimsurface +uhp=get(findTessellationHandles, 'UserData'); +trimsurface_gui(uhp.h) + +function action_curvature + + +function []=action_extent_threshold(hctx,z) +if nargin==0 || ~isequal(get(hctx, 'type'), 'patch') + hctx=findTessellationHandles; +end +uhp=get(hctx, 'UserData'); +if isempty(uhp); return; end; +c=colormap; +fv=get(uhp.h, 'FaceVertexCData'); +if prod(caxis)>=0 + fv(abs(fv)<=min(caxis))=0; +else + cb=c(ceil(length(c)/2),:); + cb=repmat(cb,length(c),1); + fv(abs(fv)=z); +fv(setdiff(1:length(fv),[clu{:}]))=0; +set(uhp.h, 'FaceVertexCData',fv) + +function []=action_move_hemi_away(hctx) +if nargin<1 || ~isequal(get(hctx, 'type'), 'patch') + hctx=findTessellationHandles; +end +uhp=get(hctx, 'UserData'); +if isempty(uhp); + uhp.h = hctx; + %return; +end; +v=get(uhp.h, 'Vertices'); +if size(v,1)==10774 || size(v,1)==12045 + fprintf('MNI4Yann cortex'); + v(1:5418,1)=v(1:5418,1)+1/2*mean(v(1:5418,1)); + v(5419:10774,1)=v(5419:10774,1)+1/2*mean(v(5419:10774,1)); + if size(v,1)==12045 + v(10774:end,3)=v(10774:end,3)-1/2*mean(v(10774:end,3)); + end +elseif size(v,1)==15028 + nR = 7509;% first right + fprintf('Brainstorm Default Subject\n'); + v(1:nR,2)=v(1:nR,2)+1/2*mean(v(1:nR,2)); + v(nR+1:end,2)=v(nR+1:end,2)+1/2*mean(v(nR+1:end,2)); + % Open with a given angle + % a= -75/360*2*pi; % in deg/rad + % t = exp(j*a)*(v(1:nR,2)+j*(v(1:nR,1)+.07)); + % v(1:nR,2) = real(t); + % v(1:nR,1) = imag(t)-.07; + % % +else + f=get(uhp.h, 'Faces'); + %try to guess which is the orthosagittal axis + m=median(v); + % it should be the one where as few faces as possible cross the + % median plane + for i=1:3 + left = v(:,i)m(i); + s(i) = sum(any(right(f),2) & any(left(f),2)); + end + [min_s,lr_dim] = min(s); + % Now we have it, we must found the position of the sagittal plane + fun = @(m) sum(any(reshape(v(f,lr_dim),size(f,1),3)>m,2)&any(reshape(v(f,lr_dim),size(f,1),3)m; + v(:,lr_dim)=v(:,lr_dim)+(max(v(:,lr_dim))-min(v(:,lr_dim)))/4*right; +end +set(uhp.h, 'Vertices', v); +axis image + +function []=action_move_hemi_closer(hctx) +if nargin<1 || ~isequal(get(hctx, 'type'), 'patch') + hctx=findTessellationHandles; +end +uhp=get(hctx, 'UserData'); +if isempty(uhp); return; end; +v=get(uhp.h, 'Vertices'); +if size(v,1)==10774 || size(v,1)==12045 + v(1:5418,1)=v(1:5418,1)-1/2*mean(v(1:5418,1)); + v(5419:10774,1)=v(5419:10774,1)-1/2*mean(v(5419:10774,1)); + if size(v,1)==12045 + v(10774:end,3)=v(10774:end,3)+1/2*mean(v(10774:end,3)); + end +elseif size(v,1)==15028 + nR = 7509; + fprintf('Brainstorm Default Subject'); + v(1:nR,2)=v(1:nR,2)-1/2*mean(v(1:nR,2)); + v(nR+1:end,2)=v(nR+1:end,2)-1/2*mean(v(nR+1:end,2)); + +else + v(:,1)=v(:,1)+10*(-1+2*(v(:,1)>0)); +end +set(uhp.h, 'Vertices', v); +axis image + +function []=action_open_hemi(hctx) +if nargin<1 || ~isequal(get(hctx, 'type'), 'patch') + hctx=findTessellationHandles; +end +uhp=get(hctx, 'UserData'); +if isempty(uhp); + uhp.h = hctx; + %return; +end; +v=get(uhp.h, 'Vertices'); +if size(v,1)==10774 || size(v,1)==12045 + fprintf('MNI4Yann cortex'); + v(1:5418,1)=v(1:5418,1)+1/2*mean(v(1:5418,1)); + v(5419:10774,1)=v(5419:10774,1)+1/2*mean(v(5419:10774,1)); + if size(v,1)==12045 + v(10774:end,3)=v(10774:end,3)-1/2*mean(v(10774:end,3)); + end +elseif size(v,1)==15028 + nR = 7509;% first right + fprintf('Brainstorm Default Subject\n'); + % v(1:nR,2)=v(1:nR,2)+1/2*mean(v(1:nR,2)); + %v(nR+1:end,2)=v(nR+1:end,2)+1/2*mean(v(nR+1:end,2)); + % Open with a given angle + a= -60/360*2*pi; % in deg/rad + t = exp(j*a)*(v(1:nR,2)+j*(v(1:nR,1)+.07)); + v(1:nR,2) = real(t); + v(1:nR,1) = imag(t)-.07; + % +else + f=get(uhp.h, 'Faces'); + %try to guess which is the orthosagittal axis + m=median(v); + % it should be the one where as few faces as possible cross the + % median plane + for i=1:3 + left = v(:,i)m(i); + s(i) = sum(any(right(f),2) & any(left(f),2)); + end + [min_s,lr_dim] = min(s); + % Now we have it, we must found the position of the sagittal plane + fun = @(m) sum(any(reshape(v(f,lr_dim),size(f,1),3)>m,2)&any(reshape(v(f,lr_dim),size(f,1),3)m; + v(:,lr_dim)=v(:,lr_dim)+(max(v(:,lr_dim))-min(v(:,lr_dim)))/4*right; +end +set(uhp.h, 'Vertices', v); +axis image + + + +function []=action_move_hemi_closer(hctx) +if nargin<1 || ~isequal(get(hctx, 'type'), 'patch') + hctx=findTessellationHandles; +end +uhp=get(hctx, 'UserData'); +if isempty(uhp); return; end; +v=get(uhp.h, 'Vertices'); +if size(v,1)==10774 || size(v,1)==12045 + v(1:5418,1)=v(1:5418,1)-1/2*mean(v(1:5418,1)); + v(5419:10774,1)=v(5419:10774,1)-1/2*mean(v(5419:10774,1)); + if size(v,1)==12045 + v(10774:end,3)=v(10774:end,3)+1/2*mean(v(10774:end,3)); + end +elseif size(v,1)==15028 + nR = 7509; + fprintf('Brainstorm Default Subject'); + v(1:nR,2)=v(1:nR,2)-1/2*mean(v(1:nR,2)); + v(nR+1:end,2)=v(nR+1:end,2)-1/2*mean(v(nR+1:end,2)); + +else + v(:,1)=v(:,1)+10*(-1+2*(v(:,1)>0)); +end +set(uhp.h, 'Vertices', v); +axis image + +function []=action_hide_hemi(hctx) +if nargin<1 || ~isequal(get(hctx, 'type'), 'patch') + hctx=findTessellationHandles; +end +uhp=get(hctx, 'UserData'); +if isempty(uhp); + uhp.h = hctx; + %return; +end; +v=get(uhp.h, 'Vertices'); +if size(v,1)==10774 || size(v,1)==12045 + fprintf('MNI4Yann cortex'); + LR = [ones(1,5418) 2*ones(1,5356) ]; + if size(v,1)==12045 + % cerebellum + LR = [LR zeros(1,1272)]; + end +elseif size(v,1)==15028 + LR = [ones(1,7509) 2*ones(1,7519) ]; +else + f=get(uhp.h, 'Faces'); + %try to guess which is the orthosagittal axis + m=median(v); + % it should be the one where as few faces as possible cross the + % median plane + for i=1:3 + left = v(:,i)m(i); + s(i) = sum(any(right(f),2) & any(left(f),2)); + end + [min_s,lr_dim] = min(s); + % Now we have it, we must found the position of the sagittal plane + fun = @(m) sum(any(reshape(v(f,lr_dim),size(f,1),3)>m,2)&any(reshape(v(f,lr_dim),size(f,1),3)m; + v(:,lr_dim)=v(:,lr_dim)+(max(v(:,lr_dim))-min(v(:,lr_dim)))/4*right; +end +alpha = get(uhp.h,'FaceVertexAlphaData'); +if alpha(find(LR==1,1)) == 0 + alpha(LR==1) = 1; + alpha(LR==2) = 0; +elseif alpha(find(LR==2,1)) == 0 + alpha(LR==1) = 1; + alpha(LR==2) = 1; +else + alpha(LR==1) = 0; + alpha(LR==2) = 1; +end +if all(alpha==1) + set(uhp.h,'alphadatamapping','none',... + 'FaceVertexAlphaData',alpha,.... + 'FaceAlpha','flat',... + 'backfacelighting','unlit') +else + set(uhp.h,'alphadatamapping','scaled',... + 'FaceVertexAlphaData',alpha,.... + 'FaceAlpha','interp',... + 'backfacelighting','lit') +end +%set(uhp.h, 'VerticesAlphaDapa', v); + + diff --git a/brainstorm/ctf_readClassFile.m b/brainstorm/ctf_readClassFile.m new file mode 100644 index 0000000..e69de29 diff --git a/brainstorm/ds2brainstorm.m b/brainstorm/ds2brainstorm.m new file mode 100644 index 0000000..05a01b9 --- /dev/null +++ b/brainstorm/ds2brainstorm.m @@ -0,0 +1,1258 @@ +function [F,Channel,imegsens,ieegsens,iothersens,irefsens,grad_order_no,no_trials,filter,Time,RunTitle] = ds2brainstorm(ds_directory,VERBOSE,READRES,CHANNELS, TIME, NO_TRIALS, DCOffset); +%DS2BRAINSTORM Convert a DS CTF dataset into BrainStorm format +% function [F,Channel,imegsens,ieegsens,iothersens,irefsens,grad_order_no,no_trials,filter,Time,RunTitle] = ds2brainstorm(ds_directory,VERBOSE,READRES,CHANNELS,TIME, NO_TRIALS, DCOffset); +% [F,Channel,imegsens,ieegsens,iothersens,irefsens,grad_order_no,no_trials,filter,Time, RunTitle] +% ... = ds2brainstorm(ds_directory,VERBOSE,READRES,CHANNELS,TIME, NO_TRIALS, DCOffset); +% Reads CTF Systems Inc. Data and Resource file formats (4.1 MEG Data Format) +% +% INPUTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% DS_DIRECTORY: full path to the .ds directory containing the orginal data set +% +% VERBOSE = 1 : Toggle VERBOSE mode ON +% -> Activate screen display +% -> Save channel information in channel_file.txt +% -> Save coefficient information in coef_file.txt +% +% VERBOSE = 0 : Toggle VERBOSE mode OFF +% +%---> The rest of the input parameters are optionnal (more specific to data block extraction) +% +% READRES = 0: Do not read the original .res4 file assuming it has been read previously. +% Read _res4.mat instead. If _res4.mat does not exist, automatic switch to READRES = 1. +% READRES = 1: Read the original .res4 file and writes out a file called **_res4.mat in the .ds folder, +% which is a shortened version of the CTF resources file .res4. +% MEG/EEG data are also read from the .meg4 file ONLY IF other input arguments are specified +% READRES = 2: Same as READRES = 1, without the long reading of coefficient information +% but reads ONLY a short version of data information and returns them in F as a structure. +% MEG/EEG data are nor read from the .meg4 file. +% This is useful for display in importdata_gui for instance. +% READRES = 3: Do not read .meg4 file (useful when data need to be kept in native file format when importing into BrainStorm) +% F is then a string indicating the name of the corresponding .ds folder. +% +% +% Set READRES to 0 when you want to extract another block of data after you have already read one block with READRES = 1. +% +% If READRES is left blank, the whole data are read - ATTENTION when data set is large - possible 'out of memory' issues +% +% If the optional fields below are left blank, ds2brainstorm only reads the res4 file and generates a *_res4.mat file in the current .ds folder +% with all useful information regarding EEG, MEG channels etc. for subsequent data extraction. +% +% CHANNELS: the indices of the channels to be extracted as in IEEGSENS, IMEGSENS or IREFSENS +% If you don't know what are the indices of these channels, run ds2brainstorm with READRES = 1 first. +% +% TIME: the time window to be extracted in seconds within a trial; ie a subset of Time, a vector saved in **_res4.mat (see above) +% +% NO_TRIALS: a vector containing the number(s) of the trial(s) to be extracted. +% +% DCOffset: A flag indicating whether DC offset needs to be removed from every MEG channel. +% Acceptable values: +% 0 - DC offset is not removed from MEG channels (DEFAULT) +% 1 - DC offset is removed from MEG channels based on the entire trial length +% 2 - DC offset is removed from MEG channels based on pretigger time period +% +% Example; ds2brainstorm('tipouic.ds',1,0,[10:30],[-.5 3],2) +% .. extracts in VERBOSE mode from tipouic.ds, assuming tipouic_res4.mat was already created during a previous call to ds2brainstorm with READRES = 1 +% channels 10 to 30 (could be MEG or other -> check IEEGSENS and IMEGSENS first), between -0.5sec and 3sec, in trial 2. +% +% % +% OUTPUTS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% CHANNEL - MEG,EEG, REFERENCE and OTHER channel information +% CHANNEL is a cell array of structures following the BrainStorm data format. +% F - Data Matrix: One sensor per row; one time sample per column / alternatively a structure containing all data information +% (resources contained in .res4 file). +% IMEGSENS - Indices of the MEG data rows in F +% IEEGSENS - Indices of the EEG data rows in F +% IOTHERSENS - Indices of the OTHER data rows (not including reference coils) in F +% IREFSENS - Indices of the REFERENCE data rows in F +% GRAD_ORDER_NO - Order of the gradient correction to be taken into account in the forward model only +% NO_TRIALS - Number of trials in the .ds folder. +% FILTER - An array of structures; each structure containing information related to each filter +% TIME - A vector containing the time instants at every data sample +% RUNTITLE - A character array labelling the current run. +% +% Please refer to CTF Systems Inc. Technical Note #1 for details on DataSet file formats + +% ---------------------- 15-Dec-2003 16:09:01 ----------------------- +% --------- Automatically Generated Comments Block Using AUTO_COMMENTS --------- +% +% CATEGORY: Data Processing +% +% Alphabetical list of external functions (non-Matlab): +% toolbox\bst_message_window.m +% toolbox\findclosest.m +% toolbox\good_channel.m +% +% Subfunctions in this file, in order of occurrence in file: +% PrintCoilPos(channel_file, CoilRec_ext,nCoils) ; +% save_sensor_locs(Channel,SensorLocFile) +% +% At Check-in: $Author: Mosher $ $Revision: 23 $ $Date: 12/15/03 2:48p $ +% +% Overall BrainStorm authors are: +% ** Dr. John C. Mosher, Los Alamos National Laboratory +% ** Dr. Sylvain Baillet, CNRS Cognitive Neuroscience & Brain Imaging Laboratory +% +% Copyright (c) 2003 BrainStorm MMIII by the University of Southern California +% Principal Investigator: +% ** Professor Richard M. Leahy, USC Signal & Image Processing Institute +% +% Source code may not be distributed in original or altered form. +% For license and copyright notices, type 'bst_splashscreen' in Matlab, +% or see BrainStorm website at http://neuroimage.usc.edu, +% or email Professor Richard M. Leahy at leahy@sipi.usc.edu +% for further information and permissions. +% ------------------------ 15-Dec-2003 16:09:01 ----------------------- + + +% /---Script Authors-------------------------------------\ +% | | +% | *** Sylvain Baillet, Ph.D. | +% | Cognitive Neuroscience & Brain Imaging Laboratory | +% | CNRS UPR640 - LENA | +% | Hopital de la Salpetriere, Paris, France | +% | sylvain.baillet@chups.jussieu.fr | +% | | +% | *** John C. Mosher, Ph.D. | +% | Design Technology Group | +% | Los Alamos National Laboratory | +% | Los Alamos, New Mexico, USA | +% | mosher@lanl.gov | +% | | +% | -> with contributions from | +% | Line Garnero, Ph.D. and Antoine Ducorps, Ph.D. | +% | MEG Center & | +% | Cognitive Neuroscience | +% | & Brain Imaging Laboratory - CNRS | +% | La Salpetriere Hospital, Paris - France | +% \------------------------------------------------------/ + +% Script History ---------------------------------------------------------------------------------------- +% +% 11/28/00 - SB : Updated to read the 3rd-order gradient information +% 03/09/01 - SB : Data block extraction +% 15/11/01 - SB : Extraction of the ADC Input channels in iothersens, combined with the STIM channel +% 19/11/01 - SB : Save iothersens indices in the _res4.mat file +% JCM 06-Jun-2002 : made waitbar update less often, autocomments +% SB 26-Jul-2002 : Added the READRES = 2 condition to read basic file information for display importdata_gui. +% .............. Removed creation of channel_file.txt. +% SB 27-Jul-2002 : Minor alterations in the READRES conditions - see help header. +% SB 28-Jul-2002 : When CHANNELS is emptu, all channels, including reference channels are read. +% .............. Changed name of sensor location result file (for visualization in MRITool or 3DViewer) +% SB 10-Sep-2002 : Minor layout editing. +% SB 07-Nov-2002 : Display all progress information into bst_message_window +% SB 15-Nov-2002 : EEG channel .loc field is now only 3x1 (was 3x2, second column filled with zeros). +% Added DCOffset input argurment to remove DC Offset from MEG channels. +% SB 20-Nov-2002 : Assign "MEG REF" type to MEG reference channels for fast retrieval with GOOD_CHANNEL +% Removed .Gcoef and .i*sens fields form Channel structure. +% SB 23-Dec-2002 : Time now returns only the time window on which data has been extracted (not the whole original time span of data) +% SB 21-Jan-2003 : Fixed 1-sample bug for total Time length +% SB 23-Jan-2003 : Added READRES = 3 option. +% SB 06-Feb-2003 : Fixed bug when accessing data with multiple trials specificied in NO_TRIALS +% From the second NO_TRIALS entry on, data was always read from beguinning of trial +% not from the first entry specified in TIME. +% JCM 10-Dec-2003 : Added additional blank SensorNames to account for type "17" in the 275 channel arrays. Moved around +% redundant definitions of SensorNames. Thanks to Fred Carver NIMH for finding this. +% JCM 11-Dec-2003 : Updating SensorNames based on email from CTF, +% plus correcting 0 indexing scheme +% KND 01-Jun-2004 : Added a quick & dirty test for virtual MEG channels +% KND 02-Jun-2004 : Modified Check input arguments, so that 4 arguments is ok [173-183] +% Bug: TIME was defined as a 1xN array (=Time) instead of a [BeginLatency EndLatency] +% -------------------------------------------------------------------------------------------------------- + +% Check input arguments + +if nargin == 2 + READRES = 1; + DCOffset = 0; +elseif nargin==5 % No Marker file and marker range are defined + MARKER_FILE = 0; + MARKER_RANGE = []; + DCOffset = 0; +% KND: +elseif nargin==4 + % if READRES ~= 1 | READRES ~= 2 + % READRES = 2; % Read data information only. + % end + MARKER_FILE = 0; + MARKER_RANGE = []; + TIME = []; + NO_TRIALS = []; + DCOffset = 0; +% -- KND +elseif nargin==3 + % Read only file information. + % Check that READRES has acceptable values - if not, the whole file could be read + % which could end-up in loading very large files into Matlab format + % and cause memory crash. + if READRES ~= 1 | READRES ~= 2 + READRES = 2; % Read data information only. + end + MARKER_FILE = 0; + MARKER_RANGE = []; + CHANNELS = []; + TIME = []; + NO_TRIALS = []; + DCOffset = 0; +end + + +% SensorNames ={'Ref Magnetometer','Ref Gradiometer' ,'' , '' , '' ,'MEG Sensor','' , '', '','EEG Sensor','ADC Input','Stimulation input',''}; % CHEAT - added the 13th '' ofor testing purposes +% 10 December 2003 Fred Carver at NIMH found that 275 channel arrays had a type 17. Added extra blanks to account for. +% 11 December 2003, based on email directly from Dough McKenzie at CTF: +% Comments on types, email of 10 December 2003: +%The new types are +% +% SAM Sensor -- Synthetic electrode channel from SAM +% Virtual Channel -- Linear combination of sensor channels +% System Clock -- 32 bit counter incremented at 12,000 +% ticks per second for Omega 2000 system +% ( new systems like NIMH ) +% and incremented at 12,500 ticks per +% second for older systems. +% +% Video Time -- A 32 bit encoded value that can be recorded from +% a SONY Video time output from a video player or +% recorder +% +% The format is 'hhmmssff' where each byte contains +% hh hours in decimal ( 0-- +% mm minutes ( 0 ~ 59 ) +% ss seconds ( 0 ~ 59 ) +% ff frames ( 0 ~ 29 ) +% +% ADC Input Voltage -- Used if the ADC channels are measuring +% voltage. == 11 if measuring current +% +%NOTE: There will be some additional channels types defined +% mid next year. +SensorNames ={... + 'Ref Magnetometer',... % Sensor Type Index of 0 + 'Ref Gradiometer' ,... % Index of 1 + '' ,... % 2 + '' ,... % 3 + '' ,... % 4 + 'MEG Sensor',... % 5 + '' ,... % 6 + '',... % 7 + '',... % 8 + 'EEG Sensor',... % 9 + 'ADC Input Current',...% 10 ADC Input Current (Amps) + 'Stimulation input',...% 11 + 'Video Time',... % 12 + '',... % 13 + '',... % 14 + 'SAM Sensor',... % 15 + 'Virtual Channel',... % 16 + 'System Clock',... % 17 System Time Ref + 'ADC Input Voltage',...% 18 ADC Input Voltage (Volts) + }; + + +%% Checking files +cd(ds_directory) +[path,rootname] = fileparts(ds_directory); +meg4file = [rootname,'.meg4']; +rec4file = [rootname,'.res4']; +if isdir('BrainStorm') % Was a BrainStorm directory created in .ds folder ? Yes if ds2brainstorm called from importdata + res4_mat = fullfile('BrainStorm',[rootname,'_res4.mat']); +else + res4_mat = [rootname,'_res4.mat']; +end + +if ~exist(rec4file,'file') | ~exist(meg4file,'file') + errordlg([rec4file ' or ' meg4file ' missing']) + return +end + +% If .mat version of the .res4 does not exist while we supposed it did - read it. +if ~exist(res4_mat,'file') & READRES == 0 + % Force reading of original .res4 + READRES = 1; +end + +if VERBOSE + bst_message_window(['Working in...',ds_directory ]) +end + + +%********************************************* + +if READRES > 0 % Read the .res4 file + + %% Define constants + MAX_COILS = 8; + MAX_BALANCING = 50; + SENSOR_LABEL = 31; + MAX_AVERAGE_BINS = 8; + + %***** nfSetUp **** Data Structure + gSetUp = struct('no_samples',[],'no_channels',[],'sample_rate',[],... + 'epoch_time',[],'no_trials',[],'preTrigPts',[],'no_trials_done',[],'no_trials_display',[],... + 'save_trials',[],'primaryTrigger',[],'secondaryTrigger',[],'triggerPolarityMask',[],... + 'trigger_mode',[],'accept_reject_Flag',[],'run_time_display',[],'zero_Head_Flag',[],... + 'artifact_mode',[]); + + nfSetUp = struct('nf_run_name','','nf_run_title','','nf_instruments','','nf_collectdescriptor','',... + 'nf_subject_id','','nf_operator','','nf_sensorFileName',''); + + GenRes4 = struct('appName','','dataOrigin','','dataDescription','',... + 'no_trials_avgd',[],'data_time',[],'data_date',[],'gSetUp',gSetUp,'nfSetUp',nfSetUp,'rdlen',[]); + + %********************************************* + + + %% Reading Resources------------------------------------------------------------------------ + [rec,message] = fopen(rec4file,'rb','s'); % Big-endian byte ordering + if rec < 0 + errordlg(message) + return + end + + % Read HEADER + header = fread(rec,8,'char')'; + if VERBOSE + bst_message_window({... + sprintf('ds2brainstorm -> %s file format',char(header))... + }) + end + + + % Read nfSetUp + GenRes4.appName = char(fread(rec,256,'char')'); + GenRes4.dataOrigin = char(fread(rec,256,'char')'); + GenRes4.dataDescription = char(fread(rec,256,'char')'); + + GenRes4.no_trials_avgd = (fread(rec,1,'int16')'); + GenRes4.data_time = char(fread(rec,255,'char')'); + GenRes4.data_date = char(fread(rec,255,'char')'); + + gSetUp.no_samples = (fread(rec,1,'int32')'); + gSetUp.no_channels = (fread(rec,1,'int16')'); + + fseek(rec,ceil(ftell(rec)/8)*8,-1); + gSetUp.sample_rate = (fread(rec,1,'double')'); + fseek(rec,ceil(ftell(rec)/8)*8,-1); + gSetUp.epoch_time = (fread(rec,1,'double')'); + gSetUp.no_trials = (fread(rec,1,'int16')'); + fseek(rec,ceil(ftell(rec)/4)*4,-1); + gSetUp.preTrigPts = (fread(rec,1,'int32')'); + gSetUp.no_trials_done = (fread(rec,1,'int16')'); + gSetUp.no_trials_bst_message_windowlay = (fread(rec,1,'int16')'); + fseek(rec,ceil(ftell(rec)/4)*4,-1); + gSetUp.save_trials = (fread(rec,1,'int32')'); + gSetUp.primaryTrigger = char(fread(rec,1,'uchar')'); + gSetUp.secondaryTrigger = char(fread(rec,MAX_AVERAGE_BINS,'uchar')'); + gSetUp.triggerPolarityMask = char(fread(rec,1,'uchar')'); + + gSetUp.trigger_mode = (fread(rec,1,'int16')'); + fseek(rec,ceil(ftell(rec)/4)*4,-1); + gSetUp.accept_reject_Flag = (fread(rec,1,'int32')'); + gSetUp.run_time_bst_message_windowlay = (fread(rec,1,'int16')'); + fseek(rec,ceil(ftell(rec)/4)*4,-1); + gSetUp.zero_Head_Flag = (fread(rec,1,'int32')'); + fseek(rec,ceil(ftell(rec)/4)*4,-1); + gSetUp.artifact_mode = (fread(rec,1,'int32')'); + gSetUp.padding = (fread(rec,1,'int32')'); + + + nfSetUp.nf_run_name = char(fread(rec,32,'char')'); + nfSetUp.nf_run_title = char(fread(rec,256,'char')'); + + RunTitle = nfSetUp.nf_run_title ; + + nfSetUp.nf_instruments = char(fread(rec,32,'char')'); + nfSetUp.nf_collect_descriptor = char(fread(rec,32,'char')'); + nfSetUp.nf_subject_id = char(fread(rec,32,'char')'); + nfSetUp.nf_operator = char(fread(rec,32,'char')'); + tmp = fread(rec,60,'char')'; + tmp(tmp<0) = 0; % prevent out of range character conversion warning + nfSetUp.nf_sensorFileName = tmp; clear tmp + + fseek(rec,ceil(ftell(rec)/4)*4,-1); + nfSetUp.rdlen = fread(rec,1,'int32')'; + + GenRes4.gSetUp = gSetUp; + GenRes4.nfSetUp = nfSetUp; + + + % Time span of data record + Time = linspace(-GenRes4.gSetUp.preTrigPts/GenRes4.gSetUp.sample_rate,... + GenRes4.gSetUp.epoch_time/GenRes4.gSetUp.no_trials-(GenRes4.gSetUp.preTrigPts+1)/GenRes4.gSetUp.sample_rate,... + GenRes4.gSetUp.no_samples); + % + %---------------------------------------------------------------------------------------- + + if VERBOSE + ON_OFF = {'Off','On'}; + bst_message_window({... + sprintf('ds2brainstorm -> Header information'),... + sprintf(' Collected %s starting at %s', GenRes4.data_date, GenRes4.data_time),... + sprintf(' Run Name: %s', nfSetUp.nf_run_name),... + sprintf(' Run Title: %s', nfSetUp.nf_run_title),... + sprintf(' Col Desc: %s', nfSetUp.nf_collect_descriptor),... + sprintf(' Run Desc: %s', GenRes4.dataDescription),... + sprintf(' Operator: %s', nfSetUp.nf_operator),... + sprintf(' Subject : %s', nfSetUp.nf_subject_id),... + sprintf(' Channels: %d',gSetUp.no_channels),... + sprintf(' Samples : %d per trial', gSetUp.no_samples),... + sprintf(' Rate : %g samples/sec', gSetUp.sample_rate),... + sprintf(' Trials : %d (average of %d)', gSetUp.no_trials, GenRes4.no_trials_avgd),... + sprintf(' Duration: %g seconds/trial', gSetUp.epoch_time/gSetUp.no_trials),... + sprintf(' Pre-trig: %g samples', gSetUp.preTrigPts),... + sprintf(' Sensor file name : %s', nfSetUp.nf_sensorFileName),... + sprintf(' Head zeroing: %s', ON_OFF{gSetUp.zero_Head_Flag+1}),... + sprintf('_________________')... + }) + end + + + %------------------------------------------------------------------------------- + + %----------------------------------READ FILTERS--------------------------------- + + fseek(rec,1844,-1); + + % Run Description + rundescript = char(fread(rec,nfSetUp.rdlen,'char')); + if VERBOSE + bst_message_window({... + sprintf('ds2brainstorm -> Run Description: %s', rundescript )... + }) + end + + classType = {'CLASSERROR','BUTTERWORTH'}; + filtType = {'TYPERROR','LOWPASS','HIGHPASS','NOTCH'}; + + + % Number of filters + no_filters = fread(rec,1,'int16'); + if VERBOSE + bst_message_window({... + 'ds2brainstorm -> Filter information',... + sprintf('Number of filters: %d', no_filters)... + }) + end + + % JCM fixes 10/26/00, each of these should be looped through, not read in bulk + % old + if(0) + filter.freq = fread(rec,no_filters,'double'); + filter.fClass = classType{fread(rec,no_filters,'int32')+1}; + filter.fType = filtType{fread(rec,no_filters,'int32')+1}; + filter.numParam = fread(rec,no_filters,'int16'); + + filter.params = []; + for fi = 1:no_filters + for p = 1: filter(fi).numParam + filter(fi).params= fread(rec,filter(fi).numParam,'double'); + end + end + else % new + [filter(1:no_filters)] = struct('freq',[],'fClass',[],'fType',[],'numParam',[],'params',[]); + for fi = 1:no_filters, + filter(fi).freq = fread(rec,1,'double'); + filter(fi).fClass = classType{fread(rec,1,'int32')+1}; + filter(fi).fType = filtType{fread(rec,1,'int32')+1}; + filter(fi).numParam = fread(rec,1,'int16'); + filter(fi).params= fread(rec,filter(fi).numParam,'double'); + end + end + + if VERBOSE + for fi = 1:no_filters + bst_message_window({... + sprintf(' Filter - %d',fi),... + sprintf(' -> Frequency: %g Hz',filter(fi).freq),... + sprintf(' -> Class: %s',filter(fi).fClass),... + sprintf(' -> Type: %s',filter(fi).fType),... + sprintf(' -> Number of parameters: %d',filter(fi).numParam)... + }) + + if ~isempty(filter(fi).params) + bst_message_window({... + sprintf(' -> Parameter Value(s): %g',filter(fi).params)... + }) + end + end + + bst_message_window({... + sprintf('ds2brainstorm -> Reading Filter Information - DONE'),... + sprintf('ds2brainstorm -> Reading Channel Information. . .')... + }) + end + + % Channel Names + for chan = 1:gSetUp.no_channels + channel_name{chan} = fread(rec,32,'char')'; + tmp = channel_name{chan}; + tmp(tmp>127) = 0; + tmp(tmp<0) = 0; + channel_name{chan} = strtok(tmp,char(0)); + ChannelName{chan} = char(strtok(channel_name{chan},'-')); + end + + % Sensor Resources + CoilType = {'CIRCULAR','SQUARE','???'}; + + for chan = 1:gSetUp.no_channels + oldtell = ftell(rec); + SensorRes(chan).sensorTypeIndex = fread(rec,1,'int16'); + SensorRes(chan).originalRunNum = fread(rec,1,'int16'); + + id = fread(rec,1,'int32')+1; + + if isempty(id) + id = -1; + end + + if id > 3 | id <0 + id = 3; + end + + SensorRes(chan).coilShape = CoilType{id}; + + SensorRes(chan).properGain = fread(rec,1,'double'); + SensorRes(chan).qGain = fread(rec,1,'double'); + SensorRes(chan).ioGain= fread(rec,1,'double'); + SensorRes(chan).ioOffset = fread(rec,1,'double'); + SensorRes(chan).numCoils = fread(rec,1,'int16'); + SensorRes(chan).grad_order_no = fread(rec,1,'int16'); + + padding = fread(rec,1,'int32'); + + for coil = 1:MAX_COILS + SensorRes(chan).coilTbl(coil).position.x = fread(rec,1,'double'); + SensorRes(chan).coilTbl(coil).position.y = fread(rec,1,'double'); + SensorRes(chan).coilTbl(coil).position.z = fread(rec,1,'double'); + SensorRes(chan).coilTbl(coil).position.junk = fread(rec,1,'double'); + SensorRes(chan).coilTbl(coil).orient.x = fread(rec,1,'double'); + SensorRes(chan).coilTbl(coil).orient.y = fread(rec,1,'double'); + SensorRes(chan).coilTbl(coil).orient.z = fread(rec,1,'double'); + SensorRes(chan).coilTbl(coil).orient.junk = fread(rec,1,'double'); + SensorRes(chan).coilTbl(coil).numturns = fread(rec,1,'int16'); + padding = fread(rec,1,'int32'); + padding = fread(rec,1,'int16'); + SensorRes(chan).coilTbl(coil).area = fread(rec,1,'double'); + end + + for coil = 1:MAX_COILS + SensorRes(chan).HdcoilTbl(coil).position.x = fread(rec,1,'double'); + SensorRes(chan).HdcoilTbl(coil).position.y = fread(rec,1,'double'); + SensorRes(chan).HdcoilTbl(coil).position.z = fread(rec,1,'double'); + SensorRes(chan).HdcoilTbl(coil).position.junk = fread(rec,1,'double'); + SensorRes(chan).HdcoilTbl(coil).orient.x = fread(rec,1,'double'); + SensorRes(chan).HdcoilTbl(coil).orient.y = fread(rec,1,'double'); + SensorRes(chan).HdcoilTbl(coil).orient.z = fread(rec,1,'double'); + SensorRes(chan).HdcoilTbl(coil).orient.junk = fread(rec,1,'double'); + SensorRes(chan).HdcoilTbl(coil).numturns = fread(rec,1,'int16'); + padding = fread(rec,1,'int32'); + padding = fread(rec,1,'int16'); + SensorRes(chan).HdcoilTbl(coil).area = fread(rec,1,'double'); + end + + end + + if VERBOSE + bst_message_window({... + 'ds2brainstorm -> Reading Channel Information - DONE',... + 'ds2brainstorm -> Converting Channel Information. . .'... + }) + + end + + nchan= length(channel_name); % Number of channels + + % Explode data according to channel type + imegsens = find([SensorRes.sensorTypeIndex] == 5); % Indices of MEG sensors + ieegsens = find([SensorRes.sensorTypeIndex] == 9); % Indices of EEG sensors + iothersens = [find([SensorRes.sensorTypeIndex] == 11),find([SensorRes.sensorTypeIndex] == 10)]; % Indices of OTHER sensors ('Stimulation input' and 'ADC') + irefsens = find([SensorRes.sensorTypeIndex] == 0); % Reference Channels + irefsens = [irefsens,find([SensorRes.sensorTypeIndex] == 1)]; % Reference Channels + + if READRES == 2 % Enough data information - send it out of here + % Initialize output arguments + [F,Channel,grad_order_no,no_trials,Time, RunTitle] = deal([]); + F = GenRes4; + F.filter = filter; + F.imegsens = imegsens; + F.ieegsens = ieegsens; + F.irefsens = irefsens; + F.iothersens = iothersens; + F.ChannelNames = char(ChannelName); + F.grad_order_no = [SensorRes(:).grad_order_no]; + F.nchan = length([imegsens,ieegsens,irefsens,iothersens]); + F.Time = Time; + return + end + + ieeg = 0; imeg = 0; + iother = 0; + iother2 = 0; + eegID = []; + otherID = []; + other2ID = []; + megID = []; + + iref = 0; + refID = []; + + chaneeg = [];% Index of EEG channels + chanmeg = [];chanother = []; + + [Channel(1:nchan)] = deal(struct('Loc',[],'Orient',[],'Comment',[],'Weight',[],'Type',[],'Name','')); + + for chan = 1:nchan + switch SensorRes(chan).sensorTypeIndex + case {0,1} %'References coils + iref = iref+1; + MAX_COILS = 2; % Take only into account the 2 first coils for every sensor + tmp = [SensorRes(chan).HdcoilTbl(:).position]; + tmpx = [tmp(1:MAX_COILS).x]/100; % In meters + tmpy = [tmp(1:MAX_COILS).y]/100; + tmpz = [tmp(1:MAX_COILS).z]/100; + + Channel(chan).Loc = ... + [tmpx;tmpy;tmpz]; + + tmp = [SensorRes(chan).HdcoilTbl(:).orient]; + tmpx = [tmp(1:MAX_COILS).x]; + tmpy = [tmp(1:MAX_COILS).y]; + tmpz = [tmp(1:MAX_COILS).z]; + + Channel(chan).Orient = ... + [tmpx;tmpy;tmpz]; + + + % Reorient along the outward pointing normal + loc = Channel(chan).Loc'; + ps1=sum((loc(1,:).*Channel(chan).Orient(:,1)')')'; + ps2=sum((loc(2,:).*Channel(chan).Orient(:,2)')')'; + Channel(chan).Orient(:,1) = (sign(ps1)*ones(1,3).*Channel(chan).Orient(:,1)')'; + Channel(chan).Orient(:,2) = (sign(ps2)*ones(1,3).*Channel(chan).Orient(:,2)')'; + + Channel(chan).Comment = ''; % SB 20-Nov-2002 + Channel(chan).Name = [SensorNames{SensorRes(chan).sensorTypeIndex+1},' ',int2str(chan)];% SB 20-Nov-2002 + Channel(chan).Type = 'MEG REF'; % SB 20-Nov-2002 + + % Channel(chan).Type = SensorNames{SensorRes(chan).sensorTypeIndex+1} ; + % if isempty(deblank(Channel(chan).Type)) + % Channel(chan).Type = 'MEG REF'; + % end + + chanmeg = [chanmeg,chan]; + refID(iref) = chan; + Channel(chan).Weight = [1 -1] ; + + case 5 % MEG + % KND: + % Quick & dirty test for possible Virtual MEG channel + % To detect Virtual ones, we could also use + % Head Coil Position = (0,0,0) + % ie. SensorRes(chanNumber).HdcoilTbl(1).position.x=0 + % SensorRes(chanNumber).HdcoilTbl(1).position.y=0 + % SensorRes(chanNumber).HdcoilTbl(1).position.z=0 + + if SensorRes(chan).coilTbl(1).area > 0 + % Not a virtual one... I guess ! + % -- KND + + imeg = imeg+1; + MAX_COILS = 2; % Take only into account the 2 first coils for every sensor + tmp = [SensorRes(chan).HdcoilTbl(:).position]; + tmpx = [tmp(1:MAX_COILS).x]/100; % In meters + tmpy = [tmp(1:MAX_COILS).y]/100; + tmpz = [tmp(1:MAX_COILS).z]/100; + + Channel(chan).Loc = ... + [tmpx;tmpy;tmpz]; + + tmp = [SensorRes(chan).HdcoilTbl(:).orient]; + tmpx = [tmp(1:MAX_COILS).x]; + tmpy = [tmp(1:MAX_COILS).y]; + tmpz = [tmp(1:MAX_COILS).z]; + + Channel(chan).Orient = ... + [tmpx;tmpy;tmpz]; + + % Reorient along the outward pointing normal + loc = Channel(chan).Loc'; + ps1=sum((loc(1,:).*Channel(chan).Orient(:,1)')')'; + ps2=sum((loc(2,:).*Channel(chan).Orient(:,2)')')'; + Channel(chan).Orient(:,1) = (sign(ps1)*ones(1,3).*Channel(chan).Orient(:,1)')'; + Channel(chan).Orient(:,2) = (sign(ps2)*ones(1,3).*Channel(chan).Orient(:,2)')'; + + Channel(chan).Comment = ' ';%[SensorNames{SensorRes(chan).sensorTypeIndex+1},' ',int2str(chan)]; + Channel(chan).Type = 'MEG';%SensorNames{SensorRes(chan).sensorTypeIndex+1} ; + + chanmeg = [chanmeg,chan]; + megID(imeg) = chan; + Channel(chan).Weight = [1 -1] ; + Channel(chan).Name = ChannelName{chan}; %char(strtok(channel_name{chan},'-')); + else + %KND : + % Virtual Channel Specifications + Channel(chan).Loc = []; + Channel(chan).Orient = []; + Channel(chan).Comment = [SensorNames{SensorRes(chan).sensorTypeIndex+1},' ',int2str(chan)]; + Channel(chan).Type = 'VirtualMEG'; + Channel(chan).Weight = [] ; + Channel(chan).Name = char(strtok(channel_name{chan},'-')); + iother = iother+1; + chanother = [chanother,chan]; + otherID(iother) = chan; + %remove it from imegsens + imegsens=setdiff(imegsens, chan); + end + % -- KND + case 9 % EEG + ieeg = ieeg + 1; + MAX_COILS = 1; % Take only into account the first sensor location for EEG (second pseudo-coil location is [0 0 0] as writtent in .ds files) + tmp = [SensorRes(chan).HdcoilTbl(:).position]; + tmpx = [tmp(1:MAX_COILS).x]/100;% In meters + tmpy = [tmp(1:MAX_COILS).y]/100; + tmpz = [tmp(1:MAX_COILS).z]/100; + + Channel(chan).Loc = ... + [tmpx;tmpy;tmpz]; + + Channel(chan).Orient = []; + + Channel(chan).Type = 'EEG';%SensorNames{SensorRes(chan).sensorTypeIndex+1} ; + Channel(chan).Comment = ' ';%[SensorNames{SensorRes(chan).sensorTypeIndex+1},' ',int2str(chan)]; + Channel(chan).Weight = []; + Channel(chan).Name = ChannelName{chan}; %char(strtok(channel_name{chan},'-'));%int2str(ieeg) ; + chaneeg = [chaneeg,chan]; + eegID(ieeg) = chan; + + case 11 % STIM + iother = iother+1; + Channel(chan).Loc = []; + Channel(chan).Orient = []; + Channel(chan).Type = SensorNames{SensorRes(chan).sensorTypeIndex+1} ; + if isempty(deblank(Channel(chan).Type )) + Channel(chan).Type = 'STIM'; + end + Channel(chan).Comment = [SensorNames{SensorRes(chan).sensorTypeIndex+1},' ',int2str(chan)]; + Channel(chan).Weight = []; + chanother = [chanother,chan]; + otherID(iother) = chan; + + otherwise + % KND: + % for MicroMed EEG dataHandled... + % iother2 = iother2+1; + iother = iother+1; + % -- KND + Channel(chan).Loc = []; + Channel(chan).Orient = []; + Channel(chan).Type = SensorNames{SensorRes(chan).sensorTypeIndex+1} ; + if isempty(deblank(Channel(chan).Type )) + Channel(chan).Type = 'OTHER'; + end + Channel(chan).Comment = [SensorNames{SensorRes(chan).sensorTypeIndex+1},' ',int2str(chan)]; + Channel(chan).Weight = []; + chanother = [chanother,chan]; + other2ID(iother) = chan; + + end + end + + if VERBOSE + bst_message_window('ds2brainstorm -> Converting Channel Information - DONE') + end + %------------------------------------------------------------------------------- + + + %----------------------------------READ Coefficients--------------------------------- + + % Number of coefficient records + nrec = fread(rec,1,'int16'); + + channel_name = cellstr(char(channel_name{:})); + hexadef = {'00000000','47314252','47324252','47334252','47324f49','47334f49'}; + strdef = {'NOGRAD','G1BR','G2BR','G3BR','G2OI','G3OI'}; + + CoefInfo = cell(length(channel_name),length(strdef)-1); + + %SensorCoefResRec + if VERBOSE + h = waitbar(0,'Reading Coefficient Information...'); + end + + for k = 1:nrec + SensorCoefResRec(k).sensorName = char(fread(rec,32,'char')'); + SensorCoefResRec(k).coefType = (fread(rec,1,'bit32')); + padding = fread(rec,1,'int32'); + SensorCoefResRec(k).CoefResRec.num_of_coefs = fread(rec,1,'int16'); + + SensorCoefResRec(k).CoefResRec.sensor_list = char(fread(rec,[SENSOR_LABEL,MAX_BALANCING],'uchar')'); + SensorCoefResRec(k).CoefResRec.coefs_list = fread(rec,MAX_BALANCING,'double'); + + cchannel = strmatch( deblank(SensorCoefResRec(k).sensorName),channel_name); + CoefType = find(hex2dec(hexadef)==(SensorCoefResRec(k).coefType)); + + if CoefType > 0 + + CoefInfo{cchannel,CoefType-1}.num_of_coefs = SensorCoefResRec(k).CoefResRec.num_of_coefs; + + for i=1:CoefInfo{cchannel,CoefType-1}.num_of_coefs + CoefInfo{cchannel,CoefType-1}.sensor_list(i) = irefsens(strmatch(strtok(SensorCoefResRec(k).CoefResRec.sensor_list(i,:),char(0)),channel_name(irefsens))); + CoefInfo{cchannel,CoefType-1}.coefs(i) = SensorCoefResRec(k).CoefResRec.coefs_list(i); + end + end + if VERBOSE + if(~rem(k,floor(nrec/10))), % only occasionally + waitbar(k/nrec); + end + end + + end + + if VERBOSE + delete(h) + end + + % Channel Gains + gain_chan = zeros(size(channel_name,1),1); + gain_chan(imegsens) = ([SensorRes(imegsens).properGain]'.*[SensorRes(imegsens).qGain]'); + gain_chan(irefsens) = ([SensorRes(irefsens).properGain]'.*[SensorRes(irefsens).qGain]'); + % gain_chan(ieegsens) = 1./([SensorRes(ieegsens).qGain]'*1e-6); + gain_chan(ieegsens) = 1./([SensorRes(ieegsens).qGain]'); + gain_chan(iothersens) = ([SensorRes(iothersens).qGain]'); % Don't know exactly which gain to apply here + + if VERBOSE + bst_message_window({... + 'ds2brainstorm -> Coefficient Information',... + sprintf(' Number of coefficient records = %d ', nrec)... + }) + + % [coef_file, message] = fopen('coef_file.txt','wt+'); + % + % if coef_file < 0 + % errordlg(message) + % return + % end + % + % fprintf(coef_file,'Number of coefficient records = %d \n', nrec); + % + % for k = 1:nrec + % coefType = dec2hex(SensorCoefResRec(k).coefType); + % coefType = strdef{hex2dec(hexadef)==hex2dec(coefType)}; + % + % fprintf(coef_file,'Channel %s type %s, ',SensorCoefResRec(k).sensorName, coefType); + % + % num_of_coefs = SensorCoefResRec(k).CoefResRec.num_of_coefs; + % fprintf(coef_file,'number = %d:\n', num_of_coefs); + % if 0 + % for j=1:num_of_coefs + % fprintf(coef_file,' %s ', SensorCoefResRec(k).CoefResRec.sensor_list{j}); + % fprintf(coef_file,' %g ',SensorCoefResRec(k).CoefResRec.coefs_list(j)); + % end + % fprintf(coef_file,'\n'); + % end + % end + % + % bst_message_window({... + % sprintf('ds2brainstorm -> Coefficient Information saved in coef_file.txt'),... + % sprintf('ds2brainstorm -> Reading Coefficient Information - DONE'),... + % ' '... + % }) + bst_message_window({... + sprintf('ds2brainstorm -> Reading Coefficient Information - DONE'),... + ' '... + }) + end + + + % Calculus of the matrix for nth-order gradient correction + % Coefficients for unused reference channels are weigthed by zeros in + % the correction matrix. + Gcoef = zeros(length(imegsens),length(min(irefsens):max(irefsens))); + grad_order_no = [SensorRes(:).grad_order_no]; + for k = 1:length(imegsens) + + % Reference coils for channel k + if grad_order_no(imegsens(k)) == 0 + %Data is saved as RAW + %Save 3rd order gradient sensor-list for subsequent correction if requested later by the user + [refs] = (CoefInfo{imegsens(k),3}.sensor_list); + Gcoef(k,refs-min(irefsens)+1) = CoefInfo{imegsens(k),3}.coefs ... + .* gain_chan(refs)'/gain_chan(imegsens(k)); + else + [refs] = (CoefInfo{imegsens(k),grad_order_no(imegsens(k))}.sensor_list); + Gcoef(k,refs-min(irefsens)+1) = CoefInfo{imegsens(k),grad_order_no(imegsens(k))}.coefs ... + .* gain_chan(refs)'/gain_chan(imegsens(k)); + end + + end + + if ~isempty(imegsens) + Channel(imegsens(1)).Comment = Gcoef; + end + % Channel(imegsens(1)).imegsens = imegsens; + % Channel(imegsens(1)).ieegsens = imegsens; + % Channel(imegsens(1)).irefsens = irefsens; + % Channel(imegsens(1)).RefChannel = Channel; % Store MEG reference information for future call to MEG forward routines and proper processing of gradient correction + + SensorLocFile = strrep(ds_directory,[fileparts(ds_directory),filesep],''); + SensorLocFile = strrep(SensorLocFile,'.ds','_SensorLoc_Results.mat'); + save_sensor_locs(Channel,SensorLocFile) + + if VERBOSE + bst_message_window('wrap',... + sprintf('ds2brainstorm -> Sensor locations can be visualized with the MRI Tool by loading the file: %s',SensorLocFile)... + ) + end + + fclose('all'); + + % Create the BrainStorm res4 file for subsequent fast access to the data file + no_channels = length(Channel); + gain_chan(ieegsens)=1./gain_chan(ieegsens); + no_trials = [1:gSetUp.no_trials]; + + save(res4_mat,'gSetUp','meg4file','ieegsens','irefsens', 'iothersens', 'imegsens','Time','no_channels','gain_chan','channel_name','Channel','grad_order_no','filter','RunTitle','no_trials'); + + if READRES == 1 + READRES = 0; %read the res4.mat file just created and read the data by block. + if nargin == 2 % Stop there + return + %CHANNELS = 1:length(Channel); % With all available channels... + %TIME = Time; % whole time duration... + %NO_TRIALS = 1:gSetUp.no_trials; % All trials + elseif nargin > 2 + % Fill empty arguments + if isempty(CHANNELS) + CHANNELS = 1:length(Channel); % Read all available channels + end + if isempty(TIME) + % KND: + % Bug in the following line? + % TIME = Time; % Read entire trial duration + TIME=[Time(1) Time(end)]; + % -- KND + + end + if isempty(NO_TRIALS) + NO_TRIALS = 1:gSetUp.no_trials; % Read all trials + end + end + end + +else % READRES == 0 + if VERBOSE + bst_message_window({... + sprintf('ds2brainstorm -> Loading resources : %s',res4_mat),... + }) + end + load(res4_mat); + + % KND: + if isempty(TIME) + TIME = [Time(1) Time(end)]; % Read entire trial duration + if VERBOSE + bst_message_window({... + sprintf('ds2brainstorm -> Whole trial will be read: Time window: %f %f', TIME(1),TIME(2))... + }) + end + end + % -- KND +end + + +%--------------------------- Readind DATA FILE ------------------------------ + +if VERBOSE + bst_message_window({... + sprintf('ds2brainstorm -> Reading Data')... + }) +end + +if nargin == 2 & READRES ~= 3 % Read entire block of data + + no_trials = gSetUp.no_trials; + + [meg,message] = fopen(meg4file,'rb','s'); % Big-endian byte ordering + if meg < 0 + errordlg(message) + return + end + + F = cell(gSetUp.no_trials,1); % allocate space for the data array % ASSUME SINGLE TRIAL FOR THE MOMENT - ie enough memory space + header = char(fread(meg,8,'char')'); + + nref=length(irefsens); + + for trial = 1:gSetUp.no_trials + + if VERBOSE + bst_message_window({... + sprintf('ds2brainstorm -> Trial %d/%d',trial,gSetUp.no_trials)... + }) + end + + F{trial} = zeros(gSetUp.no_channels,gSetUp.no_samples); + F{trial} = fread(meg,[gSetUp.no_samples gSetUp.no_channels],'int32')'; + + if VERBOSE + bst_message_window({... + sprintf('ds2brainstorm -> Done')... + }) + end + % IMPORTANT NOTICE : Applying Gradient Correction + % Data are saved in a given nth-order gradient correction + % Applying gradient correction si only needed for forward model computation + % or if it is desired to reverse to lower-order gradient correction (see importdata.m for instance). + + % Apply channel gains + F{trial}(ieegsens,:) = diag(1./gain_chan(ieegsens))*F{trial}(ieegsens,:) ; %To get Microvolts->already inverted when saved _res4.mat + F{trial}(imegsens,:) = diag(1./gain_chan(imegsens))*F{trial}(imegsens,:) ; + + % Removing DC Offset + switch(DCOffset) + case {1,2} + if VERBOSE + bst_message_window({... + 'ds2brainstorm -> Removing DC offset'... + }) + end + + if DCOffset == 1 % Based on entire trial length + F{trial}(imegsens,:) = F{trial}(imegsens,:) - repmat(mean(F{trial}(imegsens,:)')',1,size(F{trial},2)); + F{trial}(irefsens,:) = F{trial}(irefsens,:) - repmat(mean(F{trial}(irefsens,:)')',1,size(F{trial},2)); + else % Based on pretrigger + TimeNeg = TIME(TIME<0); % Find pretrigger time points + if ~isempty(TimeNeg) + F{trial}(imegsens,:) = F{trial}(imegsens,:) - repmat(mean(F{trial}(imegsens,TimeNeg)')',1,size(F{trial},2)); + F{trial}(irefsens,:) = F{trial}(irefsens,:) - repmat(mean(F{trial}(irefsens,TimeNeg)')',1,size(F{trial},2)); + else + % Do nothing + end + end + + if VERBOSE + bst_message_window({... + 'ds2brainstorm -> Removing DC offset - DONE'... + }) + end + + otherwise + % Do nothing + end + + F{trial}(irefsens,:) = diag(1./gain_chan(irefsens))*F{trial}(irefsens,:) ; + F{trial}(iothersens,:) = diag(1./gain_chan(iothersens))*F{trial}(iothersens,:) ; + + end + + fclose('all'); + + %------------------------------------------------------------------------------------------------------------------------------------ + +else % Reads sub-block of data + + + if READRES == 3 + + F = ds_directory; + + if isempty(NO_TRIALS) % argument NO_TRIALS not defined + no_trials = [1:gSetUp.no_trials]; + else + no_trials = [NO_TRIALS]; + end + + else + + %ds2brainstorm(ds_directory,VERBOSE,READRES,CHANNELS,TIME, NO_TRIALS); + if isempty(NO_TRIALS) % argument NO_TRIALS not defined + no_trials = [1:gSetUp.no_trials]; + else + no_trials = [NO_TRIALS]; + end + + if isempty(CHANNELS) + CHANNELS = [1:length(Channel)]; + end + + fclose('all'); + [meg,message] = fopen(meg4file,'rb','s'); % Big-endian byte ordering + if meg < 0 + errordlg(message) + return + end + + % no_samples = round((TIME(end)-TIME(1))*gSetUp.sample_rate) +1; % Number of time samples in a trial + + F = cell(length(no_trials),1); % allocate space for the data array % ASSUME SINGLE TRIAL FOR THE MOMENT - ie enough memory space + % Adjust time range to the closest time samples; + if isempty(TIME) + return + end + + header = char(fread(meg,8,'char')'); + implicit.sample_rate = 1/(Time(2) - Time(1)); % i.e. the one used in the data file given the time begin_time end period. + % t_in = round((TIME(1)-Time(1))*implicit.sample_rate) +1; % Sample time offset since the beginning of the trial: beginning of the time window TIME + % + % no_samples = round((TIME(end)-TIME(1))*implicit.sample_rate)+1; % Number of time samples to extract + % t_out = t_in + no_samples - 1; + + tmp = findclosest([TIME],Time'); + t_in = tmp(1); + t_out = tmp(2); + no_samples = length(t_in:t_out); + + try + Time = Time(t_in:t_out); % SB 23-Dec-2002 + catch + error(... + sprintf('Data time ranges from %3.1f to %3.2f ms. Please adjust time extraction window',Time(1),Time(end))); + end + + diff_trials = diff(no_trials)-1; % Number of trials between each selected trials (useful when skipping a few trials) + + ByteSizeOfTrial= no_channels*gSetUp.no_samples*4; % Byte size of a single trial of data (Int32 coding) + + samples_skip = gSetUp.no_samples-no_samples; %Number of time samples to skip per channel + + LastChannelSkip = (no_channels - max(CHANNELS))*gSetUp.no_samples*4; % Skip data from last channels + + channels = [min(CHANNELS):max(CHANNELS)]; % Block of channels to extract. + + FirstChannelSkip = (min(CHANNELS)-1)*gSetUp.no_samples*4; % Skip data from first channels + no_channels = length(channels); + + itrial = 0; + + for trial = no_trials + itrial = itrial+1; + if VERBOSE + bst_message_window({... + sprintf('ds2brainstorm -> Trial %d / %d',itrial,length(no_trials))... + }) + end + + F{itrial} = zeros(no_channels,no_samples); + + if trial == no_trials(1) % Read first trial + fseek(meg,(trial-1)*ByteSizeOfTrial + FirstChannelSkip + (t_in-1)*4 ,0); + else % just shift from the size of a trial + fseek(meg,LastChannelSkip + diff_trials(itrial-1)*ByteSizeOfTrial + FirstChannelSkip + (t_in-1)*4 ,0); + end + + F{itrial} = fread(meg,[no_samples no_channels],[num2str(no_samples),'*int32=>int32'], samples_skip*4)'; + F{itrial} = F{itrial}(CHANNELS-min(CHANNELS)+1,:); + + % IMPORTANT NOTICE : Applying Gradient Correction + % Data are saved in a given nth-order gradient correction + % Applying gradient correction is only needed for forward model computation + % or if it is desired to reverse to lower-order gradient correction (see importdata.m for instance). + + % Apply channel gains + F{itrial} = diag(1./gain_chan(CHANNELS))*double(F{itrial}); + + % Removing DC Offset + if isempty(DCOffset) + DCOffset = 0; + end + switch(DCOffset) + case {1,2} + if VERBOSE + bst_message_window({... + 'ds2brainstorm -> Removing DC offset'... + }) + end + + if DCOffset == 1 % Based on entire trial length + F{itrial}(imegsens,:) = F{itrial}(imegsens,:) - repmat(mean(F{itrial}(imegsens,:)')',1,size(F{itrial},2)); + F{itrial}(irefsens,:) = F{itrial}(irefsens,:) - repmat(mean(F{itrial}(irefsens,:)')',1,size(F{itrial},2)); + else % Based on pretrigger + TimeNeg = intersect(find(Time(t_in:t_out) >= TIME(1)),find(Time(t_in:t_out)<0)); % Find pretrigger time points + if ~isempty(TimeNeg) + F{itrial}(imegsens,:) = F{itrial}(imegsens,:) - repmat(mean(F{itrial}(imegsens,TimeNeg)')',1,size(F{itrial},2)); + F{itrial}(irefsens,:) = F{itrial}(irefsens,:) - repmat(mean(F{itrial}(irefsens,TimeNeg)')',1,size(F{itrial},2)); + else + % Do nothing + end + end + + if VERBOSE + bst_message_window({... + 'ds2brainstorm -> Removing DC offset - DONE'... + }) + end + + otherwise + % Do nothing + end + + + if VERBOSE + bst_message_window({... + sprintf('ds2brainstorm -> Done')... + }) + end + + end + + fclose('all'); + end + +end + + +function PrintCoilPos(channel_file, CoilRec_ext,nCoils) ; + +% Subroutine to DS2BRAINSTORM +% Formatted output of channel locations in a file (channel_file is the pointer to this file) +% as defined in theCoilRec_ext structure, according to the number of coils nCoils for every gradiometer + +for coil=1:nCoils + fprintf(channel_file,'Coil %d:', coil); + fprintf(channel_file,' pX %g,', CoilRec_ext(coil).position.x); + fprintf(channel_file,' pY %g,', CoilRec_ext(coil).position.y); + fprintf(channel_file,' pZ %g ', CoilRec_ext(coil).position.z); + fprintf(channel_file,'-'); + fprintf(channel_file,' oX %g,', CoilRec_ext(coil).orient.x); + fprintf(channel_file,' oY %g,', CoilRec_ext(coil).orient.y); + fprintf(channel_file,' oZ %g ', CoilRec_ext(coil).orient.z); + fprintf(channel_file,'-'); + fprintf(channel_file,' turns %d,', CoilRec_ext(coil).numturns); + fprintf(channel_file,' area %g', CoilRec_ext(coil).area); + + if coil < nCoils + fprintf(channel_file,' --- '); + end + + fprintf(channel_file,'\n'); +end + +return + +function save_sensor_locs(Channel,SensorLocFile) +% Save Channel location in a pseudo-result file for visualization in the MRITool + +nchan = length(Channel); +meg = good_channel(Channel,ones(nchan,1),'MEG'); +eeg = good_channel(Channel,ones(nchan,1),'EEG'); + +nchan= length([meg,eeg]); +SourceLoc = cell(1,nchan); + +i = 0; +for k = [meg,eeg] + i = i+1; + SourceLoc{i} = Channel(k).Loc(:,1); + SourceOrder(i) = -1; + Comment = 'Sensor Locations'; +end +DataFlag = 'Sensors'; +if isdir('BrainStorm') + save(fullfile('BrainStorm',SensorLocFile),'SourceLoc','SourceOrder','DataFlag','Comment') +else + save sensor_result SourceLoc SourceOrder DataFlag Comment +end + +return diff --git a/brainstorm/getChannelLocs.m b/brainstorm/getChannelLocs.m new file mode 100755 index 0000000..5f4125b --- /dev/null +++ b/brainstorm/getChannelLocs.m @@ -0,0 +1,4 @@ +function [pos]=getChannelLocs(Channel) +for i=1:length(Channel) + pos(i,1:3)=reshape(Channel(i).Loc(1:3), 1,3); +end \ No newline at end of file diff --git a/brainstorm/getPositionChannel.m b/brainstorm/getPositionChannel.m new file mode 100755 index 0000000..88ef2f1 --- /dev/null +++ b/brainstorm/getPositionChannel.m @@ -0,0 +1,4 @@ +function [pos]=getPositionChannel(Channel) +for i=1:length(Channel) + pos(i,1:3)=reshape(Channel(i).Loc(1:3), 1,3); +end \ No newline at end of file diff --git a/brainstorm/meeg_menu.m b/brainstorm/meeg_menu.m new file mode 100644 index 0000000..60da259 --- /dev/null +++ b/brainstorm/meeg_menu.m @@ -0,0 +1,21 @@ +function [hmenu] = meeg_menu( varargin ) +%MEEG_MENU - Add a MEEG related menu + +[hp,ha,hf]=findTessellationHandles(varargin{:}); +uhp=get(hp, 'UserData'); + +% Remove previous instances +hmenu=findobj(hf, 'Type', 'uimenu', 'Label', 'MEEG'); +delete(hmenu); + +hmenu=uimenu('Label', 'MEEG', 'Parent', hf); +t1 = cortex_menu; +set(t1, 'Parent', hmenu); +t1 = meegui_menu; +set(t1, 'Parent', hmenu, 'Separator', 'on'); +t1 = viewpoint_menu('create',hp); +set(t1, 'Parent', hmenu, 'Separator', 'on'); +t1 = scout_menu('create',hp); +set(t1, 'Parent', hmenu, 'Separator', 'on'); + +return diff --git a/brainstorm/meegui_menu.m b/brainstorm/meegui_menu.m new file mode 100644 index 0000000..92f14c8 --- /dev/null +++ b/brainstorm/meegui_menu.m @@ -0,0 +1,39 @@ +function [hmenu]= meegui_menu(action, varargin) +% cortex_menu - Menu related to cortical surface processing + +if nargin==0 + action='create'; +end +switch lower(action) + case 'create' + hmenu=action_create(varargin); +end +return + + +function [hmenu]=action_create(varargin) +hmenu = uimenu('Label','MEEG Gui'); +cback='uhp=get(findTessellationHandles, ''UserData'');if isempty(uhp); return; end;'; +t = uimenu('Parent',hmenu,... + 'Label','Display GUIs',... + 'Checked', 'on',... + 'CallBack', [... + 'if isempty(get(gcbo, ''UserData'')),'... + 'set(gcbo, ''UserData'', findobj(gcbf, ''type'', ''uicontrol''));'... + 'set(findobj(gcbf, ''type'', ''uicontrol''), ''Visible'', ''off'');'... + 'set(gcbo, ''Checked'', ''off'');'... + 'else,'... + 'set(get(gcbo, ''UserData''), ''Visible'', ''on'');'... + 'set(gcbo, ''UserData'', []);'... + 'set(gcbo, ''Checked'', ''on'');'... + 'end;']); + +t = uimenu('Parent',hmenu,... + 'Label','Rotate 3D axes',... + 'Checked', 'off',... + 'CallBack', [... + 'rotate3daxes(get(findTessellationHandles, ''Parent''));'... + 'set(gcbo, ''Checked'',get(findall(gcbf,''Tag'',''figToolRotate3DAxes''),''State''));'... + ]); + + diff --git a/brainstorm/mybrainstorm_toolbar.m b/brainstorm/mybrainstorm_toolbar.m new file mode 100644 index 0000000..b31af28 --- /dev/null +++ b/brainstorm/mybrainstorm_toolbar.m @@ -0,0 +1,134 @@ +function [] = mybrainstorm_toolbar + +hToolbar = findobj(gcf, 'tag', mfilename) +delete(hToolbar) +% Create the toolbar +hToolbar = uitoolbar(gcf, 'tag', mfilename); +th =hToolbar; + +% Add a push tool to the toolbar +a = [.20:.05:0.95] +img1(:,:,1) = repmat(a,16,1)' +img1(:,:,2) = repmat(a,16,1); +img1(:,:,3) = repmat(flipdim(a,2),16,1); +pth = uipushtool(th,'CData',img1,... + 'TooltipString','My push tool',... + 'HandleVisibility','off') +% Add a toggle tool to the toolbar +img2 = im2cdata('~/yomega/img/sci/brain-icons/iconTiny_KnowYourBrain.png') + +tth = uitoggletool(th,'CData',img2,'Separator','on',... + 'TooltipString','Your toggle tool',... + 'HandleVisibility','off', 'callback', '') + + + +% Matlab icons +filename = fullfile(matlabroot,'/toolbox/matlab/icons/greenarrowicon.gif'); +cdataRedo = im2cdata(filename) +cdataUndo = cdataRedo(:,[16:-1:1],:); + +% Add the icon (and its mirror image = undo) to the latest toolbar +hUndo = uipushtool('cdata',cdataUndo, 'tooltip','undo', 'ClickedCallback','uiundo(gcbf,''execUndo'')'); +hRedo = uipushtool('cdata',cdataRedo, 'tooltip','redo', 'ClickedCallback','uiundo(gcbf,''execRedo'')'); + +hToolbar = findall(hFig,'tag','FigureToolBar'); +%hToolbar = get(hUndo,'Parent'); % an alternative +hButtons = findall(hToolbar); +set(hToolbar,'children',hButtons([4:end-4,2,3,end-3:end])); +set(hUndo,'Separator','on'); + +% Retrieve redo/undo object +undoObj = getappdata(hFig,'uitools_FigureToolManager'); +if isempty(undoObj) + undoObj = uitools.FigureToolManager(hFig); + setappdata(hFig,'uitools_FigureToolManager',undoObj); +end + +% Customize the toolbar buttons +latestUndoAction = undoObj.CommandManager.peekundo; +if isempty(latestUndoAction) + set(hUndo, 'Tooltip','', 'Enable','off'); +else + tooltipStr = ['undo' latestUndoAction.Name]; + set(hUndo, 'Tooltip',tooltipStr, 'Enable','on'); +end + +return + +% Add undo dropdown list to the toolbar +jToolbar = get(get(hToolbar,'JavaContainer'),'ComponentPeer'); +if ~isempty(jToolbar) + undoActions = get(undoObj.CommandManager.UndoStack,'Name'); + jCombo = javax.swing.JComboBox(undoActions(end:-1:1)); + set(jCombo, 'ActionPerformedCallback', @myUndoCallbackFcn); + jToolbar(1).add(jCombo,5); %5th position, after printer icon + jToolbar(1).repaint; + jToolbar(1).revalidate; +end + +end +% Drop-down (combo-box) callback function +function myUndoCallbackFcn(hCombo,hEvent) +itemIndex = get(hCombo,'SelectedIndex'); % 0=topmost item +itemName = get(hCombo,'SelectedItem'); +% user processing needs to be placed here +end + + +function [ho]=action_mni(vp,ho) +% Change viewpoint (vp) for object ho (which can be a patch handle or a +% menu handle) +if nargin<2 + ho=[]; + hmnu=[]; +end +if isequal(get(ho, 'type'), 'uimenu') + hmnu=ho; + ho=get(get(ho, 'Parent'), 'UserData'); +end +[ho,ha]=findTessellationHandles(ho); +switch lower(vp) + case 'top' + view(ha,[0 0 1]) + case 'bottom' + view(ha,[0 0 -1]) + case 'left' + view(ha,[-1 0 0]) + case 'right' + view(ha,[1 0 0]) + case 'front' + view(ha,[0 1 0]) + case 'back' + view(ha,[0 -1 0]) + case 'zflip' + if z + set(ha, 'zdir', 'reverse') + set(hmnu, 'Checked', 'on') + else + set(ha, 'zdir', 'normal') + set(hmnu, 'Checked', 'off') + end +end +end + +function cdata=im2cdata(filename) + +% Load the icon +[cdata,map] = imread(filename); + +if ~isempty(map) +% Convert white pixels into a transparent background +map(find(map(:,1)+map(:,2)+map(:,3)==3)) = NaN; + +% Convert white pixels into a transparent background +map(find(map(:,1)+map(:,2)+map(:,3)==0)) = NaN; + + +% Convert into 3D RGB-space +cdata = ind2rgb(cdata,map); +elseif isinteger(cdata) + cdata=double(cdata)./255; +end + +end \ No newline at end of file diff --git a/brainstorm/scout_button.m b/brainstorm/scout_button.m new file mode 100644 index 0000000..1adffd1 --- /dev/null +++ b/brainstorm/scout_button.m @@ -0,0 +1,147 @@ +function [varargout]= scout_button(action,varargin) +% Add a GUI button on a figure to click scouts etc. +% scout_button; +% [hbtn] = scout_button; +% [hbtn] = scout_button('action', 'Property1', ...); +% e.g. scout_button('init','Position', [x y dx dy]) +% scout_button('set','UserData', handle_cortex) +% scout_button('click', handle_cortex) +% +if nargin==0 + action='init'; +end +switch(action) + case 'init' + hbtn=action_init(varargin{:}); + if nargout>0 + varargout={hbtn}; + end + case 'click' + [idx_scout,hctx,hbtn]=action_click(varargin{:}); + if nargout>0 + varargout={idx_scout,hctx,hbtn}; + else + ns = numel(idx_scout); + z=get(hctx, 'FaceVertexCData'); + assignin('base', 'ans', idx_scout); + for i=1:min(ns,10) + fprintf('\nScout #:%d', idx_scout(i)); + if ~isempty(z) + fprintf('\tValue:%g', z(idx_scout(i))); + end + fprintf(' (variable ans has been changed)\n'); + end + if ns>1 && ~isempty(z) + fprintf('... %d scouts in the cluster ...\n',ns); + fprintf('Mean value : %g \n',mean(z(idx_scout))); + end + end +end + +function [hbtn]=action_init(varargin) +delete(findobj(gcf, 'Tag', mfilename, 'Style', 'pushbutton')) +hbtn=uicontrol('Style', 'pushbutton','Tag', mfilename); +test_select3d; +set(hbtn, 'String', 'Scout'); +set(hbtn, 'Units', 'normalized'); +set(hbtn, 'Callback', 'scout_button(''click'', gcbo);'); +action_set(hbtn, varargin{:}); + +function b=test_select3d +if exist('select3d')<2 + warning('Function needs BrainStorm/PublicToolbox/OtherTools/select3D.m') + b=0; +else + b=1; +end + + +function[hbtn]=action_set(hbtn,varargin) +if nargin==2 + set(hbtn, 'UserData', varargin{1}); +elseif nargin>1 && mod(nargin-1,2)==0 + set(hbtn, varargin{:}); +end + + +function [vi,hctx,hbtn]=action_click(varargin) +if ~test_select3d + return +end +hctx=[]; +if nargin==0 + hbtn=gcbo; +else + if isequal(get(varargin{1}, 'Tag'), mfilename) + hbtn=varargin{1}; + hctx=get(hbtn, 'UserData'); + else + hctx=varargin{1}; + hbtn=[]; + end +end +if isempty(hctx) + try + hctx=findTessellationHandles; + catch + end +end +if isempty(hctx) + error('No surface found'); + return +end +ax=get(hctx, 'Parent'); +axes(ax); +[x, y, button] = ginput(1); +[p v vi f fi]=select3d(hctx);; + +% +ctx=getappdata(hctx); +if button > 1 + if isempty(fieldnames(ctx)) + fv.faces = get(hctx,'Faces'); + fv.vertices = get(hctx,'Vertices'); + vc=tess_vertices_connectivity(fv); + f=get(hctx,'FaceVertexCData'); + + if size(f,2)==3 + % try to guess what appears "activated" + [v,n]=histk(f,'rows'); + v=v(n>.25*size(f,1),:); + f = ~ismember(f,v, 'rows'); + end + if all(f) + fprintf('Using Color threshold, based on CLim') + % use CLim + f=f.*(f>min(get(ax,'Clim'))); + end + end + + [c,sz,mc,v2c] = clustering(f,vc); + if v2c(vi) == 0 + vi = [] + else + vi = c{v2c(vi)}; + end + hold on + hplot=plot3(fv.vertices(vi,1),fv.vertices(vi,2),fv.vertices(vi,3),'.'); + drawnow; + pause(1) + delete(hplot) + hold off +end +if not(isfield(ctx, 'tex')) + % warning('No texture (''AppData''.tex) is associated with this cortical surface') + return +end +figure('Name', sprintf('Vertex %d [%0.2g %0.2g %0.2g]', vi, v)); +if ~isfield(ctx, 'time') + ctx.time=1:size(ctx.tex,2); +end +plot(ctx.time, ctx.tex(vi,:)); +hold on; +if isfield(ctx, 'htime') + stem(ctx.time(get(ctx.htime, 'Value')), ctx.tex(vi,get(ctx.htime, 'Value')), '.r'); +end +% psh_scout_cbk=['ginput(1);' scout_cbk]; + diff --git a/brainstorm/scout_menu.m b/brainstorm/scout_menu.m new file mode 100644 index 0000000..a37ef93 --- /dev/null +++ b/brainstorm/scout_menu.m @@ -0,0 +1,125 @@ +function [hmenu]= scouts_menu(action, varargin) +% cortex_menu - Menu related to cortical surface processing + +if nargin==0 + action='init'; +end +switch(action) + case {'init', 'create'} + hmenu=action_init(varargin{:}); + if nargout>0 + varargout={hmenu}; + end + case 'click' + [idx_scout,hctx,hbtn]=action_click(varargin{:}); + if nargout>0 + varargout={idx_scout,hctx,hbtn}; + else + z=get(hctx, 'FaceVertexCData'); + assignin('base', 'ans', idx_scout); + disp(sprintf('Scout #:%d Value:%g', idx_scout, z(idx_scout))); + end +end + + +function [hbtn]=action_init(varargin) +hmenu = uimenu('Label','Scout manager'); +cback='uhp=get(findTessellationHandles, ''UserData'');'; +% delete(findobj(gcf, 'Tag', mfilename, 'Style', 'pushbutton')) +% hbtn=uicontrol('Style', 'pushbutton','Tag', mfilename); +hbtn=uimenu('Label','Pick one...', 'Parent', hmenu, 'Tag', mfilename); +test_select3d; +set(hbtn, 'Callback', 'scout_menu(''click'', gcbo);'); +action_set(hbtn, varargin{:}); + +% set(hmenu, 'Callback',[ cback ... +% 'if isfield(uhp, ''vertconn''), state=''on''; else state=''off''; end;'... +% 'set(findobj(gcbo, ''Label'', ''Smooth surface''), ''Enable'',state);'... +% 'set(findobj(gcbo, ''Label'', ''Adv. Smooth surface''), ''Enable'', state);'... +% ]); + +% t1 = uimenu('Parent',hmenu,... +% 'Label','Define scout',... +% + +function b=test_select3d +if exist('select3d')<2 + warning('Function needs BrainStorm/PublicToolbox/OtherTools/select3D.m') + b=0; +else + b=1; +end + +function[hbtn]=action_set(hbtn,varargin) +if nargin==2 + set(hbtn, 'UserData', varargin{1}); +elseif nargin>1 && mod(nargin-1,2)==0 + set(hbtn, varargin{:}); +end + + +function [vi,hctx,hbtn]=action_click(varargin) +if ~test_select3d + return +end +hctx=[]; +if nargin==0 + hbtn=gcbo; +else + if isequal(get(varargin{1}, 'Tag'), mfilename) + hbtn=varargin{1}; + hctx=get(hbtn, 'UserData'); + else + hctx=varargin{1}; + hbtn=[]; + end +end +if isempty(hctx) + try + hctx=findTessellationHandles; + catch + end +end +if isempty(hctx) + error('No surface found'); + return +end +ax=get(hctx, 'Parent'); +axes(ax); +[x, y, button] = ginput(1); +[p v vi f fi]=select3d(hctx);; + +figure('Name', sprintf('Vertex %d [%0.2g %0.2g %0.2g]', vi, v)); +ctx=getappdata(hctx) +if button > 1 + if isempty(fieldnames(ctx)) + fv.faces = get(hctx,'Faces'); + fv.vertices = get(hctx,'Vertices'); + vc=tess_vertices_connectivity(fv); + f=get(hctx,'FaceVertexCData'); + if all(f) + % use CLim + f=f.*(f>min(get(ax,'Clim'))); + end + + end +end + + +if not(isfield(ctx, 'tex')) +% warning('No texture (''AppData''.tex) is associated with this cortical surface') + return +end +if ~isfield(ctx, 'time') + ctx.time=1:size(ctx.tex,2); +end +plot(ctx.time, ctx.tex(vi,:)); +hold on; +if isfield(ctx, 'htime') + stem(ctx.time(get(ctx.htime, 'Value')), ctx.tex(vi,get(ctx.htime, 'Value')), '.r'); +end +% psh_scout_cbk=['ginput(1);' scout_cbk]; + + + + diff --git a/brainstorm/scout_swell.m b/brainstorm/scout_swell.m new file mode 100644 index 0000000..87d353b --- /dev/null +++ b/brainstorm/scout_swell.m @@ -0,0 +1,30 @@ +function newverts = scout_swell(iverts,vconn,depth); +%scout_swell - find neighboring vertices of a scout/patch +% [newverts]=scout_swell(iverts,vconn) +% [newverts]=scout_swell(iverts,vconn,depth) + +if nargin<3 + depth=1; +end +iverts=unique(iverts); +newverts=swell(iverts,vconn,depth); +return + + +function [nv]=swell(iv,vc,d) +if d==0 + nv=iv; + return +end +if iscell(vc) + nv=[vc{iv}]; + vc(iv)={[]}; +else + nv=[find(any(vc(iv,:),1))]; + vc(iv,nv)=0; + vc(nv,iv)=0; +end +nv = [nv(:) ; iv(:)]; +nv=unique(nv(:)'); +nv=unique([nv swell(nv,vc,d-1)]); +return diff --git a/brainstorm/scouts_menu.m b/brainstorm/scouts_menu.m new file mode 100644 index 0000000..0dd8bab --- /dev/null +++ b/brainstorm/scouts_menu.m @@ -0,0 +1,26 @@ +function [hmenu]= scouts_menu(action, varargin) +% cortex_menu - Menu related to cortical surface processing + +if nargin==0 + action='create'; +end +switch lower(action) + + case 'create' + hmenu = uimenu('Label','Scout manager'); + + cback='uhp=get(findTessellationHandles, ''UserData'');'; + +% set(hmenu, 'Callback',[ cback ... +% 'if isfield(uhp, ''vertconn''), state=''on''; else state=''off''; end;'... +% 'set(findobj(gcbo, ''Label'', ''Smooth surface''), ''Enable'',state);'... +% 'set(findobj(gcbo, ''Label'', ''Adv. Smooth surface''), ''Enable'', state);'... +% ]); + +% t1 = uimenu('Parent',hmenu,... +% 'Label','Define scout',... +% + + + return +end diff --git a/brainstorm/vertconn2adjacency.m b/brainstorm/vertconn2adjacency.m new file mode 100644 index 0000000..de297cd --- /dev/null +++ b/brainstorm/vertconn2adjacency.m @@ -0,0 +1,61 @@ +function [A]=vertconn2adjacency(vc,VERBOSE) +% vertconn2adjacency - compute the adjacency patrix +% [A]=vertconn2adjacency(vc,VERBOSE) +% +% vc: vertex connectivity, cell array of indices OR patch structure +% A: (sparse) adjacency matrix + +if nargin<2 + VERBOSE=0; +end + +if isfield(vc, 'faces') + VertConn=0; + nv=size(vc.vertices,1); +elseif iscell(vc) + VertConn=1; + nv=length(vc); +else + error('Wrong type of inputs') +end +% Could think of preallocating I and J +% with an a priori ratio of the number of vertices... +% but (sigh)... +I=[]; +J=[]; + +if VERBOSE + h=timebar('Building adjacency matrix','Adjacency'); +end + +if VertConn + l=[0; cellfun('length', vc)]; + cl=cumsum(l); + I=zeros(cl(end),1); + J=[vc{:}]'; + for i=1:nv + I(cl(i)+[1:l(i+1)])=i; + if VERBOSE + timebar(h,i/nv) + end + end +else + % I J should be preallocated + for i=1:nv; + adj=setdiff(unique(vc.faces(find(any(vc.faces==i,2)))),i); + adj=adj(:)'; + adj=double(adj); + n=length(adj); + I=[I i*ones(1,n)]; + J=[J adj]; + if VERBOSE + timebar(h,i/nv) + end + end +end +A=sparse(I,J,ones(length(I),1),nv,nv); +A=A|A'; + +if VERBOSE + close(h); +end diff --git a/brainstorm/view_MNEresults.m b/brainstorm/view_MNEresults.m new file mode 100644 index 0000000..d66fabb --- /dev/null +++ b/brainstorm/view_MNEresults.m @@ -0,0 +1,336 @@ +function [ varargout ] = view_MNEresults(action,varargin) +% view_MNEresults - Diplays BrainStorm results +% +% [ hf ] = view_MNEresults(fv,cdata) +% [ hf ] = view_MNEresults(faces,verts,cdata) +% [ hf ] = view_MNEresults(fv,cdata,time) + +if nargin<1 + error('No data!') +elseif nargin==1 || ~ischar(action) + varargin=[{action} varargin ]; + action='init'; +end + +try + varargout={eval(sprintf('action_%s(varargin{:});',action))}; +catch + eval(sprintf('action_%s(varargin{:});',action)); +end + +%__________________________________________________________________________ +% +%% ACTION INIT +% +function [hf]=action_init(varargin) +if isfield(varargin{1}, 'faces') + ctx=varargin{1}; + varargin(1)=[]; +else + if nargin>=3 + ctx.faces=varargin{1}; + ctx.vertices=varargin{2}; + varargin([1 2])=[]; + else + error('not enough inputs!') + end +end +if ~isempty(varargin) + if isnumeric(varargin{1}) + ctx.tex=varargin{1}; + if length(varargin)>1 + if isnumeric(varargin{2}) + ctx.time=varargin{2}; + end + end + end +end + +if(size(ctx.vertices,2) ~= 3), % assume transposed + ctx.vertices = ctx.vertices'; % if the assumption is wrong, will crash below anyway +end + +if size(ctx.vertices,1) ~= size(ctx.tex,1) + if size(ctx.vertices,1) == size(ctx.tex,2) + ctx.tex=ctx.tex'; + else + error(sprintf('Data provided are ill-shaped\nVertices: %d\nData: %s',size(ctx.vertices,1),sprintf('%d ',size(ctx.tex)))) + return; + end +end + +nsamples=size(ctx.tex,2); + +if isfield(ctx, 'time') + if length(ctx.time) ~= nsamples + error('Incorrect time vector (bad nuimber of samples)') + end + u.time=ctx.time; +else + u.time=[1:nsamples]; +end + +if ~isfield(ctx,'tex') + warning('No data provided. Distance to origin is used') + ctx.tex = rownorm(ctx.vertices); +end + +hf=figure; +hp=carto_cortex(ctx, ctx.tex(:,1)); +u.hpax=get(hp, 'parent'); +ha=u.hpax; +if any(ctx.tex(:)>0) & any(ctx.tex(:)<0) + u.cmap=jet(256); + u.cmapsym=0; +else + u.cmap=hot(256); + u.cmapsym=1; +end +colormap(u.cmap) + +hcbar=colorbar; +hcmap=uicontrol('Style', 'slider'); +set(hcmap, 'Units', get(hcbar, 'Units')); +set(hcmap, 'Min', min(0,-(log10(size(ctx.tex,1)))+2) , 'Max', 2, 'Value', 2); +set(hcmap, 'Position', get(hcbar, 'Position')*[1 0 0 0; 0 1 0 0; 1.7 0 .5 0 ;0 0 0 1 ]); +set(hcmap, 'Callback', sprintf('%s(''%s'',gcbf)',mfilename,'cmap')); + +hcmap2=uicontrol('Style', 'slider'); +set(hcmap2, 'Units', get(hcbar, 'Units')); +set(hcmap2, 'Max', 1 , 'Min', 0 , 'Value', 1); +set(hcmap2, 'Position', get(hcmap, 'Position')*[1 0 0 0; 0 1 0 0; 1.1 0 1 0 ;0 0 0 1 ]); +set(hcmap2, 'Callback', sprintf('%s(''%s'',gcbf)',mfilename,'cmap2')); + +%Tick box above & below colormap slider +hcmaptop=uicontrol('Style', 'checkbox', 'Units', 'normalized'); +set(hcmaptop, 'Position', get(hcmap, 'position')*[1 0 0 0; 0 1 0 0; 0 0 1 1 ; 0 1 0 0]) +hcmapbot=uicontrol('Style', 'checkbox', 'Units', 'normalized'); +set(hcmapbot, 'Position', get(hcmap, 'position')*[1 0 0 0; 0 1 0 0; 0 -1 1 1 ; 0 0 0 0]) +set([hcmapbot hcmaptop] , 'Callback', sprintf('%s(''%s'',gcbf)',mfilename,'cmap')); + +% Number of active sources (with the colormap) +hcmaptxt=uicontrol('Style', 'edit'); +set(hcmaptxt, 'Units', 'normalized') +set(hcmaptxt, 'Position', get(hcmapbot, 'Position')*[1 0 0 0; 0 1 0 0; -2 0 0 0; 0 0 0 0]+[0 -0.07 0.1 0.05 ]) +set(hcmaptxt, 'Callback', sprintf('%s(''%s'',gcbf)',mfilename,'cmaptxt')); + +% Slider to control the time +htime=uicontrol('Style', 'slider', 'Value', 1, 'Min', 1, 'Max', 1+eps); +if nsamples>1 + set(htime, 'SliderStep', [1/ceil(nsamples-1) min(.05, 100/ceil(nsamples-1))]); + set(htime, 'Max', nsamples); +else + set(htime, 'SliderStep', [1 1]); +end +set(htime, 'Units', 'normalized') +set(htime, 'Position', [ .02 .045 .8 .045]) +set(htime, 'Callback', sprintf('%s(''%s'',gcbf)',mfilename,'timeslider')); + +% Label to display Time (in time units) +htimetxtu=uicontrol('Style', 'edit'); +set(htimetxtu, 'Units', 'normalized') +set(htimetxtu, 'Position', get(htime, 'Position')*[1 0 0 0; 0 1 0 0; 0 0 0 0; 0 1 0 1]+[0 0 0.1 0]) +set(htimetxtu, 'Callback', sprintf('%s(''%s'',gcbf)',mfilename,'timetxtu')); + +% Label to display Time (in samples) +htimetxts=uicontrol('Style', 'edit'); +set(htimetxts, 'Units', 'normalized') +set(htimetxts, 'Position', get(htimetxtu, 'Position')*[1 0 0 0; 0 1 0 0; 0 0 1 0; 0 1.3 0 1]) +set(htimetxts, 'Callback', sprintf('%s(''%s'',gcbf)',mfilename,'timetxts')); + + +% To point a scout +hscout=scout_button('init','Position', [.7 .1 .1 .05]); +set(hscout, 'callback', [... + '[vi,hctx]=scout_button(''click'', gcbo); '... + 'figure(''Tag'', ''scout:plot'', ''Name'', sprintf(''Scout: #%d'', vi));'... + 'plot(getfield(get(gcbf, ''UserData''), ''time''),subarray(getfield(get(hctx, ''UserData''), ''tex''),vi))'... + ]); +% Set user data in the figure +u.hp=hp; +u.hcmap=hcmap; +u.hcmaptop=hcmaptop; +u.hcmapbot=hcmapbot; +u.hcmaptxt=hcmaptxt; +u.hcmap2=hcmap2; + +u.htime=htime; +u.htimetxtu=htimetxtu; +u.htimetxts=htimetxts; +set(hf, 'UserData', u); + +% Add tooltips +set(u.htime, 'TooltipString', 'Time') +set(u.htimetxtu, 'TooltipString', 'Time (in samples)') +set(u.htimetxts, 'TooltipString', 'Time (in sec)') +set(u.hcmaptxt, 'TooltipString', 'Number of active sources') +set(u.hcmap, 'TooltipString', 'Percentage of active sources') + +% set(u.hp, 'ButtonDownFcn', 'rotate3d on'); +rotate3d(hf, 'on') + +meeg_menu + +return + + +%__________________________________________________________________________ +% +%% ACTION COLORMAP FROM SLIDER +% +function []=action_cmap(hf,ho) +u=get(hf, 'UserData'); +n=power(10,get(u.hcmap, 'Value')); +s=get(u.hcmaptop, 'Value')-get(u.hcmapbot, 'Value'); +if s==0 && ~get(u.hcmaptop, 'Value') + s=u.cmapsym; +end +n=action_colormap(u.hp,n,s); +set(u.hcmaptxt, 'String',num2str(n)); +colorbar('peer', u.hpax); +%rotate3d; +return +%__________________________________________________________________________ +% +%% ACTION COLORMAP FROM TEXT +% +function []=action_cmaptxt(hf,ho) +u=get(hf, 'UserData'); +cdata=get(u.hp, 'FaceVertexCData'); +set(u.hcmap, 'Value', log10(100*str2num(get(u.hcmaptxt, 'String'))/size(cdata,1))); +action_cmap(hf) +return +%__________________________________________________________________________ +% +%% ACTION COLORMAP APPLY +% +function [n]=action_colormap(hp,n,s) +n=showpercent(n,hp,[],[],s); +return +%__________________________________________________________________________ +% +%% ACTION COLORMAP CROP +% +function []=action_cmap2(hf,ho) +u=get(hf, 'UserData'); +ctx=get(u.hp, 'UserData'); +s=get(u.hcmaptop, 'Value')-get(u.hcmapbot, 'Value'); +v=ctx.tex(:,get(u.htime, 'Value')); +% q=power(10,get(u.hcmap2, 'Value')) +q=get(u.hcmap2, 'Value'); +% Slider is more ergonomic as we may want to use to remove one or few +% outliers +q=(q)^(1/6); +t=get(u.hpax,'CLim'); +if s==0 + cmap=colormap(u.hpax); + n=size(cmap,1); + x=sum(all(cmap==repmat(cmap(ceil(n/2),:),n,1),2)); + v(abs(v)0 + v(vt(2))=[]; + t(1)=quantile(v,q); +end +set(u.hpax,'Clim',[t]) +colorbar('peer', u.hpax); +% action_cmap(hf) +return + + + + + +%__________________________________________________________________________ +% +%% ACTION SCOUT BUTTON (scoutbtn) +% +function [h]=action_scoutbtn(hf) +u=get(hf, 'UserData'); +[p v vi f fi]=select3d(u.hp); +figure('Name', sprintf('Scout: Vert#%d [%0.2g %0.2g %0.2g]', vi, v)); +ctx=get(u.hp, 'UserData'); +h=plot(u.time, ctx.tex(vi,:)); +hold on; +stem(u.time(get(u.htime, 'Value')), ctx.tex(vi,get(u.htime, 'Value')), '.r'); +assignin('caller', 'vi', vi) + + +%__________________________________________________________________________ +% +%% ACTION TIME SLIDER (timeslider) +% +function []=action_timeslider(hf) +u=get(hf, 'UserData'); +set(u.htime, 'Value',round(get(u.htime, 'Value'))); +ctx=get(u.hp, 'UserData'); +set(u.hp, 'FaceVertexCData',ctx.tex(:,get(u.htime, 'Value'))); +set(u.htimetxts, 'String', num2str(get(u.htime, 'Value'))); +set(u.htimetxtu, 'String', num2str(u.time(get(u.htime, 'Value')))); +colorbar('peer', u.hpax); +rotate3d; + + +%__________________________________________________________________________ +% +%% ACTION TIME TEXT IN TIME UNITS (timetxtu) +% +function [h]=action_timetxtu(hf) +u=get(hf, 'UserData'); +t=get(u.htime, 'Value'); +set(u.htime, 'Value', whichTime(str2num(get(u.htimetxtu, 'String')),u.time)); +if t~=get(u.htime, 'Value'); + action_timeslider(hf) + rotate3d; +end + +%__________________________________________________________________________ +% +%% ACTION TIME TEXT IN SAMPLES (timetxts) +% +function [h]=action_timetxts(hf) +u=get(hf, 'UserData'); +t=get(u.htime, 'Value'); +set(u.htime, 'Value', str2num(get(u.htimetxts, 'String'))); +if t~=get(u.htime, 'Value'); + action_timeslider(hf) + rotate3d; +end +return + +F=gcf; +% t0 = findobj(get(F,'Children'),'Flat','Label','&Help'); +% set(findobj(t0,'Position',1),'Separator','on'); +% t0=uimenu('Label', 'test') +% t0 = uicontextmenu('Parent',F,'HandleVisibility','CallBack'); + +t0=uimenu('Label', 'test') +t1 = uimenu('Parent',t0,'Position',1,... + 'Label','SPM web',... + 'CallBack','web(''http://www.fil.ion.ucl.ac.uk/spm'');'); +t1 = uimenu('Parent',t0,'Position',1,... + 'Label','SPM help','ForegroundColor',[0 1 0],... + 'CallBack','spm_help'); + +t0=uimenu('Parent', F,'Label','Colours','HandleVisibility','off'); +t1=uimenu('Parent',t0,'Label','ColorMap'); +t2=uimenu('Parent',t1,'Label','Gray','CallBack','spm_figure(''ColorMap'',''gray'')'); +t2=uimenu('Parent',t1,'Label','Hot','CallBack','spm_figure(''ColorMap'',''hot'')'); +t2=uimenu('Parent',t1,'Label','Pink','CallBack','spm_figure(''ColorMap'',''pink'')'); +t2=uimenu('Parent',t1,'Label','Gray-Hot','CallBack','spm_figure(''ColorMap'',''gray-hot'')'); +t2=uimenu('Parent',t1,'Label','Gray-Pink','CallBack','spm_figure(''ColorMap'',''gray-pink'')'); +t1=uimenu('Parent',t0,'Label','Effects'); +t2=uimenu('Parent',t1,'Label','Invert','CallBack','spm_figure(''ColorMap'',''invert'')'); +t2=uimenu('Parent',t1,'Label','Brighten','CallBack','spm_figure(''ColorMap'',''brighten'')'); +t2=uimenu('Parent',t1,'Label','Darken','CallBack','spm_figure(''ColorMap'',''darken'')'); +t0=uimenu('Parent', F,'Label','Clear','HandleVisibility','off','CallBack','spm_figure(''Clear'',gcbf)'); +t0=uimenu('Parent', F,'Label','SPM-Print','HandleVisibility','off','CallBack','spm_figure(''Print'',gcbf)'); + + + +return diff --git a/brainstorm/viewpoint_menu.m b/brainstorm/viewpoint_menu.m new file mode 100644 index 0000000..0ffea69 --- /dev/null +++ b/brainstorm/viewpoint_menu.m @@ -0,0 +1,199 @@ +function [h]=viewpoint_menu(action, varargin) +% viewpoint_menu - Add a menu to (easily) set viewpoint +% +% viewpoint_menu(h) adds a menu in the figure containing the patch h +% viewpoint_menu(action, ... ) runs various callbacks. +% +% viewpoint_menu(ref,ori) change orientation +% ref: ctf|mni|aims +% ori: top|bottom|left|right|front|back +% Example: +% >> viewpoint_menu('mni', 'top') +% +% viewpoint_menu(ref,ori,h) change orientation for handle h + +if nargin==0 + action='create'; +end +if ~ischar(action) + varargin=[{action}, varargin]; + action='create'; +end +%try + h=feval(sprintf('action_%s', lower(action)), varargin{:}); + %catch + % warning(sprintf('Unknown action: %s', action)); + %end +return + +function [hmenu]=action_create(hp) +if nargin<1 + hp=[]; +end +[hp,ha,hf]=findTessellationHandles(hp); + +% Remove previous instances +hmenu=findobj('Type', 'uimenu', 'Tag', mfilename, 'UserData', hp); +delete(hmenu); + +hmenu = uimenu('Label',['Viewpoints'], 'Tag', mfilename); +set(hmenu, 'UserData', hp); +set(hmenu, 'Callback', 'set(findobj(gcbo, ''Label'', ''Z-flip''), ''checked'', iff(strncmp(get(get(get(gcbo,''UserData''), ''Parent''), ''Zdir''),''r'',1),''on'',''off''))'); + +vp={'Top', 'Bottom', 'Left', 'Right', 'Front', 'Back','Front Right','Front Left','Back Right','Back Left' }; + +t = uimenu('Parent',hmenu,'Label','CTF'); +for i=1:length(vp) + uimenu('Parent', t, 'Label', vp{i}, 'Callback', [ mfilename '(''ctf'', ''' lower(vp{i}) ''',gcbo);']); +end + +t = uimenu('Parent',hmenu,'Label','MNI'); +vp={'Top', 'Bottom', 'Left', 'Right', 'Front', 'Back'}; +for i=1:length(vp) + uimenu('Parent', t, 'Label', vp{i}, 'Callback', [ mfilename '(''mni'', ''' lower(vp{i}) ''',gcbo);']); +end + +t = uimenu('Parent',hmenu,'Label','AIMS'); +vp={'Top', 'Bottom', 'Left', 'Right', 'Front', 'Back'}; +for i=1:length(vp) + uimenu('Parent', t, 'Label', vp{i}, 'Callback', [ mfilename '(''aims'', ''' lower(vp{i}) ''',gcbo);']); +end + +t=uimenu('Parent', hmenu, 'Label', 'Z-flip', 'Separator', 'on', ... + 'Callback',[ mfilename '(''zflip'',gcbo);'] ); +if strncmp(get(ha, 'zdir'), 'r',1); + set(t, 'Checked', 'on'); +end + +return + + +function [ho]=action_zflip(ho) +if nargin<1 + ho=[]; + hmnu=[]; +end +if isequal(get(ho, 'type'), 'uimenu') + hmnu=ho; + ho=get(get(ho, 'Parent'), 'UserData'); +end +[ho,ha]=findTessellationHandles(ho); +if strncmp(get(ha, 'zdir'), 'normal',1); + set(ha, 'zdir', 'reverse') + set(hmnu, 'Checked', 'on') +else + set(ha, 'zdir', 'normal') + set(hmnu, 'Checked', 'off') +end + + +function [ho]=action_ctf(vp,ho) +% Change viewpoint (vp) for object ho (which can be a patch handle or a +% menu handle) +if nargin<2 + ho=[]; + hmnu=[]; +end +if isequal(get(ho, 'type'), 'uimenu') + hmnu=ho; + ho=get(get(ho, 'Parent'), 'UserData'); +end +[ho,ha]=findTessellationHandles(ho); +z=strncmp(get(ha, 'zdir'), 'n',1); +switch lower(vp) + case 'top' + view(ha,[0,0,1]) + % view(ha,180*(1-z),90*(1-2*z)) + case 'bottom' + view(ha,[0,0,-1]) + % view(ha,180*z,-90*(1-2*z)) + case 'left' + view(ha,[0,1,0]) + % view(ha, 90+180*z,0) + case 'right' + view(ha,[0,-1,0]) + % view(ha,-90+180*z,0) + case 'front' + view(ha,[1,0,0]) + % view(ha,0,0) + case 'back' + view(ha,[-1,0,0]) + % view(ha,180,0) + case 'front left' + view(ha,[90+40 15]) + case 'front right' + view(ha,[40 15]) + % view(ha,180,0) + case 'back left' + view(ha,[-90-40 15]) + % view(ha,180,0) + case 'back right' + view(ha,[-40 15]) + % view(ha,180,0) + +end + + + +function [ho]=action_mni(vp,ho) +% Change viewpoint (vp) for object ho (which can be a patch handle or a +% menu handle) +if nargin<2 + ho=[]; + hmnu=[]; +end +if isequal(get(ho, 'type'), 'uimenu') + hmnu=ho; + ho=get(get(ho, 'Parent'), 'UserData'); +end +[ho,ha]=findTessellationHandles(ho); +switch lower(vp) + case 'top' + view(ha,[0 0 1]) + case 'bottom' + view(ha,[0 0 -1]) + case 'left' + view(ha,[-1 0 0]) + case 'right' + view(ha,[1 0 0]) + case 'front' + view(ha,[0 1 0]) + case 'back' + view(ha,[0 -1 0]) + case 'zflip' + if z + set(ha, 'zdir', 'reverse') + set(hmnu, 'Checked', 'on') + else + set(ha, 'zdir', 'normal') + set(hmnu, 'Checked', 'off') + end +end + + +function [ho]=action_aims(vp,ho) +% Change viewpoint (vp) for object ho (which can be a patch handle or a +% menu handle) +if nargin<2 + ho=[]; + hmnu=[]; +end +if isequal(get(ho, 'type'), 'uimenu') + hmnu=ho; + ho=get(get(ho, 'Parent'), 'UserData'); +end +[ho,ha]=findTessellationHandles(ho); +switch lower(vp) + case 'top' + view(ha,[0 0 1]) + case 'bottom' + view(ha,[0 0 -1]) + case 'left' + view(ha,[-1 0 0]) + case 'right' + view(ha,[1 0 0]) + case 'front' + view(ha,[0 1 0]) + case 'back' + view(ha,[0 -1 0]) +end diff --git a/brainstorm3.m b/brainstorm3.m new file mode 100644 index 0000000..4dbe5b7 --- /dev/null +++ b/brainstorm3.m @@ -0,0 +1,26 @@ +DIRS = { + 'I:/mtoolbox/brainstorm3', + 'e:/mtoolbox/brainstorm3', + '/mount/usb/mtoolbox/brainstorm3', + '\\Serveur_meg\homes\mtoolbox\brainstorm3', + '~/mtoolbox/brainstorm3' }; + +for i=1:length(DIRS) + if exist(DIRS{i}, 'dir') + addpath(DIRS{i}) + break; + end + i=i+1; +end + +if i>length(DIRS) + % try to guess where that folder could be + DIRS{i} = fullfile(fileparts(fileparts(mfilename('fullpath'))),'mtoolbox','brainstorm3'); + if exist(DIRS{i},'dir') + addpath(DIRS{i}); + else + error('No directory found for brainstorm3') + end +end +disp(['Launching Brainstorm from: ' DIRS{i}]) +brainstorm \ No newline at end of file diff --git a/brainstorm3/ExtThreshold.zip b/brainstorm3/ExtThreshold.zip new file mode 100644 index 0000000000000000000000000000000000000000..64391c9d2ff045b89a3a8a9217ddaa28d48b9d38 GIT binary patch literal 83824 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 n>E!@=UEBulJTVPx66w58!<=}Wg&N{8Zxw1BUV z7?t0fE!Y7t6V|Ln^_CSdgmNibTUOD)1uCPbRE8qi*LJmW zB#nN)`z!kRULT1>mJuY`T{-x91PIVZAg&}FI~*S$AqF(DkXVgmaD43F{&ZEpRlki! z2(r0%&aQ)Ix~r?JyQ{0Ks;eSnsmkoI$n=M$63|nJ49=3=Di)D~0MwpNFF!~O%P|2$ zmXF1ADD<4aS2v)mBq~y%15W1yXh%98UDAsFf&;064i2q$l23J?V?;q2*kehNdBD-Y z^NYd(u3}|k5{?FV%E5adg9%?gdfvuSmFRl%@Yy8&_ac$V==%)fwo1U0G+I2oi>POk z+_lpTPKk<2;ZclEMH|TZN))c>Mgz($Y&9?s#2_>-`M!+TU~#K^p_jbo6XP`VwR-S? z`sGsKW=Wkv6mzaV$|4Xbp`u7&{GM%e_sqNHfhieSXCBHuJaAM$I z_SYas9&_O*Qt&y9a;PO!C8Ps(=L!97PKM%SAhc3PjUHMa_UU=SqVjWdw-^e``^C#8 zf2{_!7agCnx3_#ZMNbc<3*^%m_BSa){K*OU72n;!eMDX%;j z%1Bwi__;`*V4q2=e)dcp148GP;6-KQy;QR8f!-_#sW;DVsd5vm*5jhR`$)0sM-pz5Elv*(7GgQdOmiC&AvRfbA}9@_N3XC*Y*W28AJ$l_XO~K z)3|=4DmJPSpklkN8-s^zHbR@f(yn9}BE`Uli2|GcrYL2kW|Zf=*We%fZHJZqkHagY zlpy|pl-C&=a151(P~1=kI~9f$kSDAg72Q;6LCVgtkMCWQ89mBzkLX=eJf08GZ!*1} z+?X3ma`+qY`T(O>YEnkl6O>c|pv?7}mhL@oXlq}B-P#9$-Ys57KMLcQbJN$-kD5`i zCirQ$vj-8Rci+ME?l$;D@6rh_?tied;zA%z%{Ze=W|W@-q?5DbU8q}UZ(7Y?S?{RR z?eDqjKw*%rc@B`@pv#{06p1SYp}+Hdltm+3>osyQozJH?%bVF=ubCMQ?i6q3hxJ;y z0*~zFt^BxNDlk2f~xao7N$urpTktRSmj-wT1s~4*HXONe^LVi+BwD(1$)) z5Z4Q$0An1<(6vnHYxZaNRfZ;hct=~%QLxd#J$Fb273o>}R6XNjg6!D`Y<~%xiuI!j z{ZPINoSTg?Ram4mNwhPLkXEd3;p4EL-sPK+H=skC__PoCHKSRcFvwD z#qqOvd!83U(J*Cp^Lw9%<3z`}n2#X)*v<4Zfglnxj95j@N&Uz7k%{RbXvPL-VS9Nc zct|QeIUE7cXZO*LNV3HTv{EwxX7XBj804k+M=YZ7JTEdIGyw`wSrwA8{#~I&d_+%l zoelsaItFjLSb(ow*h{IEnHDQ{b|jM^Ag|ABG_bTgd4m%B7JDC&!l_a`P^8=JpLI`^ zPSDk)$0RXS28sqhjyHU@hCCzS)s{b-y2=Va9$afu?GEw`GsnpwgH5GMs>Y{#1iokU*kLbf zaVXNsQ@{cR%5|X9JbaVs$qrzDGp*kh}YekcvSgP_)*Qcvk3y9PSgIz|G!X zNE=9EVHQSzlhUl-h8ve zI@^WpfND!kz9L}`F@-yZZ!xsU`Bi9fg*c?aNC)F!Xc2l#SR2Wm=*!q7SlyX&P7t=l zMzPqGQ4_ZvTTcdOqZvBZ&V=!t^=Ih1dWFHcMbZu#8?lPkK)8fjVQ6mz`{P|Y9|=cO zZ1`_tEj&B|k26TPf%F?E`w*j^jFqN*xJx7fx9HXJ;A6tFh43((oA1JYI!wmW5milA zm6X(m1COb?!@lSrUtc+EVBO&+F$KVf0-}y*`M&kwsfKP#4|j3u@~X+_q`Xb-zsKE{ z+pxMcQvr8p{7q(>bsZWU&?ecOkFa_jD5;|~T8e_kWH6uJfOiy9HA#m6mwXoOnC~`x*kt5xX0@@-UPW4IAt&S83tY26 zi=;^Iz}LkrfhhenK(M^hV>TD|n);fL_Qumm9vc_meZ~B9Wq%Zm7O|Km|6Zgs$Z5q} zGcvczz_nHPkg>+klG{YPA=~H~CSuV(+CwOK8M6hAPLey}lO!Pq1GkuOB+Z+8f|>~~ z{6LlE&4$Q|RYU%Kx=Akx1H@R4@mxrO0h$GF$32Z$$Lmm|A{Bf>RKPuuT=>;_rk+imF<+;c^_h+9K% zHqqrqYB!c6VJ`(*{=0KY5e#U$o#&_BAGw-*V**LQ9dU zY=$Zz5t|v647m8FR*KG&XK`4ZWKx9I6OFd)-dn)TPYNc+pW(*}8Gt{)y;A{)4~{FN z(?*o^Eb?@JA!}F&@O~H^xSuPLzzas%lnp)&((wS4_W2mOQa@rB+8J@eK`RjTFBB@) z>!A4&+!sS#h5>AhZ$|^W=niFM6i-v3IQ-sddbhWjeS+{)NfG!~h2rh#D*X{>Vk5|A zo!^dMVYN;*FRyjTm_nVx3dP!xCq}#tRA^vmg%!GSGv(6W8;lWHtQ)36u~tA~u+j|E z6^g@eP3C6I3pPRP6mO|{zj{lwPO+9~fVV<4qZNuZ0eZ*b+%1{Ygs2d@H!e3a$q+Lz zSuTe2kRYAqZXy*6(cl$zJ7uTO4b3IEIDa?F7_UA&qKV^KDpg(tQ@Djq;Z_94UF9CR zb%_pVIe{O8=2gT+UZ7&wLZQYBU1*7dZYni{Fpz=E`?eFV#0P(g+hlmIjPaSUQYv(7 zG;j_OQDBanCudsU4%`$14#MR|-i5(e&@US zeKosFC)d^QUb$bYgS&a`b)p=B(TT?@Go4*mFLZLrYC4=wsxAIcdpfvGW<)6v4YBr@ zh?peMQ1(DJ$g=4$9l+IRsnNHU%Ul|qPlwa7lp{f($;Sqgu_X8Na*2LcVx)#0x1p*c z{%tf0(DXx;OcrK`RE**+J8E4p+jg3@!{BNou)!^@l}^yaha$E3>Vzy48HtfKnnmk-?v(?7xO@WXMV_X|CJ zfGH1w!#0NO>{MlRGhX)WzssXa@@b=phc2=JiaE}w>frKHwBwz=k&gY&Y3H!pI6Zpj z#^!Q7-ObBH5Eo*11eT0~e=!F~q}Ms??l~uT!qa_Q zmh|j}<>Cge_Xh=$mWNu{<=vs+Eaj}-?>*8u^!~JQ(C*D^41y`vOKu0Elkw1b#vz1= zGivRuz}+lY-({}AOFV#ohjAtsn{nLBCGGY>*#j*KLklyZ>`n*SN0)c(u$~trC0ks; zre-!Iq(#VcxX_kygnG!VDkH2(Fa(jux_$nfHSdSx1$g5sjkntF^_6#-ivp$17Z}(} z6)o$V->AaMAd9pBVBbHFPKuBZmgUnMDv)JoKgA-8Q}}3Vh8GwYX=QlawTT{wpO!UXkGP#BpHhx1OL>)y?jB_uE+;HN#dtsEKw2vg3s0nRF>!Zz$!OR?yK(5; zn2v5{;j1AM;Blk-3z~kZ5BOoHvu}$G@Q^ieIGq9yLpK0SZm|kC^TsB^mB3nn|eU=RbbR7x*lJ5(8Gf=XIWzW((a?crbhCP!qA!76_ zJVP+h(eCu#%oM5iRrLb%Lr%VmVMvd3plv?-f!4pt_Dv)GuC_oJcz1*o2PK{5YQ;=@rWdXMo5W`t}s z*4R_6afi}*H!+0?%a9yzU9Mng^y^PT)dL2?m6k`g)(o(&eOefYC|8V$g=~sgo2c_B zAXisMm36o6*)NmWRL)B1U}z`>XlC{Qn?_$s)S)a zmCEs4=gMN`lEl;shs#PErT4k8;yOtgTKI#p0l9V+4;bX?q1WuZ+3)5RLtC8!0aZ&N ztOL^!!9L0xPJG{p<;BCqnWSn~s1PFFL@;+@L=o#M3{5{%NHwf55wM7cSdW%84Q%+j zV!Q70;gN=n+_4d`o@8_sy$X$tC>tA_`Cy(qF#LP?hLY0=zzLC@zt5vL|NLl+0f0Rb zxq%ABhz