├── README.md ├── test_base_connectivity.py ├── test_embedding.py ├── connstacking.py ├── quality_check_rs_fmri_adni.py ├── test_rtov_connectivity.py ├── reho.py ├── .gitignore ├── test_network_connectivity.py ├── preprocess_sample.py ├── preprocess_baseline_rs_fmri_adni.py ├── ward_baseline_rs_fmri_adni.py ├── ica_baseline_rs_fmri_adni.py ├── rpbi_baseline_rs_fmri_adni.py ├── test_network_connectivity_classifier.py ├── canica_extract_regions.py ├── test_connectivity_classifier.py ├── correlation_baseline_rs_fmri_adni.py ├── plot_connectomes.py ├── plot_accuracies.py ├── covariance_baseline_rs_fmri_adni.py ├── connclassifier.py ├── base_connectivity_classifier.py ├── base_rtov_connectivity.py ├── base_network_connectivity.py ├── base_connectivity.py ├── embedding.py └── LICENSE /README.md: -------------------------------------------------------------------------------- 1 | adni_rs_fmri_analysis 2 | ===================== 3 | 4 | ADNI resting-state fMRI analysis scripts 5 | -------------------------------------------------------------------------------- /test_base_connectivity.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Wed May 27 16:59:34 2015 4 | 5 | @author: mehdi.rahim@cea.fr 6 | """ 7 | 8 | from base_connectivity import Connectivity, fetch_atlas 9 | import os 10 | import numpy as np 11 | from fetch_data import fetch_adni_baseline_rs_fmri, fetch_adni_masks, set_cache_base_dir 12 | 13 | CACHE_DIR = set_cache_base_dir() 14 | dataset = fetch_adni_baseline_rs_fmri() 15 | mask = fetch_adni_masks()['mask_petmr'] 16 | 17 | conn = Connectivity('msdl', 'tangent', mask, memory=CACHE_DIR, n_jobs=2) 18 | fc = conn.fit(dataset.func[:10]) 19 | 20 | -------------------------------------------------------------------------------- /test_embedding.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Mon Jun 1 10:21:42 2015 4 | 5 | @author: mr243268 6 | """ 7 | 8 | from embedding import CovEmbedding, vec_to_sym 9 | from nilearn.datasets import fetch_nyu_rest, fetch_msdl_atlas 10 | from nilearn.input_data import NiftiMapsMasker 11 | 12 | dataset = fetch_nyu_rest(n_subjects=1) 13 | atlas = fetch_msdl_atlas() 14 | 15 | masker = NiftiMapsMasker(atlas['maps'], detrend=True, standardize=True) 16 | masker.fit() 17 | ts = masker.transform(dataset.func[0]) 18 | cov_embed = CovEmbedding(kind='tangent') 19 | output = cov_embed.fit_transform([ts]) 20 | 21 | m = vec_to_sym(output) 22 | -------------------------------------------------------------------------------- /connstacking.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | 4 | # 1- Compute connectivity features 5 | # 2- Classify for pairwise and multiclass 6 | # 3- Stack prediction 7 | 8 | Created on Wed May 27 15:13:23 2015 9 | 10 | @author: mehdi.rahim@cea.fr 11 | """ 12 | 13 | import os 14 | import numpy as np 15 | from fetch_data import fetch_adni_baseline_rs_fmri, fetch_adni_masks 16 | 17 | 18 | 19 | 20 | ############################################################################### 21 | # Main loop 22 | ############################################################################### 23 | 24 | dataset = fetch_adni_baseline_rs_fmri() 25 | mask = fetch_adni_masks()['mask_petmr'] 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /quality_check_rs_fmri_adni.py: -------------------------------------------------------------------------------- 1 | """ 2 | ADNI EPI Quality-check 3 | """ 4 | import os, glob 5 | import pandas as pd 6 | from nilearn.plotting import plot_epi 7 | from nilearn.image import mean_img 8 | 9 | BASE_DIR = '/disk4t/mehdi/data/ADNI_baseline_rs_fmri' 10 | DST_DIR = '/disk4t/mehdi/data/tmp/quality_check' 11 | 12 | data = pd.read_csv(os.path.join(BASE_DIR, 'description_file.csv')) 13 | 14 | for idx, row in data.iterrows(): 15 | fmri_file = glob.glob(os.path.join(BASE_DIR, 16 | 'I' + str(row.Image_ID), '*.nii')) 17 | 18 | mean_fmri = mean_img(fmri_file[0], n_jobs=4) 19 | plot_epi(mean_fmri, 20 | output_file=os.path.join(DST_DIR, row['Subject_ID']), 21 | title=row['Subject_ID']) 22 | -------------------------------------------------------------------------------- /test_rtov_connectivity.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Wed Jun 3 14:03:25 2015 4 | 5 | @author: mehdi.rahim@cea.fr 6 | """ 7 | 8 | from fetch_data import fetch_adni_baseline_rs_fmri, fetch_adni_masks, set_cache_base_dir 9 | from base_rtov_connectivity import ROItoVoxConnectivity 10 | 11 | CACHE_DIR = set_cache_base_dir() 12 | mask = fetch_adni_masks()['mask_fmri'] 13 | dataset = fetch_adni_baseline_rs_fmri() 14 | 15 | conn = ROItoVoxConnectivity(atlas_name='msdl', 16 | metric='corr', 17 | mask=mask, 18 | memory=CACHE_DIR, 19 | memory_level=2, 20 | n_jobs=10) 21 | 22 | output = conn.fit_subjects(dataset.func[:10]) 23 | 24 | 25 | -------------------------------------------------------------------------------- /reho.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | ReHO : Kendall's W 4 | 5 | W = 12 * S / m^2 (n^3 - n) 6 | 7 | S = sum_1^n ( R_i - R )^2 8 | 9 | R = 1/2 * m * (n + 1) 10 | 11 | R_i = sum_j=1^m ( r_i,j ) 12 | 13 | n time-samples, m voxels 14 | 15 | 16 | Created on Fri Mar 20 17:24:32 2015 17 | 18 | @author: mehdi.rahim@cea.fr 19 | """ 20 | 21 | import numpy as np 22 | 23 | def ReHo(r): 24 | """ Returns kendall W on r (time x voxels) n*m 25 | """ 26 | 27 | # n:time, m:voxels 28 | n, m = r.shape 29 | 30 | # Ri: sum of all times (gives m voxels vector) 31 | Ri = np.sum(r, axis=0) 32 | 33 | # R: mean value 34 | R = .5 * m * (n + 1) 35 | 36 | # sum of squared deviation 37 | S = np.sum((Ri - R)**2) 38 | 39 | # kendall's W 40 | W = 12 * S / m**2 * (n**3 - n) 41 | 42 | return W -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # PyInstaller 26 | # Usually these files are written by a python script from a template 27 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 28 | *.manifest 29 | *.spec 30 | 31 | # Installer logs 32 | pip-log.txt 33 | pip-delete-this-directory.txt 34 | 35 | # Unit test / coverage reports 36 | htmlcov/ 37 | .tox/ 38 | .coverage 39 | .cache 40 | nosetests.xml 41 | coverage.xml 42 | 43 | # Translations 44 | *.mo 45 | *.pot 46 | 47 | # Django stuff: 48 | *.log 49 | 50 | # Sphinx documentation 51 | docs/_build/ 52 | 53 | # PyBuilder 54 | target/ 55 | -------------------------------------------------------------------------------- /test_network_connectivity.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Mon Jun 8 09:34:59 2015 4 | 5 | @author: mehdi.rahim@cea.fr 6 | """ 7 | 8 | 9 | from base_connectivity import Connectivity 10 | import numpy as np 11 | from fetch_data import fetch_adni_baseline_rs_fmri, fetch_adni_masks, \ 12 | set_cache_base_dir, set_group_indices, fetch_adni_longitudinal_rs_fmri_DARTEL 13 | from nilearn.plotting import plot_connectome 14 | from scipy import stats 15 | 16 | CACHE_DIR = set_cache_base_dir() 17 | dataset = fetch_adni_baseline_rs_fmri() 18 | #dataset = fetch_adni_longitudinal_rs_fmri_DARTEL() 19 | mask = fetch_adni_masks()['mask_fmri'] 20 | 21 | atlas_name = 'mayo' 22 | metric = 'correlation' 23 | 24 | conn = Connectivity(atlas_name=atlas_name, rois=True, metric=metric, mask=mask, detrend=True, 25 | low_pass=.1, high_pass=.01, t_r=3., 26 | resampling_target='data', smoothing_fwhm=6., 27 | memory=CACHE_DIR, memory_level=2, n_jobs=20) 28 | 29 | fc = conn.fit(dataset.func) 30 | np.savez_compressed('longitudinal_dartel_fc', data=fc) 31 | 32 | idx = set_group_indices(dataset.dx_group) 33 | groups = [['AD', 'MCI'], ['AD', 'Normal'], ['MCI', 'Normal']] 34 | 35 | 36 | for g in groups: 37 | t, p = stats.ttest_ind(fc[idx[g[0]]], fc[idx[g[1]]]) 38 | tv = t 39 | tv[np.where(p>.01)] = 0 40 | 41 | n_rois = conn.rois['n_rois'] 42 | centroids = conn.rois['rois_centroids'] 43 | ind = np.tril_indices(n_rois, k=-1) 44 | m = np.zeros((n_rois, n_rois)) 45 | m[ind] = tv 46 | m = .5 * (m + m.T) 47 | plot_connectome(m, centroids, title='_'.join(g)) 48 | -------------------------------------------------------------------------------- /preprocess_sample.py: -------------------------------------------------------------------------------- 1 | """ 2 | Preprocessing ADNI baseline resting state fmri 3 | - Alignement 4 | - Coreg MRI 5 | - Normalization MNI 6 | - Smoothing 7 | """ 8 | import os, glob 9 | import nibabel as nib 10 | from pypreprocess.nipype_preproc_spm_utils import do_subjects_preproc 11 | 12 | 13 | BASE_DIR = '/home/mr243268/data/adni_sample' 14 | 15 | ### 16 | def delete_scans_fmri(niimg): 17 | """ Load a 4D nii image and delete 3 first scans 18 | """ 19 | img = nib.load(niimg) 20 | img = nib.Nifti1Image(img.get_data()[...,3:], img.get_affine()) 21 | head, tail = os.path.split(niimg) 22 | img.to_filename(os.path.join(head, 'z' + tail)) 23 | 24 | ### 25 | def delete_scans_adni(): 26 | """ Process adni rs fmri folder 27 | """ 28 | fmri_paths = sorted(glob.glob(os.path.join(BASE_DIR, 's*'))) 29 | for fmri_path in fmri_paths: 30 | print os.path.split(fmri_path)[1] 31 | fmri_file = glob.glob(os.path.join(fmri_path, 'func', 'A*.nii')) 32 | if len(fmri_file) > 0: 33 | delete_scans_fmri(fmri_file[0]) 34 | 35 | ### 36 | def clean_dirs_adni(): 37 | """ Remove all processed data 38 | """ 39 | fmri_paths = sorted(glob.glob(os.path.join(BASE_DIR, 's*'))) 40 | for fmri_path in fmri_paths: 41 | fmri_file = glob.glob(os.path.join(fmri_path, 'func', 'z*.nii')) 42 | for f in fmri_file: 43 | os.remove(f) 44 | print f, 'removed' 45 | 46 | """ 47 | # Delete first 3 scans 48 | clean_dirs_adni() 49 | delete_scans_adni() 50 | """ 51 | 52 | jobfile = 'preprocess_sample_config.ini' 53 | dataset_dir = BASE_DIR 54 | 55 | # sourcing FSL 56 | os.system('source /etc/fsl/4.1/fsl.sh') 57 | 58 | # preprocess the data 59 | results = do_subjects_preproc(jobfile, dataset_dir=BASE_DIR) -------------------------------------------------------------------------------- /preprocess_baseline_rs_fmri_adni.py: -------------------------------------------------------------------------------- 1 | """ 2 | Preprocessing ADNI baseline resting state fmri 3 | - Alignement 4 | - Coreg MRI 5 | - Normalization MNI 6 | - Smoothing 7 | """ 8 | import os, glob 9 | import nibabel as nib 10 | from pypreprocess.nipype_preproc_spm_utils import do_subjects_preproc 11 | 12 | 13 | BASE_DIR = '/disk4t/mehdi/data/ADNI_baseline_rs_fmri_mri' 14 | 15 | ### 16 | def delete_scans_fmri(niimg): 17 | """ Load a 4D nii image and delete 3 first scans 18 | """ 19 | img = nib.load(niimg) 20 | img = nib.Nifti1Image(img.get_data()[...,3:], img.get_affine()) 21 | head, tail = os.path.split(niimg) 22 | img.to_filename(os.path.join(head, 'z' + tail)) 23 | 24 | ### 25 | def delete_scans_adni(): 26 | """ Process adni rs fmri folder 27 | """ 28 | fmri_paths = sorted(glob.glob(os.path.join(BASE_DIR, 's*'))) 29 | for fmri_path in fmri_paths: 30 | print os.path.split(fmri_path)[1] 31 | fmri_file = glob.glob(os.path.join(fmri_path, 'func', 'A*.nii')) 32 | if len(fmri_file) > 0: 33 | delete_scans_fmri(fmri_file[0]) 34 | 35 | ### 36 | def clean_dirs_adni(): 37 | """ Remove all processed data 38 | """ 39 | fmri_paths = sorted(glob.glob(os.path.join(BASE_DIR, 's*'))) 40 | for fmri_path in fmri_paths: 41 | fmri_file = glob.glob(os.path.join(fmri_path, 'func', 'z*.nii')) 42 | for f in fmri_file: 43 | os.remove(f) 44 | print f, 'removed' 45 | 46 | 47 | # Delete first 3 scans 48 | """ 49 | clean_dirs_adni() 50 | delete_scans_adni() 51 | """ 52 | 53 | jobfile = 'preprocess_rs_fmri_adni_config.ini' 54 | dataset_dir = BASE_DIR 55 | 56 | # sourcing FSL 57 | os.system('source /etc/fsl/4.1/fsl.sh') 58 | 59 | # preprocess the data 60 | results = do_subjects_preproc(jobfile, dataset_dir=BASE_DIR) -------------------------------------------------------------------------------- /ward_baseline_rs_fmri_adni.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Tue Jan 6 13:18:47 2015 4 | 5 | @author: Mehdi Rahim 6 | """ 7 | import os 8 | from fetch_data import datasets 9 | import numpy as np 10 | from sklearn.cluster import WardAgglomeration 11 | from sklearn.feature_extraction import image 12 | from nilearn.input_data import NiftiMasker 13 | 14 | CACHE_DIR = '/disk4t/mehdi/data/tmp' 15 | 16 | def ward_adni_rs_fmri(func_files, n_clusters=200): 17 | 18 | masker = NiftiMasker(mask_strategy='epi', 19 | mask_args=dict(opening=1)) 20 | masker.fit(func_files) 21 | func_masked = masker.transform(func_files) 22 | #func_masked = masker.transform_niimgs(func_files, n_jobs=4) 23 | func_masked = np.vstack(func_masked) 24 | 25 | ########################################################################### 26 | # Ward 27 | ########################################################################### 28 | 29 | mask = masker.mask_img_.get_data().astype(np.bool) 30 | shape = mask.shape 31 | connectivity = image.grid_to_graph(n_x=shape[0], n_y=shape[1], 32 | n_z=shape[2], mask=mask) 33 | 34 | # Computing the ward for the first time, this is long... 35 | ward = WardAgglomeration(n_clusters=n_clusters, connectivity=connectivity, 36 | memory='nilearn_cache') 37 | 38 | ward.fit(func_masked) 39 | ward_labels_unique = np.unique(ward.labels_) 40 | ward_labels = ward.labels_ 41 | 42 | ward_filename = '_'.join(['ward', str(n_clusters)]) 43 | img_ward = masker.inverse_transform(ward.labels_) 44 | img_ward.to_filename(os.path.join(CACHE_DIR, ward_filename)) 45 | 46 | 47 | 48 | dataset = datasets.fetch_adni_rs_fmri() 49 | func_files = dataset['func'] 50 | n_sample = 140 51 | idx = np.random.randint(len(func_files), size=n_sample) 52 | func_files_sample = np.array(func_files)[idx][0] 53 | 54 | ward_adni_rs_fmri(func_files_sample) -------------------------------------------------------------------------------- /ica_baseline_rs_fmri_adni.py: -------------------------------------------------------------------------------- 1 | """ 2 | CanICA on ADNI rs-fmri 3 | """ 4 | import os 5 | import numpy as np 6 | from nilearn.plotting import plot_img 7 | from nilearn.decomposition.canica import CanICA 8 | from nilearn.input_data import MultiNiftiMasker 9 | import nibabel as nib 10 | import matplotlib.pyplot as plt 11 | from nilearn.plotting import plot_stat_map 12 | from fetch_data import datasets 13 | 14 | CACHE_DIR = '/home/mr234268/data' 15 | 16 | 17 | dataset = datasets.fetch_adni_rs_fmri() 18 | func_files = dataset['func'] 19 | dx_group = dataset['dx_group'] 20 | 21 | 22 | n_sample = 140 23 | idx = np.random.randint(len(func_files), size=n_sample) 24 | func_files_sample = np.array(func_files)[idx] 25 | 26 | 27 | multi_masker = MultiNiftiMasker(mask_strategy='epi', 28 | memory=CACHE_DIR, 29 | n_jobs=1, memory_level=2) 30 | multi_masker.fit(func_files_sample) 31 | plot_img(multi_masker.mask_img_) 32 | 33 | 34 | n_components = 40 35 | canica = CanICA(mask=multi_masker, n_components=n_components, 36 | smoothing_fwhm=6., memory=CACHE_DIR, memory_level=5, 37 | threshold=3., verbose=10, random_state=0) 38 | canica.fit(func_files_sample) 39 | 40 | 41 | # Retrieve the independent components in brain space 42 | components_img = canica.masker_.inverse_transform(canica.components_) 43 | # components_img is a Nifti Image object, and can be saved to a file with 44 | # the following line: 45 | components_img.to_filename(os.path.join(CACHE_DIR, 46 | 'canica_resting_state_140.nii.gz')) 47 | 48 | ### Visualize the results ##################################################### 49 | # Show some interesting components 50 | 51 | for i in range(n_components): 52 | plot_stat_map(nib.Nifti1Image(components_img.get_data()[..., i], 53 | components_img.get_affine()), 54 | display_mode="z", title="IC %d"%i, cut_coords=1, 55 | colorbar=False) 56 | 57 | plt.show() 58 | -------------------------------------------------------------------------------- /rpbi_baseline_rs_fmri_adni.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | RPBI of pairwise DX groups. 4 | Plot t-maps and p-maps on the voxels. 5 | @author: Mehdi 6 | """ 7 | 8 | import os, glob 9 | import numpy as np 10 | import pandas as pd 11 | import nibabel as nib 12 | from nilearn.input_data import NiftiMasker, MultiNiftiMasker 13 | from nilearn.plotting import plot_roi, plot_stat_map, plot_img 14 | from nilearn.mass_univariate import randomized_parcellation_based_inference 15 | from matplotlib import cm 16 | from fetch_data import datasets 17 | 18 | CACHE_DIR = os.path.join('/', 'disk4t', 'mehdi', 19 | 'data', 'tmp') 20 | 21 | dataset = datasets.fetch_adni_rs_fmri() 22 | func_files = dataset['func'] 23 | dx_group = np.array(dataset['dx_group']) 24 | 25 | ############################################################################### 26 | # 1- Masking 27 | ############################################################################### 28 | masker = MultiNiftiMasker(mask_strategy='epi', 29 | mask_args=dict(opening=1), 30 | memory_level=2, 31 | memory=CACHE_DIR, 32 | n_jobs=8) 33 | func_masked = masker.fit_transform(func_files) 34 | 35 | ############################################################################### 36 | #2- Testing 37 | ############################################################################### 38 | idx = {} 39 | for g in ['AD', 'LMCI', 'EMCI', 'Normal']: 40 | idx[g] = np.where(dx_group == g) 41 | 42 | groups = [['AD', 'Normal'], ['AD', 'EMCI'], ['AD', 'LMCI'], 43 | ['EMCI', 'LMCI'], ['EMCI', 'Normal'], ['LMCI', 'Normal']] 44 | 45 | for gr in groups: 46 | 47 | test_var = np.ones((len(func_files), 1), dtype=float) # intercept 48 | 49 | neg_log_pvals_rpbi, _, _ = randomized_parcellation_based_inference( 50 | test_var, func_masked, # + intercept as a covariate by default 51 | np.asarray(masker.mask_img_.get_data()).astype(bool), 52 | n_parcellations=100, # 30 for the sake of time, 100 is recommended 53 | n_parcels=500, 54 | threshold=0, 55 | n_perm=10000, # 1,000 for the sake of time. 10,000 is recommended 56 | memory=CACHE_DIR, 57 | n_jobs=20, verbose=False) 58 | 59 | neg_log_pvals_rpbi_unmasked = masker.inverse_transform( 60 | np.ravel(neg_log_pvals_rpbi)) 61 | 62 | p_path = os.path.join(CACHE_DIR, 'figures', 63 | 'pmap_rpbi_'+gr[0]+'_'+gr[1]+'_baseline_fmri_adni') 64 | neg_log_pvals_rpbi_unmasked.to_filename(p_path+'.nii.gz') 65 | break -------------------------------------------------------------------------------- /test_network_connectivity_classifier.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Mon Jun 8 18:16:30 2015 4 | 5 | @author: mehdi.rahim@cea.fr 6 | """ 7 | 8 | import os, sys 9 | import numpy as np 10 | from fetch_data import fetch_adni_baseline_rs_fmri, fetch_adni_masks, \ 11 | fetch_adni_longitudinal_rs_fmri_DARTEL, set_cache_base_dir 12 | from base_connectivity_classifier import ConnectivityClassifier 13 | 14 | #sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0) 15 | #sys.stderr = os.fdopen(sys.stderr.fileno(), 'w', 0) 16 | 17 | 18 | CACHE_DIR = set_cache_base_dir() 19 | 20 | #dataset = fetch_adni_baseline_rs_fmri() 21 | dataset = fetch_adni_longitudinal_rs_fmri_DARTEL() 22 | mask = fetch_adni_masks()['mask_fmri'] 23 | 24 | 25 | all_groups = [['AD', 'MCI'], ['AD', 'Normal'], ['MCI', 'Normal']] 26 | 27 | atlas_names = ['msdl', 'canica141', 'canica', 'mayo', 28 | 'harvard_oxford', 'juelich', 'tvmsdl'] 29 | 30 | classifier_names = ['ridge', 'svc_l1', 'svc_l2', 31 | 'logreg_l1', 'logreg_l2'] 32 | 33 | conn_names = ['corr', 'correlation', 'tangent', 'gl', 'lw', 'oas', 'scov'] 34 | 35 | ### 36 | atlas_names = ['mayo']#, 'msdl', 'canica'] 37 | classifier_names = ['svc_l2', 'logreg_l2'] 38 | conn_names = ['correlation'] 39 | 40 | for atlas_name in atlas_names: 41 | print atlas_name 42 | for conn_name in conn_names: 43 | connclassif = ConnectivityClassifier(dataset.func, mask, 44 | atlas=atlas_name, 45 | rois=True, 46 | dx_group=dataset.dx_group, 47 | memory='', 48 | memory_level=0, 49 | n_jobs=20) 50 | connclassif.compute_connectivity(conn_name) 51 | #np.savez_compressed(atlas_name, data=connclassif.connectivity) 52 | for groups in all_groups: 53 | print groups 54 | for classifier_name in classifier_names: 55 | connclassif.classify(dataset=dataset, 56 | groups=groups, 57 | classifier_name=classifier_name) 58 | output_folder = os.path.join(CACHE_DIR, 'DARTEL_ROIS', 59 | '_'.join(['conn', atlas_name])) 60 | if not os.path.isdir(output_folder): 61 | os.mkdir(output_folder) 62 | output_file = os.path.join(output_folder, 63 | '_'.join([groups[0], groups[1], 64 | atlas_name, conn_name, 65 | classifier_name, '_subj'])) 66 | np.savez_compressed(output_file, scores=connclassif.scores_, 67 | coefs=connclassif.coefs_) 68 | 69 | -------------------------------------------------------------------------------- /canica_extract_regions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Thu May 21 09:39:53 2015 4 | 5 | @author: mehdi.rahim@cea.fr 6 | """ 7 | import os, glob 8 | import numpy as np 9 | import matplotlib.pyplot as plt 10 | from nilearn.plotting import plot_stat_map, plot_roi 11 | from nilearn.image import index_img 12 | from fetch_data import fetch_adni_masks, array_to_niis, array_to_nii 13 | from scipy.ndimage import label 14 | from matplotlib import cm 15 | import nibabel as nib 16 | from fetch_data import set_cache_base_dir 17 | from joblib import Parallel, delayed 18 | 19 | base_dir = os.path.join(set_cache_base_dir(), 'decomposition') 20 | mask = fetch_adni_masks()['mask_petmr'] 21 | mask_shape = nib.load(mask).shape 22 | mask_affine = nib.load(mask).get_affine() 23 | np_files = os.listdir(base_dir) 24 | 25 | 26 | def extract_region_i(maps, i): 27 | """ Extract ROIs and plot 28 | """ 29 | m = maps[i, ...] 30 | th_value = np.percentile(m, 100.-(100./42.)) 31 | data = np.absolute(array_to_nii(m, mask).get_data()) 32 | data[data <= th_value] = 0 33 | data[data > th_value] = 1 34 | data_lab = label(data)[0] 35 | 36 | for v in np.unique(data_lab): 37 | if len(np.where(data_lab == v)[0]) < 1000: 38 | data_lab[data_lab == v] = 0 39 | 40 | img_l = nib.Nifti1Image(data_lab, mask_affine) 41 | plot_roi(img_l, title=map_title + '_roi_' + str(i), cmap=cm.rainbow) 42 | plot_stat_map(index_img(img, i), title=map_title + '_' + str(i), 43 | threshold=0) 44 | 45 | 46 | output_label_list = [] 47 | 48 | for np_file in np_files: 49 | data = np.load(os.path.join(base_dir, np_file)) 50 | map_title = 'canica' 51 | if os.path.splitext(np_file)[0] != 'canica' and os.path.splitext(np_file)[0] != 'canica_200': 52 | map_title = 'tv_msdl' 53 | data = data['subject_maps'] 54 | print data.shape 55 | maps = data 56 | img = array_to_niis(maps, mask) 57 | 58 | # Parallel(n_jobs=21, verbose=5)(delayed(extract_region_i)(maps, i) 59 | # for i in range(maps.shape[0])) 60 | 61 | for i in range(maps.shape[0]): 62 | m = maps[i, ...] 63 | th_value = np.percentile(m, 100.-(100./42.)) 64 | data = np.absolute(array_to_nii(m, mask).get_data()) 65 | data[data <= th_value] = 0 66 | data[data > th_value] = 1 67 | data_lab = label(data)[0] 68 | 69 | for v in np.unique(data_lab): 70 | if len(np.where(data_lab == v)[0]) < 250: 71 | data_lab[data_lab == v] = 0 72 | 73 | for v in np.unique(data_lab): 74 | data_labels = np.copy(data_lab) 75 | if v > 0: 76 | data_labels[data_labels != v] = 0 77 | data_labels[data_labels == v] = 1 78 | output_label_list.append(data_labels) 79 | 80 | img_l = nib.Nifti1Image(data_lab, mask_affine) 81 | plot_roi(img_l, title=map_title + '_roi_' + str(i), cmap=cm.rainbow, output_file='canica130/'+map_title + '_roi_' + str(i)) 82 | plot_stat_map(index_img(img, i), title=map_title + '_' + str(i), output_file='canica130/'+map_title + '_stat_' + str(i), 83 | threshold=0) 84 | 85 | break -------------------------------------------------------------------------------- /test_connectivity_classifier.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Thu May 28 10:52:46 2015 4 | 5 | @author: mehdi.rahim@cea.fr 6 | """ 7 | import os, sys 8 | import numpy as np 9 | import matplotlib.pyplot as plt 10 | from fetch_data import fetch_adni_baseline_rs_fmri, fetch_adni_masks, \ 11 | fetch_adni_longitudinal_rs_fmri_DARTEL, set_cache_base_dir 12 | from base_connectivity_classifier import ConnectivityClassifier 13 | from sklearn.metrics import confusion_matrix 14 | 15 | #sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0) 16 | #sys.stderr = os.fdopen(sys.stderr.fileno(), 'w', 0) 17 | 18 | 19 | CACHE_DIR = set_cache_base_dir() 20 | 21 | #dataset = fetch_adni_baseline_rs_fmri() 22 | dataset = fetch_adni_longitudinal_rs_fmri_DARTEL() 23 | mask = fetch_adni_masks()['mask_fmri'] 24 | 25 | 26 | all_groups = [['AD', 'MCI'], ['AD', 'Normal'], ['MCI', 'Normal']] 27 | 28 | atlas_names = ['msdl', 'canica141', 'canica', 'mayo', 29 | 'harvard_oxford', 'juelich', 'tvmsdl'] 30 | 31 | classifier_names = ['ridge', 'svc_l1', 'svc_l2', 32 | 'logreg_l1', 'logreg_l2'] 33 | 34 | conn_names = ['corr', 'correlation', 'tangent', 'gl', 'lw', 'oas', 'scov'] 35 | 36 | ### 37 | 38 | atlas_names = ['mayo'] 39 | classifier_names = ['logreg_l2'] 40 | conn_names = ['corr'] 41 | #all_groups = [['AD', 'MCI', 'Normal']] 42 | #all_groups = [['AD', 'MCI'], ['AD', 'Normal'], ['MCI', 'Normal']] 43 | all_groups = [['AD', 'MCI']] 44 | 45 | for atlas_name in atlas_names: 46 | print atlas_name 47 | for conn_name in conn_names: 48 | connclassif = ConnectivityClassifier(dataset.func, mask, 49 | atlas=atlas_name, 50 | dx_group=dataset.dx_group, 51 | n_jobs=20) 52 | connclassif.compute_connectivity(conn_name, confounds=dataset.motions) 53 | for groups in all_groups: 54 | print groups 55 | for classifier_name in classifier_names: 56 | connclassif.classify(dataset=dataset, 57 | groups=groups, 58 | classifier_name=classifier_name) 59 | if len(groups)>2: 60 | cc = [ confusion_matrix(connclassif.yp_[i][1], 61 | connclassif.yp_[i][0]) 62 | for i in range(connclassif.n_iter) ] 63 | t = np.sum(cc, axis=0) 64 | m = t/(1.*np.sum(t, axis=1)[:, np.newaxis]) 65 | plt.imshow(m/np.sum(m, axis=1), interpolation='nearest', 66 | cmap=plt.cm.gray) 67 | output_folder = os.path.join(CACHE_DIR, 'DARTEL_SUBJ', 68 | '_'.join(['conn', atlas_name])) 69 | if not os.path.isdir(output_folder): 70 | os.mkdir(output_folder) 71 | groups_name = '_'.join(groups) 72 | output_file = os.path.join(output_folder, 73 | '_'.join([groups_name, 74 | atlas_name, conn_name, 75 | classifier_name])) 76 | np.savez_compressed(output_file, data=connclassif.scores_) 77 | 78 | -------------------------------------------------------------------------------- /correlation_baseline_rs_fmri_adni.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Wed Jan 14 09:35:32 2015 4 | 5 | @author: mr243268 6 | """ 7 | import os 8 | import numpy as np 9 | from fetch_data import fetch_adni_rs_fmri 10 | from nilearn.datasets import fetch_msdl_atlas 11 | from nilearn.input_data import NiftiMapsMasker 12 | from sklearn.preprocessing import StandardScaler 13 | from sklearn.grid_search import GridSearchCV 14 | import matplotlib.pyplot as plt 15 | 16 | 17 | FIG_PATH = '/disk4t/mehdi/data/tmp/figures' 18 | 19 | def plot_shufflesplit(score, pairwise_groups): 20 | bp = plt.boxplot(score, 0, '', 0) 21 | for key in bp.keys(): 22 | for box in bp[key]: 23 | box.set(linewidth=2) 24 | plt.grid(axis='x') 25 | plt.xlim([.4, 1.]) 26 | plt.xlabel('Accuracy (%)', fontsize=18) 27 | plt.title('Shuffle Split Accuracies ', 28 | fontsize=17) 29 | plt.yticks(range(1,7), ['AD/Normal', 'AD/EMCI', 'AD/LMCI', 'LMCI/Normal', 'LMCI/EMCI', 'EMCI/Normal'], fontsize=18) 30 | #plt.yticks(range(1,7), ['AD/Normal', 'AD/EMCI', 'AD/LMCI', 'EMCI/LMCI', 'EMCI/Normal', 'LMCI/Normal'], fontsize=18) 31 | plt.xticks(np.linspace(0.4,1.0,7), np.arange(40,110,10), fontsize=18) 32 | plt.tight_layout() 33 | for ext in ['png', 'pdf', 'svg']: 34 | fname = '.'.join(['boxplot_adni_baseline_rs_fmri_corr', 35 | ext]) 36 | plt.savefig(os.path.join(FIG_PATH, fname), transparent=True) 37 | 38 | 39 | CACHE_DIR = os.path.join('/', 'disk4t', 'mehdi', 40 | 'data', 'tmp') 41 | 42 | dataset = fetch_adni_rs_fmri() 43 | atlas = fetch_msdl_atlas() 44 | func_files = dataset['func'] 45 | dx_group = np.array(dataset['dx_group']) 46 | idx = {} 47 | for g in ['AD', 'LMCI', 'EMCI', 'Normal']: 48 | idx[g] = np.where(dx_group == g) 49 | 50 | n_subjects = len(func_files) 51 | subjects = [] 52 | corr_feat = [] 53 | corr_mat = [] 54 | for subject_n in range(n_subjects): 55 | filename = func_files[subject_n] 56 | print("Processing file %s" % filename) 57 | print("-- Computing region signals ...") 58 | masker = NiftiMapsMasker(atlas["maps"], 59 | resampling_target="maps", standardize=False, 60 | memory=CACHE_DIR, memory_level=1, verbose=0) 61 | region_ts = masker.fit_transform(filename) 62 | subjects.append(region_ts) 63 | print("-- Computing correlations") 64 | corr_matrix = np.corrcoef(region_ts.T) 65 | corr_feat.append(corr_matrix[np.tril_indices(len(corr_matrix))]) 66 | corr_mat.append(corr_matrix) 67 | 68 | from sklearn.svm import SVC 69 | from sklearn.cross_validation import StratifiedShuffleSplit 70 | 71 | corr_mat = np.array(corr_mat) 72 | #corr_feat = StandardScaler().fit_transform(np.array(corr_feat)) 73 | corr_feat = np.array(corr_feat) 74 | 75 | nb_iter = 100 76 | pg_counter = 0 77 | """ 78 | groups = [['AD', 'Normal'], ['AD', 'EMCI'], ['AD', 'LMCI'], 79 | ['EMCI', 'LMCI'], ['EMCI', 'Normal'], ['LMCI', 'Normal']] 80 | """ 81 | groups = [['AD', 'Normal'], ['AD', 'EMCI'], ['AD', 'LMCI'], 82 | ['LMCI', 'Normal'], ['EMCI', 'LMCI'], ['EMCI', 'Normal']] 83 | score = np.zeros((nb_iter, len(groups))) 84 | for gr in groups: 85 | g1_feat = corr_feat[idx[gr[0]][0]] 86 | g2_feat = corr_feat[idx[gr[1]][0]] 87 | x = np.concatenate((g1_feat, g2_feat), axis=0) 88 | y = np.ones(len(x)) 89 | y[len(x) - len(g2_feat):] = 0 90 | svr = SVC(kernel='linear') 91 | param_grid = {'C': np.logspace(-3,3,7), 'kernel': ['linear']} 92 | estim = GridSearchCV(cv=None, estimator=svr, 93 | param_grid=param_grid, n_jobs=1) 94 | sss = StratifiedShuffleSplit(y, n_iter=nb_iter, test_size=0.1) 95 | # 100 runs with randoms 90% / 10% : StratifiedShuffleSplit 96 | counter = 0 97 | for train, test in sss: 98 | Xtrain, Xtest = x[train], x[test] 99 | Ytrain, Ytest = y[train], y[test] 100 | Yscore = estim.fit(Xtrain,Ytrain) 101 | score[counter, pg_counter] = estim.score(Xtest, Ytest) 102 | counter += 1 103 | pg_counter += 1 104 | 105 | plot_shufflesplit(score, groups) -------------------------------------------------------------------------------- /plot_connectomes.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Thu May 21 17:40:11 2015 4 | 5 | @author: mehdi.rahim@cea.fr 6 | """ 7 | 8 | import os 9 | import numpy as np 10 | import nibabel as nib 11 | from fetch_data import fetch_adni_masks, fetch_adni_rs_fmri, \ 12 | set_cache_base_dir, set_group_indices, \ 13 | fetch_adni_baseline_rs_fmri 14 | from nilearn.datasets import fetch_msdl_atlas 15 | from nilearn.plotting import plot_connectome 16 | from scipy.stats import ttest_1samp 17 | 18 | CACHE_DIR = set_cache_base_dir() 19 | 20 | ############################################################################### 21 | # Atlas 22 | ############################################################################### 23 | 24 | def fetch_atlas(atlas_name): 25 | """Retruns selected atlas path 26 | """ 27 | if atlas_name == 'msdl': 28 | atlas = fetch_msdl_atlas()['maps'] 29 | elif atlas_name == 'harvard_oxford': 30 | # atlas = os.path.join(CACHE_DIR, 'atlas', 31 | # 'HarvardOxford-cortl-prob-2mm.nii.gz') 32 | atlas = os.path.join(CACHE_DIR, 'atlas', 33 | 'HarvardOxford-cortl-maxprob-thr0-2mm.nii.gz') 34 | elif atlas_name == 'juelich': 35 | # atlas = os.path.join(CACHE_DIR, 'atlas', 36 | # 'Juelich-prob-2mm.nii.gz') 37 | atlas = os.path.join(CACHE_DIR, 'atlas', 38 | 'Juelich-maxprob-thr0-2mm.nii.gz') 39 | elif atlas_name == 'mayo': 40 | atlas = os.path.join(CACHE_DIR, 'atlas', 'atlas_68_rois.nii.gz') 41 | elif atlas_name == 'canica': 42 | atlas = os.path.join(CACHE_DIR, 'atlas', 'atlas_canica_61_rois.nii.gz') 43 | return atlas 44 | 45 | 46 | def atlas_to_coords(atlas_name): 47 | """Returns coords of atlas ROIs 48 | """ 49 | 50 | atlas = fetch_atlas('harvard_oxford') 51 | affine = nib.load(atlas).get_affine() 52 | data = nib.load(atlas).get_data() 53 | 54 | centroids = [] 55 | if len(data.shape) == 4: 56 | for i in range(data.shape[-1]): 57 | centroid = np.mean(np.where(data[..., i] == 1), axis=1) 58 | centroid = np.append(centroid, 1) 59 | centroid = np.dot(affine, centroid)[:-1] 60 | centroids.append(centroid) 61 | else: 62 | vals = np.unique(data) 63 | for i in range(len(vals)): 64 | centroid = np.mean(np.where(data == i), axis=1) 65 | centroid = np.append(centroid, 1) 66 | centroid = np.dot(affine, centroid)[:-1] 67 | centroids.append(centroid) 68 | 69 | centroids = np.asarray(centroids) 70 | return centroids 71 | 72 | 73 | ############################################################################### 74 | # coeffs 75 | ############################################################################### 76 | 77 | def retrieve_coeffs(atlas_name, metric, classifier_name, group, n_rois): 78 | ''' boxplot accuracies 79 | ''' 80 | 81 | fname = '_'.join([group[0], group[1], atlas_name, metric, classifier_name]) 82 | input_file = os.path.join(BASE_DIR, fname + '.npz') 83 | 84 | if not os.path.isfile(input_file): 85 | print 'not found' 86 | return 0 87 | else: 88 | data = np.load(input_file)['data'] 89 | coeffs = [] 90 | for d in data: 91 | coeffs.append(d.coef[0, :]) 92 | coeffs = np.asarray(coeffs) 93 | 94 | # T-TEST 95 | threshold = .05 96 | threshold /= n_rois*(n_rois-1)/2. 97 | thresh_log = -np.log10(threshold) 98 | tv, pv = ttest_1samp(coeffs, 0.) 99 | 100 | # Convert in log-scale 101 | pv = -np.log10(pv) 102 | #Locate unsignificant tests 103 | ind_threshold = np.where(pv < thresh_log) 104 | 105 | coeffs = np.mean(coeffs, axis=0) 106 | #and then threshold 107 | coeffs[ind_threshold] = 0 108 | 109 | ind = np.tril_indices(n_rois, k=-1) 110 | m_coeffs = np.zeros((n_rois, n_rois)) 111 | m_coeffs[ind] = coeffs 112 | 113 | m_coeffs = (m_coeffs + m_coeffs.T) / 2. 114 | 115 | return m_coeffs 116 | 117 | 118 | atlas_names = ['canica', 'mayo', 'juelich', 'harvard_oxford'] 119 | atlas_name = 'harvard_oxford' 120 | metric = 'oas' 121 | classifier_name = 'ridge' 122 | groups = [['AD', 'MCI'], ['AD', 'Normal'], ['MCI', 'Normal']] 123 | 124 | BASE_DIR = '/disk4t/mehdi/data/tmp/conn_' + atlas_name 125 | centroids = atlas_to_coords(atlas_name) 126 | adj_mat = retrieve_coeffs(atlas_name, metric, classifier_name, groups[0], len(centroids)) 127 | plot_connectome(adj_mat , centroids, edge_threshold='99.8%') 128 | -------------------------------------------------------------------------------- /plot_accuracies.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Wed May 20 09:31:56 2015 4 | 5 | @author: mehdi.rahim@cea.fr 6 | """ 7 | 8 | import os, glob 9 | import numpy as np 10 | import pandas as pd 11 | import seaborn as sns 12 | import matplotlib.pyplot as plt 13 | 14 | ############################################################################### 15 | # Retrieve scores 16 | ############################################################################### 17 | def retrieve_scores(atlas_name, metric, classifier_name, group): 18 | ''' boxplot accuracies 19 | ''' 20 | 21 | fname = '_'.join([group[0], group[1], atlas_name, metric, classifier_name]) 22 | input_file = os.path.join(BASE_DIR, fname + '.npz') 23 | 24 | if not os.path.isfile(input_file): 25 | return 0 26 | else: 27 | data = np.load(input_file)['data'] 28 | accuracies = [] 29 | for d in data: 30 | accuracies.append(d.score) 31 | 32 | accuracies = np.asarray(accuracies) 33 | if np.median(accuracies) < .5: 34 | accuracies = 1 - accuracies 35 | return accuracies 36 | 37 | ############################################################################### 38 | # FACETGRID plotting 39 | ############################################################################### 40 | def plot_facetgrid(): 41 | ''' FacetGrid (Metric, Classifier) 42 | ''' 43 | dataframe = pd.DataFrame(columns=['accuracy', 'metric', 'classifier', 'group']) 44 | for group in groups: 45 | for metric in metrics: 46 | for classifier_name in classifier_names: 47 | data = retrieve_scores(atlas_name, metric, classifier_name, group) 48 | dictionary = {} 49 | dictionary['accuracy'] = data 50 | dictionary['metric'] = metric 51 | dictionary['classifier'] = classifier_name 52 | dictionary['group'] = '_'.join(group) 53 | df = pd.DataFrame.from_dict(dictionary) 54 | dataframe = pd.concat([dataframe, df]) 55 | 56 | g = sns.FacetGrid(dataframe, row='classifier', col='metric', margin_titles=True) 57 | g.map(sns.barplot, 'group', 'accuracy') 58 | g.savefig('msdl_accuracies.pdf') 59 | g.savefig('msdl_accuracies.png') 60 | 61 | 62 | ############################################################################### 63 | # BOXPLOT plotting 64 | ############################################################################### 65 | def boxplot_grid(atlas_name): 66 | """ Subplots accuracies according to metrics and classfiers. 67 | for a given atlas 68 | """ 69 | plt.figure(figsize=(22, 18)) 70 | for i, metric in enumerate(metrics): 71 | for j, classifier_name in enumerate(classifier_names): 72 | print i, j, len(metrics), len(classifier_names), (j+1) + i*len(classifier_names) 73 | data = [] 74 | group_names = [] 75 | for group in groups: 76 | data.append(retrieve_scores(atlas_name, metric, classifier_name, group)) 77 | group_names.append('/'.join(group)) 78 | plt.subplot(len(metrics), len(classifier_names), 79 | (j + 1) + i * len(classifier_names)) 80 | ax = sns.boxplot(data, names=group_names) 81 | plt.ylim([.3, .9]) 82 | plt.title(classifier_names_full[classifier_name], fontsize=16) 83 | plt.ylabel(metrics_full[metric], fontsize=16) 84 | ax.yaxis.set_label_position('right') 85 | plt.suptitle(atlas_names_full[atlas_name], fontsize=22, y=.95) 86 | plt.savefig(atlas_name + '_grid_metric_classifier.pdf') 87 | plt.savefig(atlas_name + '_grid_metric_classifier.png') 88 | 89 | ############################################################################### 90 | # Main loop 91 | ############################################################################### 92 | 93 | atlas_names = ['canica', 'msdl', 'mayo', 'harvard_oxford', 'juelich'] 94 | atlas_names_full = {'msdl' : 'Atlas MSDL', 95 | 'mayo' : 'Atlas Mayo Clinic', 96 | 'harvard_oxford': 'Atlas Harvard-Oxford', 97 | 'juelich': 'Atlas Juelich', 98 | 'canica': 'CanICA 61 ROIs'} 99 | 100 | atlas_names = ['canica', 'mayo', 'harvard_oxford', 'juelich', 'msdl'] 101 | 102 | metrics = ['gl', 'lw', 'oas', 'scov', 'corr', 'pcorr'] 103 | metrics_full = {'gl' : 'GraphLasso', 104 | 'lw' : 'LedoitWolf', 105 | 'oas': 'OraclApproxShrink', 106 | 'scov' : 'ShrunkCov', 107 | 'corr' : 'Pearson Corr', 108 | 'pcorr' : 'Partial Corr'} 109 | classifier_names = ['logreg_l1', 'logreg_l2', 'ridge', 'svc_l1', 'svc_l2'] 110 | classifier_names_full = {'logreg_l1' : 'LogisticRegression_l1', 111 | 'logreg_l2' : 'LogisticRegression_l2', 112 | 'ridge' : 'RidgeClassifier', 113 | 'svc_l1' : 'SVC_l1', 114 | 'svc_l2' : 'SVC_l2'} 115 | groups = [['AD', 'MCI'], ['AD', 'Normal'], ['MCI', 'Normal']] 116 | 117 | for atlas_name in atlas_names: 118 | print atlas_name 119 | BASE_DIR = '/disk4t/mehdi/data/tmp/conn_' + atlas_name 120 | boxplot_grid(atlas_name) 121 | -------------------------------------------------------------------------------- /covariance_baseline_rs_fmri_adni.py: -------------------------------------------------------------------------------- 1 | """ 2 | Covariance matrix rs fmri ADNI 3 | """ 4 | 5 | import os 6 | from fetch_data.datasets import fetch_adni_rs_fmri 7 | from nilearn.datasets import fetch_msdl_atlas 8 | import numpy as np 9 | import nibabel as nib 10 | from nilearn.input_data import NiftiMapsMasker 11 | from nilearn.plotting import plot_roi, plot_stat_map, plot_img 12 | import matplotlib.pyplot as plt 13 | import matplotlib.cm as cm 14 | from sklearn.covariance import EmpiricalCovariance 15 | 16 | ############################################################################### 17 | ############################################################################### 18 | plotted_subject = 0 # subject to plot 19 | 20 | 21 | FIG_PATH = '/disk4t/mehdi/data/tmp/figures' 22 | 23 | import matplotlib.pyplot as plt 24 | import matplotlib 25 | # Copied from matplotlib 1.2.0 for matplotlib 0.99 compatibility. 26 | _bwr_data = ((0.0, 0.0, 1.0), (1.0, 1.0, 1.0), (1.0, 0.0, 0.0)) 27 | plt.cm.register_cmap(cmap=matplotlib.colors.LinearSegmentedColormap.from_list( 28 | "bwr", _bwr_data)) 29 | 30 | def plot_shufflesplit(score, pairwise_groups): 31 | bp = plt.boxplot(score, 0, '', 0) 32 | for key in bp.keys(): 33 | for box in bp[key]: 34 | box.set(linewidth=2) 35 | plt.grid(axis='x') 36 | plt.xlim([.4, 1.]) 37 | plt.xlabel('Accuracy (%)', fontsize=18) 38 | plt.title('Shuffle Split Accuracies ', 39 | fontsize=17) 40 | plt.yticks(range(1,7), ['AD/Normal', 'AD/EMCI', 'AD/LMCI', 'LMCI/Normal', 'LMCI/EMCI', 'EMCI/Normal'], fontsize=18) 41 | plt.xticks(np.linspace(0.4,1.0,7), np.arange(40,110,10), fontsize=18) 42 | plt.tight_layout() 43 | for ext in ['png', 'pdf', 'svg']: 44 | fname = '.'.join(['boxplot_adni_baseline_rs_fmri', 45 | ext]) 46 | plt.savefig(os.path.join(FIG_PATH, fname), transparent=True) 47 | 48 | 49 | def plot_matrices(cov, prec, title): 50 | """Plot covariance and precision matrices, for a given processing. """ 51 | 52 | prec = prec.copy() # avoid side effects 53 | 54 | # Display sparsity pattern 55 | sparsity = prec == 0 56 | plt.figure() 57 | plt.imshow(sparsity, interpolation="nearest") 58 | plt.title("%s / sparsity" % title) 59 | 60 | # Put zeros on the diagonal, for graph clarity. 61 | size = prec.shape[0] 62 | prec[range(size), range(size)] = 0 63 | span = max(abs(prec.min()), abs(prec.max())) 64 | 65 | # Display covariance matrix 66 | plt.figure() 67 | plt.imshow(cov, interpolation="nearest", 68 | vmin=-1, vmax=1, cmap=plt.cm.get_cmap("bwr")) 69 | plt.colorbar() 70 | plt.title("%s / covariance" % title) 71 | 72 | # Display precision matrix 73 | plt.figure() 74 | plt.imshow(prec, interpolation="nearest", 75 | vmin=-span, vmax=span, 76 | cmap=plt.cm.get_cmap("bwr")) 77 | plt.colorbar() 78 | plt.title("%s / precision" % title) 79 | 80 | ############################################################################### 81 | ############################################################################### 82 | 83 | CACHE_DIR = os.path.join('/', 'disk4t', 'mehdi', 84 | 'data', 'tmp') 85 | 86 | dataset = fetch_adni_rs_fmri() 87 | atlas = fetch_msdl_atlas() 88 | func_files = dataset['func'] 89 | dx_group = np.array(dataset['dx_group']) 90 | idx = {} 91 | for g in ['AD', 'LMCI', 'EMCI', 'Normal']: 92 | idx[g] = np.where(dx_group == g) 93 | 94 | atlas4d = nib.load(atlas['maps']) 95 | atlas4d_data = atlas4d.get_data() 96 | atlas3d_data = np.sum(atlas4d_data, axis=3) 97 | atlas3d = nib.Nifti1Image(atlas3d_data, atlas4d.get_affine()) 98 | 99 | n_subjects = len(func_files) 100 | subjects = [] 101 | cov_feat = [] 102 | 103 | for subject_n in range(n_subjects): 104 | filename = func_files[subject_n] 105 | print("Processing file %s" % filename) 106 | print("-- Computing region signals ...") 107 | masker = NiftiMapsMasker(atlas["maps"], 108 | resampling_target="maps", standardize=False, 109 | memory=CACHE_DIR, memory_level=1, verbose=0) 110 | region_ts = masker.fit_transform(filename) 111 | subjects.append(region_ts) 112 | print("-- Computing covariances") 113 | cov_matrix = np.cov(region_ts.T) 114 | cov_feat.append(cov_matrix[np.tril_indices(len(cov_matrix))]) 115 | 116 | from sklearn.svm import SVC 117 | from sklearn.cross_validation import StratifiedShuffleSplit 118 | 119 | cov_feat = np.array(cov_feat) 120 | 121 | nb_iter = 100 122 | pg_counter = 0 123 | groups = [['AD', 'Normal'], ['AD', 'EMCI'], ['AD', 'LMCI'], 124 | ['EMCI', 'LMCI'], ['EMCI', 'Normal'], ['LMCI', 'Normal']] 125 | score = np.zeros((nb_iter, len(groups))) 126 | for gr in groups: 127 | g1_feat = cov_feat[idx[gr[0]][0]] 128 | g2_feat = cov_feat[idx[gr[1]][0]] 129 | x = np.concatenate((g1_feat, g2_feat), axis=0) 130 | y = np.ones(len(x)) 131 | y[len(x) - len(g2_feat):] = 0 132 | 133 | estim = SVC(kernel='linear') 134 | sss = StratifiedShuffleSplit(y, n_iter=nb_iter, test_size=0.2) 135 | # 1000 runs with randoms 80% / 20% : StratifiedShuffleSplit 136 | counter = 0 137 | for train, test in sss: 138 | Xtrain, Xtest = x[train], x[test] 139 | Ytrain, Ytest = y[train], y[test] 140 | Yscore = estim.fit(Xtrain,Ytrain) 141 | score[counter, pg_counter] = estim.score(Xtest, Ytest) 142 | counter += 1 143 | pg_counter += 1 144 | 145 | plot_shufflesplit(score, groups) -------------------------------------------------------------------------------- /connclassifier.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Tue May 19 15:48:41 2015 4 | 5 | @author: mehdi.rahim@cea.fr 6 | """ 7 | 8 | import os, sys 9 | import numpy as np 10 | import nibabel as nib 11 | from fetch_data import fetch_adni_masks, fetch_adni_rs_fmri, \ 12 | set_cache_base_dir, set_group_indices, \ 13 | fetch_adni_baseline_rs_fmri 14 | from nilearn.input_data import NiftiMapsMasker, NiftiLabelsMasker 15 | from nilearn.datasets import fetch_msdl_atlas 16 | from sklearn.covariance import GraphLassoCV, LedoitWolf, OAS, \ 17 | ShrunkCovariance 18 | from sklearn.linear_model import LogisticRegression, RidgeClassifierCV 19 | from sklearn.svm import LinearSVC 20 | from sklearn.datasets.base import Bunch 21 | from sklearn.cross_validation import StratifiedShuffleSplit 22 | from sklearn.preprocessing import StandardScaler 23 | 24 | from joblib import Parallel, delayed 25 | CACHE_DIR = set_cache_base_dir() 26 | 27 | sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0) 28 | sys.stderr = os.fdopen(sys.stderr.fileno(), 'w', 0) 29 | 30 | ############################################################################### 31 | # Atlas 32 | ############################################################################### 33 | 34 | def fetch_atlas(atlas_name): 35 | """Retruns selected atlas path 36 | """ 37 | if atlas_name == 'msdl': 38 | atlas = fetch_msdl_atlas()['maps'] 39 | elif atlas_name == 'harvard_oxford': 40 | # atlas = os.path.join(CACHE_DIR, 'atlas', 41 | # 'HarvardOxford-cortl-prob-2mm.nii.gz') 42 | atlas = os.path.join(CACHE_DIR, 'atlas', 43 | 'HarvardOxford-cortl-maxprob-thr0-2mm.nii.gz') 44 | elif atlas_name == 'juelich': 45 | # atlas = os.path.join(CACHE_DIR, 'atlas', 46 | # 'Juelich-prob-2mm.nii.gz') 47 | atlas = os.path.join(CACHE_DIR, 'atlas', 48 | 'Juelich-maxprob-thr0-2mm.nii.gz') 49 | 50 | elif atlas_name == 'mayo': 51 | atlas = os.path.join(CACHE_DIR, 'atlas', 'atlas_68_rois.nii.gz') 52 | elif atlas_name == 'canica': 53 | atlas = os.path.join(CACHE_DIR, 'atlas', 'atlas_canica_61_rois.nii.gz') 54 | elif atlas_name == 'canica141': 55 | atlas = os.path.join(CACHE_DIR, 'atlas', 'atlas_canica_141_rois.nii.gz') 56 | elif atlas_name == 'tvmsdl': 57 | atlas = os.path.join(CACHE_DIR, 'atlas', 'atlas_tv_msdl.nii.gz') 58 | return atlas 59 | 60 | 61 | ############################################################################### 62 | # Connectivity 63 | ############################################################################### 64 | from scipy import stats, linalg 65 | 66 | def partial_corr(C): 67 | """ 68 | Returns the sample linear partial correlation coefficients between pairs of variables in C, controlling 69 | for the remaining variables in C. 70 | 71 | 72 | Parameters 73 | ---------- 74 | C : array-like, shape (n, p) 75 | Array with the different variables. Each column of C is taken as a variable 76 | 77 | 78 | Returns 79 | ------- 80 | P : array-like, shape (p, p) 81 | P[i, j] contains the partial correlation of C[:, i] and C[:, j] controlling 82 | for the remaining variables in C. 83 | """ 84 | 85 | C = np.asarray(C) 86 | p = C.shape[1] 87 | P_corr = np.zeros((p, p), dtype=np.float) 88 | for i in range(p): 89 | P_corr[i, i] = 1 90 | for j in range(i+1, p): 91 | idx = np.ones(p, dtype=np.bool) 92 | idx[i] = False 93 | idx[j] = False 94 | beta_i = linalg.lstsq(C[:, idx], C[:, j])[0] 95 | beta_j = linalg.lstsq(C[:, idx], C[:, i])[0] 96 | 97 | res_j = C[:, j] - C[:, idx].dot( beta_i) 98 | res_i = C[:, i] - C[:, idx].dot(beta_j) 99 | 100 | corr = stats.pearsonr(res_i, res_j)[0] 101 | P_corr[i, j] = corr 102 | P_corr[j, i] = corr 103 | 104 | return P_corr 105 | 106 | 107 | 108 | def compute_connectivity_subject(conn, func, masker): 109 | """ Returns connectivity of one fMRI for a given atlas 110 | """ 111 | ts = masker.fit_transform(func) 112 | 113 | if conn == 'gl': 114 | fc = GraphLassoCV(max_iter=1000) 115 | elif conn == 'lw': 116 | fc = LedoitWolf() 117 | elif conn == 'oas': 118 | fc = OAS() 119 | elif conn == 'scov': 120 | fc = ShrunkCovariance() 121 | elif conn == 'corr' or conn == 'pcorr': 122 | fc = Bunch(covariance_=0, precision_=0) 123 | 124 | if conn == 'corr' or conn == 'pcorr': 125 | fc.covariance_ = np.corrcoef(ts) 126 | fc.precision_ = partial_corr(ts) 127 | else: 128 | fc.fit(ts) 129 | ind = np.tril_indices(ts.shape[1], k=-1) 130 | return fc.covariance_[ind], fc.precision_[ind] 131 | 132 | def compute_connectivity_subjects(func_list, atlas, mask, conn, n_jobs=-1): 133 | """ Returns connectivities for all subjects 134 | tril matrix n_subjects * n_rois_tril 135 | """ 136 | if len(nib.load(atlas).shape) == 4: 137 | masker = NiftiMapsMasker(maps_img=atlas, mask_img=mask, 138 | detrend=True, low_pass=.1, high_pass=.01, t_r=3., 139 | resampling_target='data', smoothing_fwhm=6, 140 | memory=CACHE_DIR, memory_level=2) 141 | else: 142 | masker = NiftiLabelsMasker(labels_img=atlas, mask_img=mask, t_r=3., 143 | detrend=True, low_pass=.1, high_pass=.01, 144 | resampling_target='data', smoothing_fwhm=6, 145 | memory=CACHE_DIR, memory_level=2) 146 | 147 | p = Parallel(n_jobs=n_jobs, verbose=5)(delayed( 148 | compute_connectivity_subject)(conn, func, masker)\ 149 | for func in func_list) 150 | return np.asarray(p) 151 | 152 | 153 | ############################################################################### 154 | # Classification 155 | ############################################################################### 156 | def train_and_test(classifier, X, y, train, test): 157 | """ Returns accuracy and coeffs for a train and test 158 | """ 159 | classifier.fit(X[train, :], y[train]) 160 | score = classifier.score(X[test, :], y[test]) 161 | B = Bunch(score=score, coef=classifier.coef_) 162 | return B 163 | 164 | def classify_connectivity(X, y, classifier_name, n_jobs=-1): 165 | """ Returns 100 shuffle split scores 166 | """ 167 | if classifier_name == 'logreg_l1': 168 | classifier = LogisticRegression(penalty='l1', dual=False, 169 | random_state=42) 170 | elif classifier_name == 'logreg_l2': 171 | classifier = LogisticRegression(penalty='l2', random_state=42) 172 | elif classifier_name == 'ridge': 173 | classifier = RidgeClassifierCV(alphas=np.logspace(-3, 3, 7)) 174 | elif classifier_name == 'svc_l2': 175 | classifier = LinearSVC(penalty='l2', random_state=42) 176 | elif classifier_name == 'svc_l1': 177 | classifier = LinearSVC(penalty='l1', dual=False, random_state=42) 178 | 179 | p = Parallel(n_jobs=n_jobs, verbose=5)(delayed(train_and_test)( 180 | classifier, X, y, train, test) for train, test in sss) 181 | return p 182 | 183 | 184 | ############################################################################### 185 | # Main loop 186 | ############################################################################### 187 | #dataset = fetch_adni_rs_fmri() 188 | dataset = fetch_adni_baseline_rs_fmri() 189 | 190 | mask = fetch_adni_masks()['mask_petmr'] 191 | 192 | atlas_names = ['canica141', 'canica', 'mayo', 'harvard_oxford', 'juelich', 'msdl'] 193 | atlas_names = ['tvmsdl'] 194 | for atlas_name in atlas_names: 195 | atlas = fetch_atlas(atlas_name) 196 | conn_names = ['gl', 'lw', 'oas', 'scov'] 197 | #conn_names = ['corr'] 198 | for conn_name in conn_names: 199 | conn = compute_connectivity_subjects(list(dataset.func), atlas, mask, 200 | conn=conn_name, n_jobs=-1) 201 | idx = set_group_indices(dataset.dx_group) 202 | idx['MCI'] = np.hstack((idx['EMCI'], idx['LMCI'])) 203 | all_groups = [['AD', 'MCI'], ['AD', 'Normal'], ['MCI', 'Normal']] 204 | 205 | for groups in all_groups: 206 | groups_idx = np.hstack((idx[groups[0]], idx[groups[1]])) 207 | X = conn[groups_idx, 0, :] 208 | #X = StandardScaler().fit_transform(X) 209 | y = np.asarray([1] * len(idx[groups[0]]) + 210 | [0] * len(idx[groups[1]])) 211 | sss = StratifiedShuffleSplit(y, n_iter=100, 212 | test_size=.25, random_state=42) 213 | 214 | classifier_names = ['ridge', 'svc_l1', 'svc_l2', 215 | 'logreg_l1', 'logreg_l2'] 216 | 217 | for classifier_name in classifier_names: 218 | print atlas_name, conn_name, groups, classifier_name 219 | p = classify_connectivity(X, y, classifier_name) 220 | output_folder = os.path.join(CACHE_DIR, 221 | '_'.join(['conn', atlas_name])) 222 | if not os.path.isdir(output_folder): 223 | os.mkdir(output_folder) 224 | output_file = os.path.join(output_folder, 225 | '_'.join([groups[0], groups[1], 226 | atlas_name, conn_name, 227 | classifier_name])) 228 | np.savez_compressed(output_file, data=p) 229 | -------------------------------------------------------------------------------- /base_connectivity_classifier.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Thu May 28 09:24:06 2015 4 | 5 | @author: mehdi.rahim@cea.fr 6 | """ 7 | import numpy as np 8 | from joblib import Parallel, delayed 9 | from sklearn.linear_model import LogisticRegression, RidgeClassifierCV 10 | from sklearn.svm import LinearSVC 11 | from sklearn.datasets.base import Bunch 12 | from sklearn.cross_validation import StratifiedShuffleSplit, ShuffleSplit 13 | from fetch_data import set_group_indices, set_cache_base_dir 14 | from sklearn.base import BaseEstimator, TransformerMixin 15 | from sklearn.metrics import accuracy_score 16 | from base_connectivity import Connectivity 17 | 18 | CACHE_DIR = set_cache_base_dir() 19 | 20 | 21 | ############################################################################### 22 | # Classification 23 | ############################################################################### 24 | def StratifiedSubjectShuffleSplit(dataset, groups, n_iter=100, test_size=.3, 25 | random_state=42): 26 | """ Stratified ShuffleSplit on subjects 27 | (train and test size may change depending on the number of acquistions)""" 28 | 29 | idx = set_group_indices(dataset.dx_group) 30 | groups_idx = np.hstack([idx[group] for group in groups]) 31 | 32 | subjects = np.asarray(dataset.subjects) 33 | subjects = subjects[groups_idx] 34 | 35 | dx = np.asarray(dataset.dx_group) 36 | dx = dx[groups_idx] 37 | 38 | # extract unique subject ids and dx 39 | subjects_unique_values, \ 40 | subjects_unique_indices = np.unique(subjects, return_index=True) 41 | 42 | # extract indices for the needed groups 43 | dx_unique_values = dx[subjects_unique_indices] 44 | y = dx_unique_values 45 | 46 | # generate folds stratified on dx 47 | sss = StratifiedShuffleSplit(y, n_iter=n_iter, test_size=test_size, 48 | random_state=random_state) 49 | ssss = [] 50 | for tr, ts in sss: 51 | # get training subjects 52 | subjects_tr = subjects_unique_values[tr] 53 | 54 | # get testing subjects 55 | subjects_ts = subjects_unique_values[ts] 56 | 57 | # get all subject indices 58 | train = [] 59 | test = [] 60 | for subj in subjects_tr: 61 | train.extend(np.where(subjects == subj)[0]) 62 | for subj in subjects_ts: 63 | test.extend(np.where(subjects == subj)[0]) 64 | 65 | # append ssss 66 | ssss.append([train, test]) 67 | return ssss 68 | 69 | 70 | def SubjectShuffleSplit(dataset, groups, n_iter=100, 71 | test_size=.3, random_state=42): 72 | """ Specific ShuffleSplit (train on all subject images, 73 | but test only on one image of the remaining subjects)""" 74 | 75 | idx = set_group_indices(dataset.dx_group) 76 | groups_idx = np.hstack([idx[group] for group in groups]) 77 | 78 | subjects = np.asarray(dataset.subjects) 79 | subjects = subjects[groups_idx] 80 | subjects_unique = np.unique(subjects) 81 | 82 | n = len(subjects_unique) 83 | ss = ShuffleSplit(n, n_iter=n_iter, 84 | test_size=test_size, random_state=random_state) 85 | 86 | subj_ss = [] 87 | for train, test in ss: 88 | train_set = np.array([], dtype=int) 89 | for subj in subjects_unique[train]: 90 | subj_ind = np.where(subjects == subj) 91 | train_set = np.concatenate((train_set, subj_ind[0])) 92 | test_set = np.array([], dtype=int) 93 | for subj in subjects_unique[test]: 94 | subj_ind = np.where(subjects == subj) 95 | test_set = np.concatenate((test_set, subj_ind[0])) 96 | subj_ss.append([train_set, test_set]) 97 | return subj_ss 98 | 99 | 100 | def train_and_test(classifier, X, y, train, test, subjects=None): 101 | """ Returns accuracy and coeffs for a train and test 102 | """ 103 | classifier.fit(X[train, :], y[train]) 104 | score = classifier.score(X[test, :], y[test]) 105 | yp = classifier.predict(X[test, :]) 106 | yd = classifier.decision_function(X[test, :]) 107 | B = Bunch(score=score, coef=classifier.coef_, 108 | y_dec=yd, 109 | y=y[test], 110 | y_pred=yp, 111 | subj=subjects[test]) 112 | return B 113 | 114 | 115 | def average_predictions(y, decision_function, subjects): 116 | """ Returns a score averaged on subject acquisitions 117 | """ 118 | # Compute subj average decision_function 119 | subjects_unique_values, \ 120 | subjects_unique_indices = np.unique(subjects, return_index=True) 121 | 122 | decision_function_unique = [np.mean(decision_function[subjects == subj]) 123 | for subj in subjects_unique_values] 124 | decision_function_unique = np.array(decision_function_unique) 125 | 126 | # Threshold for the prediction 127 | yp = np.zeros(decision_function_unique.shape, dtype=np.int) 128 | yp[decision_function_unique > 0] = 1 129 | 130 | # Compute accuracy 131 | score = accuracy_score(y[subjects_unique_indices], yp) 132 | return (decision_function_unique, yp, y[subjects_unique_indices], y, 133 | subjects_unique_indices, score) 134 | 135 | 136 | def classify_connectivity(X, y, sss, classifier_name, n_jobs=-1, 137 | subjects=None): 138 | """ Returns 100 shuffle split scores 139 | """ 140 | if classifier_name == 'logreg_l1': 141 | classifier = LogisticRegression(penalty='l1', dual=False, 142 | random_state=42) 143 | elif classifier_name == 'logreg_l2': 144 | classifier = LogisticRegression(penalty='l2', random_state=42) 145 | elif classifier_name == 'ridge': 146 | classifier = RidgeClassifierCV(alphas=np.logspace(-3, 3, 7)) 147 | elif classifier_name == 'svc_l2': 148 | classifier = LinearSVC(penalty='l2', random_state=42) 149 | elif classifier_name == 'svc_l1': 150 | classifier = LinearSVC(penalty='l1', dual=False, random_state=42) 151 | 152 | p = Parallel(n_jobs=n_jobs, verbose=5)( 153 | delayed(train_and_test)(classifier, X, y, train, test, subjects) 154 | for train, test in sss) 155 | return np.asarray(p) 156 | 157 | 158 | class ConnectivityClassifier(BaseEstimator, TransformerMixin): 159 | """ Connectivity based binary classification 160 | Parameters 161 | ---------- 162 | Attributes 163 | ---------- 164 | scores_ 165 | coefs_ 166 | """ 167 | 168 | def __init__(self, imgs, mask, atlas, dx_group, rois=False, n_iter=100, 169 | memory=CACHE_DIR, memory_level=2, n_jobs=-1, random_state=42): 170 | """ initialization 171 | """ 172 | self.imgs = np.array(imgs) 173 | self.mask = mask 174 | self.atlas = atlas 175 | self.rois = rois 176 | self.n_jobs = n_jobs 177 | self.n_iter = n_iter 178 | self.memory = memory 179 | self.memory_level = memory_level 180 | self.idx = set_group_indices(dx_group) 181 | self.random_state = random_state 182 | 183 | def compute_connectivity(self, metric, confounds=None): 184 | """ Return covariance matrix 185 | """ 186 | conn = Connectivity(self.atlas, metric, self.mask, self.rois, 187 | memory=CACHE_DIR, n_jobs=self.n_jobs, 188 | smoothing_fwhm=None) 189 | self.connectivity = conn.fit(self.imgs, confounds) 190 | 191 | def classify(self, dataset=None, groups=['AD', 'MCI'], 192 | classifier_name='logreg_l2'): 193 | """ Returns accuracy scores 194 | """ 195 | if hasattr(self, 'connectivity'): 196 | groups_idx = np.hstack([self.idx[group] for group in groups]) 197 | subjects = np.array(dataset.subjects) 198 | subjects = subjects[groups_idx] 199 | y = np.hstack([[i] * len(self.idx[group]) 200 | for i, group in enumerate(groups)]) 201 | 202 | X = self.connectivity[groups_idx, :] 203 | 204 | if dataset is None: 205 | sss = StratifiedShuffleSplit(y, n_iter=self.n_iter, 206 | test_size=.25, 207 | random_state=self.random_state) 208 | else: 209 | sss = StratifiedSubjectShuffleSplit(dataset, groups, 210 | n_iter=self.n_iter, 211 | test_size=.3, 212 | random_state=self.random_state) 213 | 214 | # sss = SubjectShuffleSplit(dataset, groups, n_iter=self.n_iter, 215 | # test_size=.2, 216 | # random_state=self.random_state) 217 | 218 | results = classify_connectivity(X, y, sss, classifier_name, 219 | n_jobs=self.n_jobs, 220 | subjects=subjects) 221 | 222 | self.y_pred_ = map(lambda r: r['y_pred'], results) 223 | self.y_dec_ = map(lambda r: r['y_dec'], results) 224 | self.y_ = map(lambda r: r['y'], results) 225 | self.coefs_ = map(lambda r: r['coef'], results) 226 | self.scores_ = map(lambda r: r['score'], results) 227 | self.subj_ = map(lambda r: r['subj'], results) 228 | 229 | self.results_ = map(average_predictions, self.y_, self.y_dec_, 230 | self.subj_) 231 | 232 | self.scores_ = np.asarray(self.scores_) 233 | self.coefs_ = np.asarray(self.coefs_) 234 | self.subj_ = np.asarray(self.subj_) 235 | else: 236 | raise ValueError('Connectivity not yet computed !') 237 | -------------------------------------------------------------------------------- /base_rtov_connectivity.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Wed Jun 3 13:08:19 2015 4 | 5 | @author: mehdi.rahim@cea.fr 6 | """ 7 | 8 | ############################################################################### 9 | # Connectivity 10 | ############################################################################### 11 | 12 | import os 13 | import numpy as np 14 | from scipy import stats, linalg 15 | from sklearn.covariance import GraphLassoCV, LedoitWolf, OAS, \ 16 | ShrunkCovariance 17 | 18 | from sklearn.datasets.base import Bunch 19 | from sklearn.base import BaseEstimator, TransformerMixin 20 | from nilearn.input_data import NiftiLabelsMasker, NiftiMapsMasker, NiftiMasker 21 | import nibabel as nib 22 | from joblib import Parallel, delayed, Memory 23 | from nilearn.datasets import fetch_msdl_atlas 24 | from fetch_data import set_cache_base_dir 25 | from embedding import CovEmbedding, vec_to_sym 26 | 27 | 28 | CACHE_DIR = set_cache_base_dir() 29 | 30 | def fetch_atlas(atlas_name): 31 | """Retruns selected atlas path 32 | """ 33 | if atlas_name == 'msdl': 34 | atlas = fetch_msdl_atlas()['maps'] 35 | elif atlas_name == 'harvard_oxford': 36 | atlas = os.path.join(CACHE_DIR, 'atlas', 37 | 'HarvardOxford-cortl-maxprob-thr0-2mm.nii.gz') 38 | elif atlas_name == 'juelich': 39 | atlas = os.path.join(CACHE_DIR, 'atlas', 40 | 'Juelich-maxprob-thr0-2mm.nii.gz') 41 | elif atlas_name == 'mayo': 42 | atlas = os.path.join(CACHE_DIR, 'atlas', 'atlas_68_rois.nii.gz') 43 | elif atlas_name == 'canica': 44 | atlas = os.path.join(CACHE_DIR, 'atlas', 'atlas_canica_61_rois.nii.gz') 45 | elif atlas_name == 'canica141': 46 | atlas = os.path.join(CACHE_DIR, 'atlas', 'atlas_canica_141_rois.nii.gz') 47 | elif atlas_name == 'tvmsdl': 48 | atlas = os.path.join(CACHE_DIR, 'atlas', 'atlas_tv_msdl.nii.gz') 49 | return atlas 50 | 51 | def partial_corr(C): 52 | """ 53 | Returns the sample linear partial correlation coefficients 54 | between pairs of variables in C, controlling 55 | for the remaining variables in C. 56 | 57 | 58 | Parameters 59 | ---------- 60 | C : array-like, shape (n, p) 61 | Array with the different variables. 62 | Each column of C is taken as a variable 63 | 64 | 65 | Returns 66 | ------- 67 | P : array-like, shape (p, p) 68 | P[i, j] contains the partial correlation 69 | of C[:, i] and C[:, j] controlling 70 | for the remaining variables in C. 71 | """ 72 | 73 | C = np.asarray(C) 74 | p = C.shape[1] 75 | P_corr = np.zeros((p, p), dtype=np.float) 76 | for i in range(p): 77 | P_corr[i, i] = 1 78 | for j in range(i+1, p): 79 | idx = np.ones(p, dtype=np.bool) 80 | idx[i] = False 81 | idx[j] = False 82 | beta_i = linalg.lstsq(C[:, idx], C[:, j])[0] 83 | beta_j = linalg.lstsq(C[:, idx], C[:, i])[0] 84 | 85 | res_j = C[:, j] - C[:, idx].dot( beta_i) 86 | res_i = C[:, i] - C[:, idx].dot(beta_j) 87 | 88 | corr = stats.pearsonr(res_i, res_j)[0] 89 | P_corr[i, j] = corr 90 | P_corr[j, i] = corr 91 | 92 | return P_corr 93 | 94 | 95 | def do_mask_img(func, masker): 96 | return masker.fit_transform(func) 97 | 98 | 99 | 100 | def compute_connectivity_voxel(roi, voxel, conn): 101 | """ Returns connectivity of one voxel for a given roi 102 | """ 103 | 104 | if conn == 'gl': 105 | fc = GraphLassoCV(max_iter=1000) 106 | elif conn == 'lw': 107 | fc = LedoitWolf() 108 | elif conn == 'oas': 109 | fc = OAS() 110 | elif conn == 'scov': 111 | fc = ShrunkCovariance() 112 | 113 | ts = np.array([roi, voxel]).T 114 | 115 | if conn == 'corr' or conn == 'pcorr': 116 | cov = np.corrcoef(ts)[0, 1] 117 | else: 118 | fc.fit(ts) 119 | cov = fc.covariance_[0, 0] 120 | 121 | return cov 122 | 123 | 124 | def compute_connectivity_subject(func, masker_ROI, masker_vox, metric): 125 | """ 126 | For each subject : 127 | 1. mask rois and voxels 128 | 2. compute connectivities 129 | """ 130 | 131 | print func 132 | ts_rois = masker_ROI.fit_transform(func) 133 | ts_vox = masker_vox.fit_transform(func) 134 | 135 | fc_ = np.empty((ts_rois.shape[1], ts_vox.shape[1])) 136 | for i, roi in enumerate(ts_rois.T): 137 | print 'ROI ', i 138 | for j, vox in enumerate(ts_vox.T): 139 | fc_[i, j] = compute_connectivity_voxel(roi, vox, metric) 140 | return fc_ 141 | 142 | 143 | def compute_connectivity_subjects(imgs, n_jobs, masker_ROI, masker_vox, metric): 144 | """ All subjects 145 | """ 146 | return Parallel(n_jobs=n_jobs, verbose=5)(delayed(compute_connectivity_subject)\ 147 | (img, masker_ROI, masker_vox, metric) for img in imgs) 148 | 149 | 150 | class ROItoVoxConnectivity(BaseEstimator, TransformerMixin): 151 | """ ROI to Voxel Connectivity Estimator 152 | computes the functional connectivity of a list of 4D niimgs, 153 | according to ROIs defined on an atlas. 154 | First, the timeseries on ROIs are extracted. 155 | Then, the connectivity is computed for each pair of ROIs. 156 | The result is a ravel of half symmetric matrix. 157 | 158 | Parameters 159 | ---------- 160 | atlas : atlas filepath 161 | metric : metric name (gl, lw, oas, scov, corr, pcorr) 162 | mask : mask filepath 163 | detrend : masker param 164 | low_pass: masker param 165 | high_pass : masker param 166 | t_r : masker param 167 | smoothing : masker param 168 | resampling_target : masker param 169 | memory : masker param 170 | memory_level : masker param 171 | n_jobs : masker param 172 | 173 | Attributes 174 | ---------- 175 | fc_ : functional connectivity (covariance and precision) 176 | """ 177 | 178 | def __init__(self, atlas_name, metric, mask, detrend=True, 179 | low_pass=.1, high_pass=.01, t_r=3., 180 | resampling_target='data', smoothing_fwhm=6., 181 | memory='', memory_level=2, n_jobs=1): 182 | """ - Setting attributes 183 | - Preparing maskers 184 | """ 185 | self.atlas = fetch_atlas(atlas_name) 186 | self.metric = metric 187 | self.mask = mask 188 | self.n_jobs = n_jobs 189 | self.masker_vox = NiftiMasker(mask_img=self.mask, 190 | detrend=detrend, 191 | low_pass=low_pass, 192 | high_pass=high_pass, 193 | t_r=t_r, 194 | smoothing_fwhm=smoothing_fwhm, 195 | memory=memory, 196 | memory_level=memory_level) 197 | if len(nib.load(self.atlas).shape) == 4: 198 | self.masker_ROI = NiftiMapsMasker(maps_img=self.atlas, 199 | mask_img=self.mask, 200 | detrend=detrend, 201 | low_pass=low_pass, 202 | high_pass=high_pass, 203 | t_r=t_r, 204 | resampling_target=resampling_target, 205 | smoothing_fwhm=smoothing_fwhm, 206 | memory=memory, 207 | memory_level=memory_level) 208 | else: 209 | self.masker_ROI = NiftiLabelsMasker(labels_img=self.atlas, 210 | mask_img=self.mask, 211 | detrend=detrend, 212 | low_pass=low_pass, 213 | high_pass=high_pass, 214 | t_r=t_r, 215 | resampling_target=resampling_target, 216 | smoothing_fwhm=smoothing_fwhm, 217 | memory=memory, 218 | memory_level=memory_level) 219 | 220 | 221 | def fit_subjects(self, imgs): 222 | """ All subjects 223 | """ 224 | joblibMemory = Memory(CACHE_DIR, mmap_mode='r+', verbose=5) 225 | return np.asarray(joblibMemory.cache(compute_connectivity_subjects)\ 226 | (imgs, self.n_jobs, self.masker_ROI, self.masker_vox, self.metric)) 227 | 228 | def fit2(self, imgs): 229 | """ compute connectivities 230 | """ 231 | if self.metric == 'correlation' or \ 232 | self.metric == 'partial correlation' or \ 233 | self.metric == 'tangent' : 234 | ts = Parallel(n_jobs=self.n_jobs, verbose=5)(delayed( 235 | do_mask_img)(func, self.masker) for func in imgs) 236 | cov_embedding = CovEmbedding( kind=self.metric ) 237 | p = np.asarray(vec_to_sym(cov_embedding.fit_transform(ts))) 238 | ind = np.tril_indices(p.shape[1], k=-1) 239 | 240 | self.fc_ = np.asarray([p[i, ...][ind] for i in range(p.shape[0])]) 241 | else: 242 | p = Parallel(n_jobs=self.n_jobs, verbose=5)(delayed( 243 | compute_connectivity_subject)(self.metric, func, 244 | self.masker) for func in imgs) 245 | self.fc_ = np.asarray(p)[:, 0, :] 246 | return self.fc_ -------------------------------------------------------------------------------- /base_network_connectivity.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Mon Jun 8 09:27:14 2015 4 | 5 | @author: mehdi.rahim@cea.fr 6 | """ 7 | 8 | import os 9 | import numpy as np 10 | from scipy import stats, linalg 11 | from sklearn.covariance import GraphLassoCV, LedoitWolf, OAS, \ 12 | ShrunkCovariance 13 | 14 | from sklearn.datasets.base import Bunch 15 | from sklearn.base import BaseEstimator, TransformerMixin 16 | from nilearn.input_data import NiftiLabelsMasker, NiftiMapsMasker 17 | import nibabel as nib 18 | from joblib import Parallel, delayed 19 | from nilearn.datasets import fetch_msdl_atlas 20 | from fetch_data import set_cache_base_dir 21 | from embedding import CovEmbedding, vec_to_sym 22 | 23 | 24 | CACHE_DIR = set_cache_base_dir() 25 | 26 | 27 | def atlas_rois_to_coords(atlas_name, rois): 28 | """Returns coords of atlas ROIs 29 | """ 30 | 31 | atlas = fetch_atlas(atlas_name) 32 | affine = nib.load(atlas).get_affine() 33 | data = nib.load(atlas).get_data() 34 | centroids = [] 35 | if len(data.shape) == 4: 36 | for i in range(data.shape[-1]): 37 | voxels = np.where(data[..., i] > 0) 38 | centroid = np.mean(voxels, axis=1) 39 | dvoxels = data[..., i] 40 | dvoxels = dvoxels[voxels] 41 | voxels = np.asarray(voxels).T 42 | centroid = np.average(voxels, axis=0, weights=dvoxels) 43 | centroid = np.append(centroid, 1) 44 | centroid = np.dot(affine, centroid)[:-1] 45 | centroids.append(centroid) 46 | else: 47 | vals = np.unique(data) 48 | for i in range(len(vals)): 49 | centroid = np.mean(np.where(data == i), axis=1) 50 | centroid = np.append(centroid, 1) 51 | centroid = np.dot(affine, centroid)[:-1] 52 | centroids.append(centroid) 53 | 54 | centroids = np.asarray(centroids)[rois] 55 | return centroids 56 | 57 | 58 | 59 | def fetch_dmn_atlas(atlas_name): 60 | """ Returns a bunch containing the DMN rois 61 | and their coordinates 62 | """ 63 | 64 | if atlas_name == 'msdl': 65 | rois = np.arange(3, 7) 66 | rois_names = ['L-DMN', 'M-DMN', 'F-DMN', 'R-DMN'] 67 | elif atlas_name == 'mayo': 68 | rois = np.concatenate(( range(39, 43), range(47, 51), 69 | range(52, 56), range(62, 68) )) 70 | rois_names = ['adDMN_L', 'adDMN_R', 'avDMN_L', 'avDMN_R', 'dDMN_L_Lat', 71 | 'dDMN_L_Med', 'dDMN_R_Lat', 'dDMN_R_Med', 'pDMN_L_Lat', 72 | 'pDMN_L_Med', 'pDMN_R_Lat', 'pDMN_R_Med', 'tDMN_L', 73 | 'tDMN_R', 'vDMN_L_Lat', 'vDMN_L_Med', 'vDMN_R_Lat', 74 | 'vDMN_R_Med'] 75 | elif atlas_name == 'canica': 76 | rois = np.concatenate((range(20, 23), [36])) 77 | rois_names = ['DMN']*4 78 | n_rois = len(rois) 79 | centroids = atlas_rois_to_coords(atlas_name, rois) 80 | 81 | return Bunch(n_rois=n_rois, rois=rois, rois_names=rois_names, 82 | rois_centroids=centroids) 83 | 84 | 85 | def fetch_atlas(atlas_name): 86 | """Retruns selected atlas path 87 | """ 88 | if atlas_name == 'msdl': 89 | atlas = fetch_msdl_atlas()['maps'] 90 | elif atlas_name == 'harvard_oxford': 91 | atlas = os.path.join(CACHE_DIR, 'atlas', 92 | 'HarvardOxford-cortl-maxprob-thr0-2mm.nii.gz') 93 | elif atlas_name == 'juelich': 94 | atlas = os.path.join(CACHE_DIR, 'atlas', 95 | 'Juelich-maxprob-thr0-2mm.nii.gz') 96 | elif atlas_name == 'mayo': 97 | atlas = os.path.join(CACHE_DIR, 'atlas', 'atlas_68_rois.nii.gz') 98 | elif atlas_name == 'canica': 99 | atlas = os.path.join(CACHE_DIR, 'atlas', 'atlas_canica_61_rois.nii.gz') 100 | elif atlas_name == 'canica141': 101 | atlas = os.path.join(CACHE_DIR, 'atlas', 'atlas_canica_141_rois.nii.gz') 102 | elif atlas_name == 'tvmsdl': 103 | atlas = os.path.join(CACHE_DIR, 'atlas', 'atlas_tv_msdl.nii.gz') 104 | return atlas 105 | 106 | def partial_corr(C): 107 | """ 108 | Returns the sample linear partial correlation coefficients 109 | between pairs of variables in C, controlling 110 | for the remaining variables in C. 111 | 112 | 113 | Parameters 114 | ---------- 115 | C : array-like, shape (n, p) 116 | Array with the different variables. 117 | Each column of C is taken as a variable 118 | 119 | 120 | Returns 121 | ------- 122 | P : array-like, shape (p, p) 123 | P[i, j] contains the partial correlation 124 | of C[:, i] and C[:, j] controlling 125 | for the remaining variables in C. 126 | """ 127 | 128 | C = np.asarray(C) 129 | p = C.shape[1] 130 | P_corr = np.zeros((p, p), dtype=np.float) 131 | for i in range(p): 132 | P_corr[i, i] = 1 133 | for j in range(i+1, p): 134 | idx = np.ones(p, dtype=np.bool) 135 | idx[i] = False 136 | idx[j] = False 137 | beta_i = linalg.lstsq(C[:, idx], C[:, j])[0] 138 | beta_j = linalg.lstsq(C[:, idx], C[:, i])[0] 139 | 140 | res_j = C[:, j] - C[:, idx].dot( beta_i) 141 | res_i = C[:, i] - C[:, idx].dot(beta_j) 142 | 143 | corr = stats.pearsonr(res_i, res_j)[0] 144 | P_corr[i, j] = corr 145 | P_corr[j, i] = corr 146 | 147 | return P_corr 148 | 149 | 150 | def do_mask_img(func, masker): 151 | return masker.fit_transform(func) 152 | 153 | def compute_network_connectivity_subject(conn, func, masker, rois): 154 | """ Returns connectivity of one fMRI for a given atlas 155 | """ 156 | ts = masker.fit_transform(func) 157 | ts = np.asarray(ts)[ :, rois] 158 | 159 | if conn == 'gl': 160 | fc = GraphLassoCV(max_iter=1000) 161 | elif conn == 'lw': 162 | fc = LedoitWolf() 163 | elif conn == 'oas': 164 | fc = OAS() 165 | elif conn == 'scov': 166 | fc = ShrunkCovariance() 167 | 168 | fc = Bunch(covariance_=0, precision_=0) 169 | 170 | if conn == 'corr' or conn == 'pcorr': 171 | fc = Bunch(covariance_=0, precision_=0) 172 | fc.covariance_ = np.corrcoef(ts) 173 | fc.precision_ = partial_corr(ts) 174 | else: 175 | fc.fit(ts) 176 | ind = np.tril_indices(ts.shape[1], k=-1) 177 | return fc.covariance_[ind], fc.precision_[ind] 178 | 179 | 180 | 181 | class NetworkConnectivity(BaseEstimator, TransformerMixin): 182 | """ Connectivity Estimator 183 | computes the functional connectivity of a list of 4D niimgs, 184 | according to ROIs defined on an atlas. 185 | First, the timeseries on ROIs are extracted. 186 | Then, the connectivity is computed for each pair of ROIs. 187 | The result is a ravel of half symmetric matrix. 188 | 189 | Parameters 190 | ---------- 191 | atlas : atlas filepath 192 | metric : metric name (gl, lw, oas, scov, corr, pcorr) 193 | mask : mask filepath 194 | detrend : masker param 195 | low_pass: masker param 196 | high_pass : masker param 197 | t_r : masker param 198 | smoothing : masker param 199 | resampling_target : masker param 200 | memory : masker param 201 | memory_level : masker param 202 | n_jobs : masker param 203 | 204 | Attributes 205 | ---------- 206 | fc_ : functional connectivity (covariance and precision) 207 | """ 208 | 209 | def __init__(self, atlas_name, rois, metric, mask, detrend=True, 210 | low_pass=.1, high_pass=.01, t_r=3., 211 | resampling_target='data', smoothing_fwhm=6., 212 | memory='', memory_level=2, n_jobs=1): 213 | self.atlas = fetch_atlas(atlas_name) 214 | self.rois = np.asarray(rois) 215 | self.metric = metric 216 | self.mask = mask 217 | self.n_jobs = n_jobs 218 | if len(nib.load(self.atlas).shape) == 4: 219 | self.masker = NiftiMapsMasker(maps_img=self.atlas, 220 | mask_img=self.mask, 221 | detrend=detrend, 222 | low_pass=low_pass, 223 | high_pass=high_pass, 224 | t_r=t_r, 225 | resampling_target=resampling_target, 226 | smoothing_fwhm=smoothing_fwhm, 227 | memory=memory, 228 | memory_level=memory_level) 229 | else: 230 | self.masker = NiftiLabelsMasker(labels_img=self.atlas, 231 | mask_img=self.mask, 232 | detrend=detrend, 233 | low_pass=low_pass, 234 | high_pass=high_pass, 235 | t_r=t_r, 236 | resampling_target=resampling_target, 237 | smoothing_fwhm=smoothing_fwhm, 238 | memory=memory, 239 | memory_level=memory_level) 240 | 241 | def fit(self, imgs): 242 | """ compute connectivities 243 | """ 244 | if self.metric == 'correlation' or \ 245 | self.metric == 'partial correlation' or \ 246 | self.metric == 'tangent' : 247 | ts = Parallel(n_jobs=self.n_jobs, verbose=5)(delayed( 248 | do_mask_img)(func, self.masker) for func in imgs) 249 | 250 | #ts = np.asarray(ts)[0, :, self.rois].T 251 | 252 | cov_embedding = CovEmbedding( kind=self.metric ) 253 | p = np.asarray(vec_to_sym(cov_embedding.fit_transform(ts))) 254 | ind = np.tril_indices(p.shape[1], k=-1) 255 | self.fc_ = np.asarray([p[i, ...][ind] for i in range(p.shape[0])]) 256 | else: 257 | p = Parallel(n_jobs=self.n_jobs, verbose=5)(delayed( 258 | compute_network_connectivity_subject)(self.metric, func, 259 | self.masker, self.rois) for func in imgs) 260 | self.fc_ = np.asarray(p)[:, 0, :] 261 | return self.fc_ -------------------------------------------------------------------------------- /base_connectivity.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Wed May 27 15:24:36 2015 4 | 5 | @author: mehdi.rahim@cea.fr 6 | """ 7 | 8 | ############################################################################### 9 | # Connectivity 10 | ############################################################################### 11 | 12 | import os 13 | import numpy as np 14 | from scipy import stats, linalg 15 | from sklearn.covariance import GraphLassoCV, LedoitWolf, OAS, \ 16 | ShrunkCovariance 17 | 18 | from sklearn.datasets.base import Bunch 19 | from sklearn.base import BaseEstimator, TransformerMixin 20 | from nilearn.input_data import NiftiLabelsMasker, NiftiMapsMasker 21 | import nibabel as nib 22 | from joblib import Parallel, delayed 23 | from nilearn.datasets import fetch_msdl_atlas 24 | from fetch_data import set_cache_base_dir 25 | from embedding import CovEmbedding, vec_to_sym 26 | from nilearn.image import index_img 27 | 28 | CACHE_DIR = set_cache_base_dir() 29 | 30 | 31 | def atlas_rois_to_coords(atlas_name, rois): 32 | """Returns coords of atlas ROIs 33 | """ 34 | 35 | affine = nib.load(atlas_name).get_affine() 36 | data = nib.load(atlas_name).get_data() 37 | centroids = [] 38 | if len(data.shape) == 4: 39 | for i in range(data.shape[-1]): 40 | voxels = np.where(data[..., i] > 0) 41 | centroid = np.mean(voxels, axis=1) 42 | dvoxels = data[..., i] 43 | dvoxels = dvoxels[voxels] 44 | voxels = np.asarray(voxels).T 45 | centroid = np.average(voxels, axis=0, weights=dvoxels) 46 | centroid = np.append(centroid, 1) 47 | centroid = np.dot(affine, centroid)[:-1] 48 | centroids.append(centroid) 49 | else: 50 | vals = np.unique(data) 51 | for i in range(len(vals)): 52 | centroid = np.mean(np.where(data == i), axis=1) 53 | centroid = np.append(centroid, 1) 54 | centroid = np.dot(affine, centroid)[:-1] 55 | centroids.append(centroid) 56 | 57 | centroids = np.asarray(centroids)[rois] 58 | return centroids 59 | 60 | def fetch_dmn_atlas(atlas_name, atlas): 61 | """ Returns a bunch containing the DMN rois 62 | and their coordinates 63 | """ 64 | 65 | if atlas_name == 'msdl': 66 | rois = np.arange(3, 7) 67 | rois_names = ['L-DMN', 'M-DMN', 'F-DMN', 'R-DMN'] 68 | elif atlas_name == 'mayo': 69 | rois = np.concatenate((range(39, 43), range(47, 51), 70 | range(52, 56), range(62, 68))) 71 | rois_names = ['adDMN_L', 'adDMN_R', 'avDMN_L', 'avDMN_R', 'dDMN_L_Lat', 72 | 'dDMN_L_Med', 'dDMN_R_Lat', 'dDMN_R_Med', 'pDMN_L_Lat', 73 | 'pDMN_L_Med', 'pDMN_R_Lat', 'pDMN_R_Med', 'tDMN_L', 74 | 'tDMN_R', 'vDMN_L_Lat', 'vDMN_L_Med', 'vDMN_R_Lat', 75 | 'vDMN_R_Med'] 76 | elif atlas_name == 'canica': 77 | rois = np.concatenate((range(20, 23), [36])) 78 | rois_names = ['DMN']*4 79 | n_rois = len(rois) 80 | centroids = atlas_rois_to_coords(atlas, rois) 81 | 82 | return Bunch(n_rois=n_rois, rois=rois, rois_names=rois_names, 83 | rois_centroids=centroids) 84 | 85 | def nii_shape(img): 86 | """ Returns the img shape 87 | """ 88 | 89 | if isinstance(img, nib.Nifti1Image): 90 | return img.shape 91 | else: 92 | return nib.load(img).shape 93 | 94 | def fetch_atlas(atlas_name, rois=False): 95 | """Retruns selected atlas path 96 | """ 97 | 98 | if atlas_name == 'msdl': 99 | atlas = fetch_msdl_atlas()['maps'] 100 | elif atlas_name == 'harvard_oxford': 101 | atlas = os.path.join(CACHE_DIR, 'atlas', 102 | 'HarvardOxford-cortl-maxprob-thr0-2mm.nii.gz') 103 | elif atlas_name == 'juelich': 104 | atlas = os.path.join(CACHE_DIR, 'atlas', 105 | 'Juelich-maxprob-thr0-2mm.nii.gz') 106 | elif atlas_name == 'mayo': 107 | atlas = os.path.join(CACHE_DIR, 'atlas', 'atlas_68_rois.nii.gz') 108 | elif atlas_name == 'canica': 109 | atlas = os.path.join(CACHE_DIR, 'atlas', 'atlas_canica_61_rois.nii.gz') 110 | elif atlas_name == 'canica141': 111 | atlas = os.path.join(CACHE_DIR, 'atlas', 'atlas_canica_141_rois.nii.gz') 112 | elif atlas_name == 'tvmsdl': 113 | atlas = os.path.join(CACHE_DIR, 'atlas', 'atlas_tv_msdl.nii.gz') 114 | 115 | dmn = None 116 | if (atlas_name in ['msdl', 'mayo', 'canica']) and rois: 117 | dmn = fetch_dmn_atlas(atlas_name, atlas) 118 | atlas_img = index_img(atlas, dmn['rois']) 119 | atlas = os.path.join(CACHE_DIR, 'atlas', 'atlas_dmn.nii.gz') 120 | atlas_img.to_filename(atlas) 121 | return atlas, dmn 122 | 123 | def partial_corr(C): 124 | """ 125 | Returns the sample linear partial correlation coefficients 126 | between pairs of variables in C, controlling 127 | for the remaining variables in C. 128 | 129 | 130 | Parameters 131 | ---------- 132 | C : array-like, shape (n, p) 133 | Array with the different variables. 134 | Each column of C is taken as a variable 135 | 136 | 137 | Returns 138 | ------- 139 | P : array-like, shape (p, p) 140 | P[i, j] contains the partial correlation 141 | of C[:, i] and C[:, j] controlling 142 | for the remaining variables in C. 143 | """ 144 | 145 | C = np.asarray(C) 146 | p = C.shape[1] 147 | P_corr = np.zeros((p, p), dtype=np.float) 148 | for i in range(p): 149 | P_corr[i, i] = 1 150 | for j in range(i+1, p): 151 | idx = np.ones(p, dtype=np.bool) 152 | idx[i] = False 153 | idx[j] = False 154 | beta_i = linalg.lstsq(C[:, idx], C[:, j])[0] 155 | beta_j = linalg.lstsq(C[:, idx], C[:, i])[0] 156 | 157 | res_j = C[:, j] - C[:, idx].dot(beta_i) 158 | res_i = C[:, i] - C[:, idx].dot(beta_j) 159 | 160 | corr = stats.pearsonr(res_i, res_j)[0] 161 | P_corr[i, j] = corr 162 | P_corr[j, i] = corr 163 | 164 | return P_corr 165 | 166 | 167 | def do_mask_img(masker, func, confound=None): 168 | """ Masking functional acquisitions 169 | """ 170 | c = None 171 | if not confound is None: 172 | c = np.loadtxt(confound) 173 | return masker.transform(func, c) 174 | 175 | def compute_connectivity_subject(conn, masker, func, confound=None): 176 | """ Returns connectivity of one fMRI for a given atlas 177 | """ 178 | 179 | ts = do_mask_img(masker, func, confound) 180 | 181 | if conn == 'gl': 182 | fc = GraphLassoCV(max_iter=1000) 183 | elif conn == 'lw': 184 | fc = LedoitWolf() 185 | elif conn == 'oas': 186 | fc = OAS() 187 | elif conn == 'scov': 188 | fc = ShrunkCovariance() 189 | 190 | fc = Bunch(covariance_=0, precision_=0) 191 | 192 | if conn == 'corr' or conn == 'pcorr': 193 | fc = Bunch(covariance_=0, precision_=0) 194 | fc.covariance_ = np.corrcoef(ts) 195 | fc.precision_ = partial_corr(ts) 196 | else: 197 | fc.fit(ts) 198 | ind = np.tril_indices(ts.shape[1], k=-1) 199 | return fc.covariance_[ind], fc.precision_[ind] 200 | 201 | 202 | class Connectivity(BaseEstimator, TransformerMixin): 203 | """ Connectivity Estimator 204 | computes the functional connectivity of a list of 4D niimgs, 205 | according to ROIs defined on an atlas. 206 | First, the timeseries on ROIs are extracted. 207 | Then, the connectivity is computed for each pair of ROIs. 208 | The result is a ravel of half symmetric matrix. 209 | 210 | Parameters 211 | ---------- 212 | atlas : atlas filepath 213 | metric : metric name (gl, lw, oas, scov, corr, pcorr) 214 | mask : mask filepath 215 | detrend : masker param 216 | low_pass: masker param 217 | high_pass : masker param 218 | t_r : masker param 219 | smoothing : masker param 220 | resampling_target : masker param 221 | memory : masker param 222 | memory_level : masker param 223 | n_jobs : masker param 224 | 225 | Attributes 226 | ---------- 227 | fc_ : functional connectivity (covariance and precision) 228 | """ 229 | 230 | def __init__(self, atlas_name, metric, mask, rois=False, detrend=True, 231 | low_pass=.1, high_pass=.01, t_r=3., 232 | resampling_target='data', smoothing_fwhm=6., 233 | memory='', memory_level=2, n_jobs=1): 234 | 235 | self.fc_ = None 236 | self.atlas, self.rois = fetch_atlas(atlas_name, rois) 237 | self.metric = metric 238 | self.mask = mask 239 | self.n_jobs = n_jobs 240 | if len(nii_shape(self.atlas)) == 4: 241 | self.masker = NiftiMapsMasker(maps_img=self.atlas, 242 | mask_img=self.mask, 243 | detrend=detrend, 244 | low_pass=low_pass, 245 | high_pass=high_pass, 246 | t_r=t_r, 247 | resampling_target=resampling_target, 248 | smoothing_fwhm=smoothing_fwhm, 249 | memory=memory, 250 | memory_level=memory_level, 251 | verbose=5) 252 | else: 253 | self.masker = NiftiLabelsMasker(labels_img=self.atlas, 254 | mask_img=self.mask, 255 | detrend=detrend, 256 | low_pass=low_pass, 257 | high_pass=high_pass, 258 | t_r=t_r, 259 | resampling_target=resampling_target, 260 | smoothing_fwhm=smoothing_fwhm, 261 | memory=memory, 262 | memory_level=memory_level, 263 | verbose=5) 264 | 265 | def fit(self, imgs, confounds=None): 266 | """ compute connectivities 267 | """ 268 | 269 | self.masker.fit() 270 | if self.metric == 'correlation' or \ 271 | self.metric == 'partial correlation' or \ 272 | self.metric == 'tangent': 273 | 274 | if confounds is None: 275 | ts = Parallel(n_jobs=self.n_jobs, verbose=5)(delayed( 276 | do_mask_img)(self.masker, func) for func in imgs) 277 | else: 278 | ts = Parallel(n_jobs=self.n_jobs, verbose=5)(delayed( 279 | do_mask_img)(self.masker, func, confound) 280 | for func, confound in zip(imgs, confounds)) 281 | 282 | cov_embedding = CovEmbedding(kind=self.metric) 283 | p_ = np.asarray(vec_to_sym(cov_embedding.fit_transform(ts))) 284 | ind = np.tril_indices(p_.shape[1], k=-1) 285 | 286 | self.fc_ = np.asarray([p_[i, ...][ind] for i in range(p_.shape[0])]) 287 | else: 288 | p_ = Parallel(n_jobs=self.n_jobs, verbose=5)(delayed( 289 | compute_connectivity_subject)(self.metric, 290 | self.masker, func, confound) 291 | for func, confound in zip(imgs, confounds)) 292 | 293 | self.fc_ = np.asarray(p_)[:, 0, :] 294 | return self.fc_ 295 | -------------------------------------------------------------------------------- /embedding.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | from math import floor, sqrt 3 | 4 | import numpy as np 5 | from scipy import linalg 6 | 7 | from sklearn.base import BaseEstimator, TransformerMixin, clone 8 | from sklearn.covariance import EmpiricalCovariance 9 | from nilearn._utils.extmath import is_spd 10 | 11 | 12 | def check_mat(mat, prop): 13 | """Raise a ValueError if the input matrix does not satisfy the property. 14 | 15 | Parameters 16 | ---------- 17 | mat : numpy.ndarray 18 | Input array. 19 | 20 | prop : {'square', 'symmetric', 'spd'} 21 | Property to check. 22 | """ 23 | if prop == 'square': 24 | if mat.ndim != 2 or (mat.shape[0] != mat.shape[-1]): 25 | raise ValueError('Expected a square matrix, got array of shape' + 26 | ' {0}.'.format(mat.shape)) 27 | if prop == 'symmetric': 28 | if not np.allclose(mat, mat.T): 29 | raise ValueError('Expected a symmetric matrix.') 30 | 31 | if prop == 'spd': 32 | if not is_spd(mat): 33 | raise ValueError('Expected a symmetric positive definite matrix.') 34 | 35 | 36 | def map_eig(function, vals, vecs): 37 | """Return the symmetric matrix with eigenvectors vecs and eigenvalues 38 | obtained by applying the function to vals. 39 | 40 | Parameters 41 | ---------- 42 | function : function 43 | The function to apply. 44 | 45 | vals : numpy.ndarray, shape (M, ) 46 | Input argument of the function. 47 | 48 | vecs : numpy.ndarray, shape (M, M) 49 | Unitary matrix. 50 | 51 | Returns 52 | ------- 53 | output : numpy.ndarray, shape (M, M) 54 | The symmetric matrix obtained after transforming the eigenvalues, with 55 | eigenvectors the columns of vecs. 56 | """ 57 | return np.dot(vecs * function(vals), vecs.T) 58 | 59 | 60 | def map_sym(function, sym): 61 | """Matrix function, for real symmetric matrices. The function is applied 62 | to the eigenvalues of sym. 63 | 64 | Parameters 65 | ---------- 66 | function : function 67 | The function to apply. 68 | 69 | sym : numpy.ndarray, shape (M, M) 70 | The matrix to be transformed. 71 | 72 | Returns 73 | ------- 74 | output : numpy.ndarray, shape (M, M) 75 | The new symmetric matrix obtained after transforming the eigenvalues. 76 | 77 | Note 78 | ---- 79 | If input matrix is not real symmetric, no error is reported but result will 80 | be wrong. 81 | """ 82 | vals, vecs = linalg.eigh(sym) 83 | return map_eig(function, vals, vecs) 84 | 85 | 86 | def geometric_mean(mats, init=None, max_iter=10, tol=1e-7): 87 | """Compute the geometric mean of symmetric positive definite matrices. 88 | 89 | The geometric mean is the minimizer of the sum of squared distances from an 90 | arbitrary matrix to each input matrix in the manifold of symmetric positive 91 | definite matrices. 92 | 93 | Minimization of the objective function is done by an intrinsic gradient 94 | descent in the manifold: moving from the current point to the next one 95 | along a short geodesic arc in the opposite direction of the 96 | covariant derivative. 97 | 98 | See Algorithm 3 of: 99 | P. Thomas Fletcher, Sarang Joshi. Riemannian Geometry for the 100 | Statistical Analysis of Diffusion Tensor Data. Signal Processing, 2007. 101 | 102 | Parameters 103 | ---------- 104 | mats : list of numpy.ndarray, shape of each (n_features, n_features) 105 | List of matrices whose geometric mean to compute. Raise an error if the 106 | arrays are not all symmetric positive definite of the same shape. 107 | 108 | init : numpy.ndarray, shape (n_features, n_features) or None, optional 109 | Initialization matrix. Raise an error if the array is not symmetric 110 | positive definite of the same shape as the elements of mats. Set to the 111 | arithmetic mean of mats if None. 112 | 113 | max_iter : int, optional (default to 10) 114 | Maximal number of iterations. 115 | 116 | tol : float, optional (default to 1e-7) 117 | Tolerance. 118 | 119 | Returns 120 | ------- 121 | geo : numpy.ndarray, shape (n_features, n_features) 122 | Geometric mean of the matrices. 123 | """ 124 | # Shape and symmetry positive definiteness checks 125 | n_features = mats[0].shape[0] 126 | for mat in mats: 127 | check_mat(mat, 'square') 128 | if mat.shape[0] != n_features: 129 | raise ValueError("Matrices are not of the same shape.") 130 | check_mat(mat, 'spd') 131 | 132 | # Initialization 133 | mats = np.array(mats) 134 | if init is None: 135 | geo = np.mean(mats, axis=0) 136 | else: 137 | check_mat(init, 'square') 138 | if init.shape[0] != n_features: 139 | raise ValueError("Initialization has not the correct shape.") 140 | check_mat(init, 'spd') 141 | geo = init 142 | 143 | norm_old = np.inf 144 | step = 1. 145 | 146 | # Gradient descent 147 | for n in range(max_iter): 148 | # Computation of the gradient 149 | vals_geo, vecs_geo = linalg.eigh(geo) 150 | geo_inv_sqrt = map_eig(np.sqrt, 1. / vals_geo, vecs_geo) 151 | whitened_mats = [geo_inv_sqrt.dot(mat).dot(geo_inv_sqrt) 152 | for mat in mats] 153 | logs = [map_sym(np.log, w_mat) for w_mat in whitened_mats] 154 | logs_mean = np.mean(logs, axis=0) # Covariant derivative is 155 | # - geo.dot(logms_mean) 156 | if np.any(np.isnan(logs_mean)): 157 | raise FloatingPointError("Nan value after logarithm operation.") 158 | 159 | norm = np.linalg.norm(logs_mean) # Norm of the covariant derivative on 160 | # the tangent space at point geo 161 | 162 | # Update of the minimizer 163 | vals_log, vecs_log = linalg.eigh(logs_mean) 164 | geo_sqrt = map_eig(np.sqrt, vals_geo, vecs_geo) 165 | geo = geo_sqrt.dot(map_eig(np.exp, vals_log * step, vecs_log)).dot( 166 | geo_sqrt) # Move along the geodesic with step size step 167 | 168 | # Update the norm and the step size 169 | if norm < norm_old: 170 | norm_old = norm 171 | if norm > norm_old: 172 | step = step / 2. 173 | norm = norm_old 174 | if tol is not None and norm / geo.size < tol: 175 | break 176 | if tol is not None and norm / geo.size >= tol: 177 | warnings.warn("Maximum number of iterations {0} reached without " \ 178 | "getting to the requested tolerance level " \ 179 | "{1}.".format(max_iter, tol)) 180 | 181 | return geo 182 | 183 | 184 | def grad_geometric_mean(mats, init=None, max_iter=10, tol=1e-7): 185 | """Return the norm of the covariant derivative at each iteration step of 186 | geometric_mean. See its docstring for details. 187 | 188 | Norm is intrinsic norm on the tangent space of the manifold of symmetric 189 | positive definite matrices. 190 | 191 | Returns 192 | ------- 193 | grad_norm : list of float 194 | Norm of the covariant derivative in the tangent space at each step. 195 | """ 196 | mats = np.array(mats) 197 | 198 | # Initialization 199 | if init is None: 200 | geo = np.mean(mats, axis=0) 201 | else: 202 | geo = init 203 | norm_old = np.inf 204 | step = 1. 205 | grad_norm = [] 206 | for n in range(max_iter): 207 | # Computation of the gradient 208 | vals_geo, vecs_geo = linalg.eigh(geo) 209 | geo_inv_sqrt = map_eig(np.sqrt, 1. / vals_geo, vecs_geo) 210 | whitened_mats = [geo_inv_sqrt.dot(mat).dot(geo_inv_sqrt) 211 | for mat in mats] 212 | logs = [map_sym(np.log, w_mat) for w_mat in whitened_mats] 213 | logs_mean = np.mean(logs, axis=0) # Covariant derivative is 214 | # - geo.dot(logms_mean) 215 | norm = np.linalg.norm(logs_mean) # Norm of the covariant derivative on 216 | # the tangent space at point geo 217 | 218 | # Update of the minimizer 219 | vals_log, vecs_log = linalg.eigh(logs_mean) 220 | geo_sqrt = map_eig(np.sqrt, vals_geo, vecs_geo) 221 | geo = geo_sqrt.dot(map_eig(np.exp, vals_log * step, vecs_log)).dot( 222 | geo_sqrt) # Move along the geodesic with step size step 223 | 224 | # Update the norm and the step size 225 | if norm < norm_old: 226 | norm_old = norm 227 | if norm > norm_old: 228 | step = step / 2. 229 | norm = norm_old 230 | 231 | grad_norm.append(norm / geo.size) 232 | if tol is not None and norm / geo.size < tol: 233 | break 234 | 235 | return grad_norm 236 | 237 | 238 | def sym_to_vec(sym, isometry=True): 239 | """Return the flattened lower triangular part of an array. 240 | 241 | Acts on the last two dimensions of the array if not 2-dimensional. 242 | 243 | Parameters 244 | ---------- 245 | sym : numpy.ndarray, shape (..., p, p) 246 | Input array. 247 | 248 | isometry : bool, optional (default to True) 249 | If True, off diagonal terms of sym are multiplied by sqrt(2). 250 | 251 | Returns 252 | ------- 253 | output : numpy.ndarray, shape (..., p * (p + 1) / 2) 254 | The output flattened lower triangular part of sym. 255 | """ 256 | tril_mask = np.tril(np.ones(sym.shape[-2:]), -1).astype(np.bool) 257 | if isometry: 258 | sym = sym.copy() 259 | sym[..., tril_mask] *= sqrt(2) 260 | 261 | tril_mask.flat[::sym.shape[-1] + 1] = True 262 | return sym[..., tril_mask] 263 | 264 | 265 | def vec_to_sym(vec, isometry=True): 266 | """Return the symmetric array given its flattened lower triangular part. 267 | 268 | Acts on the last dimension of the array if not 1-dimensional. 269 | 270 | Parameters 271 | ---------- 272 | vec : numpy.ndarray, shape (..., p * (p + 1) /2) 273 | The input array. 274 | 275 | isometry : bool, optional (default to True) 276 | If True, off diagonal terms of the output array are divided by sqrt(2). 277 | 278 | Returns 279 | ------- 280 | sym : numpy.ndarray, shape (..., p, p) 281 | The output symmetric array. 282 | """ 283 | n = vec.shape[-1] 284 | # solve p * (p + 1) / 2 = n subj. to p > 0 285 | # p ** 2 + p - 2n = 0 & p > 0 286 | # p = - 1 / 2 + sqrt( 1 + 8 * n) / 2 287 | p = (sqrt(8 * n + 1) - 1.) / 2 288 | if p > floor(p): 289 | raise ValueError("Vector size unsuitable, can not transform vector to " 290 | "symmetric matrix.") 291 | 292 | p = int(p) 293 | mask = np.tril(np.ones((p, p))).astype(np.bool) 294 | sym = np.zeros(vec.shape[:-1] + (p, p)) 295 | sym[..., mask] = vec 296 | sym.swapaxes(-1, -2)[..., mask] = vec 297 | if isometry: 298 | mask.flat[::p + 1] = False 299 | mask = mask + mask.T 300 | sym[..., mask] /= sqrt(2) 301 | 302 | return sym 303 | 304 | 305 | def cov_to_corr(cov): 306 | """Return correlation matrix for a given covariance matrix. 307 | 308 | Parameters 309 | ---------- 310 | cov : 2D numpy.ndarray 311 | The input covariance matrix. 312 | 313 | Returns 314 | ------- 315 | corr : 2D numpy.ndarray 316 | The ouput correlation matrix. 317 | """ 318 | d = np.atleast_2d(1. / np.sqrt(np.diag(cov))) 319 | corr = cov * d * d.T 320 | return corr 321 | 322 | 323 | def prec_to_partial(prec): 324 | """Return partial correlation matrix for a given precision matrix. 325 | 326 | Parameters 327 | ---------- 328 | prec : 2D numpy.ndarray 329 | The input precision matrix. 330 | 331 | Returns 332 | ------- 333 | partial : 2D numpy.ndarray 334 | The 2D ouput partial correlation matrix. 335 | """ 336 | partial = -cov_to_corr(prec) 337 | np.fill_diagonal(partial, 1.) 338 | return partial 339 | 340 | 341 | class CovEmbedding(BaseEstimator, TransformerMixin): 342 | """Tranformer that returns the coefficients on a flat space to perform the 343 | analysis. 344 | 345 | Parameters 346 | ---------- 347 | cov_estimator : estimator object, optional 348 | The covariance estimator. 349 | 350 | kind : {"correlation", "partial correlation", "tangent", "precision"}, \ 351 | optional 352 | The connectivity measure. 353 | 354 | Attributes 355 | ---------- 356 | `cov_estimator_` : estimator object 357 | A new covariance estimator with the same parameters as cov_estimator. 358 | 359 | `mean_cov_` : numpy.ndarray 360 | The geometric mean of the covariance matrices. 361 | 362 | `whitening_` : numpy.ndarray 363 | The inverted square-rooted geometric mean of the covariance matrices. 364 | """ 365 | 366 | def __init__(self, cov_estimator=EmpiricalCovariance(assume_centered=True), 367 | kind='covariance'): 368 | self.cov_estimator = cov_estimator 369 | self.kind = kind 370 | 371 | def fit(self, X, y=None): 372 | """Fits the group sparse precision model according to the given 373 | training data and parameters. 374 | 375 | Parameters 376 | ---------- 377 | X : list of numpy.ndarray, shapes (n_samples, n_features) 378 | The input subjects. 379 | 380 | Attributes 381 | ---------- 382 | `cov_estimator_` : estimator object 383 | A new covariance estimator with the same parameters as 384 | cov_estimator. 385 | 386 | `mean_cov_` : numpy.ndarray 387 | The geometric mean of the covariance matrices. 388 | 389 | `whitening_` : numpy.ndarray 390 | The inverted square-rooted geometric mean of the covariance 391 | matrices. 392 | 393 | Returns 394 | ------- 395 | self : CovEmbedding instance 396 | The object itself. Useful for chaining operations. 397 | """ 398 | self.cov_estimator_ = clone(self.cov_estimator) 399 | 400 | if self.kind == 'tangent': 401 | covs = [self.cov_estimator_.fit(x).covariance_ for x in X] 402 | self.mean_cov_ = geometric_mean(covs, max_iter=300, tol=1e-6) 403 | self.whitening_ = map_sym(lambda x: 1. / np.sqrt(x), 404 | self.mean_cov_) 405 | 406 | return self 407 | 408 | def transform(self, X): 409 | """Apply transform to covariances. 410 | 411 | Parameters 412 | ---------- 413 | X : list of numpy.ndarray with shapes (n_samples, n_features) 414 | The input subjects. 415 | 416 | Returns 417 | ------- 418 | output : numpy.ndarray, shape (len(X), \ 419 | n_features * (n_features + 1) / 2) 420 | The transformed covariance matrices. 421 | """ 422 | covs = [self.cov_estimator_.fit(x).covariance_ for x in X] 423 | covs = np.array(covs) 424 | if self.kind == 'covariance': 425 | pass 426 | elif self.kind == 'tangent': 427 | covs = [map_sym(np.log, self.whitening_.dot(c).dot( 428 | self.whitening_)) for c in covs] 429 | elif self.kind == 'precision': 430 | covs = [map_sym(lambda x: 1. / x, g) for g in covs] 431 | elif self.kind == 'partial correlation': 432 | covs = [prec_to_partial(map_sym(lambda x: 1. / x, g)) 433 | for g in covs] 434 | elif self.kind == 'correlation': 435 | covs = [cov_to_corr(g) for g in covs] 436 | else: 437 | raise ValueError("Unknown connectivity measure.") 438 | 439 | return np.array([sym_to_vec(c) for c in covs]) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | 341 | --------------------------------------------------------------------------------