├── LICENSE
├── README.md
└── matlab
├── example01_exploredata.m
├── example02_loaddata.m
├── example03_understandspaces.m
├── example04_surfacevisualization.m
├── example05_inspectdataatscale.m
├── example06_basicbetaloading.m
├── example07_simplecontrast.m
├── example08_simplecontrastmvpa.m
├── example09_behavioraldata.m
├── example10_encodingmodel.m
├── example11_rsa.m
├── example12_rsfc.m
└── html
├── example01_exploredata.html
├── example02_loaddata.html
├── example02_loaddata.png
├── example02_loaddata_01.png
├── example02_loaddata_02.png
├── example02_loaddata_03.png
├── example02_loaddata_04.png
├── example02_loaddata_05.png
├── example02_loaddata_06.png
├── example02_loaddata_07.png
├── example02_loaddata_08.png
├── example03_understandspaces.html
├── example04_surfacevisualization.html
├── example04_surfacevisualization.png
├── example04_surfacevisualization_01.png
├── example04_surfacevisualization_02.png
├── example04_surfacevisualization_03.png
├── example04_surfacevisualization_04.png
├── example04_surfacevisualization_05.png
├── example04_surfacevisualization_06.png
├── example04_surfacevisualization_07.png
├── example04_surfacevisualization_08.png
├── example05_inspectdataatscale.html
├── example05_inspectdataatscale.png
├── example05_inspectdataatscale_01.png
├── example05_inspectdataatscale_02.png
├── example06_basicbetaloading.html
├── example06_basicbetaloading.png
├── example06_basicbetaloading_01.png
├── example06_basicbetaloading_02.png
├── example06_basicbetaloading_03.png
├── example06_basicbetaloading_04.png
├── example06_basicbetaloading_05.png
├── example06_basicbetaloading_06.png
├── example06_basicbetaloading_07.png
├── example07_simplecontrast.html
├── example07_simplecontrast.png
├── example07_simplecontrast_01.png
├── example07_simplecontrast_02.png
├── example07_simplecontrast_03.png
├── example07_simplecontrast_04.png
├── example07_simplecontrast_05.png
├── example07_simplecontrast_06.png
├── example07_simplecontrast_07.png
├── example08_simplecontrastmvpa.html
├── example08_simplecontrastmvpa.png
├── example08_simplecontrastmvpa_01.png
├── example08_simplecontrastmvpa_02.png
├── example09_behavioraldata.html
├── example09_behavioraldata.png
├── example09_behavioraldata_01.png
├── example09_behavioraldata_02.png
├── example09_behavioraldata_03.png
├── example09_behavioraldata_04.png
├── example10_encodingmodel.html
├── example10_encodingmodel.png
├── example10_encodingmodel_01.png
├── example10_encodingmodel_02.png
├── example10_encodingmodel_03.png
├── example10_encodingmodel_04.png
├── example10_encodingmodel_05.png
├── example10_encodingmodel_06.png
├── example10_encodingmodel_07.png
├── example11_rsa.html
├── example11_rsa.png
├── example11_rsa_01.png
├── example12_rsfc.html
├── example12_rsfc.png
├── example12_rsfc_01.png
├── example12_rsfc_02.png
└── index.html
/README.md:
--------------------------------------------------------------------------------
1 | # nsdexamples
2 |
3 | This repository contains example scripts that load and analyze the NSD data.
4 |
5 | Outputs from the scripts (via MATLAB's publish) are available here:
6 | https://htmlpreview.github.io/?https://github.com/kendrickkay/nsdexamples/blob/master/matlab/html/index.html
7 |
8 | The example scripts in this repository have the following MATLAB dependencies:
9 | - analyzePRF [http://github.com/kendrickkay/analyzePRF/]
10 | - cvncode [http://github.com/kendrickkay/cvncode/]
11 | - GLMdenoise [http://github.com/kendrickkay/GLMdenoise/]
12 | - knkutils [http://github.com/kendrickkay/knkutils/]
13 | - nsdcode [http://github.com/kendrickkay/nsdcode/]
14 | - freesurfer/matlab [matlab functions provided with FreeSurfer]
15 | - NIfTI_20140122 [https://www.mathworks.com/matlabcentral/fileexchange/8797-tools-for-nifti-and-analyze-image]
16 | - xticklabel_rotate [https://www.mathworks.com/matlabcentral/fileexchange/3486-xticklabel_rotate]
17 |
18 | Some of the examples use the following applications:
19 | - FreeSurfer [specifically, the freeview application]
20 | - ITK-SNAP
21 | - HandBrake
22 |
--------------------------------------------------------------------------------
/matlab/example01_exploredata.m:
--------------------------------------------------------------------------------
1 | %% Example 1: Basic exploration of the NSD data files
2 |
3 | %% Introduction
4 |
5 | % In this script, we are going to do some initial exploration of the
6 | % types of data files that are made available as part of the prepared
7 | % NSD data. For this, we are going to use ITK-SNAP (as a volume viewer)
8 | % and freeview (as a surface viewer).
9 | %
10 | % Skills/concepts:
11 | % - How to use ITK-SNAP and freeview
12 | % - The nature of volumes and surfaces
13 | % - The different types of FreeSurfer surfaces
14 | % - MNI and fsaverage spaces
15 |
16 |
17 |
18 | %% Use ITK-SNAP to explore volume-format data
19 |
20 | % Load the following file into ITK-SNAP:
21 | % ~/nsd/nsddata/ppdata/subj01/anat/T1_0pt8_masked.nii.gz
22 | %%
23 |
24 | % Play around in ITK-SNAP and know how to:
25 | % - Navigate / Zoom / Pan
26 | % - Change the colormap and color range
27 | % - View the NIFTI header information (e.g. resolution, origin, orientation)
28 | %%
29 |
30 | % Load as a second image the following file:
31 | % ~/nsd/nsddata/ppdata/subj01/anat/roi/visualsulc.nii.gz
32 | %%
33 |
34 | % Play around and know how to:
35 | % - Flip through different volumes ([ or ])
36 | % - Overlay volumes using a thresholding approach
37 | % - Overlay volumes using transparency
38 | % - Toggle overlays on/off (w)
39 | %%
40 |
41 | % Draw an ROI and save the ROI out as a NIFTI file called:
42 | % testroi.nii.gz
43 | % Then load that file back into ITK-SNAP.
44 | %%
45 |
46 | % Start over. Load T1_0pt8_masked.nii.gz as the main image,
47 | % and load roi/visualsulc.nii.gz as the segmentation image.
48 | %%
49 |
50 | % Using Label Editor, hide all of the labels and show only
51 | % Label 3 (fusiform gyrus). Play around with the 3D volume rendering.
52 | % Information on the meaning of the values in visualsulc is in:
53 | % ~/nsd/nsddata/freesurfer/fsaverage/label/visualsulc.mgz.ctab
54 | %%
55 |
56 | % Quit ITK-SNAP and try launching it from the command-line:
57 | % cd ~/nsd/nsddata/ppdata/subj01/anat/
58 | % itksnap T1_0pt8_masked.nii.gz
59 | %%
60 |
61 | % There are many other volume viewers out there.
62 | % If you are curious, you can try, for example, mricro.
63 |
64 |
65 |
66 | %% Inspect NSD anatomical data (anat)
67 |
68 | % The prepared NSD data come in 3 anatomical spaces (0.5 mm, 0.8 mm, and 1.0 mm).
69 | % These different spaces are exactly coincident and share exactly the same
70 | % field-of-view and origin. (The origin is placed at the center of each volume.)
71 |
72 | % Load the 0.5-mm and 1.0-mm T1 volumes as a main image and an additional image,
73 | % respectively. Compare the two volumes.
74 | %%
75 |
76 | % Load the 0.5-mm and 1.0-mm T1 volumes in two separate ITK-SNAP sessions with
77 | % synchronized views (see Preferences). Play around a little.
78 | %%
79 |
80 | % Assess the co-registration quality between the 0.8-mm T1 and 0.8-mm T2.
81 | % Notice that compared to the T2, the T1 has a little bit of dropout in
82 | % the ventral part of the frontal lobe.
83 | %%
84 |
85 | % Inspect the Kastner atlas (roi/Kastner2015.nii.gz) overlaid on the 0.8-mm T1.
86 | % Information on the meaning of the values is in:
87 | % ~/nsd/nsddata/freesurfer/fsaverage/label/Kastner2015.mgz.ctab
88 | %%
89 |
90 | % Explore and visualize some additional volumes:
91 | % - aseg (anatomical segmentation from FreeSurfer)
92 | % - brainmask (liberal brain mask used to de-identify data and reduce file sizes)
93 | % - EPI_to_anat1pt0 (mean EPI volume that has been warped to the anat1pt0 space)
94 | %%
95 |
96 | % Thus far, we have been visualizing data from a single subject.
97 | % To facilitate comparison across subjects, we can put subjects in a common space.
98 | % A typical one is MNI space, and the prepared NSD data already include some
99 | % MNI transformations for your convenience.
100 |
101 | % To determine the transformation between an individual subject and MNI space,
102 | % the T1 from that subject has been nonlinearly warped to an MNI template.
103 | % Furthermore, a version of each subject's T1 that reflects this warping (and
104 | % resampling) has been saved. Let's use ITK-SNAP to quickly look at the MNI
105 | % template and each individual subject's warped T1 volume.
106 | % cd ~/nsd/nsddata/
107 | % itksnap -g templates/MNI152_T1_1mm.nii.gz -o ppdata/subj*/anat/T1_to_MNI.nii.gz
108 | % How good is the alignment?
109 | %%
110 |
111 |
112 |
113 | %% Inspect NSD functional data (func1pt8mm)
114 |
115 | % The prepared NSD data come in 2 functional spaces (1.8 mm, 1.0 mm).
116 | % These spaces are aligned to each other, but have slightly different field-of-views
117 | % and origins (see 'nsddata description' for details). Note that the functional spaces
118 | % are not the same as the anatomical spaces.
119 |
120 | % Load floc_facestval.nii.gz (t-value for the contrast of faces vs. non-faces from
121 | % the floc category localizer experiment) and overlay it on mean.nii.gz.
122 | %%
123 |
124 | % Alternatively, overlay the t-values on T1_to_func1pt8mm.nii.gz (this is a
125 | % version of the T1 that has already been warped to the func1pt8mm space).
126 | %%
127 |
128 | % Explore and visualize some additional volumes:
129 | % - R2 (a measure of signal quality from the NSD experiment)
130 | % - prf_eccentricity (estimate of pRF eccentricity from the prf experiment)
131 | % - valid (fraction of NSD scan sessions in which valid data were recorded)
132 | % - R2_session??.nii.gz (R2 from individual NSD scan sessions)
133 | % Hint: itksnap -g mean.nii.gz -o R2_*.nii.gz
134 | %%
135 |
136 |
137 |
138 | %% Use freeview to explore surface-format data
139 |
140 | % Compared to volume data, surface data are a bit trickier to
141 | % visualize and manage. We will use FreeSurfer's freeview as
142 | % a simple surface viewer, though there are many alternatives.
143 |
144 | % Load subj01's left hemisphere inflated surface:
145 | % ~/nsd/nsddata/freesurfer/subj01/surf/lh.inflated
146 | %%
147 |
148 | % Play around in freeview and know how to:
149 | % - Navigate / Rotate / Zoom / Pan
150 | %%
151 |
152 | % Load as an overlay (generic):
153 | % ~/nsd/nsddata/freesurfer/subj01/label/lh.flocfacestval.mgz
154 | %%
155 |
156 | % Play around and know how to:
157 | % - Change the colormap and color range
158 | %%
159 |
160 | % Set the Render to 'Surface & mesh' to see the faces and vertices
161 | % that comprise a surface.
162 | %%
163 |
164 | % Set the Render back to Surface and load in:
165 | % ~/nsd/nsddata/freesurfer/subj01/label/lh.visualsulc.mgz
166 | % Develop a good visualization of this atlas.
167 | % Save a screenshot.
168 | %%
169 |
170 | % Now visualize the same data (visualsulc) on some
171 | % other versions of the cortical surface:
172 | % lh.white, lh.pial, lh.sphere
173 | % Use the same color settings as in the lh.inflated case and
174 | % save some screenshots to facilitate comparison.
175 | %%
176 |
177 | % Finally, to illustrate the folding-based registration
178 | % that underlies FreeSurfer's fsaverage surface, take
179 | % screenshots of the following (be careful to keep
180 | % the camera view fixed):
181 | % (1) subj01's lh.sphere - curvature
182 | % (2) subj01's lh.sphere - Kastner2015
183 | % (3) subj01's lh.sphere.reg - curvature
184 | % (4) subj01's lh.sphere.reg - Kastner2015
185 | % (5) fsaverage's lh.sphere - curvature
186 | % (6) fsaverage's lh.sphere - Kastner2015
187 | % Compare these screenshots and notice how sphere.reg is a
188 | % surface such that visualizing subj01's curvature on that surface
189 | % yields a map that looks well matched to fsaverage's curvature.
190 | %%
191 |
192 | % Like MNI, fsaverage is a common space that can be used to
193 | % compare results across subjects. MNI is a volume-based space
194 | % that is semi-accurate for cortex and is most accurate for
195 | % subcortical structures. In contrast, fsaverage is a surface-based
196 | % space that is applicable only to cortex.
197 |
198 |
199 |
--------------------------------------------------------------------------------
/matlab/example02_loaddata.m:
--------------------------------------------------------------------------------
1 | %% Example 2: Loading data into MATLAB
2 |
3 | %% Introduction
4 |
5 | % In this script, we are going to go through examples of how
6 | % to load various types of data files into MATLAB. Loading data
7 | % is the first step before we can do data analysis.
8 | %
9 | % Skills/concepts:
10 | % - File formats
11 | % - Thinking about data formats (e.g. double, single, int16)
12 | % - Issues specific to NIFTI files
13 | % - Issues specific to surfaces
14 | % - HDF5-related concepts
15 |
16 |
17 |
18 | %% Miscellaneous data (.mat format)
19 |
20 | % MATLAB's .mat format is a catch-all format that can hold
21 | % all sorts of different kinds of data.
22 |
23 | % Load information pertaining to the experimental
24 | % design of the NSD experiment. By assigning the output
25 | % of load.m to a variable, we encapsulate the loaded variables
26 | % into a struct (thus, leaving the workspace uncluttered).
27 | a1 = load('~/nsd/nsddata/experiments/nsd/nsd_expdesign.mat');
28 | a1
29 | %%
30 |
31 |
32 |
33 | %% Text data (.tsv format)
34 |
35 | % Although inefficient in terms of disk space, it is sometimes
36 | % convenient to store data in text format. The main advantage is that text
37 | % is easy to open (e.g. using a text editor or a spreadsheet program).
38 | %
39 | % A common format is .tsv (tab-separated values), which is similar
40 | % to .csv (comma-separated values).
41 | %
42 | % Typical things to watch out for include: making sure that any descriptive header rows
43 | % are ignored or handled appropriately, making sure that missing data (blanks or NaNs)
44 | % are handled correctly, making sure that numerical precision is handled correctly,
45 | % and making sure that any beginning or ending blank lines are not erroneously
46 | % treated as data entries.
47 |
48 | % Load in motion parameters for one NSD run. Note that in this case,
49 | % the text file consists of purely numeric data and has no header rows;
50 | % thus, we can simply use load.m to get the data.
51 | file0 = '~/nsd/nsddata_timeseries/ppdata/subj01/func1pt8mm/motion/motion_session21_run10.tsv';
52 | a1 = load(file0);
53 | size(a1) % 226 volumes x 6 motion parameters
54 | %%
55 | figure; plot(a1(:,1));
56 | %%
57 |
58 | % Load in behavioral data for one subject. Here we use importdata.m,
59 | % which incorporates some assumptions and returns the data in a
60 | % somewhat structured format (which is nice). However, because of the
61 | % assumptions, it is always a good idea to check the validity of the results.
62 | file0 = '~/nsd/nsddata/ppdata/subj01/behav/responses.tsv';
63 | a1 = importdata(file0);
64 | a1
65 | %%
66 | a1.colheaders
67 | %%
68 |
69 | % Let's use an alternative method to load in the behavioral data.
70 | % In this method, we will use low-level routines that provide greater
71 | % degree of control over the loading process.
72 | fid = fopen(file0);
73 | a2 = textscan(fid,repmat('%f',[1 19]),'Delimiter','\t','HeaderLines',1);
74 | fclose(fid);
75 | a2
76 | %%
77 |
78 | % Are the two loading schemes equivalent?
79 | isequal(a1.data,cat(2,a2{:}))
80 | %%
81 |
82 | % Let's try that again. The trick is to realize that NaN does
83 | % not equal NaN according to == or isequal.m, but does according
84 | % to isequalwithequalnans.m. (Be careful!)
85 | isequalwithequalnans(a1.data,cat(2,a2{:}))
86 | %%
87 |
88 |
89 |
90 | %% Volume data as NIFTI files (.nii format)
91 |
92 | % NIFTI is a common neuroimaging format that is used to store 3D
93 | % (e.g. X x Y x Z) and 4D (e.g. X x Y x Z x T) volumes.
94 |
95 | % Here, we will use a function from the NIfTI_20140122 toolbox
96 | % in order to load in a volume of t-values (faces vs. non-faces).
97 | a1 = load_untouch_nii('~/nsd/nsddata/ppdata/subj01/func1pt8mm/floc_facestval.nii.gz');
98 | a1
99 | %%
100 |
101 | % Some header fields that may be of interest include:
102 | a1.hdr.dime.dim % the volume has matrix size 81 x 104 x 83
103 | %%
104 | a1.hdr.dime.pixdim % the voxel size is 1.8-mm isotropic; the temporal resolution is 1.333 s
105 | %%
106 |
107 | % Importantly, the actual data is stored in a1.img.
108 | % Notice that the data have been saved in single format
109 | % (which occupies half the space compared to double format).
110 | a1
111 | %%
112 |
113 | % Let's visualize one slice of the data
114 | figure; imagesc(a1.img(:,:,34),[0 10]); colormap(hot); colorbar; axis image;
115 | %%
116 |
117 | % As a matter of convention, all prepared NSD data are saved in LPI,
118 | % meaning that the first voxel is left, posterior, and inferior.
119 | % In the following animation, we can check that this is the case.
120 | if 0
121 | for p=1:size(a1.img,3), p
122 | imagesc(a1.img(:,:,p),[0 10]);
123 | colormap(hot); colorbar;
124 | axis image;
125 | pause;
126 | end
127 | end
128 |
129 | % One more example. Here, we load a volume of ROI labels for visual cortex.
130 | % These are in the 0.8-mm anatomical space (matrix size is 320 x 320 x 320).
131 | a1 = load_untouch_nii('~/nsd/nsddata/ppdata/subj01/anat/roi/lh.Kastner2015.nii.gz');
132 | a1
133 | %%
134 |
135 | % Visualize and check that it makes sense.
136 | figure; imagesc(a1.img(:,:,150),[0 25]); colormap(jet); axis image;
137 | %%
138 |
139 | % You may also want to load the exact same file in ITK-SNAP to compare.
140 |
141 |
142 |
143 | %% Surface data as MGZ files (.mgz format)
144 |
145 | % FreeSurfer uses .mgh/.mgz format for its internal file storage.
146 | % Although this format can store both volume and surface data, the prepared
147 | % NSD data make use of the .mgz format just to store surface data.
148 | %
149 | % Surface data do not have an easy ordering scheme, so surface data
150 | % can essentially be thought of as a vector of values that correspond
151 | % to the vertices that make up a surface. FreeSurfer generally keeps
152 | % left and right hemispheres separate; hence, for a complete set of
153 | % surface data, we typically use two .mgz files (e.g. lh.XXX.mgz
154 | % and rh.XXX.mgz).
155 |
156 | % Load in curvature values for the left hemisphere of one subject.
157 | % We use cvnloadmgz.m (which is just a wrapper around load_mgh.m,
158 | % a function provided by FreeSurfer).
159 | a1 = cvnloadmgz('~/nsd/nsddata/freesurfer/subj01/surf/lh.curvature.mgz');
160 | size(a1) % vertices x 1
161 | %%
162 | figure; hist(a1(:),100); % gyri are < 0; sulci are > 0
163 | %%
164 |
165 | % Load in the Kastner2015 ROI labeling for the left hemisphere
166 | % of the fsaverage surface. Notice that the number of vertices in the
167 | % fsaverage surface is special and canonical.
168 | a1 = cvnloadmgz('~/nsd/nsddata/freesurfer/fsaverage/label/lh.Kastner2015.mgz');
169 | size(a1) % vertices x 1
170 | %%
171 | union(a1(:),[])' % values are integers 0-25
172 | %%
173 |
174 |
175 |
176 | %% Betas as HDF5 files (.hdf5 format)
177 |
178 | % In the prepared NSD data, the primary data of interest are the beta weights
179 | % (fMRI response amplitudes) obtained for each trial. These betas are provided
180 | % in both NIFTI format as well as HDF5 format. The primary advantage of
181 | % HDF5 format is that it supports random access, and therefore can greatly
182 | % speed up loading of the data.
183 |
184 | % Use h5read.m to load in betas from the 15th NSD scan session. There are 750
185 | % trials in each session, and so we will be loading in 750 betas.
186 | % We load in data for only 5 voxels: we start at voxel (10,10,10) and load
187 | % in voxels 10 through 14 along the 3rd dimension.
188 | file0 = '~/nsd/nsddata_betas/ppdata/subj01/func1pt8mm/betas_fithrf/betas_session15.mat';
189 | data = h5read(file0,'/betas',[10 10 10 1],[1 1 5 750]);
190 | size(data)
191 | %%
192 | class(data) % note that the data are stored in int16 format
193 | %%
194 |
195 | % Alternatively, we can use matfile.m as the loading mechanism. Compared to load.m,
196 | % the primary draw of matfile.m is that variables are not actually loaded
197 | % from disk until you specifically request them. In addition, matfile.m
198 | % supports random access (like h5read.m). In the code below, notice that we explicitly
199 | % cast the data to double format and then divide by 300 in order to convert
200 | % the betas to units of percent signal change.
201 | data = []; % 10 x 10 x 10 x 750 trials x 40 sessions
202 | for p=1:40, p
203 | file0 = sprintf('~/nsd/nsddata_betas/ppdata/subj01/func1pt8mm/betas_fithrf/betas_session%02d.mat',p);
204 | a1 = matfile(file0);
205 | tic;
206 | data(:,:,:,:,p) = double(a1.betas(41:50,3:12,35:44,:))/300;
207 | toc;
208 | end
209 | %%
210 | size(data)
211 | %%
212 |
213 | % Let's average the betas across trials and sessions, and then look at the result.
214 | temp = mean(mean(data,4),5);
215 | figure; imagesc(makeimagestack(temp),[-10 10]); axis image; colormap(cmapsign4); colorbar;
216 | %%
217 |
218 | % We might want to look at the results using ITK-SNAP, which might be more intuitive/easy.
219 | % To do that, we need to write the result to a NIFTI file.
220 | nsd_savenifti(temp,[1.8 1.8 1.8],'test.nii.gz');
221 |
222 | % Load the test.nii.gz file into ITK-SNAP and see if you can convince yourself
223 | % that the two visualizations are consistent. Note that the function makeimagestack.m
224 | % extracts slices from the third dimension and places successive images downwards
225 | % and then rightwards. In addition, remember that the NSD volumes are saved in LPI
226 | % order. Thus, the orientation of the slices from makeimagestack.m do not match
227 | % the orientation of the first panel in ITK-SNAP (which is in "AR" ordering).
228 | % To make the figure from makeimagestack.m match ITK-SNAP, one can do the following:
229 | fun = @(x) flipdim(flipdim(permute(x,[2 1 3]),1),2);
230 | figure; imagesc(makeimagestack(fun(temp)),[-10 10]); axis image; colormap(cmapsign4); colorbar;
231 | %%
232 |
233 |
234 |
235 | %% Images as PNG files (.png format)
236 |
237 | % The .png file format is a lossless image format that is commonly
238 | % used for everyday computer tasks. It is convenient because your OS
239 | % can probably just open it and view it. However, the .png format does
240 | % not store multiple images, and having to keep track of large numbers
241 | % of files can cause severe slowdowns.
242 |
243 | % Load in one specific image
244 | file0 = '~/nsd/nsddata/stimuli/nsd/shared1000/shared0001_nsd02951.png';
245 | im = imread(file0); % 425 pixels x 425 pixels x 3 (uint8 format)
246 | size(im)
247 | %%
248 | figure; imshow(im);
249 | %%
250 | class(im)
251 | %%
252 |
253 | % Load in many images
254 | stimfiles = matchfiles('~/nsd/nsddata/stimuli/nsd/shared1000/*.png');
255 | im = zeros(425,425,3,1000,'uint8');
256 | for p=1:length(stimfiles)
257 | statusdots(p,length(stimfiles));
258 | im(:,:,:,p) = imread(stimfiles{p});
259 | end
260 | size(im)
261 | %%
262 |
263 |
264 |
265 | %% Images in HDF5 format (.hdf5 format)
266 |
267 | % The NSD experiment involves a large number of images (73,000). We elected
268 | % to store these images in uint8 format in a single, very large .hdf5 file
269 | % in order to facilitate access.
270 |
271 | % Load in the 2951st NSD image
272 | stimfile = '~/nsd/nsddata_stimuli/stimuli/nsd/nsd_stimuli.hdf5';
273 | im = h5read(stimfile,'/imgBrick',[1 1 1 2951],[3 425 425 1]);
274 | im = permute(im,[3 2 1]); % 425 pixels x 425 pixels x 3 (uint8 format)
275 | figure; imshow(im);
276 | %%
277 | class(im)
278 | %%
279 |
280 |
281 |
282 | %% Cortical surfaces in native FreeSurfer format (lh.white, lh.pial, etc.)
283 |
284 | % Use freesurfer_read_surf_kj.m (which is just a wrapper around
285 | % the freesurfer_read_surf.m function provided by FreeSurfer) to
286 | % read in the left hemisphere white-matter surface of one subject.
287 | [vertices,faces] = freesurfer_read_surf_kj('~/nsd/nsddata/freesurfer/subj01/surf/lh.white');
288 | size(vertices) % vertices x 3 coordinates
289 | %%
290 | size(faces) % faces x 3 vertex indices
291 | %%
292 |
293 | % Let's also load in the inflated version of this surface.
294 | [vertices2,faces2] = freesurfer_read_surf_kj('~/nsd/nsddata/freesurfer/subj01/surf/lh.inflated');
295 |
296 | % Notice that the white and inflated surfaces have exactly the same number of vertices
297 | isequal(size(vertices),size(vertices2))
298 | %%
299 |
300 | % Also, the white and inflated surfaces have the exact same faces
301 | isequal(faces,faces2)
302 | %%
303 |
304 | % We can also use cvncode's cvnreadsurface.m to do the loading (which
305 | % is yet another wrapper). Note that the function relies on having the
306 | % FreeSurfer environment variable SUBJECTS_DIR correctly set.
307 | % (That is how it knows where to find the surface files.)
308 | getenv('SUBJECTS_DIR')
309 | %%
310 | surfL = cvnreadsurface('subj01','lh','inflated',[]);
311 | surfL
312 | %%
313 |
314 |
315 |
316 | %% Surface-based ROI files (various formats such as .mgz, .annot, .label)
317 |
318 | % FreeSurfer outputs a variety of different ROI files; in addition,
319 | % the prepared NSD data includes a number of ROIs. To facilitate access
320 | % to these ROIs, we can use the function cvnroimask.m.
321 |
322 | % Load in the Kastner 2015 atlas that has been prepared and mapped onto
323 | % subj01's cortical surface. Here we load in ROI labelings for the left hemisphere.
324 | [roimask,roidescription] = cvnroimask('subj01','lh','Kastner2015');
325 |
326 | % There are 25 ROIs in the atlas; each element of roimask is a binary vector.
327 | roimask
328 | %%
329 |
330 | % A set of names are available too.
331 | roidescription
332 | %%
333 |
334 | % The names can also be read using this alternative method:
335 | file0 = '~/nsd/nsddata/freesurfer/subj01/label/Kastner2015.mgz.ctab';
336 | roidescription2 = read_ctab(file0);
337 | roidescription2
338 | %%
339 |
340 | % Or directly as a text file:
341 | txt = loadtext(file0);
342 | txt
343 | %%
344 |
345 |
346 |
--------------------------------------------------------------------------------
/matlab/example03_understandspaces.m:
--------------------------------------------------------------------------------
1 | %% Example 3: Understand mapping between spaces
2 |
3 | %% Introduction
4 |
5 | % nsd_mapdata.m is a utility function provided with NSD
6 | % that performs mapping between various NSD spaces (e.g. functional,
7 | % anatomical, surface). Please see nsdcode/matlab/examples_nsdmapdata.m
8 | % for a comprehensive example of how to use that function.
9 | %
10 | % Skills/concepts:
11 | % - How to use nsd_mapdata.m
12 | % - Interpolation/resampling
13 | % - Mapping between volume and surface
14 | % - Mapping between individual subjects and fsaverage
15 |
16 |
17 |
--------------------------------------------------------------------------------
/matlab/example04_surfacevisualization.m:
--------------------------------------------------------------------------------
1 | %% Example 4: Automated surface visualization
2 |
3 | %% Introduction
4 |
5 | % cvnlookup.m is a function in the cvncode repository that enables
6 | % automated surface visualization (no manual intervention). This is
7 | % useful for large-scale analyses where manual intervention would
8 | % be prohibitively slow. Below, we provide some simple examples
9 | % of how to use the function. These examples include how to use
10 | % the function to draw an ROI on a surface.
11 | %
12 | % There are many different tools that can visualize surfaces, and
13 | % we encourage the user to decide what is best for their needs.
14 | % The major disadvantage of cvnlookup.m is that it has no GUI and
15 | % is not designed for interactive use (e.g. rotating/panning/zooming).
16 | %
17 | % Skills/concepts:
18 | % - How to use cvnlookup.m
19 | % - How to draw ROIs on surfaces
20 | % - Issues when mapping volume to surface
21 |
22 |
23 |
24 | %% Example use of cvnlookup.m
25 |
26 | % Note that a pre-requisite for using cvnlookup.m is to have your
27 | % FreeSurfer SUBJECTS_DIR environment variable set correctly.
28 | getenv('SUBJECTS_DIR')
29 | %%
30 |
31 | % Let's visualize the Kastner2015 atlas on subj01
32 | data = '~/nsd/nsddata/freesurfer/subj01/label/lh.Kastner2015.mgz';
33 | cvnlookup('subj01',1,data,[0 25],jet(256),0.5);
34 | %%
35 |
36 | % Note that we automatically get some variables (rawimg,Lookup,rgbimg,himg)
37 | % assigned to the workspace (see 'help cvnlookup').
38 |
39 | % We can directly call imagesc on rawimg. This might be useful
40 | % for quickly playing around with colormaps and color ranges.
41 | figure; imagesc(rawimg,[0 25]); colormap(jet(256)); axis image;
42 | %%
43 |
44 |
45 |
46 | %% Draw an ROI
47 |
48 | % Let's call cvnlookup again and explicitly get the outputs.
49 | % In this call, we will threshold away all of the values,
50 | % leaving just the curvature showing.
51 | [rawimg,Lookup,rgbimg,himg] = cvnlookup('subj01',1,data,[0 25],jet(256),25.5);
52 | %%
53 |
54 | % Manually draw an ROI on the left hemisphere near the tip of the calcarine sulcus.
55 | % Note: drawroipoly.m is valid only on spherical surfaces.
56 | if 0
57 | Rmask = drawroipoly(himg,Lookup);
58 | end
59 |
60 | % To allow automated execution, we embed the above code in an if-statement
61 | % and provide the following hard-coded values:
62 | Rmask = zeros(453622,1);
63 | Rmask([3635 3653 3654 3666 4269 4285 4286 4305 4306 4318 4319 4338 4339 4358 4375 4393 4408 4994 4995 5009 5010 5027 5028 5044 5045 5066 5076 5092 5107 5120 5708 5709 5710 5719 5720 5721 5733 5734 5735 5749 5750 5770 5791 5805 5806 5825 5826 5841 5842 5856 5857 5869 5870 6493 6503 6504 6515 6516 6534 6535 6557 6558 6571 6572 6594 6595 6616 6632 6647 7353 7354 7355 7366 7367 7387 7388 7410 7423 7441 7455 7469 7481 8173 8174 8188 8213 8214 8231 8232 8249 8269 8285 8286 8295 8296 8306 8307 8317 8318 8998 9019 9046 9062 9063 9084 9108 9128 9141 9151 9159]) = 1;
64 |
65 | % Rmask is a (numlh+numrh)x1 binary mask
66 | size(Rmask)
67 | %%
68 |
69 | % Let's visualize the ROI in order to check it
70 | extraopts = {'roicolor','k','roimask',Rmask};
71 | cvnlookup('subj01',1,data,[0 25],jet(256),0.5,[],[],extraopts);
72 | %%
73 |
74 | % We can also directly visualize the ROI as if it were data
75 | cvnlookup('subj01',1,Rmask,[0 1],gray);
76 | %%
77 |
78 |
79 |
80 | %% Save the ROI
81 |
82 | % This is a little tricky because the ROI data reflects both hemispheres concatenated,
83 | % whereas we typically want to save .mgz files that reflect one hemisphere at a time.
84 |
85 | % First, create a valstruct and put the ROI inside it
86 | valstruct = valstruct_create('subj01');
87 | valstruct = setfield(valstruct,'data',Rmask);
88 | valstruct
89 | %%
90 |
91 | % Now, we can save an .mgz file
92 | fsdir = '~/nsd/nsddata/freesurfer/subj01';
93 | nsd_savemgz(valstruct_getdata(valstruct,'lh'),'lh.testA.mgz',fsdir);
94 |
95 | % Note that lh.testA.mgz can be fed back as data into cvnlookup (even though it is just
96 | % one hemisphere, the code automatically fills in NaNs for the other hemisphere).
97 | cvnlookup('subj01',1,'lh.testA.mgz',[0 1],gray,0.5);
98 | %%
99 |
100 |
101 |
102 | %% Example of automated surface visualization
103 |
104 | % We will load in each subject's curvature values, map them (via nearest-neighbor)
105 | % to fsaverage, and then generate a visualization of the results.
106 |
107 | % define
108 | subjs = {'subj01' 'subj02' 'subj03' 'subj04' 'subj05' 'subj06' 'subj07' 'subj08' 'fsaverage'};
109 | hemis = {'lh' 'rh'};
110 |
111 | % do it
112 | for p=1:length(subjs)
113 | subjectid = subjs{p};
114 |
115 | % for individual subjects, we have to map the curvature values to fsaverage
116 | if p <= 8
117 |
118 | % loop over hemispheres
119 | data = [];
120 | for h=1:length(hemis)
121 | inputfile = sprintf('~/nsd/nsddata/freesurfer/%s/surf/%s.curvature.mgz',subjectid,hemis{h});
122 | data = [data; nsd_mapdata(p,sprintf('%s.white',hemis{h}),'fsaverage',inputfile)];
123 | end
124 |
125 | % for fsaverage, we just have to load in the curvature values
126 | else
127 |
128 | % load the curvature values from both hemispheres
129 | inputfile = sprintf('~/nsd/nsddata/freesurfer/%s/surf/??.curvature.mgz',subjectid);
130 | data = cvnloadmgz(inputfile);
131 |
132 | end
133 |
134 | % perform lookup and save to .png file
135 | outputfile = sprintf('curvature_%s.png',subjectid);
136 | [rawimg,Lookup,rgbimg,himg] = cvnlookup('fsaverage',1,data < 0,[-1 2],gray(256),[],[],0);
137 | imwrite(rgbimg,outputfile);
138 |
139 | end
140 |
141 | % visualize
142 | figure; imshow('curvature_subj01.png');
143 | %%
144 |
145 | % Review all of the .png files that are written out. Confirm that the
146 | % curvature-based alignment of the different subjects to fsaverage is
147 | % of reasonable quality. An effective method for this is to quickly
148 | % flip between the various figures.
149 | %%
150 |
151 |
152 |
153 | %% Example of surface visualization from volume data
154 |
155 | % The official spaces for the prepared NSD betas are the functional
156 | % 1.8-mm space and the functional 1.0-mm space. This has the
157 | % consequence that if analyses are performed for the data in these
158 | % spaces, in order to generate surface visualizations of the results,
159 | % one must map from the functional volume spaces to the cortical
160 | % surface. Given the high-resolution of the functional data, we
161 | % prepare 3 different cortical surfaces (layerB1, layerB2, layerB3)
162 | % that exist at different depths through the gray matter. Here,
163 | % we show an example of how one might map from the functional volume
164 | % spaces to the cortical surface so that a surface visualization can
165 | % be made.
166 | %
167 | % Note that some provided data files are already mapped to the cortical
168 | % surface of each subject; for such data, there is no need to map to the surface.
169 |
170 | % define
171 | hemis = {'lh' 'rh'};
172 | subjix = 1;
173 | file0 = '~/nsd/nsddata/ppdata/subj01/func1pt8mm/prf_eccentricity.nii.gz';
174 |
175 | % load data
176 | a1 = load_untouch_nii(file0);
177 |
178 | % map to the three depth surfaces using cubic interpolation
179 | tdata = {};
180 | for q=1:length(hemis)
181 | for d=1:3
182 | tdata{q}(:,d) = nsd_mapdata(subjix,'func1pt8',sprintf('%s.layerB%d',hemis{q},d),a1.img,'cubic',[],[],[],[]);
183 | end
184 | end
185 |
186 | % average across depth and concatenate into a vector
187 | data = cat(1,mean(tdata{1},2),mean(tdata{2},2));
188 |
189 | % generate surface visualization
190 | cvnlookup(sprintf('subj%02d',subjix),1,data,[0 5],jet(256));
191 | %%
192 |
193 | % There are some issues that should be considered when performing the mapping
194 | % from volume to surface. One issue is the choice of interpolation (e.g. nearest vs.
195 | % linear vs. cubic), as this choice will indeed affect the results. Keep in mind that
196 | % different types of data may be more or less suitable for interpolation. For example,
197 | % circular quantities will need to be specially handled. Another issue is how
198 | % cortical depth is handled. In the example above, we simply average across
199 | % the three depths, but the user may want to handle this differently. A third issue
200 | % is that one needs to be careful about any potential non-finite values in the data
201 | % volume being mapped. Such values may propagate depending on the interpolation choice.
202 | % A fourth issue is that the mapping from volume to surface is in general a lossy
203 | % process (i.e. interpolation involves data modification and potential resolution loss),
204 | % so one should be careful in that regard.
205 |
206 |
207 |
--------------------------------------------------------------------------------
/matlab/example05_inspectdataatscale.m:
--------------------------------------------------------------------------------
1 | %% Example 5: Inspect data at scale
2 |
3 | %% Introduction
4 |
5 | % In this script, we demonstrate two different ways to quickly and effectively
6 | % inspect large amounts of data. The first way is to use a high framerate
7 | % movie, thus exploiting the rapidity of our visual system. The second way is
8 | % to use an image to visualize many time series at once.
9 | %
10 | % Skills/concepts:
11 | % - Manipulation of images, volumes, and movies
12 | % - Image normalization and color ranges
13 | % - aseg (from FreeSurfer)
14 |
15 |
16 |
17 | %% Make a movie of brain volumes over time
18 |
19 | % In this example, we will inspect all of the pre-processed fMRI time-series data
20 | % from the first NSD session from subj01. Knowing how to use low-level routines
21 | % to create the movie is useful as it allows you to customize the movie to your needs.
22 |
23 | % define
24 | files = '~/nsd/nsddata_timeseries/ppdata/subj01/func1pt8mm/timeseries/timeseries_session01_*.nii.gz';
25 | outfile = 'testmovie'; % we will add file extensions later
26 | fps = 72; % frames per second for the movie
27 |
28 | % find the files
29 | files0 = matchfiles(files);
30 |
31 | % load the data from each run and construct orthographic image inspections
32 | im = uint8([]);
33 | cnt = 1;
34 | for p=1:length(files0)
35 |
36 | % load data and convert to double
37 | data0 = load_untouch_nii(files0{p});
38 | data0 = double(data0.img);
39 |
40 | % for each volume, make an image
41 | for r=1:size(data0,4)
42 |
43 | % pull out middle slice in all three dimensions
44 | imA = []; % 120 x 120 x 3
45 | imA = cat(3,imA,placematrix(zeros(120,120), squeeze(data0(:,:,round(end/2),r))));
46 | imA = cat(3,imA,placematrix(zeros(120,120),rotatematrix(squeeze(data0(:,round(end/2),:,r)),1,2,1)));
47 | imA = cat(3,imA,placematrix(zeros(120,120),rotatematrix(squeeze(data0(round(end/2),:,:,r)),1,2,1)));
48 |
49 | % normalize, make a mosaic, and convert to uint8
50 | im(:,:,cnt) = uint8(255*makeimagestack(imA,[0 2000],1,[1 3]));
51 | cnt = cnt + 1;
52 |
53 | % the first ten volumes of each run get marked by a little white square
54 | if r <= 10
55 | im(1:4,1:4,end) = 255;
56 | end
57 |
58 | end
59 |
60 | end
61 |
62 | % visualize one image
63 | figure; imshow(im(:,:,1));
64 | %%
65 |
66 | % write all of the images to a single .mov file
67 | imagesequencetomovie(im,sprintf('%s.mov',outfile),fps);
68 |
69 | % make a compressed version of the movie (mpeg-4 format)
70 | unix_wrapper(sprintf('HandBrakeCLI -i %s.mov -q 2 --strict-anamorphic -o %s.m4v',outfile,outfile));
71 |
72 | % note that instead of QTWriter (used by imagesequencetomovie.m), we could perhaps use
73 | % other utilities such as built-in MATLAB toolbox functions and/or ffmpeg.
74 |
75 |
76 |
77 | %% Use a carpet plot (Power NeuroImage 2017) to quickly inspect time-series data
78 |
79 | % In this example, we will use a carpet plot to visualize the same data as above.
80 |
81 | % define
82 | files = '~/nsd/nsddata_timeseries/ppdata/subj01/func1pt8mm/timeseries/timeseries_session01_*.nii.gz';
83 | asegfile = '~/nsd/nsddata/ppdata/subj01/func1pt8mm/aseg.nii.gz';
84 |
85 | % define more
86 | grayix = [42 3]; % gray matter (rh is 42, lh is 3)
87 | wmix = [41 2]; % white matter (rh is 41, lh is 2)
88 | csfix = 24; % CSF
89 | maxplot = 500; % maximum number of voxels to plot from each compartment
90 |
91 | % use aseg to decide which voxels to extract
92 | aseg = load_untouch_nii(asegfile);
93 | grayvx = picksubset(find(ismember(aseg.img,grayix)),maxplot);
94 | wmvx = picksubset(find(ismember(aseg.img,wmix)),maxplot);
95 | csfvx = picksubset(find(ismember(aseg.img,csfix)),maxplot);
96 |
97 | % calculate row bounds
98 | rowbounds = [length(grayvx) length(wmvx) length(csfvx)];
99 | rowbounds = cumsum(rowbounds);
100 |
101 | % find the time-series files
102 | files0 = matchfiles(files);
103 |
104 | % for each file, collect the data
105 | colbounds = [];
106 | alldata = [];
107 | for p=1:length(files0)
108 |
109 | % load data, convert to double, flatten voxels
110 | data0 = load_untouch_nii(files0{p});
111 | data0 = squish(double(data0.img),3);
112 |
113 | % extract data and z-score each voxel
114 | temp = cat(1,data0(grayvx,:),data0(wmvx,:),data0(csfvx,:));
115 | temp = calczscore(temp,2); % there are other ways we could normalize...
116 |
117 | % record
118 | alldata = cat(2,alldata,temp);
119 |
120 | % calculate column bounds
121 | colbounds = [colbounds size(data0,2)];
122 |
123 | end
124 | colbounds = cumsum(colbounds);
125 |
126 | % make the visualization
127 | figureprep([100 100 800 600],1); hold on;
128 | imagesc(alldata,[-3 3]);
129 | colormap(gray);
130 | axis ij;
131 | axis([.5 size(alldata,2)+.5 .5 size(alldata,1)+.5]);
132 | straightline(rowbounds+.5,'h','r-');
133 | straightline(colbounds+.5,'v','r-');
134 | xlabel('Volume number');
135 | ylabel('Voxel');
136 | %%
137 |
138 | % In the plot, the three row compartments are gray matter, white matter, and CSF,
139 | % and the column compartments correspond to different fMRI runs.
140 |
141 |
142 |
--------------------------------------------------------------------------------
/matlab/example06_basicbetaloading.m:
--------------------------------------------------------------------------------
1 | %% Example 6: Basic loading and inspection of NSD betas
2 |
3 | %% Introduction
4 |
5 | % In this script, we go through an example of loading and inspecting the NSD betas.
6 | %
7 | % Skills/concepts:
8 | % - Thinking about data units
9 | % - Data visualization
10 | % - Simple ROI definition
11 | % - Non-trivial indexing into the NSD data
12 |
13 |
14 |
15 | %% General setup
16 |
17 | % define
18 | stimfile = '~/nsd/nsddata_stimuli/stimuli/nsd/nsd_stimuli.hdf5';
19 | expfile = '~/nsd/nsddata/experiments/nsd/nsd_expdesign.mat';
20 | subjix = 3; % which NSD subject we are analyzing
21 | nsess = 32; % how many NSD sessions are available
22 | betaver = 'betas_fithrf'; % which beta version to load
23 |
24 |
25 |
26 | %% Define ROI
27 |
28 | % We are going to examine NSD betas for one region of interest (ROI).
29 | % Specifically, right hemisphere (RH) fusiform face area (FFA).
30 | % Here, we go through one approach for defining that region.
31 |
32 | % load in t-values for faces vs. nonfaces from the floc experiment
33 | a1 = load_untouch_nii(sprintf('~/nsd/nsddata/ppdata/subj%02d/func1pt8mm/floc_facestval.nii.gz',subjix));
34 |
35 | % in ITK-SNAP, look at the NIFTI file and determine a small box in
36 | % the appropriate anatomical location and containing high t-values (e.g. > 5)
37 | lrix = 58:66; % from left to right
38 | paix = 31:54; % from posterior to anterior
39 | isix = 25:32; % from inferior to superior
40 |
41 | % note that this a rough-and-ready approach to defining the ROI. it is
42 | % sufficient for the sake of this example, but a more accurate approach
43 | % would be to examine the data on the cortical surface.
44 |
45 | % create a binary volume with the box
46 | boxvol = zeros(size(a1.img));
47 | boxvol(lrix,paix,isix) = 1;
48 |
49 | % define the ROI as within the box AND t > 5
50 | mask = boxvol & a1.img > 5;
51 |
52 | % to aid in loading the data, compute a tight fitting box and associated indices
53 | [d1,d2,d3,ii] = computebrickandindices(mask);
54 |
55 |
56 |
57 | %% Load in the betas
58 |
59 | % load data
60 | data = []; % voxels x 750 trials x sessions
61 | for p=1:nsess
62 | fprintf('sess %d...',p);
63 | file0 = sprintf('~/nsd/nsddata_betas/ppdata/subj%02d/func1pt8mm/%s/betas_session%02d.mat',subjix,betaver,p);
64 | a1 = matfile(file0);
65 | temp = double(a1.betas(d1,d2,d3,:))/300; % convert to double and then convert to percent signal change
66 | temp = squish(temp,3); % flatten voxels
67 | temp = temp(ii,:); % extract the voxels we want
68 | data(:,:,p) = temp; % record
69 | end
70 |
71 |
72 |
73 | %% Inspect the data
74 |
75 | % visualize data in original units (percent signal change)
76 | figure; hold on;
77 | imagesc(reshape(data,size(data,1),[]),[-10 10]);
78 | axis ij tight;
79 | colormap(cmapsign4); colorbar;
80 | xlabel('Trial');
81 | ylabel('Voxel');
82 | title('Response (% BOLD)');
83 | %%
84 |
85 | % Notice the high heterogeneity across voxels.
86 | %%
87 |
88 | % visualize each voxel's mean PSC
89 | figure; hold on;
90 | bar(mean(mean(data,2),3));
91 | xlabel('Voxel');
92 | ylabel('Average response (% BOLD)');
93 | %%
94 |
95 | % This reinforces the point: voxels have highly different overall BOLD responses.
96 | %%
97 |
98 | % on a per-voxel basis, z-score the betas obtained in each session.
99 | % this is a fairly drastic measure, but ensures stationarity across time
100 | % and comparable units across voxels.
101 | dataZ = calczscore(data,2);
102 |
103 | % visualize data again in z-score units
104 | figure; hold on;
105 | imagesc(reshape(dataZ,size(dataZ,1),[]),[-3 3]);
106 | axis ij tight;
107 | colormap(cmapsign4); colorbar;
108 | xlabel('Trial');
109 | ylabel('Voxel');
110 | title('Response (z-score units)');
111 | %%
112 |
113 |
114 |
115 | %% Load in experiment information
116 |
117 | % load
118 | exp1 = load(expfile);
119 | theorder = exp1.masterordering(1:750*nsess); % the trials that we have data for
120 | uniqueix = union(theorder,[]); % unique indices into the 10k
121 | length(theorder) % total number of trials
122 | length(uniqueix) % total number of unique images
123 | %%
124 |
125 |
126 |
127 | %% Visualize ROI-averaged responses
128 |
129 | % define
130 | numtodo = 100; % number of distinct images to plot responses for
131 |
132 | % massage dimensionality
133 | dataZ = reshape(dataZ,size(dataZ,1),[]); % voxels x trials*sessions
134 |
135 | % make the plot
136 | todo = picksubset(uniqueix,numtodo); % pick a small subset to plot
137 | versions = {'Regular' 'Shuffled'};
138 | for ver=1:2
139 | figureprep([100 100 1000 300],1); hold on;
140 | avgresp = [];
141 | for p=1:length(todo)
142 | switch ver
143 | case 1
144 | ix = find(theorder==todo(p)); % which trials correspond to the image
145 | case 2
146 | ix = find(permutedim(theorder==todo(p))); % SHUFFLE!
147 | end
148 | yy = mean(dataZ(:,ix),1); % compute ROI average for each trial
149 | scatter(repmat(p,[1 length(yy)]),yy,'ro');
150 | avgresp(p) = mean(yy);
151 | end
152 | h = plot(1:length(todo),avgresp,'ko-');
153 | set(h,'MarkerFaceColor','k');
154 | ax = axis;
155 | axis([ax(1:2) -2 2]);
156 | xlabel('Image');
157 | ylabel('Response (z-score units)');
158 | title(versions{ver});
159 | end
160 | %%
161 |
162 | % In the 'Regular' plot, we are looking for small within-image variability
163 | % compared to the across-image variability. The 'Shuffled' plot looks slightly
164 | % different compared to the 'Regular' plot, indicating that the image-evoked
165 | % signal in the data is fairly weak. This is expected given the noise in fMRI
166 | % data, the fact that an aggressive rapid event-related design was used in NSD
167 | % (where responses to successive trials overlap substantially), and the small
168 | % number of trials per distinct image that was used in the design.
169 |
170 |
171 |
172 | %% Look at best and worst images
173 |
174 | % compute the mean across trials only for those images with all 3 trials
175 | newdata = []; % voxels x images with the trial-averaged response
176 | newdatastim = []; % 1 x images with indices into the 10k
177 | for p=1:length(uniqueix)
178 | ix = find(theorder==uniqueix(p));
179 | if length(ix)==3
180 | newdata(:,end+1) = mean(dataZ(:,ix),2);
181 | newdatastim(end+1) = uniqueix(p);
182 | end
183 | end
184 |
185 | % sort ROI-averaged response in descending order
186 | [ss,ssix] = sort(mean(newdata,1),'descend');
187 |
188 | % plot best and worst images
189 | strs = {'Best' 'Worst'};
190 | for flag=1:2
191 | figureprep([100 100 1000 300],1);
192 | for p=1:5
193 |
194 | % figure out 73k ID
195 | switch flag
196 | case 1
197 | id73k = exp1.subjectim(subjix,newdatastim(ssix(p)));
198 | case 2
199 | id73k = exp1.subjectim(subjix,newdatastim(ssix(end-p+1)));
200 | end
201 |
202 | % get image (425 x 425 x 3, uint8)
203 | im = permute(h5read(stimfile,'/imgBrick',[1 1 1 id73k],[3 425 425 1]),[3 2 1]);
204 |
205 | % plot it
206 | subplot(1,5,p);
207 | imshow(im);
208 | if p==1
209 | title(strs{flag});
210 | end
211 |
212 | end
213 | end
214 | %%
215 |
216 | % As expected, the images that most strongly drove the response tend to have faces.
217 |
218 |
219 |
--------------------------------------------------------------------------------
/matlab/example07_simplecontrast.m:
--------------------------------------------------------------------------------
1 | %% Example 7: A simple contrast-based analysis of the NSD betas
2 |
3 | %% Introduction
4 |
5 | % In this script, we demonstrate a simple analysis of the NSD betas. The analysis
6 | % is based on the actual content of the NSD images --- specifically, we make use of
7 | % face annotations that were generated as part of "NSD_Annotation_Efforts_1.0",
8 | % which is an extension to the NSD data (it is not officially part of the
9 | % prepared NSD data). The analysis itself is very simple (essentially just a
10 | % t-test), but this script goes through a number of important points.
11 | %
12 | % Skills/concepts:
13 | % - Memory management
14 | % - Resampling strategies (split-half, shuffle/permutation)
15 | % - Using line plots to better understand cortical map visualizations
16 |
17 |
18 |
19 | %% General setup
20 |
21 | % define
22 | expfile = '~/nsd/nsddata/experiments/nsd/nsd_expdesign.mat';
23 | nsess = 2; % how many of the first N NSD sessions to consider (must be >= 2)
24 | betaver = 'betas_fithrf'; % which beta version to load
25 |
26 | % load
27 | exp1 = load(expfile);
28 |
29 |
30 |
31 | %% Load face annotations
32 |
33 | % load automated outputs: simple face count
34 | a1 = load('~/Dropbox/nsdabudhabi/nsdextensions/NSD_Annotation_Efforts_1.0/Automated/Faces/regcount_mode1.mat');
35 |
36 |
37 |
38 | %% Load betas prepared in fsaverage space and analyze them
39 |
40 | % The large scale of the NSD data comes with pain points. One is memory usage.
41 | % Unless you have large amounts of RAM, it may be impossible to hold all of
42 | % the relevant data in RAM at a single time. Thus, one may need to analyze
43 | % the data in chunks. In this script, we will process each subject one at a
44 | % time and only keep in RAM the relevant analysis outputs (not the data).
45 | %
46 | % Designing code to handle memory well is not trivial. One must have a birds-eye
47 | % perspective on the architecture of the analysis, the memory usage, and the
48 | % exact quantities that need to be computed. One wants to make sure that all
49 | % of the necessary analysis outputs are computed before one gets rid of the
50 | % data to make room for new edata. Since loading data from disk is expensive
51 | % in terms of execution time, ideally, code would be written correctly up front.
52 |
53 | % loop over subjects
54 | tvals = single([]); % vertices x 4 x subjects (full, odd, even, shuffle)
55 | mns = single([]); % vertices x 2 x subjects (face, non-face)
56 | ses = single([]); % vertices x 2 x subjects (face, non-face)
57 | for subjix=1:8, subjix
58 |
59 | % load in all of the betas
60 | alldata = single([]); % 327684 vertices x 750 trials x sessions
61 | for sess=1:nsess
62 | fprintf('sess%d...',sess);
63 | file0 = sprintf('~/nsd/nsddata_betas/ppdata/subj%02d/fsaverage/%s/*.betas_session%02d.*',subjix,betaver,sess);
64 | data = cvnloadmgz(file0); % 327684 x 1 x 1 x 750
65 | alldata(:,:,sess) = single(permute(data,[1 4 2 3])); % use single to save on memory
66 | clear data;
67 | end
68 |
69 | % at this point, the betas are in units of percent signal change.
70 | % to deal with gross session differences, you may want to consider
71 | % z-scoring the betas from each session (on a per-voxel basis).
72 |
73 | % prepare face counts (750 trials x sessions, non-negative integers)
74 | imageix = exp1.subjectim(subjix,exp1.masterordering(1:750*nsess)); % 1 x trials with 73k IDs
75 | counts = a1.countval(imageix); % 1 x trials with face counts
76 | counts = reshape(counts,750,[]); % 750 trials x sessions
77 |
78 | % compute t-value for faces vs. non-faces
79 | counts0 = counts;
80 | [mn1,se1] = meanandse(alldata(:,flatten(counts)>0),2); % at least one face
81 | [mn2,se2] = meanandse(alldata(:,flatten(counts)==0),2); % no faces
82 | tvals(:,1,subjix) = (mn1 - mn2) ./ sqrt(se1.^2 + se2.^2);
83 | % we also save the means and std errs for this case
84 | mns(:,1,subjix) = mn1;
85 | mns(:,2,subjix) = mn2;
86 | ses(:,1,subjix) = se1;
87 | ses(:,2,subjix) = se2;
88 |
89 | % compute t-value for odd sessions
90 | counts0 = counts;
91 | counts0(:,2:2:end) = NaN; % set even sessions to NaNs (the NaNs will not satisfy the logical operators)
92 | [mn1,se1] = meanandse(alldata(:,flatten(counts0)>0),2); % at least one face
93 | [mn2,se2] = meanandse(alldata(:,flatten(counts0)==0),2); % no faces
94 | tvals(:,2,subjix) = (mn1 - mn2) ./ sqrt(se1.^2 + se2.^2);
95 |
96 | % compute t-value for even sessions
97 | counts0 = counts;
98 | counts0(:,1:2:end) = NaN; % set odd sessions to NaNs (the NaNs will not satisfy the logical operators)
99 | [mn1,se1] = meanandse(alldata(:,flatten(counts0)>0),2); % at least one face
100 | [mn2,se2] = meanandse(alldata(:,flatten(counts0)==0),2); % no faces
101 | tvals(:,3,subjix) = (mn1 - mn2) ./ sqrt(se1.^2 + se2.^2);
102 |
103 | % shuffle labels and re-analyze
104 | for shuffle=1:1 % here, we do just one, but one can adapt this to do many
105 | counts0 = permutedim(counts,0);
106 | [mn1,se1] = meanandse(alldata(:,flatten(counts0)>0),2); % at least one face
107 | [mn2,se2] = meanandse(alldata(:,flatten(counts0)==0),2); % no faces
108 | tvals(:,3+shuffle,subjix) = (mn1 - mn2) ./ sqrt(se1.^2 + se2.^2);
109 | end
110 |
111 | end
112 |
113 |
114 |
115 | %% Generate cortical map visualizations
116 |
117 | % loop through each of the 4 analysis results (full, odd, even, shuffle)
118 | Lookup = [];
119 | titlestr = {'All' 'Odd' 'Even' 'Shuffle'};
120 | for typ=1:size(tvals,2)
121 |
122 | figureprep([0 0 1000 700],1);
123 | for subjix=1:8
124 |
125 | % use ventral inflated view of fsaverage; range -10 to 10;
126 | % threshold values that are less than 3 and greater than -3
127 | [rawimg,Lookup,rgbimg] = cvnlookup('fsaverage',3,tvals(:,typ,subjix),[-10 10],cmapsign4(256),3*j,Lookup,0);
128 |
129 | % write image to disk
130 | imwrite(rgbimg,sprintf('facetval_typ%02d_subj%02d.png',typ,subjix));
131 |
132 | % also place into figure window
133 | subplot(2,4,subjix); hold on;
134 | imshow(rgbimg);
135 | if subjix==1
136 | title(sprintf('%s; Subject %d',titlestr{typ},subjix));
137 | else
138 | title(sprintf('Subject %d',subjix));
139 | end
140 |
141 | end
142 |
143 | end
144 | %%
145 |
146 | % Notice that even though the subjects are in fsaverage space, there is
147 | % substantial variability in the location of face-selective cortex.
148 | %%
149 |
150 | % Notice that there is high consistency across the Odd and Even analyses.
151 | %%
152 |
153 | % Notice that the shuffle analysis produces very few statistically significant
154 | % regions (this is good). The null hypothesis embodied by this analysis is that
155 | % there is no relationship between the faces vs. non-faces categorization and the
156 | % brain data. The shuffling analysis has the nice property that it is closely
157 | % matched to the actual empirical data (it has the same exact numerical values;
158 | % the order is simply scrambled).
159 | %
160 | % The visualization is thresholded at |t|>3, and under this threshold,
161 | % there are still a few false positives that occur. Note that one could do
162 | % more extensive NHST quantification, such as performing many shuffles and
163 | % calculating some metric of the results (e.g. number of vertices that achieve
164 | % significance). Also, note that the issues here are closely related to
165 | % family-wise error rate and false discovery rate, but we won't go into
166 | % those issues here.
167 | %%
168 |
169 |
170 |
171 | %% Use line plots to better understand the cortical maps
172 |
173 | % plot the Full result, averaged across subjects
174 | [rawimg,Lookup,rgbimg] = cvnlookup('fsaverage',3,mean(tvals(:,1,:),3),[-10 10],cmapsign4(256),3*j,Lookup,0);
175 |
176 | % put up the image
177 | hmapfig = figure;
178 | himg = imshow(rgbimg);
179 | %%
180 |
181 | % draw a line on the right hemisphere from left to right,
182 | % through the middle of the large yellow region
183 | if 0
184 | % ask the user to draw a line (green=start, red=end)
185 | xypoints = []; % Note: can provide initial xypoints as Nx2 matrix of pixel coords
186 | [xypoints,xyline] = roiline(hmapfig,[],xypoints);
187 |
188 | % we now have:
189 | % xypoints = Nx2 pixel coords of line segment endpoints that the user defined/clicked (decimal)
190 | % xyline = Vx2 pixel coords of ALL pixel centers along the defined line segments (integer)
191 | end
192 |
193 | % to allow automated execution, we embed the above code in an if-statement
194 | % and provide the following hard-coded values:
195 | xypoints = [75.7499999999999 668.75;146.25 658.25];
196 | xyline = [76 669;77 669;78 668;79 668;80 668;81 668;82 668;83 668;84 667;85 667;86 667;87 667;88 667;89 667;90 667;91 666;92 666;93 666;94 666;95 666;96 666;97 666;98 665;99 665;100 665;101 665;102 665;103 665;104 665;105 664;106 664;107 664;108 664;109 664;110 664;111 663;112 663;113 663;114 663;115 663;116 663;117 663;118 662;119 662;120 662;121 662;122 662;123 662;124 662;125 661;126 661;127 661;128 661;129 661;130 661;131 660;132 660;133 660;134 660;135 660;136 660;137 660;138 659;139 659;140 659;141 659;142 659;143 659;144 659;145 658];
197 |
198 | % convert pixel coords (xyline) to vertex indices via Lookup.
199 | % each pixel center is mapped to a single surface vertex (via nearest-neighbor).
200 | % vertidx = Vx1 vertex indices (between 1 and numlh+numrh)
201 | vertidx = spherelookup_imagexy2vertidx(xyline,Lookup);
202 |
203 | % remove repeating vertices
204 | vertidxU = filterout(removerepeats(vertidx),NaN);
205 |
206 | % let's check the location of these vertices
207 | cvnlookup('fsaverage',3,copymatrix(zeros(size(tvals,1),1),vertidxU,1),[0 1],gray,0.5,Lookup);
208 | %%
209 |
210 | % plot some data values along the line (we call this a "line profile").
211 | % keep in mind that the line is not necessarily exactly straight, given
212 | % that it is constrained to be a series of fsaverage vertices.
213 | subjix = 1; % for simplicity, we will show only subj01
214 | figure;
215 | subplot(2,1,1); hold on;
216 | plot(tvals(vertidxU,1,subjix),'ro-');
217 | straightline(0,'h','k-');
218 | xlabel('Vertex');
219 | ylabel('t-value');
220 | subplot(2,1,2); hold on;
221 | errorbar3(1:length(vertidxU),mns(vertidxU,1,subjix)',ses(vertidxU,1,subjix)','v',[1 .5 .5]);
222 | errorbar3(1:length(vertidxU),mns(vertidxU,2,subjix)',ses(vertidxU,2,subjix)','v',[.5 .5 1]);
223 | plot(mns(vertidxU,1,subjix),'r-','LineWidth',2);
224 | plot(mns(vertidxU,2,subjix),'b-','LineWidth',2);
225 | straightline(0,'h','k-');
226 | xlabel('Vertex');
227 | ylabel('BOLD (%)');
228 |
229 | % We leave it as an exercise for the reader to explore:
230 | % (1) expressing cortical distance in mm units
231 | % (2) plotting multiple subjects on a single figure
232 | % (3) summarizing results across subjects
233 |
234 |
235 |
--------------------------------------------------------------------------------
/matlab/example08_simplecontrastmvpa.m:
--------------------------------------------------------------------------------
1 | %% Example 8: A simple example of MVPA on the NSD betas
2 |
3 | %% Introduction
4 |
5 | % In this script, we perform an analysis that has parallels to the analysis
6 | % demonstrated in the Example 7 script. Whereas in that script we perform a
7 | % t-test to determine brain regions that show differential activity to
8 | % faces vs. non-faces, in this script we perform so-called
9 | % 'multivariate pattern analysis' (MVPA) in which we use a statistical
10 | % classifier in an attempt to decode, given activity in a set of voxels,
11 | % whether the presented stimulus was a face or non-face. The main
12 | % differences across the two approaches is (1) the direction of the
13 | % statistical mapping learned and (2) the fact that MVPA is typically
14 | % used to analyze many voxels at the same time whereas the t-test
15 | % operates on single voxels at a time.
16 | %
17 | % The example below also demonstrates the analysis approach termed
18 | % 'searchlight'; in this approach, analysis is performed throughout
19 | % the brain, but using small groups of voxels that are extracted
20 | % at different brain locations.
21 | %
22 | % Skills/concepts:
23 | % - MVPA, searchlight
24 | % - Dealing with surface representations
25 | % - Dealing with subsampled surfaces (e.g. fsaverage5)
26 |
27 |
28 |
29 | %% General setup
30 |
31 | % define
32 | expfile = '~/nsd/nsddata/experiments/nsd/nsd_expdesign.mat';
33 | nsess = 2; % how many of the first N NSD sessions to consider
34 | betaver = 'betas_fithrf'; % which beta version to load
35 | radius = 3; % searchlight radius on fsaverage
36 | fsalt = 'fsaverage5'; % alternative surface for determining searchlight locations
37 | hemis = {'lh' 'rh'}; % strings referring to left and right hemispheres
38 | restrictroi = 'gVTC'; % restrict searchlight analysis to this ROI
39 | restrictroiix = [1]; % index into the ROI
40 | nfold = 5; % how many folds for n-fold cross-validation?
41 |
42 | % load
43 | exp1 = load(expfile);
44 |
45 |
46 |
47 | %% Load face annotations
48 |
49 | % load automated outputs: simple face count
50 | a1 = load('~/Dropbox/nsdabudhabi/nsdextensions/NSD_Annotation_Efforts_1.0/Automated/Faces/regcount_mode1.mat');
51 |
52 |
53 |
54 | %% Load betas prepared in fsaverage space and analyze them
55 |
56 | % loop over subjects
57 | pctcorrect = single([]); % 164k vertices x 2 hemis x 8 subjects
58 | for subjix=1:8, subjix
59 |
60 | for hh=1:length(hemis)
61 |
62 | %%% load data
63 |
64 | % load in all of the betas
65 | alldata = single([]); % 327684 vertices x 750 trials x sessions
66 | for sess=1:nsess
67 | fprintf('sess%d...',sess);
68 | file0 = sprintf('~/nsd/nsddata_betas/ppdata/subj%02d/fsaverage/%s/%s.betas_session%02d.mgh',subjix,betaver,hemis{hh},sess);
69 | data = cvnloadmgz(file0); % 327684 x 1 x 1 x 750
70 | alldata(:,:,sess) = single(permute(data,[1 4 2 3])); % use single to save on memory
71 | clear data;
72 | end
73 |
74 | % prepare face counts (750 trials x sessions, non-negative integers)
75 | imageix = exp1.subjectim(subjix,exp1.masterordering(1:750*nsess)); % 1 x trials with 73k IDs
76 | counts = a1.countval(imageix); % 1 x trials with face counts
77 | counts = reshape(counts,750,[]); % 750 trials x sessions
78 |
79 | %%% prepare for searchlight
80 |
81 | % load in ROI labelings (we will use this to restrict searchlight locations)
82 | [roimask,roidescription] = cvnroimask('fsaverage',hemis{hh},restrictroi,[],[],'collapsevals');
83 |
84 | % load in surfaces
85 | surf = cvnreadsurface('fsaverage',hemis{hh},'sphere');
86 | surfALT = cvnreadsurface(fsalt, hemis{hh},'sphere');
87 | hash = surf.vertices*[1000 100 1]'; assert(length(unique(hash))==size(surf.vertices,1));
88 | hashALT = surfALT.vertices*[1000 100 1]'; assert(length(unique(hashALT))==size(surfALT.vertices,1));
89 | XYZ = [surf.vertices ones(size(surf.vertices,1),1)]'; % prepare coordinates as 4 x V
90 |
91 | % for which fsaverage vertices will we perform searchlight analysis?
92 | mask = ismember(roimask,restrictroiix) & ... % only fsaverage vertices within the chosen ROI
93 | ismember(hash,hashALT); % only fsaverage vertices that are also in the alternative surface
94 | mask = find(mask); % a vector of indices into fsaverage vertices
95 | maskALT = []; % a corresponding vector of indices into the vertices of the alternative surface
96 | for mm=1:length(mask)
97 | maskALT(mm) = find(ismember(hashALT,hash(mask(mm))));
98 | end
99 |
100 | %%% perform analysis
101 |
102 | % loop over searchlight locations
103 | results = zeros(1,length(mask)); % this will contain pct correct for each searchlight location
104 | for ss=1:length(mask)
105 | statusdots(ss,length(mask));
106 |
107 | %%% figure out vertices that live within the searchlight
108 |
109 | % get 3D coordinates
110 | coord = surf.vertices(mask(ss),:); % 1 x 3
111 |
112 | % figure out rotation matrix
113 | rotmatrix = xyzrotatetoz(coord);
114 |
115 | % rotate all vertices so that the vertex is along z+ axis
116 | XYZ0 = rotmatrix*XYZ;
117 |
118 | % find the vertices above the equator and within radius mm
119 | searchix = find((XYZ0(3,:) >= 0) & (XYZ0(1,:).^2 + XYZ0(2,:).^2 <= radius^2)); % a vector of indices into fsaverage
120 |
121 | % Note that this procedure of selecting vertices within a circle placed on the
122 | % fsaverage sphere is only approximate. The fsaverage sphere is not only a
123 | % distorted brain shape (a sphere), it is also only an approximate representation
124 | % of the topology of individual subjects. A more accurate method of defining
125 | % searchlights would be to compute physical units on the native surface of
126 | % individual subjects.
127 |
128 | %%% get the data
129 |
130 | thedata = reshape(alldata(searchix,:,:),length(searchix),[]); % vertices x trials
131 |
132 | %%% do n-fold cross-validation
133 |
134 | % obtain cross-validated predictions
135 | pred = []; % 1 x trials with the predicted outcome
136 | for ff=1:nfold
137 |
138 | % split into training and testing
139 | [testix,~,trainix] = picksubset(1:size(thedata,2),[nfold ff]);
140 |
141 | % train the classifer and obtain predictions
142 | sample = thedata(:,testix)'; % testing-trials x vertices
143 | training = thedata(:,trainix)'; % training-trials x vertices
144 | group = vflatten(counts(trainix)) > 0; % training-trials x 1 with 0/1 (1 means at least one face)
145 | testclass = classify(sample,training,group,'diagLinear'); % testing-trials x 1 with the predictions
146 | pred(testix) = testclass;
147 |
148 | % Note that this is a Naive Bayes classifier, and it is just one of many
149 | % possible statistical classifiers that one could use. Common alternatives
150 | % include SVM, LDA, logistic regression, and nearest-centroid classifier.
151 |
152 | end
153 |
154 | % calculate percent correct
155 | results(ss) = mean( pred(:) == (counts(:) > 0) ) * 100;
156 |
157 | end
158 |
159 | %%% transform searchlight results to a full fsaverage representation
160 |
161 | % embed searchlight results into a vector for the alternative surface
162 | vals = copymatrix(zeros(size(surfALT.vertices,1),1),maskALT,results);
163 |
164 | % use nearest-neighbor interpolation to map to the fsaverage surface
165 | vals = cvntransfertosubject(fsalt,'fsaverage',vals(:),hemis{hh},'nearest','orig','orig');
166 |
167 | % record the results
168 | pctcorrect(:,hh,subjix) = vals;
169 |
170 | end
171 |
172 | end
173 |
174 |
175 |
176 | %% Generate cortical map visualizations
177 |
178 | % first, we show individual subjects
179 | Lookup = [];
180 | figureprep([0 0 1000 700],1);
181 | for subjix=1:8
182 |
183 | % use ventral inflated view of fsaverage; range 50% to 80%
184 | [rawimg,Lookup,rgbimg] = cvnlookup('fsaverage',3,vflatten(pctcorrect(:,:,subjix)), ...
185 | [50 80],jet(256),[],Lookup,0);
186 |
187 | % write image to disk
188 | imwrite(rgbimg,sprintf('facepct_subj%02d.png',subjix));
189 |
190 | % also place into figure window
191 | subplot(2,4,subjix); hold on;
192 | imshow(rgbimg);
193 | title(sprintf('Subject %d',subjix));
194 |
195 | end
196 | %%
197 |
198 | % then, we show the group average
199 | cvnlookup('fsaverage',3,vflatten(mean(pctcorrect,3)),[50 80],jet(256),[],Lookup);
200 | colorbar; % since cvnlookup shows RGB images, we have to explicitly set the colormap
201 | colormap(jet);
202 | caxis([50 80]);
203 | %%
204 |
205 | % Notice that the spatial patterns of results are similar to those found in Example 7.
206 | %%
207 |
208 |
209 |
--------------------------------------------------------------------------------
/matlab/example09_behavioraldata.m:
--------------------------------------------------------------------------------
1 | %% Example 9: Some example analyses of the behavioral data
2 |
3 | %% Introduction
4 |
5 | % In this script, we perform a few simple analyses of the behavioral data
6 | % that were collected during the NSD experiment. These data are rich and
7 | % extensive, as they reflect up to 30,000 trials from a given subject.
8 | %
9 | % Skills/concepts:
10 | % - Various plotting techniques
11 | % - Bootstrapping
12 |
13 |
14 |
15 | %% Load data
16 |
17 | % Load behavioral .tsv file. A few hints:
18 | % 2 = session
19 | % 7 = time
20 | % 9 = iscorrect
21 | % 10 = RT
22 | % 19 = missingdata
23 | thedata = {};
24 | for subjix=1:8
25 | file0 = sprintf('~/nsd/nsddata/ppdata/subj%02d/behav/responses.tsv',subjix);
26 | a1 = importdata(file0);
27 | thedata{subjix} = a1.data;
28 | end
29 | thedata
30 | %%
31 |
32 |
33 |
34 | %% Histogram of RTs
35 |
36 | % plot histogram of RTs
37 | bins = 0:50:4200; % use the same bins for every subject
38 | figureprep([100 100 1000 400],1);
39 | for subjix=1:8
40 | subplot(2,4,subjix); hold on;
41 | hist(thedata{subjix}(:,10),bins);
42 | straightline(0:500:4200,'v','r:');
43 | xlabel('Reaction time (ms)');
44 | ylabel('Frequency');
45 | title(sprintf('Subject %d',subjix));
46 | end
47 | %%
48 |
49 |
50 |
51 | %% RTs as a function of time
52 |
53 | % define
54 | subjix = 1;
55 |
56 | % Plot RT as a function of time
57 | figure; hold on;
58 | scatter(thedata{subjix}(:,7),thedata{subjix}(:,10),'ro'); % note that NaNs just disappear
59 | xlabel('Number of days');
60 | ylabel('Reaction time (ms)');
61 | %%
62 |
63 | % The figure is hard to interpret given the very large
64 | % number of dots. Thus, we will also plot a summary metric.
65 | %%
66 |
67 | % Plot the median RT in each session
68 | allsess = unique(thedata{subjix}(:,2)); % all sessions
69 | time0 = []; % 1 x N with the mean time
70 | md = []; % 1 x N with the median RT
71 | for p=1:length(allsess)
72 | ix = find(thedata{subjix}(:,2) == allsess(p)); % trials to consider
73 | ix = ix(isfinite(thedata{subjix}(ix,10))); % only consider those with valid data
74 | time0(p) = mean(thedata{subjix}(ix,7)); % mean time
75 | md(p) = median(thedata{subjix}(ix,10)); % median RT
76 | end
77 | plot(time0,md,'ko-','LineWidth',2);
78 | %%
79 |
80 | % Note that the second black data point is a little funny because it
81 | % reflects data pooled from two split sessions (frankenstein session).
82 | % Reaction times were fairly stable throughout the experiment.
83 | %%
84 |
85 |
86 |
87 | %% Calculate percent correct in each session, bootstrapping to get reliability
88 |
89 | % Here, we will use bootstrapping to estimate the reliability of
90 | % the percent correct obtained in each scan session. The use of
91 | % bootstrapping is a bit overkill, as we could alternatively just
92 | % use parametric error estimates from the binomial distribution.
93 | % But, it is nonetheless useful to demonstrate how bootstrapping
94 | % can be implemented.
95 |
96 | % do it
97 | numboot = 100;
98 | pctcorrect = NaN*zeros(8,40,numboot); % initialize with NaNs
99 | for subjix=1:8
100 | for p=1:40
101 | ix = thedata{subjix}(:,2)==p; % find trials
102 | if sum(ix ~= 0) % some subjects did not complete all 40
103 | for boot=1:numboot
104 |
105 | % extract data
106 | subjdata = thedata{subjix}(ix,:); % trials x columns
107 |
108 | % perform bootstrap sampling (see also bootstrp.m)
109 | n = size(subjdata,1); % how many trials are there?
110 | bootix = ceil(n*rand(1,n)); % generate bootstrap indices
111 | bootdata = subjdata(bootix,:); % create the sample
112 |
113 | % calculate percent correct
114 | isok = bootdata(:,19)==0; % which rows have valid data?
115 | pctcorrect(subjix,p,boot) = mean(bootdata(isok,9)==1) * 100; % NaNs are treated as false here
116 |
117 | end
118 | end
119 | end
120 | end
121 |
122 | % visualize
123 | figure; hold on;
124 | cmap0 = jet(8);
125 | h1 = []; h2 = [];
126 | for subjix=1:8
127 | pp0 = prctile(pctcorrect(subjix,:,:),[16 84],3); % 1 x 40 x 2 (68% confidence interval)
128 | md0 = median(pctcorrect(subjix,:,:),3); % 1 x 40
129 | h1(subjix) = errorbar3(1:40,md0,permute(pp0,[3 2 1]),'v',(cmap0(subjix,:)+[1 1 1])/2);
130 | h2(subjix) = plot(md0,'-','LineWidth',2,'Color',cmap0(subjix,:));
131 | end
132 | uistack(h2,'top'); % ensure the median lines are on top
133 | xlabel('Session number');
134 | ylabel('Percent correct');
135 | %%
136 |
137 |
138 |
--------------------------------------------------------------------------------
/matlab/example10_encodingmodel.m:
--------------------------------------------------------------------------------
1 | %% Example 10: Building encoding models
2 |
3 | %% Introduction
4 |
5 | % In this script, we go through an example of building an encoding model for one
6 | % voxel in the NSD data. The model that we use is based on image contrast (computed
7 | % as the standard deviation of luminance within small square chunks of the image),
8 | % but the principles demonstrated here generalize to other types of models.
9 | %
10 | % Skills/concepts:
11 | % - OLS regression, ridge regression
12 | % - Cross-validation
13 | % - Correlated regressors
14 | % - pRF concepts and the analyzePRF.m toolbox
15 |
16 |
17 |
18 | %% General setup
19 |
20 | % define
21 | stimfile = '~/nsd/nsddata_stimuli/stimuli/nsd/nsd_stimuli.hdf5';
22 | expfile = '~/nsd/nsddata/experiments/nsd/nsd_expdesign.mat';
23 | nsess = 5; % how many of the first N NSD sessions to consider
24 | betaver = 'betas_fithrf'; % which beta version to load
25 | subjix = 1;
26 | ng = 17; % number of elements in contrast grid (ng x ng)
27 | npx = 25; % number of pixels that make up one grid element (npx x npx)
28 | stimdeg = 8.4; % size of stimulus in degrees of visual angle
29 |
30 | % load
31 | exp1 = load(expfile);
32 |
33 |
34 |
35 | %% Do some stimulus pre-processing
36 |
37 | % determine vector of all of the 73k IDs that are involved in the data we will load
38 | allimix = unique(exp1.subjectim(subjix,exp1.masterordering(1:750*nsess)));
39 |
40 | % load and prepare images
41 | ims = zeros(425,425,length(allimix));
42 | for p=1:length(allimix)
43 | statusdots(p,length(allimix));
44 | im = h5read(stimfile,'/imgBrick',[1 1 1 allimix(p)],[3 425 425 1]);
45 | im = single(rgb2gray(permute(im,[3 2 1]))); % convert to grayscale and to single format
46 | im = (im/255).^2; % convert to [0,1] and square to match display gamma
47 | ims(:,:,p) = im;
48 | end
49 | size(ims)
50 | %%
51 |
52 | % compute a "contrast grid" from the images
53 | imagecon = zeros(ng,ng,length(allimix));
54 | for rowix=1:ng
55 | statusdots(rowix,ng);
56 | for colix=1:ng
57 | rowii = (rowix-1)*npx + (1:npx);
58 | colii = (colix-1)*npx + (1:npx);
59 | imagecon(rowix,colix,:) = std(squish(ims(rowii,colii,:),2),[],1); % standard deviation of pixels
60 | end
61 | end
62 | size(imagecon)
63 | %%
64 |
65 |
66 |
67 | %% Do some experimental-design preparation
68 |
69 | % we want a trial-length vector that provides indices into the loaded images
70 | expandtrials = [];
71 | for p=1:750*nsess
72 | thisid = exp1.subjectim(subjix,exp1.masterordering(p)); % the 73k ID
73 | expandtrials(p) = find(ismember(allimix,thisid)); % index relative to loaded images
74 | end
75 | size(expandtrials)
76 | min(expandtrials)
77 | max(expandtrials)
78 | %%
79 |
80 |
81 |
82 | %% Define the ROI that we will load from
83 |
84 | % load the Kastner atlas
85 | roi1 = load_untouch_nii(sprintf('~/nsd/nsddata/ppdata/subj%02d/func1pt8mm/roi/Kastner2015.nii.gz',subjix));
86 |
87 | % select any voxel in V1v or V1d (in either hemisphere)
88 | mask = ismember(roi1.img,[1 2]); % V1v, V1d
89 |
90 | % compute some handy indices
91 | [d1,d2,d3,dii] = computebrickandindices(mask);
92 |
93 |
94 |
95 | %% Load NSD betas for the ROI
96 |
97 | % load data
98 | data = []; % voxels x 750 trials x sessions
99 | for p=1:nsess
100 | fprintf('sess %d...',p);
101 | file0 = sprintf('~/nsd/nsddata_betas/ppdata/subj%02d/func1pt8mm/%s/betas_session%02d.mat',subjix,betaver,p);
102 | a1 = matfile(file0);
103 | temp = double(a1.betas(d1,d2,d3,:))/300; % convert to double and then convert to percent signal change
104 | temp = squish(temp,3); % flatten voxels
105 | temp = temp(dii,:); % extract the voxels we want
106 | data(:,:,p) = temp; % record
107 | end
108 | size(data)
109 | %%
110 |
111 |
112 |
113 | %% Start with a simple univariate regression
114 |
115 | % Here, we have pre-selected a single voxel and a single stimulus
116 | % feature to demonstrate some basic points.
117 |
118 | % define
119 | voxix = 59; % this is an index relative to the voxels we have loaded
120 | stimrowix = 2;
121 | stimcolix = 7;
122 |
123 | % get the data for one voxel (concatenate all trials)
124 | onevox = vflatten(data(voxix,:,:)); % trials x 1
125 |
126 | % simple inspection
127 | figure; hold on;
128 | xx = flatten(imagecon(stimrowix,stimcolix,expandtrials));
129 | yy = flatten(onevox);
130 | scatter(xx,yy,'ro');
131 | xlabel('Contrast');
132 | ylabel('BOLD response (%)');
133 | %%
134 |
135 | % In essence, this shows the key result that drives all other
136 | % analyses: the higher the contrast, the larger the BOLD response.
137 | % However, the details of the plot are hard to interpret due
138 | % to the density of the dots.
139 | %%
140 |
141 | % make a better plot
142 | figure; hold on;
143 | xbins = 0:.01:.5;
144 | ybins = -10:.2:10;
145 | [n,x,y] = hist2d(xx,yy,xbins,ybins);
146 | imagesc(x(1,:),y(:,1),log(n));
147 | colormap(jet);
148 | straightline(0,'h','w-');
149 | xlabel('Contrast');
150 | ylabel('BOLD response (%)');
151 | r = calccorrelation(xx,yy);
152 | title(sprintf('r = %.2f',r));
153 | axis([-0.05 0.55 -11 11]);
154 | %%
155 |
156 | % We haven't really done "regression" per se; however, Pearson's
157 | % correlation is essentially regression with one predictor
158 | % plus an offset (constant) term.
159 | %
160 | % Notice:
161 | % (1) There is a clear positive relationship between the
162 | % stimulus feature (contrast) and the activity.
163 | % (2) The relationship is actually not perfectly linear.
164 | % (3) Most images tend to sample near the low end of contrast.
165 | %%
166 |
167 |
168 |
169 | %% Think about multiple regression and stimulus correlations
170 |
171 | % The activity in the voxel might be driven by the selected
172 | % grid location (row 2, column 7), but might be also driven
173 | % by nearby grid locations. The conventional approach to address
174 | % this is to fit a multiple regression model that incorporates
175 | % regressors for every grid location. However, stimulus correlations
176 | % will make this endeavor challenging.
177 |
178 | % define the reference grid element
179 | ref = imagecon(stimrowix,stimcolix,:);
180 |
181 | % compute correlation with all grid elements
182 | featurecorr = calccorrelation(repmat(ref,[ng ng]),imagecon,3);
183 |
184 | % visualize
185 | figure; hold on;
186 | imagesc(featurecorr,[-1 1]);
187 | axis image tight;
188 | set(gca,'YDir','reverse');
189 | colormap(cmapsign4);
190 | colorbar;
191 | %%
192 |
193 | % Notice that the contrast in location (2,7) is highly correlated
194 | % with contrast in nearby locations. In fact, small positive
195 | % correlations are found everywhere.
196 |
197 |
198 |
199 | %% Perform the multiple regression
200 |
201 | % Here, we will fit an encoding model that has 17*17 = 289
202 | % parameters. The underlying model is that the response is
203 | % given as a weighted sum of the grid contrasts. No intercept
204 | % in included in this model; thus, the model implies that
205 | % having no contrast anywhere (all grid contrasts are 0)
206 | % corresponds to a BOLD response of 0%. This is sensible
207 | % given that in the way that the NSD data are prepared,
208 | % beta weights of 0% correspond to the BOLD signal intensity
209 | % during the absence of image stimulation (gray background
210 | % during blank trials).
211 |
212 | % prepare for regression
213 | X = squish(imagecon(:,:,expandtrials),2)'; % trials x features
214 | y = onevox; % trials x 1
215 |
216 | % fit the weights using ordinary least-squares
217 | h = inv(X'*X)*X'*y;
218 |
219 | % visualize
220 | figure; hold on;
221 | h1 = imagesc(reshape(h,[ng ng]));
222 | set(h1,'XData',([1 ng] - ((1+ng)/2)) * (stimdeg/ng));
223 | set(h1,'YData',([ng 1] - ((1+ng)/2)) * (stimdeg/ng));
224 | axis image tight;
225 | set(gca,'YDir','normal');
226 | caxis(max(h(:)) * [-1 1]);
227 | colormap(cmapsign4); colorbar;
228 | xlabel('x-position (deg)');
229 | ylabel('y-position (deg)');
230 | %%
231 |
232 | % The weights appear to be noisy. This is somewhat expected given:
233 | % (i) fMRI single-trial responses are quite noisy; (ii) the large
234 | % number of parameters being fitted (289) without any regularization;
235 | % (iii) the fact that we loaded only 5 NSD sessions (3750 trials);
236 | % and (iv) the fact that the regressors are highly correlated.
237 | %%
238 |
239 |
240 |
241 | %% Build the encoding model using ridge regression
242 |
243 | % A natural solution to the challenges noted above is to use
244 | % regularization in the parameter estimation, and a classic
245 | % regularization technique is ridge regression. Here, we will
246 | % perform ridge regression and use cross-validation to select
247 | % the amount of regularization to use.
248 |
249 | % define
250 | lambdas = 10.^(-3:4);
251 |
252 | % calculate the training/testing split
253 | [testix,~,trainix] = picksubset(1:length(y),[5 1]);
254 |
255 | % do it
256 | hs = []; % weights x lambdas
257 | testerr = []; % 1 x lambdas with the mean squared error
258 | for p=1:length(lambdas)
259 | X0 = X(trainix,:);
260 | y0 = y(trainix);
261 | hs(:,p) = inv(X0'*X0 + lambdas(p)*eye(size(X0,2)))*X0'*y0;
262 | testerr(p) = mean((X(testix,:)*hs(:,p) - y(testix)).^2); % mean squared error
263 | end
264 |
265 | % which lambda was best?
266 | [~,ii] = min(testerr);
267 | hfinal = hs(:,ii);
268 |
269 | % visualize the results
270 | figureprep([100 100 1000 400],1); hold on;
271 | for p=1:length(lambdas)
272 | subplot(2,ceil(length(lambdas)/2),p); hold on;
273 | h1 = imagesc(reshape(hs(:,p),[ng ng]));
274 | set(h1,'XData',([1 ng] - ((1+ng)/2)) * (stimdeg/ng));
275 | set(h1,'YData',([ng 1] - ((1+ng)/2)) * (stimdeg/ng));
276 | axis image tight;
277 | set(gca,'YDir','normal');
278 | caxis(max(hs(:,p)) * [-1 1]);
279 | colormap(cmapsign4); colorbar;
280 | xlabel('x-position (deg)');
281 | ylabel('y-position (deg)');
282 | title(sprintf('Lambda number %d',p));
283 | end
284 | %%
285 |
286 | % visualize test error
287 | figure; hold on;
288 | plot(testerr,'ro-');
289 | straightline(ii,'v','k-');
290 | xlabel('Lambda number');
291 | ylabel('Mean squared error');
292 | %%
293 |
294 | % Lambda number 1 produces essentially no regularization, whereas
295 | % Lambda number 8 produces extremely heavy regularization. The
296 | % best cross-validation performance is achieved using Lambda number 5.
297 | %%
298 |
299 |
300 |
301 | %% Use analyzePRF to build the encoding model
302 |
303 | % The toolbox analyzePRF implements a 2D Gaussian pRF model. Here, we will
304 | % use it to analyze the NSD betas. While it is interesting that the tool
305 | % can be used more or less as-is on the NSD betas, there are many
306 | % conceptual differences between the two approaches, including the following:
307 | %
308 | % (1) Typically, pRF analyses are performed on binary stimulus representations that are
309 | % either OFF (0) or ON (1). In contrast, the preparation of the NSD stimuli that we
310 | % have constructed is continuous (and involves certain assumptions about the "chunk size").
311 | %
312 | % (2) pRF analyses are typically performed on time-series data, whereas the betas we
313 | % have prepared are amplitudes (expressed in percent signal change).
314 | %
315 | % (3) The pRF model implemented in analyzePRF not only imposes a shape constraint
316 | % (2D isotropic Gaussian) on weights across the visual field, but also incorporates
317 | % a static output nonlinearity. In contrast, the regression model demonstrated in
318 | % this script is just a linear regression model with no constraints on the weights
319 | % (aside from the regularization imposed by ridge regression).
320 | %
321 | % More information on analyzePRF can be found at analyzePRF/example*.m.
322 |
323 | % run analyzePRF.m
324 | results = analyzePRF(double(imagecon(:,:,expandtrials)), ...
325 | onevox',1,struct('hrf',1,'maxpolydeg',0,'seedmode',2,'typicalgain',1));
326 | %%
327 |
328 |
329 |
330 | %% Inspect all of the results
331 |
332 | % We are now ready to do some comparisons. We have three different sets of results.
333 | % One result is the simple linear regression model fitted to the NSD betas
334 | % using ridge regression. Another result is the 2D Gaussian pRF model (with output
335 | % nonlinearity) fitted to the NSD betas. A third result is the actual prf experiment
336 | % that was conducted on the NSD subjects and analyzed using analyzePRF.m. Note that
337 | % this reflects a completely different set of data (compared to the NSD betas).
338 |
339 | % load the prf results
340 | prfang = getfield(load_untouch_nii(sprintf('~/nsd/nsddata/ppdata/subj%02d/func1pt8mm/prf_angle.nii.gz',subjix)),'img');
341 | prfecc = getfield(load_untouch_nii(sprintf('~/nsd/nsddata/ppdata/subj%02d/func1pt8mm/prf_eccentricity.nii.gz',subjix)),'img');
342 | prfsize = getfield(load_untouch_nii(sprintf('~/nsd/nsddata/ppdata/subj%02d/func1pt8mm/prf_size.nii.gz',subjix)),'img');
343 | prfexpt = getfield(load_untouch_nii(sprintf('~/nsd/nsddata/ppdata/subj%02d/func1pt8mm/prf_exponent.nii.gz',subjix)),'img');
344 |
345 | % perform some indexing voodoo to calculate an index into the 1.8-mm volume space
346 | temp = reshape(1:numel(roi1.img),size(roi1.img));
347 | temp = temp(d1,d2,d3); % pull out the brick
348 | temp = temp(dii); % pull out the voxels extracted from this brick
349 | volix = temp(voxix); % pull out the single voxel used in this script
350 |
351 | % make a visualization
352 | % first, deal with ridge-regression on NSD betas
353 | figureprep([100 100 600 600],1); hold on;
354 | h1 = imagesc(reshape(hfinal,[ng ng]));
355 | set(h1,'XData',([1 ng] - ((1+ng)/2)) * (stimdeg/ng));
356 | set(h1,'YData',([ng 1] - ((1+ng)/2)) * (stimdeg/ng));
357 | axis image tight;
358 | set(gca,'YDir','normal');
359 | caxis(max(hfinal) * [-1 1]);
360 | colormap(cmapsign4); colorbar;
361 | xlabel('x-position (deg)');
362 | ylabel('y-position (deg)');
363 | % then, deal with analyzePRF on NSD betas
364 | xpos = cos(results.ang/180*pi) * results.ecc * (stimdeg/ng);
365 | ypos = sin(results.ang/180*pi) * results.ecc * (stimdeg/ng);
366 | rfsize = results.rfsize * (stimdeg/ng);
367 | set(drawellipse(xpos,ypos,0,rfsize,rfsize,[],[],'g-'),'LineWidth',3);
368 | % finally, deal with results obtained from the prf experiment
369 | xpos = cos(prfang(volix)/180*pi) * prfecc(volix);
370 | ypos = sin(prfang(volix)/180*pi) * prfecc(volix);
371 | set(drawellipse(xpos,ypos,0,prfsize(volix),prfsize(volix),[],[],'c-'),'LineWidth',3);
372 | % finish up
373 | axis([-6 6 -6 6]);
374 | %%
375 |
376 | % The three sets of results are reasonably consistent.
377 | %%
378 |
379 |
380 |
381 | %% Postmortem
382 |
383 | % There are many choices made in this example script, all of which will
384 | % have an effect on modeling results and interpretation. These include
385 | % choices with respect to data preparation:
386 | %
387 | % - We used the NSD betas in their prepared raw units of percent signal change.
388 | % Performing session-level normalization, such as z-scoring, may get rid of
389 | % unwanted instabilities across sessions and may improve results.
390 | %
391 | % And include choices with respect to the modeling setup:
392 | %
393 | % - We prepared the NSD betas at the trial level as opposed to the image level.
394 | % Thus, the cross-validation procedure does not necessarily test
395 | % generalization to new images.
396 | %
397 | % And include choices with respect to the model itself:
398 | %
399 | % - The specific "chunk size" for the contrast grid calculation was somewhat
400 | % arbitrary; changing the chunk size will change the nature of the model
401 | % and will affect the results.
402 | % - To compute contrast, we used standard deviation of luminance values. Other
403 | % ways to compute contrast are possible, such as standard deviation divided by
404 | % mean luminance. Furthermore, instead of contrast, one could use specific image
405 | % features such as the outputs of local oriented filters (e.g. Gabor functions)
406 | % applied to the stimulus.
407 | %%
408 |
409 |
410 |
--------------------------------------------------------------------------------
/matlab/example11_rsa.m:
--------------------------------------------------------------------------------
1 | %% Example 11: Representational similarity analysis
2 |
3 | %% Introduction
4 |
5 | % In this script, we provide a simple example of performing
6 | % representational similarity analysis. We actually do this
7 | % not on the core NSD data but on the category-localizer floc
8 | % experiment that was also conducted in the NSD subjects.
9 | %
10 | % Skills/concepts:
11 | % - Creating representational dissimilarity matrices
12 |
13 |
14 |
15 | %% Load data
16 |
17 | % load the names of the categories used in the floc experiment
18 | catlabels = importdata('~/nsd/nsddata/experiments/floc/categories.tsv');
19 |
20 | % load in floc betas from a ventral temporal cortex ROI
21 | betas = {};
22 | for subjix=1:8
23 |
24 | % load in the visualsulc atlas
25 | roi1 = load_untouch_nii(sprintf('~/nsd/nsddata/ppdata/subj%02d/func1pt8mm/roi/visualsulc.nii.gz',subjix));
26 |
27 | % load in the floc betas (the 60 betas are ordered as 6 condition-splits * 10 categories)
28 | a1 = load_untouch_nii(sprintf('~/nsd/nsddata/ppdata/subj%02d/func1pt8mm/floc_betas.nii.gz',subjix));
29 |
30 | % extract data for voxels within the union of OTS, FG, mFus, CoS
31 | betas{subjix} = subscript(squish(a1.img,3),{find(ismember(roi1.img,[1 3 4 6])) ':'}); % voxels x betas
32 |
33 | end
34 | betas
35 | %%
36 |
37 |
38 |
39 | %% Construct RDMs (representational dissimilarity matrices)
40 |
41 | % do it
42 | cmatrix = []; % 10 x 10 x subjects, values are in [0,2]
43 | for subjix=1:8
44 |
45 | % average across condition-splits
46 | temp = squish(mean(reshape(betas{subjix},[],6,10),2),2); % voxels x 10 categories
47 |
48 | % calculate pairwise correlations of activity patterns and subtract from 1
49 | cmatrix(:,:,subjix) = 1-calcconfusionmatrix(temp,[],2);
50 |
51 | end
52 |
53 |
54 |
55 | %% Visualize results
56 |
57 | % plot
58 | figureprep([100 100 1000 500],1);
59 | for subjix=1:8
60 | subplot(2,4,subjix); hold on;
61 | imagesc(cmatrix(:,:,subjix),[0 0.5]);
62 | colormap(copper); colorbar;
63 | axis image tight;
64 | set(gca,'YDir','reverse');
65 | set(gca,'YTick',1:10,'YTickLabel',catlabels);
66 | set(gca,'XTick',1:10,'XTickLabel',catlabels);
67 | xticklabel_rotate;
68 | title(sprintf('Subject %d',subjix));
69 | end
70 | %%
71 |
72 | % Note that results will vary depending on the units of the data and the
73 | % choice of similarity metric. In the above example, we used the
74 | % beta weights as given in units of percent BOLD signal change and
75 | % used one minus Pearson's correlation as the measure of dissimilarity.
76 | % One might consider other similarity metrics and/or normalization of the
77 | % data prior to RDM construction (e.g. mean-subtraction or z-scoring
78 | % of voxel responses or activity patterns). Caution should be exercised
79 | % to ensure proper intepretation of results.
80 |
81 |
82 |
--------------------------------------------------------------------------------
/matlab/example12_rsfc.m:
--------------------------------------------------------------------------------
1 | %% Example 12: Resting-state functional connectivity
2 |
3 | %% Introduction
4 |
5 | % In this script, we show an example of how one might perform a functional-
6 | % connectivity analysis on the resting-state data collected as part of NSD.
7 | % This type of data and analysis is often termed 'resting-state functional
8 | % connectivity' (RSFC). In its simplest form, RSFC is essentially just
9 | % correlating time-series across different voxels (or regions).
10 | %
11 | % Skills/concepts:
12 | % - Loading the NSD pre-processed time-series data
13 | % - Simple forms of signal processing and normalization
14 | % - Using the HCP_MMP atlas
15 |
16 |
17 |
18 | %% General setup
19 |
20 | % define
21 | subjix = 6;
22 |
23 |
24 |
25 | %% Load atlas parcellation
26 |
27 | roi1 = load_untouch_nii(sprintf('~/nsd/nsddata/ppdata/subj%02d/func1pt8mm/roi/HCP_MMP1.nii.gz',subjix));
28 | roilabel1 = read_ctab(sprintf('~/nsd/nsddata/freesurfer/subj%02d/label/HCP_MMP1.mgz.ctab',subjix));
29 |
30 |
31 |
32 | %% Load and prepare time-series data
33 |
34 | % prepare projection matrix to perform high-pass filtering
35 | polymatrix = constructpolynomialmatrix(226,0:1); % constant term, linear term
36 | pmatrix = projectionmatrix(polymatrix); % create projection matrix
37 |
38 | % load time-series data
39 | ptimeseries = []; % time x 2 runs x 10 sessions x 180 ROIs
40 | sesstoload = 21:30;
41 | runstoload = [1 14];
42 | for p=1:length(sesstoload)
43 | fprintf('sess=%d...',sesstoload(p));
44 |
45 | for r=1:length(runstoload)
46 |
47 | % load data
48 | file0 = '~/nsd/nsddata_timeseries/ppdata/subj%02d/func1pt8mm/timeseries/timeseries_session%02d_run%02d.nii.gz';
49 | a1 = load_untouch_nii(sprintf(file0,subjix,sesstoload(p),runstoload(r)));
50 | data = squish(single(a1.img),3); % XYZ x time
51 |
52 | % prepare time series for each of the ROIs in the atlas
53 | for roi=1:180
54 |
55 | % average across voxels
56 | temp = mean(data(find(roi1.img==roi),:),1)'; % time x 1
57 |
58 | % high-pass filter the data (i.e. subtract mean and linearly detrend)
59 | temp = pmatrix*temp;
60 |
61 | % z-score
62 | temp = calczscore(temp);
63 |
64 | % record
65 | ptimeseries(:,r,p,roi) = temp;
66 |
67 | % Note that RSFC analyses typically involve more aggressive procedures that
68 | % attempt to remove noise from the time-series data. Also note that in the
69 | % code above, the time series from different voxels are averaged before
70 | % detrending and normalization, but one might wish to average as the last
71 | % step instead (the order of operations will affect the result).
72 |
73 | end
74 |
75 | end
76 | end
77 |
78 |
79 |
80 | %% Visualize the data for sanity checking
81 |
82 | % plot time series for every 10th region
83 | figureprep([100 100 1000 600],1);
84 | for roi=10:10:180
85 | subplot(3,6,roi/10); hold on;
86 | plot(squish(ptimeseries(:,:,:,roi),3));
87 | title(roilabel1.struct_names{roi});
88 | end
89 | %%
90 |
91 |
92 |
93 | %% Perform functional connectivity
94 |
95 | % define
96 | wh = {1:10 1:2:10 2:2:10}; % define sets of sessions to analyze
97 | whstr = {'All' 'Odd' 'Even'}; % corresponding text labels
98 |
99 | % compute correlation matrices
100 | cmatrix = []; % roi x roi
101 | for p=1:length(wh)
102 |
103 | % concatenate data across runs and sessions
104 | data0 = squish(ptimeseries(:,:,wh{p},:),3); % time x roi
105 |
106 | % compute pairwise correlations
107 | cmatrix(:,:,p) = calcconfusionmatrix(data0,[],2);
108 |
109 | end
110 |
111 | % visualize the results
112 | figureprep([100 100 1000 400],1);
113 | for p=1:3
114 | subplot(1,3,p); hold on;
115 | imagesc(cmatrix(:,:,p),[0.3 1]);
116 | axis image tight;
117 | set(gca,'YDir','reverse');
118 | colormap(copper); colorbar;
119 | title(whstr{p});
120 | end
121 | %%
122 |
123 | % Notice that pairwise correlations are generally highly positive.
124 | % This is likely due to global noise sources in the data that tend
125 | % to cause time series to be correlated, hence motivating efforts
126 | % in the field to attempt to remove these sources of noise.
127 | %%
128 |
129 |
130 |
--------------------------------------------------------------------------------
/matlab/html/example01_exploredata.html:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 | Example 1: Basic exploration of the NSD data files
Example 1: Basic exploration of the NSD data files
% In this script, we are going to do some initial exploration of the
70 | % types of data files that are made available as part of the prepared
71 | % NSD data. For this, we are going to use ITK-SNAP (as a volume viewer)
72 | % and freeview (as a surface viewer).
73 | %
74 | % Skills/concepts:
75 | % - How to use ITK-SNAP and freeview
76 | % - The nature of volumes and surfaces
77 | % - The different types of FreeSurfer surfaces
78 | % - MNI and fsaverage spaces
79 |
Use ITK-SNAP to explore volume-format data
% Load the following file into ITK-SNAP:
80 | % ~/nsd/nsddata/ppdata/subj01/anat/T1_0pt8_masked.nii.gz
81 |
% Play around in ITK-SNAP and know how to:
82 | % - Navigate / Zoom / Pan
83 | % - Change the colormap and color range
84 | % - View the NIFTI header information (e.g. resolution, origin, orientation)
85 |
% Load as a second image the following file:
86 | % ~/nsd/nsddata/ppdata/subj01/anat/roi/visualsulc.nii.gz
87 |
% Play around and know how to:
88 | % - Flip through different volumes ([ or ])
89 | % - Overlay volumes using a thresholding approach
90 | % - Overlay volumes using transparency
91 | % - Toggle overlays on/off (w)
92 |
% Draw an ROI and save the ROI out as a NIFTI file called:
93 | % testroi.nii.gz
94 | % Then load that file back into ITK-SNAP.
95 |
% Start over. Load T1_0pt8_masked.nii.gz as the main image,
96 | % and load roi/visualsulc.nii.gz as the segmentation image.
97 |
% Using Label Editor, hide all of the labels and show only
98 | % Label 3 (fusiform gyrus). Play around with the 3D volume rendering.
99 | % Information on the meaning of the values in visualsulc is in:
100 | % ~/nsd/nsddata/freesurfer/fsaverage/label/visualsulc.mgz.ctab
101 |
% Quit ITK-SNAP and try launching it from the command-line:
102 | % cd ~/nsd/nsddata/ppdata/subj01/anat/
103 | % itksnap T1_0pt8_masked.nii.gz
104 |
% There are many other volume viewers out there.
105 | % If you are curious, you can try, for example, mricro.
106 |
Inspect NSD anatomical data (anat)
% The prepared NSD data come in 3 anatomical spaces (0.5 mm, 0.8 mm, and 1.0 mm).
107 | % These different spaces are exactly coincident and share exactly the same
108 | % field-of-view and origin. (The origin is placed at the center of each volume.)
109 |
110 | % Load the 0.5-mm and 1.0-mm T1 volumes as a main image and an additional image,
111 | % respectively. Compare the two volumes.
112 |
% Load the 0.5-mm and 1.0-mm T1 volumes in two separate ITK-SNAP sessions with
113 | % synchronized views (see Preferences). Play around a little.
114 |
% Assess the co-registration quality between the 0.8-mm T1 and 0.8-mm T2.
115 | % Notice that compared to the T2, the T1 has a little bit of dropout in
116 | % the ventral part of the frontal lobe.
117 |
% Inspect the Kastner atlas (roi/Kastner2015.nii.gz) overlaid on the 0.8-mm T1.
118 | % Information on the meaning of the values is in:
119 | % ~/nsd/nsddata/freesurfer/fsaverage/label/Kastner2015.mgz.ctab
120 |
% Explore and visualize some additional volumes:
121 | % - aseg (anatomical segmentation from FreeSurfer)
122 | % - brainmask (liberal brain mask used to de-identify data and reduce file sizes)
123 | % - EPI_to_anat1pt0 (mean EPI volume that has been warped to the anat1pt0 space)
124 |
% Thus far, we have been visualizing data from a single subject.
125 | % To facilitate comparison across subjects, we can put subjects in a common space.
126 | % A typical one is MNI space, and the prepared NSD data already include some
127 | % MNI transformations for your convenience.
128 |
129 | % To determine the transformation between an individual subject and MNI space,
130 | % the T1 from that subject has been nonlinearly warped to an MNI template.
131 | % Furthermore, a version of each subject's T1 that reflects this warping (and
132 | % resampling) has been saved. Let's use ITK-SNAP to quickly look at the MNI
133 | % template and each individual subject's warped T1 volume.
134 | % cd ~/nsd/nsddata/
135 | % itksnap -g templates/MNI152_T1_1mm.nii.gz -o ppdata/subj*/anat/T1_to_MNI.nii.gz
136 | % How good is the alignment?
137 |
Inspect NSD functional data (func1pt8mm)
% The prepared NSD data come in 2 functional spaces (1.8 mm, 1.0 mm).
138 | % These spaces are aligned to each other, but have slightly different field-of-views
139 | % and origins (see 'nsddata description' for details). Note that the functional spaces
140 | % are not the same as the anatomical spaces.
141 |
142 | % Load floc_facestval.nii.gz (t-value for the contrast of faces vs. non-faces from
143 | % the floc category localizer experiment) and overlay it on mean.nii.gz.
144 |
% Alternatively, overlay the t-values on T1_to_func1pt8mm.nii.gz (this is a
145 | % version of the T1 that has already been warped to the func1pt8mm space).
146 |
% Explore and visualize some additional volumes:
147 | % - R2 (a measure of signal quality from the NSD experiment)
148 | % - prf_eccentricity (estimate of pRF eccentricity from the prf experiment)
149 | % - valid (fraction of NSD scan sessions in which valid data were recorded)
150 | % - R2_session??.nii.gz (R2 from individual NSD scan sessions)
151 | % Hint: itksnap -g mean.nii.gz -o R2_*.nii.gz
152 |
Use freeview to explore surface-format data
% Compared to volume data, surface data are a bit trickier to
153 | % visualize and manage. We will use FreeSurfer's freeview as
154 | % a simple surface viewer, though there are many alternatives.
155 |
156 | % Load subj01's left hemisphere inflated surface:
157 | % ~/nsd/nsddata/freesurfer/subj01/surf/lh.inflated
158 |
% Play around in freeview and know how to:
159 | % - Navigate / Rotate / Zoom / Pan
160 |
% Load as an overlay (generic):
161 | % ~/nsd/nsddata/freesurfer/subj01/label/lh.flocfacestval.mgz
162 |
% Play around and know how to:
163 | % - Change the colormap and color range
164 |
% Set the Render to 'Surface & mesh' to see the faces and vertices
165 | % that comprise a surface.
166 |
% Set the Render back to Surface and load in:
167 | % ~/nsd/nsddata/freesurfer/subj01/label/lh.visualsulc.mgz
168 | % Develop a good visualization of this atlas.
169 | % Save a screenshot.
170 |
% Now visualize the same data (visualsulc) on some
171 | % other versions of the cortical surface:
172 | % lh.white, lh.pial, lh.sphere
173 | % Use the same color settings as in the lh.inflated case and
174 | % save some screenshots to facilitate comparison.
175 |
% Finally, to illustrate the folding-based registration
176 | % that underlies FreeSurfer's fsaverage surface, take
177 | % screenshots of the following (be careful to keep
178 | % the camera view fixed):
179 | % (1) subj01's lh.sphere - curvature
180 | % (2) subj01's lh.sphere - Kastner2015
181 | % (3) subj01's lh.sphere.reg - curvature
182 | % (4) subj01's lh.sphere.reg - Kastner2015
183 | % (5) fsaverage's lh.sphere - curvature
184 | % (6) fsaverage's lh.sphere - Kastner2015
185 | % Compare these screenshots and notice how sphere.reg is a
186 | % surface such that visualizing subj01's curvature on that surface
187 | % yields a map that looks well matched to fsaverage's curvature.
188 |
% Like MNI, fsaverage is a common space that can be used to
189 | % compare results across subjects. MNI is a volume-based space
190 | % that is semi-accurate for cortex and is most accurate for
191 | % subcortical structures. In contrast, fsaverage is a surface-based
192 | % space that is applicable only to cortex.
193 |
% nsd_mapdata.m is a utility function provided with NSD
70 | % that performs mapping between various NSD spaces (e.g. functional,
71 | % anatomical, surface). Please see nsdcode/matlab/examples_nsdmapdata.m
72 | % for a comprehensive example of how to use that function.
73 | %
74 | % Skills/concepts:
75 | % - How to use nsd_mapdata.m
76 | % - Interpolation/resampling
77 | % - Mapping between volume and surface
78 | % - Mapping between individual subjects and fsaverage
79 |
% In this script, we demonstrate two different ways to quickly and effectively
70 | % inspect large amounts of data. The first way is to use a high framerate
71 | % movie, thus exploiting the rapidity of our visual system. The second way is
72 | % to use an image to visualize many time series at once.
73 | %
74 | % Skills/concepts:
75 | % - Manipulation of images, volumes, and movies
76 | % - Image normalization and color ranges
77 | % - aseg (from FreeSurfer)
78 |
Make a movie of brain volumes over time
% In this example, we will inspect all of the pre-processed fMRI time-series data
79 | % from the first NSD session from subj01. Knowing how to use low-level routines
80 | % to create the movie is useful as it allows you to customize the movie to your needs.
81 |
82 | % define
83 | files = '~/nsd/nsddata_timeseries/ppdata/subj01/func1pt8mm/timeseries/timeseries_session01_*.nii.gz';
84 | outfile = 'testmovie'; % we will add file extensions later
85 | fps = 72; % frames per second for the movie
86 |
87 | % find the files
88 | files0 = matchfiles(files);
89 |
90 | % load the data from each run and construct orthographic image inspections
91 | im = uint8([]);
92 | cnt = 1;
93 | for p=1:length(files0)
94 |
95 | % load data and convert to double
96 | data0 = load_untouch_nii(files0{p});
97 | data0 = double(data0.img);
98 |
99 | % for each volume, make an image
100 | for r=1:size(data0,4)
101 |
102 | % pull out middle slice in all three dimensions
103 | imA = []; % 120 x 120 x 3
104 | imA = cat(3,imA,placematrix(zeros(120,120), squeeze(data0(:,:,round(end/2),r))));
105 | imA = cat(3,imA,placematrix(zeros(120,120),rotatematrix(squeeze(data0(:,round(end/2),:,r)),1,2,1)));
106 | imA = cat(3,imA,placematrix(zeros(120,120),rotatematrix(squeeze(data0(round(end/2),:,:,r)),1,2,1)));
107 |
108 | % normalize, make a mosaic, and convert to uint8
109 | im(:,:,cnt) = uint8(255*makeimagestack(imA,[0 2000],1,[1 3]));
110 | cnt = cnt + 1;
111 |
112 | % the first ten volumes of each run get marked by a little white square
113 | if r <= 10
114 | im(1:4,1:4,end) = 255;
115 | end
116 |
117 | end
118 |
119 | end
120 |
121 | % visualize one image
122 | figure; imshow(im(:,:,1));
123 |
% write all of the images to a single .mov file
124 | imagesequencetomovie(im,sprintf('%s.mov',outfile),fps);
125 |
126 | % make a compressed version of the movie (mpeg-4 format)
127 | unix_wrapper(sprintf('HandBrakeCLI -i %s.mov -q 2 --strict-anamorphic -o %s.m4v',outfile,outfile));
128 |
129 | % note that instead of QTWriter (used by imagesequencetomovie.m), we could perhaps use
130 | % other utilities such as built-in MATLAB toolbox functions and/or ffmpeg.
131 |
Use a carpet plot (Power NeuroImage 2017) to quickly inspect time-series data
% In this example, we will use a carpet plot to visualize the same data as above.
220 |
221 | % define
222 | files = '~/nsd/nsddata_timeseries/ppdata/subj01/func1pt8mm/timeseries/timeseries_session01_*.nii.gz';
223 | asegfile = '~/nsd/nsddata/ppdata/subj01/func1pt8mm/aseg.nii.gz';
224 |
225 | % define more
226 | grayix = [42 3]; % gray matter (rh is 42, lh is 3)
227 | wmix = [41 2]; % white matter (rh is 41, lh is 2)
228 | csfix = 24; % CSF
229 | maxplot = 500; % maximum number of voxels to plot from each compartment
230 |
231 | % use aseg to decide which voxels to extract
232 | aseg = load_untouch_nii(asegfile);
233 | grayvx = picksubset(find(ismember(aseg.img,grayix)),maxplot);
234 | wmvx = picksubset(find(ismember(aseg.img,wmix)),maxplot);
235 | csfvx = picksubset(find(ismember(aseg.img,csfix)),maxplot);
236 |
237 | % calculate row bounds
238 | rowbounds = [length(grayvx) length(wmvx) length(csfvx)];
239 | rowbounds = cumsum(rowbounds);
240 |
241 | % find the time-series files
242 | files0 = matchfiles(files);
243 |
244 | % for each file, collect the data
245 | colbounds = [];
246 | alldata = [];
247 | for p=1:length(files0)
248 |
249 | % load data, convert to double, flatten voxels
250 | data0 = load_untouch_nii(files0{p});
251 | data0 = squish(double(data0.img),3);
252 |
253 | % extract data and z-score each voxel
254 | temp = cat(1,data0(grayvx,:),data0(wmvx,:),data0(csfvx,:));
255 | temp = calczscore(temp,2); % there are other ways we could normalize...
256 |
257 | % record
258 | alldata = cat(2,alldata,temp);
259 |
260 | % calculate column bounds
261 | colbounds = [colbounds size(data0,2)];
262 |
263 | end
264 | colbounds = cumsum(colbounds);
265 |
266 | % make the visualization
267 | figureprep([100 100 800 600],1); hold on;
268 | imagesc(alldata,[-3 3]);
269 | colormap(gray);
270 | axis ij;
271 | axis([.5 size(alldata,2)+.5 .5 size(alldata,1)+.5]);
272 | straightline(rowbounds+.5,'h','r-');
273 | straightline(colbounds+.5,'v','r-');
274 | xlabel('Volume number');
275 | ylabel('Voxel');
276 |
% In the plot, the three row compartments are gray matter, white matter, and CSF,
277 | % and the column compartments correspond to different fMRI runs.
278 |
% In this script, we go through an example of loading and inspecting the NSD betas.
70 | %
71 | % Skills/concepts:
72 | % - Thinking about data units
73 | % - Data visualization
74 | % - Simple ROI definition
75 | % - Non-trivial indexing into the NSD data
76 |
General setup
% define
77 | stimfile = '~/nsd/nsddata_stimuli/stimuli/nsd/nsd_stimuli.hdf5';
78 | expfile = '~/nsd/nsddata/experiments/nsd/nsd_expdesign.mat';
79 | subjix = 3; % which NSD subject we are analyzing
80 | nsess = 32; % how many NSD sessions are available
81 | betaver = 'betas_fithrf'; % which beta version to load
82 |
Define ROI
% We are going to examine NSD betas for one region of interest (ROI).
83 | % Specifically, right hemisphere (RH) fusiform face area (FFA).
84 | % Here, we go through one approach for defining that region.
85 |
86 | % load in t-values for faces vs. nonfaces from the floc experiment
87 | a1 = load_untouch_nii(sprintf('~/nsd/nsddata/ppdata/subj%02d/func1pt8mm/floc_facestval.nii.gz',subjix));
88 |
89 | % in ITK-SNAP, look at the NIFTI file and determine a small box in
90 | % the appropriate anatomical location and containing high t-values (e.g. > 5)
91 | lrix = 58:66; % from left to right
92 | paix = 31:54; % from posterior to anterior
93 | isix = 25:32; % from inferior to superior
94 |
95 | % note that this a rough-and-ready approach to defining the ROI. it is
96 | % sufficient for the sake of this example, but a more accurate approach
97 | % would be to examine the data on the cortical surface.
98 |
99 | % create a binary volume with the box
100 | boxvol = zeros(size(a1.img));
101 | boxvol(lrix,paix,isix) = 1;
102 |
103 | % define the ROI as within the box AND t > 5
104 | mask = boxvol & a1.img > 5;
105 |
106 | % to aid in loading the data, compute a tight fitting box and associated indices
107 | [d1,d2,d3,ii] = computebrickandindices(mask);
108 |
Load in the betas
% load data
109 | data = []; % voxels x 750 trials x sessions
110 | for p=1:nsess
111 | fprintf('sess %d...',p);
112 | file0 = sprintf('~/nsd/nsddata_betas/ppdata/subj%02d/func1pt8mm/%s/betas_session%02d.mat',subjix,betaver,p);
113 | a1 = matfile(file0);
114 | temp = double(a1.betas(d1,d2,d3,:))/300; % convert to double and then convert to percent signal change
115 | temp = squish(temp,3); % flatten voxels
116 | temp = temp(ii,:); % extract the voxels we want
117 | data(:,:,p) = temp; % record
118 | end
119 |
% visualize data in original units (percent signal change)
120 | figure; hold on;
121 | imagesc(reshape(data,size(data,1),[]),[-10 10]);
122 | axis ijtight;
123 | colormap(cmapsign4); colorbar;
124 | xlabel('Trial');
125 | ylabel('Voxel');
126 | title('Response (% BOLD)');
127 |
% Notice the high heterogeneity across voxels.
128 |
% visualize each voxel's mean PSC
129 | figure; hold on;
130 | bar(mean(mean(data,2),3));
131 | xlabel('Voxel');
132 | ylabel('Average response (% BOLD)');
133 |
% This reinforces the point: voxels have highly different overall BOLD responses.
134 |
% on a per-voxel basis, z-score the betas obtained in each session.
135 | % this is a fairly drastic measure, but ensures stationarity across time
136 | % and comparable units across voxels.
137 | dataZ = calczscore(data,2);
138 |
139 | % visualize data again in z-score units
140 | figure; hold on;
141 | imagesc(reshape(dataZ,size(dataZ,1),[]),[-3 3]);
142 | axis ijtight;
143 | colormap(cmapsign4); colorbar;
144 | xlabel('Trial');
145 | ylabel('Voxel');
146 | title('Response (z-score units)');
147 |
Load in experiment information
% load
148 | exp1 = load(expfile);
149 | theorder = exp1.masterordering(1:750*nsess); % the trials that we have data for
150 | uniqueix = union(theorder,[]); % unique indices into the 10k
151 | length(theorder) % total number of trials
152 | length(uniqueix) % total number of unique images
153 |
% define
164 | numtodo = 100; % number of distinct images to plot responses for
165 |
166 | % massage dimensionality
167 | dataZ = reshape(dataZ,size(dataZ,1),[]); % voxels x trials*sessions
168 |
169 | % make the plot
170 | todo = picksubset(uniqueix,numtodo); % pick a small subset to plot
171 | versions = {'Regular''Shuffled'};
172 | for ver=1:2
173 | figureprep([100 100 1000 300],1); hold on;
174 | avgresp = [];
175 | for p=1:length(todo)
176 | switch ver
177 | case 1
178 | ix = find(theorder==todo(p)); % which trials correspond to the image
179 | case 2
180 | ix = find(permutedim(theorder==todo(p))); % SHUFFLE!
181 | end
182 | yy = mean(dataZ(:,ix),1); % compute ROI average for each trial
183 | scatter(repmat(p,[1 length(yy)]),yy,'ro');
184 | avgresp(p) = mean(yy);
185 | end
186 | h = plot(1:length(todo),avgresp,'ko-');
187 | set(h,'MarkerFaceColor','k');
188 | ax = axis;
189 | axis([ax(1:2) -2 2]);
190 | xlabel('Image');
191 | ylabel('Response (z-score units)');
192 | title(versions{ver});
193 | end
194 |
% In the 'Regular' plot, we are looking for small within-image variability
195 | % compared to the across-image variability. The 'Shuffled' plot looks slightly
196 | % different compared to the 'Regular' plot, indicating that the image-evoked
197 | % signal in the data is fairly weak. This is expected given the noise in fMRI
198 | % data, the fact that an aggressive rapid event-related design was used in NSD
199 | % (where responses to successive trials overlap substantially), and the small
200 | % number of trials per distinct image that was used in the design.
201 |
Look at best and worst images
% compute the mean across trials only for those images with all 3 trials
202 | newdata = []; % voxels x images with the trial-averaged response
203 | newdatastim = []; % 1 x images with indices into the 10k
204 | for p=1:length(uniqueix)
205 | ix = find(theorder==uniqueix(p));
206 | if length(ix)==3
207 | newdata(:,end+1) = mean(dataZ(:,ix),2);
208 | newdatastim(end+1) = uniqueix(p);
209 | end
210 | end
211 |
212 | % sort ROI-averaged response in descending order
213 | [ss,ssix] = sort(mean(newdata,1),'descend');
214 |
215 | % plot best and worst images
216 | strs = {'Best''Worst'};
217 | for flag=1:2
218 | figureprep([100 100 1000 300],1);
219 | for p=1:5
220 |
221 | % figure out 73k ID
222 | switch flag
223 | case 1
224 | id73k = exp1.subjectim(subjix,newdatastim(ssix(p)));
225 | case 2
226 | id73k = exp1.subjectim(subjix,newdatastim(ssix(end-p+1)));
227 | end
228 |
229 | % get image (425 x 425 x 3, uint8)
230 | im = permute(h5read(stimfile,'/imgBrick',[1 1 1 id73k],[3 425 425 1]),[3 2 1]);
231 |
232 | % plot it
233 | subplot(1,5,p);
234 | imshow(im);
235 | if p==1
236 | title(strs{flag});
237 | end
238 |
239 | end
240 | end
241 |
% As expected, the images that most strongly drove the response tend to have faces.
242 |
% In this script, we perform a few simple analyses of the behavioral data
70 | % that were collected during the NSD experiment. These data are rich and
71 | % extensive, as they reflect up to 30,000 trials from a given subject.
72 | %
73 | % Skills/concepts:
74 | % - Various plotting techniques
75 | % - Bootstrapping
76 |
% plot histogram of RTs
107 | bins = 0:50:4200; % use the same bins for every subject
108 | figureprep([100 100 1000 400],1);
109 | for subjix=1:8
110 | subplot(2,4,subjix); hold on;
111 | hist(thedata{subjix}(:,10),bins);
112 | straightline(0:500:4200,'v','r:');
113 | xlabel('Reaction time (ms)');
114 | ylabel('Frequency');
115 | title(sprintf('Subject %d',subjix));
116 | end
117 |
RTs as a function of time
% define
118 | subjix = 1;
119 |
120 | % Plot RT as a function of time
121 | figure; hold on;
122 | scatter(thedata{subjix}(:,7),thedata{subjix}(:,10),'ro'); % note that NaNs just disappear
123 | xlabel('Number of days');
124 | ylabel('Reaction time (ms)');
125 |
% The figure is hard to interpret given the very large
126 | % number of dots. Thus, we will also plot a summary metric.
127 |
% Plot the median RT in each session
128 | allsess = unique(thedata{subjix}(:,2)); % all sessions
129 | time0 = []; % 1 x N with the mean time
130 | md = []; % 1 x N with the median RT
131 | for p=1:length(allsess)
132 | ix = find(thedata{subjix}(:,2) == allsess(p)); % trials to consider
133 | ix = ix(isfinite(thedata{subjix}(ix,10))); % only consider those with valid data
134 | time0(p) = mean(thedata{subjix}(ix,7)); % mean time
135 | md(p) = median(thedata{subjix}(ix,10)); % median RT
136 | end
137 | plot(time0,md,'ko-','LineWidth',2);
138 |
% Note that the second black data point is a little funny because it
139 | % reflects data pooled from two split sessions (frankenstein session).
140 | % Reaction times were fairly stable throughout the experiment.
141 |
Calculate percent correct in each session, bootstrapping to get reliability
% Here, we will use bootstrapping to estimate the reliability of
142 | % the percent correct obtained in each scan session. The use of
143 | % bootstrapping is a bit overkill, as we could alternatively just
144 | % use parametric error estimates from the binomial distribution.
145 | % But, it is nonetheless useful to demonstrate how bootstrapping
146 | % can be implemented.
147 |
148 | % do it
149 | numboot = 100;
150 | pctcorrect = NaN*zeros(8,40,numboot); % initialize with NaNs
151 | for subjix=1:8
152 | for p=1:40
153 | ix = thedata{subjix}(:,2)==p; % find trials
154 | if sum(ix ~= 0) % some subjects did not complete all 40
155 | for boot=1:numboot
156 |
157 | % extract data
158 | subjdata = thedata{subjix}(ix,:); % trials x columns
159 |
160 | % perform bootstrap sampling (see also bootstrp.m)
161 | n = size(subjdata,1); % how many trials are there?
162 | bootix = ceil(n*rand(1,n)); % generate bootstrap indices
163 | bootdata = subjdata(bootix,:); % create the sample
164 |
165 | % calculate percent correct
166 | isok = bootdata(:,19)==0; % which rows have valid data?
167 | pctcorrect(subjix,p,boot) = mean(bootdata(isok,9)==1) * 100; % NaNs are treated as false here
168 |
169 | end
170 | end
171 | end
172 | end
173 |
174 | % visualize
175 | figure; hold on;
176 | cmap0 = jet(8);
177 | h1 = []; h2 = [];
178 | for subjix=1:8
179 | pp0 = prctile(pctcorrect(subjix,:,:),[16 84],3); % 1 x 40 x 2 (68% confidence interval)
180 | md0 = median(pctcorrect(subjix,:,:),3); % 1 x 40
181 | h1(subjix) = errorbar3(1:40,md0,permute(pp0,[3 2 1]),'v',(cmap0(subjix,:)+[1 1 1])/2);
182 | h2(subjix) = plot(md0,'-','LineWidth',2,'Color',cmap0(subjix,:));
183 | end
184 | uistack(h2,'top'); % ensure the median lines are on top
185 | xlabel('Session number');
186 | ylabel('Percent correct');
187 |
% In this script, we provide a simple example of performing
70 | % representational similarity analysis. We actually do this
71 | % not on the core NSD data but on the category-localizer floc
72 | % experiment that was also conducted in the NSD subjects.
73 | %
74 | % Skills/concepts:
75 | % - Creating representational dissimilarity matrices
76 |
Load data
% load the names of the categories used in the floc experiment
77 | catlabels = importdata('~/nsd/nsddata/experiments/floc/categories.tsv');
78 |
79 | % load in floc betas from a ventral temporal cortex ROI
80 | betas = {};
81 | for subjix=1:8
82 |
83 | % load in the visualsulc atlas
84 | roi1 = load_untouch_nii(sprintf('~/nsd/nsddata/ppdata/subj%02d/func1pt8mm/roi/visualsulc.nii.gz',subjix));
85 |
86 | % load in the floc betas (the 60 betas are ordered as 6 condition-splits * 10 categories)
87 | a1 = load_untouch_nii(sprintf('~/nsd/nsddata/ppdata/subj%02d/func1pt8mm/floc_betas.nii.gz',subjix));
88 |
89 | % extract data for voxels within the union of OTS, FG, mFus, CoS
90 | betas{subjix} = subscript(squish(a1.img,3),{find(ismember(roi1.img,[1 3 4 6])) ':'}); % voxels x betas
91 |
92 | end
93 | betas
94 |
% do it
112 | cmatrix = []; % 10 x 10 x subjects, values are in [0,2]
113 | for subjix=1:8
114 |
115 | % average across condition-splits
116 | temp = squish(mean(reshape(betas{subjix},[],6,10),2),2); % voxels x 10 categories
117 |
118 | % calculate pairwise correlations of activity patterns and subtract from 1
119 | cmatrix(:,:,subjix) = 1-calcconfusionmatrix(temp,[],2);
120 |
121 | end
122 |
% Note that results will vary depending on the units of the data and the
136 | % choice of similarity metric. In the above example, we used the
137 | % beta weights as given in units of percent BOLD signal change and
138 | % used one minus Pearson's correlation as the measure of dissimilarity.
139 | % One might consider other similarity metrics and/or normalization of the
140 | % data prior to RDM construction (e.g. mean-subtraction or z-scoring
141 | % of voxel responses or activity patterns). Caution should be exercised
142 | % to ensure proper intepretation of results.
143 |
% In this script, we show an example of how one might perform a functional-
70 | % connectivity analysis on the resting-state data collected as part of NSD.
71 | % This type of data and analysis is often termed 'resting-state functional
72 | % connectivity' (RSFC). In its simplest form, RSFC is essentially just
73 | % correlating time-series across different voxels (or regions).
74 | %
75 | % Skills/concepts:
76 | % - Loading the NSD pre-processed time-series data
77 | % - Simple forms of signal processing and normalization
78 | % - Using the HCP_MMP atlas
79 |
% prepare projection matrix to perform high-pass filtering
84 | polymatrix = constructpolynomialmatrix(226,0:1); % constant term, linear term
85 | pmatrix = projectionmatrix(polymatrix); % create projection matrix
86 |
87 | % load time-series data
88 | ptimeseries = []; % time x 2 runs x 10 sessions x 180 ROIs
89 | sesstoload = 21:30;
90 | runstoload = [1 14];
91 | for p=1:length(sesstoload)
92 | fprintf('sess=%d...',sesstoload(p));
93 |
94 | for r=1:length(runstoload)
95 |
96 | % load data
97 | file0 = '~/nsd/nsddata_timeseries/ppdata/subj%02d/func1pt8mm/timeseries/timeseries_session%02d_run%02d.nii.gz';
98 | a1 = load_untouch_nii(sprintf(file0,subjix,sesstoload(p),runstoload(r)));
99 | data = squish(single(a1.img),3); % XYZ x time
100 |
101 | % prepare time series for each of the ROIs in the atlas
102 | for roi=1:180
103 |
104 | % average across voxels
105 | temp = mean(data(find(roi1.img==roi),:),1)'; % time x 1
106 |
107 | % high-pass filter the data (i.e. subtract mean and linearly detrend)
108 | temp = pmatrix*temp;
109 |
110 | % z-score
111 | temp = calczscore(temp);
112 |
113 | % record
114 | ptimeseries(:,r,p,roi) = temp;
115 |
116 | % Note that RSFC analyses typically involve more aggressive procedures that
117 | % attempt to remove noise from the time-series data. Also note that in the
118 | % code above, the time series from different voxels are averaged before
119 | % detrending and normalization, but one might wish to average as the last
120 | % step instead (the order of operations will affect the result).
121 |
122 | end
123 |
124 | end
125 | end
126 |
% plot time series for every 10th region
127 | figureprep([100 100 1000 600],1);
128 | for roi=10:10:180
129 | subplot(3,6,roi/10); hold on;
130 | plot(squish(ptimeseries(:,:,:,roi),3));
131 | title(roilabel1.struct_names{roi});
132 | end
133 |
Perform functional connectivity
% define
134 | wh = {1:10 1:2:10 2:2:10}; % define sets of sessions to analyze
135 | whstr = {'All''Odd''Even'}; % corresponding text labels
136 |
137 | % compute correlation matrices
138 | cmatrix = []; % roi x roi
139 | for p=1:length(wh)
140 |
141 | % concatenate data across runs and sessions
142 | data0 = squish(ptimeseries(:,:,wh{p},:),3); % time x roi
143 |
144 | % compute pairwise correlations
145 | cmatrix(:,:,p) = calcconfusionmatrix(data0,[],2);
146 |
147 | end
148 |
149 | % visualize the results
150 | figureprep([100 100 1000 400],1);
151 | for p=1:3
152 | subplot(1,3,p); hold on;
153 | imagesc(cmatrix(:,:,p),[0.3 1]);
154 | axis imagetight;
155 | set(gca,'YDir','reverse');
156 | colormap(copper); colorbar;
157 | title(whstr{p});
158 | end
159 |
% Notice that pairwise correlations are generally highly positive.
160 | % This is likely due to global noise sources in the data that tend
161 | % to cause time series to be correlated, hence motivating efforts
162 | % in the field to attempt to remove these sources of noise.
163 |