├── 00_lemon_download.py ├── 01_lemon_preproc.py ├── 02_lemon_glms.py ├── 03_lemon_group.py ├── 10_plot_example_confound_design.py ├── 11_lemon_plot_single_subject.py ├── 12_plot_group_glms.py ├── 13_collate_preproc_info.py ├── 20_supp_check_firstlevel_residual_autocorr.py ├── 21_supp_check_firstlevel_distributions.py ├── 22_supp_varcopescaling.py ├── 23_supp_periodogram_parameters.py ├── 25_supp_einsum-varcopes.py ├── 25_supp_linear_sqrt_log.py ├── LICENSE.md ├── README.md ├── glm_config.py ├── glm_config.yaml ├── glmspectrum_env.yml ├── lemon_plotting.py ├── lemon_preproc.yml ├── lemon_structural_vols.csv ├── lemon_support.py ├── requirements.txt └── sub-010060.mat /00_lemon_download.py: -------------------------------------------------------------------------------- 1 | """This script will download the raw EEG data from the MPI-Leipzig_Mind-Brain-Body-LEMON dataset. 2 | 3 | This can be run once before the start of any analyses. It can be skipped if the 4 | data are already present on disk in BIDS format. 5 | 6 | """ 7 | 8 | import os 9 | import urllib.request 10 | from pathlib import Path 11 | 12 | import bs4 13 | import requests 14 | 15 | from glm_config import cfg 16 | 17 | #%% ------------------------------------------------------------ 18 | # Download raw EEG data 19 | 20 | datadir = cfg['lemon_raw_eeg'] 21 | 22 | url = cfg['lemon_raw_url'] 23 | r = requests.get(url) 24 | data = bs4.BeautifulSoup(r.text, "html.parser") 25 | 26 | for l in data.find_all("a"): 27 | subj_id = l.get_text()[:-1] 28 | 29 | subj_url = url + l.get_text() + '/RSEEG' 30 | 31 | subj_r = requests.get(subj_url) 32 | subj_data = bs4.BeautifulSoup(subj_r.text, "html.parser") 33 | 34 | if len(subj_data.find_all("a")) == 4: 35 | local_dir = os.path.join(datadir, subj_id, 'RSEEG') 36 | Path(local_dir).mkdir(parents=True, exist_ok=True) 37 | 38 | for m in subj_data.find_all("a"): 39 | if m.get_text() == '../': 40 | continue 41 | remote_file = subj_url + '/' + m.get_text() 42 | local_file = os.path.join(local_dir, m.get_text()) 43 | print(local_file) 44 | 45 | urllib.request.urlretrieve(remote_file, filename=local_file) 46 | 47 | 48 | #%% --------------------------- 49 | # 50 | # header errors (wrong DataFile and MarkerFile) in: 51 | # sub-010193.vhdr 52 | # sub-010219.vhdr 53 | # 54 | # Can be fixed by hand 55 | 56 | 57 | #%% ------------------------------------------------------------ 58 | # Download Subject metadata 59 | 60 | 61 | datadir = cfg['lemon_raw'] 62 | fname = 'META_File_IDs_Age_Gender_Education_Drug_Smoke_SKID_LEMON.csv' 63 | 64 | url = cfg['lemon_behav_url'] + fname 65 | 66 | local_file = os.path.join(os.path.dirname(cfg['lemon_raw'].rstrip('/')), fname) 67 | urllib.request.urlretrieve(url, filename=local_file) 68 | -------------------------------------------------------------------------------- /01_lemon_preproc.py: -------------------------------------------------------------------------------- 1 | """This script runs the preprocessing of the raw EEG data. 2 | 3 | The preprocessing chain is defined in lemon_preproc.yml - you can adapt the 4 | preprocessing by changing the values in this file. 5 | 6 | The script can be set to preprocess just the single example subject by changing 7 | 'subj' in the main body. 8 | 9 | """ 10 | 11 | import os 12 | 13 | import osl 14 | from dask.distributed import Client 15 | 16 | from glm_config import cfg 17 | from lemon_support import (lemon_create_heog, lemon_ica, 18 | lemon_set_channel_montage, lemon_zapline_dss) 19 | 20 | #%% ------------------------------ 21 | # User options to set 22 | 23 | subj = 'all' # Set to 'sub-010060' to run single subject in paper examples 24 | n_dask_workers = 45 # Number of dask parallel workers to use if subj is 'all' 25 | 26 | #%% ------------------------------- 27 | # Main code 28 | 29 | if __name__ == '__main__': 30 | 31 | extra_funcs = [lemon_set_channel_montage, lemon_create_heog, lemon_ica, lemon_zapline_dss] 32 | 33 | fbase = os.path.join(cfg['lemon_raw_eeg'], '{subj}', 'RSEEG', '{subj}.vhdr') 34 | st = osl.utils.Study(fbase) 35 | 36 | config = osl.preprocessing.load_config('lemon_preproc_revisions.yml') 37 | proc_outdir = cfg['lemon_processed_data'] 38 | 39 | if subj == 'all': 40 | # Run everybody using a dask cluster 41 | client = Client(n_workers=n_dask_workers, threads_per_worker=1) 42 | goods = osl.preprocessing.run_proc_batch(config, st.match_files, proc_outdir, 43 | overwrite=True, 44 | extra_funcs=extra_funcs, 45 | dask_client=True, 46 | gen_report=False) 47 | else: 48 | # Run a single subject 49 | inputs = st.get(subj=subj) 50 | if len(inputs) == 0: 51 | print(f"Subject '{subj}' not found in raw_data directory") 52 | osl.preprocessing.run_proc_chain(inputs[0], config, 53 | outdir=proc_outdir, 54 | extra_funcs=extra_funcs) 55 | -------------------------------------------------------------------------------- /02_lemon_glms.py: -------------------------------------------------------------------------------- 1 | """This script runs the first-level GLM-Spectra and prepares the group data. 2 | 3 | The script can be set to preprocess just the single example subject by changing 4 | 'subj' in the main body. 5 | 6 | If the group-level data collation is run, then all glm-spectrum datasets found 7 | in the output dir are collated. 8 | 9 | """ 10 | 11 | import glob 12 | import os 13 | 14 | import glmtools as glm 15 | import h5py 16 | import matplotlib.pyplot as plt 17 | import mne 18 | import numpy as np 19 | import osl 20 | import pandas as pd 21 | import sails 22 | from anamnesis import obj_from_hdf5file 23 | 24 | from glm_config import cfg 25 | from lemon_support import (get_eeg_data, lemon_make_bads_regressor, 26 | lemon_make_blinks_regressor, 27 | lemon_make_task_regressor, quick_plot_eog_epochs, 28 | quick_plot_eog_icas) 29 | 30 | plt.switch_backend('agg') 31 | 32 | #%% -------------------------------------------------------------- 33 | # Functions for first level analysis 34 | 35 | 36 | def run_first_level(fname, outdir): 37 | """Run the first-level GLM-Spectrum for a single datafile.""" 38 | runname = fname.split('/')[-1].split('.')[0] 39 | print('processing : {0}'.format(runname)) 40 | 41 | subj_id = osl.utils.find_run_id(fname) 42 | 43 | raw = mne.io.read_raw_fif(fname, preload=True) 44 | 45 | icaname = fname.replace('preproc_raw.fif', 'ica.fif') 46 | ica = mne.preprocessing.read_ica(icaname) 47 | 48 | picks = mne.pick_types(raw.info, eeg=True, ref_meg=False) 49 | chlabels = np.array(raw.info['ch_names'], dtype=h5py.special_dtype(vlen=str))[picks] 50 | 51 | #%% ---------------------------------------------------------- 52 | # GLM-Prep 53 | 54 | # Make blink regressor 55 | fout = os.path.join(outdir, '{subj_id}_blink-summary.png'.format(subj_id=subj_id)) 56 | blink_vect, numblinks, evoked_blink = lemon_make_blinks_regressor(raw, figpath=fout) 57 | 58 | fout = os.path.join(outdir, '{subj_id}_icaeog-summary.png'.format(subj_id=subj_id)) 59 | quick_plot_eog_icas(raw, ica, figpath=fout) 60 | 61 | fout = os.path.join(outdir, '{subj_id}_{0}.png'.format('{0}', subj_id=subj_id)) 62 | quick_plot_eog_epochs(raw, figpath=fout) 63 | 64 | veog = np.abs(raw.get_data(picks='ICA-VEOG')[0, :])#**2 65 | #veog = veog > np.percentile(veog, 97.5) 66 | 67 | heog = np.abs(raw.get_data(picks='ICA-HEOG')[0, :])#**2 68 | #heog = heog > np.percentile(heog, 97.5) 69 | 70 | # Make task regressor 71 | task = lemon_make_task_regressor({'raw': raw}) 72 | 73 | # Make bad-segments regressor 74 | bads_raw = lemon_make_bads_regressor(raw, mode='raw') 75 | bads_diff = lemon_make_bads_regressor(raw, mode='diff') 76 | 77 | # Get data 78 | XX = get_eeg_data(raw).T 79 | print(XX.shape) 80 | 81 | # Run GLM-Periodogram 82 | conds = {'Eyes Open': task == 1, 'Eyes Closed': task == -1} 83 | covs = {'Linear Trend': np.linspace(0, 1, raw.n_times)} 84 | confs = {'Bad Segs': bads_raw, 85 | 'Bad Segs Diff': bads_diff, 86 | 'V-EOG': veog, 'H-EOG': heog} 87 | eo_val = np.round(np.sum(task == 1) / len(task), 3) 88 | ec_val = np.round(np.sum(task == -1) / len(task), 3) 89 | conts = [{'name': 'OverallMean', 'values': {'Constant': 1, 90 | 'Eyes Open': eo_val, 91 | 'Eyes Closed': ec_val}}, 92 | {'name': 'RestMean', 'values': {'Eyes Open': 0.5, 'Eyes Closed': 0.5}}, 93 | {'name': 'Eyes Open AbsEffect', 'values': {'Constant': 1, 'Eyes Open': 0.5}}, 94 | {'name': 'Eyes Closed AbsEffect', 'values': {'Constant': 1, 'Eyes Closed': 0.5}}, 95 | {'name': 'Open > Closed', 'values': {'Eyes Open': 1, 'Eyes Closed': -1}}] 96 | 97 | fs = raw.info['sfreq'] 98 | 99 | # Reduced model - no confounds or covariates 100 | freq_vect0, copes0, varcopes0, extras0 = sails.stft.glm_periodogram( 101 | XX, axis=0, fit_constant=True, 102 | conditions=conds, contrasts=conts, 103 | nperseg=int(fs * 2), noverlap=int(fs), 104 | fmin=0.1, fmax=100, fs=fs, 105 | mode="magnitude", fit_method="glmtools", 106 | ) 107 | model0, design0, data0 = extras0 108 | print(model0.contrast_names) 109 | print(model0.design_matrix.shape) 110 | 111 | fs = raw.info['sfreq'] 112 | 113 | # Full model 114 | freq_vect, copes, varcopes, extras = sails.stft.glm_periodogram( 115 | XX, axis=0, fit_constant=True, 116 | conditions=conds, covariates=covs, 117 | confounds=confs, contrasts=conts, 118 | nperseg=int(fs * 2), noverlap=int(fs), 119 | fmin=0.1, fmax=100, fs=fs, 120 | mode="magnitude", fit_method="glmtools", 121 | ) 122 | 123 | model, design, data = extras 124 | print(model.contrast_names) 125 | print(model.design_matrix.shape) 126 | 127 | data.info['dim_labels'] = ['Windows', 'Frequencies', 'Sensors'] 128 | 129 | print('----') 130 | print('Reduced Model AIC : {0} - R2 : {1}'.format(model0.aic.mean(), model0.r_square.mean())) 131 | print('Full Model AIC : {0} - R2 : {1}'.format(model.aic.mean(), model.r_square.mean())) 132 | 133 | data.info['dim_labels'] = ['Windows', 'Frequencies', 'Sensors'] 134 | 135 | hdfname = os.path.join(outdir, '{subj_id}_glm-data.hdf5'.format(subj_id=subj_id)) 136 | if os.path.exists(hdfname): 137 | print('Overwriting previous results') 138 | os.remove(hdfname) 139 | with h5py.File(hdfname, 'w') as F: 140 | model.to_hdf5(F.create_group('model')) 141 | model0.to_hdf5(F.create_group('reduced_model')) 142 | design.to_hdf5(F.create_group('design')) 143 | design0.to_hdf5(F.create_group('reduced_design')) 144 | data.to_hdf5(F.create_group('data')) 145 | F.create_dataset('freq_vect', data=freq_vect) 146 | F.create_dataset('chlabels', data=chlabels) 147 | F.create_dataset('scan_duration', data=raw.times[-1]) 148 | F.create_dataset('num_blinks', data=numblinks) 149 | 150 | fout = os.path.join(outdir, '{subj_id}_glm-design.png'.format(subj_id=subj_id)) 151 | design.plot_summary(show=False, savepath=fout) 152 | fout = os.path.join(outdir, '{subj_id}_glm-efficiency.png'.format(subj_id=subj_id)) 153 | design.plot_efficiency(show=False, savepath=fout) 154 | 155 | quick_plot_firstlevel(hdfname, raw.filenames[0]) 156 | 157 | plt.close('all') 158 | 159 | 160 | def quick_plot_firstlevel(hdfname, rawpath): 161 | """Make a summary plot for a first level GLM-Spectrum.""" 162 | model = obj_from_hdf5file(hdfname, 'model') 163 | freq_vect = h5py.File(hdfname, 'r')['freq_vect'][()] 164 | 165 | tstat_args = {'varcope_smoothing': 'medfilt', 'window_size': 15, 'smooth_dims': 1} 166 | ts = model.get_tstats(**tstat_args) 167 | ts = model.copes 168 | 169 | plt.figure(figsize=(16, 9)) 170 | for ii in range(13): 171 | ax = plt.subplot(3, 5, ii+1) 172 | ax.plot(freq_vect, ts[ii, :, :]) 173 | ax.set_title(model.contrast_names[ii]) 174 | 175 | outf = hdfname.replace('.hdf5', '_glmsummary.png') 176 | plt.savefig(outf, dpi=300) 177 | plt.close('all') 178 | 179 | 180 | #%% --------------------------------------------------------- 181 | # Select datasets to run 182 | 183 | # Set to * 'sub-010060' to run single subject in paper examples 184 | # * 'all' to run everyone 185 | # * None to run nothing and skip to group preparation 186 | subj = 'all' 187 | 188 | # Whether to collate first level results into group file, set to false if only 189 | # running a single subject 190 | collate_first_levels = True 191 | 192 | #%% ----------------------------------------------------------- 193 | # Run first level GLMs 194 | 195 | proc_outdir = cfg['lemon_processed_data'] 196 | 197 | fbase = os.path.join(cfg['lemon_processed_data'], '{subj}_preproc_raw.fif') 198 | st = osl.utils.Study(fbase) 199 | 200 | if subj == 'all': 201 | inputs = st.match_files 202 | else: 203 | inputs = st.get(subj=subj) 204 | 205 | for fname in inputs: 206 | try: 207 | run_first_level(fname, cfg['lemon_glm_data']) 208 | except Exception as e: 209 | print(e) 210 | pass 211 | 212 | #%% ------------------------------------------------------- 213 | # Run first-level model selection 214 | # takes a while and independent from most results so run separately 215 | 216 | print('Loading first levels') 217 | 218 | # Get first level filenames 219 | fnames = sorted(glob.glob(cfg['lemon_glm_data'] + '/sub*glm-data.hdf5')) 220 | 221 | r2_group = [] 222 | # Main loop - load first levels and store copes + meta data 223 | for idx, fname in enumerate(fnames): 224 | print('{0}/{1} - {2}'.format(idx, len(fnames), fname.split('/')[-1])) 225 | 226 | # Load data and design 227 | design = obj_from_hdf5file(fname, 'design') 228 | data = obj_from_hdf5file(fname, 'data') 229 | 230 | models = glm.fit.run_regressor_selection(design, data) 231 | 232 | r2 = np.concatenate([m.r_square[None, None, 0, :, :] * 100 for m in models], axis=1) 233 | r2_group.append(r2) 234 | 235 | r2_group = np.concatenate(r2_group, axis=0) 236 | 237 | outf = os.path.join(cfg['lemon_glm_data'], 'lemon_eeg_sensorglm_rsquared.npy') 238 | np.save(outf, r2_group) 239 | 240 | 241 | 242 | #%% ------------------------------------------------------- 243 | # Prepare first level files for group analysis 244 | 245 | print('Loading first levels') 246 | 247 | # Get first level filenames 248 | fnames = sorted(glob.glob(cfg['lemon_glm_data'] + '/sub*glm-data.hdf5')) 249 | 250 | # Load subject meta data 251 | fname = 'META_File_IDs_Age_Gender_Education_Drug_Smoke_SKID_LEMON.csv' 252 | meta_file = os.path.join(os.path.dirname(cfg['lemon_raw'].rstrip('/')), fname) 253 | df = pd.read_csv(meta_file) 254 | 255 | # Extract subject IDs 256 | allsubj = np.unique([fname.split('/')[-1].split('_')[0][4:] for fname in fnames]) 257 | allsubj_no = np.arange(len(allsubj)) 258 | 259 | # Preallocate lists 260 | subj_id = [] 261 | subj = [] 262 | age = [] 263 | sex = [] 264 | hand = [] 265 | task = [] 266 | scandur = [] 267 | num_blinks = [] 268 | 269 | first_level = [] 270 | first_level_reduced = [] 271 | r2 = [] 272 | r2_reduced = [] 273 | aic = [] 274 | aic_reduced = [] 275 | sw = [] 276 | sw_reduced = [] 277 | 278 | # Main loop - load first levels and store copes + meta data 279 | for idx, fname in enumerate(fnames): 280 | print('{0}/{1} - {2}'.format(idx, len(fnames), fname.split('/')[-1])) 281 | 282 | # Load data and design 283 | design = obj_from_hdf5file(fname, 'design') 284 | reduced_design = obj_from_hdf5file(fname, 'reduced_design') 285 | data = obj_from_hdf5file(fname, 'data') 286 | 287 | # Load reduced model 288 | reduced_model = obj_from_hdf5file(fname, 'reduced_model') 289 | reduced_model.design_matrix = reduced_design.design_matrix 290 | first_level_reduced.append(reduced_model.copes[None, :, :, :]) 291 | r2_reduced.append(reduced_model.r_square.mean()) 292 | sw_reduced.append(reduced_model.get_shapiro(data.data).mean()) 293 | aic_reduced.append(reduced_model.aic.mean()) 294 | 295 | # Load full model 296 | model = obj_from_hdf5file(fname, 'model') 297 | model.design_matrix = design.design_matrix 298 | first_level.append(model.copes[None, :, :, :]) 299 | r2.append(model.r_square.mean()) 300 | sw.append(model.get_shapiro(data.data).mean()) 301 | aic.append(model.aic.mean()) 302 | 303 | s_id = fname.split('/')[-1].split('_')[0][4:] 304 | subj.append(np.where(allsubj == s_id)[0][0]) 305 | subj_id.append(s_id) 306 | if fname.find('EO') > 0: 307 | task.append(1) 308 | elif fname.find('EC') > 0: 309 | task.append(2) 310 | 311 | demo_ind = np.where(df['ID'].str.match('sub-' + s_id))[0] 312 | if len(demo_ind) > 0: 313 | tmp_age = df.iloc[demo_ind[0]]['Age'] 314 | age.append(np.array(tmp_age.split('-')).astype(float).mean()) 315 | sex.append(df.iloc[demo_ind[0]]['Gender_ 1=female_2=male']) 316 | num_blinks.append(h5py.File(fname, 'r')['num_blinks'][()]) 317 | 318 | # Stack first levels into new glm dataset + save 319 | first_level = np.concatenate(first_level, axis=0) 320 | group_data = glm.data.TrialGLMData(data=first_level, subj_id=subj_id, shapiro=sw, 321 | subj=subj, task=task, age=age, num_blinks=num_blinks, 322 | sex=sex, scandur=scandur, aic=aic, r2=r2) 323 | 324 | outf = os.path.join(cfg['lemon_glm_data'], 'lemon_eeg_sensorglm_groupdata.hdf5') 325 | with h5py.File(outf, 'w') as F: 326 | group_data.to_hdf5(F.create_group('data')) 327 | F.create_dataset('aic', data=aic) 328 | F.create_dataset('r2', data=r2) 329 | 330 | first_level_reduced = np.concatenate(first_level_reduced, axis=0) 331 | group_data = glm.data.TrialGLMData(data=first_level_reduced, subj_id=subj_id, shapiro=sw_reduced, 332 | subj=subj, task=task, age=age, num_blinks=num_blinks, 333 | sex=sex, scandur=scandur, aic=aic_reduced, r2=r2_reduced) 334 | 335 | outf = os.path.join(cfg['lemon_glm_data'], 'lemon_eeg_sensorglm_groupdata_reduced.hdf5') 336 | with h5py.File(outf, 'w') as F: 337 | group_data.to_hdf5(F.create_group('data')) 338 | F.create_dataset('aic', data=aic_reduced) 339 | F.create_dataset('r2', data=r2_reduced) -------------------------------------------------------------------------------- /03_lemon_group.py: -------------------------------------------------------------------------------- 1 | """This script runs the group-level GLM-Spectra and non-parametric permutations. 2 | 3 | The script can be set to preprocess just the single example subject by changing 4 | 'subj' in the main body. 5 | 6 | If the group-level data collation is run, then all glm-spectrum datasets found 7 | in the output dir are collated. 8 | 9 | """ 10 | 11 | import os 12 | from copy import deepcopy 13 | 14 | import dill 15 | import glmtools as glm 16 | import h5py 17 | import mne 18 | import numpy as np 19 | import osl 20 | import pandas as pd 21 | import sails 22 | from anamnesis import obj_from_hdf5file 23 | 24 | from glm_config import cfg 25 | 26 | #%% --------------------------------------------------- 27 | # Load single subject for reference 28 | 29 | fbase = os.path.join(cfg['lemon_processed_data'], 'sub-010002_preproc_raw.fif') 30 | raw = mne.io.read_raw_fif(fbase).pick_types(eeg=True) 31 | 32 | st = osl.utils.Study(os.path.join(cfg['lemon_glm_data'], '{subj}_preproc_raw_glm-data.hdf5')) 33 | freq_vect = h5py.File(st.match_files[0], 'r')['freq_vect'][()] 34 | fl_model = obj_from_hdf5file(st.match_files[0], 'model') 35 | 36 | df = pd.read_csv(os.path.join(cfg['code_dir'], 'lemon_structural_vols.csv')) 37 | 38 | #%% -------------------------------------------------- 39 | # Load first level results and fit group model 40 | 41 | inputs = os.path.join(cfg['lemon_glm_data'], 'lemon_eeg_sensorglm_groupdata.hdf5') 42 | reduceds = os.path.join(cfg['lemon_glm_data'], 'lemon_eeg_sensorglm_groupdata_reduced.hdf5') 43 | 44 | data = obj_from_hdf5file(inputs, 'data') 45 | datareduced = obj_from_hdf5file(reduceds, 'data') 46 | 47 | # Drop dataset with enormous V-EOG effect - different order of magnitude to rest 48 | ind = np.argmax(data.data[:, 11, :, :].mean(axis=(1,2))) 49 | data = data.drop(ind) 50 | datareduced = datareduced.drop(ind) 51 | 52 | data.info['age_group'] = np.array(data.info['age']) < 45 53 | 54 | tbv = [] 55 | gmv = [] 56 | htv = [] 57 | for subj_id in data.info['subj_id']: 58 | row = df[df['PPT_ID'] == 'sub-' + subj_id] 59 | if len(row) > 0: 60 | tbv.append(row[' TotalBrainVol'].values[0]) 61 | gmv.append(row[' GreyMatterVolNorm'].values[0]) 62 | htv.append(row[' HippoTotalVolNorm'].values[0]) 63 | else: 64 | tbv.append(np.nan) 65 | gmv.append(np.nan) 66 | htv.append(np.nan) 67 | 68 | data.info['total_brain_vol'] = np.array(tbv) 69 | data.info['grey_matter_vol'] = np.array(gmv) 70 | data.info['hippo_vol'] = np.array(htv) 71 | datareduced.info['total_brain_vol'] = np.array(tbv) 72 | datareduced.info['grey_matter_vol'] = np.array(gmv) 73 | datareduced.info['hippo_vol'] = np.array(htv) 74 | 75 | # Drop obvious outliers & those with missing MR 76 | bads = sails.utils.detect_artefacts(data.data[:, 0, :, :], axis=0) 77 | bads = np.logical_or(bads, np.isnan(htv)) 78 | clean_data = data.drop(np.where(bads)[0]) 79 | clean_reduced = datareduced.drop(np.where(bads)[0]) 80 | 81 | DC = glm.design.DesignConfig() 82 | DC.add_regressor(name='Young', rtype='Categorical', datainfo='age_group', codes=1) 83 | DC.add_regressor(name='Old', rtype='Categorical', datainfo='age_group', codes=0) 84 | DC.add_regressor(name='Sex', rtype='Parametric', datainfo='sex', preproc='z') 85 | DC.add_regressor(name='TotalBrainVol', rtype='Parametric', datainfo='total_brain_vol', preproc='z') 86 | DC.add_regressor(name='GreyMatterVol', rtype='Parametric', datainfo='grey_matter_vol', preproc='z') 87 | 88 | young_prop = np.round(np.sum(np.array(data.info['age']) < 40) / len(data.info['age']), 3) 89 | old_prop = np.round(np.sum(np.array(data.info['age']) > 40) / len(data.info['age']), 3) 90 | 91 | DC.add_contrast(name='Mean', values={'Young': young_prop, 'Old': old_prop}) 92 | DC.add_contrast(name='Young>Old', values={'Young': 1, 'Old': -1}) 93 | DC.add_simple_contrasts() 94 | 95 | design = DC.design_from_datainfo(clean_data.info) 96 | gmodel = glm.fit.OLSModel(design, clean_data) 97 | 98 | gmodel_reduced = glm.fit.OLSModel(design, clean_reduced) 99 | 100 | with h5py.File(os.path.join(cfg['lemon_glm_data'], 'lemon-group_glm-data.hdf5'), 'w') as F: 101 | gmodel.to_hdf5(F.create_group('model')) 102 | gmodel_reduced.to_hdf5(F.create_group('model_reduced')) 103 | design.to_hdf5(F.create_group('design')) 104 | # hdf5 is messy sometimes - hopefully someone improves string type handling sometime 105 | clean_data.info['subj_id'] = np.array(clean_data.info['subj_id'], 106 | dtype=h5py.special_dtype(vlen=str)) 107 | clean_data.to_hdf5(F.create_group('data')) 108 | 109 | fout = os.path.join(cfg['lemon_figures'], 'lemon-group_glm-design.png') 110 | design.plot_summary(show=False, savepath=fout) 111 | fout = os.path.join(cfg['lemon_figures'], 'lemon-group_glm-efficiency.png') 112 | design.plot_efficiency(show=False, savepath=fout) 113 | 114 | fl_contrast_names = ['OverallMean', 'RestMean', 115 | 'Eyes Open AbsEffect', 'Eyes Closed AbsEffect', 'Open > Closed', 116 | 'Constant', 'Eyes Open', 'Eyes Closed', 117 | 'Linear Trend', 'Bad Segs', 'Bad Segs Diff', 'V-EOG', 'H-EOG'] 118 | 119 | #%% ------------------------------------------------------ 120 | # Permutation stats - run or load from disk 121 | 122 | adjacency, ch_names = mne.channels.channels._compute_ch_adjacency(raw.info, 'eeg') 123 | ntests = np.prod(data.data.shape[2:]) 124 | ntimes = data.data.shape[2] 125 | adjacency = mne.stats.cluster_level._setup_adjacency(adjacency, ntests, ntimes) 126 | 127 | cft = 3 128 | tstat_args = {'varcope_smoothing': 'medfilt', 129 | 'window_size': 5, 'smooth_dims': 1} 130 | #tstat_args = {} 131 | 132 | # Each line is (group cont, firstlevel cont, group regressor, firstlevel regressor, savename) 133 | to_permute = ( 134 | (0, 4, (0, 1), 4, 'Mean_OpenClosed'), 135 | (1, 0, (0, 1), (0, 1, 2), 'YoungOld_Mean'), 136 | (1, 4, (0, 1), 4, 'YoungOld_OpenClosed'), 137 | (0, 8, (0, 1), 3, 'Mean_Linear'), 138 | (0, 9, (0, 1), 4, 'Mean_BadSeg'), 139 | (0, 10, (0, 1), 5, 'Mean_DiffBadSeg'), 140 | (0, 11, (0, 1), 6, 'Mean_VEOG'), 141 | (0, 12, (0, 1), 7, 'Mean_HEOG'), 142 | (4, 0, 2, (0, 1, 2), 'Sex_Mean'), 143 | (5, 0, 3, (0, 1, 2), 'BrainVol_Mean'), 144 | (6, 0, 4, (0, 1, 2), 'GreyVol_Mean'), 145 | ) 146 | 147 | for icon in range(len(to_permute)): 148 | gl_con, fl_con, gl_reg, fl_reg, p_name = to_permute[icon] 149 | print('### Contrast - {}'.format(p_name)) 150 | print('permuting') 151 | # Only working with mean regressor for the moment 152 | fl_mean_data = deepcopy(clean_data) 153 | fl_mean_data.data = clean_data.data[:, fl_con, :, :] 154 | 155 | if icon > 0 and gl_con == 0: 156 | cft = 5 157 | else: 158 | cft = 3 159 | 160 | p = glm.permutations.MNEClusterPermutation(design, fl_mean_data, gl_con, 1500, 161 | nprocesses=24, 162 | metric='tstats', 163 | cluster_forming_threshold=cft, 164 | tstat_args=tstat_args, 165 | adjacency=adjacency) 166 | 167 | dill_fname = os.path.join(cfg['lemon_glm_data'], 'lemon-group_smo-5_perms-{0}.pkl'.format(p_name)) 168 | with open(dill_fname, "wb") as dill_file: 169 | dill.dump(p, dill_file) 170 | -------------------------------------------------------------------------------- /10_plot_example_confound_design.py: -------------------------------------------------------------------------------- 1 | """This script creates figure 1.""" 2 | 3 | import os 4 | 5 | import glmtools as glm 6 | import matplotlib.pyplot as plt 7 | import numpy as np 8 | from scipy.stats import gaussian_kde 9 | 10 | import lemon_plotting 11 | from glm_config import cfg 12 | 13 | outdir = cfg['lemon_figures'] 14 | 15 | #%% ------------------------------------------------ 16 | # Data scatter example 17 | 18 | const = np.ones((128,)) 19 | bads = np.zeros((128,)) 20 | bads[35:40] = 1 21 | bads[99:108] = 1 22 | 23 | Y = np.random.randn(128,) + 1 24 | Y[bads.astype(bool)] += 4 25 | data = glm.data.TrialGLMData(data=Y) 26 | 27 | X1 = np.vstack((const,)).T 28 | regressor_names1 = ['Simple Mean'] 29 | C1 = np.eye(1) 30 | contrast_names1 = ['Simple Mean'] 31 | 32 | design1 = glm.design.GLMDesign.initialise_from_matrices(X1, C1, 33 | regressor_names=regressor_names1, 34 | contrast_names=contrast_names1) 35 | 36 | X2 = np.vstack((const, bads)).T 37 | regressor_names2 = ['Intercept', 'Artefact'] 38 | C2 = np.eye(2) 39 | C2 = np.array([[1, 0], [0, 1], [1, 1]]) 40 | contrast_names2 = ['Intercept', 'Artefact Effect', 'Artefact Mean'] 41 | 42 | design2 = glm.design.GLMDesign.initialise_from_matrices(X2, C2, 43 | regressor_names=regressor_names2, 44 | contrast_names=contrast_names2) 45 | model = glm.fit.OLSModel(design2, data) 46 | 47 | 48 | fig = plt.figure(figsize=(16, 9)) 49 | 50 | # Plot first design 51 | ax = plt.subplot(1, 4, 1) 52 | glm.viz.plot_design_summary(X1, regressor_names1, 53 | contrasts=C1, contrast_names=contrast_names1, 54 | ax=ax) 55 | pos1 = ax.get_position() # get the original position 56 | pos2 = [pos1.x0 - 0.035, pos1.y0+0.07, pos1.width, pos1.height*0.92] 57 | ax.set_position(pos2) # set a new position 58 | lemon_plotting.subpanel_label(ax, 'A') 59 | 60 | # Plot second design 61 | ax = plt.subplot(1, 4, 2) 62 | glm.viz.plot_design_summary(X2, regressor_names2, 63 | contrasts=C2, contrast_names=contrast_names2, 64 | ax=ax) 65 | lemon_plotting.subpanel_label(ax, 'B') 66 | 67 | ax = plt.axes((0.62, 0.2, 0.35, 0.6)) 68 | # Calculate the point density 69 | xy = np.vstack([bads, Y]) 70 | z = gaussian_kde(xy)(xy) 71 | 72 | # Plot line for simple mean term 73 | plt.scatter(bads, Y, c=z, s=100) 74 | plt.plot((-0.1, 1.1), (Y.mean(), Y.mean()), 'k--') 75 | plt.text(0.75, 1.55, 'Simple Mean') 76 | 77 | # Plot line for intercept 78 | plt.plot((-0.1, 0.1), (model.betas[0, 0], model.betas[0, 0]), 'k--') 79 | plt.text(0.055, 0.25, 'Intercept\n(Conditioned Mean)') 80 | 81 | # Plot line for artefact mean term 82 | plt.plot((0.9, 1.1), (model.copes[2, 0], model.copes[2, 0]), 'k--') 83 | plt.text(1.055, 5, 'Artefact Mean') 84 | 85 | # Plot line for regressor effect 86 | plt.plot(np.linspace(0, 1), model.betas[0, 0] + np.linspace(0, 1) * model.betas[1, 0]) 87 | plt.text(0.35, 3, 'Artefact\nEffect') 88 | plt.xlabel('Artefact Regressor Value') 89 | plt.ylabel('Observed Data') 90 | for tag in ['top', 'right']: 91 | plt.gca().spines[tag].set_visible(False) 92 | 93 | # colourbar to indicate point density 94 | plt.colorbar(label='Point Density', shrink=0.5) 95 | lemon_plotting.subpanel_label(ax, 'C') 96 | 97 | fig.axes[1].set_visible(False) 98 | fig.axes[3].set_position([0.51, 0.4, 0.01, 0.35]) # set a new position 99 | 100 | fout = os.path.join(cfg['lemon_figures'], 'glm-spectrum_example-designs-confound.png') 101 | plt.savefig(fout, dpi=300, transparent=True) 102 | fout = os.path.join(cfg['lemon_figures'], 'glm-spectrum_example-designs-confound_low-res.png') 103 | plt.savefig(fout, dpi=100, transparent=True) -------------------------------------------------------------------------------- /11_lemon_plot_single_subject.py: -------------------------------------------------------------------------------- 1 | """This script creates figures 1.""" 2 | 3 | import os 4 | from copy import deepcopy 5 | 6 | import dill 7 | import glmtools as glm 8 | import matplotlib.pyplot as plt 9 | import mne 10 | import numpy as np 11 | import osl 12 | import sails 13 | from anamnesis import obj_from_hdf5file 14 | 15 | import lemon_plotting 16 | from glm_config import cfg 17 | from lemon_support import (get_eeg_data, plot_design, lemon_make_task_regressor, 18 | lemon_make_bads_regressor, lemon_set_standard_montage) 19 | 20 | outdir = cfg['lemon_figures'] 21 | subj_id = 'sub-010060' 22 | 23 | #%% -------------------------------------------------- 24 | # Load dataset 25 | 26 | fbase = os.path.join(cfg['lemon_processed_data'], 'sub-010002', 'sub-010002_preproc_raw.fif') 27 | reference = mne.io.read_raw_fif(fbase).pick_types(eeg=True) 28 | 29 | fbase = os.path.join(cfg['lemon_processed_data'], '{subj}/{subj}_preproc_raw.fif') 30 | st_raw = osl.utils.Study(fbase) 31 | 32 | raw = mne.io.read_raw_fif(st_raw.get(subj=subj_id)[0], preload=True) 33 | raw.crop(tmin=29) 34 | 35 | sensor = 'Pz' 36 | ch_ind = raw.info['ch_names'].index(sensor) 37 | 38 | 39 | # ------------------- 40 | veog = np.abs(raw.get_data(picks='VEOG')[0, :])#**2 41 | heog = np.abs(raw.get_data(picks='HEOG')[0, :])#**2 42 | # Make task regressor 43 | task = lemon_make_task_regressor({'raw': raw}) 44 | # Make bad-segments regressor 45 | #bads_raw = lemon_make_bads_regressor(raw, mode='raw') 46 | #bads_diff = lemon_make_bads_regressor(raw, mode='diff') 47 | #bads = np.logical_or(bads_raw, bads_diff) 48 | bads = lemon_make_bads_regressor(raw) 49 | 50 | 51 | 52 | conds = {'Eyes Open': task > 0, 'Eyes Closed': task < 0} 53 | covs = {'Linear Trend': np.linspace(0, 1, raw.n_times)} 54 | confs = {'Bad Segs': bads, 55 | 'V-EOG': veog, 'H-EOG': heog} 56 | eo_val = np.round(np.sum(task == 1) / len(task), 3) 57 | ec_val = np.round(np.sum(task == -1) / len(task), 3) 58 | conts = [{'name': 'RestMean', 'values': {'Eyes Open': 0.5, 'Eyes Closed': 0.5}}, 59 | {'name': 'Open>Closed', 'values': {'Eyes Open': 1, 'Eyes Closed': -1}}] 60 | 61 | fs = raw.info['sfreq'] 62 | 63 | # Reduced model - no confounds or covariates 64 | XX = get_eeg_data(raw) 65 | glmsp = osl.glm.glm_spectrum(XX, fmin=1, fmax=95, 66 | fs=fs, 67 | fit_intercept=False, 68 | nperseg=int(fs * 2), 69 | mode='magnitude', 70 | contrasts=conts, 71 | reg_categorical=conds, 72 | reg_ztrans=covs, reg_unitmax=confs, 73 | standardise_data=True) 74 | glmsp = osl.glm.SensorGLMSpectrum(glmsp, reference.info) # Store with standard channel info 75 | glmsp = lemon_set_standard_montage(glmsp) 76 | # Load GLM Results 77 | #st = osl.utils.Study(os.path.join(cfg['lemon_glm_data'],'{subj_id}_preproc_raw-glm-spectrum_model-{model}_data.pkl')) 78 | #fname = st.get(subj=subj_id)[0] 79 | 80 | #glmsp = osl.glm.read_glm_spectrum(fname) 81 | 82 | # This shouldn't be necessary... 83 | #model.design_matrix = design.design_matrix 84 | #model.regressor_list = design.regressor_list 85 | 86 | fout = os.path.join(outdir, '{subj_id}_firstlevel-design.png'.format(subj_id=subj_id)) 87 | fig = glmsp.design.plot_summary(figargs={'figsize': (16, 12)}, show=False) 88 | fig.savefig(fout, dpi=100, transparent=True) 89 | 90 | fout = os.path.join(outdir, '{subj_id}_firstlevel-efficiency.png'.format(subj_id=subj_id)) 91 | fig = glmsp.design.plot_efficiency() 92 | fig.savefig(fout, dpi=100, transparent=True) 93 | 94 | #%% --------------------------------------------------------- 95 | # Single channel example figure 1 96 | 97 | sensor = 'Pz' 98 | fs = glmsp.info['sfreq'] 99 | 100 | ch_ind = glmsp.info['ch_names'].index(sensor) 101 | YY = XX[ch_ind, :] 102 | 103 | # Extract data segment 104 | inds = np.arange(140*fs, 160*fs) 105 | inds = np.arange(770*fs, 790*fs).astype(int) 106 | inds = np.arange(520*fs, 550*fs).astype(int) - int(28*fs) 107 | 108 | #eog = dataset['raw'].copy().pick_types(eog=True, eeg=False) 109 | #eog.filter(l_freq=1, h_freq=25, picks='eog') 110 | #eog = eog.get_data(picks='eog')[:, inds].T 111 | time = np.linspace(0, YY.shape[0]/fs, YY.shape[0]) 112 | 113 | config = deepcopy(glmsp.config) 114 | config_flat = deepcopy(glmsp.config) 115 | config_flat.window = None 116 | config_flat.detrend_func = None 117 | 118 | 119 | # Apply sliding window to subset indices to get corresponding stft windows 120 | subset = np.zeros((YY.shape[0],)) 121 | subset[inds] = 1 122 | xsubset = sails.stft.apply_sliding_window(subset, **config_flat.sliding_window_args) 123 | stft_inds = np.where(xsubset.sum(axis=1) > 200)[0] 124 | 125 | stft = glmsp.data.data[stft_inds, ch_ind, :] 126 | stft_time = np.arange(config.nperseg/2, YY.shape[0] - config.nperseg/2 + 1, 127 | config.nperseg - config.noverlap) / float(fs) 128 | stft_time = stft_time[stft_inds] - stft_time[stft_inds[0]] + 1 129 | 130 | stft_dm = glmsp.design.design_matrix[stft_inds, :] 131 | 132 | # Exaggerate linear trend to help visualisation 133 | stft_dm[:, 2] = np.linspace(-1, 1, len(stft_inds)) 134 | 135 | f = glmsp.f #sails.stft._set_freqvalues(config.nfft, config.fs, 'onesided') 136 | #fidx = (f >= config.fmin) & (f <= config.fmax) 137 | #f = f[fidx] 138 | 139 | inds = np.arange(stft_inds[0]*250 - 125, stft_inds[-1]*250 + 250) 140 | 141 | # prep sqrt(f) axes 142 | fx, ftl, ft = lemon_plotting.prep_scaled_freq(0.5, f) 143 | 144 | #%% ------------------------------------------------------------ 145 | # Make figure 2 146 | 147 | wlagX = sails.stft.apply_sliding_window(YY, **config.sliding_window_args)[stft_inds, :] 148 | lagX = sails.stft.apply_sliding_window(YY, **config_flat.sliding_window_args)[stft_inds, :] 149 | 150 | panel_label_height = 1.075 151 | plt.figure(figsize=(16, 9)) 152 | 153 | ax_ts = plt.axes([0.05, 0.1, 0.4, 0.8]) 154 | ax_tf = plt.axes([0.45, 0.1, 0.16, 0.8]) 155 | ax_tf_cb = plt.axes([0.47, 0.065, 0.12, 0.01]) 156 | ax_des = plt.axes([0.7, 0.1, 0.25, 0.8]) 157 | ax_des_cb = plt.axes([0.96, 0.15, 0.01, 0.2]) 158 | 159 | # Plot continuous time series 160 | scale = 1 / (np.std(YY) * 4) 161 | ax_ts.plot(scale*YY[inds], time[inds] - time[inds[0]], 'k', lw=0.5) 162 | 163 | # Plot window markers + guidelines 164 | for ii in range(stft.shape[0]): 165 | jit = np.remainder(ii, 3) / 5 166 | ax_ts.plot((2+jit, 2+jit), (ii, ii+2), lw=4, solid_capstyle="butt") 167 | ax_ts.plot((0, 14), (ii+1, ii+1), lw=0.5, color=[0.8, 0.8, 0.8]) 168 | 169 | ax_ts.set_prop_cycle(None) 170 | x = np.linspace(4, 8, 500) 171 | ax_ts.plot(x, scale*lagX.T + np.arange(stft.shape[0])[None, :] + 1, lw=0.8) 172 | 173 | ax_ts.plot(x, np.zeros_like(x), 'k') 174 | ax_ts.text(x[0], -0.1, '2 Seconds', ha='left', va='top') 175 | 176 | ax_ts.set_prop_cycle(None) 177 | x = np.linspace(9, 13, 500) 178 | ax_ts.plot(x, scale*wlagX.T + np.arange(stft.shape[0])[None, :] + 1, lw=0.8) 179 | ax_ts.set_ylim(0, stft.shape[0]+1) 180 | ax_ts.set_xlim(-2, 14) 181 | 182 | ax_ts.plot(x, np.zeros_like(x), 'k') 183 | ax_ts.text(x[0], -0.1, '2 Seconds', ha='left', va='top') 184 | 185 | for tag in ['top', 'right', 'bottom']: 186 | ax_ts.spines[tag].set_visible(False) 187 | ax_ts.set_xticks([]) 188 | ax_ts.set_yticks(np.linspace(0, stft.shape[0]+1, 9)) 189 | ax_ts.set_ylabel('Time (seconds)') 190 | 191 | lemon_plotting.subpanel_label(ax_ts, 'A', xf=-0.02, yf=panel_label_height) 192 | ax_ts.text(0.1, panel_label_height, '\nRaw EEG\nChannel: {}'.format(sensor), 193 | ha='center', transform=ax_ts.transAxes, fontsize='large') 194 | lemon_plotting.subpanel_label(ax_ts, 'B', xf=0.25, yf=panel_label_height) 195 | ax_ts.text(0.4, panel_label_height, 'Segmented EEG', 196 | ha='center', transform=ax_ts.transAxes, fontsize='large') 197 | lemon_plotting.subpanel_label(ax_ts, 'C', xf=0.7, yf=panel_label_height) 198 | ax_ts.text(0.825, panel_label_height, "'Windowed' EEG", 199 | ha='center', transform=ax_ts.transAxes, fontsize='large') 200 | 201 | pcm = ax_tf.pcolormesh(fx, stft_time, stft, cmap='magma_r') 202 | ax_tf.set_xticks(ft) 203 | ax_tf.set_xticklabels(ftl) 204 | plt.colorbar(pcm, cax=ax_tf_cb, orientation='horizontal') 205 | ax_tf_cb.set_title('Magnitude') 206 | for tag in ['bottom', 'right']: 207 | ax_tf.spines[tag].set_visible(False) 208 | ax_tf.xaxis.tick_top() 209 | ax_tf.set_xlabel('Frequency (Hz)') 210 | ax_tf.xaxis.set_label_position('top') 211 | ax_tf.set_yticks(np.linspace(0, stft.shape[0]+1, 7)) 212 | ax_tf.set_yticklabels([]) 213 | ax_tf.text(0.475, panel_label_height, 'Short Time Fourier Tranform', 214 | va='center', ha='center', transform=ax_tf.transAxes, fontsize='large') 215 | lemon_plotting.subpanel_label(ax_tf, 'D', xf=-0.05, yf=panel_label_height) 216 | 217 | pcm = plot_design(ax_des, stft_dm, glmsp.design.regressor_names) 218 | for ii in range(len(glmsp.design.regressor_names)): 219 | ax_des.text(0.5+ii, stft.shape[0], glmsp.design.regressor_names[ii], 220 | ha='left', va='bottom', rotation=20) 221 | ax_des.set_yticks(np.linspace(0, stft.shape[0]+1, 9)-0.5, 222 | np.linspace(0, stft.shape[0]+1, 9).astype(int)) 223 | ax_des.text(0.25, panel_label_height, 'GLM Design Matrix', 224 | ha='center', transform=ax_des.transAxes, fontsize='large') 225 | plt.colorbar(pcm, cax=ax_des_cb) 226 | lemon_plotting.subpanel_label(ax_des, 'E', xf=-0.02, yf=panel_label_height) 227 | 228 | 229 | fout = os.path.join(outdir, '{subj_id}_single-channel_glm-top.png'.format(subj_id=subj_id)) 230 | plt.savefig(fout, dpi=300, transparent=True) 231 | fout = os.path.join(outdir, '{subj_id}_single-channel_glm-top_low-res.png'.format(subj_id=subj_id)) 232 | plt.savefig(fout, dpi=100, transparent=True) 233 | 234 | #%% --------------------------------------------- 235 | # Bottom of figure 1 236 | 237 | tstat_args = {'varcope_smoothing': 'medfilt', 'window_size': 15, 'smooth_dims': 1} 238 | tstat_args_for_plotting = {'varcope_smoothing': 'medfilt', 'window_size': 15, 'smooth_dims': 0} 239 | tstat_args = {} 240 | tstat_args_for_plotting = {} 241 | 242 | con_ind = [1, 7] 243 | 244 | fig = plt.figure(figsize=(16, 6)) 245 | ax = plt.axes([0.075, 0.2, 0.25, 0.6]) 246 | ax.plot(fx, glmsp.model.copes[2, ch_ind, :], label='Eyes Open') 247 | ax.plot(fx, glmsp.model.copes[3, ch_ind, :], label='Eyes Closed') 248 | ax.set_xticks(ft) 249 | ax.set_xticklabels(ftl) 250 | ax.set_ylabel('Magnitude') 251 | ax.set_xlabel('Frequency (Hz)') 252 | ax.set_ylim(0) 253 | lemon_plotting.subpanel_label(ax, 'F', xf=-0.02, yf=1.1) 254 | plt.legend(frameon=False) 255 | ax.text(0.5, 1.1, 'GLM cope-spectrum', ha='center', transform=ax.transAxes, fontsize='large') 256 | for tag in ['top', 'right']: 257 | plt.gca().spines[tag].set_visible(False) 258 | 259 | for ii in range(len(con_ind)): 260 | if ii > 0: 261 | continue 262 | plt.axes([0.4+ii*0.3, 0.55, 0.15, 0.25]) 263 | plt.plot(fx, glmsp.model.copes[con_ind[ii], ch_ind, :]) 264 | lemon_plotting.subpanel_label(plt.gca(), chr(71+ii), xf=-0.02, yf=1.3) 265 | plt.xticks(ft[::2], ftl[::2]) 266 | for tag in ['top', 'right']: 267 | plt.gca().spines[tag].set_visible(False) 268 | plt.title('cope-spectrum\n') 269 | plt.axes([0.4+ii*0.3, 0.15, 0.15, 0.25]) 270 | plt.plot(fx, glmsp.model.varcopes[con_ind[ii], ch_ind, :]) 271 | plt.xticks(ft[::2], ftl[::2]) 272 | for tag in ['top', 'right']: 273 | plt.gca().spines[tag].set_visible(False) 274 | plt.title('varcope-spectrum\n') 275 | plt.xlabel('Frequency (Hz)') 276 | 277 | #ax = plt.axes([0.4+ii*0.3, 0.1, 0.25, 0.5]) 278 | ax = plt.axes([0.65, 0.2, 0.25, 0.6]) 279 | 280 | ts = glm.fit.get_tstats(glmsp.model.copes[con_ind[ii], ch_ind, :], 281 | glmsp.model.varcopes[con_ind[ii], ch_ind, :], 282 | **tstat_args_for_plotting) 283 | ax.plot(fx, ts) 284 | lemon_plotting.subpanel_label(ax, chr(72+ii), xf=-0.02, yf=1.1) 285 | name = glmsp.model.contrast_names[con_ind[ii]] 286 | ax.text(0.5, 1.7, f'Contrast : {name}', ha='center', transform=ax.transAxes, fontsize='large') 287 | ax.set_xticks(ft) 288 | ax.set_xticklabels(ftl) 289 | for tag in ['top', 'right']: 290 | plt.gca().spines[tag].set_visible(False) 291 | plt.title('t-spectrum') 292 | plt.xlabel('Frequency (Hz)') 293 | plt.ylabel('t statistic') 294 | 295 | fout = os.path.join(outdir, '{subj_id}_no-smo_single-channel_glm-bottom.png'.format(subj_id=subj_id)) 296 | plt.savefig(fout, dpi=300, transparent=True) 297 | fout = os.path.join(outdir, '{subj_id}_no-smo_single-channel_glm-bottom_low-res.png'.format(subj_id=subj_id)) 298 | plt.savefig(fout, dpi=100, transparent=True) 299 | 300 | #%% -------------------------------------------------------- 301 | # Whole head GLM single subject figure 302 | 303 | 304 | 305 | #tstat_args = {'varcope_smoothing': 'medfilt', 'window_size': 15, 'smooth_dims': 1} 306 | tstat_args = {} 307 | #ts = model.get_tstats(**tstat_args) 308 | 309 | 310 | ll = [['Rec Start', 'Rec End'], 311 | ['Good Seg', 'Bad Seg'], 312 | ['Low V-EOG Activity', 'High V-EOG Activity'], 313 | ['Low H-EOG Activity', 'High H-EOG Activity']] 314 | 315 | col_heads = ['Mean', 'Linear Trend', 'Rest Condition', 'Bad Segments', 'VEOG', 'HEOG'] 316 | 317 | plt.figure(figsize=(16, 16)) 318 | ax = plt.axes([0.075, 0.6, 0.175, 0.3]) 319 | glmsp.plot_joint_spectrum(2, ax=ax, base=0.5, ylabel='Magnitude', freqs=(9, 22)) 320 | lemon_plotting.subpanel_label(ax, chr(65), yf=1.1) 321 | 322 | ax = plt.axes([0.3125, 0.6, 0.175, 0.3]) 323 | glmsp.plot_joint_spectrum(3, ax=ax, base=0.5, ylabel='Magnitude', freqs=(9, 22)) 324 | lemon_plotting.subpanel_label(ax, chr(66), yf=1.1) 325 | 326 | # Plot Open > Closd 327 | ax = plt.axes([0.55, 0.6, 0.175, 0.3]) 328 | glmsp.plot_joint_spectrum(1, ax=ax, base=0.5, ylabel='t-values', metric='tstats', freqs=(9, 22)) 329 | lemon_plotting.subpanel_label(ax, chr(67), yf=1.1) 330 | 331 | ax = plt.axes([0.775, 0.6, 0.2, 0.2]) 332 | lemon_plotting.plot_channel_layout2(ax, glmsp.info, size=100) 333 | 334 | # Covariate freqs of interest 335 | fois = [[9, 23], # linear 336 | [2, 64], # bad segs 337 | [2, 9, ], # VEOG 338 | [9, 64], # HEOG 339 | ] 340 | 341 | # Plot covariates 342 | for ii in range(4): 343 | ax = plt.axes([0.07+ii*0.24, 0.25, 0.15, 0.25]) 344 | 345 | #lemon_plotting.plot_joint_spectrum(ax, glmsp.model.tstats[ii+8, :, :], rawref, xvect=f, 346 | # freqs=fois[ii], base=0.5, topo_scale=None, 347 | # ylabel='pseudo t-statistic', title=model.contrast_names[ii+8]) 348 | glmsp.plot_joint_spectrum(ii+4, ax=ax, base=0.5, ylabel='t-values', metric='tstats', freqs=fois[ii]) 349 | lemon_plotting.subpanel_label(ax, chr(68+ii), yf=1.1) 350 | 351 | ax2 = plt.axes([0.07+ii*0.24, 0.07, 0.15, 0.2*2/3]) 352 | #ax2.set_ylim(0, 1.5e-5) 353 | proj, llabels = glmsp.model.project_range(ii+2, nsteps=2) 354 | ax2.plot(fx, proj.mean(axis=1).T, lw=2) 355 | ax2.set_xticks(ft) 356 | ax2.set_xticklabels(ftl) 357 | ylabel = 'Magnitude' if ii == 0 else '' 358 | lemon_plotting.decorate_spectrum(ax2, ylabel=ylabel) 359 | ax2.legend(ll[ii], frameon=False, fontsize=8) 360 | 361 | fout = os.path.join(outdir, '{subj_id}_no-smo_whole-head-glm-summary.png'.format(subj_id=subj_id)) 362 | plt.savefig(fout, dpi=300, transparent=True) 363 | fout = os.path.join(outdir, '{subj_id}_no-smo_whole-head-glm-summary_low-res.png'.format(subj_id=subj_id)) 364 | plt.savefig(fout, dpi=100, transparent=True) 365 | -------------------------------------------------------------------------------- /12_plot_group_glms.py: -------------------------------------------------------------------------------- 1 | """This script creates the group level figures.""" 2 | 3 | import os 4 | from copy import deepcopy 5 | 6 | import dill 7 | import glmtools as glm 8 | import h5py 9 | import matplotlib as mpl 10 | import matplotlib.pyplot as plt 11 | import mne 12 | import numpy as np 13 | import osl 14 | from anamnesis import obj_from_hdf5file 15 | from matplotlib.lines import Line2D 16 | from lemon_support import lemon_set_standard_montage 17 | 18 | import lemon_plotting 19 | from glm_config import cfg 20 | 21 | #%% --------------------------------------------------- 22 | # Load single subject for reference 23 | 24 | perm_name = os.path.join(cfg['lemon_glm_data'], 'lemon-group_glm-spec_model-full_perm-{pname}.pkl') 25 | perms = osl.utils.Study(perm_name) 26 | 27 | fl_path = os.path.join(cfg['lemon_glm_data'],'{subj_id}_preproc_raw-glm-spectrum_model-{model}_data.pkl') 28 | st = osl.utils.Study(fl_path) 29 | glmsp = osl.glm.read_glm_spectrum(st.get(model='full')[0]) 30 | 31 | gl_name = os.path.join(cfg['lemon_glm_data'], 'lemon-group_glm-spec_model-{model}.pkl') 32 | glglms = osl.utils.Study(gl_name) 33 | fglmsp = osl.glm.read_glm_spectrum(glglms.get(model='full_data')[0]) 34 | rglmsp = osl.glm.read_glm_spectrum(glglms.get(model='reduced_data')[0]) 35 | 36 | rglmsp = lemon_set_standard_montage(rglmsp) 37 | fglmsp = lemon_set_standard_montage(fglmsp) 38 | 39 | 40 | #%% ------------------------------------------------------ 41 | 42 | flcf = np.load(os.path.join(cfg['lemon_glm_data'], 'lemon-group_glm-spec_fl-cohens-f2.npy')) 43 | flr2 = np.load(os.path.join(cfg['lemon_glm_data'], 'lemon-group_glm-spec_fl-r-square.npy')) 44 | glcf = np.load(os.path.join(cfg['lemon_glm_data'], 'lemon-group_glm-spec_gl-cohens-f2.npy')) 45 | glr2 = np.load(os.path.join(cfg['lemon_glm_data'], 'lemon-group_glm-spec_gl-r-square.npy')) 46 | 47 | fx, ftl, ft = lemon_plotting.prep_scaled_freq(0.5, fglmsp.f) 48 | 49 | lc = [0.8, 0.8, 0.8] 50 | 51 | plt.figure(figsize=(12, 9)) 52 | # First levels 53 | ax = plt.axes([0.075, 0.6, 0.2, 0.35]) 54 | ax.plot(fx, flr2[:, 0, :, :].mean(axis=1).T, lw=0.5, color=lc) 55 | ax.plot(fx, flr2[:, 0, :, :].mean(axis=(0, 1)), lw=2, color='k') 56 | ax.set_xticks(ft) 57 | ax.set_xticklabels(ftl) 58 | lemon_plotting.decorate_spectrum(ax, ylabel="R-Square") 59 | ax.set_title('Full Model', y=0.95) 60 | lines = [Line2D([0], [0], color=lc, linewidth=0.5), Line2D([0], [0], color='k', linewidth=2)] 61 | plt.legend(lines, ['Single Dataset', 'Group Average']) 62 | 63 | ax = plt.axes([0.35, 0.6, 0.2, 0.35]) 64 | ax.plot(fx, flcf[:, 1, :, :].mean(axis=1).T, lw=0.5, color=lc) 65 | ax.plot(fx, flcf[:, 1, :, :].mean(axis=(0, 1)), lw=2, color='k') 66 | ax.set_xticks(ft) 67 | ax.set_xticklabels(ftl) 68 | lemon_plotting.decorate_spectrum(ax, ylabel="Cohen's F2") 69 | ax.set_title('Eyes Open > Eyes Closed', y=0.95) 70 | 71 | axs = [plt.axes([0.6, 0.8, 0.18, 0.18]), plt.axes([0.8, 0.8, 0.18, 0.18]), 72 | plt.axes([0.6, 0.55, 0.18, 0.18]), plt.axes([0.8, 0.55, 0.18, 0.18])] 73 | 74 | for idx, ax in enumerate(axs): 75 | ax.plot(fx, flcf[:, idx+2, :, :].mean(axis=1).T, lw=0.5, color=lc) 76 | ax.plot(fx, flcf[:, idx+2, :, :].mean(axis=(0, 1)), lw=2, color='k') 77 | ax.set_xticks(ft) 78 | ax.set_xticklabels(ftl) 79 | ax.set_title(glmsp.model.regressor_names[idx+2], y=0.85) 80 | ax.set_ylim(0, 0.5) 81 | lemon_plotting.decorate_spectrum(ax, ylabel="Cohens F2") 82 | if idx < 2: 83 | ax.set_xlabel('') 84 | if idx == 1 or idx == 3: 85 | ax.set_ylabel('') 86 | 87 | ff = [(5, 17), (6.5, 17, 45), (8, 16), (5, 10)] 88 | for ii in range(4): 89 | ax = plt.axes([0.05+0.24*ii, 0.1, 0.175, 0.35]) 90 | yl = "Cohen's F2" if ii == 0 else "" 91 | title = "Age" if ii == 0 else fglmsp.model.regressor_names[ii+1] 92 | osl.glm.plot_joint_spectrum(fglmsp.f, glcf[ii+1, 0, :, :].T, glmsp.info, ax=ax, base=0.5, 93 | ylabel=yl, title=title, freqs=ff[ii]) 94 | ax.set_ylim(0, 0.5) 95 | 96 | fout = os.path.join(cfg['lemon_figures'], 'lemon-group_model-stat-summary.png') 97 | plt.savefig(fout, transparent=True, dpi=300) 98 | fout = os.path.join(cfg['lemon_figures'], 'lemon-group_model=stat-summary_low-res.png') 99 | plt.savefig(fout, transparent=True, dpi=100) 100 | 101 | #%% ------------------------------------------------------ 102 | # Group-level experimental difference between null and full models 103 | 104 | diff = fglmsp.model.tstats[0, 1, :, :] - rglmsp.model.tstats[0, 1, :, :] 105 | freqs = (2, 8, 23) 106 | plt.figure(figsize=(12, 5)) 107 | plt.subplots_adjust(left=0.05, right=0.95, hspace=0.3) 108 | ax = plt.subplot(131) 109 | t = 'Eyes Open > Eyes Closed\nNo Covariates' 110 | rglmsp.plot_joint_spectrum(0, 1, ax=ax, base=0.5, ylabel='t-stats', metric='tstats', title=t, freqs=freqs) 111 | lemon_plotting.subpanel_label(ax, chr(65)) 112 | 113 | ax = plt.subplot(132) 114 | t = 'Eyes Open > Eyes Closed\nWith Covariates' 115 | fglmsp.plot_joint_spectrum(0, 1, ax=ax, base=0.5, ylabel='t-stats', metric='tstats', title=t, freqs=freqs) 116 | lemon_plotting.subpanel_label(ax, chr(66)) 117 | 118 | ax = plt.subplot(133) 119 | osl.glm.plot_joint_spectrum(fglmsp.f, diff.T, fglmsp.info, ax=ax, base=0.5, ylabel='t-stat difference', title='Difference', freqs=freqs) 120 | lemon_plotting.subpanel_label(ax, chr(67)) 121 | 122 | fout = os.path.join(cfg['lemon_figures'], 'lemon-group_confound-openclosed.png') 123 | plt.savefig(fout, transparent=True, dpi=300) 124 | fout = os.path.join(cfg['lemon_figures'], 'lemon-group_confound-openclosed_low-res.png') 125 | plt.savefig(fout, transparent=True, dpi=100) 126 | 127 | 128 | #%% ------------------------------------------------------ 129 | # Effect of covariates across first-levels 130 | 131 | fx, ftl, ft = lemon_plotting.prep_scaled_freq(0.5, fglmsp.f) 132 | 133 | lc = [0.8, 0.8, 0.8] 134 | ppts = [43, 145, 68, 62, 112] 135 | ppts = ['sub-010060', 'sub-010255', 'sub-010086', 'sub-010079', 'sub-010213'] 136 | fnames = sorted(st.get()) 137 | ppts2 = [] 138 | for idx in range(len(ppts)): 139 | tmp = np.where([fn.find(ppts[idx]) > -1 for fn in fnames]) 140 | ppts2.append(tmp[0][0]) 141 | ppts2[1] -= 1 142 | ppts2[2] -= 2 143 | ppts2[3] += 2 144 | ppts2[4] -= 3 145 | ppts = ppts2 146 | 147 | labs = ['i', 'ii', 'iii', 'iv', 'v', 'vi', 'vii'] 148 | fontargs = {'fontsize': 'large', 'fontweight': 'bold', 'color': 'r', 'ha': 'center', 'va': 'center'} 149 | 150 | plt.figure(figsize=(16, 9)) 151 | 152 | ax = plt.axes([0.075, 0.55, 0.2, 0.35]) 153 | ax.plot(fx, flr2[:, 0, :, :].mean(axis=1).T, lw=0.5, color=lc) 154 | ax.plot(fx, flr2[:, 0, :, :].mean(axis=(0, 1)), lw=2, color='k') 155 | ax.set_xticks(ft) 156 | ax.set_xticklabels(ftl) 157 | lemon_plotting.decorate_spectrum(ax, ylabel="R-Square") 158 | ax.set_title('Full Model', y=0.95) 159 | lines = [Line2D([0], [0], color=lc, linewidth=0.5), Line2D([0], [0], color='k', linewidth=2)] 160 | plt.legend(lines, ['Single Dataset', 'Group Average']) 161 | lemon_plotting.subpanel_label(ax, chr(65), yf=1.1) 162 | 163 | ax = plt.axes([0.35, 0.55, 0.2, 0.35]) 164 | ax.plot(fx, flcf[:, 1, :, :].mean(axis=1).T, lw=0.5, color=lc) 165 | ax.plot(fx, flcf[:, 1, :, :].mean(axis=(0, 1)), lw=2, color='k') 166 | ax.set_xticks(ft) 167 | ax.set_xticklabels(ftl) 168 | lemon_plotting.decorate_spectrum(ax, ylabel="Cohen's F2") 169 | ax.set_title('Eyes Open > Eyes Closed', y=0.95) 170 | lemon_plotting.subpanel_label(ax, chr(65+1), yf=1.1) 171 | 172 | axs = [plt.axes([0.6, 0.75, 0.18, 0.18]), plt.axes([0.8, 0.75, 0.18, 0.18]), 173 | plt.axes([0.6, 0.5, 0.18, 0.18]), plt.axes([0.8, 0.5, 0.18, 0.18])] 174 | 175 | for idx, ax in enumerate(axs): 176 | ax.plot(fx, flcf[:, idx+2, :, :].mean(axis=1).T, lw=0.5, color=lc) 177 | ax.plot(fx, flcf[:, idx+2, :, :].mean(axis=(0, 1)), lw=2, color='k') 178 | ax.set_xticks(ft) 179 | ax.set_xticklabels(ftl) 180 | ax.set_title(glmsp.model.regressor_names[idx+2], y=0.85) 181 | ax.set_ylim(0, 0.5) 182 | lemon_plotting.decorate_spectrum(ax, ylabel="Cohen's F2") 183 | if idx < 2: 184 | ax.set_xlabel('') 185 | if idx == 1 or idx == 3: 186 | ax.set_ylabel('') 187 | if idx == 0: 188 | lemon_plotting.subpanel_label(ax, chr(65+2), yf=1.1) 189 | 190 | 191 | labs = ['D i', 'ii', 'iii', 'iv', 'v', 'vi', 'vii'] 192 | for ii in range(len(ppts[:5])): 193 | ax = plt.axes((0.1+ii*0.18, 0.07, 0.125, 0.3)) 194 | ax.plot(fx, rglmsp.data.data[ppts[ii], 0, :, :].mean(axis=0)) 195 | ax.plot(fx, fglmsp.data.data[ppts[ii], 0, :, :].mean(axis=0)) 196 | ax.set_xticks(ft)[::2] 197 | ax.set_xticklabels(ftl)[::2] 198 | for tag in ['top', 'right']: 199 | plt.gca().spines[tag].set_visible(False) 200 | #plt.ylim(0, 1.2e-5) 201 | lemon_plotting.subpanel_label(plt.gca(), labs[ii]) 202 | if ii == 0: 203 | plt.ylabel('Magnitude') 204 | plt.xlabel('Frequency (Hz)') 205 | plt.legend(['reduced Model', 'Full Model'], frameon=False) 206 | 207 | fout = os.path.join(cfg['lemon_figures'], 'lemon-group_stats-summary.png') 208 | plt.savefig(fout, transparent=True, dpi=300) 209 | fout = os.path.join(cfg['lemon_figures'], 'lemon-group_stats-summary_low-res.png') 210 | plt.savefig(fout, transparent=True, dpi=100) 211 | 212 | 213 | #%% ----------------------------------------------------- 214 | # group overall design summary 215 | 216 | sensor = 'Pz' 217 | ch_ind = mne.pick_channels(glmsp.info['ch_names'], [sensor])[0] 218 | 219 | I = np.arange(48) 220 | 221 | plt.figure(figsize=(16, 9)) 222 | aspect = 16/9 223 | xf = lemon_plotting.prep_scaled_freq(0.5, fglmsp.f) 224 | 225 | subj_ax = plt.axes([0.025, 0.125, 0.35, 0.75]) 226 | des_ax = plt.axes([0.4, 0.1, 0.25, 0.75]) 227 | mean_ax = plt.axes([0.725, 0.55, 0.25, 0.35]) 228 | cov_ax = plt.axes([0.725, 0.1, 0.25, 0.35]) 229 | 230 | xstep = 35 231 | ystep = 2e-6 232 | ntotal = 36 233 | subj_ax.plot((0, xstep*ntotal), (0, ystep*ntotal), color=[0.8, 0.8, 0.8], lw=0.5) 234 | for ii in range(28): 235 | d = fglmsp.data.data[I[ii], 0, ch_ind, :] 236 | ii = ii + 8 if ii > 14 else ii 237 | subj_ax.plot(np.arange(len(xf[0]))+xstep*ii, d + ystep*ii) 238 | for tag in ['top', 'right']: 239 | subj_ax.spines[tag].set_visible(False) 240 | subj_ax.spines['bottom'].set_bounds(0, len(xf[0])) 241 | subj_ax.spines['left'].set_bounds(0, 1e-5) 242 | subj_ax.set_xlim(0) 243 | subj_ax.set_ylim(0) 244 | subj_ax.set_xticks([]) 245 | subj_ax.set_yticks([]) 246 | l = subj_ax.set_xlabel(r'Frequency (Hz) $\rightarrow$', loc='left') 247 | l = subj_ax.set_ylabel(r'Amplitude $\rightarrow$', loc='bottom') 248 | subj_ax.text(48+35*18, ystep*19, '...', fontsize='xx-large', rotation=52) 249 | subj_ax.text(48+35*18, ystep*16, r'Participants $\rightarrow$', rotation=52) 250 | lemon_plotting.subpanel_label(subj_ax, chr(65), yf=0.75, xf=0.05) 251 | subj_ax.text(0.125, 0.725, 'First Level GLM\nCope-Spectra', 252 | transform=subj_ax.transAxes, fontsize='large') 253 | 254 | with mpl.rc_context({'font.size': 7}): 255 | fig = glm.viz.plot_design_summary(fglmsp.design.design_matrix, fglmsp.design.regressor_names, 256 | contrasts=fglmsp.design.contrasts, 257 | contrast_names=fglmsp.design.contrast_names, 258 | ax=des_ax) 259 | fig.axes[4].set_position([0.655, 0.4, 0.01, 0.2]) 260 | lemon_plotting.subpanel_label(fig.axes[1], chr(65+1), yf=1.1) 261 | fig.axes[1].set_ylabel('Participants') 262 | 263 | mean_ax.errorbar(xf[0], fglmsp.model.copes[0, 0, ch_ind, :], 264 | yerr=np.sqrt(fglmsp.model.varcopes[0, 0, ch_ind, :]), errorevery=1) 265 | mean_ax.set_xticks(xf[2], xf[1]) 266 | mean_ax.set_title('Group Mean Spectrum') 267 | lemon_plotting.decorate_spectrum(mean_ax, ylabel='Magnitude') 268 | lemon_plotting.subpanel_label(mean_ax, chr(65+2), yf=1.1) 269 | mean_ax.set_ylim(0) 270 | 271 | cov_ax.errorbar(xf[0], fglmsp.model.copes[1, 0, ch_ind, :], 272 | yerr=np.sqrt(fglmsp.model.varcopes[1, 0, ch_ind, :]), errorevery=2) 273 | cov_ax.errorbar(xf[0], fglmsp.model.copes[4, 0, ch_ind, :], 274 | yerr=np.sqrt(fglmsp.model.varcopes[4, 0, ch_ind, :]), errorevery=2) 275 | cov_ax.errorbar(xf[0], fglmsp.model.copes[5, 0, ch_ind, :], 276 | yerr=np.sqrt(fglmsp.model.varcopes[5, 0, ch_ind, :]), errorevery=2) 277 | cov_ax.errorbar(xf[0], fglmsp.model.copes[6, 0, ch_ind, :], 278 | yerr=np.sqrt(fglmsp.model.varcopes[6, 0, ch_ind, :]), errorevery=2) 279 | cov_ax.set_title('Group effects on Mean Spectrum') 280 | cov_ax.legend(list(np.array(fglmsp.model.contrast_names)[[1, 4, 5, 6]]), frameon=False, fontsize=12) 281 | cov_ax.set_xticks(xf[2], xf[1]) 282 | lemon_plotting.decorate_spectrum(cov_ax, ylabel='Magnitude') 283 | lemon_plotting.subpanel_label(cov_ax, chr(65+3), yf=1.1) 284 | 285 | fout = os.path.join(cfg['lemon_figures'], 'lemon-group_glm-overview.png') 286 | plt.savefig(fout, transparent=True, dpi=300) 287 | fout = os.path.join(cfg['lemon_figures'], 'lemon-group_glm-overview_low-res.png') 288 | plt.savefig(fout, transparent=True, dpi=100) 289 | 290 | 291 | fout = os.path.join(cfg['lemon_figures'], 'lemon-group_grouplevel-design.png') 292 | fig = fglmsp.design.plot_summary(figargs={'figsize': (16, 12)}, show=False) 293 | fig.savefig(fout, dpi=100, transparent=True) 294 | 295 | fout = os.path.join(cfg['lemon_figures'], 'lemon-group_grouplevel-efficiency.png') 296 | fig = fglmsp.design.plot_efficiency() 297 | fig.savefig(fout, dpi=100, transparent=True) 298 | 299 | eye 300 | 301 | #%% ---------------------------- 302 | # Group first-level covariates 303 | 304 | def project_first_level_range(glmsp, contrast, nsteps=2, min_step=0, max_step=1): 305 | steps = np.linspace(min_step, max_step, nsteps) 306 | pred = np.zeros((nsteps, *glmsp.model.betas.shape[2:])) 307 | 308 | # Run projection 309 | for ii in range(nsteps): 310 | if nsteps == 1: 311 | coeff = 0 312 | else: 313 | coeff = steps[ii] 314 | pred[ii, ...] = glmsp.model.betas[0, 0, ...] + coeff*glmsp.model.betas[0, contrast, ...] 315 | 316 | return pred 317 | 318 | fx = lemon_plotting.prep_scaled_freq(0.5, fglmsp.f,) 319 | 320 | plt.figure(figsize=(16, 9)) 321 | 322 | ax = plt.axes([0.775, 0.7, 0.15, 0.15]) 323 | lemon_plotting.plot_channel_layout2(ax, fglmsp.info, size=100) 324 | 325 | ll = [['Rec Start', 'Rec End'], 326 | ['Good Seg', 'Bad Seg'], 327 | ['Low V-EOG Activity', 'High V-EOG Activity'], 328 | ['Low H-EOG Activity', 'High H-EOG Activity']] 329 | 330 | plt.subplots_adjust(left=0.05, right=0.95, wspace=0.45, hspace=0.5, top=0.9) 331 | 332 | perms = ['MeanLinear Trend', 'MeanBad Segs', 'MeanV-EOG', 'MeanH-EOG'] 333 | 334 | for ii in range(4): 335 | ax1 = plt.subplot(3, 4, (ii+1, ii+5)) 336 | Pname = os.path.join(cfg['lemon_glm_data'], 'lemon-group_glm-spec_model-full_perm-{}.pkl'.format(perms[ii])) 337 | P = lemon_set_standard_montage(osl.glm.read_glm_spectrum(Pname)) 338 | P.plot_sig_clusters(95, ax=ax1, base=0.5) 339 | plt.legend(ll[ii], frameon=False) 340 | lemon_plotting.subpanel_label(ax1, chr(65+ii), yf=0.75) 341 | ax2 = plt.subplot(3, 4, ii+9) 342 | if ii == 0: 343 | proj = project_first_level_range(fglmsp, 4+ii, min_step=-1.73, max_step=1.73) 344 | else: 345 | proj = project_first_level_range(fglmsp, 4+ii) 346 | ax2.plot(fx[0], proj.mean(axis=1).T) 347 | ax2.set_xticks(fx[2]) 348 | ax2.set_xticklabels(fx[1]) 349 | ax2.set_xlim(fx[0][0], fx[0][-1]) 350 | lemon_plotting.decorate_spectrum(ax2) 351 | ax2.legend(ll[ii], frameon=False) 352 | 353 | fout = os.path.join(cfg['lemon_figures'], 'lemon-group_group-glm-meancov.png') 354 | plt.savefig(fout, transparent=True, dpi=300) 355 | fout = os.path.join(cfg['lemon_figures'], 'lemon-group_group-glm-meancov_low-res.png') 356 | plt.savefig(fout, transparent=True, dpi=100) 357 | 358 | #%% ---------------------------- 359 | # Group ANOVA type plot 360 | 361 | fx, ftl, ft = lemon_plotting.prep_scaled_freq(0.5, fglmsp.f) 362 | 363 | plt.figure(figsize=(16, 9)) 364 | 365 | ax1 = plt.axes([0.075, 0.5, 0.175, 0.4]) 366 | ax2 = plt.axes([0.075, 0.1, 0.175, 0.30]) 367 | Pname = os.path.join(cfg['lemon_glm_data'], 'lemon-group_glm-spec_model-full_perm-{}.pkl'.format('MeanOpen>Closed')) 368 | P = lemon_set_standard_montage(osl.glm.read_glm_spectrum(Pname)) 369 | P.plot_sig_clusters(95, ax=ax1, base=0.5) 370 | lemon_plotting.subpanel_label(ax1, 'A') 371 | 372 | ax2.plot(fx, fglmsp.model.copes[0, 2, :, :].mean(axis=0)) 373 | ax2.plot(fx, fglmsp.model.copes[0, 3, :, :].mean(axis=0)) 374 | lemon_plotting.decorate_spectrum(ax2) 375 | ax2.set_xticks(ft) 376 | ax2.set_xticklabels(ftl) 377 | ax2.set_ylabel('Magnitude') 378 | ax2.set_ylim(0) 379 | plt.legend(['Eyes Open Rest', 'Eyes Closed Rest'], frameon=False) 380 | 381 | 382 | ax1 = plt.axes([0.3125, 0.5, 0.175, 0.4]) 383 | ax2 = plt.axes([0.3125, 0.1, 0.175, 0.3]) 384 | Pname = os.path.join(cfg['lemon_glm_data'], 'lemon-group_glm-spec_model-full_perm-{}.pkl'.format('Young>OldRestMean')) 385 | P = lemon_set_standard_montage(osl.glm.read_glm_spectrum(Pname)) 386 | P.plot_sig_clusters(95, ax=ax1, base=0.5) 387 | lemon_plotting.subpanel_label(ax1, 'B') 388 | ax2.plot(fx, fglmsp.model.copes[2, 0, :, :].mean(axis=0)) 389 | ax2.plot(fx, fglmsp.model.copes[3, 0, :, :].mean(axis=0)) 390 | lemon_plotting.decorate_spectrum(ax2) 391 | ax2.set_xticks(ft) 392 | ax2.set_xticklabels(ftl) 393 | ax2.set_ylabel('Magnitude') 394 | ax2.set_ylim(0) 395 | plt.legend(['Young', 'Old'], frameon=False) 396 | 397 | 398 | ax1 = plt.axes([0.55, 0.5, 0.175, 0.4]) 399 | ax2 = plt.axes([0.55, 0.1, 0.175, 0.3]) 400 | Pname = os.path.join(cfg['lemon_glm_data'], 'lemon-group_glm-spec_model-full_perm-{}.pkl'.format('Young>OldOpen>Closed')) 401 | P = lemon_set_standard_montage(osl.glm.read_glm_spectrum(Pname)) 402 | P.plot_sig_clusters(95, ax=ax1, base=0.5) 403 | lemon_plotting.subpanel_label(ax1, 'C') 404 | ax2.plot(fx, fglmsp.model.copes[2, 1, :, :].mean(axis=0)) 405 | ax2.plot(fx, fglmsp.model.copes[3, 1, :, :].mean(axis=0)) 406 | lemon_plotting.decorate_spectrum(ax2) 407 | ax2.set_xticks(ft) 408 | ax2.set_xticklabels(ftl) 409 | ax2.set_ylabel('Magnitude') 410 | plt.legend(['YoungOpen>Closed', 'OldOpen>Closed'], frameon=False) 411 | 412 | ax = plt.axes([0.775, 0.7, 0.2, 0.2]) 413 | lemon_plotting.plot_channel_layout2(ax, fglmsp.info, size=100) 414 | 415 | ax = plt.axes([0.8, 0.175, 0.175, 0.35]) 416 | plt.plot(fx, fglmsp.model.copes[2, 2, :, :].mean(axis=0)) 417 | plt.plot(fx, fglmsp.model.copes[3, 2, :, :].mean(axis=0)) 418 | plt.plot(fx, fglmsp.model.copes[2, 3, :, :].mean(axis=0)) 419 | plt.plot(fx, fglmsp.model.copes[3, 3, :, :].mean(axis=0)) 420 | plt.legend(['{0} {1}'.format(fglmsp.model.contrast_names[2], fglmsp.fl_contrast_names[2][:-10]), 421 | '{0} {1}'.format(fglmsp.model.contrast_names[3], fglmsp.fl_contrast_names[2][:-10]), 422 | '{0} {1}'.format(fglmsp.model.contrast_names[2], fglmsp.fl_contrast_names[3][:-10]), 423 | '{0} {1}'.format(fglmsp.model.contrast_names[3], fglmsp.fl_contrast_names[3][:-10])], frameon=False) 424 | lemon_plotting.decorate_spectrum(ax, ylabel='Magnitude') 425 | ax2.set_xticks(ft) 426 | ax2.set_xticklabels(ftl) 427 | ax.set_ylim(0, 6.5e-6) 428 | lemon_plotting.subpanel_label(ax, 'D') 429 | 430 | fout = os.path.join(cfg['lemon_figures'], 'lemon-group_group-glm-anova.png') 431 | plt.savefig(fout, transparent=True, dpi=300) 432 | fout = os.path.join(cfg['lemon_figures'], 'lemon-group_group-glm-anova_low-res.png') 433 | plt.savefig(fout, transparent=True, dpi=100) 434 | 435 | #%% ---------------------------- 436 | # GROUP COVARIATES 437 | 438 | plt.figure(figsize=(16, 9)) 439 | 440 | # Sex effect 441 | ax1 = plt.axes([0.075, 0.5, 0.175, 0.4]) 442 | ax2 = plt.axes([0.075, 0.1, 0.175, 0.30]) 443 | Pname = os.path.join(cfg['lemon_glm_data'], 'lemon-group_glm-spec_model-full_perm-{}.pkl'.format('SexRestMean')) 444 | P = lemon_set_standard_montage(osl.glm.read_glm_spectrum(Pname)) 445 | P.plot_sig_clusters(95, ax=ax1, base=0.5) 446 | lemon_plotting.subpanel_label(ax1, 'C') 447 | proj, ll = fglmsp.model.project_range(2, nsteps=2) 448 | ax2.plot(fx, proj[0, 0, :, :].mean(axis=0)) 449 | ax2.plot(fx, proj[1, 0, :, :].mean(axis=0)) 450 | lemon_plotting.decorate_spectrum(ax2) 451 | ax2.set_xticks(ft) 452 | ax2.set_xticklabels(ftl) 453 | ax2.set_ylabel('Magnitude') 454 | plt.legend(['Female', 'Male'], frameon=False) 455 | 456 | 457 | # Head Size 458 | ax1 = plt.axes([0.3125, 0.5, 0.175, 0.4]) 459 | ax2 = plt.axes([0.3125, 0.1, 0.175, 0.3]) 460 | Pname = os.path.join(cfg['lemon_glm_data'], 'lemon-group_glm-spec_model-full_perm-{}.pkl'.format('TotalBrainVolRestMean')) 461 | P = lemon_set_standard_montage(osl.glm.read_glm_spectrum(Pname)) 462 | P.plot_sig_clusters(95, ax=ax1, base=0.5) 463 | lemon_plotting.subpanel_label(ax1, 'C') 464 | proj, ll = fglmsp.model.project_range(3, nsteps=2) 465 | ax2.plot(fx, proj[0, 0, :, :].mean(axis=0)) 466 | ax2.plot(fx, proj[1, 0, :, :].mean(axis=0)) 467 | lemon_plotting.decorate_spectrum(ax2) 468 | ax2.set_xticks(ft) 469 | ax2.set_xticklabels(ftl) 470 | ax2.set_ylabel('Magnitude') 471 | plt.legend(['Small Head', 'Large Head'], frameon=False) 472 | 473 | # GreyMatter 474 | ax1 = plt.axes([0.55, 0.5, 0.175, 0.4]) 475 | ax2 = plt.axes([0.55, 0.1, 0.175, 0.3]) 476 | Pname = os.path.join(cfg['lemon_glm_data'], 'lemon-group_glm-spec_model-full_perm-{}.pkl'.format('GreyMatterVolRestMean')) 477 | P = lemon_set_standard_montage(osl.glm.read_glm_spectrum(Pname)) 478 | P.plot_sig_clusters(95, ax=ax1, base=0.5) 479 | lemon_plotting.subpanel_label(ax1, 'C') 480 | proj, ll = fglmsp.model.project_range(4, nsteps=2) 481 | ax2.plot(fx, proj[0, 0, :, :].mean(axis=0)) 482 | ax2.plot(fx, proj[1, 0, :, :].mean(axis=0)) 483 | lemon_plotting.decorate_spectrum(ax2) 484 | ax2.set_xticks(ft) 485 | ax2.set_xticklabels(ftl) 486 | ax2.set_ylabel('Magnitude') 487 | plt.legend(['Small Vol', 'Large Vol'], frameon=False) 488 | 489 | ax = plt.axes([0.725, 0.7, 0.2, 0.2]) 490 | lemon_plotting.plot_channel_layout2(ax, fglmsp.info, size=100) 491 | 492 | fout = os.path.join(cfg['lemon_figures'], 'lemon-group_group-glm-covariates.png') 493 | plt.savefig(fout, transparent=True, dpi=300) 494 | fout = os.path.join(cfg['lemon_figures'], 'lemon-group_group-glm-covariates_low-res.png') 495 | plt.savefig(fout, transparent=True, dpi=100) 496 | -------------------------------------------------------------------------------- /13_collate_preproc_info.py: -------------------------------------------------------------------------------- 1 | """This script produces a table summarising the preprocessing outcomes.""" 2 | 3 | import os 4 | 5 | import mne 6 | import numpy as np 7 | import osl 8 | import pandas as pd 9 | 10 | from glm_config import cfg 11 | 12 | #%% -------------------------------------------------------------- 13 | # Find preprocessed data 14 | 15 | proc_outdir = cfg['lemon_processed_data'] 16 | 17 | fbase = os.path.join(cfg['lemon_processed_data'], '{subj}_{ftype}.fif') 18 | st = osl.utils.Study(fbase) 19 | 20 | #%% -------------------------------------------------------------- 21 | # Find preprocessed data 22 | 23 | preprocs = [] 24 | 25 | for subj_id in np.unique(st.fields['subj']): 26 | print(subj_id) 27 | raw = mne.io.read_raw_fif(st.get(subj=subj_id, ftype='preproc_raw')[0]) 28 | ica = mne.preprocessing.read_ica(st.get(subj=subj_id, ftype='ica')[0]) 29 | 30 | # We later mark the very start and end as bad - so the counters don't start at zero 31 | bads = 6 32 | dbads = 2 33 | for an in raw.annotations: 34 | if an['description'].find('bad_segment_eeg_raw') >= 0: 35 | bads += an['duration'] 36 | elif an['description'].find('bad_segment_eeg_diff') >= 0: 37 | dbads += an['duration'] 38 | 39 | # From ica._get_infos_for_repr 40 | abs_vars = ica.pca_explained_variance_ 41 | rel_vars = abs_vars / abs_vars.sum() 42 | fit_explained_variance = rel_vars[:ica.n_components_].sum() 43 | 44 | info = {'subj_id': subj_id, 45 | 'total_ica': len(np.unique(ica.exclude)), 46 | 'VEOG': len(ica.labels_['eog/0/VEOG']), 47 | 'HEOG': len(ica.labels_['eog/0/HEOG']), 48 | 'PCA_explained': fit_explained_variance, 49 | 'bad_seg_duration': bads, 50 | 'diff_bad_seg_duration': dbads} 51 | preprocs.append(info) 52 | 53 | #%% -------------------------------------------------------------- 54 | # Print a summary 55 | 56 | df = pd.DataFrame(preprocs) 57 | print(df.describe()) 58 | -------------------------------------------------------------------------------- /20_supp_check_firstlevel_residual_autocorr.py: -------------------------------------------------------------------------------- 1 | """This script creates a summary of the residual autocorrelation.""" 2 | 3 | import os 4 | 5 | import h5py 6 | import matplotlib.pyplot as plt 7 | import mne 8 | import numpy as np 9 | import osl 10 | import sails 11 | 12 | import lemon_plotting 13 | from glm_config import cfg 14 | from lemon_support import (get_eeg_data, lemon_make_bads_regressor, 15 | lemon_make_blinks_regressor, 16 | lemon_make_task_regressor) 17 | 18 | 19 | # FROM STATSMODELS 20 | def durbin_watson(resids, axis=0): 21 | r""" 22 | Calculate the Durbin-Watson statistic. 23 | 24 | Parameters 25 | ---------- 26 | resids : array_like 27 | Data for which to compute the Durbin-Watson statistic. Usually 28 | regression model residuals. 29 | axis : int, optional 30 | Axis to use if data has more than 1 dimension. Default is 0. 31 | 32 | Returns 33 | ------- 34 | dw : float, array_like 35 | The Durbin-Watson statistic. 36 | 37 | Notes 38 | ----- 39 | The null hypothesis of the test is that there is no serial correlation 40 | in the residuals. 41 | The Durbin-Watson test statistic is defined as: 42 | 43 | .. math:: 44 | 45 | \sum_{t=2}^T((e_t - e_{t-1})^2)/\sum_{t=1}^Te_t^2 46 | 47 | The test statistic is approximately equal to 2*(1-r) where ``r`` is the 48 | sample autocorrelation of the residuals. Thus, for r == 0, indicating no 49 | serial correlation, the test statistic equals 2. This statistic will 50 | always be between 0 and 4. The closer to 0 the statistic, the more 51 | evidence for positive serial correlation. The closer to 4, the more 52 | evidence for negative serial correlation. 53 | 54 | """ 55 | # This function has been borrowed from statsmodels 56 | resids = np.asarray(resids) 57 | diff_resids = np.diff(resids, 1, axis=axis) 58 | dw = np.sum(diff_resids**2, axis=axis) / np.sum(resids**2, axis=axis) 59 | return dw 60 | 61 | 62 | #%% ---------------------------------------------------------- 63 | # GLM-Prep 64 | 65 | fbase = os.path.join(cfg['lemon_processed_data'], '{subj}_preproc_raw.fif') 66 | st = osl.utils.Study(fbase) 67 | 68 | fname = st.get(subj='sub-010060')[0] 69 | 70 | runname = fname.split('/')[-1].split('.')[0] 71 | print('processing : {0}'.format(runname)) 72 | 73 | subj_id = osl.utils.find_run_id(fname) 74 | 75 | raw = mne.io.read_raw_fif(fname, preload=True) 76 | 77 | icaname = fname.replace('preproc_raw.fif', 'ica.fif') 78 | ica = mne.preprocessing.read_ica(icaname) 79 | 80 | picks = mne.pick_types(raw.info, eeg=True, ref_meg=False) 81 | chlabels = np.array(raw.info['ch_names'], dtype=h5py.special_dtype(vlen=str))[picks] 82 | 83 | #%% ---------------------------------------------------------- 84 | # GLM-Prep 85 | 86 | # Make blink regressor 87 | blink_vect, numblinks, evoked_blink = lemon_make_blinks_regressor(raw, figpath=None) 88 | 89 | veog = raw.get_data(picks='ICA-VEOG')[0, :]**2 90 | veog = veog > np.percentile(veog, 97.5) 91 | 92 | heog = raw.get_data(picks='ICA-HEOG')[0, :]**2 93 | heog = heog > np.percentile(heog, 97.5) 94 | 95 | # Make task regressor 96 | task = lemon_make_task_regressor({'raw': raw}) 97 | 98 | # Make bad-segments regressor 99 | bads_raw = lemon_make_bads_regressor(raw, mode='raw') 100 | bads_diff = lemon_make_bads_regressor(raw, mode='diff') 101 | 102 | # Get data 103 | XX = get_eeg_data(raw).T 104 | print(XX.shape) 105 | 106 | # Run GLM-Periodogram 107 | conds = {'Eyes Open': task == 1, 'Eyes Closed': task == -1} 108 | covs = {'Linear Trend': np.linspace(0, 1, raw.n_times)} 109 | confs = {'Bad Segments': bads_raw, 110 | 'Bad Segments Diff': bads_diff, 111 | 'V-EOG': veog, 'H-EOG': heog} 112 | conts = [{'name': 'Mean', 'values': {'Eyes Open': 0.5, 'Eyes Closed': 0.5}}, 113 | {'name': 'Open < Closed', 'values': {'Eyes Open': 1, 'Eyes Closed': -1}}] 114 | 115 | fs = raw.info['sfreq'] 116 | 117 | 118 | #%% ---------------------------------------------------------- 119 | # GLM-Run 120 | 121 | npersegs = np.array([100, fs, fs*2, fs*5]) 122 | 123 | xc1 = [] 124 | dw1 = [] 125 | ff1 = [] 126 | 127 | xc2 = [] 128 | dw2 = [] 129 | ff2 = [] 130 | 131 | xc3 = [] 132 | dw3 = [] 133 | ff3 = [] 134 | 135 | for nn in range(len(npersegs)): 136 | # Full model 137 | freq_vect, copes, varcopes, extras = sails.stft.glm_periodogram(XX, axis=0, 138 | fit_constant=False, 139 | conditions=conds, 140 | covariates=covs, 141 | confounds=confs, 142 | contrasts=conts, 143 | nperseg=int(npersegs[nn]), 144 | noverlap=1, 145 | fmin=0.1, fmax=100, 146 | fs=fs, mode='magnitude', 147 | fit_method='glmtools') 148 | 149 | resids = extras[0].get_residuals(extras[2].data) 150 | 151 | xc_iter = np.zeros((resids.shape[1], 61)) 152 | dw_iter = np.zeros((resids.shape[1], 61)) 153 | 154 | for ii in range(resids.shape[1]): 155 | for jj in range(61): 156 | xc_iter[ii, jj] = np.corrcoef(resids[1:, ii, jj], resids[:-1, ii, jj])[1, 0] 157 | dw_iter[ii, jj] = durbin_watson(resids[:, ii, jj]) 158 | 159 | dw1.append(dw_iter) 160 | xc1.append(xc_iter) 161 | ff1.append(freq_vect) 162 | 163 | # Full model 164 | freq_vect, copes, varcopes, extras = sails.stft.glm_periodogram(XX, axis=0, 165 | fit_constant=False, 166 | conditions=conds, 167 | covariates=covs, 168 | confounds=confs, 169 | contrasts=conts, 170 | nperseg=int(npersegs[nn]), 171 | noverlap=int(npersegs[nn]//2), 172 | fmin=0.1, fmax=100, 173 | fs=fs, mode='magnitude', 174 | fit_method='glmtools') 175 | 176 | resids = extras[0].get_residuals(extras[2].data) 177 | 178 | xc_iter = np.zeros((resids.shape[1], 61)) 179 | dw_iter = np.zeros((resids.shape[1], 61)) 180 | 181 | for ii in range(resids.shape[1]): 182 | for jj in range(61): 183 | xc_iter[ii, jj] = np.corrcoef(resids[1:, ii, jj], resids[:-1, ii, jj])[1, 0] 184 | dw_iter[ii, jj] = durbin_watson(resids[:, ii, jj]) 185 | 186 | dw2.append(dw_iter) 187 | xc2.append(xc_iter) 188 | ff2.append(freq_vect) 189 | 190 | # Full model 191 | freq_vect, copes, varcopes, extras = sails.stft.glm_periodogram(XX, axis=0, 192 | fit_constant=False, 193 | conditions=conds, 194 | covariates=covs, 195 | confounds=confs, 196 | contrasts=conts, 197 | nperseg=int(npersegs[nn]), 198 | noverlap=3*int(npersegs[nn]//4), 199 | fmin=0.1, fmax=100, 200 | fs=fs, mode='magnitude', 201 | fit_method='glmtools') 202 | 203 | resids = extras[0].get_residuals(extras[2].data)[10:, :, :] 204 | 205 | xc_iter = np.zeros((resids.shape[1], 61)) 206 | dw_iter = np.zeros((resids.shape[1], 61)) 207 | 208 | for ii in range(resids.shape[1]): 209 | for jj in range(61): 210 | xc_iter[ii, jj] = np.corrcoef(resids[1:, ii, jj], resids[:-1, ii, jj])[1, 0] 211 | dw_iter[ii, jj] = durbin_watson(resids[:, ii, jj]) 212 | 213 | dw3.append(dw_iter) 214 | xc3.append(xc_iter) 215 | ff3.append(freq_vect) 216 | 217 | #%% -------------------------------------------------------------- 218 | 219 | plt.figure(figsize=(16, 16)) 220 | 221 | for ii in range(len(npersegs)): 222 | plt.subplot(3, len(npersegs), ii+1) 223 | plt.pcolormesh(np.arange(61), ff1[ii], np.abs(dw1[ii]), vmin=0, vmax=4, cmap='RdBu') 224 | if ii == len(npersegs) - 1: 225 | plt.colorbar() 226 | plt.title('nperseg: {0}, nstep: {1}'.format(int(npersegs[ii]), int(npersegs[ii]-1))) 227 | 228 | if ii == 0: 229 | plt.ylabel('Frequency (Hz)') 230 | lemon_plotting.subpanel_label(plt.gca(), 'A') 231 | 232 | plt.subplot(3, len(npersegs), ii+len(npersegs)+1) 233 | plt.pcolormesh(np.arange(61), ff2[ii], np.abs(dw2[ii]), vmin=0, vmax=4, cmap='RdBu') 234 | if ii == len(npersegs) - 1: 235 | plt.colorbar() 236 | plt.title('nperseg: {0}, nstep: {1}'.format(int(npersegs[ii]), int(npersegs[ii]//2))) 237 | 238 | if ii == 0: 239 | plt.ylabel('Frequency (Hz)') 240 | lemon_plotting.subpanel_label(plt.gca(), 'B') 241 | 242 | plt.subplot(3, len(npersegs), ii+2*len(npersegs)+1) 243 | plt.pcolormesh(np.arange(61), ff3[ii], np.abs(dw3[ii]), vmin=0, vmax=4, cmap='RdBu') 244 | if ii == len(npersegs) - 1: 245 | plt.colorbar() 246 | plt.xlabel('Sensor') 247 | plt.title('nperseg: {0}, nstep: {1}'.format(int(npersegs[ii]), int(npersegs[ii]//4))) 248 | 249 | if ii == 0: 250 | plt.ylabel('Frequency (Hz)') 251 | lemon_plotting.subpanel_label(plt.gca(), 'C') 252 | 253 | fout = os.path.join(cfg['lemon_figures'], 'lemon-supp_first-level-residualautocorrelation.png') 254 | plt.savefig(fout, transparent=True, dpi=300) 255 | fout = os.path.join(cfg['lemon_figures'], 'lemon-supp_first-level-residualautocorrelation_low-res.png') 256 | plt.savefig(fout, transparent=True, dpi=100) 257 | -------------------------------------------------------------------------------- /21_supp_check_firstlevel_distributions.py: -------------------------------------------------------------------------------- 1 | """This script produces a table summarising first level distributions.""" 2 | 3 | import os 4 | from copy import deepcopy 5 | 6 | import glmtools as glm 7 | import h5py 8 | import matplotlib.pyplot as plt 9 | import mne 10 | import numpy as np 11 | import osl 12 | import sails 13 | 14 | import lemon_plotting 15 | from glm_config import cfg 16 | from lemon_support import (get_eeg_data, lemon_make_bads_regressor, 17 | lemon_make_blinks_regressor, 18 | lemon_make_task_regressor) 19 | 20 | #%% ---------------------------------------------------------- 21 | # GLM-Prep 22 | 23 | fbase = os.path.join(cfg['lemon_processed_data'], '{subj}/{subj}_preproc_raw.fif') 24 | st = osl.utils.Study(fbase) 25 | 26 | fname = st.get(subj='sub-010060')[0] 27 | 28 | runname = fname.split('/')[-1].split('.')[0] 29 | print('processing : {0}'.format(runname)) 30 | 31 | subj_id = osl.utils.find_run_id(fname) 32 | 33 | raw = mne.io.read_raw_fif(fname, preload=True) 34 | 35 | picks = mne.pick_types(raw.info, eeg=True, ref_meg=False) 36 | chlabels = np.array(raw.info['ch_names'], dtype=h5py.special_dtype(vlen=str))[picks] 37 | 38 | fbase = os.path.join(cfg['lemon_processed_data'], 'sub-010002', 'sub-010002_preproc_raw.fif') 39 | reference = mne.io.read_raw_fif(fbase).pick_types(eeg=True) 40 | 41 | #%% ---------------------------------------------------------- 42 | # GLM-Prep 43 | 44 | # Make blink regressor 45 | blink_vect, numblinks, evoked_blink = lemon_make_blinks_regressor(raw, figpath=None) 46 | 47 | veog = np.abs(raw.get_data(picks='ICA-VEOG')[0, :]) 48 | heog = np.abs(raw.get_data(picks='ICA-HEOG')[0, :]) 49 | 50 | # Make task regressor 51 | task = lemon_make_task_regressor({'raw': raw}) 52 | 53 | # Make bad-segments regressor 54 | bads = lemon_make_bads_regressor(raw) 55 | print('Bad segs in regressor {} / {}'.format(bads.sum(), len(bads))) 56 | 57 | # Get data 58 | XX = get_eeg_data(raw) 59 | print(XX.shape) 60 | 61 | # Run GLM-Periodogram 62 | 63 | conds = {'Eyes Open': task > 0, 'Eyes Closed': task < 0} 64 | covs = {'Linear Trend': np.linspace(0, 1, raw.n_times)} 65 | confs = {'Bad Segs': bads, 66 | 'V-EOG': veog, 'H-EOG': heog} 67 | eo_val = np.round(np.sum(task == 1) / len(task), 3) 68 | ec_val = np.round(np.sum(task == -1) / len(task), 3) 69 | conts = [{'name': 'RestMean', 'values': {'Eyes Open': 0.5, 'Eyes Closed': 0.5}}, 70 | {'name': 'Open>Closed', 'values': {'Eyes Open': 1, 'Eyes Closed': -1}}] 71 | 72 | fs = raw.info['sfreq'] 73 | 74 | # Reduced model - no confounds or covariates 75 | glmspec_mag = osl.glm.glm_spectrum(XX, fmin=1, fmax=95, 76 | fs=fs, 77 | fit_intercept=False, 78 | nperseg=int(fs * 2), 79 | mode='magnitude', 80 | contrasts=conts, 81 | reg_categorical=conds, 82 | reg_ztrans=covs, reg_unitmax=confs, 83 | standardise_data=False) 84 | glmspec_mag = osl.glm.SensorGLMSpectrum(glmspec_mag, reference.info) # Store with standard channel info 85 | 86 | # Reduced model - no confounds or covariates 87 | glmspec_pow = osl.glm.glm_spectrum(XX, fmin=1, fmax=95, 88 | fs=fs, 89 | fit_intercept=False, 90 | nperseg=int(fs * 2), 91 | mode='psd', 92 | contrasts=conts, 93 | reg_categorical=conds, 94 | reg_ztrans=covs, reg_unitmax=confs, 95 | standardise_data=False) 96 | glmspec_pow = osl.glm.SensorGLMSpectrum(glmspec_pow, reference.info) # Store with standard channel info 97 | 98 | # Reduced model - no confounds or covariates 99 | glmspec_log2 = osl.glm.glm_spectrum(XX, fmin=1, fmax=95, 100 | fs=fs, 101 | fit_intercept=False, 102 | nperseg=int(fs * 2), 103 | mode='log_psd', 104 | contrasts=conts, 105 | reg_categorical=conds, 106 | reg_ztrans=covs, reg_unitmax=confs, 107 | standardise_data=False) 108 | glmspec_log2 = osl.glm.SensorGLMSpectrum(glmspec_log2, reference.info) # Store with standard channel info 109 | 110 | # Reduced model - no confounds or covariates 111 | glmspec_log = deepcopy(glmspec_pow) 112 | glmspec_log.data.data = np.log(glmspec_log.data.data) 113 | glmspec_log.model = glm.fit.OLSModel(glmspec_log.design, glmspec_log.data) 114 | 115 | 116 | #%% ---------------------------------------------------------- 117 | # Figure 118 | 119 | plt.figure(figsize=(16, 9)) 120 | plt.subplots_adjust(wspace=0.45, hspace=0.35, left=0.04, right=0.975) 121 | ax = plt.subplot(2, 3, 1) 122 | glmspec_pow.plot_joint_spectrum(0, base=0.5, freqs=(3, 9 , 25), ax=ax) 123 | ax = plt.subplot(2, 3, 2) 124 | glmspec_mag.plot_joint_spectrum(0, base=0.5, freqs=(3, 9 , 25), ax=ax, ylabel='Magnitude') 125 | ax = plt.subplot(2, 3, 3) 126 | glmspec_log.plot_joint_spectrum(0, base=0.5, freqs=(3, 9 , 25), ax=ax, ylabel='log(Power)') 127 | 128 | ax = plt.subplot(2, 3, 4) 129 | glmspec_pow.plot_joint_spectrum(1, base=0.5, freqs=(3, 9 , 25), ax=ax, metric='tstats') 130 | ax = plt.subplot(2, 3, 5) 131 | glmspec_mag.plot_joint_spectrum(1, base=0.5, freqs=(3, 9 , 25), ax=ax, metric='tstats') 132 | ax = plt.subplot(2, 3, 6) 133 | glmspec_log.plot_joint_spectrum(1, base=0.5, freqs=(3, 9 , 25), ax=ax, metric='tstats') 134 | 135 | 136 | fout = os.path.join(cfg['lemon_figures'], 'lemon-supp_first-level-distribution-comparison.png') 137 | plt.savefig(fout, transparent=True, dpi=300) 138 | fout = os.path.join(cfg['lemon_figures'], 'lemon-supp_first-level-distribution-comparison_low-res.png') 139 | plt.savefig(fout, transparent=True, dpi=100) 140 | 141 | eye 142 | 143 | data_pow = deepcopy(data) 144 | data_pow.data = data_pow.data**2 145 | model_pow = glm.fit.OLSModel(design, data_pow) 146 | 147 | model_mag = glm.fit.OLSModel(design, data) 148 | 149 | data_logpow = deepcopy(data) 150 | data_logpow.data = np.log(data_logpow.data**2) 151 | model_logpow = glm.fit.OLSModel(design, data_logpow) 152 | 153 | #%% ---------------------------------------------------------- 154 | # Figure 155 | 156 | ff = 19 157 | chan = 24 158 | 159 | 160 | def quick_decorate(ax): 161 | """Decorate an axes.""" 162 | for tag in ['top', 'right']: 163 | ax.spines[tag].set_visible(False) 164 | ax.set_ylabel('Num Segments') 165 | 166 | 167 | plt.figure(figsize=(12, 12)) 168 | plt.subplots_adjust(hspace=0.5) 169 | 170 | plt.subplot(3, 3, 1) 171 | plt.plot(data_pow.data.mean(axis=0)) 172 | lemon_plotting.decorate_spectrum(plt.gca(), ylabel='Power') 173 | plt.title('Data Spectrum') 174 | lemon_plotting.subpanel_label(plt.gca(), 'A') 175 | plt.subplot(3, 3, 2) 176 | plt.hist(data_pow.data[:, ff, chan], 64) 177 | quick_decorate(plt.gca()) 178 | plt.xlabel('Power') 179 | plt.title('Data Distribution\n(single channel and freq)') 180 | plt.subplot(3, 3, 3) 181 | resids = model_pow.get_residuals(data_pow.data) 182 | plt.hist(resids[:, ff, chan], 64) 183 | quick_decorate(plt.gca()) 184 | plt.xlabel('Power') 185 | plt.title('Residual Distribution\n(single channel and freq)') 186 | 187 | plt.subplot(3, 3, 4) 188 | plt.plot(data.data.mean(axis=0)) 189 | lemon_plotting.decorate_spectrum(plt.gca(), ylabel='Magnitude') 190 | lemon_plotting.subpanel_label(plt.gca(), 'B') 191 | plt.subplot(3, 3, 5) 192 | plt.hist(data.data[:, ff, chan], 64) 193 | quick_decorate(plt.gca()) 194 | plt.xlabel('Magnitude') 195 | plt.subplot(3, 3, 6) 196 | resids = model_mag.get_residuals(data.data) 197 | plt.hist(resids[:, ff, chan], 64) 198 | quick_decorate(plt.gca()) 199 | plt.xlabel('Magnitude') 200 | 201 | plt.subplot(3, 3, 7) 202 | plt.plot(data_logpow.data.mean(axis=0)) 203 | lemon_plotting.decorate_spectrum(plt.gca(), ylabel='log(Power)') 204 | lemon_plotting.subpanel_label(plt.gca(), 'C') 205 | plt.subplot(3, 3, 8) 206 | plt.hist(data_logpow.data[:, ff, chan], 64) 207 | quick_decorate(plt.gca()) 208 | plt.xlabel('log(Power)') 209 | plt.subplot(3, 3, 9) 210 | resids = model_logpow.get_residuals(data_logpow.data) 211 | plt.hist(resids[:, ff, chan], 64) 212 | quick_decorate(plt.gca()) 213 | plt.xlabel('log(Power)') 214 | 215 | plt.savefig('dist_check.png') 216 | 217 | fout = os.path.join(cfg['lemon_figures'], 'lemon-supp_first-level-distribution-check.png') 218 | plt.savefig(fout, transparent=True, dpi=300) 219 | fout = os.path.join(cfg['lemon_figures'], 'lemon-supp_first-level-distribution-check_low-res.png') 220 | plt.savefig(fout, transparent=True, dpi=100) 221 | -------------------------------------------------------------------------------- /22_supp_varcopescaling.py: -------------------------------------------------------------------------------- 1 | """This script produces a figure summarising varcope smoothing.""" 2 | 3 | import os 4 | 5 | import glmtools as glm 6 | import matplotlib.pyplot as plt 7 | import mne 8 | import numpy as np 9 | import osl 10 | import sails 11 | 12 | import lemon_plotting 13 | from glm_config import cfg 14 | from lemon_support import (get_eeg_data, lemon_make_bads_regressor, 15 | lemon_make_blinks_regressor, 16 | lemon_make_task_regressor) 17 | 18 | #%% ---------------------------------------------------------- 19 | # GLM-Prep 20 | 21 | fbase = os.path.join(cfg['lemon_processed_data'], '{subj}_preproc_raw.fif') 22 | st = osl.utils.Study(fbase) 23 | 24 | fname = st.get(subj='sub-010060')[0] 25 | 26 | runname = fname.split('/')[-1].split('.')[0] 27 | print('processing : {0}'.format(runname)) 28 | 29 | subj_id = osl.utils.find_run_id(fname) 30 | 31 | raw = mne.io.read_raw_fif(fname, preload=True) 32 | refraw = raw.copy().pick_types(eeg=True) 33 | 34 | #%% ---------------------------------------------------------- 35 | # GLM-Prep 36 | 37 | # Make blink regressor 38 | blink_vect, numblinks, evoked_blink = lemon_make_blinks_regressor(raw, figpath=None) 39 | 40 | veog = raw.get_data(picks='ICA-VEOG')[0, :]**2 41 | veog = veog > np.percentile(veog, 97.5) 42 | 43 | heog = raw.get_data(picks='ICA-HEOG')[0, :]**2 44 | heog = heog > np.percentile(heog, 97.5) 45 | 46 | # Make task regressor 47 | task = lemon_make_task_regressor({'raw': raw}) 48 | 49 | # Make bad-segments regressor 50 | bads_raw = lemon_make_bads_regressor(raw, mode='raw') 51 | bads_diff = lemon_make_bads_regressor(raw, mode='diff') 52 | 53 | # Get data 54 | XX = get_eeg_data(raw).T 55 | print(XX.shape) 56 | 57 | # Run GLM-Periodogram 58 | conds = {'Eyes Open': task == 1, 'Eyes Closed': task == -1} 59 | covs = {'Linear Trend': np.linspace(0, 1, raw.n_times)} 60 | confs = {'Bad Segs': bads_raw, 61 | 'Bad Segs Diff': bads_diff, 62 | 'V-EOG': veog, 'H-EOG': heog} 63 | eo_val = np.round(np.sum(task == 1) / len(task), 3) 64 | ec_val = np.round(np.sum(task == -1) / len(task), 3) 65 | conts = [{'name': 'OverallMean', 'values': {'Constant': 1, 66 | 'Eyes Open': eo_val, 67 | 'Eyes Closed': ec_val}}, 68 | {'name': 'RestMean', 'values': {'Eyes Open': 0.5, 'Eyes Closed': 0.5}}, 69 | {'name': 'Eyes Open AbsEffect', 'values': {'Constant': 1, 'Eyes Open': 0.5}}, 70 | {'name': 'Eyes Closed AbsEffect', 'values': {'Constant': 1, 'Eyes Closed': 0.5}}, 71 | {'name': 'Open > Closed', 'values': {'Eyes Open': 1, 'Eyes Closed': -1}}] 72 | 73 | fs = raw.info['sfreq'] 74 | 75 | #%% ---------------------------------------------------------- 76 | # GLM-Run 77 | 78 | 79 | # Simple model 80 | f, c, v, extras = sails.stft.glm_periodogram(XX, axis=0, 81 | fit_constant=True, 82 | conditions=conds, 83 | covariates=covs, 84 | confounds=confs, 85 | contrasts=conts, 86 | nperseg=int(fs*2), 87 | noverlap=int(fs), 88 | fmin=0.1, fmax=100, 89 | fs=fs, mode='magnitude', 90 | fit_method='glmtools') 91 | model, design, data = extras 92 | 93 | #%% 94 | contrast = 4 95 | 96 | plt.figure(figsize=(16, 9)) 97 | plt.subplots_adjust(hspace=0.5, wspace=0.5) 98 | 99 | ax = plt.subplot(2, 3, 1) 100 | lemon_plotting.plot_sensor_spectrum(ax, model.copes[contrast, :, :], refraw, f, base=0.5) 101 | ax.set_title('cope-spectrum') 102 | lemon_plotting.subpanel_label(ax, 'A') 103 | 104 | ax = plt.subplot(2, 3, 2) 105 | lemon_plotting.plot_sensor_spectrum(ax, model.varcopes[contrast, :, :], refraw, f, base=0.5) 106 | ax.set_title('varcope-spectrum') 107 | lemon_plotting.subpanel_label(ax, 'B') 108 | 109 | ax = plt.subplot(2, 3, 3) 110 | lemon_plotting.plot_sensor_spectrum(ax, model.tstats[contrast, :, :], refraw, f, base=0.5) 111 | ax.set_title('t-spectrum') 112 | lemon_plotting.subpanel_label(ax, 'C') 113 | 114 | ax = plt.subplot(2, 3, 4) 115 | ax.plot(np.abs(model.copes[contrast, :, :].reshape(-1)), model.varcopes[contrast, :, :].reshape(-1), '.') 116 | lemon_plotting.subpanel_label(ax, 'D') 117 | for tag in ['top', 'right']: 118 | ax.spines[tag].set_visible(False) 119 | ax.set_xlabel('abs(cope_ values') 120 | ax.set_ylabel('varcope values') 121 | 122 | ax = plt.subplot(2, 3, 5) 123 | vc = glm.fit.varcope_corr_medfilt(model.varcopes[contrast, :, :], window_size=15, smooth_dims=0) 124 | lemon_plotting.plot_sensor_spectrum(ax, vc, refraw, f, base=0.5) 125 | ax.set_title('smoothed varcope-spectrum') 126 | lemon_plotting.subpanel_label(ax, 'E') 127 | 128 | ax = plt.subplot(2, 3, 6) 129 | lemon_plotting.plot_sensor_spectrum(ax, model.copes[contrast, :, :]/np.sqrt(vc), refraw, f, base=0.5) 130 | ax.set_title('pseudo t-spectrum') 131 | lemon_plotting.subpanel_label(ax, 'F') 132 | 133 | fout = os.path.join(cfg['lemon_figures'], 'lemon-supp_varcope-smoothing.png') 134 | plt.savefig(fout, transparent=True, dpi=300) 135 | fout = os.path.join(cfg['lemon_figures'], 'lemon-supp_varcope-smoothing_low-res.png') 136 | plt.savefig(fout, transparent=True, dpi=100) 137 | -------------------------------------------------------------------------------- /23_supp_periodogram_parameters.py: -------------------------------------------------------------------------------- 1 | """This script produces a figure summarising periodogram parameter options.""" 2 | 3 | import os 4 | 5 | import matplotlib.pyplot as plt 6 | import mne 7 | import numpy as np 8 | import osl 9 | import sails 10 | 11 | import lemon_plotting 12 | from glm_config import cfg 13 | from lemon_support import get_eeg_data 14 | 15 | #%% ---------------------------------------------------------- 16 | # GLM-Prep 17 | 18 | fbase = os.path.join(cfg['lemon_processed_data'], '{subj}_preproc_raw.fif') 19 | st = osl.utils.Study(fbase) 20 | 21 | fname = st.get(subj='sub-010060')[0] 22 | 23 | runname = fname.split('/')[-1].split('.')[0] 24 | print('processing : {0}'.format(runname)) 25 | 26 | subj_id = osl.utils.find_run_id(fname) 27 | 28 | raw = mne.io.read_raw_fif(fname, preload=True) 29 | refraw = raw.copy().pick_types(eeg=True) 30 | 31 | # Get data 32 | XX = get_eeg_data(raw).T 33 | print(XX.shape) 34 | fs = raw.info['sfreq'] 35 | 36 | #%% --------------------------------------------------------- 37 | # Explore window length 38 | 39 | wls = [25, 50, 250, 1000, 2500] 40 | win_len = [] 41 | for nperseg in wls: 42 | f, pxx = sails.stft.periodogram(XX, axis=0, nperseg=nperseg, mode='magnitude', fs=fs) 43 | win_len.append((f, pxx)) 44 | 45 | srs = [25, 50, 100, 250, 500] 46 | sample_rate = [] 47 | for fs in srs: 48 | YY = sails.utils.fast_resample(XX, 250/fs, axis=0) 49 | f, pxx = sails.stft.periodogram(YY, axis=0, nperseg=250, mode='magnitude', fs=fs) 50 | sample_rate.append((f, pxx)) 51 | fs = raw.info['sfreq'] 52 | 53 | dls = [1, 3, 5, 30, 60] 54 | data_len = [] 55 | for dl in dls: 56 | f, pxx = sails.stft.periodogram(XX[:int(dl*fs), :], axis=0, 57 | nperseg=250, mode='magnitude', fs=fs) 58 | data_len.append((f, pxx)) 59 | 60 | 61 | sfreqs = np.arange(100, 700) 62 | wlengths = np.arange(100, 700) 63 | res_matrix = np.zeros((sfreqs.shape[0], wlengths.shape[0])) 64 | 65 | for ii in range(len(sfreqs)): 66 | for jj in range(len(wlengths)): 67 | N = wlengths[jj] 68 | res_matrix[ii, jj] = sfreqs[ii] / N 69 | 70 | 71 | #%% ------------------------------------------------------ 72 | # 73 | 74 | plt.figure(figsize=(12, 9)) 75 | plt.subplots_adjust(hspace=0.7, top=0.925, bottom=0.075, left=0.05, right=0.95) 76 | 77 | for ii in range(len(win_len)): 78 | ax = plt.subplot(3, len(win_len), ii+1) 79 | lemon_plotting.plot_sensor_spectrum(ax, win_len[ii][1], refraw, win_len[ii][0], base=1) 80 | ax.set_ylim(0, 2.2e-5) 81 | plt.text(0.9, 0.8, '{} Samples'.format(wls[ii]), transform=ax.transAxes, ha='right') 82 | if ii == 0: 83 | lemon_plotting.subpanel_label(ax, 'A - Segment Length', ha='left', yf=1.2) 84 | 85 | ax = plt.subplot(3, len(win_len), ii+6) 86 | lemon_plotting.plot_sensor_spectrum(ax, sample_rate[ii][1], refraw, sample_rate[ii][0], base=1) 87 | plt.text(0.9, 0.8, '{}Hz'.format(srs[ii]), transform=ax.transAxes, ha='right') 88 | ax.set_ylim(0, 8e-5) 89 | if ii == 0: 90 | lemon_plotting.subpanel_label(ax, 'B - Sample Rate', ha='left', yf=1.2) 91 | 92 | ax = plt.subplot(3, len(win_len), ii+11) 93 | lemon_plotting.plot_sensor_spectrum(ax, data_len[ii][1], refraw, data_len[ii][0], base=1) 94 | plt.text(0.9, 0.8, '{} Seconds'.format(dls[ii]), transform=ax.transAxes, ha='right') 95 | ax.set_ylim(0, 3e-5) 96 | if ii == 0: 97 | lemon_plotting.subpanel_label(ax, 'C - Data Length', ha='left', yf=1.2) 98 | 99 | fout = os.path.join(cfg['lemon_figures'], 'lemon-supp_periodogram-params.png') 100 | plt.savefig(fout, transparent=True, dpi=300) 101 | fout = os.path.join(cfg['lemon_figures'], 'lemon-supp_periodogram-params_low-res.png') 102 | plt.savefig(fout, transparent=True, dpi=100) 103 | 104 | plt.figure(figsize=(7, 6)) 105 | plt.pcolormesh(np.log(wlengths), sfreqs, res_matrix, cmap='gist_rainbow') 106 | fx = np.linspace(100, 700, 7) 107 | plt.xticks(np.log(fx), fx.astype(int)) 108 | plt.colorbar(label='Frequency Resolution (Hz)') 109 | plt.ylabel('Sample Rate (Hz)') 110 | plt.xlabel('Segment Length (Samples)') 111 | 112 | fout = os.path.join(cfg['lemon_figures'], 'lemon-supp_periodogram-resmatrix.png') 113 | plt.savefig(fout, transparent=True, dpi=300) 114 | fout = os.path.join(cfg['lemon_figures'], 'lemon-supp_periodogram-resmatrix_low-res.png') 115 | plt.savefig(fout, transparent=True, dpi=100) 116 | -------------------------------------------------------------------------------- /25_supp_einsum-varcopes.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import timeit 3 | import os 4 | import matplotlib.pyplot as plt 5 | from glm_config import cfg 6 | 7 | # https://stackoverflow.com/a/72875625 8 | import numba as nb 9 | @nb.njit(parallel=True) # , fastmath=True 10 | def diag_dot(a, b): 11 | res = np.zeros(a.shape[0]) 12 | for i in nb.prange(a.shape[0]): 13 | for j in range(a.shape[1]): 14 | res[i] += a[i, j] * b[j, i] 15 | return res 16 | # run once to get compile out of the way 17 | xx = diag_dot(np.arange(9).reshape(3,3), np.arange(9).reshape(3,3)) 18 | 19 | #Your statements here 20 | nruns = 100 21 | nobservations = 25 22 | ntests = [1, 100, 100 * 61, 100 * 204] 23 | 24 | t = np.zeros((4, 4, nruns)) 25 | 26 | # Takes a few minutes to compute 27 | msg = 'Running dataset {0}/4 of shape {1} - each gets exponentially slower...' 28 | for jj in range(4): 29 | print(msg.format(jj+1, (nobservations, ntests[jj]))) 30 | for ii in range(t.shape[2]): 31 | 32 | # test data - content doesn't matter 33 | resid = np.random.randn(nobservations, ntests[jj]) 34 | 35 | # Run dot-diag method 36 | start = timeit.default_timer() 37 | resid_dots1 = np.diag( resid.T.dot(resid) ) 38 | stop = timeit.default_timer() 39 | t[jj, 0, ii] = stop - start 40 | 41 | # Run einsum method 42 | start = timeit.default_timer() 43 | resid_dots2 = np.einsum('ij,ji->i', resid.T, resid) 44 | stop = timeit.default_timer() 45 | t[jj, 1, ii] = stop - start 46 | 47 | # Run numba parallel method 48 | start = timeit.default_timer() 49 | resid_dots3 = diag_dot(resid.T, resid) 50 | stop = timeit.default_timer() 51 | t[jj, 2, ii] = stop - start 52 | 53 | # Run sensible normal method 54 | start = timeit.default_timer() 55 | resid_dots4 = (resid**2).sum(axis=0) 56 | stop = timeit.default_timer() 57 | t[jj, 3, ii] = stop - start 58 | 59 | assert(np.allclose(resid_dots1, resid_dots2)) 60 | assert(np.allclose(resid_dots1, resid_dots3)) 61 | 62 | plt.figure(figsize=(16,6)) 63 | plt.axes([0.12, 0.1, 0.7, 0.7]) 64 | xpos = np.arange(20).reshape(4, 5)[:, :4].reshape(-1) 65 | h = plt.boxplot(t[:, :, :].reshape(16, nruns).T, positions=xpos) 66 | plt.text(1.5, 7, '25 Windows\nSingle Channel\nSingle Frequency', ha='center') 67 | plt.text(6.5, 7, '25 Windows\nSingle Channel\n100 Frequency Bins', ha='center') 68 | plt.text(11.5, 7, '25 Windows\n60 Channels\n100 Frequency Bins', ha='center') 69 | plt.text(16.5, 7, '25 Windows\n204 Channel\n100 Frequency Bins', ha='center') 70 | plt.yscale('log') 71 | plt.yticks([10e-6, 10e-5, 10e-4, 10e-3, 10e-2, 10e-1], 72 | ['1/100 millisecond', '1/10 millisecond', '1 millisecond', '10 milliseconds', '100 milliseconds', '1 Second']) 73 | plt.xticks(xpos, ['DotDiag', 'EinSum', 'Numba', 'SumSqr'] * 4) 74 | for tag in ['top', 'right']: 75 | plt.gca().spines[tag].set_visible(False) 76 | plt.xlabel('VARCOPE Method') 77 | plt.ylabel('Time for single permutation') 78 | plt.grid(axis='y', lw=0.2) 79 | fout = os.path.join(cfg['lemon_figures'], 'lemon-supp_einsum-varcopes.png') 80 | plt.savefig(fout, transparent=True, dpi=300) 81 | fout = os.path.join(cfg['lemon_figures'], 'lemon-supp_einsum-varcopes_low-res.png') 82 | plt.savefig(fout, transparent=True, dpi=100) 83 | 84 | -------------------------------------------------------------------------------- /25_supp_linear_sqrt_log.py: -------------------------------------------------------------------------------- 1 | """This script produces a figure axis scaling options.""" 2 | 3 | import os 4 | 5 | import matplotlib.pyplot as plt 6 | import mne 7 | import numpy as np 8 | import osl 9 | import sails 10 | 11 | import lemon_plotting 12 | from glm_config import cfg 13 | from lemon_support import get_eeg_data 14 | 15 | #%% ---------------------------------------------------------- 16 | # Prep 17 | 18 | fbase = os.path.join(cfg['lemon_processed_data'], '{subj}/{subj}_preproc_raw.fif') 19 | st = osl.utils.Study(fbase) 20 | 21 | fname = st.get(subj='sub-010060')[0] 22 | 23 | runname = fname.split('/')[-1].split('.')[0] 24 | print('processing : {0}'.format(runname)) 25 | 26 | subj_id = osl.utils.find_run_id(fname) 27 | 28 | raw = mne.io.read_raw_fif(fname, preload=True) 29 | refraw = raw.copy().pick_types(eeg=True) 30 | 31 | # Get data 32 | XX = get_eeg_data(raw).T 33 | fs = raw.info['sfreq'] 34 | 35 | # Simple model 36 | f, pxx = sails.stft.periodogram(XX, axis=0, 37 | nperseg=int(fs*2), 38 | noverlap=int(fs), 39 | fmin=0.1, fmax=100, 40 | fs=fs, mode='magnitude') 41 | 42 | #%% ------------------------------------------------------------ 43 | 44 | 45 | # https://docs.python.org/2/tutorial/modules.html 46 | def fib2(n): # return Fibonacci series up to n 47 | """Compute fibonacci sequence.""" 48 | result = [] 49 | a, b = 0, 1 50 | while b < n: 51 | result.append(b) 52 | a, b = b, a+b 53 | return result 54 | 55 | 56 | fx = fib2(200)[1:] 57 | 58 | # Map freq to indices of fibonacci 59 | f_fib = np.zeros_like(f) 60 | xtks = [] 61 | for ii in range(len(f_fib)): 62 | if f[ii] <= 1: 63 | # Stay linear below 1 64 | f_fib[ii] = f[ii] 65 | else: 66 | # find fib in just below or equivalent to f 67 | ind1 = np.argmin(np.abs(f[ii] - fx)) 68 | ind2 = (f[ii] - fx[ind1]) / (fx[ind1+1] - fx[ind1]) 69 | f_fib[ii] = ind1 + ind2 + 1 70 | if f[ii] in fx: 71 | xtks.append(ii) 72 | 73 | #%% ---------------------------------------------------------- 74 | # Plot 75 | 76 | plt.figure(figsize=(12, 6.75)) 77 | plt.subplots_adjust(bottom=0.25, top=0.75, wspace=0.3, left=0.05, right=0.95) 78 | 79 | ax = plt.subplot(1, 4, 1) 80 | lemon_plotting.plot_sensor_spectrum(ax, pxx, refraw, f, base=1) 81 | ax.set_title('linear-scale') 82 | 83 | ax = plt.subplot(1, 4, 2) 84 | lemon_plotting.plot_sensor_spectrum(ax, np.sqrt(pxx), refraw, f, base=0.5) 85 | ax.set_title('sqrtsqrt-scale') 86 | 87 | ax = plt.subplot(1, 4, 3) 88 | lemon_plotting.plot_sensor_spectrum(ax, np.sqrt(pxx), refraw, f_fib, base=1) 89 | plt.xticks(f_fib[xtks], fx[:-1]) 90 | ax.set_title('fibonacci-sqrt-scale') 91 | 92 | ax = plt.subplot(1, 4, 4) 93 | lemon_plotting.plot_sensor_spectrum(ax, pxx, refraw, f, base=1) 94 | ax.set_title('loglog-scale') 95 | ax.set_yscale('log') 96 | ax.set_xscale('log') 97 | 98 | fout = os.path.join(cfg['lemon_figures'], 'lemon-supp_lin-sqrt-log.png') 99 | plt.savefig(fout, transparent=True, dpi=300) 100 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 OHBA Analysis Group 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The GLM-Spectrum: Multilevel power spectrum analysis with covariate and confound modelling. 2 | This repository contains the scripts and software to run the simulations and real data analysis published in: 3 | 4 | Andrew J. Quinn, Lauren Atkinson, Chetan Gohil, Oliver Kohl, Jemma Pitt, Catharina Zich, Anna C. Nobre & Mark W. Woolrich (2022) The GLM-Spectrum: Multilevel power spectrum analysis with covariate and confound modelling 5 | 6 | ## Getting started 7 | 8 | First, clone this repository into a directory on your computer: 9 | 10 | ``` 11 | git clone https://github.com/OHBA-analysis/Quinn2022_GLMSpectrum.git 12 | ``` 13 | 14 | then create a conda environment and install the dependencies 15 | 16 | ``` 17 | conda env create -f glmspectrum_env.yml 18 | conda activate glm-spectrum 19 | ``` 20 | 21 | Next, you need to configure the `lemon_raw` and `lemon_output` directories in `glm_config.yml`. `lemon_raw` specifies a directory where the raw data will be downloaded to (or where the raw data already exists) and `lemon_output` specifies a directory where the generatedoutputs from this analysis will be stored. 22 | 23 | After specifying these paths, `glm_config.yml` should look something like this: 24 | 25 | ``` 26 | lemon_raw: /path/to/my/raw/data_folder 27 | lemon_output: /path/to/my/output_folder 28 | lemon_raw_url: https://ftp.gwdg.de/pub/misc/MPI-Leipzig_Mind-Brain-Body-LEMON/EEG_MPILMBB_LEMON/EEG_Raw_BIDS_ID/ 29 | lemon_behav_url: https://ftp.gwdg.de/pub/misc/MPI-Leipzig_Mind-Brain-Body-LEMON/Behavioural_Data_MPILMBB_LEMON/ 30 | ``` 31 | 32 | From here you can run the analysis, plotting and supplemental scripts in order. Outputs will be saved into your `lemon_output` directory. 33 | 34 | 35 | ## Requirements 36 | 37 | A full list of requirements is specified in the `requirements.txt` file and the `glmspectrum_env.yml` anaconda environment. 38 | 39 | The EEG data analysis depends on [MNE-Python](https://mne.tools/stable/index.html) and [OSL](https://github.com/OHBA-analysis/osl). The GLM-Spectrum used in this paper is implemented in the [SAILS toolbox](https://joss.theoj.org/papers/10.21105/joss.01982) as `sails.stft.glm_periodogram`. Another implementation is available in [osl-dynamics](https://github.com/OHBA-analysis/osl-dynamics) as `osl_dynamics.analysis.regression_spectra`. The GLM analysis and statistics further depend on [glmtools](https://pypi.org/project/glmtools/) 40 | 41 | ## Data 42 | 43 | This paper uses the open-data availiable from the mind-body-brain dataset. 44 | 45 | Babayan, A., Erbey, M., Kumral, D. et al. A mind-brain-body dataset of MRI, EEG, cognition, emotion, and peripheral physiology in young and old adults. Sci Data 6, 180308 (2019). 46 | https://doi.org/10.1038/sdata.2018.308 47 | -------------------------------------------------------------------------------- /glm_config.py: -------------------------------------------------------------------------------- 1 | import os 2 | import yaml 3 | import pathlib 4 | import warnings 5 | 6 | code_dir = str(pathlib.Path(__file__).parent.resolve()) 7 | 8 | yaml_path = os.path.join(code_dir, 'glm_config.yaml') 9 | with open(yaml_path, 'r') as f: 10 | cfg = yaml.load(f, Loader=yaml.FullLoader) 11 | 12 | cfg['yaml_path'] = yaml_path 13 | cfg['code_dir'] = code_dir 14 | 15 | # Check LEMON 16 | cfg['lemon_raw_eeg'] = os.path.join(cfg['lemon_raw'], 'EEG_Raw_BIDS_ID') 17 | cfg['lemon_processed_data'] = os.path.join(cfg['lemon_output'], 'preprocessed_data') 18 | cfg['lemon_glm_data'] = os.path.join(cfg['lemon_output'], 'glm_data') 19 | cfg['lemon_figures'] = os.path.join(cfg['lemon_output'], 'figures') 20 | 21 | # Check that these folders exist and warn if they don't 22 | for directory in ['lemon_raw', 'lemon_raw_eeg', 'yaml_path', 'code_dir']: 23 | if not os.path.exists(cfg[directory]): 24 | warnings.warn('WARNING: dir not found - {0} : {1}'.format(directory, cfg[directory])) 25 | 26 | # Check that these folders exist and make them if they don't 27 | for directory in ['lemon_output', 'lemon_processed_data', 'lemon_glm_data', 'lemon_glm_data']: 28 | if not os.path.exists(cfg[directory]): 29 | os.makedirs(cfg[directory]) 30 | -------------------------------------------------------------------------------- /glm_config.yaml: -------------------------------------------------------------------------------- 1 | lemon_raw: /rds/projects/q/quinna-spectral-changes-in-ageing/raw_data 2 | lemon_output: /rds/projects/q/quinna-spectral-changes-in-ageing/ 3 | lemon_raw_url: https://ftp.gwdg.de/pub/misc/MPI-Leipzig_Mind-Brain-Body-LEMON/EEG_MPILMBB_LEMON/EEG_Raw_BIDS_ID/ 4 | lemon_behav_url: https://ftp.gwdg.de/pub/misc/MPI-Leipzig_Mind-Brain-Body-LEMON/Behavioural_Data_MPILMBB_LEMON/ 5 | -------------------------------------------------------------------------------- /glmspectrum_env.yml: -------------------------------------------------------------------------------- 1 | name: glm-spectrum 2 | channels: 3 | - defaults 4 | - conda-forge 5 | dependencies: 6 | - python==3.9.5 7 | - numpy==1.21.6 8 | - scipy==1.7.1 9 | - pandas==1.3.5 10 | - dill==0.3.2 11 | - h5py==2.10.0 12 | - anamnesis==1.0.4 13 | - mne==1.1.1 14 | - requests==2.26.0 15 | - matplotlib==3.5.2 16 | - dask==2022.2.0 17 | - beautifulsoup4==4.11.1 18 | - PyYAML==6.0 19 | - pip: 20 | - osl==0.1.1 21 | - glmtools==0.2.0 22 | - sails==1.3.0 23 | -------------------------------------------------------------------------------- /lemon_plotting.py: -------------------------------------------------------------------------------- 1 | import mne 2 | import numpy as np 3 | from scipy import signal, stats 4 | import matplotlib.pyplot as plt 5 | 6 | from matplotlib.patches import ConnectionPatch 7 | import matplotlib.colors as mcolors 8 | from matplotlib import ticker 9 | 10 | 11 | # High-level 12 | 13 | def plot_sensor_data(ax, data, raw, xvect=None, lw=0.5, 14 | xticks=None, xticklabels=None, 15 | sensor_cols=True, base=1, xtick_skip=1): 16 | if xvect is None: 17 | xvect = np.arange(obs.shape[0]) 18 | fx, xticklabels, xticks = prep_scaled_freq(base, xvect) 19 | 20 | if sensor_cols: 21 | colors, pos, outlines = get_mne_sensor_cols(raw) 22 | else: 23 | colors = None 24 | 25 | plot_with_cols(ax, data, fx, colors, lw=lw) 26 | ax.set_xlim(fx[0], fx[-1]) 27 | 28 | if xticks is not None: 29 | ax.set_xticks(xticks[::xtick_skip]) 30 | if xticklabels is not None: 31 | ax.set_xticklabels(xticklabels[::xtick_skip]) 32 | 33 | 34 | def plot_sensor_spectrum(ax, psd, raw, xvect, sensor_proj=False, 35 | xticks=None, xticklabels=None, lw=0.5, 36 | sensor_cols=True, base=1, ylabel=None, xtick_skip=1): 37 | 38 | plot_sensor_data(ax, psd, raw, base=base, sensor_cols=sensor_cols, lw=lw, 39 | xvect=xvect, xticks=xticks, xticklabels=xticklabels, xtick_skip=xtick_skip) 40 | decorate_spectrum(ax, ylabel=ylabel) 41 | ax.set_ylim(psd.min()) 42 | 43 | if sensor_proj: 44 | axins = ax.inset_axes([0.6, 0.6, 0.37, 0.37]) 45 | plot_channel_layout(axins, raw) 46 | 47 | 48 | def subpanel_label(ax, label, xf=-0.1, yf=1.1, ha='center'): 49 | ypos = ax.get_ylim()[0] 50 | yyrange = np.diff(ax.get_ylim())[0] 51 | ypos = (yyrange * yf) + ypos 52 | # Compute letter position as proportion of full xrange. 53 | xpos = ax.get_xlim()[0] 54 | xxrange = np.diff(ax.get_xlim())[0] 55 | xpos = (xxrange * xf) + xpos 56 | ax.text(xpos, ypos, label, horizontalalignment=ha, 57 | verticalalignment='center', fontsize=20, fontweight='bold') 58 | 59 | # Helpers 60 | 61 | def decorate_spectrum(ax, ylabel='Power'): 62 | for tag in ['top', 'right']: 63 | ax.spines[tag].set_visible(False) 64 | ax.set_xlabel('Frequency (Hz)') 65 | ax.set_ylabel(ylabel) 66 | 67 | 68 | def decorate_timseries(ax, ylabel='Power'): 69 | for tag in ['top', 'right']: 70 | ax.spines[tag].set_visible(False) 71 | ax.set_xlabel('Frequency (Hz)') 72 | ax.set_ylabel(ylabel) 73 | 74 | 75 | def plot_with_cols(ax, data, xvect, cols=None, lw=0.5): 76 | if cols is not None: 77 | for ii in range(data.shape[1]): 78 | ax.plot(xvect, data[:, ii], lw=lw, color=cols[ii, :]) 79 | else: 80 | ax.plot(xvect, data, lw=lw) 81 | 82 | 83 | def prep_scaled_freq(base, freq_vect): 84 | """Assuming ephy freq ranges for now - around 1-40Hz""" 85 | fx = freq_vect**base 86 | if base < 1: 87 | nticks = int(np.floor(np.sqrt(freq_vect[-1]))) 88 | #ftick = np.array([2**ii for ii in range(6)]) 89 | ftick = np.array([ii**2 for ii in range(1,nticks+1)]) 90 | ftickscaled = ftick**base 91 | else: 92 | # Stick with automatic scales 93 | ftick = None 94 | ftickscaled = None 95 | return fx, ftick, ftickscaled 96 | 97 | 98 | # MNE Helpers 99 | 100 | 101 | def get_mne_sensor_cols2(info): 102 | 103 | chs = [info['chs'][i] for i in range(len(info['chs']))] 104 | locs3d = np.array([ch['loc'][:3] for ch in chs]) 105 | x, y, z = locs3d.T 106 | colors = mne.viz.evoked._rgb(x, y, z) 107 | pos, outlines = mne.viz.evoked._get_pos_outlines(info, 108 | range(len(info['chs'])), 109 | sphere=None) 110 | 111 | return colors, pos, outlines 112 | 113 | 114 | def plot_channel_layout2(ax, info, size=30, marker='o'): 115 | 116 | ax.set_adjustable('box') 117 | ax.set_aspect('equal') 118 | 119 | colors, pos, outlines = get_mne_sensor_cols2(info) 120 | pos_x, pos_y = pos.T 121 | mne.viz.evoked._prepare_topomap(pos, ax, check_nonzero=False) 122 | ax.scatter(pos_x, pos_y, 123 | color=colors, s=size * .8, 124 | marker=marker, zorder=1) 125 | mne.viz.evoked._draw_outlines(ax, outlines) 126 | 127 | def get_mne_sensor_cols(raw, picks=None): 128 | if picks is not None: 129 | raw.pick_types(**picks) 130 | 131 | chs = [raw.info['chs'][i] for i in range(len(raw.info['chs']))] 132 | locs3d = np.array([ch['loc'][:3] for ch in chs]) 133 | x, y, z = locs3d.T 134 | colors = mne.viz.evoked._rgb(x, y, z) 135 | pos, outlines = mne.viz.evoked._get_pos_outlines(raw.info, 136 | range(len(raw.info['chs'])), 137 | sphere=None) 138 | 139 | return colors, pos, outlines 140 | 141 | 142 | def plot_channel_layout(ax, raw, size=30, marker='o'): 143 | 144 | ax.set_adjustable('box') 145 | ax.set_aspect('equal') 146 | 147 | colors, pos, outlines = get_mne_sensor_cols(raw) 148 | pos_x, pos_y = pos.T 149 | mne.viz.evoked._prepare_topomap(pos, ax, check_nonzero=False) 150 | ax.scatter(pos_x, pos_y, 151 | color=colors, s=size * .8, 152 | marker=marker, zorder=1) 153 | mne.viz.evoked._draw_outlines(ax, outlines) 154 | 155 | 156 | def plot_joint_spectrum(ax, psd, raw, xvect, freqs='auto', base=1, topo_scale='joint', lw=0.5, ylabel='Power', title='', ylim=None, xtick_skip=1): 157 | 158 | if ylim is None: 159 | # Plot invisible lines to get correct xy lims - probably a better way to do 160 | # this using update_datalim but I can't find it. 161 | plot_sensor_spectrum(ax, psd, raw, xvect, base=base, lw=0, ylabel=ylabel) 162 | ylim = ax.get_ylim() 163 | else: 164 | ax.plot(xvect, np.linspace(ylim[0], ylim[1], len(xvect)), lw=0) 165 | ax.set_ylim(*ylim) 166 | 167 | fx, xtl, xt = prep_scaled_freq(base, xvect) 168 | 169 | if freqs == 'auto': 170 | freqs = signal.find_peaks(psd.mean(axis=1), distance=10)[0] 171 | if 0 not in freqs: 172 | freqs = np.r_[0, freqs] 173 | else: 174 | # Convert Hz to samples in freq dim 175 | freqs = [np.argmin(np.abs(xvect - f)) for f in freqs] 176 | 177 | topo_centres = np.linspace(0, 1, len(freqs)+2)[1:-1] 178 | topo_width = 0.4 179 | 180 | # Shrink axes to make space for topos 181 | pos = ax.get_position() 182 | ax.set_position([pos.x0, pos.y0, pos.width, pos.height*0.65]) 183 | 184 | shade = [0.7, 0.7, 0.7] 185 | 186 | #if topo_scale is 'joint': 187 | # vmin = psd.mean(axis=1).min() 188 | # vmax = psd.mean(axis=1).max() 189 | # # Set colourmaps 190 | # if np.all(np.sign((vmin, vmax))==1): 191 | # # Reds if all positive 192 | # cmap = 'Reds' 193 | # elif np.all(np.sign((vmin, vmax))==-1): 194 | # # Blues if all negative 195 | # cmap = 'Blues' 196 | # elif np.all(np.sign((-vmin, vmax))==1): 197 | # # RdBu diverging from zero if split across zero 198 | # cmap = 'RdBu_r' 199 | #else: 200 | # vmin = None 201 | # vmax = None 202 | norm = None 203 | if topo_scale is 'joint': 204 | vmin = obs.mean(axis=1).min() 205 | vmax = obs.mean(axis=1).max() 206 | # Set colourmaps 207 | if np.all(np.sign((vmin, vmax))==1): 208 | # Reds if all positive 209 | cmap = 'Reds' 210 | elif np.all(np.sign((vmin, vmax))==-1): 211 | # Blues if all negative 212 | cmap = 'Blues' 213 | elif np.all(np.sign((-vmin, vmax))==1): 214 | # RdBu diverging from zero if split across zero 215 | cmap = 'RdBu_r' 216 | norm = mcolors.TwoSlopeNorm(vmin=vmin, vmax=vmax, vcenter=0) 217 | vmax = np.max((vmin, vmax)) 218 | vmin = -vmax 219 | else: 220 | vmin = None 221 | vmax = None 222 | 223 | for idx in range(len(freqs)): 224 | # Create topomap axis 225 | topo_pos = [topo_centres[idx] - 0.2, 1.2, 0.4, 0.4] 226 | topo = ax.inset_axes(topo_pos) 227 | 228 | if topo_scale is None: 229 | vmin = psd[freqs[idx], :].min() 230 | vmax = psd[freqs[idx], :].max() 231 | # Set colourmaps 232 | if np.all(np.sign((vmin, vmax))==1): 233 | # Reds if all positive 234 | cmap = 'Reds' 235 | elif np.all(np.sign((vmin, vmax))==-1): 236 | # Blues if all negative 237 | cmap = 'Blues' 238 | elif np.all(np.sign((-vmin, vmax))==1): 239 | # RdBu diverging from zero if split across zero 240 | cmap = 'RdBu_r' 241 | 242 | # Draw topomap itself 243 | #im, cn = mne.viz.plot_topomap(psd[freqs[idx], :], raw.info, axes=topo, 244 | # cmap=cmap, vmin=vmin, vmax=vmax) 245 | dat = psd[freqs[idx], :] 246 | if len(np.unique(np.sign(dat))) == 2: 247 | print('Crossing') 248 | dat = dat / np.abs(dat).max() 249 | im, cn = mne.viz.plot_topomap(dat, raw.info, axes=topo, cmap='RdBu_r', 250 | vlim=(-1, 1), show=False) #vmin=-1, vmax=1, show=False) 251 | elif np.unique(np.sign(dat)) == [1]: 252 | print('Positive') 253 | dat = dat - dat.min() 254 | dat = dat / dat.max() 255 | im, cn = mne.viz.plot_topomap(dat, raw.info, axes=topo, cmap='Reds', 256 | vlim=(0, 1), show=False) #vmin=0, vmax=1, show=False) 257 | elif np.unique(np.sign(dat)) == [-1]: 258 | print('Negative') 259 | dat = dat - dat.max() 260 | dat = -(dat / dat.min()) 261 | im, cn = mne.viz.plot_topomap(-dat, raw.info, axes=topo, cmap='Blues', 262 | vlim=(0, 1), show=False) #vmin=0, vmax=1, show=False) 263 | print('{} - {}'.format(dat.min(), dat.max())) 264 | 265 | # Add angled connecting line 266 | xy = (fx[freqs[idx]], ax.get_ylim()[1]) 267 | con = ConnectionPatch(xyA=xy, xyB=(0, topo.get_ylim()[0]), 268 | coordsA=ax.transData, coordsB=topo.transData, 269 | axesA=ax, axesB=topo, color=shade, lw=2) 270 | ax.get_figure().add_artist(con) 271 | 272 | #if idx == len(freqs) - 1: 273 | # cb_pos = [0.95, 1.2, 0.05, 0.4] 274 | # cax = ax.inset_axes(cb_pos) 275 | # cb = plt.colorbar(im, cax=cax, boundaries=np.linspace(vmin, vmax)) 276 | # tks = _get_sensible_ticks(round_to_first_sig(vmin), round_to_first_sig(vmax), 3) 277 | # cb.set_ticks([vmin, vmax]) 278 | # cb.set_ticklabels(['min', 'max']) 279 | 280 | # Add vertical lines 281 | ax.vlines(fx[freqs], ax.get_ylim()[0], ax.get_ylim()[1], color=shade, lw=2) 282 | 283 | plot_sensor_spectrum(ax, psd, raw, xvect, base=base, lw=lw, ylabel=ylabel, xtick_skip=xtick_skip) 284 | ax.set_title(title) 285 | ax.set_ylim(ylim) 286 | 287 | 288 | def test_base(b): 289 | plt.figure() 290 | f = np.linspace(0, 40) 291 | fs = f**b 292 | fx = np.array([2**ii for ii in range(6)]) 293 | 294 | plt.plot(fs, f) 295 | plt.xticks(fx**b, fx) 296 | 297 | 298 | def plot_sensorspace_clusters(dat, P, raw, ax, xvect=None, ylabel='Power', topo_scale='joint', base=1, lw=0.5, title=None, thresh=95): 299 | from matplotlib.patches import ConnectionPatch 300 | clu, obs = P.get_sig_clusters(thresh, dat) 301 | if xvect is None: 302 | xvect = np.arange(obs.shape[0]) 303 | 304 | # Start plotting 305 | plot_sensor_spectrum(ax, obs, raw, xvect, base=base, lw=lw, ylabel=ylabel) 306 | fx, xtl, xt = prep_scaled_freq(base, xvect) 307 | 308 | shade = [0.7, 0.7, 0.7] 309 | xf = -0.03 310 | 311 | # Shrink axes to make space for topos 312 | pos = ax.get_position() 313 | ax.set_position([pos.x0, pos.y0, pos.width, pos.height*0.65]) 314 | 315 | # sort clusters by ascending freq 316 | forder = np.argsort([c[2][0].mean() for c in clu]) 317 | clu = [clu[c] for c in forder] 318 | 319 | norm = None 320 | if topo_scale is 'joint': 321 | vmin = obs.mean(axis=1).min() 322 | vmax = obs.mean(axis=1).max() 323 | # Set colourmaps 324 | if np.all(np.sign((vmin, vmax))==1): 325 | # Reds if all positive 326 | cmap = 'Reds' 327 | elif np.all(np.sign((vmin, vmax))==-1): 328 | # Blues if all negative 329 | cmap = 'Blues' 330 | elif np.all(np.sign((-vmin, vmax))==1): 331 | # RdBu diverging from zero if split across zero 332 | cmap = 'RdBu_r' 333 | norm = mcolors.TwoSlopeNorm(vmin=vmin, vmax=vmax, vcenter=0) 334 | vmax = np.max((vmin, vmax)) 335 | vmin = -vmax 336 | else: 337 | vmin = None 338 | vmax = None 339 | print('{} : {} - {}'.format(vmin, vmax, cmap)) 340 | ax.set_title(title) 341 | 342 | if len(clu) == 0: 343 | # put up an empty axes anyway 344 | topo_pos = [0.3, 1.2, 0.4, 0.4] 345 | topo = ax.inset_axes(topo_pos, frame_on=False) 346 | topo.set_xticks([]) 347 | topo.set_yticks([]) 348 | return 349 | 350 | if len(clu) >3: 351 | # Plot topos for three largest clusters 352 | cstats = [np.abs(c[0]) for c in clu] 353 | topo_cv = np.argsort(cstats)[-3] 354 | topo_plot = np.array([True if np.abs(c[0]) >= cstats[topo_cv] else False for c in clu]) 355 | else: 356 | topo_plot = np.array([True for c in clu]) 357 | 358 | topo_centres = np.linspace(0, 1, topo_plot.sum()+2)[1:-1] 359 | topo_width = 0.4 360 | 361 | stupid_counter = 0 362 | for c in range(len(clu)): 363 | inds = np.where(clu==c+1)[0] 364 | channels = np.zeros((obs.shape[1], )) 365 | channels[clu[c][2][1]] = 1 366 | if len(channels) == 204: 367 | channels = np.logical_and(channels[::2], channels[1::2]) 368 | times = np.zeros((obs.shape[0], )) 369 | times[clu[c][2][0]] = 1 370 | tinds = np.where(times)[0] 371 | #if len(tinds) == 1: 372 | # continue 373 | #tinds = [tinds[0], tinds[0]+1] 374 | ax.axvspan(fx[tinds[0]], fx[tinds[-1]], facecolor=shade, alpha=0.5) 375 | 376 | if topo_plot[c]: 377 | 378 | topo_pos = [topo_centres[stupid_counter] - 0.2, 1.2, 0.4, 0.4] 379 | stupid_counter += 1 380 | topo = ax.inset_axes(topo_pos) 381 | dat = obs[tinds, :].mean(axis=0) 382 | 383 | # Scale topo by min and max of whole data range. 384 | #dat = dat / np.abs(dat).max() 385 | dat = dat.mean() + stats.zscore(dat) 386 | vmin = dat.min() 387 | vmax = dat.max() 388 | vmin = -vmax if vmax > np.abs(vmin) else vmin 389 | vmax = -vmin if np.abs(vmin) > vmax else vmax 390 | im, cn = mne.viz.plot_topomap(dat, raw.info, axes=topo, cmap='RdBu_r', 391 | #vmin=vmin, vmax=vmax, mask=channels.astype(int), 392 | vlim=(vmin, vmax), mask=channels.astype(int), 393 | show=False) 394 | 395 | #if len(np.unique(np.sign(dat))) == 2: 396 | # dat = dat / np.abs(dat).max() 397 | # print('Crossing {} - {}'.format(dat.min(), dat.max())) 398 | # im, cn = mne.viz.plot_topomap(dat, raw.info, axes=topo, cmap='RdBu_r', 399 | # vmin=dat.min(), vmax=dat.max(), mask=channels.astype(int), 400 | # show=False) 401 | #elif np.unique(np.sign(dat)) == [1]: 402 | # dat = dat - dat.min() 403 | # dat = dat / dat.max() 404 | # print('Positive {} - {}'.format(dat.min(), dat.max())) 405 | # im, cn = mne.viz.plot_topomap(dat, raw.info, axes=topo, cmap='Reds', 406 | # vmin=0, vmax=1, mask=channels.astype(int), 407 | # show=False) 408 | #elif np.unique(np.sign(dat)) == [-1]: 409 | # dat = dat - dat.max() 410 | # dat = dat / dat.min() 411 | # print('Positive {} - {}'.format(dat.min(), dat.max())) 412 | # im, cn = mne.viz.plot_topomap(dat, raw.info, axes=topo, cmap='Blues_r', 413 | # vmin=-1, vmax=0, mask=channels.astype(int), 414 | # show=False) 415 | 416 | #if norm is not None: 417 | # im.set_norm(norm) 418 | 419 | xy = (fx[tinds].mean(), ax.get_ylim()[1]) 420 | con = ConnectionPatch(xyA=xy, xyB=(0, topo.get_ylim()[0]), 421 | coordsA=ax.transData, coordsB=topo.transData, 422 | axesA=ax, axesB=topo, color=shade) 423 | plt.gcf().add_artist(con) 424 | 425 | if c == len(clu) - 1: 426 | cb_pos = [0.95, 1.2, 0.05, 0.4] 427 | cax = ax.inset_axes(cb_pos) 428 | #cb = plt.colorbar(im, cax=cax, boundaries=np.linspace(vmin, vmax)) 429 | #tks = _get_sensible_ticks(round_to_first_sig(vmin), round_to_first_sig(vmax), 3) 430 | #cb.set_ticks(tks) 431 | cb = plt.colorbar(im, cax=cax) #, boundaries=np.linspace(-1, 1)) 432 | #tks = _get_sensible_ticks(round_to_first_sig(vmin), round_to_first_sig(vmax), 3) 433 | cb.set_ticks([vmin, 0, vmax], ['min', '0', 'max']) 434 | cb.set_ticklabels(['min', '0', 'max']) 435 | 436 | 437 | 438 | def _get_sensible_ticks(vmin, vmax, nbins=3): 439 | """Return sensibly rounded tick positions based on a plotting range. 440 | 441 | Based on code in matplotlib.ticker 442 | Assuming symmetrical axes and 3 ticks for the moment 443 | 444 | """ 445 | scale, offset = ticker.scale_range(vmin, vmax) 446 | if vmax/scale > 0.5: 447 | scale = scale / 2 448 | edge = ticker._Edge_integer(scale, offset) 449 | low = edge.ge(vmin) 450 | high = edge.le(vmax) 451 | 452 | ticks = np.linspace(low, high, nbins) * scale 453 | 454 | return ticks 455 | 456 | def round_to_first_sig(x): 457 | return np.round(x, -int(np.floor(np.log10(np.abs(x))))) 458 | 459 | 460 | def vrange_logic(data): 461 | 462 | # If all positive, use Reds 463 | 464 | # If all negative, use Blues 465 | 466 | # If split, use white in middle RdBu 467 | return None 468 | -------------------------------------------------------------------------------- /lemon_preproc.yml: -------------------------------------------------------------------------------- 1 | meta: 2 | event_codes: 3 | preproc: 4 | - lemon_set_channel_montage: None 5 | - lemon_create_heog: None 6 | - set_channel_types: {VEOG: eog, HEOG: eog} 7 | - crop: {tmin: 10} 8 | - filter: {l_freq: 0.25, h_freq: 125, method: 'iir', iir_params: {order: 5, ftype: butter}} 9 | - notch_filter: {freqs: 50 100} 10 | - bad_channels: {picks: 'eeg'} 11 | - resample: {sfreq: 250} 12 | - bad_segments: {segment_len: 2500, picks: 'eog'} 13 | - lemon_ica: {n_components: 30, picks: eeg} 14 | - bad_segments: {segment_len: 500, picks: 'eeg'} 15 | - bad_segments: {segment_len: 500, picks: 'eeg', mode: 'diff'} 16 | - interpolate_bads: None 17 | -------------------------------------------------------------------------------- /lemon_structural_vols.csv: -------------------------------------------------------------------------------- 1 | PPT_ID, TotalBrainVol, GreyMatterVol, GreyMatterVolNorm, WhiteMatterVol, WhiteMatterVolNorm, CSF_Vol, CSFVol_Norm, HippoLVol, HippoLVolNorm, HippoRVol, HippoRVolNorm, HippoTotalVolNorm, AmygRVolNorm, AmygLVolNorm, ThalLVolNorm, ThalRVolNorm, CaudLVolNorm, CaudRVolNorm, PutaLVolNorm, PutaRVolNorm, PalliLVolNorm, PalliRVolNorm 2 | sub-010002,1275244,552969.7403,43.36187744,460775.0343,36.13230364,261499.1851,20.50581576,3551,0.278456515,3382,0.265204149,0.543660664,0.069712149,0.112135403,0.551031803,0.542484419,0.335465213,0.372477738,0.346129839,0.332093309,0.124917271,0.124133107 3 | sub-010003,1237789,566087.0725,45.73372946,401722.2819,32.45482727,269980.1522,21.8114842,3551,0.286882498,3382,0.273229121,0.560111618,0.071821611,0.115528576,0.567705804,0.55889978,0.345616256,0.383748765,0.356603589,0.34214232,0.128697217,0.127889325 4 | sub-010004,1320465,571157.9372,43.25430339,463914.4929,35.13266106,285392.1235,21.61300175,3551,0.268920418,3382,0.256121896,0.525042315,0.067324768,0.108295184,0.532161019,0.523906351,0.323976781,0.359721765,0.334276183,0.320720352,0.12063932,0.119882011 5 | sub-010005,1572390,670036.5765,42.61262005,552024.127,35.10732878,350329.094,22.28003829,3551,0.225834558,3382,0.215086588,0.440921146,0.056538136,0.090944359,0.446899306,0.439967184,0.272069906,0.302087904,0.28071916,0.269335216,0.101310744,0.100674769 6 | sub-010006,1190181,541796.0123,45.5221527,423005.2096,35.54125042,225379.957,18.93661191,3551,0.298357981,3382,0.28415846,0.582516441,0.074694521,0.120149792,0.590414399,0.581256128,0.359441127,0.399098961,0.37086796,0.355828231,0.133845188,0.13300498 7 | sub-010007,1373165,548251.3504,39.92610869,483723.3213,35.2268898,341190.1125,24.8469858,3551,0.258599658,3382,0.246292325,0.504891983,0.064740945,0.104138978,0.511737482,0.503799616,0.311543041,0.345916186,0.321447168,0.308411589,0.116009365,0.115281121 8 | sub-010010,1475730,696653.2501,47.20736517,485011.7772,32.86588856,294064.6668,19.92672554,3551,0.240626673,3382,0.229174714,0.469801386,0.060241372,0.096901195,0.476171115,0.46878494,0.289890427,0.321874598,0.299106205,0.286976615,0.107946576,0.107268945 9 | sub-010012,1198479,532035.4972,44.392559,450113.301,37.5570453,216330.0145,18.05038006,3551,0.296292217,3382,0.28219101,0.578483227,0.074177353,0.119317902,0.586326502,0.577231641,0.356952437,0.396335689,0.368300154,0.353364556,0.132918474,0.132084083 10 | sub-010015,1463330,681038.2796,46.54030735,487206.5572,33.2943736,295085.3179,20.16532962,3551,0.242665701,3382,0.2311167,0.4737824,0.060751847,0.097722318,0.480206105,0.472757341,0.292346907,0.324602106,0.301640778,0.289408404,0.108861296,0.108177923 11 | sub-010016,1334324,556544.9103,41.70987783,438919.3405,32.89450991,338859.6597,25.39560555,3551,0.266127267,3382,0.253461678,0.519588945,0.066625497,0.107170372,0.526633711,0.518464781,0.320611785,0.355985503,0.330804212,0.31738918,0.119386296,0.118636853 12 | sub-010017,1631738,762466.7415,46.72727738,552424.4477,33.85497229,316846.8845,19.41775484,3551,0.217620721,3382,0.207263666,0.424884387,0.054481786,0.087636618,0.430645116,0.423965122,0.262174442,0.291100655,0.270509114,0.259539215,0.097625967,0.097013123 13 | sub-010019,1340008,582076.3048,43.43827088,444150.8938,33.14539121,313781.2604,23.41637217,3551,0.264998418,3382,0.252386553,0.517384971,0.066342888,0.106715781,0.524399854,0.516265575,0.319251825,0.354475496,0.329401019,0.316042889,0.118879887,0.118133623 14 | sub-010020,1570977,721345.4801,45.91699815,519520.7966,33.06991742,330110.2228,21.01305256,3551,0.226037682,3382,0.215280045,0.441317728,0.056588989,0.091026158,0.447301265,0.440362908,0.272314617,0.302359614,0.28097165,0.269577467,0.101401866,0.10076532 15 | sub-010021,1298638,578710.3802,44.56287127,438948.3303,33.80066888,280979.0717,21.63644308,3551,0.273440327,3382,0.260426693,0.533867021,0.068456337,0.110115367,0.541105373,0.532711964,0.329422056,0.365767828,0.339894566,0.326110895,0.122666979,0.121896941 16 | sub-010022,1468554,606374.268,41.29056664,491988.0362,33.50152846,370191.5236,25.20789318,3551,0.241802481,3382,0.230294562,0.472097042,0.060535738,0.097374696,0.478497897,0.471075629,0.291306959,0.323447418,0.300567769,0.288378909,0.10847405,0.107793108 17 | sub-010023,1321452,596957.5866,45.17436778,443926.5674,33.59384733,280567.4127,21.2317521,3551,0.26871956,3382,0.255930598,0.524650158,0.067274483,0.108214298,0.531763545,0.523515043,0.323734801,0.359453086,0.33402651,0.320480804,0.120549214,0.119792471 18 | sub-010024,1507451,678002.8296,44.97677401,522381.2997,34.65328556,307066.698,20.36992897,3551,0.235563212,3382,0.224352234,0.459915447,0.058973725,0.094862122,0.466151139,0.458920389,0.283790319,0.315101453,0.292812171,0.280937822,0.105675077,0.105011705 19 | sub-010026,1251776,531105.7607,42.4281789,427690.2065,34.16667251,292980.6124,23.40519489,3551,0.283676952,3382,0.270176134,0.553853086,0.071019096,0.114237691,0.561362416,0.552654788,0.341754435,0.379460862,0.352618999,0.338319316,0.12725919,0.126460325 20 | sub-010027,1606023,679416.0847,42.30425621,572585.1706,35.6523643,354021.7541,22.04338008,3551,0.221105177,3382,0.210582289,0.431687466,0.055354126,0.089039821,0.437540434,0.430753482,0.266372275,0.295761642,0.274840398,0.263694854,0.099189115,0.098566459 21 | sub-010028,1230301,573730.4605,46.63334099,409104.578,33.25239742,247466.2948,20.11428868,3551,0.288628555,3382,0.274892079,0.563520634,0.07225874,0.116231719,0.571161041,0.562301421,0.347719786,0.386084381,0.358773991,0.344224706,0.129480509,0.1286677 22 | sub-010029,1439432,623266.7032,43.29948919,481210.251,33.43056504,334955.1479,23.26995287,3551,0.24669453,3382,0.234953787,0.481648317,0.061760472,0.099344742,0.48817867,0.480606239,0.297200562,0.329991274,0.306648734,0.294213273,0.110668653,0.109973934 23 | sub-010030,1556133,704758.8913,45.28911676,521127.305,33.48860959,330246.9324,21.22228193,3551,0.228193863,3382,0.217333608,0.445527471,0.057128793,0.091894459,0.451568086,0.444563543,0.274912234,0.305243832,0.283651847,0.272148974,0.102369142,0.101726523 24 | sub-010031,1347030,600470.8623,44.5773934,419435.0138,31.13776336,327124.1758,24.28484709,3551,0.263616994,3382,0.251070874,0.514687869,0.065997045,0.106159477,0.521666184,0.513574308,0.317587582,0.352627633,0.327683867,0.314395374,0.118260172,0.117517798 25 | sub-010032,1188809,553728.2911,46.57840672,401308.7726,33.75721185,233771.7612,19.66436671,3551,0.298702315,3382,0.284486406,0.583188721,0.074780726,0.120288457,0.591095794,0.581926954,0.359855957,0.399559559,0.371295978,0.356238891,0.133999658,0.13315848 26 | sub-010033,1401896,666919.1578,47.57265573,450610.3379,32.142922,284366.974,20.28445577,3551,0.253299817,3382,0.241244714,0.494544531,0.063414119,0.102004714,0.501249736,0.493474552,0.305158157,0.338826846,0.314859305,0.302090883,0.113631824,0.112918505 27 | sub-010034,1318719,593450.9325,45.00207645,427581.356,32.42399298,297687.1337,22.57396259,3551,0.269276472,3382,0.256461005,0.525737477,0.067413907,0.108438568,0.532865607,0.52460001,0.32440573,0.360198041,0.334718769,0.32114499,0.120799048,0.120040737 28 | sub-010035,1338556,572120.64,42.74162904,461331.2603,34.46484572,305104.3606,22.79354473,3551,0.265285875,3382,0.252660329,0.517946205,0.066414853,0.106831541,0.524968698,0.516825594,0.319598134,0.354860013,0.329758337,0.316385717,0.119008842,0.118261769 29 | sub-010036,1371230,627004.5167,45.72570004,452219.2895,32.97909829,292006.1214,21.29519639,3551,0.258964579,3382,0.246639878,0.505604457,0.064832304,0.104285933,0.512459617,0.504510549,0.311982672,0.346404323,0.321900775,0.308846802,0.116173071,0.115443799 30 | sub-010037,1661957,700936.6882,42.17538048,546754.5208,32.89823508,414265.2788,24.92635362,3551,0.213663771,3382,0.203495036,0.417158807,0.053491155,0.086043141,0.42281479,0.416256257,0.257407382,0.285807635,0.265590506,0.254820071,0.095850855,0.095249155 31 | sub-010038,1283199,555986.4003,43.32815099,397295.135,30.96130336,329917.4599,25.71054528,3551,0.276730266,3382,0.263560056,0.540290321,0.069279979,0.111440236,0.547615763,0.539121368,0.333385547,0.370168618,0.343984059,0.330034546,0.124142865,0.123363562 32 | sub-010039,1493097,612725.1703,41.03719787,520252.4448,34.84384771,360119.5863,24.11896791,3551,0.237827817,3382,0.226509061,0.464336878,0.059540673,0.095774086,0.470632518,0.463332255,0.286518558,0.318130704,0.295627143,0.283638638,0.106690992,0.106021243 33 | sub-010040,1651483,760459.9537,46.04709547,551849.9016,33.41541521,339172.8589,20.53747201,3551,0.215018865,3382,0.204785638,0.419804503,0.053830406,0.086588842,0.425496357,0.418896228,0.259039905,0.287620278,0.267274928,0.256436185,0.096458759,0.095853242 34 | sub-010041,1597944,684350.9246,42.82696544,554360.5005,34.69211064,359232.4103,22.48091362,3551,0.222223057,3382,0.211646966,0.433870023,0.05563399,0.089489995,0.439752582,0.432931317,0.267719019,0.297256975,0.276229955,0.265028061,0.099690602,0.099064798 35 | sub-010042,1378050,593529.8548,43.07026993,452384.2898,32.82785746,332135.4728,24.10184484,3551,0.257682958,3382,0.245419252,0.50310221,0.064511447,0.10376982,0.509923443,0.502013715,0.310438663,0.34468996,0.320307681,0.307318312,0.115598128,0.114872465 36 | sub-010043,1391809,584260.2199,41.97847693,500279.1887,35.94452893,307269.0835,22.07695765,3551,0.255135583,3382,0.242993112,0.498128694,0.063873707,0.102743983,0.504882495,0.49705096,0.307369761,0.34128246,0.317141217,0.304280257,0.11445536,0.113736871 37 | sub-010044,1167152,520521.378,44.59756552,421638.4998,36.12541467,224991.8841,19.2769994,3551,0.304244863,3382,0.289765172,0.594010035,0.076168314,0.12252046,0.602063827,0.592724855,0.366533236,0.406973556,0.378185532,0.362849055,0.136486079,0.135629293 38 | sub-010045,1193510,523590.0404,43.86976568,412907.1296,34.59603435,257012.7557,21.53419374,3551,0.297525785,3382,0.28336587,0.580891656,0.074486179,0.119814664,0.588767585,0.579634859,0.358438555,0.397985773,0.369833516,0.354835737,0.13347186,0.132633996 39 | sub-010046,1546623,684359.3227,44.24861926,488749.6978,31.60108817,373513.6738,24.15027281,3551,0.229596999,3382,0.218669967,0.448266966,0.057480071,0.092459507,0.454344724,0.447297111,0.276602637,0.30712074,0.285395989,0.273822386,0.102998598,0.102352028 40 | sub-010048,1281732,520355.5474,40.59784319,492835.1023,38.45071374,268541.4437,20.95145035,3551,0.277046996,3382,0.263861712,0.540908708,0.069359273,0.111567785,0.548242534,0.539738416,0.333767121,0.370592292,0.344377764,0.330412286,0.124284952,0.123504758 41 | sub-010050,1349218,570289.8735,42.26817856,464889.0061,34.45618174,314038.8839,23.27562216,3551,0.263189492,3382,0.250663718,0.51385321,0.065890019,0.10598732,0.520820208,0.512741455,0.317072556,0.352055783,0.327152469,0.313885525,0.118068392,0.117327222 42 | sub-010051,1360619,557502.8767,40.97420929,487062.799,35.79714814,316053.955,23.22868892,3551,0.260984155,3382,0.248563338,0.509547493,0.065337909,0.105099223,0.516456113,0.508445053,0.314415718,0.349105811,0.324411169,0.311255392,0.117079065,0.116344105 43 | sub-010052,1378400,596120.9488,43.24731201,476392.0662,34.56123521,305886.6521,22.19142862,3551,0.257617528,3382,0.245356936,0.502974463,0.064495067,0.103743471,0.509793964,0.501886245,0.310359837,0.344602438,0.320226349,0.307240279,0.115568775,0.114843297 44 | sub-010053,1765468,789892.2286,44.74123737,612543.563,34.69581794,363032.0101,20.56293346,3551,0.201136469,3382,0.191563937,0.392700406,0.05035492,0.080998353,0.398024773,0.391850773,0.242315352,0.269050473,0.250018692,0.239879737,0.090231032,0.08966461 45 | sub-010056,1690431,792371.8182,46.87395216,567532.6117,33.57324917,330526.6305,19.55280225,3551,0.21006477,3382,0.20006732,0.410132091,0.052590138,0.084593811,0.415692803,0.409244743,0.253071554,0.280993427,0.261116839,0.250527824,0.094236322,0.093644757 46 | sub-010059,1558822,657061.7714,42.15117386,492761.1556,31.61112402,408998.7761,26.23768307,3551,0.227800224,3382,0.216958703,0.444758927,0.057030245,0.091735939,0.450789122,0.443796662,0.274438005,0.30471728,0.283162542,0.271679512,0.102192553,0.101551043 47 | sub-010060,1466792,595521.4258,40.6002641,487321.1358,33.22360197,383949.8497,26.17616197,3551,0.242092948,3382,0.230571206,0.472664154,0.060608457,0.097491669,0.479072697,0.471641514,0.291656895,0.323835963,0.30092883,0.288725327,0.108604356,0.107922596 48 | sub-010061,1644925,697001.564,42.37284764,578807.1118,35.18744695,369116.1841,22.43969689,3551,0.215876104,3382,0.205602079,0.421478183,0.054045017,0.086934055,0.427192729,0.420566287,0.260072648,0.288766965,0.268340502,0.257458547,0.096843321,0.096235391 49 | sub-010062,1486233,655695.7567,44.11796513,514246.876,34.6006902,316290.303,21.28134034,3551,0.238926198,3382,0.227555168,0.466481366,0.059815655,0.096216408,0.472806081,0.465472103,0.287841812,0.319599955,0.296992463,0.284948592,0.107183732,0.10651089 50 | sub-010063,1330081,613323.0219,46.11170461,436695.338,32.83223638,280062.6771,21.05606178,3551,0.266976222,3382,0.254270229,0.52124645,0.066838035,0.107512249,0.528313689,0.5201187,0.321634547,0.357121108,0.331859488,0.318401661,0.119767142,0.119015308 51 | sub-010064,1376579,597480.7358,43.40330165,448518.3441,32.58209983,330580.4214,24.01463493,3551,0.257958316,3382,0.245681505,0.50363982,0.064580384,0.103880707,0.510468342,0.502550162,0.310770395,0.345058293,0.320649959,0.30764671,0.115721655,0.114995216 52 | sub-010065,1539656,620980.9208,40.33244574,508059.8987,32.99827356,410614.7208,26.66925084,3551,0.230635934,3382,0.219659456,0.450295391,0.057740171,0.09287789,0.456400651,0.449321147,0.277854274,0.308510472,0.286687416,0.275061442,0.10346467,0.102815174 53 | sub-010066,1626340,675953.531,41.56286699,589762.1075,36.26314962,360624.676,22.17400273,3551,0.218343028,3382,0.207951597,0.426294625,0.054662617,0.087927494,0.432074474,0.425372308,0.263044628,0.292066849,0.271406963,0.260400654,0.097949998,0.097335121 54 | sub-010067,1587527,669832.2673,42.19344095,547770.7651,34.50465819,369924.398,23.30192796,3551,0.223681235,3382,0.213035747,0.436716982,0.055999048,0.090077208,0.442638141,0.435772116,0.269475732,0.29920751,0.278042515,0.266767116,0.10034475,0.099714839 55 | sub-010068,1601041,698942.1032,43.65547811,532340.0781,33.24962184,369759.0723,23.09491589,3551,0.221793196,3382,0.211237564,0.433030759,0.055526373,0.089316888,0.438901939,0.432093869,0.267201152,0.296681971,0.275695626,0.2645154,0.099497764,0.098873171 56 | sub-010069,1444512,642396.5158,44.47152504,493255.7803,34.14688007,308859.9479,21.38161178,3551,0.245826964,3382,0.234127512,0.479954476,0.061543276,0.09899537,0.486461864,0.478916063,0.29615538,0.328830775,0.305570324,0.293178596,0.110279458,0.109587182 57 | sub-010070,1907479,867308.0229,45.46881108,640444.3433,33.57543351,399726.4415,20.95574533,3551,0.186161945,3382,0.177302083,0.363464028,0.046606018,0.07496806,0.368391998,0.362677649,0.224275077,0.24901978,0.231404907,0.222020793,0.08351337,0.082989118 58 | sub-010071,1592883,690417.8335,43.34391374,557904.0815,35.02479978,344560.9046,21.63127515,3551,0.222929117,3382,0.212319423,0.43524854,0.055810753,0.089774327,0.441149789,0.434306851,0.268569631,0.298201437,0.277107609,0.265870124,0.100007345,0.099379553 59 | sub-010072,1662636,708459.7158,42.6106325,571237.2481,34.35732464,382939.4622,23.03206849,3551,0.213576513,3382,0.203411931,0.416988445,0.05346931,0.086008002,0.422642118,0.416086263,0.25730226,0.285690915,0.265482042,0.254716005,0.095811711,0.095210256 60 | sub-010073,1697635,782718.0377,46.10637962,571634.9191,33.67242777,343282.4969,20.22121934,3551,0.20917335,3382,0.199218324,0.408391674,0.052366969,0.084234833,0.413928789,0.407508092,0.251997632,0.279801017,0.260008777,0.249464696,0.093836425,0.093247371 61 | sub-010074,1575119,694331.3017,44.08119651,540850.5794,34.33712496,339937.4791,21.58170139,3551,0.225443284,3382,0.214713936,0.44015722,0.05644018,0.090786791,0.446125023,0.439204911,0.271598527,0.301564517,0.280232795,0.268868574,0.101135216,0.100500343 62 | sub-010075,1447295,653795.903,45.17364484,486322.7232,33.60218361,307176.605,21.22418754,3551,0.245354264,3382,0.233677308,0.479031573,0.061424934,0.098805012,0.485526448,0.477995156,0.295585903,0.328198467,0.304982744,0.292614844,0.110067402,0.109376457 63 | sub-010076,1411955,622697.8125,44.10181716,469378.9565,33.24319518,319877.7738,22.65495528,3551,0.251495267,3382,0.239526047,0.491021314,0.062962347,0.101278015,0.49767875,0.489958958,0.30298416,0.336412988,0.312616195,0.299938737,0.112822292,0.112114055 64 | sub-010077,1309066,578308.3647,44.17717401,439216.7725,33.55191965,291540.8722,22.27090706,3551,0.271262106,3382,0.258352138,0.529614244,0.067911014,0.10923819,0.536794936,0.528468389,0.326797885,0.362854127,0.337186971,0.3235131,0.121689815,0.120925912 65 | sub-010078,1352724,580790.3743,42.93487617,440685.7215,32.57765232,331247.8711,24.48746907,3551,0.262507356,3382,0.250014046,0.512521401,0.065719245,0.105712621,0.519470343,0.511412528,0.316250765,0.351143323,0.326304553,0.313071994,0.117762382,0.117023133 66 | sub-010079,1313790,596955.7269,45.43768234,442257.9227,33.66275605,274576.1068,20.89954306,3551,0.270286728,3382,0.25742318,0.527709908,0.067666827,0.108845401,0.534864781,0.526568173,0.325622816,0.36154941,0.335974547,0.322349843,0.121252255,0.120491098 67 | sub-010080,1311637,611019.7643,46.58451723,437063.5413,33.32198934,263553.8545,20.09350563,3551,0.270730393,3382,0.25784573,0.528576123,0.067777899,0.109024067,0.53574274,0.527432514,0.326157313,0.362142879,0.336526036,0.322878967,0.121451286,0.12068888 68 | sub-010081,1436303,629201.3861,43.80700912,496063.9942,34.53755887,311037.6809,21.65543628,3551,0.247231956,3382,0.235465636,0.482697592,0.061895018,0.099561165,0.489242172,0.481653244,0.297848017,0.330710164,0.307316771,0.294854219,0.110909745,0.110213513 69 | sub-010083,1285412,595818.0263,46.35229999,420021.7592,32.67604155,269571.9,20.971634,3551,0.276253839,3382,0.263106304,0.539360143,0.069160705,0.111248378,0.546672973,0.538193202,0.332811581,0.369531325,0.343391846,0.32946635,0.123929137,0.123151176 70 | sub-010084,1392399,582376.0575,41.82537171,453014.2067,32.53479834,357008.9511,25.63984541,3551,0.255027474,3382,0.242890149,0.497917623,0.063846642,0.102700447,0.504668561,0.496840345,0.30723952,0.341137849,0.317006835,0.304151324,0.114406862,0.113688677 71 | sub-010085,1435183,620079.2885,43.2055904,502341.781,35.00193223,312761.7733,21.79246642,3551,0.247424893,3382,0.235649391,0.483074284,0.06194332,0.099638861,0.489623971,0.482029121,0.298080454,0.330968246,0.307556597,0.29508432,0.110996298,0.110299523 72 | sub-010086,1074481,479863.3146,44.6600093,372000.192,34.6213839,222617.1219,20.71857222,3551,0.330485137,3382,0.314756613,0.645241749,0.082737619,0.133087509,0.653990159,0.643845726,0.398145709,0.442073894,0.410802983,0.394143777,0.148257624,0.147326942 73 | sub-010087,1195824,520437.1696,43.52121797,419593.6775,35.08824689,255792.9319,21.39051665,3551,0.296950053,3382,0.282817538,0.579767591,0.074342044,0.119582815,0.58762828,0.578513226,0.357744952,0.397215644,0.369117863,0.354149106,0.133213583,0.13237734 74 | sub-010088,1446671,618785.2284,42.77304435,513958.8873,35.52700561,313926.37,21.6999145,3551,0.245460094,3382,0.233778102,0.479238196,0.061451429,0.09884763,0.485735872,0.478201333,0.2957134,0.32834003,0.305114293,0.292741059,0.110114878,0.109423635 75 | sub-010089,1347976,503148.4013,37.32621362,498635.3675,36.99141286,346192.0026,25.68235656,3551,0.26343199,3382,0.250894675,0.514326665,0.065950729,0.106084975,0.521300082,0.513213885,0.317364701,0.352380161,0.327453901,0.314174733,0.118177178,0.117435325 76 | sub-010090,1246298,552728.9589,44.34966267,426802.4521,34.24561799,266766.7195,21.40472981,3551,0.28492383,3382,0.271363671,0.556287501,0.071331255,0.114739813,0.563829838,0.555083937,0.343256589,0.381128751,0.354168907,0.339806371,0.127818547,0.127016171 77 | sub-010091,1517474,642870.2426,42.36449801,530939.8978,34.98840164,343663.9913,22.64710903,3551,0.234007304,3382,0.222870375,0.45687768,0.0585842,0.094235552,0.463072184,0.455889195,0.281915868,0.31302019,0.29087813,0.279082212,0.104977087,0.104318097 78 | sub-010092,1386593,611768.4554,44.12026135,512182.065,36.93816895,262642.624,18.94158012,3551,0.256095336,3382,0.243907188,0.500002524,0.064113983,0.103130479,0.50678173,0.498920736,0.308526006,0.342566276,0.318334219,0.30542488,0.114885911,0.114164719 79 | sub-010093,1586060,610511.3959,38.49232664,491120.8126,30.96483188,484428.0827,30.54285984,3551,0.223888125,3382,0.213232791,0.437120916,0.056050843,0.090160524,0.443047552,0.436175176,0.269724979,0.299484257,0.278299686,0.267013858,0.100437562,0.099807069 80 | sub-010094,1272603,560349.3258,44.03174641,434028.6759,34.10558327,278225.1849,21.86268498,3551,0.279034389,3382,0.26575452,0.544788909,0.069856821,0.112368115,0.552175345,0.543610223,0.336161395,0.373250731,0.346848153,0.332782494,0.125176508,0.124390717 81 | sub-010100,1470331,658649.0558,44.7959715,514439.1945,34.98798532,297242.9913,20.2160596,3551,0.241510245,3382,0.230016234,0.471526479,0.060462576,0.097257012,0.477919598,0.470506301,0.290954894,0.323056509,0.300204512,0.288030382,0.108342951,0.107662832 82 | sub-010104,1259905,553442.1941,43.92729564,420536.3936,33.37842088,285926.4413,22.69428578,3551,0.281846647,3382,0.268432937,0.550279585,0.070560876,0.113500621,0.557740465,0.549089019,0.33954941,0.377012553,0.350343875,0.336136455,0.126438104,0.125644394 83 | sub-010110,1475730,697317.4544,47.2523737,484852.3381,32.85508448,293560.5827,19.89256725,3551,0.200703791,3382,0.191151851,0.391855642,0.050246598,0.080824112,0.397168556,0.391007837,0.241794092,0.268471701,0.249480861,0.239363716,0.09003693,0.089471727 84 | sub-010126,1371553,584118.3623,42.58809994,468840.9418,34.18321725,318594.0303,23.22870719,3551,0.258903593,3382,0.246581795,0.505485388,0.064817036,0.104261374,0.512338933,0.504391737,0.311909201,0.346322745,0.321824968,0.308774069,0.116145712,0.115416612 85 | sub-010134,1324276,560532.2558,42.32744955,439879.9396,33.21663608,323863.741,24.45590957,3551,0.268146519,3382,0.255384829,0.523531348,0.067131021,0.107983532,0.530629567,0.522398654,0.323044441,0.358686558,0.333314203,0.319797384,0.120292145,0.119537015 86 | sub-010136,1464072,618040.8127,42.21382642,505806.5934,34.54793162,340224.279,23.23822046,3551,0.242542716,3382,0.230999568,0.473542285,0.060721057,0.097672792,0.479962734,0.472517745,0.292198744,0.324437596,0.301487905,0.28926173,0.108806124,0.108123098 87 | sub-010137,1387734,671709.024,48.40329804,452911.6836,32.63677936,263113.1882,18.9599151,3551,0.255884773,3382,0.243706647,0.49959142,0.064061268,0.103045685,0.506365053,0.498510521,0.308272335,0.342284617,0.318072484,0.305173758,0.114791451,0.114070852 88 | sub-010138,1469272,559033.9582,38.04836396,527158.7966,35.87891123,383079.1426,26.07271782,3551,0.241684317,3382,0.230182022,0.471866339,0.060506155,0.097327112,0.478264065,0.470845425,0.291164604,0.323289357,0.300420889,0.288237985,0.108421041,0.107740432 89 | sub-010141,1516995,644162.3358,42.46304937,512821.9343,33.80511698,360010.2881,23.73180453,3551,0.234081193,3382,0.222940748,0.457021941,0.058602698,0.094265307,0.463218402,0.456033144,0.282004885,0.313119028,0.290969977,0.279170333,0.105010234,0.104351036 90 | sub-010142,1468277,633778.3355,43.16476629,518743.0637,35.33005446,315754.8788,21.50513008,3551,0.241848098,3382,0.230338008,0.472186107,0.060547158,0.097393067,0.478588168,0.471164501,0.291361916,0.323508439,0.300624473,0.288433313,0.108494514,0.107813444 91 | sub-010146,1267951,565060.2411,44.56483264,465059.261,36.67801524,237831.8316,18.75717844,3551,0.280058141,3382,0.26672955,0.546787691,0.07011312,0.112780383,0.554201227,0.54560468,0.337394742,0.374620155,0.348120708,0.334003443,0.12563577,0.124847096 92 | sub-010148,1629382,692382.3713,42.49355715,598759.2112,36.74762647,338240.7081,20.75883422,3551,0.217935389,3382,0.207563358,0.425498747,0.054560563,0.087763336,0.431267806,0.424578153,0.262553533,0.291521571,0.270900255,0.259914495,0.097767129,0.097153399 93 | sub-010150,1519613,670796.2187,44.14256911,509420.4646,33.52303939,339396.5809,22.33440889,3551,0.233677917,3382,0.222556664,0.456234581,0.058501737,0.094102906,0.462420366,0.455247487,0.281519045,0.312579584,0.290468692,0.278689377,0.104829322,0.104171259 94 | sub-010152,1251054,548828.095,43.86925705,440408.3667,35.20298618,261817.7971,20.92777747,3551,0.283840666,3382,0.270332056,0.554172722,0.071060082,0.114303619,0.561686386,0.552973733,0.341951666,0.379679854,0.3528225,0.338514565,0.127332633,0.126533307 95 | sub-010155,1384862,651633.704,47.05405333,442581.928,31.95855818,290646.2707,20.98738147,3551,0.256415441,3382,0.244212059,0.500627499,0.064194122,0.103259386,0.507415179,0.499544359,0.308911646,0.342994464,0.318732119,0.305806644,0.115029512,0.114307418 96 | sub-010157,1315491,554280.3766,42.13486649,434294.5142,33.01387194,326916.5871,24.85129789,3551,0.269937233,3382,0.257090318,0.527027551,0.06757933,0.108704659,0.534173172,0.525887292,0.325201769,0.361081908,0.335540114,0.321933027,0.121095469,0.120335297 97 | sub-010162,1745204,745791.5234,42.73377344,608546.409,34.86964326,390865.938,22.39657587,3551,0.203471915,3382,0.193788233,0.397260148,0.050939604,0.081938845,0.402646338,0.39640065,0.245128936,0.272174485,0.252921721,0.242665041,0.091278727,0.090705728 98 | sub-010163,1298945,562759.9732,43.32438812,444659.3551,34.23234664,291525.7681,22.44327266,3551,0.273375701,3382,0.260365142,0.533740844,0.068440157,0.110089342,0.540977486,0.53258606,0.329344199,0.36568138,0.339814234,0.32603382,0.122637987,0.121868131 99 | sub-010164,1225619,534115.5875,43.57925159,401918.5649,32.79310821,289584.4431,23.6276072,3551,0.289731148,3382,0.275942197,0.565673345,0.072534776,0.116675737,0.573342939,0.564449474,0.349048114,0.387559266,0.360144547,0.345539682,0.129975139,0.129159225 100 | sub-010165,1476921,659439.7318,44.64962796,471538.3592,31.9271213,345942.7168,23.42323772,3551,0.24043263,3382,0.228989905,0.469422535,0.060192793,0.096823053,0.475787127,0.468406909,0.289656657,0.321615036,0.298865004,0.286745195,0.107859527,0.107182442 101 | sub-010166,1542059,672769.8249,43.62802103,511969.7957,33.20040256,357318.9466,23.17154834,3551,0.230276533,3382,0.21931716,0.449593693,0.057650194,0.092733157,0.455689439,0.448620967,0.277421292,0.308029719,0.286240669,0.274632812,0.10330344,0.102654957 102 | sub-010168,1351915,576256.709,42.62521749,439382.9426,32.50078168,336275.5125,24.87401297,3551,0.262664443,3382,0.250163657,0.512828099,0.065758572,0.105775881,0.519781199,0.511718562,0.316440013,0.35135345,0.326499817,0.31325934,0.117832852,0.11709316 103 | sub-010169,1379656,604553.5069,43.81914817,480284.5005,34.81190242,294818.2408,21.3689674,3551,0.257383,3382,0.24513357,0.502516569,0.064436352,0.103649026,0.509329862,0.501429342,0.310077295,0.344288721,0.319934824,0.306960576,0.115463565,0.114738746 104 | sub-010170,1380197,603710.6539,43.74090466,474162.7561,34.35471575,302323.1465,21.90434746,3551,0.257282113,3382,0.245037484,0.502319596,0.064411095,0.103608398,0.509130218,0.501232795,0.309955753,0.344153769,0.319809419,0.306840255,0.115418306,0.114693772 105 | sub-010176,1505692,672758.8478,44.6810402,510771.1053,33.92268175,322161.9892,21.39627422,3551,0.235838405,3382,0.22461433,0.460452735,0.05904262,0.094972943,0.466695712,0.459456516,0.284121852,0.315469565,0.293154244,0.281266023,0.10579853,0.105134383 106 | sub-010183,1496784,616885.0683,41.21403411,492266.3108,32.8882665,387632.3627,25.89768214,3551,0.23724198,3382,0.225951106,0.463193086,0.059394007,0.095538167,0.469473217,0.462190937,0.285812783,0.317347059,0.29489893,0.282939957,0.106428182,0.105760083 107 | sub-010191,1369827,567915.5221,41.45892307,473852.1767,34.59211833,328059.4598,23.94897019,3551,0.259229815,3382,0.246892491,0.506122306,0.064898706,0.104392744,0.512984486,0.505027277,0.31230221,0.346759116,0.322230471,0.309163128,0.116292057,0.115562038 108 | sub-010192,1532772,651788.9993,42.52354553,557929.5092,36.4000327,323053.752,21.07643877,3551,0.231671769,3382,0.220645993,0.452317762,0.057999494,0.093295024,0.458450441,0.451339142,0.279102176,0.309896058,0.287974989,0.276296801,0.103929352,0.103276939 109 | sub-010193,1401328,590077.2715,42.10843368,481469.5692,34.35809241,329780.9644,23.53346,3551,0.253402487,3382,0.241342498,0.494744985,0.063439823,0.102046059,0.501452908,0.493674572,0.305281847,0.338964183,0.314986927,0.302213329,0.113677883,0.112964274 110 | sub-010194,1620615,754779.0466,46.57361844,546426.5362,33.71723304,319409.5265,19.70915526,3551,0.219114349,3382,0.208686209,0.427800557,0.054855718,0.088238107,0.433600824,0.426874983,0.263973862,0.293098608,0.272365738,0.261320548,0.098296017,0.097678968 111 | sub-010195,1806664,773252.1258,42.79999634,616599.0052,34.12914661,416812.6773,23.07084645,3551,0.196550106,3382,0.187195848,0.383745954,0.049206715,0.079151408,0.388948914,0.382915694,0.236790017,0.262915517,0.244317704,0.23440994,0.088173562,0.087620056 112 | sub-010196,1452372,678222.4298,46.69756989,496672.9527,34.19736491,277476.4617,19.10505447,3551,0.244496589,3382,0.232860452,0.477357041,0.061210213,0.098459623,0.483829212,0.476324248,0.294552635,0.327051196,0.303916627,0.291591961,0.109682643,0.108994114 113 | sub-010197,1512530,649219.2613,42.92273617,498408.9884,32.9520068,364901.5488,24.12524372,3551,0.234772203,3382,0.223598871,0.458371074,0.058775694,0.094543579,0.464585826,0.457379358,0.282837365,0.314043358,0.291828922,0.279994446,0.105320225,0.104659081 114 | sub-010199,1570996,632001.5323,40.22935337,572277.9812,36.42771727,366716.5428,23.34293294,3551,0.226034949,3382,0.215277442,0.44131239,0.056588304,0.091025057,0.447295856,0.440357582,0.272311324,0.302355958,0.280968252,0.269574206,0.10140064,0.100764101 115 | sub-010200,1416386,645112.5108,45.54637724,478439.7985,33.77891327,292834.6374,20.67477633,3551,0.250708493,3382,0.238776718,0.489485211,0.062765376,0.100961179,0.49612182,0.488426178,0.302036309,0.335360558,0.311638212,0.299000414,0.112469341,0.111763319 116 | sub-010201,1740041,661637.8995,38.02427066,538967.0099,30.97438566,539435.8506,31.00132989,3551,0.204075651,3382,0.194363236,0.398438887,0.05109075,0.082181972,0.403841059,0.397576839,0.245856276,0.272982073,0.253672184,0.24338507,0.091549567,0.090974868 117 | sub-010202,1474129,665339.9127,45.13444296,507126.9782,34.40180461,301661.8878,20.46373742,3551,0.240888009,3382,0.229423612,0.470311621,0.060306798,0.097006436,0.476688268,0.469294071,0.290205267,0.322224174,0.299431054,0.28728829,0.108063813,0.107385446 118 | sub-010203,1484098,673863.4697,45.40559112,506179.9309,34.10690742,304054.9628,20.48752594,3551,0.239269913,3382,0.227882525,0.467152439,0.059901705,0.096354823,0.473486252,0.466141724,0.288255897,0.320059727,0.297419712,0.285358514,0.107337925,0.106664115 119 | sub-010204,1318134,614887.826,46.64835487,437673.4538,33.20401824,265572.7288,20.14762754,3551,0.269395979,3382,0.256574825,0.525970804,0.067443826,0.108486694,0.533102097,0.524832832,0.324549704,0.3603579,0.33486732,0.321287517,0.12085266,0.120094012 120 | sub-010207,1449079,635567.9817,43.86013335,496827.4592,34.28574006,316683.6651,21.85413391,3551,0.245052202,3382,0.233389622,0.478441824,0.061349312,0.098683371,0.484928703,0.477406684,0.295222,0.327794413,0.304607271,0.292254598,0.109931895,0.109241801 121 | sub-010210,1388233,627759.3858,45.22003048,446352.2125,32.15254301,314121.8366,22.62745783,3551,0.255792796,3382,0.243619047,0.499411842,0.064038241,0.103008645,0.50618304,0.498331332,0.308161526,0.342161582,0.317958153,0.305064063,0.11475019,0.114029849 122 | sub-010213,1657711,724303.8104,43.69300864,570793.0271,34.43260177,362614.8006,21.87442809,3551,0.214211042,3382,0.204016261,0.418227303,0.053628166,0.086263528,0.423897772,0.41732244,0.258066696,0.286539692,0.266270779,0.255472757,0.096096364,0.095493123 123 | sub-010214,1462241,638031.6141,43.63382056,500437.3441,34.22399893,323772.2774,22.14219663,3551,0.242846425,3382,0.231288823,0.474135249,0.060797092,0.097795097,0.480563737,0.473109426,0.292564632,0.324843853,0.301865424,0.28962394,0.10894237,0.108258488 124 | sub-010215,1403747,636905.5277,45.37181755,460065.4563,32.77410077,306776.0057,21.85408095,3551,0.252965812,3382,0.240926606,0.493892418,0.0633305,0.101870209,0.500588781,0.492823849,0.304755772,0.338380064,0.314444127,0.301692541,0.113481988,0.112769609 125 | sub-010216,1467185,622955.6554,42.45924375,512207.3854,34.91089299,332021.7036,22.62984584,3551,0.242028101,3382,0.230509445,0.472537546,0.060592223,0.097465555,0.478944373,0.47151518,0.291578772,0.32374922,0.300848223,0.288647989,0.108575265,0.107893688 126 | sub-010218,1453568,665708.0904,45.79820761,479001.7694,32.95351641,308857.7451,21.2482488,3551,0.244295417,3382,0.232668853,0.47696427,0.061159849,0.09837861,0.483431116,0.475932327,0.294310277,0.326782098,0.303666564,0.291352039,0.109592396,0.108904434 127 | sub-010219,1272927,546729.5879,42.95058459,452717.7878,35.56510214,273479.583,21.48431002,3551,0.278963366,3382,0.265686878,0.544650243,0.06983904,0.112339514,0.552034799,0.543471857,0.336075832,0.373155727,0.346759869,0.33269779,0.125144647,0.124359056 128 | sub-010220,1420075,652924.422,45.97816467,470285.9396,33.116979,296865.2054,20.90489625,3551,0.250057215,3382,0.238156435,0.488213651,0.062602327,0.100698907,0.494833019,0.487157368,0.301251694,0.334489376,0.310828653,0.298223685,0.112177174,0.111472986 129 | sub-010222,1271407,556853.9401,43.798244,429292.607,33.76515994,285260.3092,22.43658476,3551,0.279296873,3382,0.266004513,0.545301387,0.069922535,0.112473818,0.55269477,0.544121591,0.336477619,0.373601844,0.34717443,0.333095539,0.125294261,0.12450773 130 | sub-010223,1061116,479860.5518,45.22225203,348747.5724,32.86611195,232507.7372,21.91162297,3551,0.334647673,3382,0.318721045,0.653368717,0.083779719,0.134763777,0.662227315,0.651955111,0.403160446,0.447641917,0.415977141,0.399108109,0.150124963,0.149182559 131 | sub-010224,1430004,668074.671,46.71837778,472873.3723,33.06797549,289056.3175,20.21367195,3551,0.248320984,3382,0.236502835,0.484823819,0.062167658,0.09999972,0.491397227,0.483774871,0.299160002,0.332166903,0.308670465,0.296153018,0.11139829,0.110698991 132 | sub-010225,1424829,516026.3059,36.21671835,430442.4008,30.21010948,478359.9183,33.57314585,3551,0.249222889,3382,0.237361817,0.486584706,0.062393452,0.100362921,0.493181989,0.485531948,0.300246556,0.333373338,0.309791561,0.29722865,0.11180289,0.111101051 133 | sub-010226,1551852,719613.9645,46.37130116,533640.0284,34.38730165,298598.317,19.24141716,3551,0.228823367,3382,0.217933153,0.446756521,0.057286391,0.092147963,0.452813799,0.445789934,0.275670618,0.30608589,0.28443434,0.272899735,0.102651542,0.10200715 134 | sub-010227,1496393,600831.1389,40.15196135,501770.1057,33.5319736,393791.7438,26.31606428,3551,0.23730397,3382,0.226010146,0.463314116,0.059409527,0.095563131,0.469595888,0.462311706,0.285887464,0.31742998,0.294975986,0.283013887,0.106455991,0.105787718 135 | sub-010228,1525246,672362.5393,44.08223587,508991.3711,33.37110021,343892.0819,22.54666342,3551,0.232814903,3382,0.221734723,0.454549627,0.05828568,0.093755368,0.460712567,0.453566179,0.280479346,0.311425173,0.28939594,0.277660128,0.104442169,0.103786537 136 | sub-010229,1376468,600301.7433,43.61174712,481109.8371,34.95248979,295056.8079,21.43579131,3551,0.257979118,3382,0.245701317,0.503680434,0.064585592,0.103889084,0.510509507,0.502590689,0.310795456,0.345086119,0.320675817,0.307671519,0.115730987,0.11500449 137 | sub-010231,1617843,682711.9273,42.19889862,574899.3349,35.53492736,360232.2801,22.26620754,3551,0.219489777,3382,0.20904377,0.428533547,0.054949708,0.088389294,0.434343753,0.427606387,0.264426153,0.293600801,0.272832407,0.261768293,0.098464437,0.09784633 138 | sub-010232,1548065,659045.4748,42.57220949,549937.2515,35.52417059,339082.5979,21.90364086,3551,0.229383133,3382,0.218466279,0.447849412,0.057426529,0.092373382,0.453921508,0.44688046,0.276344986,0.306834661,0.285130146,0.273567324,0.102902656,0.102256688 139 | sub-010233,1580312,674925.5988,42.7083765,549402.9195,34.76547159,355984.1967,22.52619715,3551,0.224702464,3382,0.214008373,0.438710837,0.056254714,0.090488461,0.444659029,0.437761657,0.270706038,0.300573558,0.279311933,0.267985056,0.100802879,0.100170093 140 | sub-010234,1110771,484824.5615,43.64757106,418658.5594,37.6908075,207287.901,18.66162341,3551,0.319687856,3382,0.304473199,0.624161056,0.080034499,0.128739407,0.632623646,0.622810642,0.385137891,0.427630898,0.397381639,0.381266706,0.143413899,0.142513623 141 | sub-010235,1371461,597495.2247,43.56632997,481594.1297,35.11540829,292371.6391,21.31826126,3551,0.258920961,3382,0.246598336,0.505519297,0.064821384,0.104268368,0.512373301,0.504425572,0.311930124,0.346345977,0.321846556,0.308794782,0.116153503,0.115424354 142 | sub-010236,1478054,646056.2424,43.70992145,501506.2146,33.93016863,330492.1078,22.35994813,3551,0.240248327,3382,0.228814373,0.4690627,0.060146652,0.096748833,0.475422414,0.468047852,0.289434621,0.321368502,0.298635909,0.286525391,0.107776847,0.107100282 143 | sub-010237,1589359,677694.3878,42.63947842,563700.7158,35.46717361,347964.0008,21.89335454,3551,0.223423405,3382,0.212790188,0.436213593,0.055934499,0.089973379,0.442127927,0.435269816,0.269165116,0.298862623,0.277722025,0.266459623,0.100229086,0.099599902 144 | sub-010238,1413842,681370.5631,48.19283647,453526.6268,32.07760321,278944.4296,19.7295334,3551,0.251159606,3382,0.239206361,0.490365967,0.062878313,0.101142843,0.497014518,0.489305028,0.302579779,0.33596399,0.312198959,0.299538421,0.112671713,0.11196442 145 | sub-010239,1448286,610625.0983,42.16191403,539456.0462,37.24789484,298204.7923,20.59018677,3551,0.245186379,3382,0.233517413,0.478703792,0.061382904,0.098737404,0.485194223,0.477668085,0.295383647,0.327973895,0.304774057,0.29241462,0.109992087,0.109301616 146 | sub-010240,1618985,697645.847,43.09155718,533586.6187,32.95809527,387752.7516,23.95036097,3551,0.219334954,3382,0.208896315,0.428231268,0.054910947,0.088326946,0.434037375,0.427304762,0.264239632,0.2933937,0.272639957,0.261583647,0.098394982,0.097777311 147 | sub-010241,1470863,614838.7938,41.80122784,488906.0164,33.23939867,367118.0621,24.95936482,3551,0.241422893,3382,0.229933039,0.471355932,0.060440707,0.097221835,0.477746738,0.470336122,0.290849658,0.322939662,0.30009593,0.287926204,0.108303765,0.107623892 148 | sub-010242,1374507,566658.5024,41.22630895,479138.9421,34.85896704,328709.2974,23.91470523,3551,0.258347175,3382,0.246051857,0.504399032,0.064677735,0.104037302,0.511237847,0.503307731,0.311238866,0.345578451,0.321133323,0.308110472,0.115896099,0.115168566 149 | sub-010243,1640348,659615.3234,40.21191378,548753.5238,33.45348206,431978.952,26.33459193,3551,0.216478455,3382,0.206175763,0.422654217,0.054195817,0.087176623,0.428384709,0.421739777,0.260798318,0.2895727,0.269089242,0.258176923,0.097113539,0.096503913 150 | sub-010244,1177176,537915.199,45.69539295,390481.3967,33.17102937,248779.5142,21.13358701,3551,0.301654128,3382,0.287297736,0.588951864,0.075519718,0.121477162,0.596937077,0.587677628,0.363412098,0.403508057,0.374965171,0.359759288,0.13532386,0.134474369 151 | sub-010245,1364868,566004.0105,41.46950551,521066.509,38.17706247,277797.3579,20.35342303,3551,0.26017168,3382,0.24778953,0.507961209,0.065134504,0.104772037,0.514848322,0.506862202,0.313436904,0.348019003,0.323401237,0.310286416,0.116714583,0.115981912 152 | sub-010246,1785620,756603.2552,42.37201954,582955.8181,32.64724959,446061.2,24.98074618,3551,0.1988665,3382,0.189402,0.388268501,0.049786629,0.080084228,0.393532779,0.387428456,0.23958065,0.266014046,0.247197052,0.237172523,0.08921271,0.088652681 153 | sub-010247,1249708,534956.1509,42.80649167,435045.414,34.81176515,279706.6601,22.38176119,3551,0.284146377,3382,0.270623218,0.554769594,0.071136618,0.11442673,0.562291351,0.553569314,0.342319966,0.380088789,0.353202508,0.338879162,0.127469777,0.12666959 154 | sub-010249,1330535,579821.5532,43.57807598,460492.3553,34.60956347,290221.4808,21.81238982,3551,0.266885125,3382,0.254183468,0.521068593,0.066815228,0.107475564,0.52813342,0.519941227,0.3215248,0.356999252,0.331746252,0.318293017,0.119726276,0.118974698 155 | sub-010250,1454909,591551.424,40.65899819,446670.8405,30.70094697,416686.7839,28.64005817,3551,0.244070248,3382,0.232454401,0.476524649,0.061103478,0.098287934,0.482985534,0.475493656,0.294039009,0.3264809,0.303386672,0.291083497,0.109491384,0.108804056 156 | sub-010252,1490714,687583.4698,46.12443901,501232.209,33.62363331,301898.0167,20.25190725,3551,0.238208,3382,0.22687115,0.46507915,0.059635852,0.095927187,0.471384853,0.464072921,0.286976576,0.318639256,0.296099721,0.284092053,0.106861544,0.106190725 157 | sub-010253,1451864,617143.4416,42.50697322,484197.6663,33.35007041,350523.2277,24.14297948,3551,0.244582137,3382,0.232941928,0.477524066,0.061231631,0.098494074,0.483998501,0.476490911,0.294655698,0.32716563,0.304022966,0.291693988,0.109721021,0.109032251 158 | sub-010254,1349342,579037.6952,42.91259704,492574.2956,36.50477755,277729.6261,20.58259701,3551,0.263165306,3382,0.250640683,0.513805988,0.065883964,0.10597758,0.520772347,0.512694335,0.317043418,0.352023431,0.327122405,0.31385668,0.118057542,0.11731644 159 | sub-010255,1284583,532705.8323,41.4691641,427867.1326,33.30786198,324010.1856,25.22298563,3551,0.276432118,3382,0.263276098,0.539708217,0.069205337,0.111320172,0.547025766,0.538540522,0.33302636,0.369769801,0.343613453,0.32967897,0.124009114,0.123230652 160 | sub-010256,1320958,579358.6193,43.85897351,432135.4873,32.71379463,309463.8937,23.42723188,3551,0.268820053,3382,0.256026308,0.524846362,0.067299642,0.108254767,0.531962409,0.523710822,0.323855868,0.359587511,0.334151426,0.320600655,0.120594296,0.11983727 161 | sub-010257,1598594,670135.8996,41.92033122,586861.0574,36.71107595,341596.7108,21.36857206,3551,0.222132699,3382,0.211560909,0.433693608,0.055611368,0.089453607,0.439573775,0.432755284,0.267610162,0.297136108,0.276117638,0.264920299,0.099650067,0.099024518 162 | sub-010260,1458168,599431.5904,41.10854102,473172.9157,32.44982167,385563.8072,26.44165879,3551,0.243524752,3382,0.231934866,0.475459618,0.060966912,0.098068261,0.481906063,0.47443093,0.293381833,0.325751217,0.302708604,0.290432927,0.109246671,0.108560879 163 | sub-010261,1377520,622281.2681,45.17402783,460769.5963,33.44921281,294469.4447,21.3767818,3551,0.257782101,3382,0.245513677,0.503295778,0.064536268,0.103809745,0.510119635,0.502206865,0.310558104,0.34482258,0.320430919,0.307436553,0.115642604,0.114916662 164 | sub-010262,1772744,842451.0823,47.52243315,600108.4129,33.85195002,330184.388,18.62561024,3551,0.20031093,3382,0.190777687,0.391088617,0.050148245,0.080665906,0.396391131,0.390242472,0.2413208,0.26794619,0.248992522,0.238895182,0.089860691,0.089296593 165 | sub-010263,1442374,630263.6122,43.69626825,485982.3098,33.69322449,326128.2729,22.61052077,3551,0.246191348,3382,0.234474554,0.480665902,0.0616345,0.099142109,0.487182936,0.47962595,0.296594365,0.329318193,0.306023264,0.293613168,0.110442923,0.109749621 166 | sub-010264,1463776,635752.3847,43.43235473,544904.0128,37.22591522,283119.7926,19.34174304,3551,0.242591763,3382,0.23104628,0.473638043,0.060733336,0.097692543,0.480059791,0.472613296,0.292257832,0.324503203,0.301548871,0.289320224,0.108828127,0.108144962 167 | sub-010265,1554372,631923.9009,40.65461169,511229.7552,32.88979441,411218.3023,26.45559121,3551,0.228452391,3382,0.217579833,0.446032224,0.057193516,0.091998569,0.452079682,0.445067204,0.275223692,0.305589653,0.283973206,0.272457301,0.102485119,0.101841773 168 | sub-010266,1348114,592350.5189,43.9392009,435994.3512,32.34105952,319769.1449,23.71974069,3551,0.263405024,3382,0.250868992,0.514274015,0.065943978,0.106074115,0.521246719,0.51316135,0.317332214,0.35234409,0.327420381,0.314142573,0.118165081,0.117423304 169 | sub-010267,1380149,589293.1697,42.69779348,482857.7965,34.98591793,307998.0133,22.31628711,3551,0.257291061,3382,0.245046006,0.502337067,0.064413335,0.103612001,0.509147925,0.501250227,0.309966533,0.344165739,0.319820541,0.306850927,0.11542232,0.114697761 170 | sub-010268,1556172,658009.6908,42.28386649,519811.4558,33.40321351,378350.4543,24.31289435,3551,0.228188144,3382,0.217328162,0.445516305,0.057127361,0.091892156,0.451556769,0.444552402,0.274905345,0.305236182,0.283644738,0.272142154,0.102366576,0.101723974 171 | sub-010269,1568877,724308.0729,46.16729501,517723.9642,32.99965289,326845.3448,20.83307645,3551,0.226340242,3382,0.215568206,0.441908448,0.056664735,0.091147999,0.447899995,0.44095235,0.27267912,0.302764334,0.281347741,0.269938306,0.101537597,0.100900198 172 | sub-010270,1339474,573748.3871,42.8338577,456790.3964,34.10222194,308935.3511,23.0639304,3551,0.265104063,3382,0.25248717,0.517591234,0.066369336,0.106758325,0.524608914,0.516471391,0.3193791,0.354616812,0.329532339,0.316168884,0.11892728,0.118180719 173 | sub-010271,1458952,611930.0991,41.94312761,497441.9139,34.09583824,349580.5786,23.9610747,3551,0.243393888,3382,0.231810231,0.475204119,0.06093415,0.098015562,0.4816471,0.474175984,0.293224177,0.325576167,0.302545937,0.290276856,0.109187965,0.108502542 174 | sub-010272,1485599,616464.8208,41.49604441,506051.0771,34.06377341,363083.1794,24.44018738,3551,0.239028163,3382,0.22765228,0.466680443,0.059841182,0.096257469,0.473007857,0.46567075,0.287964653,0.319736349,0.297119209,0.285070197,0.107229474,0.106556345 175 | sub-010273,1560842,719205.7059,46.07805953,528832.2844,33.88121824,312804.3832,20.04074616,3551,0.227505411,3382,0.216677921,0.444183332,0.056956438,0.091617217,0.450205722,0.443222312,0.274082835,0.304322923,0.282796081,0.271327911,0.102060298,0.101419618 176 | sub-010274,1203925,511022.7301,42.44639243,388678.0734,32.28424307,304224.0163,25.26934953,3551,0.294951928,3382,0.280914509,0.575866437,0.073841809,0.118778163,0.583674232,0.574620512,0.355337749,0.394542849,0.366634134,0.351766098,0.132317212,0.131486596 177 | sub-010275,1352598,608481.3523,44.98611947,432472.2947,31.97345365,311644.6186,23.04044651,3551,0.262531809,3382,0.250037336,0.512569145,0.065725367,0.105722469,0.519518734,0.511460168,0.316280225,0.351176033,0.326334949,0.313101158,0.117773352,0.117034034 178 | sub-010276,1206402,520655.0704,43.15767633,417507.1532,34.60763105,268239.5043,22.23467006,3551,0.294346329,3382,0.280337732,0.574684061,0.073690196,0.118534286,0.582475825,0.573440694,0.354608165,0.393732769,0.365881356,0.351043848,0.132045537,0.131216626 179 | sub-010277,1440717,598926.9706,41.57145162,526024.2671,36.51128341,315765.5335,21.91724909,3551,0.246474498,3382,0.234744228,0.481218727,0.061705387,0.099256134,0.487743256,0.480177578,0.296935484,0.32969695,0.306375228,0.293950859,0.110569945,0.109875847 180 | sub-010278,1567086,620469.5245,39.59384006,535262.1523,34.15652697,411354.4394,26.24964038,3551,0.226598923,3382,0.215814576,0.442413499,0.056729497,0.091252171,0.448411893,0.441456308,0.272990761,0.303110359,0.281669289,0.270246815,0.101653642,0.101015515 181 | sub-010279,1407418,589758.0518,41.9035462,510934.1981,36.30294611,306725.579,21.79349554,3551,0.252305996,3382,0.240298191,0.492604187,0.063165314,0.101604498,0.499283084,0.491538406,0.30396087,0.33749746,0.313623955,0.30090563,0.11318599,0.112475469 182 | sub-010280,1229885,523742.0969,42.58463978,428856.8992,34.86967474,277285.6127,22.54565368,3551,0.288726182,3382,0.27498506,0.563711241,0.072283181,0.116271033,0.571354232,0.562491615,0.347837399,0.386214971,0.358895344,0.344341138,0.129524305,0.128711221 183 | sub-010281,1264613,515253.7611,40.74398737,431829.7971,34.14718947,317530.0945,25.10887477,3551,0.280797366,3382,0.267433594,0.548230961,0.070298186,0.113078072,0.555664065,0.547044827,0.338285309,0.375608981,0.349039588,0.33488506,0.125967391,0.125176635 184 | sub-010282,1380515,559282.0578,40.51256653,499298.5553,36.16755742,321934.3042,23.31987006,3551,0.257222848,3382,0.24498104,0.502203888,0.064396258,0.103584532,0.509012941,0.501117337,0.309884355,0.344074494,0.319735751,0.306769575,0.11539172,0.114667352 185 | sub-010283,1229084,517724.6712,42.12280619,421479.1118,34.29213233,289880.3345,23.58507104,3551,0.288914346,3382,0.275164269,0.564078615,0.072330288,0.116346808,0.571726587,0.562858194,0.348064087,0.386466669,0.359129238,0.344565546,0.129608717,0.128795103 186 | sub-010284,1364134,628086.5142,46.04287513,444549.2,32.58838208,291498.2351,21.36873907,3551,0.26031167,3382,0.247922858,0.508234528,0.065169551,0.104828411,0.515125347,0.50713493,0.313605555,0.348206261,0.32357525,0.310453372,0.116777384,0.116044318 187 | sub-010285,1255884,525488.8988,41.84215252,443814.9115,35.3388459,286580.1298,22.8189968,3551,0.282749044,3382,0.269292387,0.552041431,0.070786792,0.113864019,0.559526198,0.550847053,0.340636556,0.378219644,0.351465581,0.337212673,0.126842925,0.126046673 188 | sub-010286,1533468,631580.484,41.18641432,553030.9466,36.06406828,348856.1849,22.74949232,3551,0.231566619,3382,0.220545848,0.452112467,0.057973169,0.09325268,0.458242363,0.451134292,0.278975499,0.309755404,0.287844285,0.276171397,0.103882181,0.103230064 189 | sub-010287,1448139,619722.7982,42.79442776,506970.7248,35.00842977,321445.727,22.19715974,3551,0.245211268,3382,0.233541117,0.478752385,0.061389135,0.098747427,0.485243475,0.477716573,0.295413631,0.328007187,0.304804995,0.292444303,0.110003252,0.109312711 190 | sub-010288,1551999,682153.3455,43.9532078,563529.9882,36.30994531,306315.4646,19.73683389,3551,0.228801694,3382,0.217912512,0.446714205,0.057280965,0.092139235,0.45277091,0.44574771,0.275644508,0.306056898,0.2844074,0.272873887,0.102641819,0.101997488 191 | sub-010289,1555410,636072.8347,40.89422305,550855.6689,35.41546402,368481.6571,23.69032327,3551,0.228299934,3382,0.217434631,0.445734565,0.057155348,0.091937174,0.451777988,0.444770189,0.275040022,0.305385718,0.283783697,0.272275477,0.102416726,0.101773809 192 | sub-010290,1421149,617989.5611,43.48520536,500854.7082,35.24294132,302304.9091,21.27186587,3551,0.24986824,3382,0.237976454,0.487844695,0.062555017,0.100622806,0.494459061,0.486789211,0.301024031,0.334236593,0.310593752,0.29799831,0.112092398,0.111388742 193 | sub-010291,1284253,537117.7774,41.82336171,474110.7869,36.91724193,273024.3784,21.25939191,3551,0.27650315,3382,0.263343749,0.539846899,0.06922312,0.111348776,0.547166329,0.538678905,0.333111934,0.369864816,0.343701747,0.329763684,0.124040979,0.123262317 194 | sub-010292,1523800,688530.6401,45.18510567,500610.5204,32.85277073,334658.619,21.96210914,3551,0.233035831,3382,0.221945137,0.454980969,0.05834099,0.093844337,0.461149757,0.453996587,0.280745505,0.311720698,0.28967056,0.277923612,0.104541278,0.103885024 195 | sub-010293,1469349,680332.0661,46.30159793,507633.7891,34.54821074,281383.0264,19.15018327,3551,0.241671652,3382,0.23016996,0.471841611,0.060502985,0.097322011,0.478239002,0.470820751,0.291149346,0.323272415,0.300405145,0.28822288,0.108415359,0.107734786 196 | sub-010294,1613820,686041.774,42.51042706,556927.2155,34.50987195,370851.4296,22.97972696,3551,0.220036931,3382,0.209564883,0.429601814,0.055086689,0.088609634,0.435426504,0.428672343,0.265085326,0.294332701,0.273512535,0.26242084,0.098709893,0.098090246 197 | sub-010295,1610270,706492.2876,43.87415077,552680.9362,34.32225255,351096.5025,21.80357968,3551,0.220522024,3382,0.21002689,0.430548914,0.055208133,0.088804983,0.436386445,0.429617393,0.265669732,0.294981587,0.274115521,0.262999373,0.098927509,0.098306495 198 | sub-010296,1628482,683731.7235,41.98583242,579511.5648,35.58599756,365239.1573,22.42819738,3551,0.218055834,3382,0.207678071,0.425733904,0.054590717,0.087811839,0.431506151,0.424812801,0.262698636,0.291682684,0.271049972,0.26005814,0.097821161,0.097207092 199 | sub-010297,1474190,648733.7657,44.00611629,498812.4732,33.83637612,326643.9912,22.1575232,3551,0.240878042,3382,0.229414119,0.47029216,0.060304303,0.097002422,0.476668543,0.469274653,0.290193259,0.322210841,0.299418664,0.287276403,0.108059341,0.107381002 200 | sub-010298,1590620,675829.7563,42.4884483,533023.5865,33.51042905,381766.5148,24.00111371,3551,0.223246281,3382,0.212621494,0.435867775,0.055890156,0.089902051,0.44177742,0.434924746,0.26895173,0.298625693,0.277501855,0.266248381,0.100149627,0.099520942 201 | sub-010299,1623242,600705.7478,37.00654294,506712.9917,31.21610898,515823.4266,31.77735831,3551,0.218759741,3382,0.208348478,0.427108219,0.054766942,0.088095306,0.432899099,0.426184143,0.263546655,0.292624267,0.27192495,0.260897636,0.098136938,0.097520887 202 | sub-010300,1650824,621795.1224,37.66574283,517129.1605,31.32551747,511898.7792,31.00868289,3551,0.215104699,3382,0.204867387,0.419972087,0.053851895,0.086623407,0.425666213,0.41906345,0.259143313,0.287735095,0.267381623,0.256538553,0.096497264,0.095891506 203 | sub-010301,1448323,630277.4335,43.51773972,470605.1308,32.49310622,347439.8763,23.98911543,3551,0.245180115,3382,0.233511447,0.478691563,0.061381336,0.098734882,0.485181828,0.477655882,0.2953761,0.327965516,0.304766271,0.29240715,0.109989277,0.109298824 204 | sub-010302,1455483,607709.2409,41.75309783,457476.9805,31.43128298,390295.9839,26.81556459,3551,0.243973994,3382,0.232362728,0.476336721,0.061079381,0.098249172,0.482795058,0.475306135,0.293923048,0.326352146,0.303267025,0.290968702,0.109448204,0.108761147 205 | sub-010303,1531825,627106.733,40.93853625,537971.5048,35.11964518,366747.5805,23.94187199,3551,0.231814992,3382,0.2207824,0.452597392,0.05803535,0.0933527,0.458733863,0.451618168,0.279274721,0.310087641,0.28815302,0.276467612,0.103993602,0.103340786 206 | sub-010304,1560220,708985.1581,45.44135815,520983.0098,33.39163771,330251.5435,21.16698565,3551,0.227596108,3382,0.216764302,0.444360411,0.056979144,0.091653741,0.450385202,0.443399008,0.274192101,0.304444245,0.282908821,0.27143608,0.102100986,0.101460051 207 | sub-010305,1577826,680307.4918,43.11676267,535111.966,33.9145106,362405.586,22.96866613,3551,0.225056502,3382,0.214345562,0.439402063,0.056343348,0.090631033,0.445359628,0.438451388,0.271132558,0.301047137,0.279752013,0.26840729,0.100961703,0.10032792 208 | sub-010306,1558080,660743.0234,42.40751588,571756.1566,36.69620024,325580.6678,20.89627412,3551,0.227908708,3382,0.217062025,0.444970733,0.057057404,0.091779626,0.4510038,0.44400801,0.2745687,0.304862395,0.283297392,0.271808893,0.10224122,0.101599404 209 | sub-010307,1395233,593287.1383,42.52244165,475713.0692,34.09560046,326233.2362,23.38198969,3551,0.254509462,3382,0.24239679,0.496906252,0.063716956,0.102491842,0.503643477,0.495831162,0.306615454,0.340444929,0.31636293,0.303533532,0.114174478,0.113457752 210 | sub-010308,1474190,638380.8732,43.30383961,465892.7074,31.6033013,369916.6031,25.09287155,3551,0.240878042,3382,0.229414119,0.47029216,0.060304303,0.097002422,0.476668543,0.469274653,0.290193259,0.322210841,0.299418664,0.287276403,0.108059341,0.107381002 211 | sub-010309,1387783,658788.8816,47.47059746,450685.8861,32.47524189,278308.1447,20.05415434,3551,0.255875738,3382,0.243698042,0.499573781,0.064059006,0.103042046,0.506347174,0.49849292,0.30826145,0.342272531,0.318061253,0.305162983,0.114787398,0.114066825 212 | sub-010310,1507946,690322.6286,45.77900194,497533.4618,32.99411662,320089.7808,21.2268729,3551,0.235485886,3382,0.224278588,0.459764474,0.058954366,0.094830982,0.465998119,0.458769744,0.283697162,0.314998017,0.292716052,0.280845601,0.105640388,0.104977234 213 | sub-010311,1693654,727379.8341,42.94736907,575049.8347,33.95320619,391225.0576,23.09946763,3551,0.20966502,3382,0.199686595,0.409351615,0.05249006,0.08443283,0.414901745,0.408465956,0.252589962,0.280458701,0.260619938,0.250051073,0.094056992,0.093466552 214 | sub-010312,1510988,621012.4683,41.09976176,519432.0233,34.37697872,370543.1011,24.52323255,3551,0.235011794,3382,0.223827059,0.458838852,0.058835676,0.094640063,0.465059948,0.457846125,0.283126008,0.314363847,0.292126741,0.280280188,0.105427707,0.104765888 215 | sub-010313,1538887,656513.8188,42.66160015,519403.0526,33.75186434,362970.0366,23.58652952,3551,0.230751186,3382,0.219769223,0.450520409,0.057769024,0.092924302,0.456628719,0.449545678,0.277993121,0.308664639,0.286830677,0.275198894,0.103516373,0.102866552 216 | sub-010314,1513026,648213.497,42.84219154,534243.8473,35.30962768,330568.1708,21.84814873,3551,0.23469524,3382,0.223525571,0.45822081,0.058756426,0.094512586,0.464433526,0.45722942,0.282744645,0.313940408,0.291733255,0.279902659,0.105285699,0.104624772 217 | sub-010315,1483837,641070.6209,43.20357431,496300.9209,33.44713206,346464.8521,23.34925279,3551,0.239312,3382,0.227922609,0.467234609,0.059912241,0.096371771,0.473569536,0.466223716,0.2883066,0.320116024,0.297472027,0.285408707,0.107356805,0.106682877 218 | sub-010316,1520170,671453.1517,44.16960943,492849.0569,32.42065406,355868.2184,23.40976459,3551,0.233592296,3382,0.222475118,0.456067414,0.058480302,0.094068427,0.462250932,0.455080682,0.281415894,0.312465053,0.290362262,0.278587263,0.104790912,0.10413309 219 | sub-010317,1425496,653413.4661,45.83762186,487424.4391,34.19332212,284657.8255,19.96903713,3551,0.249106276,3382,0.237250753,0.486357029,0.062364258,0.10031596,0.492951225,0.485304764,0.300106068,0.33321735,0.309646607,0.297089574,0.111750577,0.111049066 220 | sub-010318,1696615,743981.1238,43.85091042,557889.0735,32.88247914,394745.0758,23.26662653,3551,0.209299104,3382,0.199338094,0.408637198,0.052398452,0.084285474,0.414177642,0.407753085,0.252149132,0.279969233,0.260165093,0.249614674,0.09389284,0.093303431 221 | sub-010319,1750342,791834.3428,45.23883577,565150.4376,32.28800072,393357.8959,22.47320215,3551,0.202874638,3382,0.193219382,0.39609402,0.050790074,0.08169832,0.4014644,0.395237045,0.244409378,0.271375537,0.252179288,0.241952716,0.091010785,0.090439468 222 | sub-010320,1666361,718312.3415,43.10664625,559532.8542,33.57812948,388515.8345,23.31522608,3551,0.213099082,3382,0.202957222,0.416056305,0.053349784,0.085815739,0.421697339,0.41515614,0.256727084,0.285052279,0.264888581,0.25414661,0.095597533,0.094997423 223 | sub-010321,1272256,564838.5207,44.39660891,423329.6859,33.27393904,284088.5636,22.32951258,3551,0.279110493,3382,0.265827003,0.544937497,0.069875874,0.112398763,0.552325947,0.543758489,0.336253081,0.373352533,0.346942754,0.332873258,0.125210649,0.124424644 -------------------------------------------------------------------------------- /lemon_support.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | import osl 4 | import numpy as np 5 | import mne 6 | import sails 7 | from scipy import io, ndimage, stats 8 | import matplotlib.pyplot as plt 9 | 10 | from glm_config import cfg 11 | 12 | import logging 13 | logger = logging.getLogger('osl') 14 | 15 | 16 | def lemon_set_standard_montage(glmsp): 17 | from pathlib import Path 18 | pth = Path(mne.channels.__file__).parent 19 | pth = pth / 'data' / 'montages' / 'brainproducts-RNP-BA-128.txt' 20 | 21 | fbase = os.path.join(cfg['lemon_processed_data'], 'sub-010002', 'sub-010002_preproc_raw.fif') 22 | reference = mne.io.read_raw_fif(fbase).pick_types(eeg=True) 23 | 24 | new = [] 25 | with open(pth, 'r') as f: 26 | for line in f.readlines(): 27 | tag = line.split(' ')[0] 28 | if tag == 'Name' or tag in reference.ch_names: 29 | new.append(line) 30 | new_mon = os.path.join(cfg['code_dir'], 'lemon_custom_montage.txt') 31 | with open(new_mon, 'w') as f: 32 | for line in new: 33 | f.write(line) 34 | 35 | from mne.channels._standard_montage_utils import _read_theta_phi_in_degrees 36 | mon = _read_theta_phi_in_degrees(new_mon, mne.defaults.HEAD_SIZE_DEFAULT) 37 | reference = reference.set_montage(mon) 38 | glmsp.info = reference.info 39 | return glmsp 40 | 41 | 42 | def lemon_set_channel_montage(dataset, userargs): 43 | logger.info('LEMON Stage - load and set channel montage') 44 | logger.info('userargs: {0}'.format(str(userargs))) 45 | 46 | subj = '010060' 47 | #base = f'/Users/andrew/Projects/lemon/EEG_Raw_BIDS_ID/sub-{subj}/RSEEG/' 48 | #base = f'/ohba/pi/knobre/datasets/MBB-LEMON/EEG_MPILMBB_LEMON/EEG_Raw_BIDS_ID/sub-{subj}/RSEEG/' 49 | ref_file = os.path.join(cfg['lemon_raw'], f'sub-{subj}', 'RSEEG', f'sub-{subj}.mat') 50 | ref_file = os.path.join(cfg['code_dir'], 'sub-010060.mat') 51 | X = io.loadmat(ref_file) 52 | ch_pos = {} 53 | for ii in range(len(X['Channel'][0])-1): #final channel is reference 54 | key = X['Channel'][0][ii][0][0].split('_')[2] 55 | if key[:2] == 'FP': 56 | key = 'Fp' + key[2] 57 | value = X['Channel'][0][ii][3][:, 0] 58 | value = np.array([value[1], value[0], value[2]]) 59 | ch_pos[key] = value 60 | 61 | dig = mne.channels.make_dig_montage(ch_pos=ch_pos) 62 | dataset['raw'].set_montage(dig) 63 | 64 | return dataset 65 | 66 | 67 | def get_eeg_data(raw, csd=True): 68 | """Load EEG and perform sanity checks.""" 69 | 70 | # Use first scan as reference for channel labels and order 71 | fbase = os.path.join(cfg['lemon_processed_data'], 'sub-010002', 'sub-010002_preproc_raw.fif') 72 | reference = mne.io.read_raw_fif(fbase).pick_types(eeg=True) 73 | mon = reference.get_montage() 74 | 75 | # Load ideal layout and match data-channels 76 | raw = raw.copy().pick_types(eeg=True) 77 | ideal_inds = [mon.ch_names.index(c) for c in raw.info['ch_names']] 78 | 79 | if csd: 80 | # Apply laplacian if requested 81 | raw = mne.preprocessing.compute_current_source_density(raw) 82 | X = raw.get_data(picks='csd') 83 | else: 84 | # Get data from EEG picks 85 | X = raw.get_data(picks='eeg') 86 | 87 | # Preallocate & store ouput 88 | Y = np.zeros((len(mon.ch_names), X.shape[1])) 89 | 90 | Y[ideal_inds, :] = X 91 | 92 | return Y 93 | 94 | 95 | def lemon_create_heog(dataset, userargs): 96 | logger.info('LEMON Stage - Create HEOG from F7 and F8') 97 | logger.info('userargs: {0}'.format(str(userargs))) 98 | 99 | F7 = dataset['raw'].get_data(picks='F7') 100 | F8 = dataset['raw'].get_data(picks='F8') 101 | 102 | heog = F7-F8 103 | 104 | info = mne.create_info(['HEOG'], 105 | dataset['raw'].info['sfreq'], 106 | ['eog']) 107 | eog_raw = mne.io.RawArray(heog, info) 108 | dataset['raw'].add_channels([eog_raw], force_update_info=True) 109 | 110 | return dataset 111 | 112 | 113 | def lemon_ica(dataset, userargs, logfile=None): 114 | logger.info('LEMON Stage - custom EEG ICA function') 115 | logger.info('userargs: {0}'.format(str(userargs))) 116 | 117 | # NOTE: **userargs doesn't work because 'picks' is in there 118 | ica = mne.preprocessing.ICA(n_components=userargs['n_components'], 119 | max_iter=1000, 120 | random_state=42) 121 | 122 | # https://mne.tools/stable/auto_tutorials/preprocessing/40_artifact_correction_ica.html#filtering-to-remove-slow-drifts 123 | fraw = dataset['raw'].copy().filter(l_freq=1., h_freq=None) 124 | 125 | ica.fit(fraw, picks=userargs['picks']) 126 | dataset['ica'] = ica 127 | 128 | logger.info('starting EOG autoreject') 129 | # Find and exclude VEOG 130 | #eog_indices, eog_scores = dataset['ica'].find_bads_eog(dataset['raw']) 131 | veog_indices, eog_scores = dataset['ica'].find_bads_eog(dataset['raw'], 'VEOG') 132 | if len(veog_indices) == 0: 133 | veog_indices, eog_scores = dataset['ica'].find_bads_eog(dataset['raw'], 'VEOG', threshold=2) 134 | dataset['veog_scores'] = eog_scores 135 | dataset['ica'].exclude.extend(veog_indices) 136 | logger.info('Marking {0} ICs as EOG {1}'.format(len(dataset['ica'].exclude), 137 | veog_indices)) 138 | 139 | # Find and exclude HEOG 140 | #heog_indices = lemon_find_heog(fraw, ica) 141 | heog_indices, eog_scores = dataset['ica'].find_bads_eog(dataset['raw'], 'HEOG') 142 | if len(heog_indices) == 0: 143 | heog_indices, eog_scores = dataset['ica'].find_bads_eog(dataset['raw'], 'HEOG', threshold=2) 144 | dataset['heog_scores'] = eog_scores 145 | dataset['ica'].exclude.extend(heog_indices) 146 | logger.info('Marking {0} ICs as HEOG {1}'.format(len(heog_indices), 147 | heog_indices)) 148 | 149 | # Save components as channels in raw object 150 | src = dataset['ica'].get_sources(fraw).get_data() 151 | veog = src[veog_indices[0], :] 152 | heog = src[heog_indices[0], :] 153 | 154 | ica.labels_['top'] = [veog_indices[0], heog_indices[0]] 155 | 156 | info = mne.create_info(['ICA-VEOG', 'ICA-HEOG'], 157 | dataset['raw'].info['sfreq'], 158 | ['misc', 'misc']) 159 | eog_raw = mne.io.RawArray(np.c_[veog, heog].T, info) 160 | dataset['raw'].add_channels([eog_raw], force_update_info=True) 161 | 162 | # Apply ICA denoising or not 163 | if ('apply' not in userargs) or (userargs['apply'] is True): 164 | logger.info('Removing selected components from raw data') 165 | dataset['ica'].apply(dataset['raw']) 166 | else: 167 | logger.info('Components were not removed from raw data') 168 | return dataset 169 | 170 | 171 | def lemon_zapline_dss(dataset, userargs, logfile=None): 172 | logger.info('LEMON Stage - ZapLine power removal') 173 | logger.info('userargs: {0}'.format(str(userargs))) 174 | from meegkit import dss 175 | # https://mne.discourse.group/t/clean-line-noise-zapline-method-function-for-mne-using-meegkit-toolbox/7407 176 | fline = userargs.get('fline', 50) 177 | 178 | data = dataset['raw'].get_data() # Convert mne data to numpy darray 179 | sfreq = dataset['raw'].info['sfreq'] # Extract the sampling freq 180 | 181 | #Apply MEEGkit toolbox function 182 | out, _ = dss.dss_line(data.T, fline, sfreq, nremove=4) # fline (Line noise freq) = 50 Hz for Europe 183 | 184 | dataset['raw']._data = out.T # Overwrite old data 185 | 186 | return dataset 187 | 188 | 189 | def lemon_make_task_regressor(dataset): 190 | ev, ev_id = mne.events_from_annotations(dataset['raw']) 191 | print('Found {0} events in raw'.format(ev.shape[0])) 192 | print(ev_id) 193 | 194 | # Correct for cropping first 10 seconds - not sure why this is necessary?! 195 | ev[:, 0] -= dataset['raw'].first_samp 196 | 197 | task = np.zeros((dataset['raw'].n_times,)) 198 | for ii in range(ev.shape[0]): 199 | if ev[ii, 2] == ev_id['Stimulus/S200']: 200 | # EYES OPEN 201 | task[ev[ii,0]:ev[ii,0]+5000] = 1 202 | elif ev[ii, 2] == ev_id['Stimulus/S210']: 203 | # EYES CLOSED 204 | task[ev[ii,0]:ev[ii,0]+5000] = -1 205 | elif ev[ii, 2] == 1: 206 | task[ev[ii,0]] = task[ev[ii,0]-1] 207 | 208 | return task 209 | 210 | 211 | def find_eog_events(raw, event_id=998): 212 | eog = raw.copy().filter(l_freq=1, h_freq=10, picks='eog').get_data(picks='VEOG') 213 | eog = eog[0, :] 214 | # 10 seconds hopefully long enough to avoid rejecting real blinks - only 215 | # want to catch HUGE artefacts here. 216 | bads = sails.utils.detect_artefacts(eog, axis=0, reject_mode='segments', segment_len=2500) 217 | eog[bads] = np.median(eog) 218 | logger.info('Removed {0} bad samples from EOG ({1}%)'.format(bads.sum(), 100*(bads.sum()/len(bads)))) 219 | 220 | if np.abs(np.max(eog)) > np.abs(np.min(eog)): 221 | eog_events, _ = mne.preprocessing.eog.peak_finder(eog, 222 | None, extrema=1) 223 | else: 224 | eog_events, _ = mne.preprocessing.eog.peak_finder(eog, 225 | None, extrema=-1) 226 | 227 | n_events = len(eog_events) 228 | logger.info(f'Number of EOG events detected: {n_events}') 229 | #eog_events = np.array([eog_events + raw.first_samp, 230 | eog_events = np.array([eog_events, 231 | np.zeros(n_events, int), 232 | event_id * np.ones(n_events, int)]).T 233 | 234 | return eog_events 235 | 236 | 237 | def lemon_make_blinks_regressor(raw, corr_thresh=0.75, figpath=None): 238 | #eog_events = mne.preprocessing.find_eog_events(raw, l_freq=1, h_freq=10) 239 | eog_events = find_eog_events(raw) 240 | logger.info('found {0} blinks'.format(eog_events.shape[0])) 241 | #eog_events = find_eog_events(raw) 242 | #logger.info('found {0} blinks'.format(eog_events.shape[0])) 243 | 244 | # Correct for cropping first 10 seconds - not sure why this is necessary?! 245 | #eog_events[:, 0] -= int(10*raw.info['sfreq']) 246 | 247 | tmin = -0.1 248 | tmax = 0.15 249 | epochs = mne.Epochs(raw, eog_events, 998, tmin, tmax, picks='eog') 250 | ev_eog = epochs.get_data()[:, 0, :] 251 | C = np.abs(np.corrcoef(ev_eog.mean(axis=0), ev_eog)[1:,0]) 252 | drops = np.where(C < corr_thresh)[0] 253 | clean = epochs.copy().drop(drops) 254 | keeps = np.where(C > corr_thresh)[0] 255 | dirty = epochs.copy().drop(keeps) 256 | 257 | eog_events = np.delete(eog_events, drops, axis=0) 258 | logger.info('found {0} clean blinks'.format(eog_events.shape[0])) 259 | 260 | blink_covariate = np.zeros((raw.n_times,)) 261 | #blink_covariate[eog_events[:, 0] - raw.first_samp] = 1 262 | blink_covariate[eog_events[:, 0]] = 1 263 | blink_covariate = ndimage.maximum_filter(blink_covariate, 264 | size=raw.info['sfreq']//2) 265 | 266 | if figpath is not None: 267 | plt.figure(figsize=(16, 10)) 268 | plt.subplot(231) 269 | plt.plot(epochs.times, epochs.get_data()[:, 0, :].mean(axis=0)) 270 | plt.title('All blinks') 271 | plt.subplot(234) 272 | plt.plot(epochs.times, epochs.get_data()[:, 0, :].T) 273 | plt.subplot(232) 274 | plt.plot(epochs.times, clean.get_data()[:, 0, :].mean(axis=0)) 275 | plt.title('Clean blinks') 276 | plt.subplot(235) 277 | plt.plot(epochs.times, clean.get_data()[:, 0, :].T) 278 | plt.subplot(233) 279 | plt.title('Dirty blinks') 280 | plt.plot(epochs.times, dirty.get_data()[:, 0, :].mean(axis=0)) 281 | plt.subplot(236) 282 | plt.plot(epochs.times, dirty.get_data()[:, 0, :].T) 283 | plt.savefig(figpath, transparent=False, dpi=300) 284 | 285 | return blink_covariate, eog_events.shape[0], clean.average(picks='eog') 286 | 287 | 288 | def lemon_make_bads_regressor(raw, mode='eeg'): 289 | bads = np.zeros((raw.n_times,)) 290 | for an in raw.annotations: 291 | if an['description'].startswith('bad') and an['description'].endswith(mode): 292 | start = raw.time_as_index(an['onset'])[0] - raw.first_samp 293 | duration = int(an['duration'] * raw.info['sfreq']) 294 | bads[start:start+duration] = 1 295 | if mode == 'raw': 296 | bads[:int(raw.info['sfreq']*2)] = 1 297 | bads[-int(raw.info['sfreq']*2):] = 1 298 | else: 299 | bads[:int(raw.info['sfreq'])] = 1 300 | bads[-int(raw.info['sfreq']):] = 1 301 | return bads 302 | 303 | 304 | def quick_plot_eog_icas(raw, ica, figpath=None): 305 | 306 | inds = np.arange(250*45, 250*300) 307 | 308 | plt.figure(figsize=(16, 9)) 309 | veog = raw.get_data(picks='VEOG')[0, :] 310 | ica_veog = raw.get_data(picks='ICA-VEOG')[0, :] 311 | ax = plt.axes([0.05, 0.55, 0.125, 0.4]) 312 | comp = ica.get_components()[:, ica.labels_['top'][0]] 313 | mne.viz.plot_topomap(comp, ica.info, axes=ax, show=False) 314 | 315 | plt.axes([0.2, 0.55, 0.475, 0.4]) 316 | plt.plot(stats.zscore(veog[inds])) 317 | plt.plot(stats.zscore(ica_veog[inds])-10) 318 | plt.legend(['VEOGs', 'ICA-VEOG'], frameon=False) 319 | plt.xlim(0, 250*180) 320 | 321 | plt.axes([0.725, 0.55, 0.25, 0.4]) 322 | plt.plot(veog, ica_veog, '.k') 323 | veog = raw.get_data(picks='VEOG', reject_by_annotation='omit')[0, :] 324 | ica_veog = raw.get_data(picks='ICA-VEOG', reject_by_annotation='omit')[0, :] 325 | plt.plot(veog, ica_veog, '.r') 326 | plt.xlabel('VEOG'); plt.ylabel('ICA-VEOG') 327 | plt.plot(veog, ica_veog, '.r') 328 | plt.legend(['Samples', 'Clean Samples'], frameon=False) 329 | plt.title('Correlation : r = {0}'.format(np.corrcoef(veog, ica_veog)[0, 1])) 330 | 331 | heog = raw.get_data(picks='HEOG')[0, :] 332 | ica_heog = raw.get_data(picks='ICA-HEOG')[0, :] 333 | plt.axes([0.05, 0.05, 0.125, 0.4]) 334 | comp = ica.get_components()[:, ica.labels_['top'][1]] 335 | mne.viz.plot_topomap(comp, ica.info, show=False) 336 | 337 | plt.axes([0.2, 0.05, 0.475, 0.4]) 338 | plt.plot(stats.zscore(heog[inds])) 339 | plt.plot(stats.zscore(ica_heog[inds])-5) 340 | plt.legend(['HEOGs', 'ICA-HEOG'], frameon=False) 341 | plt.xlim(0, 250*180) 342 | 343 | plt.axes([0.725, 0.05, 0.25, 0.4]) 344 | plt.plot(heog, ica_heog, '.k') 345 | heog = raw.get_data(picks='HEOG', reject_by_annotation='omit')[0, :] 346 | ica_heog = raw.get_data(picks='ICA-HEOG', reject_by_annotation='omit')[0, :] 347 | plt.plot(heog, ica_heog, '.r') 348 | plt.legend(['Samples', 'Clean Samples'], frameon=False) 349 | plt.xlabel('HEOG'); plt.ylabel('ICA-HEOG') 350 | plt.title('Correlation : r = {0}'.format(np.corrcoef(heog, ica_heog)[0, 1])) 351 | 352 | plt.savefig(figpath, transparent=False, dpi=300) 353 | 354 | 355 | def quick_plot_eog_epochs(raw, figpath=None): 356 | 357 | fig = mne.preprocessing.create_eog_epochs(raw, picks='eeg').average().plot_joint(show=False) 358 | fig.savefig(figpath.format('eeg_eog_epochs')) 359 | 360 | fig = mne.preprocessing.create_eog_epochs(raw).average().plot(show=False) 361 | fig.savefig(figpath.format('eog_eog_epochs')) 362 | 363 | 364 | def plot_design(ax, design_matrix, regressor_names): 365 | num_observations, num_regressors = design_matrix.shape 366 | vm = np.max((design_matrix.min(), design_matrix.max())) 367 | cax = ax.pcolor(design_matrix, cmap=plt.cm.coolwarm, 368 | vmin=-vm, vmax=vm) 369 | ax.set_xlabel('Regressors') 370 | tks = np.arange(len(regressor_names)+1) 371 | ax.set_xticks(tks+0.5) 372 | ax.set_xticklabels(tks) 373 | 374 | tkstep = 2 375 | tks = np.arange(0, design_matrix.shape[0], tkstep) 376 | 377 | for tag in ['top', 'right', 'left', 'bottom']: 378 | ax.spines[tag].set_visible(False) 379 | 380 | summary_lines = True 381 | new_cols = 0 382 | for ii in range(num_regressors): 383 | if summary_lines: 384 | x = design_matrix[:, ii] 385 | if np.abs(np.diff(x)).sum() != 0: 386 | y = (0.5*x) / (np.max(np.abs(x)) * 1.1) 387 | else: 388 | # Constant regressor 389 | y = np.ones_like(x) * .45 390 | if num_observations > 50: 391 | ax.plot(y+ii+new_cols+0.5, np.arange(0, 0+num_observations)+.5, 'k') 392 | else: 393 | yy = y+ii+new_cols+0.5 394 | print('{} - {} - {}'.format(yy.min(), yy.mean(), yy.max())) 395 | ax.plot(y+ii+new_cols+0.5, np.arange(0, 0+num_observations)+.5, 396 | 'k|', markersize=5) 397 | 398 | # Add white dividing line 399 | if ii < num_regressors-1: 400 | ax.plot([ii+1+new_cols, ii+1+new_cols], [0, 0+num_observations], 401 | 'w', linewidth=4) 402 | return cax 403 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy==1.21.6 2 | scipy==1.7.1 3 | pandas==1.3.5 4 | dill==0.3.2 5 | h5py==2.10.0 6 | anamnesis==1.0.4 7 | mne==1.3.0 8 | requests==2.26.0 9 | osl==0.1.1 10 | matplotlib==3.5.2 11 | dask==2022.2.0 12 | anamnesis==1.0.4 13 | beautifulsoup4==4.11.1 14 | PyYAML==6.0 15 | sails==1.3.0 16 | glmtools==0.2.0 17 | -------------------------------------------------------------------------------- /sub-010060.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OHBA-analysis/Quinn2022_GLMSpectrum/2c81e6b24a275ff35335c22d648815075e6ffc97/sub-010060.mat --------------------------------------------------------------------------------